English
Step 9: polar charts
A polar chart draws the same series with an angle and a distance from the center. Only the axes change.
ts
import {
D3PAutoDomain, d3pAutoInteractionArea, D3PAxis, D3PAxisDomain, D3PAxisTitle, D3PBarRenderer, D3PChart,
D3PCursorIndicator, d3pDomTooltip, D3PLegendPanel, D3PLineRenderer, D3PPanTool, D3PResetTool, D3PSeries,
D3PTitle, D3PToolbox, D3PTooltip, D3PWheelZoomTool, getGlobalBus,
D3PAutoDomain, d3pAutoInteractionArea, D3PAxis, D3PAxisDomain, D3PAxisTitle, D3PChart, D3PCursorIndicator,
d3pDomTooltip, D3PLegendPanel, D3PLineRenderer, D3PPanTool, D3PResetTool, D3PSeries, D3PTitle, 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 humidity = new D3PSeries({ bus, name: 'humidity', data: [[0, 80], [4, 85], [8, 70], [12, 45], [16, 40], [20, 60], [24, 75]] })
const hours = new D3PAxisDomain({ bus, domain: [0, 24] })
const degrees = new D3PAxisDomain({ bus, domain: [0, 30] })
const percent = new D3PAxisDomain({ bus, domain: [0, 100] })
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 } } })
const yRight = new D3PAxis({ bus, name: 'humidity', domain: percent, position: 'right', props: { tickFormat: (v) => `${Math.round(v)} %` } })
// the same domains on polar axes: the hour is an angle, the temperature a distance from the center
const x = new D3PAxis({ bus, name: 'hour', domain: hours, position: 'angle', props: { tickCount: 6, tickFormat: (v) => `${Math.round(v)} h`, grid: { enabled: true } } })
const y = new D3PAxis({ bus, name: 'temperature', domain: degrees, position: 'radius', 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 humidityBars = new D3PBarRenderer({ bus, series: humidity, axes: ['hour', 'humidity'], props: { style: { color: '#9ccc96', barWidth: 14 } } })
const feelsLine = new D3PLineRenderer({ bus, series: feelsLike, props: { style: { color: '#4e79a7', curve: 'monotone' } } })
// what the pointer shows: a crosshair and a tooltip
const cursor = new D3PCursorIndicator({ bus })
const tooltip = new D3PTooltip({ bus })
// 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)
// titles and a legend take their room from the plot
const title = new D3PTitle({ bus, props: { text: 'Temperature', subtitle: 'on 12 June' } })
const xTitle = new D3PAxisTitle({ bus, axis: x, props: { text: 'hour of the day' } })
const yTitle = new D3PAxisTitle({ bus, axis: y, props: { text: 'temperature' } })
const yRightTitle = new D3PAxisTitle({ bus, axis: yRight, props: { text: 'humidity' } })
const legendPanel = new D3PLegendPanel({ bus, placement: 'bottom' })
const objects = [x, y, yRight, humidityBars, line, feelsLine, cursor, tooltip, tools, title, xTitle, yTitle, yRightTitle, legendPanel]
const objects = [x, y, line, feelsLine, cursor, tooltip, tools, title, xTitle, yTitle, legendPanel]
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)What changed:
- The axes have other positions.
position: 'angle'makes the hour an angle,position: 'radius'makes the temperature a distance from the center. The domains, the series, the renderers, the cursor, the tooltip, the tools and the legend are the ones of the cartesian chart. - The humidity is gone, to keep one chart that reads well: a polar system has one angle and one radius.
- The titles are against the disc: the angle's under it, the radius's on its left.
The renderers do not know which geometry they draw in. They ask their coordinate system to place points: the cartesian system gives them a rectangle, the polar one a disc.
The polar system
The system is created with the first polar axis, with its default settings. To change them, create it yourself and attach it before the axes:
ts
const polar = new D3PPolarSystem({ bus, props: { startAngle: 90, clockwise: false, innerRadius: 0.4 } })| Prop | Is |
|---|---|
startAngle | where the angle domain starts, in degrees (0 is up) |
clockwise | the direction the angle grows in |
sweep | the part of the circle that is covered, in degrees: 180 is a half circle (a gauge) |
innerRadius | the hole, as a fraction of the radius: a ring, a donut |
shift | moves the center, as fractions of the radius |
padding | pixels kept around the disc for the angle labels |
reach | how far beyond the disc the pointer still counts as over the chart (112 px) |
Values outside of the domain have no place: a polar line is cut at the angle range and clipped to the ring, never wrapped around.
Same data, two geometries
Swap the positions and the chart changes. The cartesian and polar example does it with a button. The radius axis can follow the pointer (D3PCursorRadiusAxisRenderer).
Pies and radars
A pie is a renderer in a polar system (D3PPieRenderer; a D3PPolarSystem with an innerRadius makes a donut). A radar uses polar axes with D3PRadarAngleAxisRenderer; a rose or radial bars use D3PBarRenderer. See the chart types.
Charts that follow each other
Two charts whose axes share one D3PAxisDomain stay in step, a cartesian one and a polar one included: cartesian and polar, synchronized.