A simple D3 plugin

D3 plugins are a integral part of the soon to be released D3 v4. Now, rather than having to include the all of D3, it is possible to pick and choose the bits of the framework that you want to use, and ignore the rest.

Moreover this lets you create charts that can be versioned and distributed through package control. Awesome!

Since the current javascript modus operandi is to modularize as much as possible, lets modularize the most familiar part of any D3 chart.

 var svg = d3.select('body')
    .append('svg')
    .attr('width', width)
    .attr('height', height);

This simple plugin is going to give us a very small script that still gives us a significant amount of D3 js power.

Project Setup

Mike Bostock gives a great introduction to D3 plugins in his Lets make a D3 Plugin article. Its highly recommended that you give that page a glance since it lays the foundation for the rest of this article.

Mike provides a zip file of a starting project but it has a few short comings that make it less than useful.

The changes are walked through below, or you can run over to the github repo if you are impatient.

Configure Rollup.js

The first change that should happen with the base zip file is getting rollup configured so that it can include files from npm. This requires using rollup-plugin-node-resolve and attaching it as a plugin to the build process.

  • Run npm i rollup-plugin-node-resolve --save to install the package.
  • Create a rollup.config.js like the one d3-svg uses.
  • Add the -c rollup.config.js option to the rollup command in the package.json file.

Package.json script changes

The great thing about the base zip file is that so much of the build/test/publish process is just taken care of for you. That being said, I've modified the scripts slightly to better fit my workflow and use case. See the d3-svg package.json for details, but the additional functionality is:

  • npm run build to be able to trigger arbitrary builds of the project.
  • npm version will add commit a build of the project at that version.
Authoring the project

Github repo here.

As you can imagine implementing this is trivial:

export {d3_svg as create};

import * as d3_selection from 'd3-selection';

function d3_svg(elem, opts) {
  var body = d3_selection.select(elem);
  var svg = body.append('svg');

  if (opts && opts.width) {
    svg.attr('width', opts.width);
  }
  if (opts && opts.height) {
    svg.attr('height', opts.height);
  }
  return svg;
}

Exporting the right thing

Take note that the D3 build system provided by Mike is going to namespace everything against your package name and that your package cannot be a single function. The function above is going to be callable as d3_svg.create(). The build system is going to also create a property d3_svg.version that is pulled from the version key of your package.json.

Write a test

Yes, write a damn test. The base zip file already has all of the testing framework set up. Just write one simple test that checks the most basic bit of functionality for your module. See d3-svg's tests if you need some inspiration.

Getting this out of the way now makes it much easier to create tests in the future and allows you to ask pull requesters to write tests for bugs that they find. Tests keep your code healthy and working properly as modules get more complex and grow.

Publish and profit!

Now that everything is pulled together there is only one last thing to do.

npm version patch
npm publish

There you go - a D3js plugin that is versioned and distributed through npm. Now, with relative ease, you can make masterpieces like this:

var svg = d3_svg.create('#example', {width: 710, height: 100});

svg.append('text')
    .text('Magic!')
    .attr('x', 710/2)
    .attr('y', 50)
    .attr('text-anchor', 'middle');

svg.selectAll('circle')
    .data([100, 130, 160, 190, 610]).enter()
    .append('circle')
    .attr('cx', function(d) {return d;})
    .attr('cy', 50).attr('r', 10)
    .style('fill', 'steelblue');

Keep Reading

Implementing the Entropy language by abusing decorators

Magic configuration in Node.js using module preloading