Learning Responsive Data Visualization
上QQ阅读APP看书,第一时间看更新

Drawing shapes with D3

D3 is a great library to create web visualizations because it includes a lot of useful abstractions when working with SVG elements. In this section, we will look at some of these SVG abstractions to draw shapes. These abstractions for drawing shapes are called generators in D3; we will mainly use the line-, area-, and arc-generators when creating visualizations.

Note

All SVG shape abstractions can be found in the D3 Github page at https://github.com/mbostock/d3/wiki/API-Reference#d3svg-svg.

Drawing Lines and Curves

If you've ever used the SVG path element and the d attribute to create custom shapes or Bézier curves, you already know that this is not as easy as it should be. Those who have never used the SVG path element only need to know that you would have to learn a special syntax to define and concatenate control points for a Bézier curve.

Note

You can find more information about the d attribute on MDN at https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d.

Thankfully, D3 provides a great abstraction to draw lines. Let's take a look at a simple example. First, let's create a dataset containing the x and y coordinates of some points:

var data = [
  [100, 100], [200, 350], [300, 50],
  [400, 200], [500, 250], [600, 40], [700, 100]
];

Now, we want to create a line generator that returns the first element of a data point as the x coordinate and the second value of the data point as the y coordinate:

var line = d3.svg.line()
  .x(function(d, i) { return d[0]; })
  .y(function(d, i) { return d[1]; });

Perfect! This is already our line generator. The advantage is that we only have to define the structure of the line generator, that is, where in the dataset it can find the x and y coordinates, and we can then draw any dataset that follows the same structure. Let's draw the line; we need to use the selection.datum() method to bind one specific datum to the selection. In contrary to the previously discussed data joins, this function will not compute a join but only returns a selection. Then, we can apply the line generator to the d attribute of the path, which will implicitly look for the bound data and creates the proper d string:

$svg.datum(data)
  .append("path")
  .attr("d", line);

The following figure shows a result of this example in the web browser:

Output of a simple line generator

In the previous example, you learned how to create a simple line generator that only connects the points of the dataset together. We can now easily define an interpolation method on the line generator in order to draw a smooth line:

var line = d3.svg.line()
  .x(function(d, i) { return d[0]; })
  .y(function(d, i) { return d[1]; })
  .interpolate('cardinal');

In the preceding example, we used the cardinal interpolation, which is a similar interpolation technique used in Bézier curves. If we look at the resulting line in the following figure, we see that the lines between the data points are now interpolated:

Output of an interpolated line

The smoothness of the interpolation can be controlled by the .tension(value) function. In the following figure, we can see the difference between tension 1, which is the edgy inner line, to tension 0.5, which is the smooth outer line:

Line with different tension values

Drawing Areas between curves

Similar to the line generator, D3 also provides an area generator that allows us to use 2 y coordinates: y0 and y1. Let's first create a dataset where the first element of the data point contains the x value, the second element the first y coordinate, and the third element the second y coordinate:

var data = [
  [100, 100, 100], [200, 350, 200], [300, 50, 50],
  [400, 200, 350], [500, 250, 375], [600, 40, 100], [700, 100, 100]
];

Now, we can create an area generator similar to the previous example:

var area = d3.svg.area()
  .x(function(d, i) { return d[0]; })
  .y0(function(d, i) { return d[1]; })
  .y1(function(d, i) { return d[2]; })
  .interpolate('cardinal');

Finally, we apply the area generator to the d attribute of the path element:

$svg.datum(data)
  .append("path")
  .attr("d", area);

The following figure shows the result of the created area path:

Output of an area generator

Drawing arcs

The third complex shape that is often used in visualizations is an arc. D3 also provides a generator function for this to easily draw arcs with polar coordinates. Again, if you have ever dealt with arcs in SVG, you will probably be very glad to know this generator.

Let's create an arc generator:

var arc = d3.svg.arc()
  .innerRadius(40)
  .outerRadius(100)
  .startAngle(0)
  .endAngle(Math.PI);

In the preceding code, we see that we can define inner and outer radius as well as the start and ending angles of the arc in radiant. Similarly to the previous example, we can now generate the d attribute for a path element:

$svg.append("path")
  .attr("d", arc)
  .attr("transform", "translate(200, 200)");

In the previous example, we observe that we need to translate the arc a little bit in order to make it visible. The output of the example in the web browser is displayed in the following figure:

Output of an arc generator

D3 provides more useful methods on the generator functions, for example, the arc.centroid() method. This function returns the center of the arc shape and suits perfectly to display labels with an arc.