public
Last active

DOM-to-Canvas using D3

  • Download Gist
README.md
Markdown

Mouseover to draw circles!

This is a quick proof-of-concept example demonstrating how to create a canvas scenegraph in the DOM using custom namespaced elements. The scenegraph in this example consists of a simple container sketch element and a number of child circle elements:

<custom:sketch width="960" height="500">
  <custom:circle x="300" y="400" r="128" strokeStyle="red"/>
  <custom:circle x="302" y="404" r="129" strokeStyle="red"/>
  …
</custom:sketch>

The browser ignores these elements because they exist in our "custom" namespace. To render them, we use a timer that iterates over the child elements and draws them to a canvas element.

Why do this? Well, if you wanted your own custom representation tailored to a specific application or domain, you can! This example demonstrates how to use the DOM to implement your own element hierarchy and render it to canvas. If you're designing a general-purpose graphical representation, though, I recommend using SVG instead. For comparison, see the original OMG Particles! example.

Implementation note: I'd prefer to use DOM Mutation Events to listen for changes to our custom elements, but browsers seem a bit sluggish in reporting them, particularly when elements are removed. If you have a slow-moving scene, you could probably get away with using mutation events rather than a timer that runs continuously. Alternatively, you could improve this example by stopping the timer after extended periods of activity.

index.html
HTML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
<!DOCTYPE html>
<html>
<head>
<title>Custom Elements</title>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.js?2.3.2"></script>
<style type="text/css">
 
body {
margin: 0;
}
 
</style>
</head>
<body>
<script type="text/javascript">
 
// Register the "custom" namespace prefix for our custom elements.
d3.ns.prefix.custom = "http://github.com/mbostock/d3/examples/dom";
 
var w = 960,
h = 500;
 
// Add our "custom" sketch element to the body.
var sketch = d3.select("body").append("custom:sketch")
.attr("width", w)
.attr("height", h)
.call(custom);
 
// On each mouse move, create a circle that increases in size and fades away.
d3.select(window).on("mousemove", function() {
sketch.append("custom:circle")
.attr("x", d3.event.clientX)
.attr("y", d3.event.clientY)
.attr("radius", 0)
.attr("strokeStyle", "red")
.transition()
.duration(2000)
.ease(Math.sqrt)
.attr("radius", 200)
.attr("strokeStyle", "white")
.remove();
});
 
function custom(selection) {
selection.each(function() {
var root = this,
canvas = root.parentNode.appendChild(document.createElement("canvas")),
context = canvas.getContext("2d");
 
canvas.style.position = "absolute";
canvas.style.top = root.offsetTop + "px";
canvas.style.left = root.offsetLeft + "px";
 
// It'd be nice to use DOM Mutation Events here instead.
// However, they appear to arrive irregularly, causing choppy animation.
d3.timer(redraw);
 
// Clear the canvas and then iterate over child elements.
function redraw() {
canvas.width = root.getAttribute("width");
canvas.height = root.getAttribute("height");
for (var child = root.firstChild; child; child = child.nextSibling) draw(child);
}
 
// For now we only support circles with strokeStyle.
// But you should imagine extending this to arbitrary shapes and groups!
function draw(element) {
switch (element.tagName) {
case "circle": {
context.strokeStyle = element.getAttribute("strokeStyle");
context.beginPath();
context.arc(element.getAttribute("x"), element.getAttribute("y"), element.getAttribute("radius"), 0, 2 * Math.PI);
context.stroke();
break;
}
}
}
});
};
 
</script>
</body>
</html>

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.