Plottable 101: Easy Chart Components in JavaScript


So you’re building a web page, the page has some data, and you need to show a chart. There’s no <chart> HTML tag, so you Google “Charts JavaScript,” and then you tumble down a deep rabbit hole of confusion and indecision.

At work, we build lots of data-driven web applications, and developer-friendly charts are a necessity. Plottable is our riff on charts in JavaScript, and it’s pretty neat. If you don’t mind, let me introduce you.

Philosophy

Plottable is built around components. Components include the common plots like lines, pies, and bars, but also encompass elements like axes, legends, and labels. You can compose primitive components into higher-order components, and then combine these sets of components with other sets to build up rather powerful charts.

Each arrow points to a component primitive; the blue boxes are higher-order components. Taken from the PlottableJS.org Examples

Other chart libraries tend to think of a particular type of plot as a single fat component — you might instantiate a LineChart object and it will draw the plot, axes, every label, and maybe even some tooltips. But when you want to combine a line and bar plot in a single chart, the monolithic abstraction breaks down.

Plottable’s emphasis on composition should sound familiar (and encouraging) if you’ve ever used component-driven libraries like React. Composition doesn’t stop at the UI — all the visual components are backed by sensible data-level abstractions (like Plottable.Scale and Plottable.Dataset).

I know you’re yelling, “Show me some code!”, so let’s get to it.

A Really Simple Chart

For our demo, start with a simple HTML page:

There’s a variety of ways to use Plottable (such as with Bower or vendor’ing into your codebase), but for simplicity we’re going to use a CDN. Note that we also have to get D3 into the browser before Plottable loads, as well as include Plottable’s stylesheet.

Down in that script tag, let’s start with some data:

var data = [
{ hoursStudied: 1, testScore: 1 },
{ hoursStudied: 2, testScore: 3 },
{ hoursStudied: 3, testScore: 2 },
{ hoursStudied: 4, testScore: 4 },
{ hoursStudied: 5, testScore: 3 },
{ hoursStudied: 6, testScore: 5 }
];

As we’ll see, Plottable doesn’t care about the underlying structure of your data. To use your data with Plottable, we need to wrap it in a Dataset object:

var dataSet = new Plottable.Dataset(data, { name: "Test Scores" });

The second argument into the Dataset constructor is for metadata. This isn’t required, but can be useful when you’re dealing with multiple or dynamic datasets.

Leveraging a Dataset abstraction, instead of a plain-old JavaScript object, enables Plottable components to intelligently listen for changes. For example, if our app fetches new data, we can reset the data and all our components will update:

// Have to use the .data() setter-method
dataSet.data(newData);
// This mutation _won't_ trigger updates to our components
newData.push({ hoursStudied: 0, testScore: 1 });

Note that if we naively mutated our original data object, Plottable would not pick up the changes.

We have a Dataset, but still nothing on the screen. We can begin to fix that by creating an instance of a Line plot component:

var plot = new Plottable.Plots.Line();
plot.addDataset(dataSet);

We’re getting somewhere, but we still need to tell our plot how to convert the data’s attributes (hoursStudied, testScore) to X and Y coordinates.

Each plot class exposes various Accessor functions, which take a function as an argument to performs the conversion of datum ➝ coordinate. Many plots like Line have x and y Accessors, but something like a Pie plot has a sectorValue Accessor.

Let’s start filling in our Accessors:

var xAccessor = function(datum, index, dataset) {
return datum.hoursStudied;
};
// TODO: add a Scale
plot.x(xAccessor, __SCALE__);

Each Accessor is invoked once per datum/entry in your dataset and receives three arguments: the entry Plottable wants converted, the index of the entry, and then the Dataset object to which the entry belongs.

The missing piece is that dangling __SCALE__ argument. Scales are objects that components use to determine how to represent data visually. You can explore the complete Scale APIs, but there are common scales like Linear, Time, Color, and Category. When multiple components share the same Scale object, changes that the affect the domain or range of the Scale will affect all components simultaneously.

Here are the Scales we want for our little example, and the outstanding Y-coordinate Accessor:

var xScale = new Plottable.Scales.Category();
plot.x(xAccessor, xScale);
var yScale = new Plottable.Scales.Linear();
var yAccessor = function(datum, index, dataset) {
return datum.testScore;
};
plot.y(yAccessor, yScale);

Finally (right?), time to render the plot:

plot.renderTo("svg#example");
Baby’s first Plottable chart

Adding Details

Our plot is pretty no-frills; unless you’re trying to mislead folks with your charts, you’ll need some axes and labels.

As we mentioned earlier, labels and axes are also components in the Plottable nomenclature. We’re going to move our chart away from being just a plot to a composition of a plot, an X-axis, a Y-axis, and labels. Labels are simple, so let’s start there.

