Skip to content

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 D3PAutoDomain linked 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.