Example

Let’s say you have a table in Excel and want to graph it. It’s easy! Just point it at your rows and columns, set a few colors, and boom! A graph.

Date Amount
2014-01-01 $10
2014-02-01 $20
2014-03-01 $40
2014-04-01 $80

So now we want to make one of these in SVG to show it off on the interwebs. It’s going to be a bit more work. What do we need to make sure we get it right?

The Scale

This graph has to be “to scale”. It has to have a coordinate system!

The x-axis goes from January 2014 to April 2014, and the y-axis goes from $0 to $80. However, the SVG is drawn in a box that’s about 200 by 300 pixels. Dates and pixels don’t map to one another on their own, so we have to specify a mapping somehow.

Note that the y-axis flips! The SVG origin, (0, 0) is in the top left, but in this graph, the origin is the bottom left. We call the chart y-up and we call SVG y-down.

The Axes

We can actually read the Excel graph because it’s clearly labeled. Those same labels with “$20” and “February” have to get to our screen somehow. They also need to be formatted correctly for the data type.

The Data

Our graph is showing our data! Somehow, the 4 rows in our source table need to turn into 4 points on a line. On top of that, the points in the line need to fit into the coordinate system we’ve defined.

We can kind of intuit this, but it’s critical to working with D3. We have data coming in, and we transform it to something visual.

Doing It The Hard Way

Let’s make a graph the hard way! As we’ve seen earlier, the SVG <path> tag is kind of complex, so we’ll swap out a line graph for a scatterplot.

We’ll need to manually write out each point. Transform attributes are inherited by child elements, so we can use <g> tags to move entire groups, such as the axes, or even offset the entire graph by a margin.

<svg width="350" height="160">
  <!-- 60px x 10px margin -->
  <g class="layer" transform="translate(60,10)">
    <!-- cx = 270px * ($X / 3)
                ^      ^   ^
    width of graph  x-value max(x)

          cy = 120px - (($Y / 80) * 120px)
                 ^       ^     ^       ^
      top of graph   y-value  max(y)  scale -->
    <circle r="5" cx="0"   cy="105" />
    <circle r="5" cx="90"  cy="90"  />
    <circle r="5" cx="180" cy="60"  />
    <circle r="5" cx="270" cy="0"   />

    <g class="y axis">
      <line x1="0" y1="0" x2="0" y2="120" />
      <text x="-40" y="105" dy="5">$10</text>
      <text x="-40" y="0"   dy="5">$80</text>
    </g>
    <g class="x axis" transform="translate(0, 120)">
      <line x1="0" y1="0" x2="270" y2="0" />
      <text x="-30"   y="20">January 2014</text>
      <text x="240" y="20">April</text>
    </g>
  </g>
</svg>
    
$10 $80 January 2014 April

Man! All that work for such a simple graph? SVG is a lot of work!

Doing It The D3 Way

Good news! D3 has pieces to help with each of the parts of a graph we listed above! However, D3 does this in the spirit of “automating the hard bits you already understand”, rather than making it all happen.

Small Helpers

There are a few operations that come up all the time, such as finding the minimum and maximum values of a data set (even both at the same time, the “extent”).

In D3, our source data is always plain old Javascript objects (POJOs). Most often the data is homogenous arrays.

var numbers = [ 5, 4, 10, 1 ],
    data = [
      { date: '2014-01-01', amount: 10 },
      { date: '2014-02-01', amount: 20 },
      { date: '2014-03-01', amount: 40 },
      { date: '2014-04-01', amount: 80 }
    ];

d3.min(numbers);
// 1
    

In D3 code, it’s common to pass callbacks that are used on all elements of a group. These callbacks are almost always called back with two arguments: the element and its index. It’s common to name these parameters d and i respectively.

d3.max(data, (d, i) => d.amount);
// 80

d3.extent(numbers);
// [1, 10]
    

Scales

D3 has objects called scales that help map values across coordinate systems. There are different kinds of scales (linear, logarithmic, linear for time). Scales are configured with a domain and a range, they map from the data to the approprate part of the screen (screen space).

Here is how we set up the y-scale for the above money example:

var y = d3.scaleLinear()
  .domain([0, 80]) // $0 to $80
  .range([200, 0]); // Seems backwards because SVG is y-down
    

Or if we wanted to take advantage of the helper methods above:

y.domain(d3.extent(data, d => d.amount));
    

The domain is in the data space, so its units are your source units. The range is in screen space (pixels).

This scale object is also a function! Calling the scale as a function is how we translate values from one coordinate to another.

y(0);   // in: $0
// 200  // out: 200px (bottom of graph)
y(80);  // in: $80
// 0    // out: 0px (top of graph)
    

We can even do the same things with dates!

var x = d3.scaleTime()
    .domain([
      new Date(Date.parse('2014-01-01')),
      new Date(Date.parse('2014-04-01'))
    ])
    .range([0, 300]);

x(new Date(Date.parse('2014-02-01')));
// 103.3811949976841
    

Scales are not just for linear transforms (continuous or quantitative scales), they can also be used for arbitrary transforms (discrete or ordinal scales). We’ll come across more scales later.

Axes

In our example, up top, we have these nice labels and tick marks. This is something D3 can do for us. We can build an axis, and apply it to a scale. We say, “hey, I want to build an axis that”.

// x is the d3.scaleTime()
var xAxis = d3.axisBottom(x)
  .ticks(4); // specify the number of ticks

var svg = d3.select('body')
  .append('svg')        // create an <svg> element
    .attr('width', 300) // set its dimentions
    .attr('height', 150);

svg.append('g')            // create a <g> element
  .attr('class', 'x axis') // specify classes
  .call(xAxis);            // let the axis do its thing


    

D3’s axes are really powerful! Notice how we built it using Date objects, and by default, it labeled the tick marks appopriately!

Data

The next thing to do is take our data and transform it into something visible. This is data binding, and it’s a big topic, so it gets its own section.

Next