D3 line generator is probably one of the most versatile generators. Though it is called a "line" generator, it has little to do with the svg:line
element. In contrast, it is implemented using the svg:path
element. Like svg:path
, D3 line
generator is so flexible that you can effectively draw any shape using line
alone, however, to make your life easier, D3 also provides other more specialized shape generators, which will be covered in later recipes in this chapter. In this recipe, we will draw multiple data-driven lines using the d3.svg.line
generator.
Open your local copy of the following file in your web browser:
https://github.com/NickQiZhu/d3-cookbook/blob/master/src/chapter7/line.html
Now, let's see the line generator in action:
<script type="text/javascript"> var width = 500, height = 500, margin = 50, x = d3.scale.linear() // <-A .domain([0, 10]) .range([margin, width - margin]), y = d3.scale.linear() // <-B .domain([0, 10]) .range([height - margin, margin]); var data = [ // <-C [ {x: 0, y: 5},{x: 1, y: 9},{x: 2, y: 7}, {x: 3, y: 5},{x: 4, y: 3},{x: 6, y: 4}, {x: 7, y: 2},{x: 8, y: 3},{x: 9, y: 2} ], d3.range(10).map(function(i){ return {x: i, y: Math.sin(i) + 5}; }) ]; var line = d3.svg.line() // <-D .x(function(d){return x(d.x);}) .y(function(d){return y(d.y);}); var svg = d3.select("body").append("svg"); svg.attr("height", height) .attr("width", width); svg.selectAll("path.line") .data(data) .enter() .append("path") // <-E .attr("class", "line") .attr("d", function(d){return line(d);}); // <-F // Axes related code omitted ... </script>
The preceding code draws multiple lines along with the x and y axes:
In this recipe, the data we used to draw the lines are defined in a two-dimensional array:
var data = [ // <-C [ {x: 0, y: 5},{x: 1, y: 9},{x: 2, y: 7}, {x: 3, y: 5},{x: 4, y: 3},{x: 6, y: 4}, {x: 7, y: 2},{x: 8, y: 3},{x: 9, y: 2} ], d3.range(10).map(function(i){ return {x: i, y: Math.sin(i) + 5}; }) ];
The first data series is defined manually and explicitly, while the second series is generated using a mathematical formula. Both of these cases are quite common in data visualization projects. Once the data is defined, then in order to map data points to its visual representation, two scales were created for the x and y coordinates:
x = d3.scale.linear() // <-A .domain([0, 10]) .range([margin, width - margin]), y = d3.scale.linear() // <-B .domain([0, 10]) .range([height - margin, margin]);
Notice that the domains for these scales were set to be large enough to include all data points in both the series, while the range were set to represent the canvas area without including the margins. The y-axis range is inverted since we want our point of origin at the lower-left corner of the canvas instead of the SVG-standard upper-left corner. Once both data and scales are set, all we need to do is generate the lines to define our generator using the d3.svg.line
function:
var line = d3.svg.line() // <-D .x(function(d){return x(d.x);}) .y(function(d){return y(d.y);});
The d3.svg.line
function returns a D3 line generator function which you can further customize. In our example, we simply stated for this particular line generator the x coordinate, which will be calculated using the x
scale mapping, while the y coordinate will be mapped by the y
scale. Using D3 scales, to map coordinates, is not only convenient but also a widely accepted best practice (separation of concerns). Though, technically you can implement these functions using any approach you prefer. Now the only thing left to do is actually create the svg:path
elements.
svg.selectAll("path.line") .data(data) .enter() .append("path") // <-E .attr("class", "line") .attr("d", function(d){return line(d);}); // <-F
Path creation process was very straightforward. Two svg:path
elements are created using the data array we defined (on line E
). Then the d
attribute for each path element was set using the line
generator we created previously by passing in the data d
as its input parameter. The following screenshot shows what the generated svg:path
elements look like:
Finally two axes are created using the same x
and y
scales we defined earlier. Due to limited scope in this book, we have omitted the axes-related code in this recipe and in the rest of this chapter, since they don't really change and also are not the focus of this chapter.