I am trying to get a better performance on my JavaScript-function. I draw with myPopulation=50.000 dots on a canvas and it takes apros 230ms. As I have this in another loop with a frequence of 100ms, I get a delay due to the below function.
function drawThis(intervals) {
ctx.clearRect(0, 0, myWidth, myHeight);
ctx.beginPath();
for (var i = 0; i < myPopulation; i++) {
ctx.arc(persons[i].posX, persons[i].posY, 2, 0, 2 * Math.PI);
if (persons[i].infected) {
ctx.fillStyle = "#FF0000";
}
else {
ctx.fillStyle = "#000000";
}
if (i < myPopulation - 1) {
ctx.moveTo(persons[i + 1].posX, persons[i + 1].posY);
}
}
ctx.fill();
My idea was to divide myPopulation in equal intervals with a helper function like
var myIntervals = divideRangeInNParts(0, myPopulation, intervals);
and to them parallel. So the pseudo-code would be:
divide myPopulation in equal parts
drawThis(interval[0][0], interval[0][1]);
drawThis(interval[1][0], interval[1][1]);
drawThis(interval[2][0], interval[2][1]);
drawThis(interval[3][0], interval[3][1]);
[...]
wait for all of them to finish
continue
I have read a lot about JavaScript as a single-threaded language, Promises, Web Workders etc., but I could not find any solution which suits my problem.
Any idea or hint?
I am new here and if there is any problem with my question, please tell me also.
Thanks in advance
Related
I made a project called "pixel paint" by javascript with p5js library, but when I run it, that project ran too slow. I don't know why and how to make it run faster. And here is my code:
let h = 40, w = 64;
let checkbox;
let scl = 10;
let painting = new Array(h);
let brush = [0, 0, 0];
for(let i = 0; i < h; i++) {
painting[i] = new Array(w);
for(let j = 0; j < w; j++) {
painting[i][j] = [255, 255, 255];
}
}
function setup() {
createCanvas(w * scl, h * scl);
checkbox = createCheckbox('Show gird line', true);
checkbox.changed(onChange);
}
function draw() {
background(220);
for(let y = 0; y < h; y++) {
for(let x = 0; x < w; x++) {
fill(painting[y][x]);
rect(x * scl, y * scl, scl, scl);
}
}
if(mouseIsPressed) {
paint();
}
}
function onChange() {
if (checkbox.checked()) {
stroke(0);
} else {
noStroke();
}
}
function paint() {
if(mouseX < w * scl && mouseY < h * scl) {
let x = floor(mouseX / scl);
let y = floor(mouseY / scl);
painting[y][x] = brush;
}
}
<!--Include-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.min.js"></script>
Is there a solution to make my project run faster?
The code you have is easy to read.
It might not be worth optimising at this stage as it would make the code potentially needlessly more complex/harder to read and change in the future.
If you want to learn about different ways you could achieve the same thing I can provide a few ideas, though, for your particular use case int terms of performance might won't make a huge difference:
Instead of using the painting as a nested [w][h] array you could use a flat [w * h] array and use a single for loop instead of a nested for loop. This would be somewhat similar to using pixels[]. (You can convert x,y to an index (index = x + (y * width)) and the other way around(x = index % width, y = floor(index / width))
You could in theory use a p5.Image, access pixels[] to draw into it and render using image() (ideally you'd get lower level access to the WebGL renderer to enable antialiasing if it's supported by the browser). The grid itself could be a texture() for a quad where you'd use vertex() to specify not only x,y geometry positions, but also u, v texture coordinates in tandem with textureWrap(REPEAT). (I posted an older repeat Processing example: the logic is the same and syntax is almost identical)
Similar to the p5.Image idea, you can cache the drawing using createGraphics(): e.g. only update the p5.Graphics instance when the mouse is dragged, otherwise render the cached drawing. Additionally you can make use of noLoop()/loop() to control when p5's canvas gets updated (e.g. loop() on mousePressed(), updated graphics on mouseMoved(), noLoop() on mouseReleased())
There are probably other methods too.
While it's good to be aware of techniques to optimise your code,
I strongly recommend not optimising until you need to; and when you do
use a profiler (DevTools has that) to focus only the bits are the slowest
and not waste time and code readability on part of code where optimisation
wouldn't really make an impact.
This question already has answers here:
Get canvas width and height using javascript
(2 answers)
Closed 1 year ago.
I saw an interesting creative maths plotting script using the modulo of the result of powers and wanted to experiment with it, changing the modulo iteratively.
Here we're drawing a 256x256 pattern and then after an interval changing the modulo value and redrawing (for simplicity just toggling the modulo between two values in an attempt to debug the problem).
It only draws two iterations before apparently the Canvas stops updating and we get stuck on the second pattern. I thought context.clearRect() might solve it but it makes no difference. The function keeps running because I get the console.debug() output but nothing updates visually.
I have the same result in both Chrome and Safari
What am I doing wrong?
https://jsbin.com/zacoperuho/edit?html,console,output
<canvas id="container"></canvas>
<script>
container.width = 1024;
container.height = 1024;
const context = container.getContext("2d");
var modulo = 9;
function draw(){
context.clearRect(0, 0, context.width, context.height);
console.debug("modulo " + modulo)
for (let x = 0; x < 256; x++) {
for (let y = 0; y < 256; y++) {
if ((x ^ y) % modulo) {
context.fillRect(x*4, y*4, 4, 4);
}
}
}
if(modulo == 9){
modulo = 7;
} else {
modulo = 9;
}
}
draw();
setInterval(draw, 5000);
</script>
You're right, you need to clear the canvas's context and context.clearRect is the regular way to do it.
The problem are the parameters you're feeding it: context.width, context.height
The constantcontext is set to getContext("2d") thus it's an instance of
CanvasRenderingContext2D. This interface doesn't offer any properties called width or height. Instead you need to query the width & height on the Canvas element itself.
So simply change
context.clearRect(0, 0, context.width, context.height);
to
context.clearRect(0, 0, container.width, container.height);
Extremely new to javascript, and this is my first project! I am building a line graph utilizing Html Canvas and Javascript. I have a function that generates random numbers for X and Y coordinates, which then fills an empty array. I want to plot points and connect them as the Array fills. The end goal is to build a line graph that scales based on the points. I know the code is a little messy (I apologize), and there are other issues, but the problem I am focusing on right now is when the code runs it plots point A, then A B, then A B C, then A B C D, etc. I would like it to plot the points progressively, so it is point A then point B then point C, etc line by line. Hope this makes sense!
From what I have seen from others, it looks like the best way to do this is to reference the previous point in the Array and make sure the line to is from that previous point. I thought that's what I was doing here or at least attempting to do.
// Return the x pixel for a graph point
function getXPixel(val) {
return ((canvas.width - xMarg) / plotArray.length) * val + (xMarg);
}
// Return the y pixel for a graph point
function getYPixel(val) {
return canvas.height - (((canvas.height - yMarg) / getMaxY()) * val) - yMarg;
}
function plotPoints() {
ctx.strokeStyle = '#f00';
ctx.beginPath();
ctx.moveTo(getXPixel(0), getYPixel(plotArray[0].Y));
for (var i = 1; i < plotArray.length; i++) {
ctx.lineTo(getXPixel(i), getYPixel(plotArray[i].Y));
}
ctx.stroke();
label();
drawCircle();
}
function drawCircle() {
ctx.fillStyle = '#333';
for (var i = 0; i < plotArray.length; i++) {
ctx.beginPath();
ctx.arc(getXPixel(i), getYPixel(plotArray[i].Y), 4, 0, Math.PI * 2, true);
ctx.fill();
}
}
function setPlotHistory(){
var plotHistory = plotArray.length
plotArray[plotHistory.res] = {};
plotArray[plotHistory.moveToX] = plotArray.X;
plotArray[plotHistory.moveToY] = plotArray.Y;
}
runTest = setInterval(function() {
var res = { //Create object of results with each test
X: testsRun,
Y: getRandomNumber(10,150)
};
if (plotArray.length === 5) {
plotArray.shift();
}
plotArray.push(res); //put the result in the array
setPlotHistory(res);
document.getElementById('output').innerHTML = "Tests Run: " + testsRun;
testsRun++; //up the number of tests by one
plotPoints();
},testInt);
Not sure, how much info I should provide and did not want to fill the page up with the entire code, so for reference you can see my full code here https://jsfiddle.net/Crashwin/72vd1osL/3/
Any help is appreciated!
If I'm understanding the desired results correctly... You should clear the canvas and redraw with each call. This will require redrawing the axis. You may want to do an initial draw to setup the graph before the first timer is fired.
Modified plotPoints function:
//Draw the line graph
function plotPoints() {
// clear the canvas for each draw.
ctx.clearRect(0,0,canvas.width,canvas.height);
// put the axis back.
drawAxis();
// draw all points and lines.
ctx.strokeStyle = '#f00';
ctx.beginPath();
ctx.moveTo(getXPixel(0), getYPixel(plotArray[0].Y));
for (var i = 1; i < plotArray.length; i++) {
ctx.lineTo(getXPixel(i), getYPixel(plotArray[i].Y));
}
ctx.stroke();
label();
drawCircle();
}
You should leave the plotArray as is (don't delete points) so it becomes your plot hisotry. No need for setPlotHistory().
Also in the drawAxis function you should set a strokeStyle otherwise it will be in the style defined in plotPoints i.e. #f00.
Modified drawAxis function:
function drawAxis() {
// set axis stroke style
ctx.strokeStyle = "#333";
ctx.beginPath(); //new line
ctx.moveTo(xMarg, 10); //move to (40, 10)
ctx.lineTo(xMarg, canvas.height - yMarg); // line to (40, height - 40)
ctx.lineTo(canvas.width, canvas.height - yMarg); // line to (width, height - 40)
ctx.stroke(); //draw lines
}
Working example over here: https://jsfiddle.net/8yw5mouz/1/
Ps. This is a pretty nice bit of code. I like how it scales.
I am writing a vizualization of neural network, and I would like to redraw it on each training iteration, so I have next button with onclick callback:
startButton.onclick = () => {
for (let i = 0; i < trainData.length; i++) {
setTimeout(() => {
network.trainSample(trainData[i])
ctx.clearRect(0, 0, width, height)
drawNN(network)
}, 0)
}
}
The problem is, if I take off setTimeout, it will execute all the training, and redraw everything in the end.
As far as I know, there is an event loop and what setTimeout trick does, it creates a Job in a event queue that will be executed not exactly now, but as soon as possible.
Okay, but if canvas drawing is asynchronous and drawing get's postponed till the end, why it's api is synchronous?
Minimal example:
const canv = document.getElementById('myCanv')
const ctx = canv.getContext('2d')
ctx.strokeStyle = '#000000'
for (let x = 0; x <= 100; x++) {
ctx.clearRect(0, 0, 100, 100)
ctx.beginPath()
ctx.moveTo(0, 0)
ctx.lineTo(x, 100)
ctx.stroke()
}
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<canvas width="100" height="100" id="myCanv"></canvas>
</body>
</html>
Your usage is incorrect and even if canvas drawing was synchronous you most likely would see only the last frame anyway with some way too fast weird animation in between. What you need is instead of standard loop use some sort of animation loop. For example:
let i = 0;
function animationLoop() {
if (i < trainData.length) {
network.trainSample(trainData[i]);
ctx.clearRect(0, 0, width, height);
drawNN(network);
i++;
requestAnimationFrame(animationLoop);
}
}
requestAnimationFrame(animationLoop);
Here I am using requestAnimationFrame which would result in around 60 frames per second. My guess for your case this might still be too fast. You can limit frames per second using additional setTimeout inside animateLoop function.
I am using javascript with p5.js framework. I created an offscreen graphics buffer. Now I want to clear part of that buffer (so it becomes invisible again). What is the best way to do it?
Right now I can only achieve that by direct changing of the alpha value for every pixel I need.
Here is a MCVE:
// sketch.js, requires p5.js
function setup() {
createCanvas(100,100);
background(0);
let mymap = createGraphics(100,100);
mymap.fill(255);
mymap.rect(20,20,40,40);
mymap.loadPixels();
for (let i = 23; i < 37; i++) {
for (let j = 23; j < 37; j++) {
mymap.pixels[400*i+4*j+3] = 0;
}
}
mymap.updatePixels();
image(mymap,0,0);
}
Some side-notes:
1) there is a tiledmap.js library to p5.js, but I am still reading into it's source code and right now it doesn't look like they got 1 buffer for the entire tilemap.
2) there is a clear function to clear the canvas graphics, it does clear things, but it clears everything on the given buffer.
You should be able to use the set() function. More info can be found in the reference.
I would expect something like this to work:
const transparency = createGraphics(20, 20);
mymap.set(30, 30, transparency);
mymap.updatePixels();
Unfortunately, this seems to only set a single pixel. This smells like a bug to me, so I've filed this bug on GitHub.
Like you've discovered, you can also set the pixel values directly. This might be simpler than what you currently have:
let mymap;
function setup() {
createCanvas(100,100);
mymap = createGraphics(100,100);
mymap.fill(255, 0, 0);
mymap.rect(20,20,40,40);
for(let y = 30; y < 50; y++){
for(let x = 30; x < 50; x++){
mymap.set(x, y, color(0, 0, 0, 0));
}
}
mymap.updatePixels();
}
function draw() {
background(0, 255, 0);
image(mymap,0,0);
}
Note that there's a tradeoff: this code is simpler, but it's also slower.