Brushes

A brush behavior is similar to drag, but it is used to capture selections. Instead of moving items, a brush draws a box that selects items that it touches. You can find documentation in the d3-brush package.

To create a new brush, we'd call d3.brush() and then call it on an element. It emits start, brush, and end events, each receiving selection, sourceEvent, and target objects as part of d3.event.

It's time for an example!

We're going back yet again to our all-singing, all-dancing prison population graph. This is the last example to use it, I promise. We will let the user zoom in on a group of bars by selecting them with a brush, zooming out on right-click.

Begin by commenting out everything in chapter5/index.js and adding the following, just like the last few examples:

import brushableChart from './brushableChart';  (async (enabled) => { 
if (!enabled) return;
await brushableChart.init();
brushableChart.addBrushBehavior();
})(true);

Create a new file at lib/chapter5/brushableChart.js. Add the following, like before:

import * as d3 from 'd3'; 
import interactivePrisonChart from './buttonChart';

const brushablePrisonPopulationChart = Object.create(interactivePrisonChart);
brushablePrisonPopulationChart.addBrushBehavior = function () {
this.brush = d3.brush();
this.container.append('g')
.classed('brush', true)
.call(this.brush
.on('brush', this.brushmove.bind(this))
.on('end', this.brushend.bind(this)));

this.update();
};

The first part is just like before; the addBrushBehavior() method then sets up the brush behavior. We create a new group element for our brush, append it to our chart, and attach event listeners. We then call update() to populate the chart.

Create a new function after addBrushBehavior():

brushablePrisonPopulationChart.brushmove = function () { 
const e = d3.event.selection;
if (e) {
d3.selectAll('.bar').classed('selected', d =>
e[0][0] <= this.x(d.year)
&& this.x(d.year) <= e[1][0]
);
}
};

Here, we get the dimensions and coordinates of the area that was brushed. d3.event.selection contains two arrays, one containing the points where the brush started and another array containing the coordinates where it ended. We then grab the x-values from each point to get the range of bars we've selected. We then add the .selected class to each of them.

When the user finishes dragging the brush, the end event is fired; this callback handles that:

brushablePrisonPopulationChart.brushend = function () { 
if (!d3.event.sourceEvent) return; // Only transition after input.
if (!d3.event.selection) return; // Ignore empty selections.
const selected = d3.selectAll('.selected');
const data = selected.data();

// Clear brush object
d3.select('g.brush').call(d3.event.target.move, null);

// Zoom to selection
if (data.length >= 2) {
const start = data.shift();
const end = data.pop();

this.update(this.data.filter(d =>
d3.range(start.year, end.year + 1).indexOf(Number(d.year)) > -1));

const hitbox = this.svg
.append('rect')
.classed('hitbox', true)
.attr('width', this.svg.attr('width'))
.attr('height', this.svg.attr('height'))
.attr('fill-opacity', 0);

hitbox.on('contextmenu', this.rightclick.bind(this));
}
};

Quite a lot happens here. First, we clear the brush overlay by calling brush.move() with null as its second argument, then we figure out the first and last elements selected by the brush. From that, we get the start and end years, which we supply to d3.range, redrawing the chart just like we did in buttonChart. Lastly, we add a hit box, which we'll use to listen to the contextmenu event (contextmenu being the right-click variant of the click event).

Lastly, we need an event handler for when the user right-clicks on the chart. Add this to the end of brushableChart.js:

brushablePrisonPopulationChart.rightclick = function () { 
d3.event.preventDefault();
this.clearSelected();
this.update();
this.svg.select('.hitbox').remove();
};

All this does is prevents the normal context menu from popping out, clears the selected bars, and runs an update using the entire dataset. We also remove the hitbox.

Finally, export it and save:

export default brushablePrisonPopulationChart;

When you select some elements, the chart will look like this:

It'll then zoom in to those bars by running update(), with the data of the bars selected.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset