Skip to content

D3.js Crash Course

D3 is a low level visualization library.

Lots of libraries build on top of it: Vega, Highcharts, Chart.js, and more all use D3 to build their charts out of SVG primitives. These higher level libraries focus on providing an easier to use interface, tailored to a limited number of use cases. Most offer a limited selection of supported charts: bar charts, scatterplots, etc.

D3 likely won't feel familiar to you coming from those or other visualization librareis. It operates on a completely different paradigm and will require substantially more code to accomplish similar results. Its usefulness comes from the fact that it allows total control every single item placed on the screen, so there is no real limit to waht can be accomplished with its primitives.

This chapter serves as a crash course on the core ideas required to become productive with D3:

  • Element Modification via chained functions/function passing
  • Data Binding
  • Scale Functions, as a means of introducing the rest of D3's extensive library

If you'd like to dive deeper, I recommend D3 in Depth which expands upon all of these concepts and others not introduced here.

Element Modification

At the lowest level, D3 is a library for modifying elements in SVG and/or HTML.

Example

Passing Functions

The second parameter to functions like style and attr can be literal values (like "red" above) or functions.

It is common to use anonymous/arrow functions, but named functions work just as well, and are preferred if a function is being re-used.

The function will be passed two arguments, commonly known as d and i so I'm following convention here and using those names.

  • d is the bound data. We'll return to that concept later.
  • i is the index of the element in the group.

So the above code sets each circle to red, and assigns each an increasing radius based on the position in the list.

Other Modification Methods

In addition to style and attr, there are methods to set classes, content, and various other properties.

Example

Appending Elements

In practice, instead of having pre-defined SVG elements, we'll use d3 to add them.

This time we'll start with an empty div:

Example

Data Binding

If we had static data, we could use a combination of built-in JavaScript methods and d3.append and build any SVG we want. It may be tedious, but the tools we have are enough to build any kind of static graphic.

In practice, especially with interactivity, we often want to "bind" specific SVG elements to the data they created, so that future modifications can leverage that data.

Remember that d parameter that our functions took?

We will use .data and .join to bind data to a given selection. We can then access the bound data via the d parameter.

Let's take a look:

Example

Data Joins Explained

The first line creates an array of hues. We'll see in the next example that we use any data, here we just create a list of evenly-spaced numbers. (d3.range works much like Python's range here.)

The format for a data join is

d3.select(container)
  .selectAll(element_type)
  .data(array)
  .join(element_type);

One step at a time:

  • We grab the element we're appending to.
  • Select all existing elements of this type (this seems redundant but is important for when we come back and animate things).
  • Create a data element.
  • Join that data element to the type we're creating, effectively creating array.length instances of element_type.

Resources

  • https://bost.ocks.org/mike/join/ - The quintessential essay on this idea from the author of D3. Uses slightly older d3 syntax (enter, exit) but core ideas are the same.

With this in mind, let's look at another example:

Example

Exercise

  • First, move the text above the baseline by 2px or so.
  • Then, change the code so that the label is at the end of the line instead of the beginning.

Scale Functions

In the above code we had the function

function(d, i) { return d.lengthKm / maxLength * width; } )

This is a common pattern: taking a ratio of some values, and then multiplying them by the size of an axis (or other range such as 360 degrees for a pie chart or hue).

For this reason, d3 gives us a set of functions that return this kind of scale functions.

Let's take a look:

Example

Now you can replace:

.attr("x2", function (d, i) {
  return (d.lengthKm / maxLength) * width;
})

with:

// because of the way JS functions handle arguments, the i is optional & can be omitted
.attr("x2", d => riverScale(d.lengthKm))

Scale Types

While that only saved us a bit of typing, there are many more scale function factories, appropriate for different types of data:

Continuous -> Continuous

  • scaleLinear
  • scaleSqrt
  • scalePow
  • scaleLog
  • scaleTime
  • scaleSequential

clamp=true*

If you are outside of the specified domain, the function will extrapolate by default. If you'd like it to instead clamp, add .clamp(true)

Continuous -> Discrete

  • scaleQuantize (TODO: demo)
  • scaleQuantile
  • scaleThreshold

Discrete -> Discrete

  • scaleOrdinal
  • scalePoint
  • scaleBand

Resources: - https://www.d3indepth.com/scales/ - https://d3js.org/d3-scale

Interaction

D3 has helpers for attaching event handlers. Unlike addEventListener, d3 will replace existing event listeners if called twice with the same event type.

function eventDebug(e) {
  // log event when fired, helpful for debugging
  console.log(`event fired => ${e.type} on ${e.target.tagName.toLowerCase()}#${e.target.id}`, e);
}

d3.select("#the-button").on("click", eventDebug);
d3.select("#the-select").on("change", eventDebug)

Animation in D3

In D3 we will achieve animation by:

  • moving our data join logic into a method we can call (e.g. update())
  • applying transition() and delay() calls as appropriate to ease/slow down transitions
  • using enter/update/exit parameters to join() to treat different elements differently
  • using ease() and tween() for more precise control of the animation

Then, interaction can be achieved by:

  • binding a function to an event (timer, button click, etc.)
  • using that function to update the data associated with a data join
  • calling our update() function as defined above

Demo: https://www.d3indepth.com/transitions/

Putting it all Together

Common D3 Modules

In addition to d3.scale, there are a few other modules you are likely to use that provide helper functions that reduce the amount of boilerplate code you'll need to write:

Fetch

D3 also has an async form of its data loading methods: d3-fetch.

// await needs to be in a script type=module
const planetData = await d3.csv("example.csv");
d3.select("#planets")
  .selectAll("li.planet")
  .data(planetData)
  .join("li")
  .text((d) => `${d.planet} has ${d.moons} moons`);

If you aren't using modules, there is a callback-based version instead:

d3.csv("example.csv").then(
 (data) => {
    console.log("LOADED", data);
    // callback function executes once data is loaded
    d3.select("#planets2").selectAll("li.planet").data(data).join("li")
      .text((d) => `${d.planet} weighs as much as ${d.mass_earths} earths`);
 }
);

Axes

Generates SVG for common axes.

Example

Color

Utilities for working with various color spaces.

Interpolation

Useful for animations, blend between two values (numbers, colors, shapes, etc.) to provide a smooth transition:

Shape

Various functions for converting data to shapes, such as converting from percentages to slices of a pie. Since SVG does not have a <pie-slice> element, you will use these to create <path> elements that are the appropriate shape. (Recall that ` lets us create any shape we want via "pen" commands.)

Includes: arc, area, curve, line, link, pie, stack, symbol, radial line/area/link, and more.

Less Common D3 Modules

See https://d3js.org/api and https://observablehq.com/@d3/gallery for more.

Highlights:

  • Chord - Chord-based visualizations.
  • Contour - Contour-line visualizations.
  • Delaunay - Space partitioning algorithms.
  • Force - Tools for (node/edge) graph layouts.
  • Geo - Tools for working with GIS data and maps.
  • Hierarchy - Tools for building hierarchical visualizations like treemaps.