UNPKG

@tanstack/charts

Version:

<div align="center"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/api/readme/charts.png?theme=dark" /> <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/

111 lines (101 loc) • 3.66 kB
--- id: framework-tick-labels title: Framework tick labels description: Position React tick content using resolved chart scales and plot bounds. --- `onRender` exposes the final `scene.scales` and `scene.chart` plot rectangle. Use them to position HTML tick content without copying scale math or inferred margins. This works with reversed scales, dates, and band centers because the chart owns the mapping. This React example reserves 72 pixels for a left axis. React owns the tick content; `onRender` updates its position after chart layout. It does not set React state, so positioning cannot trigger a chart render loop. ```tsx import * as React from 'react' import { Chart } from '@tanstack/charts/react' import { defineChart, lineY } from '@tanstack/charts' import { scaleLinear } from '@tanstack/charts/scales/linear' import type { ChartRenderContext } from '@tanstack/charts' const levels = [0, 1000, 2000] const rows = [ { match: 1, elo: 800 }, { match: 2, elo: 1600 }, ] const definition = defineChart({ margin: { left: 72 }, marks: [lineY(rows, { x: 'match', y: 'elo' })], scales: { x: { scale: scaleLinear }, y: { scale: scaleLinear().domain([0, 2000]), axis: { ticks: { values: levels }, tickLabels: false }, }, }, }) export function SkillChart() { const labels = React.useRef<HTMLOListElement>(null) const positionLabels = React.useCallback( ({ scene }: ChartRenderContext<(typeof rows)[number], number, number>) => { const layer = labels.current if (!layer) return Array.from(layer.children).forEach((element, index) => { const label = element as HTMLElement label.style.top = `${scene.scales.y.map(levels[index]!)}px` label.style.left = `${scene.chart.x - 8}px` }) layer.style.visibility = 'visible' }, [], ) return ( <figure aria-label="Skill over recent matches" style={{ margin: 0 }}> <div style={{ position: 'relative' }}> <ol ref={labels} aria-label="Elo axis" style={{ position: 'absolute', inset: 0, margin: 0, padding: 0, listStyle: 'none', pointerEvents: 'none', visibility: 'hidden', }} > {levels.map((value) => ( <li key={value} style={{ position: 'absolute', transform: 'translate(-100%, -50%)', whiteSpace: 'nowrap', }} > <span aria-hidden="true">★</span> {value} </li> ))} </ol> <Chart definition={definition} height={240} ariaLabel="Elo by match" onRender={positionLabels} /> </div> </figure> ) } ``` Replace the contents of each `li` with your badge, image, or component. Keep its width within the reserved margin. The HTML list provides axis values to assistive technology; give meaningful icons accessible names when they add information beyond the number. For an x axis, use `scene.scales.x.map(value)` for `left` and `scene.chart.y + scene.chart.height` for the bottom edge. Use `scene.scales[scaleId].ticks` when the scale chooses the tick values. Each tick already contains its resolved `position` and formatted `label`. The overlay is positioned after mounting and each layout update, including resizes. It is separate from the SVG and is not included in SVG exports, Canvas output, or Native rendering. This recipe reserves label space explicitly and does not measure framework content or apply SVG label thinning.