d3.js Simple Responsive Line Graph with Animation

While introducing myself to the fantastic data visualization library d3.js I figured I might as well post some of the things I’ve learned.

I needed a responsive line graph with just a little bit of animation to give my graph some life. I also wanted to be able to see details of the exact plot points on the graph. Lastly I wanted the graph to be able to animate if the data was ever swapped out for new data. After a little bit of work the result was the following graph below. The code is far from perfect but it’s definitely a starting point.

See the Pen d3.js Simple Responsive Line Graph with Animation by Tawin Kiethanom (@tawink) on CodePen.



Breaking down the basic line graph code.

This graph is made up of a few parts:

  • Data (JS array)
  • Axes (X axis and Y axis)
  • Line (svg path with stroke)
  • Area (the filled path area under the line)
  • Dots (with detail popup)
function init(){
  chartContainer = d3.select('.chart-container');
  svg = chartContainer.append('svg');
  marginContainer = svg.append('g').attr('class', 'margin-container');
}

These 3 lines initialize our chart by selecting the designated div, appending the SVG element and adding a marginContainer in the SVG. The marginContainer will add padding to all our content, coming right up.

svg.attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom);

marginContainer
  .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

x = d3.time.scale().range( [ 0, width ] );
y = d3.scale.linear().range( [ height, 0 ] );
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.value; }) * 1.25]);

In our render() function we setup the dimensions of our SVG and marginContainer. See how the margins are used to get the total width of the SVG and shift the marginContainer over. This provides space for our Axes in the near future. x and y are defined as a scale of time and numbers respectively. Domain is basically the min and max numbers of our graph. For our x axis we want to show only the dates from the given dataset. For the y axis we want to start from 0 and go up to about 125% of the maximum number, this gives us some padding on the top of the chart.

area = d3.svg.area()
  .x( function( d )  { return x( d.date ); } )
  .y0( height )
  .y1( function( d ) { return y( d.value ); } );

line = d3.svg.area()
  .x( function( d )  { return x( d.date ); } )
  .y( function( d ) { return y( d.value ); } );

startData = data.map( function( datum ) {
  return {
    date  : datum.date,
    value : 0
  };
});

xAxis = d3.svg.axis()
  .scale(x)
  .orient('bottom');

yAxis = d3.svg.axis()
  .scale(y)
  .orient('left');

Now we need functions to generate area and line. For area we need to generate a vertical sliver. So the first point y0 is the height because everything is reversed when you’re working with SVG. The coordinates 0,0 start in the top left corner. So y0 is essentially from the bottom. y1 is the height of our data point. The line function is similar but we just need a point so its the x and y value. Then initialize the startData to zeroes and setup our xAxis and yAxis.

marginContainer.append('g')
  .attr('class', 'x axis')
  .attr('transform', 'translate(0,' + height + ')')
  .call(xAxis);

marginContainer.append('g')
  .attr('class', 'y axis')
  .call(yAxis)
  .append('text')
  .attr('transform', 'rotate(-90)')
  .attr('y', '1.5em')
  .style('text-anchor', 'end')
  .text('Price ($)');

marginContainer.append( 'path' )
  .datum( startData )
  .attr('class', 'line')
  .attr( 'd', line )
  .transition()
  .duration( 500 )
  .ease('quad')
  .attrTween( 'd', function() {
    var interpolator = d3.interpolateArray( startData, data );

    return function( t ) {
      return line( interpolator( t ) );
    }
  } )
  .each('end', function() {
    drawCircles( data, marginContainer );
  });

marginContainer.append( 'path' )
  .datum( startData )
  .attr('class', 'area')
  .attr( 'd', area )
  .transition()
  .duration( 500 )
  .ease('quad')
  .attrTween( 'd', function() {
    var interpolator = d3.interpolateArray( startData, data );

    return function( t ) {
      return area( interpolator( t ) );
    }
  } );

Now we append all our parts, x axis, y axis, line and area. We append the x axis as a g element. We vertically translate it down to the bottom of the chart using the height (remember reversed coordinates). Then we call our previously create xAxis which creates the tick marks with values. The y axis doesn’t need to be translated, but we add a small text element.

The line and area are going to be animated. To start we append a path using the initial startData. transition() tells the element theres going to be a transition. attrTween() is where the animation actually happens through interpolation. We want to go from zero to our data value so we pass in startData and data. You’ll notice after we append the line we also run a function called drawCircles().

We’ll get into appending circles and finishing off our chart in the next post. Cheers!

Codepen

Github

Check out Part 2.