A new version of the D3 library was recently released (version 4). This brings a tonne of useful & awesome updates. We’re going to look at a couple of the changes, and see what you might use them for.

To frame these features, we’ll use an example visualisation – a timeline plot of all the tweets mentioning the term “javascript”:


This chart is a “Beeswarm” chart, based on Mike Bostock’s example (though this one uses canvas, and has realtime updates). See our github repo for the full code.

Force layout

D3 layouts are used to assign positions to elements of a graph/visualisation/anything – you apply a layout to some data, and the appropriate properties will be added to each of item.

A popular layout is the force layout. This uses a physics-based model to repel nodes from each other, creating a nicely spaced chart (see my earlier post for more details).

Previously, you could alter parameters of this model. However, in the new release you can add your own arbitrary forces to the layout, allowing you to use the force layout in completely new ways.

For example, to create our bee swarm plot we can use a force layout with the following forces:

  • The horizontal position should be close to the time value of a node
  • The vertical position should be close to the middle of the page
  • Elements shouldn’t overlap

The new force layout allows these constraints to be implemented:

d3.forceSimulation(nodes)
   .force("x", d3.forceX(d => x(d.value)).strength(1))
   .force("y", d3.forceY(h / 2))
   .force("collide", d3.forceCollide(4))

Great!

Modular architecture

Another change in v4 is that the project has been split up into several independent projects, with the central D3 repo bringing them together. (For example, the force layout is now a separate project – d3/d3-force which is exported by the main library)

This is pretty cool for the future development of D3 – each sub-project can have it’s own release cycle, documentation & contributors.

From a users’ perspective, this makes it easier to selectively import parts of the library; either by loading a sub project directly or by creating a custom bundle containing only the modules that we need.

Building a custom D3 build

Our example uses the following d3 functions: d3.scaleLinear, d3.forceSimulation, d3.forceX, d3.forceY, d3.forceCollide, d3.extent & d3.json.

We can define a custom version of D3 that selectively re-exports functions from the main D3 module, we can do this using the es2015 module syntax:

// custom.d3.js

export {
  scaleLinear,
  forceSimulation,
  forceX,
  forceY,
  forceCollide,
  extent,
  json
} from 'd3'

Now, we can build our custom version using rollup (here, using the node-resolve plugin to allow us to load the source D3 from npm):

// rollup.config.js

import nodeResolve from 'rollup-plugin-node-resolve'

export default {
  entry: 'custom.d3.js',
  format: 'umd',
  moduleName: 'd3',
  plugins: [ nodeResolve({jsnext: true}) ],
  dest: 'build/d3.js'
}

Then we just need to install the dependencies, run rollup to build a version with only our selected modules, then minify the script using uglify-js.

# install rollup and uglify + local deps
npm install -g rollup uglify-js
npm install d3 rollup-plugin-node-resolve

# build custom modules
rollup -c

# minify
uglifyjs build/d3.js -c -m -o build/d3.min.js

This results in a much smaller file (from 211k to 69k in this example), which is a drop-in replacement for the full D3 library.

And More

This is only a small part of what has changed with the library. Find out about the other changes by checking out the release notes.

All the code for the twitter graph, and custom D3 build is up on github, if you have any comments or questions then totally give me a shout on twitter (I’m @benjaminbenben).