English
Adding and removing data
Assign the data of a series and everything that draws it follows: the bars, the line, the tooltip, and the axes when an auto domain fits them.
ts
import {
D3PAutoDomain, d3pAutoInteractionArea, D3PAxis, D3PAxisDomain, D3PBarRenderer, D3PChart, D3PCursorIndicator,
d3pDomTooltip, D3PLineRenderer, D3PSeries, D3PTooltip, getGlobalBus,
} from 'd3pict'
const bus = getGlobalBus()
const container = document.getElementById('app')! // any element with a size: the chart fills it
const sales = new D3PSeries({ bus, name: 'sales', data: [[0, 34], [1, 52], [2, 41], [3, 67], [4, 58], [5, 73], [6, 49], [7, 62]] })
// the domains follow the data: they are fitted again each time it changes
const dx = new D3PAxisDomain({ bus, domain: [0, 7] })
const dy = new D3PAxisDomain({ bus, domain: [0, 80] })
const auto = new D3PAutoDomain({ bus })
auto.linkSeries(sales, [dx, dy])
const x = new D3PAxis({ bus, name: 'month', domain: dx, props: { tickFormat: (v) => `${Math.round(v)}`, grid: { enabled: true } } })
const y = new D3PAxis({ bus, name: 'sales', domain: dy, position: 'left', props: { grid: { enabled: true } } })
const bars = new D3PBarRenderer({ bus, series: sales, marker: false, props: { style: { color: '#9ccc96', barWidth: 18 } } })
const line = new D3PLineRenderer({ bus, series: sales, props: { style: { color: '#4e79a7', curve: 'monotone', strokeWidth: 2.5 } } })
const cursor = new D3PCursorIndicator({ bus })
const tooltip = new D3PTooltip({ bus })
const objects = [x, y, bars, line, cursor, tooltip]
// changing the data is assigning the series: what draws it moves from what was there to what is there now
const value = () => Math.round(20 + Math.random() * 60)
const actions: Record<string, () => void> = {
'add a point': () => {
const last = sales.data.at(-1)
sales.data = [...sales.data, [last ? +last[0] + 1 : 0, value()]]
},
'remove the last': () => (sales.data = sales.data.slice(0, -1)),
'remove the first': () => (sales.data = sales.data.slice(1)),
'replace': () => (sales.data = Array.from({ length: 5 + Math.floor(Math.random() * 8) }, (_, i) => [i, value()])),
}
const bar = Object.assign(document.createElement('div'), { className: 'toolbar' })
container.before(bar)
// a button for each way to change the data
for (const [name, run] of Object.entries(actions)) bar.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 and the tooltip ask for an interaction area
objects.forEach((o) => o.addToChart(chart))
const stopTooltip = d3pDomTooltip(tooltip)What it shows
- Changing the data is assigning it.
series.data = [...series.data, [x, value]]adds a point;series.data.slice(1)drops the first one. There is no other call: a series announces that its data changed, and the renderers and the auto domain linked to it react. - The chart moves from what was there to what is there now. A new point is born on its nearest neighbor and a removed one dies on it, so a line grows or shrinks without jumping; a new bar grows from the base and a removed one goes back to it. The transition lasts 200 ms, and starts again from what is on screen if the data changes during it. Advanced tells the algorithm.
- The axes fit the new data. The
D3PAutoDomainlinked to the series sets the x and y domains again each time. Without it, the domains keep the ranges you wrote and new points may fall outside. - Zoom and pan do not animate. A change of the domain is drawn at once, so that dragging never lags behind the pointer.