English
Cartesian and polar, synchronized
The same data on a cartesian and a polar chart that share their domains. Drag on one and the other follows; hover one and the pointer shows its value on both.
ts
import {
D3PAngleAxis, D3PAreaRenderer, d3pAutoInteractionArea, D3PAxis, D3PAxisDomain, D3PChart, D3PCursorIndicator,
D3PCursorRadiusAxisRenderer, d3pDomLegend, d3pDomTooltip, D3PLegendBundle, D3PLineRenderer, D3PPanTool,
D3PRadiusAxis, D3PResetTool, D3PSeries, D3PToolbox, D3PTooltip, D3PWheelZoomTool, getGlobalBus,
} from 'd3pict'
// your series data: replace these generators with your own values
import { pattern } from './data'
const bus = getGlobalBus()
const row = document.getElementById('app')! // a flex container with a size: the charts share it
const lobes = new D3PSeries({ bus, name: 'lobes', data: pattern(35, 55, 4) })
const swell = new D3PSeries({ bus, name: 'swell', data: pattern(15, 40, 2, 1) })
// the two charts read these two domains
const angle = new D3PAxisDomain({ bus, domain: [0, 360] })
const magnitude = new D3PAxisDomain({ bus, domain: [0, 100] })
const degrees = (v: number) => `${Math.round(v)}°`
const grid = { enabled: true }
// the same names on both charts: that is what the tooltip and the cursor follow across them
const cartesian = [
new D3PAxis({ bus, name: 'angle', domain: angle, props: { tickFormat: degrees, grid } }),
new D3PAxis({ bus, name: 'magnitude', domain: magnitude, position: 'left', props: { grid } }),
]
const polar = [
new D3PAngleAxis({ bus, name: 'angle', domain: angle, props: { tickFormat: degrees, tickCount: 12, grid } }),
new D3PRadiusAxis({ bus, name: 'magnitude', domain: magnitude, props: { grid }, renderer: new D3PCursorRadiusAxisRenderer() }),
]
// renderers, cursor, tooltip and tools are attached to both charts, and find their axes by name
// the line of the same series shows the point under the pointer: the area needs no marker of its own
const area = new D3PAreaRenderer({ bus, series: lobes, name: 'lobes area', marker: false, axes: ['angle', 'magnitude'], props: { style: { color: '#4e79a7', opacity: 0.2 } } })
const lobesLine = new D3PLineRenderer({ bus, series: lobes, axes: ['angle', 'magnitude'], props: { style: { color: '#4e79a7' } } })
const swellLine = new D3PLineRenderer({ bus, series: swell, axes: ['angle', 'magnitude'], props: { style: { color: '#e15759' } } })
const cursor = new D3PCursorIndicator({ bus })
const tooltip = new D3PTooltip({ bus, series: [lobesLine, swellLine] })
const legend = new D3PLegendBundle({ bus, series: [lobesLine, swellLine] })
const toolbox = new D3PToolbox({ bus })
toolbox.addTool(new D3PPanTool({ bus, axes: ['angle'] }))
toolbox.addTool(new D3PWheelZoomTool({ bus, axes: ['magnitude'] }))
toolbox.addTool(new D3PResetTool({ bus }))
for (const name of ['pan', 'wheel-zoom', 'reset']) toolbox.setToolActive(name)
// the legend is drawn once, on the first chart
const shared = [area, lobesLine, swellLine, cursor, tooltip, toolbox]
const charts = [
{ objects: [...cartesian, ...shared, legend], margin: { left: 56 } },
{ objects: [...polar, ...shared] },
]
const stops = charts.map(({ objects, margin }) => {
const container = row.appendChild(document.createElement('div'))
container.style.cssText = 'flex: 1; min-width: 0; min-height: 0' // the charts share the space
const chart = new D3PChart({ container, bus, margin })
const stopArea = d3pAutoInteractionArea(chart) // before the objects: the tools ask for an interaction area
objects.forEach((o) => o.addToChart(chart))
return () => (stopArea(), chart.destroy())
})
const stopTooltip = d3pDomTooltip(tooltip)
const stopLegend = d3pDomLegend(legend)How the synchronization works
- Shared domains. The cartesian and the polar axes are built on the same two
D3PAxisDomainobjects (the angle and the magnitude): writing one is seen by both charts, which each draw it in their own geometry. Dragging the angle on the cartesian chart turns the polar one, and the other way round. - The same names. Both charts name their axes
angleandmagnitude, so the renderers, the cursor, the tooltip and the tools attached to both find their axes on each. They are the same objects, attached twice. - The pointer follows across. Hover one chart and the cursor, the tooltip and the dots appear on the other, at the same angle: each chart reads the pointer event through the probe, by axis name.
- A polar chart reads the pointer beyond its ring. Within the
reachof its system (112 px by default) the pointer still gives the angle and the outer magnitude, so the dots and the tooltip do not vanish when it leaves the disc. The radius axis that follows the pointer stops at the same distance.