Français
Cartésien et polaire
Mêmes séries, mêmes rendus ligne et aire, même infobulle, même curseur, même légende et mêmes outils, dessinés sur des axes cartésiens ou polaires. Seuls les axes changent : basculez avec les boutons. Dans le code ci-dessous, axes contient les deux jeux d'axes et le graphique en prend un, plus tout le reste.
En polaire, glisser fait tourner l'angle, la molette zoome le rayon, et l'axe du rayon suit le pointeur (D3PCursorRadiusAxisRenderer). Les données couvrent trois tours, et ce qui sort du domaine d'angle n'est pas dessiné : c'est pourquoi le motif continue de remplir le cercle pendant la rotation.
ts
import {
D3PAngleAxis, D3PAreaRenderer, d3pAutoInteractionArea, D3PAxis, D3PAxisDomain, D3PChart, D3PCursorIndicator,
D3PCursorRadiusAxisRenderer, d3pDomLegend, d3pDomTooltip, D3PLegendBundle, D3PLineRenderer, d3pMergeArray,
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 container = document.getElementById('app')! // any element with a size: the chart fills it
const angle = new D3PAxisDomain({ bus, domain: [0, 360] })
const radius = new D3PAxisDomain({ bus, domain: [0, 100] })
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) })
const axisProps = { grid: { enabled: true } }
const axes = {
cartesian: [
new D3PAxis({ bus, name: 'angle', domain: angle, position: 'bottom', props: { ...axisProps, tickFormat: (v: number) => `${Math.round(v)}°` } }),
new D3PAxis({ bus, name: 'radius', domain: radius, position: 'left', props: axisProps }),
],
polar: [
new D3PAngleAxis({ bus, name: 'angle', domain: angle, props: { ...axisProps, tickCount: 12, tickFormat: (v: number) => `${Math.round(v)}°` } }),
// the spoke of the radius axis turns to follow the pointer
new D3PRadiusAxis({ bus, name: 'radius', domain: radius, props: axisProps, renderer: new D3PCursorRadiusAxisRenderer() }),
],
}
const area = new D3PAreaRenderer({ bus, series: lobes, name: 'lobes area', marker: false, props: { style: { color: '#4e79a7', opacity: 0.2 } } })
const lobesLine = new D3PLineRenderer({ bus, series: lobes, props: { style: { color: '#4e79a7' } } })
const swellLine = new D3PLineRenderer({ bus, series: swell, 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] })
// the angle turns (drag), the radius zooms (wheel): in cartesian, X pans and Y zooms
const toolbox = new D3PToolbox({ bus })
toolbox.addTool(new D3PPanTool({ bus, axes: ['angle'] }))
toolbox.addTool(new D3PWheelZoomTool({ bus, axes: ['radius'] }))
toolbox.addTool(new D3PResetTool({ bus }))
for (const name of ['pan', 'wheel-zoom', 'reset']) toolbox.setToolActive(name)
// the axes are the only thing that depends on the geometry: the chart takes one set of them and `shared`
const shared = [area, lobesLine, swellLine, cursor, tooltip, legend, toolbox]
const bar = Object.assign(document.createElement('div'), { className: 'toolbar' })
container.before(bar)
const chart = new D3PChart({ container, bus })
const stopArea = d3pAutoInteractionArea(chart)
const stopTooltip = d3pDomTooltip(tooltip)
const stopLegend = d3pDomLegend(legend)
// the axes are the only thing that depends on the geometry: swap them, keep the rest attached
let attached: { addToChart(c: D3PChart): void; removeFromChart(c: D3PChart): void }[] = []
const show = (geometry: 'cartesian' | 'polar') => {
attached = d3pMergeArray(attached, [...axes[geometry], ...shared])
.exit((o) => o.removeFromChart(chart))
.enter((o) => o.addToChart(chart))
.value()
}
for (const geometry of ['cartesian', 'polar'] as const) {
bar.append(Object.assign(document.createElement('button'), { textContent: geometry, onclick: () => show(geometry) }))
}
show('polar')Comment fonctionne la bascule
Un axe a une position. bottom, top, left et right le placent sur un côté d'un graphique cartésien ; angle et radius en font la première et la seconde dimension d'un système polaire (D3PAngleAxis et D3PRadiusAxis en sont des raccourcis). Les rendus lisent les axes du graphique et demandent à leur système de coordonnées de projeter les points : ils ne savent pas dans quelle géométrie ils dessinent.
ts
// cartésien
new D3PAxis({ name: 'angle', domain: angle, position: 'bottom' })
new D3PAxis({ name: 'radius', domain: radius, position: 'left' })
// polaire : mêmes noms, mêmes domaines
new D3PAxis({ name: 'angle', domain: angle, position: 'angle' })
new D3PAxis({ name: 'radius', domain: radius, position: 'radius' })Chaque axe est dessiné par un moteur de rendu d'axe, choisi d'après la position. Un axe refuse un moteur qui ne gère pas sa position, avec un message explicite.