Right before we render our plot, instantiate some Label objects:

var xLabel = new Plottable.Components.AxisLabel("Hours Studied");
var yLabel = new Plottable.Components.AxisLabel(
dataSet.metadata().name, 270
);

The first argument is the text of the label, and the second argument is the degrees to rotate it from its normal horizontal position. Pretty straightforward, right? We’re also using the metadata() function of our Dataset, which we grabs the dataset metadata object we mentioned earlier.

Now we need to compose our labels and plot into a higher-order component called a Table. Tables lay out their child components in a grid; they have a number of configuration options for settings like paddings and weights, but the default behavior usually works fine:

var table = new Plottable.Components.Table([
[yLabel, plot],
[null, xLabel]
]);
table.renderTo("svg#example");

The two-dimensional array we pass into the constructor determines the “grid” position of each component. Internally, tables use layout logic to figure out that the plot should occupy a maximal amount of space, given the finite sizes of the labels.

Our revised plot, with 200% more labels (that’s how percentages work, right?)

We need to add some axes to give the chart a little more context. Like Plots and Labels, Axes are their own type of component. We create them like this:

var xAxis = new Plottable.Axes.Category(xScale, "bottom");
var yAxis = new Plottable.Axes.Numeric(yScale, "left");

Note that we need to pass in the scale objects we created earlier for our plot. By sharing the same scale, the axis and plot will adjust accordingly if any interactions would change the scope of data being shown.

We’re using two different classes for our axes: Category and Numeric. A categorical axis means that there are a finite amount of discrete values, while a numeric axis implies that there could be an infinite amount of values. Our xAxis is categorical, corresponding to the categorical xScale we created initially.

Since these are components, we only need to add them to our Table:

var table = new Plottable.Components.Table([
[yLabel, yAxis, plot],
[null, null, xAxis],
[null, null, xLabel]
]);

Overlaying Plots With Groups

For our last trick, we’re going to add another plot to our chart. And not just another simple line — to spice things up, we’re going to add a Bar plot with its own Y-axis.

Here’s an easy data set you can go ahead and create:

var studentData = [
{ hoursStudied: 1, students: 10 },
{ hoursStudied: 2, students: 12 },
{ hoursStudied: 3, students: 15 },
{ hoursStudied: 4, students: 7 },
{ hoursStudied: 5, students: 3 },
{ hoursStudied: 6, students: 2 }
];
var studentDataSet = new Plottable.Dataset(data,
{ name: “Students” }
);

We’re re-using the same hoursStudied categories on our X-axis, but we now have a new students metric to compare it against. This smells like we have to create new Scale, Axis, and Label objects to represent it correctly:

var yStudentsScale = new Plottable.Scales.Linear();
var yStudentsAxis = new Plottable.Axes.Numeric(yStudentsScale,
"left"
);
var yStudentsLabel = new Plottable.Components.AxisLabel(
studentDataSet.metadata().name,
270
);

That’s the plumbing — now to create our bars! This should feel pretty similar to how we crafted our Line plot from before:

var barPlot = new Plottable.Plots.Bar();
barPlot.addDataset(studentDataSet);
barPlot.x(function(d) { return d.hoursStudied; }, xScale);
barPlot.y(function(d) { return d.students; }, yStudentsScale);
barPlot.attr('opacity', 0.3);

We took a few shortcuts: we inlined our Accessor functions, and disregarded the index and dataset arguments they receive. There’s also a new method, attr(), which lets us tweak rendering attributes of a plot. We could also pass a function to attr(), in order to make the attribute a function of each datum, but we simply hard-coded the opacity for all entries.

We have a plot, we have a label and an axis, and now we need to put them all together. We want to stack the Line and Bar plots on top of each other — we do that with a new higher-order component called a Group. Earlier we saw how Tables align their children in a grid; Groups align their children on-top of each other, sort of like z-index in CSS.

Create a Group to hold both of our plots:

var group = new Plottable.Components.Group([ barPlot, plot ]);

The order of the components in the array determines their stacking order, starting from the bottom. Since this Group is just another component, we can put it in our existing Table, along with the other new elements:

var table = new Plottable.Components.Table([
[yLabel, yAxis, group, yStudentsAxis, yStudentsLabel],
[null, null, xAxis, null, null],
[null, null, xLabel, null, null ]
]);

Give the page a refresh and you should see this:

The Road Goes Ever On

That was a whirlwind tour, and we definitely missed out on some important topics (styling, interactions, animations), but this should give you an idea on where Plottable might be a good fit.

The JavaScript charting ecosystem is dense and bewildering. No abstraction is perfect for every project, but Plottable can be a solid foundation to build upon, and I hope you consider it before you delve into your next Google search.

If you want the code for these examples, check them on Github. I work for Palantir, but have no relationship to the Plottable team — just a fan :)