Skip to content

Step 5: zoom and pan ​

Tools let the user act on the chart. They do one thing: write the domains of the axes.

ts
import {
  D3PAutoDomain, d3pAutoInteractionArea, D3PAxis, D3PAxisDomain, D3PChart, D3PCursorIndicator, d3pDomLegend,
  d3pDomTooltip, D3PLegendBundle, D3PLineRenderer, D3PSeries, D3PTooltip, getGlobalBus,
  d3pDomTooltip, D3PLegendBundle, D3PLineRenderer, D3PPanTool, D3PResetTool, D3PSeries, D3PToolbox,
  D3PTooltip, D3PWheelZoomTool, getGlobalBus,
} from 'd3pict'

const bus = getGlobalBus()

const container = document.getElementById('app')! // any element with a size: the chart fills it

const temperature = new D3PSeries({ bus, name: 'temperature', data: [[0, 12], [4, 9], [8, 14], [12, 21], [16, 24], [20, 17], [24, 13]] })
const feelsLike = new D3PSeries({ bus, name: 'feels like', data: [[0, 10], [4, 6], [8, 12], [12, 22], [16, 25], [20, 15], [24, 11]] })

const hours = new D3PAxisDomain({ bus, domain: [0, 24] })
const degrees = new D3PAxisDomain({ bus, domain: [0, 30] })
const auto = new D3PAutoDomain({ bus })
auto.linkSeries(temperature, [hours, degrees])
auto.linkSeries(feelsLike, [hours, degrees])

const x = new D3PAxis({ bus, name: 'hour', domain: hours, props: { tickCount: 6, tickFormat: (v) => `${Math.round(v)} h`, grid: { enabled: true } } })
const y = new D3PAxis({ bus, name: 'temperature', domain: degrees, position: 'left', props: { tickFormat: (v) => `${Math.round(v * 10) / 10} °C`, grid: { enabled: true } } })
// one renderer per series; its style is a prop
const line = new D3PLineRenderer({ bus, series: temperature, props: { style: { color: '#e15759', curve: 'monotone', strokeWidth: 2.5 } } })
const feelsLine = new D3PLineRenderer({ bus, series: feelsLike, props: { style: { color: '#4e79a7', curve: 'monotone' } } })

// what the pointer shows: a crosshair, a tooltip, a legend
const cursor = new D3PCursorIndicator({ bus })
const tooltip = new D3PTooltip({ bus })
const legend = new D3PLegendBundle({ bus })

const objects = [x, y, line, feelsLine, cursor, tooltip, legend]
// tools act on the domains: drag to pan, wheel to zoom
const tools = new D3PToolbox({ bus })
tools.addTool(new D3PPanTool({ bus }))
tools.addTool(new D3PWheelZoomTool({ bus }))
tools.addTool(new D3PResetTool({ bus }))
for (const name of ['pan', 'wheel-zoom', 'reset']) tools.setToolActive(name)

const objects = [x, y, line, feelsLine, cursor, tooltip, legend, tools]

const chart = new D3PChart({ container, bus })
const stopArea = d3pAutoInteractionArea(chart) // before the objects: the cursor, the tooltip and the tools ask for an interaction area
objects.forEach((o) => o.addToChart(chart))
const stopTooltip = d3pDomTooltip(tooltip)
const stopLegend = d3pDomLegend(legend)

What changed:

  • D3PToolbox holds tools and says which ones are active. setToolActive('pan') turns a tool on.
  • D3PPanTool moves the domains when the pointer drags the plot. D3PWheelZoomTool zooms around the pointer with the wheel. D3PResetTool brings the domains back to where they were.

The axes follow the domains, and so does everything drawn from them: the lines, the grid, the cursor.

The tools ​

ToolNameDoes
D3PPanToolpandrag to move
D3PZoomSelectionToolzoom-selectiondrag a rectangle to zoom to it
D3PClickZoomToolzoom-clickclick to zoom in
D3PWheelZoomToolwheel-zoomwheel to zoom around the pointer
D3PWheelPanToolwheel-panwheel to move along x
D3PResetToolresetback to the first view

Choose what a tool acts on ​

By default, a tool acts on every axis of the chart. The axes option names the ones it moves:

ts
new D3PPanTool({ bus, axes: ['hour'] }) // moves x, leaves the y range alone

new D3PToolbox({ bus, mutuallyExclusiveGroups: [['pan', 'zoom-selection']] }) makes tools exclusive: turning one on turns the others of its group off.

The wheel and the page ​

A chart with a wheel tool takes the wheel while the pointer is over it. A chart without one lets the page scroll.