Canvas local rendering optimization summary
Introduction to the
G2 (graph engine) 4.0 and G6 (graph analysis engine) 3.4 have replaced G (2D Rendering engine) 4.0. The biggest improvement in this version is the support for partial rendering, which has a huge performance improvement in some scenes such as node state changes and individual animations of graphics. G 4.0 has experienced six months of continuous improvement from the beginning of reconstruction to the present stability, and encountered various problems. This paper will make a summary of the local rendering of Canvas, so as to give some help to the laters.
Problem analysis
As the drawing method of Canvas is paintbrush, it will be drawn on Canvas every time the API is called when drawing on Canvas, and once the drawing becomes a part of the Canvas. No objects are saved when the graph is drawn. Once the graph needs to be updated, the entire canvas needs to be cleared and redrawn.
- The impact range of a single refresh is minimal
- The refreshed graph does not affect the correct drawing of other graphs
It’s not enough just to narrow down the scope of the refresh to improve performance, as in the image on the right, if we want to refresh graph 2 and turn the graph red. At this point, if you just clear graph 2 and redraw it:
plan
When we think about the Canvas local rendering scheme, we need to see what kind of interface the Canvas API provides us. Two methods are mainly used here:
- Clip () to determine the drawing of cutting area, area outside the drawings can’t, for details see CanvasRenderingContext2D. The clip ()
- clearRect(x.y.width.height) erases the color in the specified rectangle and views itCanvasRenderingContext2D.clearRect()
Through these two apis, we can get the Canvas local refresh scheme:
- Clears the color of the specified area and sets clip
- All shapes that intersect this region are redrawn
- First determine the rectangle bounding box of the graph
- Clear the color inside the bounding box and set the region to the Clip region
- Redraw all shapes that intersect this region
- Redraw figure 3
- Redraw figure 4
Problems encountered
The actual problems encountered when implementing partial rendering in G 4.0 are much more complex than the above examples:
- G not only supports graphics rendering, but also groups, which can trigger a local refresh if the Group changes
- In addition to changes in graphics properties, reordering of graphics, adding, removing graphics, and showing and hiding graphics can also cause a refresh
- Various matrices are added to graphs and groups, and the calculation of bounding boxes of graphs is frequent and complicated
These problems were solved within 1-2 weeks, but some unexpected problems occurred during G2 and G6 access, which lasted for half a year, mainly reflected in two aspects:
- The surrounding box calculation is not accurate, resulting in the residual shadow problem
- Performance degradation due to partial refresh
The problem of residual shadows
First let’s look at the two lines on the canvas. The same 1 pixel line with color #333. What’s the difference?
- Line segment 1 (thick) : (10, 100) – (200, 100)
- Line segment 2 (fine) :(10, 149.5) – (200, 149.5)
Since the screen resolution can only draw colors on integer points, half of line 1 is drawn on (10, 99) – (200, 99) and half on (10, 101) – (200 101), so the browser will automatically expand the dots that fall on half a pixel into a single dot, and the color will fade. This becomes an example of the image below (in which the canvas is enlarged and each cell represents a pixel).
Floating-point calculation problems
Many graphics attributes are calculated automatically when we draw graphics, for example:
- Straight line (10.2, 44.3) – (20.1, 10.5)
- Circle, center (10.5, 8.8) radius 3.4
At this time, the area drawn by the graph is not consistent with the mathematical calculation, which will lead to the insufficient area emptied during the local refresh, leaving some residual shadows.
- Round down minX and minY of the surrounding box (10.2, 10.5) -> (10, 10)
- Round up the maxX and maxY of the surrounding box (20.1, 44.3) -> (21, 45)
The Angle between broken lines
Since Canvas does some processing at the intersection of line segments when it realizes a broken line, it will add extra pixels to make the broken line more beautiful. Let’s look at the difference between drawing two line segments and one broken line:
- Canvas when mapping provides a parameters: the lineJoin, can set the context. LineJoin = “bevel | round | miter”; Canvas lineJoin can be changed to bevel to get rid of sharp corners, but not as we expected.
- When calculating the polygonal bounding box, add the calculation of corner. If the included Angle of the polygonal line segment is less than 90 degrees, calculate the number of pixels exceeded. If there are many line segments, you can only calculate the number of pixels beyond the four inflection points on the left, right and bottom of the line (there is a certain risk).
The problem of shadow
The shadow can be specified when drawing a graph on the Canvas. There are four parameters related to the setting of the shadow:
shadowColor | Sets or returns the color used for the shadow |
---|---|
shadowBlur | Sets or returns the level of blur used for shadows |
shadowOffsetX | Sets or returns the horizontal distance of the shadow from the shape |
shadowOffsetY | Sets or returns the vertical distance of the shadow from the shape |
For the following two circles, if you do a partial refresh without considering shadows, the following will happen:
// Count shadow if shadow exists
if (attrs.shadowColor) {
const { shadowBlur = 0, shadowOffsetX = 0, shadowOffsetY = 0 } = attrs;
const shadowLeft = minX - shadowBlur + shadowOffsetX;
const shadowRight = maxX + shadowBlur + shadowOffsetX;
const shadowTop = minY - shadowBlur + shadowOffsetY;
const shadowBottom = maxY + shadowBlur + shadowOffsetY;
minX = Math.min(minX, shadowLeft);
maxX = Math.max(maxX, shadowRight);
minY = Math.min(minY, shadowTop);
maxY = Math.max(maxY, shadowBottom);
}
Copy the code
The arrow problem
Adding an arrow to the line is a common requirement, but since the arrow is attached to the line, the calculation of the bounding box is not included in the calculation, which results in the arrow is not cleared when refreshing. At the same time, there are many cases of the arrow, and we need to consider the customization of the arrow:
Text rendering issues
Can you see what’s going on in the text below? If you look closely, you’ll notice that a pixel is cropped to the left of the text in multiple scenarios
- Adds the off-screen CavAs to the current page file stream
- Set all font attributes in the diagram, overriding the attributes on the body
Browser zooming
After G2 4.0 and G6 3.4 were released, some users reported that there were some scratches on the lines when they were operating on the page
// Adding 0.5 pixels will solve the 1px to 2px problem, regardless of the pixelRatio value
// In the real test environment, it is found that >2 and <1 will occur between 1 and 2, but it does not occur in the case of 0.5 for safety
const appendPixel = 0.5;
if (region) {
region.minX = Math.floor(region.minX - appendPixel);
region.minY = Math.floor(region.minY - appendPixel);
region.maxX = Math.ceil(region.maxX + appendPixel);
region.maxY = Math.ceil(region.maxY + appendPixel);
}
Copy the code
Performance issues
Lots of scattered graphics refresh
If multiple graphics are refreshed at the same time, in order to reduce the calculation of bounding boxes, we will merge the bounding boxes of all refreshed graphics. However, some special circumstances will occur, resulting in the performance of local rendering decline, for example:
Graphics refresh outside window
conclusion
After half a year of transformation, the partial rendering scheme of G 4.0 has gone through the test of G2 and G6, and has improved 7-10 times in terms of interaction and performance in partial refresh scenes. However, there are still performance problems in some special scenes, which are still under continuous optimization. Render a point of improvement, the upper level will have a very harvest. AntV official website: ANTV. Vision / 2D drawing engine G: github.com/antvis/g