Skip to content

Step 8: other kinds of chart ​

The kind of chart is the renderer. The axes, the series, the cursor, the tooltip stay as they are.

ts
import {
  D3PAutoDomain, d3pAutoInteractionArea, D3PAxis, D3PAxisDomain, D3PAxisTitle, D3PChart, D3PCursorIndicator,
  d3pDomTooltip, D3PLegendPanel, D3PLineRenderer, D3PPanTool, D3PResetTool, D3PSeries, D3PTitle, D3PToolbox,
  D3PTooltip, D3PWheelZoomTool, getGlobalBus,
  D3PAutoDomain, d3pAutoInteractionArea, D3PAxis, D3PAxisDomain, D3PAxisTitle, D3PBarRenderer, 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 } } })
// a second Y axis, on the right, for a quantity in other units
const yRight = new D3PAxis({ bus, name: 'humidity', domain: percent, position: 'right', props: { tickFormat: (v) => `${Math.round(v)} %` } })
// 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 humidityLine = new D3PLineRenderer({ bus, series: humidity, axes: ['hour', 'humidity'], props: { style: { color: '#59a14f', curve: 'monotone' } } })
// the same series, drawn as bars; a renderer is the only thing that changes
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, line, feelsLine, humidityLine, cursor, tooltip, tools, title, xTitle, yTitle, yRightTitle, legendPanel]
const objects = [x, y, yRight, humidityBars, line, feelsLine, cursor, tooltip, tools, title, xTitle, yTitle, yRightTitle, 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)

What changed:

  • Bars instead of a line. The humidity series is the same; D3PBarRenderer replaces D3PLineRenderer. barWidth is in pixels on a continuous axis, and a fraction of the band on a band axis.
  • The bars are first in the list of objects, so they are drawn behind the lines: renderers draw in the order they are attached.

To put an area under a line, add a second renderer on the same series: new D3PAreaRenderer({ bus, series, marker: false }). Its marker: false leaves the dot of the tooltip to the line above it.

The renderers ​

RendererDraws
D3PLineRenderer, D3PAreaRenderer, D3PScatterRendererlines, areas (down to zero, to a base, or between two series), points
D3PBarRenderercolumns, horizontal bars, grouped or stacked bars, lollipops, ranges; bars on polar axes
D3PBubbleRendererpoints of a size given by a third column
D3PCandlestickRenderer, D3PBoxPlotRenderer, D3PDumbbellRendererseries with several values per point
D3PHeatmapRenderera grid colored by a value (D3PColorScale, D3PColorLegend)
D3PPieRenderer, D3PFunnelRendererparts of a whole: no axes

Some chart types need a transform to prepare the series: D3PStack (stacked areas and bars, streamgraphs), D3PWaterfall, D3PHistogram. Annotations (D3PAnnotationLine, D3PAnnotationBand, D3PAnnotationPoint, D3PAnnotationText) mark values of the axes and follow zooms. They stop at the edge of the plot: a mark whose value is outside the axis is not drawn, and neither is its label.

Each chart type has a page with the chart and the code that makes it, and the chart types reference says how each one is built.