Plotting Functions in D3
I’ve updated a few old posts with beautiful plots. Mostly, that’s because I’m taking a class with Jeffrey Heer and decided to learn D3. But my function-plotting solution has a few interesting features.
Tldr
My JavaScript library is available please copy it to your own server if you’d like to use it. It’s not as full-featured as most other JavaScript plotting libraries; I wrote it as a learning exercise.
A quick usage example
The main function provided by my code is the Chart
constructor. Calling it inserts a new SVG block into the document, right after the script
element the constructor was called in (plots are drawn on page load, so they shouldn’t be too expensive).
var chart = new Chart(460, 250);
The arguments are the size of the chart; I’m using 460 pixels because the body text on my site is 560 pixels wide.
Once the chart is created, you can define a function and plot it:
function sigmoid(t) { with (Math) { return 1 / (1 + exp(-2*t)); } } chart.cartesian(sigmoid, [-5, 5, 100]);
The second argument to Chart.cartesian
contains the minimum and maximum value for the independent variable, and the minimum number of samples. The resulting chart looks like so:
I’m using with
to get prefix-less access to the Math
namespace. Since the functions I’m plotting are pure, this doesn’t introduce strange JavaScript artifacts, so most cautions against with
don’t apply.
There’s also a Chart.polar
and a Chart.parametric
. Plot two functions on the same chart, the chart doesn’t resize; its size is determined by the first function plotted.
function trigsig(x) { with (Math) { return .5+.5*sin(atan(2*x)); } } chart.cartesian(trigsig, [-1, 1, 100]).style("stroke", "lightblue");
As you can see above, D3 commands can be used to style the plots.
My plotting library has two nontrivial features that I’d like to describe.
Displaying source code
You might have noticed the “Show Source” button in the examples above. If you click it, you get the function bodies of the plotted functions. This actually works by parsing the function bodies passed to cartesian
and friends:
a_fn = (""+vars[name]).match(/^function ([\w_]+)\s*\(([\w_]*)\)\s\{\s*(?:with\s*\(Math\)\s*\{\s*)?((?:\n|.)*?)(?:\s*\}){1,2}$/);
Now that I’ve written it, I’m not sure what this regular expression actually does, but it successfully produces the function name and body of the function, while stripping out with (Math)
instances.
This definitely isn’t the best solution; I’m going to rewrite that function some day. But all the JavaScript parsers in JavaScript that I’ve found are too heavy-weight for this task.
Refining points
When you tell Chart.cartesian
that you want at least 100 samples, you don’t always get exactly 100 samples. Sometimes a function will increase rapidly, such as the sides of the “bump” function above. This can lead to plots where the sample points are unevenly spaced along the curve and the curve looks jagged, not smooth.
My plotting library detects when two sample points are spaced too far apart in the plot (when the function rises at more than a 45° angle) and subdivides the interval between the two points until this is no longer the case.
This produces much nicer plots for some functions; it was important to the gaussian and sine example earlier. I think all plotting libraries should do this (as far as I know, many do).