3 5rollering units d3 5l11 8 d3 5l13 8 d3 5l15 8 d3 5l17 8 d3 5l19 8 d3 5l21 8 d3 5l23 8 d3 5l25 8 d
Improving D3 Performance with CANVAS and other Hacks
-
Upload
philip-tellis -
Category
Technology
-
view
1.413 -
download
0
Transcript of Improving D3 Performance with CANVAS and other Hacks
I M P R O V I N G D 3 P E R F O R M A N C E W I T H C A N VA S A N D O T H E R H A C K S
WebTechCon 2015 2015-10-27
Philip Tellis @bluesmoon
https://github.com/lognormal/boomerang
http://www.soasta.com/mpulse/
D 3 I S …
• A JavaScript library that maps Data to DOM Nodes
• Extended via layouts & plugins for rich data visualisations
• You still need to write code to draw things
• Fast on its own, but you can easily make it sluggish
• BSD Licensed — http://d3js.org/
H T T P S : / / G I T H U B . C O M / M B O S T O C K / D 3 /W I K I / G A L L E R Y
G E T S TA R T E D W I T H D 3
B A S I C D 3 T U T O R I A L
• Adding nodes
• Mapping data to nodes
• Data Driven Documents
• Examples 01-06 at http://soasta.github.io/improving-d3-performance/d3/
A N I M AT E D D 3 C H A R T S
• Force Directed Layout
• Reacting to user interaction
• Reacting to changing data
F O R C E D I R E C T E D L AY O U TU S I N G P H Y S I C S
F O R C E D I R E C T E D L AY O U T
• http://bl.ocks.org/mbostock/4062045
• Took basic Force Directed Layout and added enhancements:
• Convex hulls
• Labels
• Mouseovers
• Variable sized points
F O R C E D I R E C T E D L AY O U T — P R O B L E M S
• Rendering is O(n) based on number of SVG nodes
• Calculations are O(n2) based on number of links
• Drawing the hull is expensive as nodes move around a lot
F O R C E D I R E C T E D L AY O U T — S O L U T I O N S
• Reduce number of links by using a Minimum Spanning Tree
• Identify clusters and only link one node from each cluster
• Visually reduce nodes within the cluster using approximation
• Add decorators later as the animation stabilizes
F O R C E D I R E C T E D L AY O U T — A D D I T I O N A L O P T I O N S
• We could use CANVAS to get rid of SVG nodes
• Create subgroups within each group to further reduce links
• Get rid of some of our visual enhancements
E D G E B U N D L E D L AY O U TU S E R I N T E R A C T I O N
E D G E B U N D L E D L AY O U T
• http://bl.ocks.org/mbostock/7607999
• We added many more links
• Links colored based on performance
• Links sized based on volume of data flow
• Mouseover should highlight connected links
• Nodes sized based on volume within that node
E D G E B U N D L E D L AY O U T — P R O B L E M S
• Default behaviour is to iterate through every link on mouseover and change colour — this is slooooow!
• Using CSS class to change colour causes major re-render
• Quickly cycling through nodes has noticeable lag
E D G E B U N D L E D L AY O U T — S O L U T I O N S
• First we tried to not iterate, just use CSS class cascades
• This was a trade off because we ended up adding a large number of classes, two per node and one per link
E D G E B U N D L E D L AY O U T — S O L U T I O N S
• The second attempt was to use CANVAS to draw everything
• The problem here is that CANVAS is not great for text, and mouseovers no longer worked
E D G E B U N D L E D L AY O U T — S O L U T I O N S
• The third attempt was to use CANVAS to draw links and SVG for nodes
• The biggest problem was to make sure they overlapped perfectly, ie, it was a small problem.
C A N VA S D O E S N O T S U P P O R T B E Z I E R C U R V E S ! ! !
E X C E P T …
D 3 W I T H C A N VA S
• There are some great examples online…
• https://bocoup.com/weblog/d3js-and-canvas
• https://gist.github.com/mbostock/1276463
• But not quite what we wanted
D 3 W I T H C A N VA S
• Create a Custom XML NameSpace
• Map dummy nodes from this namespace for each link
• Each dummy node contains the SVG path required to draw the curve as well as other attributes
• After all nodes have been mapped, call a renderer to convert this to CANVAS
d3.ns.prefix.custom = "https://foo-bar.com/edge_bundling.html";
d3.select("body").append("custom:sketch") .classed("link-container", true);
…
linkBinding.enter() .append("custom:path") .attr("lineCap", "round") .attr("lineJoin", "round") .attr("lineWidth", .2) .attr("opacity", 0.1) .attr("selected-opacity", 0.3);
linkBinding .attr("selected-lineWidth", function(d) { return weightScale(d.weight); }) .attr("d", function(d) { return line(d.path); });
C R E AT I N G N O D E S
var pathAttrs = ["strokeStyle", "lineCap", "lineJoin", "lineWidth"];
ctx.beginPath();
pathAttrs.forEach(function(a) { var val = node.attr(selected + a) || node.attr(a); if(a === "strokeStyle") { var color = d3.rgb(val); val = "rgba(" + color.r + "," + color.g + "," + color.b + "," + node.attr(selected + "opacity") + ")"; } ctx[a] = val; });
A P P LY I N G S T Y L E S
var path = node.attr("d"), m; while((m = path.match(/^ *([MLCQ])([\d.,e-]+) */))) { var cmd = m[1]; var coords = m[2].split(",").map(pathToCoords); path = path.replace(/^ *([MLCQ])([\d.,e-]+) */, "");
switch(cmd) { case "M": ctx.moveTo(coords[0], coords[1]); break; case "L": ctx.lineTo(coords[0], coords[1]); break; case "C": ctx.bezierCurveTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]); break; case "Q": ctx.quadraticCurveTo(coords[0], coords[1], coords[2], coords[3]); break; } }
D R A W I N G C U R V E S
B U T I T S T I L L W A S N ’ T FA S T E N O U G H !
E N H A N C E M E N T S
• Use 2 CANVAS elements,
• one to hold the background (ie, all links),
• the second to show the currently selected links
• Change opacity on the background to darken it
• If mouse pointer returns to last selected node, just redisplay
S U M M A R Y
• Number of DOM nodes is very significant
• Reduce calculations on in-memory objects
• Show approximate data, based on available pixels and power
• Use CANVAS when mouse interaction is not required
• Cache repeated states rather than redrawing them
Thank You