Skip to content

Step 3: several series and their style ​

A chart can hold any number of series. Each one is drawn by a renderer, and a renderer is told how to look with its props.

ts
import {
  D3PAutoDomain, D3PAxis, D3PAxisDomain, D3PChart, D3PLineRenderer, D3PSeries, 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])

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 line = new D3PLineRenderer({ bus, series: temperature })
// 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' } } })

const objects = [x, y, line]
const objects = [x, y, line, feelsLine]

const chart = new D3PChart({ container, bus })
objects.forEach((o) => o.addToChart(chart))

What changed:

  • A second series, a second renderer. feelsLike is linked to the same domains, so both lines share the axes. A renderer has no other idea of the data than its series.
  • The style is a prop. props: { style: { color, curve, strokeWidth } }. curve joins the points: linear (default), monotone, step, stepBefore, stepAfter, basis, cardinal, catmullRom, natural. Without a color, a renderer takes one from its name.

Props ​

Every object that has options has a props. Read a prop (line.props.style.color), change a part (line.props({ style: { color: 'tomato' } })), or assign a field (line.props.style.strokeWidth = 4): the chart redraws by itself.

What a series can hold ​

A series is a list of rows. The first column is x, the second the main value; more columns are named and can be read by renderers (a range area has a min and a max, a candlestick has an open, a high, a low and a close).

ts
// rows: [x, value]
new D3PSeries({ bus, name: 'a', data: [[0, 12], [4, 9]] })
// a range: the columns are named, and a renderer picks the ones it needs
new D3PSeries({ bus, name: 'band', columns: ['x', 'max', 'min'], data: [[0, 20, 10], [4, 18, 8]] })
// numbers only: the index is x
new D3PSeries({ bus, name: 'b', data: [12, 9, 14] })
// objects: say where each column comes from (a key, or a function of the row)
new D3PSeries({ bus, name: 'c', data: rows, fields: { x: 'date', y: 'close' } })
// columns: one array per column
new D3PSeries({ bus, name: 'd', data: { x: [0, 4], y: [12, 9] } })

To change the data of a series, assign it: series.data = [...series.data, [x, value]]. Everything that draws it follows, with an animation (see step 10 and the adding and removing data example).

A missing value (NaN) leaves a gap in the line when the style has gaps: 'break'; by default the line goes over it.