Object Constancy

This bar chart shows the top ten states for a given age bracket, sorted by population percentage. For example, Utah’s burgeoning youth population earns it the top spot in the 5 to 13 (15.1%) and under 5 (9.8%) brackets, while Florida is popular with retirees (17.4%).

The chart shows multiple slices of a dataset, transitioning smoothly when the age bracket changes. The x-axis rescales to accommodate the change in maximum value, while bars reshuffle along the y-axis to preserve sorted order. Graphical elements enter and exit: Hawaii enters the top ten for the 65 and older age brackets, but fades out in younger ones. The axis ticks change suitably, from whole percentages to fifths. Old values fade-out while the new values fade-in, both translating to preserve a valid display across the transition.

Animated transitions are pretty, but they also serve a purpose: they make it easier to follow the data. This is known as object constancy: a graphical element that represents a particular data point (such as Ohio) can be tracked visually through the transition. This lessens the cognitive burden by using preattentive processing of motion rather than sequential scanning of labels.

#Key Functions

To achieve object constancy with D3.js, specify a key function as the second argument to selection.data. This function takes a data point as input and returns a corresponding key: a string, such as a name, that uniquely identifies the data point. For example, the bar chart above defines data as objects:

{
  "State": "ND",
  "Total": 641481,
  "Under 5 Years": 0.065,
  "5 to 13 Years": 0.105,
  "14 to 17 Years": 0.053,
  "18 to 24 Years": 0.129,
  "16 Years and Over": 0.804,
  "18 Years and Over": 0.777,
  "15 to 44 Years": 0.410,
  "45 to 64 Years": 0.260,
  "65 Years and Over": 0.147,
  "85 Years and Over": 0.028
}

A suitable key function for this data returns the State property, a FIPS code:

function key(d) {
  return d.State;
}

When you join the top-ten states to the bars, three selections are returned:

var bar = svg.selectAll(".bar")
    .data(top, function(d) { return d.State; });

The selection bar is the update selection: states that persist across the transition. The selections bar.enter() and bar.exit() are the enter and exit selections: states that are incoming or outgoing, respectively. For more on these three selections, see Thinking with Joins.

For example, when changing from the 18-24 bracket to 14-17, Alaska moves from spot #5 to #1. Since it is in the top ten in both age brackets, it is in the update selection. An update transition interpolates the transform attribute to translate Alaska smoothly to its new position. Simultaneous subtransitions resize the bar and reposition the associated label:

var barUpdate = d3.transition(bar)
    .attr("transform", function(d) { return "translate(0," + y(d.State) + ")"; });

barUpdate.select("rect")
    .attr("width", function(d) { return x(d[age]); });

barUpdate.select("text")
    .attr("x", function(d) { return x(d[age]) - 3; })
    .text(function(d) { return format(d[age]); });

Transitions are also used to fade entering and exiting elements. For the full code, view source.

Key functions can be useful for improving performance independent of transitions. For example, if you filter a large table, you can use a key function to reduce the number of DOM modifications: reorder DOM elements in the update selection rather than regenerating them. We used this technique at Square to improve the performance of merchant analytics, and it’s one of the reasons that D3 is faster than most template frameworks.

#When Constancy Matters

Above all, animation should be meaningful. While it may be visually impressive for bars to fly around the screen during transitions, animation should only be used when it enhances understanding. Transitions between unrelated datasets or dimensions (e.g., from temperature to stock price) should use a simpler cross-fade or cut rather than gratuitous, nonsensical movement.

Use a key function whenever you want to follow graphical elements through animation and interaction: filtering (adding or removing elements), reordering (sorting), switching dimensions within multivariate data, etc. If you forget to specify a key function, the default join-by-index can be misleading! Assist your viewers by maintaining object constancy.