Source
182 chart lines · 2 files · 5.0 kB
U.S. industry unemployment1,708 records · CSV · 53.9 kB
@charts-poc/demo-data/industriesSelection: Complete published snapshot
- date
- Date
- industry
- string
- unemployed
- number
U.S. Bureau of Labor Statistics@observablehq/sample-datasets@1.0.1 · revision 732c0148de74 · industries.csv · ISC distribution; upstream source credited · SHA-256 4cfc13b6ebe0Pinned snapshot
cases/35-grouped-tooltip/tanstack.ts159 lines · entry
cases/35-grouped-tooltip/tanstack.ts
import {
bandX,
defineChart,
dot,
lineY,
mountChart,
whenFocused,
} from '@tanstack/charts'
import type { ChartHostOptions } from '@tanstack/charts'
import { tooltip } from '@tanstack/charts/tooltip'
import { portal } from '@tanstack/charts/tooltip/portal'
import { scaleLinear, scaleUtc } from 'd3-scale'
import { industries } from '@charts-poc/demo-data/industries'
import type {
ConformanceHandle,
ConformanceInput,
ConformanceTestDriver,
} from '../../types'
import { industryNames, selectGroupedTooltipData } from './selection'
import type { GroupedTooltipDatum } from './selection'
const colors = ['#2563eb', '#f97316', '#10b981']
const definition = (input: ConformanceInput) => {
const rows = selectGroupedTooltipData(industries, input.revision)
const dates = rows.filter((row) => row.industry === industryNames[0])
return defineChart({
marks: [
whenFocused(
bandX(dates, {
x: 'date',
fill: '#64748b',
fillOpacity: 0.14,
inset: 3,
radius: 4,
}),
{ match: 'x' },
),
lineY(rows, {
x: 'date',
y: 'unemployed',
color: 'industry',
}),
dot(rows, {
x: 'date',
y: 'unemployed',
color: 'industry',
r: 2.5,
states: [
{
when: { focus: 'group' },
style: { r: 5, stroke: 'Canvas', strokeWidth: 1.5 },
transition: { duration: 140, easing: 'ease-out' },
},
{
when: { focus: 'unmatched' },
style: { opacity: 0.3 },
},
],
}),
],
x: { scale: scaleUtc },
y: {
scale: scaleLinear,
grid: true,
axis: { label: 'Unemployed (thousands)' },
},
color: {
domain: industryNames,
range: colors,
},
})
}
const configuredDefinition = (input: ConformanceInput) =>
defineChart(definition(input), {
animate: false,
keyboard: true,
focus: 'group-x',
tooltip: {
use: tooltip,
portal,
anchor: 'group-center',
placement: ['top', 'right', 'left', 'bottom'],
sort: 'color-domain',
},
})
export function mount(
container: HTMLElement,
input: ConformanceInput,
): ConformanceHandle {
let focusedRows: string[] = []
const options: ChartHostOptions<GroupedTooltipDatum> = {
definition: configuredDefinition(input),
width: input.width,
height: input.height,
ariaLabel: 'Grouped industry unemployment tooltip',
onFocusGroupChange(points) {
focusedRows = points.map(
(point) => `${dateKey(point.datum.date)}:${point.datum.industry}`,
)
},
}
const host = mountChart(container, options)
const driver: ConformanceTestDriver = {
resolveTarget(target) {
if (target.view && target.view !== 'main') return null
const date = target.anchor.startsWith('date:')
? target.anchor.slice('date:'.length)
: null
const point = host
.getScene()
.points.find(
(candidate) =>
dateKey(candidate.datum.date) === date &&
candidate.datum.industry === industryNames[0],
)
const svg = container.querySelector('svg')
if (!point || !svg) return null
const scene = host.getScene()
const bounds = svg.getBoundingClientRect()
return {
x: bounds.left + (point.x / scene.width) * bounds.width,
y: bounds.top + (point.y / scene.height) * bounds.height,
focusElement: svg,
}
},
readState() {
const tooltip = container.querySelector<HTMLElement>('.ts-chart-tooltip')
return {
focus: { rows: [...focusedRows] },
tooltip: {
visible: Boolean(tooltip && !tooltip.hidden),
text: tooltip?.textContent?.trim() ?? '',
},
}
},
}
return {
driver,
update(nextInput) {
host.update({
...options,
definition: configuredDefinition(nextInput),
width: nextInput.width,
height: nextInput.height,
})
},
destroy() {
host.destroy()
},
}
}
function dateKey(date: Date) {
return date.toISOString().slice(0, 10)
}cases/35-grouped-tooltip/selection.ts23 lines · support
cases/35-grouped-tooltip/selection.ts
import type { IndustriesRow } from '@charts-poc/demo-data/industries'
export const industryNames = ['Manufacturing', 'Construction', 'Finance']
export type IndustryName = (typeof industryNames)[number]
export type GroupedTooltipDatum = IndustriesRow & {
readonly industry: IndustryName
}
export function selectGroupedTooltipData(
rows: readonly IndustriesRow[],
revision = 0,
): readonly GroupedTooltipDatum[] {
const offset = Math.abs(revision) % 2
const start = Date.UTC(2000, offset)
const end = Date.UTC(2003, offset)
return rows.filter(
(row): row is GroupedTooltipDatum =>
row.date.getTime() >= start &&
row.date.getTime() < end &&
industryNames.some((industry) => industry === row.industry),
)
}