233 lines · 4 files · 6.3 kB
cases/80-echarts-axis-pointer/colors.ts8 lines · dependency
cases/80-echarts-axis-pointer/colors.ts
import type { AxisPointerIndustry } from './selection'
export const axisPointerColors: Readonly<Record<AxisPointerIndustry, string>> =
{
Manufacturing: '#2563eb',
Construction: '#f97316',
Finance: '#10b981',
}cases/80-echarts-axis-pointer/example.tsx154 lines · entry
cases/80-echarts-axis-pointer/example.tsx
import { Chart } from '@tanstack/charts/react/tooltip'
import { industries } from '@tanstack/charts-data/industries'
import { colorLegend, defineChart, dot, lineY } from '@tanstack/charts'
import { focusGuideX } from '@tanstack/charts/focus/guide'
import { tooltip } from '@tanstack/charts/tooltip'
import { scaleLinear, scaleUtc } from 'd3-scale'
import { axisPointerColors } from './colors'
import { axisPointerDateKey } from './model'
import { axisPointerData, axisPointerIndustries } from './selection'
import type { ChartTooltipOptions } from '@tanstack/charts'
import type { AxisPointerDatum } from './selection'
export const exampleAriaLabel = 'Snapped axis pointer with grouped tooltip'
const month = new Intl.DateTimeFormat('en-US', {
month: 'short',
timeZone: 'UTC',
})
const monthYear = new Intl.DateTimeFormat('en-US', {
month: 'short',
year: 'numeric',
timeZone: 'UTC',
})
const axisPointerTooltip: ChartTooltipOptions<AxisPointerDatum> = {
className: 'conformance-tooltip-grouped',
sticky: false,
anchor: { x: 'value', y: 'plot-top' },
placement: ['bottom-right', 'bottom-left', 'right', 'left'],
offset: 10,
sort: 'color-domain',
items: [
{
channel: 'x',
label: '',
text: (point) => monthYear.format(point.datum.date),
},
{
channel: 'y',
text: (point) => point.datum.unemployed.toLocaleString('en-US'),
},
{ channel: 'group', label: 'Industry' },
],
}
export function createExampleChart(input: ExampleChartInput) {
const rows = axisPointerData(industries, input.revision)
return defineChart(
{
marks: [
lineY(rows, {
id: 'industry-lines',
x: 'date',
y: 'unemployed',
z: 'industry',
color: 'industry',
key: axisPointerKey,
strokeWidth: 2,
}),
dot(rows, {
id: 'industry-points',
x: 'date',
y: 'unemployed',
z: 'industry',
color: 'industry',
key: axisPointerKey,
r: 3,
stroke: '#ffffff',
strokeWidth: 1,
}),
focusGuideX(rows, {
id: 'axis-pointer-guide',
x: 'date',
y: 'unemployed',
z: 'industry',
key: axisPointerKey,
xRule: {
stroke: '#64748b',
strokeWidth: 1,
strokeDasharray: '4 4',
},
}),
],
x: {
scale: scaleUtc,
axis: { ticks: { format: (value) => month.format(value) } },
},
y: {
scale: scaleLinear,
grid: input.preview !== true,
axis: {
ticks: { count: 5 },
...(input.preview === true
? {}
: { label: 'Unemployed (thousands)' }),
},
},
color: {
domain: axisPointerIndustries,
range: axisPointerIndustries.map(
(industry) => axisPointerColors[industry],
),
legend: colorLegend({ itemWidth: 100 }),
},
focus: 'group-x',
focusRing: false,
maxFocusDistance: Number.POSITIVE_INFINITY,
svgAnimation: false,
margin:
input.preview === true
? { top: 4, right: 4, bottom: 22, left: 38 }
: { top: 38, right: 24, bottom: 45, left: 60 },
},
{
keyboard: true,
tooltip: {
use: tooltip,
...axisPointerTooltip,
},
},
)
}
function axisPointerKey(row: AxisPointerDatum) {
return `${row.industry}:${axisPointerDateKey(row.date)}`
}
export interface ExampleChartInput {
width: number
height: number
revision: number
preview?: boolean
interactive?: boolean
}
export const chart = createExampleChart({
width: 640,
height: 480,
revision: 0,
preview: false,
})
export default function Example() {
return (
<Chart
definition={chart}
height={480}
ariaLabel={exampleAriaLabel}
ariaDescription="Move across the chart or use the arrow keys to compare all three industries at the nearest month."
/>
)
}cases/80-echarts-axis-pointer/model.ts37 lines · dependency
cases/80-echarts-axis-pointer/model.ts
import { axisPointerDates, axisPointerIndustries } from './selection'
import type { AxisPointerDatum } from './selection'
export function axisPointerDateKey(date: Date) {
return date.toISOString().slice(0, 10)
}
export function axisPointerRowsAtDate(
rows: readonly AxisPointerDatum[],
date: Date,
) {
const timestamp = date.getTime()
return axisPointerIndustries.flatMap((industry) => {
const row = rows.find(
(candidate) =>
candidate.industry === industry &&
candidate.date.getTime() === timestamp,
)
return row ? [row] : []
})
}
export function axisPointerTargetValue(rows: readonly AxisPointerDatum[]) {
if (!rows.length) return null
return rows.reduce((sum, row) => sum + row.unemployed, 0) / rows.length
}
export function axisPointerAnchorDate(
anchor: string,
rows: readonly AxisPointerDatum[],
) {
const key = anchor.startsWith('date:') ? anchor.slice(5) : ''
return (
axisPointerDates(rows).find((date) => axisPointerDateKey(date) === key) ??
null
)
}cases/80-echarts-axis-pointer/selection.ts34 lines · dependency
cases/80-echarts-axis-pointer/selection.ts
import type { IndustriesRow } from '@tanstack/charts-data/industries'
export const axisPointerIndustries = [
'Manufacturing',
'Construction',
'Finance',
]
export type AxisPointerIndustry = (typeof axisPointerIndustries)[number]
export type AxisPointerDatum = IndustriesRow & {
readonly industry: AxisPointerIndustry
}
export function axisPointerData(
rows: readonly IndustriesRow[],
revision = 0,
): readonly AxisPointerDatum[] {
const offset = Math.abs(revision) % 2
const start = Date.UTC(2005, offset)
const end = Date.UTC(2005, offset + 8)
return rows.filter(
(row): row is AxisPointerDatum =>
row.date.getTime() >= start &&
row.date.getTime() < end &&
axisPointerIndustries.some((industry) => industry === row.industry),
)
}
export function axisPointerDates(rows: readonly AxisPointerDatum[]) {
return Array.from(
new Set(rows.map((row) => row.date.getTime())),
(timestamp) => new Date(timestamp),
)
}