Brushing Numerical and Ordinal Axes in D3.js

Brushing in visualisation refers to the act of selecting a subset of data elements inside a visualisation. As a visualisation designer, what you do with that selection is up to you. You can use the brush selection to:

D3.js has brushes built-in. Here’s an example. The area chart below shows the Tableau stock price for a three-month period in 2015, with .

You can brush the data by dragging a selection using the mouse across the x-axis. Neat, isn’t it? You can remove the brush by simply clicking somewhere outside the brush.

The x-scale used in this chart looks something like this:

The domain is the full extent (minimum and maximum) of all dates in the stock data array, and the range is the width of the chart.

d3_scale_time

Our scale maps values from the domain (the data space, in this case, dates) to the output range (the view space, in this case, pixel position). In the above example, we map days in a month to the range [0, 500]. The middle of the month would map to an output value of around 250.

Creating a Basic Brush

D3 gives us brushing (almost) for free in that it takes care of the mouse events and drawing. It also provides us with some useful functions to figure out when the user has modified the brush, and what data range is captured by the brush. Both 1D and 2D brushes are supported. The latter are useful for scatterplots and other chart types where selection in more than one axis makes sense. We’re going to stick to 1D brushes in this example, on the x-axis.

We can create a brush by creating an instance of d3.svg.brush and then calling it on a DOM selection to add the brush to that selection:

First, the brush is created and given the scale that is used for our x-axis. The brush is now aware of the x-axis’ input domain and output range.

The brush object can now be called as a function to draw the brush into a selection. Writing selection.call(brush) is equivalent to calling brush(selection). Lots of people find the call() syntax confusing and somehow magic, but really it’s just a convenience so that we can keep chaining things onto a selection!

Callbacks

D3.js can tell us when the user has modified the brush so that we can do other useful things with the selection rather than just display it. In the chart above, I updated a description of the date range based on your brushing activity. Brushes support the standard D3.js .on('nameOfEvent', myCallback) syntax for calling your own functions based on some event. The brush event name is simply brush:

The function userHasBrushed is called repeatedly as the user drags the brush around. If brush.empty() returns true, then there is no brush selection. Otherwise, we can call brush.extent() to get the brush’s extent.

For our 1D brush, the extent is a two-element array that represents the minimum and maximum data range of the selection. In other words, the data elements inside the extent will be those of the scale’s domain. In our case, these are JavaScript Date objects because that’s what we provided to the scale’s domain function. We could check if any given date was ‘within’ the brush by simply comparing it to the minimum and maximum dates:

D3 can provide the minimum and maximum of the data because d3.time.scale, like a standard d3.scale.linear, has a well-defined concept of the order of elements. We can more formally define it as an interval data type.

This brings us to an important question: what would brush.extent() return if we created a brush on a scale (and associated axis) that was something like a list of fruits?

Ordinal Brushing

Let’s imagine we have a list of fruits:

Fruits are a nominal data type and can be represented in D3 using a d3.scale.ordinal:

d3_scale_ordinal

The domain of this scale is the full set of available elements, because there’s no concept of ‘minimum’ and ‘maximum’ within the data: it’s an arbitrary set of elements. Although it’s a categorical dataset, it becomes an ordinal dataset (that is, it has an order) by virtue of being placed into an array. Arrays do have an order.

Here’s a brushable chart of our fruits. The brush is created exactly as before by passing our scale to it. Go ahead and select some fruits. At the moment, selected.

I’ve used Emojis because it’s a bit more fun that way. Please don’t start putting emojis in your real-world D3 visualisations, though – your users will not be amused and I will be blamed for starting a terrible trend.

So, what do you think I should get when I ask this brush for its extent? You might think that since our input data technically has an order since it’s an array, then D3 could provide us with the minimum and maximum indices into the fruits array. However, this would mean we’d have to keep telling the brush about our constantly-updating array.

What does the extent function return?

D3’s solution for providing brush extents for ordinals is to provide the extent in range space rather than domain space. Remember in the stocks example, we got back an array [ Date, Date ] because our domain was a range of dates. For the fruits, we get back [ Number, Number ] because our output range is numerical (specified above using scale.rangePoints).

The solution for checking whether a fruit is within the brush is now straightforward: check everything in range space. The extent is already in range space. To convert a fruit to test into range space, we just apply the x-scale to it:

So there we have it. If we wanted a big list of everything within the brush, then it’s as simple as filtering the data array based on this comparison:

You now have a full inventory of all fruits within the brush.

More on Brushing

If you’d like to know more, check out the excellent D3 documentation on brushing. There are also lots and lots of great examples of brushing around if you do a little Googling. Hopefully this post has given you enough of an understanding of brushing on different datatypes to implement them in your visualisations, no matter what the datatype.

Leave a Reply