Source
109 chart lines · 1 file · 2.7 kBBenchmark harness excluded · shared/mount.ts
U.S. driving and gasoline prices55 records · CSV · 1.5 kB
@charts-poc/demo-data/drivingSelection: Complete published snapshot
- side
- string
- year
- number
- miles
- number
- gas
- number
The New York Timesobservablehq/plot@356f579b1d94 · revision 356f579b1d94 · test/data/driving.csv · Observable Plot repository MIT; upstream source credited · SHA-256 3aced2fa41f2Pinned snapshot
cases/29-waterfall/tanstack.ts109 lines · entry
cases/29-waterfall/tanstack.ts
import { barY, colorLegend, defineChart, ruleY } from '@tanstack/charts'
import { pairs } from 'd3-array'
import { scaleBand, scaleLinear } from 'd3-scale'
import { driving } from '@charts-poc/demo-data/driving'
import type { DrivingRow } from '@charts-poc/demo-data/driving'
import { tanstackMount } from '../../shared/mount'
interface WaterfallPoint extends DrivingRow {
label: string
change: number
start: number
end: number
kind: 'increase' | 'decrease' | 'total'
}
const kinds: readonly WaterfallPoint['kind'][] = [
'increase',
'decrease',
'total',
]
const colors = ['#10b981', '#ef4444', '#2563eb']
const signedAmount = new Intl.NumberFormat('en-US', {
signDisplay: 'always',
style: 'currency',
currency: 'USD',
maximumFractionDigits: 2,
})
const observations = driving.filter((row) => row.year >= 2004)
const definition = () =>
defineChart(({ width }) => {
const rows = buildWaterfall(observations)
return {
marks: [
barY(rows, {
x: 'label',
y1: 'start',
y2: 'end',
color: 'kind',
inset: 1,
}),
ruleY([0], { stroke: '#64748b', strokeOpacity: 0.6 }),
],
x: {
scale: () => scaleBand<string>().padding(0.14),
axis: { tickLabels: { rotate: width < 560 ? -32 : 0 } },
},
y: {
scale: scaleLinear,
grid: true,
axis: { label: 'Change in gasoline price (USD per gallon)' },
},
color: {
domain: kinds,
range: colors,
legend: colorLegend({ label: 'Change' }),
},
}
})
function buildWaterfall(
rows: readonly DrivingRow[],
): readonly WaterfallPoint[] {
let total = 0
const changes = pairs(rows, (previous, current): WaterfallPoint => {
const change = current.gas - previous.gas
const start = total
total += change
return {
...current,
label: `${current.year}`,
change,
start,
end: total,
kind: change >= 0 ? 'increase' : 'decrease',
}
})
const first = rows[0]
const last = rows.at(-1)
if (!first || !last) return changes
return [
...changes,
{
...last,
label: `${first.year}–${String(last.year).slice(-2)}`,
change: total,
start: 0,
end: total,
kind: 'total',
},
]
}
export const mount = tanstackMount(
definition,
'Annual changes in U.S. gasoline prices',
{
format: ({ datum }) =>
datum.kind === 'total'
? `${datum.label} · ${signedAmount.format(datum.end)} net change`
: `${datum.label} · ${signedAmount.format(
datum.end - datum.start,
)} · ${signedAmount.format(datum.end)} running change`,
},
)