Graphing Calculator, Part 2

Tue 09 July 2013

Filed under code

Welcome! This is part 2 of my documentation of writing a simple graphing calculator/function plotter using d3.js (see part 1 if you missed it). Today I will replace the line segments I used for plotting with the more involved (and actually easier to use, as you will see) SVG path element. Let's get started!

Paths To Success

The SVG path element is different from the other elements like circle and line we've seen so far. Instead of adding a discrete circle or line to the graph for each datapoint, you add a single element which comprises all the datapoints, and whose components are described in a graphical language similar to Logo. For example:

<svg width="100" height="100">
<path d=" M 25 25
L 25 75
L 75 75
L 75 25
L 25 25"
stroke="red" fill="none" />
</svg>

produces:

Don't worry about what M and L mean for now - these SVG path mini-language commands are abstracted away by d3.  Remember what I said above about not having to enter a distinct element for each datapoint in your set? For a path, d3 asks for a line() function which provides each of the points, plus an interpolation style which tells d3 how to draw lines between the explicitly plotted points.

We will change our previous code:

svg.selectAll('#line_not_in_axis_tick')
.data(plotdata.slice(0, plotdata.length :html_entity:`mdash` 1))
.enter()
.append('svg:line')
.attr("x1", function(d, i) { return xScale(d); })
.attr("y1", function(d, i) { return yScale(d); })
.attr("x2", function(d, i) { return xScale(plotdata[i+1]); })
.attr("y2", function(d, i) { return yScale(plotdata[i+1]); })

to the following:

// Line function which returns an X,Y pair for each point in our set, plus an interpolation method
// (more on the latter later).
var lineFunction = d3.svg.line()
.x(function(d) { return xScale(d); })
.y(function(d) { return yScale(d); })
.interpolate('linear');

// This is all we need to do to set up our path!
svg.append('svg:path')
.attr('d', lineFunction(plotdata))

Ah, one more thing — the fill attribute on a path element isn't just for show as it is with the line element! With a path it works the way you'd expect — it closes the path by drawing a line back to the starting point, and fills in the polygon created thereby. All we need to do is change

.style('fill', "rgb(6, 120, 155)");

from our previous code to:

.style('fill', "none");

There it is! Now we have a path which exactly matches what our previous line-based graph did, and in fewer lines of code.

Smoothing Things Out

Look closely at the bottom left of the function graph — there's a "kink" at (1, 1) where the path segment from (0, 0) to (1, 1) meets the segment from (1, 1) to (2, 1.414). This is an artifact of the 'linear' interpolation method I chose above. It's more obvious if we change the function and the viewing window:

var plotfunc = function(x) { return 1 + Math.sin(x); },
plotdata = [];
var lowX = 0, highX = 10, dX = 1;

Now that's ugly. Fortunately, changing the interpolation method used by d3 is simple: 

var lineFunction = d3.svg.line()
.x(function(d) { return xScale(d); })
.y(function(d) { return yScale(d); })
.interpolate('basis');  // only this line was changed

Wrapping Up

The full code for this exercise can be found here. We still need to change the x and y domains to be dynamic rather than hardcoded, and allow the user to graph arbitrary functions, so tune in next week for part 3!

Footnotes

  If you are interested in SVG's path mini-language, W3Schools has a quick reference, and Mozilla has a more in-depth tutorial.
  A full list of interpolation methods with visual examples can be found at the bottom of this page.