Drag

Instead of having the user click buttons in the last example, what if we just let them drag the chart area to see the UK's prison population change? It involves a bit more work on the user's behalf, but it also gives them the ability to freely navigate through the chart, which may be desirable in some circumstances.

Let's extend our prisonChart object again. Comment out everything in chapter5/index.js and add the following:

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

Now, create a new file in lib/chapter5, called draggableChart.js:

import * as d3 from 'd3'; 
import PrisonPopulationChart from './prisonChart';

const draggablePrisonPopulationChart = Object.create(PrisonPopulationChart);
draggablePrisonPopulationChart.addDragBehavior = function addDrag() {};

This is the same way we started the other chart.

Inside of our addDrag function, add the following:

  this.x.range([0, this.width * 4]); 
this.update();

const bars = d3.select('.bars');
bars.attr('transform', 'translate(0,0)');

We set the x scale range to four times the screen size and update our chart to get the initial draw as well as set our axes. We also set an initial translate value on the bars, as we will use this value to ensure that it moves correctly.

You may have noted that there's an unpleasant flash while the axes update. There are a few ways we can get around that--we can update prisonChart to call our axis functions outside of the init() method, or we could hide the chart initially using CSS, making it visible once the axes have updated. I'll leave this as an exercise for the reader.

Next, add the following, still inside our addDrag function:

  const dragContainer = this.container.append('rect') 
.classed('bar-container', true)
.attr('width', this.svg.node().getBBox().width)
.attr('height', this.svg.node().getBBox().height)
.attr('transform', `translate(${this.margin.left}, ${this.margin.top})`)
.attr('x', 0)
.attr('y', 0)
.attr('fill-opacity', 0);

const xAxisTranslateY = d3.select('.axis.x').node().transform.baseVal[0].matrix.f;

We add a new, invisible element on top of everything, and we get the x axis translate value so that we can keep it at a constant Y position when we drag it. To do that, we get the x axis group element and then look at its transform.baseVal[0].matrix properties. The e property corresponds to y translate, and the f property corresponds to x translate.

There used to be a helpful function called d3.transform that would make this really easy. Alas, it has been removed in D3 v4.

Next, we set up our actual drag behavior and call it:

  const drag = d3.drag().on('drag', () => { 
const barsTranslateX = bars.node().transform.baseVal[0].matrix.e;
const barsWidth = bars.node().getBBox().width;
const xAxisTranslateX = d3.select('.axis.x').node()
.transform.baseVal[0].matrix.e;
const dx = d3.event.dx;

if (barsTranslateX + dx < 0 && barsTranslateX + dx >
-barsWidth + this.innerWidth()) {
bars.attr('transform', &grave;translate(${barsTranslateX + dx}, 0)&grave;);
d3.select('.axis.x').attr('transform',
&grave;translate(${xAxisTranslateX + d3.event.dx}, ${xAxisTranslateY})&grave;);
}
});

dragContainer.call(drag);

We attach the drag behavior to our dragContainer element. When a user drags a top of that element, it fires a drag event, which we use to set translate our bars and x axis horizontally. The d3.event object contains dx and dy, which are the horizontal and vertical distances dragged, respectively. The if-statement is used to prevent us from dragging past or ahead of our bars, meaning that they'll stop one dragged to the y axis or the last bar is dragged to the right of the x axis.

Outside of addDrag(), add the following to export our chart:

export default draggablePrisonPopulationChart;

Save, hit refresh, and you'll see your new chart:

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

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