Nelson's log

D3 scales and interpolation

D3 has a notion of “scales”, transformations of data from a domain to a range. Say your data is percentages (0% to 100%) and you want to draw them as bars of length 10-20. You can easily construct a linear scale to map your domain [0,100] to a range [10,20]:


var s = d3.scale.linear.domain([0,100]).range([10,20])
d3.selectAll("rect").data(data)
  .enter().append("svg:rect")
    .attr("height", s)

If that use of s seems magic, equivalents would be


    .attr("height", function(n) { return s(n); })

    .attr("height", function(n) { return 10 + 10 * (n/100) });

D3 provides various useful scales. Numeric scales like linear, log, and pow, also discrete scales like quantize and ordinal.

One thing you can set on a scale is the interpolate function. It’s invoked when mapping the domain to the range and lets you make the range be something other than the usual numeric range.


s = d3.scale.linear()
  .domain([0,100])
  .interpolate(d3.interpolateRgb)
  .range(["#ff0000", "#0000ff"])
s(0) == "rgb(255,0,0)"
s(100) == "rgb(0,0,255)"
s(50) == "rgb(128,0,128)"

There are pre-defined interpolation functions for numbers, colours, strings, objects, etc. I believe they’re also used by D3’s transition animations, specifically to automatically warp complex things like SVG paths from one shape to another.

The application of interpolation in d3.scale.linear.interpolate is pretty simple to follow:


  interpolate = d3.interpolate,
  i = interpolate(y0, y1);

  function scale(x) {
    return i((x - x0) * kx);
  }

In the linear.js code [x0,x1] is the domain, kx is 1/(x1-x0). [y0,y1] is the range. So the above code takes the input x, maps it to the interval [0,1], then applies cached interpolation function i(). By default that function is a linear interpolation in [y0,y1], but the developer can override it.