Now you need to write the draw() function to get the elements on screen. Let's just note here that nothing has happened yet. You haven't called databind() yet because you need to find a way to draw it to the canvas first. So, off we go.
The draw() function takes the context you want to draw on as an argument:
function draw(ctx) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawRainScene();
drawScene();
var elements = custom.selectAll('custom.drop');
elements.each(function(d, i) {
var node = d3.select(this);
ctx.save();
ctx.beginPath();
ctx.globalCompositeOperation = 'source-atop'
ctx.fillStyle = node.attr('fillStyle');
ctx.arc(node.attr('cx'), node.attr('cy'), node.attr('r'), 0, 2 *
Math.PI);
ctx.fill();
ctx.restore();
});
Then it does the following:
- It clears the canvas.
- It draws the background scene, including the house and tree, as well as a cloud and a puddle drawn in drawRainScene().
- It loops through each of our virtual elements to draw it according to the attributes we specified in databind().
That's it! You can close the draw() function.
See the line ctx.globalCompositeOperation = 'source-atop'? The globalCompositeOperation allows us to fuse or blend-in shapes. It operates on a source shape, the shape we are about to draw, and a destination, the Canvas content underneath the source shape. You can apply a number of compositing effects, but we use source-atop here.
As a result, the new shape is only drawn where it overlaps the existing canvas content. The shape will not be visible in canvas regions without any drawings. This is why we need all objects in drawRainScene(). They form the background to our raindrops which they can't escape. By the way, if you don't want to draw all complex shapes by hand, you can draw them with vector graphics software such as Illustrator, save them as SVG, and use apps such as the SVG to HTML5 Canvas converter at http://www.professorcloud.com/svg-to-canvas/ to convert SVG paths into Canvas commands.