Français
Étape 10 : changer les données
Affectez les données d'une série et tout ce qui la dessine suit. Aucun autre appel n'est nécessaire.
ts
import {
D3PAutoDomain, d3pAutoInteractionArea, D3PAxis, D3PAxisDomain, D3PAxisTitle, D3PChart, D3PCursorIndicator,
d3pDomTooltip, D3PLegendPanel, D3PLineRenderer, D3PPanTool, D3PResetTool, D3PSeries, D3PTitle, D3PToolbox,
D3PTooltip, D3PWheelZoomTool, getGlobalBus,
d3pDomTooltip, D3PLegendPanel, D3PLineRenderer, D3PPanTool, D3PPoint, 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 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])
// 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 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 legendPanel = new D3PLegendPanel({ bus, placement: 'bottom' })
// changing the data is assigning the series: the lines move from what was there to what is there now, and the domains fit it
const next = (series: D3PSeries): D3PPoint => {
const last = series.data[series.data.length - 1]
return [+last[0] + 4, Math.round(Math.min(30, Math.max(0, last[1] + (Math.random() - 0.5) * 10)))]
}
const both = [temperature, feelsLike]
const actions: Record<string, () => void> = {
'add a point': () => both.forEach((series) => (series.data = [...series.data, next(series)])),
'remove the last': () => both.forEach((series) => series.data.length > 2 && (series.data = series.data.slice(0, -1))),
'remove the first': () => both.forEach((series) => series.data.length > 2 && (series.data = series.data.slice(1))),
replace: () => both.forEach((series) => (series.data = Array.from({ length: 5 + Math.floor(Math.random() * 5) }, (_, i) => [i * 4, Math.round(5 + Math.random() * 22)]))),
}
const objects = [x, y, line, feelsLine, cursor, tooltip, tools, title, xTitle, yTitle, legendPanel]
const toolbar = document.createElement('div')
container.before(toolbar)
// a button for each way to change the data
for (const [name, run] of Object.entries(actions)) toolbar.append(Object.assign(document.createElement('button'), { textContent: name, onclick: run }))
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é :
series.data = ...est toute l'API.[...series.data, ligne]ajoute une ligne,series.data.slice(1)retire la première, un nouveau tableau remplace tout. La série annonce le changement ; les rendus et leD3PAutoDomainqui lui sont liés réagissent.- Les domaines s'ajustent aux nouvelles données. Le domaine automatique règle de nouveau
hoursetdegrees, donc les nouveaux points ne sont jamais dehors. Sans lui, les domaines gardent les plages que vous avez écrites. - Les boutons sont les vôtres. Ce sont de simples fonctions qui affectent une série ; le graphique ne les connaît pas.
L'animation
Un rendu passe de ce qui est à l'écran à ce que disent les données maintenant. Un point est apparié au point de même valeur x, quelle que soit sa place à l'écran, donc :
- un point qui reste n'est pas apparié à son voisin, même quand les axes se redimensionnent ;
- un point nouveau naît sur son plus proche voisin, et un point retiré meurt sur lui : la courbe grandit ou rétrécit sans sauter ;
- une nouvelle barre grandit depuis la base, et une barre retirée y retourne.
Un redessin dure 200 ms. Si les données changent de nouveau pendant ce temps, le suivant part de ce qui est visible. Le zoom et le déplacement ne sont pas animés, pour que le glissement ne traîne jamais derrière le pointeur. Avancé donne l'algorithme.
Plusieurs séries
Affectez chaque série qui change. Elles se redessinent ensemble, dans la même image. Pour changer plusieurs graphiques à la fois, donnez-leur les mêmes séries : la vitrine a un seul jeu de boutons pour sept graphiques.
Beaucoup de points
Une courbe de 100 000 points se dessine en environ 70 ms, et se déplace, se zoome et se survole à 60 images par seconde ; seule l'animation entre deux jeux de données ralentit. La vitrine a un graphique pour la mesurer sur votre machine.