0

I was working against a financial library that requires me to provide realtime updates to a line chart in canvas. To optimize the process of updating the chart, I thought of just updated the latest data-point rather than clearing and re-drawing the entire canvas.

When re-rendering only the latest datapoint frequently, I'm noticing that the line is not clear(there's a spot in the image).

Here's how the line looks initially(no redraw)

Inital

And after a few updates of calling "partial_rerender", this is how the line looks: Redraw

Notice the "joining" of the 2 lines is visible with a darker shade.

Is there a way to achieve partial re-drawing of lines only for the latest data point & not drawing the entire line completely?

Reference code

var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.lineWidth = 2;
ctx.lineJoin = "miter";
ctx.moveTo(20, 50);
ctx.lineTo(100, 50);
ctx.save();
ctx.lineTo(150, 60);
ctx.stroke();

/*Call this every second to re-draw only the latest data point*/
function partial_rerender(){
ctx.clearRect(100,50, 400,400);
ctx.restore();
ctx.lineTo(150, 60);
ctx.stroke();
}
Akash
  • 4,956
  • 11
  • 42
  • 70
  • 1
    On top of clearRect (which is a good idea) you should also use [clip](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/clip). After that line endings will not "leak". – tevemadar Jun 30 '17 at 12:17
  • You are not calling `beginPath` in `partial_rerender`... `ctx.save` saves **all** the properties of your context, but **not** the current state of your path declaration. (i.e it's an heavy call for nothing here). For such a case, simply redraw everything ; for really complicated and heavy to draw shapes, store it in an offscreen canvas, and at every frame clear all, draw the offscreen canvas, beginPath(), moveTo(theLastPoint), partial_rerender(). But by all means, remove these save and restore... – Kaiido Jun 30 '17 at 12:44
  • Possible duplicate of [Why does omitting beginPath() redraw everything?](https://stackoverflow.com/questions/21869609/why-does-omitting-beginpath-redraw-everything) – Kaiido Jun 30 '17 at 12:45

2 Answers2

1

You need to create a new path each time you render or you end up re- rendering the same content over and over.

ctx.save() and ctx.restore() push and pop from a stack, for every restore you need to have a matching save. ctx.save(), ctx.restore(), ctx.restore()the second restore does nothing as there is no matching save.

var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.lineWidth = 2;
ctx.lineJoin = "miter";
ctx.moveTo(20, 50);
ctx.lineTo(100, 50);
ctx.save();
ctx.lineTo(150, 60);
ctx.stroke();

// Your function should look more like this
function partial_rerender(){
   ctx.lineWidth = 2;
   ctx.lineJoin = "miter";
   ctx.save();  // needed to remove clip
   ctx.beginPath(); // removes old path ready to create a new one
   ctx.rect(100,50, 400,400); // create a clip area
   ctx.clip(); // activate the clip area
   ctx.clearRect(100,50, 400,400);
   ctx.beginPath(); // needed to draw the new path 
   ctx.moveTo(100,50)
   ctx.lineTo(150, 60);
   ctx.stroke();
   ctx.restore(); // remove the clip area
}
Blindman67
  • 51,134
  • 11
  • 73
  • 136
  • 1
    Why a clip here ? – Kaiido Jun 30 '17 at 13:03
  • 1
    @Kaiido To stop the edge of the line messing with existing pixels. The line from 100,50 to 100,60 with a width of 2 (or 1 or less) will render to pixels at (99,50),(99,49),(100,49) – Blindman67 Jun 30 '17 at 13:42
  • @Blindman67: If the line width is 10ps, it would render the pixels 89? (always 1 less than the overall location)? – Akash Jun 30 '17 at 14:27
  • Also can you explain why does this happen(shouldn't the pixel's only start/end at the actual points)? – Akash Jun 30 '17 at 14:38
  • 1
    @Akash A line has width, as set by `ctx.lineWidth = ?` thus if you render at 100,50 (the top left corner of a pixel) half the line will spill into the adjacent pixels. Also depending on the `ctx.lineCap` https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineCap setting the line will not start at that point but be up to `sqrt(lineWidth *lineWidth)` pixels away from the start point. – Blindman67 Jun 30 '17 at 14:50
  • @Blindman67 So the clip() ensures that we start re-drawing the line starting in the given rect(100,50, 400,400) ensuring no pixel's go outside it? – Akash Jun 30 '17 at 14:51
0

When you draw onto the canvas you do override the necessary pixels. But the rest stays the same. What you are trying to achieve is not possible. You have to clear the canvas (canvas.clear()) and then redraw all elements to remove these artifacts from previous draw calls and to achieve your desired result.