Aller au contenu

Étape 9 : graphiques polaires ​

Un graphique polaire dessine les mêmes séries avec un angle et une distance au centre. Seuls les axes changent.

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)

Ce qui a changé :

  • Les axes ont d'autres positions. position: 'angle' fait de l'heure un angle, position: 'radius' fait de la température une distance au centre. Les domaines, les séries, les rendus, le curseur, l'infobulle, les outils et la légende sont ceux du graphique cartésien.
  • L'humidité a disparu, pour garder un graphique qui se lit bien : un système polaire a un angle et un rayon.
  • Les titres sont contre le disque : celui de l'angle dessous, celui du rayon à sa gauche.

Les rendus ne savent pas dans quelle géométrie ils dessinent. Ils demandent à leur système de coordonnées de placer les points : le système cartésien leur donne un rectangle, le polaire un disque.

Le système polaire ​

Le système est créé avec le premier axe polaire, avec ses réglages par défaut. Pour les changer, créez-le vous-même et attachez-le avant les axes :

ts
const polar = new D3PPolarSystem({ bus, props: { startAngle: 90, clockwise: false, innerRadius: 0.4 } })
PropEst
startAngleoù commence le domaine de l'angle, en degrés (0 est en haut)
clockwisele sens dans lequel l'angle croît
sweepla partie du cercle couverte, en degrés : 180 est un demi-cercle (une jauge)
innerRadiusle trou, en fraction du rayon : un anneau, un donut
shiftdéplace le centre, en fractions du rayon
paddingpixels gardés autour du disque pour les étiquettes de l'angle
reachjusqu'où au-delà du disque le pointeur compte encore comme sur le graphique (112 px)

Les valeurs hors du domaine n'ont pas de place : une courbe polaire est coupée à la plage d'angle et écrêtée à l'anneau, jamais enroulée autour du cercle.

Mêmes données, deux géométries ​

Échangez les positions et le graphique change. L'exemple cartésien et polaire le fait avec un bouton. L'axe du rayon peut suivre le pointeur (D3PCursorRadiusAxisRenderer).

Camemberts et radars ​

Un camembert est un rendu dans un système polaire (D3PPieRenderer ; un D3PPolarSystem avec un innerRadius fait un anneau). Un radar utilise des axes polaires avec D3PRadarAngleAxisRenderer ; une rose ou des barres radiales utilisent D3PBarRenderer. Voir les types de graphiques.

Des graphiques qui se suivent ​

Deux graphiques dont les axes partagent un D3PAxisDomain restent synchrones, un cartésien et un polaire compris : cartésien et polaire, synchronisés.