I have a dynamic graph with real time array values which using canvas to plot graph which is working fine, but I want over 50 unique graphs in single page with 50 different array of values.
What is the best way to achieve this? I have goggled it got to know that using multiple canvas application performance may slow, but my application is for desktops not for mobiles.
-- Edit--
I did some thing like this to get my requirement..
http://jsfiddle.net/atluriajith/v4Rhv/
graphs are plotting properly upto 100, after that the speed of the graphs are getting slow. Is this the right way what i did?
-- Edit--
You can use a single canvas that is big enough to hold the graphs.
Then you can use translate and clip to draw the graphs into isolated areas on that canvas (aka. "virtual canvases"). Depending on how you intend to draw the graphs even this may not be necessary.
This only needs a single clear before you redraw the graphs.
In this case you would probably organize it into 5 x 10 cells.
For example (disclaimer: not tested, meant for example):
var canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d'),
cols = 5,
rows = 10,
cellWidth = 200,
cellHeight = 100;
canvas.width = cols * cellWidth;
canvas.height = rows * cellHeight;
document.body.appendChild(canvas);
...
/// draw a graph:
var x = cellX * cellWidth, /// cell indexes to position
y = cellY * cellHeight;
ctx.save();
ctx.rect(x, y, cellWidth, cellHeight);
ctx.clip();
/// draw graph here
ctx.restore();
If you can contain the graph within x, y, and cellWidth/cellHeight the clipping is not necessary.
Before you continue I would think it worthwhile to verify that you have performance issues.
"Premature optimization is the root of all evil"
One fairly straight forward solution is simply to reduce the number of canvasses you're rendering. Make your UI such that you never render more canvasses than you can reasonable support with a given performance. No one needs to see 50 graphs at once, that is simply too much information.
Related
I have two separate datasets in Chart.JS, one represents a selection of historical data, the other the predictions based on that data. Here is what the chart currently looks like. Historical data is black, prediction is blue:
I'd like to maintain the two as separate datasets, but connect them so the chart displays as a single line. In a previous version I accomplished this by adding a datapoint to the prediction dataset that is an exact duplicate of the final datapoint in the historical set, but this creates an inaccurate redundancy I want to avoid.
Unless there's some chart.js setting I've been unable to find I'm worried I might have to register a plugin to do this, which seems like it would become needlessly clunk. Thanks.
Managed to solve the problem, posting to help anyone with a similar problem.
I ended up creating a chart.js plugin that draws a line finds the X and Y coordinates of the last historical point and then draws a line between that and the X and Y coordinate of the first prediction point. After beating my head against it for a while I can't believe it was so simple.
I'm posting the code down below - this is a react application so it may not fit cleanly into every use case. The JSFiddle isn't setup to work, but you can see how to grab the point data which was the hardest part for me!
//Grab the chart meta data and identify the x and y coordinates of the
appropriate points
var meta = chartInstance.getDatasetMeta(0);
var predictionMeta = chartInstance.getDatasetMeta(1);
var chartFirstPointX = meta.data[this.props.actualData.length - 1]._model.x;
var lastActualY = meta.data[this.props.actualData.length - 1]._model.y;
var predictionFirstPointX = predictionMeta.data[0]._model.x;
var predictionFirstPointY = predictionMeta.data[0]._model.y;
Chart.pluginService.register({
id: 'forecastLine',
beforeDraw: function(chartInstance) {
var ctx = chartInstance.chart.ctx,
yTop = chartInstance.chartArea.top,
yBottom = chartInstance.chartArea.bottom,
firstPredictionX = predictionFirstPointX,
firstPredictionY = chartInstance.config.data.datasets[1].data[0].y;
ctx.fillStyle = '#FAFAFA';
ctx.fillRect(0, 0, chartInstance.chart.width, chartInstance.chart.height);
//draw a vertical line separating the two datasets, then draw a line
connecting them
ctx.save();
ctx.beginPath();
ctx.moveTo(chartFirstPointX, yBottom);
ctx.lineTo(chartFirstPointX, yTop);
ctx.moveTo(chartFirstPointX, lastActualY);
ctx.lineTo(predictionFirstPointX, predictionFirstPointY);
ctx.lineWidth = 2;
ctx.strokeStyle = '#68D1FE';
ctx.stroke();
ctx.restore();
I am wondering how I could alter my Javascript to only clear the falling sprites, and not the entire canvas (as it does currently).
I hope to place multiple other (animated) sprites on the canvas, which do not appear with the way my function animate is structured.
Is there a way so that if there was another image/sprite was on the canvas, it would not be affected by the function animate.
I'm thinking that this line needs to change:
ctx.clearRect(0, 0, canvas.width, canvas.height);
Though I have no idea what parameters I would need to place inside.
The falling sprites draw at a size of 60x60, but as they fall downwards this is where I am a bit stuck with clearing the only the sprite path.
Any help would be appreciated :)
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d");
canvas.width = 1408;
canvas.height = 640;
canvasWidth = canvas.width;
canvasHeight = canvas.height;
var orangeEnemy = new Image();
orangeEnemy.src = "http://www.catholicsun.org/wp-content/uploads/2016/09/cropped-sun-favicon-512x512-270x270.png";
var yellowEnemy = new Image();
yellowEnemy.src = "http://www.clker.com/cliparts/o/S/R/S/h/9/transparent-red-circle-hi.png";
var srcX;
var srcY;
var enemySpeed = 2.75;
var images = [orangeEnemy, yellowEnemy];
var spawnLineY=-50;
var spawnRate=2500;
var spawnRateOfDescent=1.50;
var lastSpawn=-1;
var objects=[];
var startTime=Date.now();
animate();
function spawnRandomObject() {
var object = {
x: Math.random() * (canvas.width - 15),
y: spawnLineY,
image: images[Math.floor(Math.random() * images.length)]
}
objects.push(object);
}
function animate(){
var time=Date.now();
if(time>(lastSpawn+spawnRate)){
lastSpawn=time;
spawnRandomObject();
}
requestAnimationFrame(animate);
ctx.clearRect(0, 0, canvas.width, canvas.height);
// move each object down the canvas
for(var i=0;i<objects.length;i++){
var object=objects[i];
object.y += enemySpeed;
ctx.drawImage(object.image, object.x, object.y, 60, 60);
}
}
<html>
<canvas id="canvas" style="border:3px solid"></canvas>
</html>
The easiest and quickest way would be to overlay another canvas, specifically for your sprites, atop your current canvas (requires a bit of CSS). Put all your sprites in one, everything else in the other. The clearRect() in your animate() function will then only apply to your sprite canvas, and not the other.
Otherwise, you will have to keep track of the positions of the sprites, and clear each programatically with 60x60 rectangles using clearRect(offsetX, offsetY, 60, 60).
P.S. excuse the non-formatted answer... still figuring SO out
Clear once for performance.
You are much better off clearing the whole canvas and redrawing the sprites. Using the previous position, checking for overlap and then clearing each sprite in turn, making sure you don't clear an existing sprite will take many more CPU cycles than clearing the screen once.
The clear screen function is very fast and is done in hardware, the following is the results of a performance test on Firefox (currently the quickest renderer) of clearing 65K pixels using just one call for whole area then 4 calls each a quarter, then 16 calls each clearing a 16th. (µs is 1/1,000,000th second)
Each test clears 256*256 pixels Each sample is 100 tests
'Clear 1/1' Mean time: 213µs ±4µs 1396 samples
'Clear 4/4' Mean time: 1235µs ±14µs 1390 samples
'Clear 16/16' Mean time: 4507µs ±42µs 1405 samples
As you can see clearing 65K pixels is best done in one call with the actual javascript call adding about 100µs to do.
On Firefox the number of pixels to clear does not affect the execution time of the call to clearRect with a call clearRect(0,0,256,256) and clearRect(0,0,16,16) both taking ~2µs
Apart from the speed, trying to clear overlapping animated sprites per sprite becomes extremely complicated and can result in 100s of clear calls with only a dozen sprites. This is due to the overlapping.
So i have done an webpage that tests rendering times times for SVG and Canvas. Test were done for diffrent browsers. I tought that Canvas will be better then SVG but from my test i see that Canvas has a problem with bigger objects and circle objects. I have results of my test here:
http://lezag2.linuxpl.info/wykresT2.html - this are reslts for 50.000 rectangles with a=500 pixsels
http://lezag2.linuxpl.info/wykresT4.html - this are results for 50.000 circles with r=250 pixels
I use simple code to generate objects on page.
Canvas:
var ctx = document.getElementById('canvas').getContext('2d');
for(var i=0;i<50000;i++){
ctx.beginPath();
var centreX = Math.random() * 1000;
var centreY = Math.random() * 1000;
var radius = 50;
var startAngle = 0 * Math.PI/180;
var endAngle = 360 * Math.PI/180;
ctx.arc(centreX,centreY,radius,startAngle,endAngle,false);
ctx.fillStyle = "rgb("+Math.floor(Math.random()*256)+","+ Math.floor(Math.random()*256)+","+ Math.floor(Math.random()*256)+")";;
ctx.fill();
ctx.closePath();
}
And SVG:
for (var i = 0; i < 50000; i++) {
var x = Math.random() * 1000,
y = Math.random() * 1000;
var circ = document.createElementNS(svgns, 'circle');
circ.setAttributeNS(null, 'cx', x);
circ.setAttributeNS(null, 'cy', y);
circ.setAttributeNS(null, 'r', 50);
circ.setAttributeNS(null, 'fill', '#'+Math.round(0xffffff * Math.random()).toString(16));
document.getElementById('svgOne').appendChild(circ);
}
I am wondering why Canvas has such bad times compared to SVG. I tryd to google my problem but found only very comlpex tests. Could some one explain me why canvas has such bad times?? Also do i name it good - i mean rendering times??
EDIT
I forgot to show how i mesure rendering time.
befTime = (new Date()).getTime();
{
(drawing function)
}
var actTime = (new Date()).getTime();
var testTime = (actTime-befTime)/1000;
Thats why i asked if i dont name it wrong by saying "rendering time"
You cannot infer from your tests that svg is faster or slower than canvas, for several reasons :
minor reasons :
closePath is not necessary, especially after filling.
you recompute start/end angle on each loop for canvas.
fillStyle has to be converted 'rgb(...)' in canvas and not in svg.
more important reason :
drawing 50.000 canvas of 500 radius on a 1000X1000 canvas leads to a per-pixel overdraw
of... 78500 !!! This has just NOTHING to do with a real-use case, which is very annoying
to get any conclusion out of it.
even more important reason :
you do not draw in synch (using requestAnimationFrame), so it is certain that your canvas code is often waiting for its buffer to be able to draw.
killing reason :
You do not measure the render time of svg, just the svg creation + add to DOM time.
Not a single pixel is drawn in your svg loop : the actual render will be performed AFTER the javascript code returns : only then it will see that a reflow is necessary, and repaint everything.
AFAIK every browsers have only one thread for all operations going on a page : so your program will fully stop for rendering, during a time that you do not measure as of now.
Rq : You could try to measure svg draw time by using a short setInterval and seeing how many times really elapsed in between two calls instead of the real interval : this is the time when the system was stuck rendering.
Bottom line : you are comparing the time to create DOM objects and add them to the document vs the time to render circles on a out-of-sync canvas. No conclusion can be drawn out of those figures.
)
I have a small web-application which uses jquery and jcanvas (http://calebevans.me/projects/jcanvas/) to print some shapes onto a canvas.
It's basically a map. The user can zoom into it and drag it around and whenever he does so everything has to be drawn again.
As you may see in the code below I remove and recreate layers pretty often (whenever the user drags the map, zooms or resizes his window). I need the layers to handle hover- and click-events. My question is whether there is a big performance impact of this way to handle events in comparison to other solutions. If this is the case, how could I optimize my performance?
var posX = 0, posY = 0;
var zoom = 100;
var points = []; //array of up to 1000 points retrieved by ajax
function draw(){
$("canvas").removeLayers();
$("canvas").clearCanvas();
var xp, yp, ra;
var name;
$.each(points, function(index) {
xp = (this["x"]-posX)/zoom;
yp = (this["y"]-posY)/zoom;
ra = 1000/zoom;
$("#map").drawArc({
layer:true,
fillStyle: "black",
x: xp,
y: yp,
radius: ra,
mouseover: function(layer) {
$(this).animateLayer(layer, {
fillStyle: "#c33",
scale: 1.0
},200);
},
mouseout: function(layer) {
$(this).animateLayer(layer, {
fillStyle: "black",
scale: 1.0
},200);
},
mouseup: function(layer){
context(index,layer.x,layer.y);
}
});
});
}
Thank you for your time :-)
Optimization is in general a case-to-case thing but I'll try to give some general points for this case:
Keep operations to a minimum
Creating and removing "layers" (canvas elements) are costly operations - try to avoid if possible.
Rather move content around using drawImage() on itself or use clearRect() if you need to clear the whole canvas.
The same with a little more details:
You can for example use drawImage() to move the content to one of the sides, if we wanted to move everything to the left by 10 pixels we could do:
context.drawImage(canvas, 10, 0, canvas.width - 10, canvas.height,
0, 0, canvas.width - 10, canvas.height);
This will clip only the part we want to move/scroll and then redraw it in the new position.
Finally draw in the 10px gap at right with new graphics (you would probably want to cache the width and height but frankly, in the modern JavaScript engines this does not matter so much anymore as it did in the past). Test your points to see what points actually needs to be drawn to avoid drawing all of them again which otherwise renders this step pointless.
If you want to clear the canvas, rather than removing the canvas element and create a new canvas use the method clearRect() or use fillRect() if you want a pattern, color etc. Removing and creating elements are a costly operation from a optimization perspective especially if they affect the browser's flow (if it need to reflow the content). It triggers a bunch of operations from layout, css parsing, repaint etc. Reuse if you can.
I'm currently implementing a 2d deformable terrain effect in a game I'm working on and its going alright but I can envision it becoming a performance hog very fast as I start to add more layers to the effect.
Now what I'm looking for is a way to possibly save a path, or clipping mask or similar instead of having to store each point of the path in the terrain that i need to draw through each frame. And as I add more layers I will have to iterate over the path more and more which could contain thousands of points.
Some very simple code to demonstrate what I'm currently doing
for (var i = 0; i < aMousePoints.length; i++)
{
cRenderContext.save();
cRenderContext.beginPath();
var cMousePoint = aMousePoints[i];
cRenderContext.arc(cMousePoint.x, cMousePoint.y, 30, 0, 2 * Math.PI, false);
cRenderContext.clip();
cRenderContext.drawImage(cImg, 0, 0);
cRenderContext.closePath();
cRenderContext.restore();
}
Basically I'm after an effecient way to draw my clipping mask for my image over and over each frame
Notice how your clipping region stays exactly the same except for its x/y location. This is a big plus.
The clipping region is one of the things that is saved and restored with context.save() and context.restore() so it is possible to save it that way (in other words defining it only once). When you want to place it, you will use ctx.translate() instead of arc's x,y.
But it is probably more efficient to do it a second way:
Have an in-memory canvas (never added to the DOM or shown on the page) that is solely for containing the clipping region and is the size of the clipping region
Apply the clipping region to this in-memory canvas, and then draw the image onto this canvas.
Then use drawImage with the in-memory canvas onto your game context. In other words: cRenderContext.drawImage(in-memory-canvas, x, y); where x and y are the appropriate location.
So this way the clipping region always stays in the same place and is only ever drawn once. The image is moved on the clipping-canvas and then drawn to look correct, and then the in-memory canvas is drawn to your main canvas. It should be much faster that way, as calls to drawImage are far faster than creating and drawing paths.
As a separate performance consideration, don't call save and restore unless you have to. They do take time and they are unnecessary in your loop above.
If your code is open-source, let me know and I'll take a look at it performance-wise in general if you want.
Why not have one canvas for the foreground and one canvas for the background? Like the following demo
Foreground/Background Demo (I may of went a little overboard making the demo :? I love messing with JS/canvas.
But basically the foreground canvas is transparent besides the content, so it acts like a mask over the background canvas.
It looks like it is now possible with a new path2D object.
The new Path2D API (available from Firefox 31+) lets you store paths, which simplifies your canvas drawing code and makes it run faster. The constructor provides three ways to create a Path2D object:
new Path2D(); // empty path object
new Path2D(path); // copy from another path
new Path2D(d); // path from from SVG path data
The third version, which takes SVG path data to construct, is especially handy. You can now re-use your SVG paths to draw the same shapes directly on a canvas as well:
var p = new Path2D("M10 10 h 80 v 80 h -80 Z");
Information is taken from Mozilla official site.