Skip to content

0.109.0 - Refactor charts to use shared primitives and add Histogram - #122

Merged
jalexw merged 3 commits into
mainfrom
claude/inspiring-newton-uiri7c
Sep 26, 2026
Merged

jalexw merged 3 commits into
mainfrom
claude/inspiring-newton-uiri7c

Conversation

@jalexw

@jalexw jalexw commented Sep 25, 2026

Copy link
Copy Markdown
Contributor

Summary

This release introduces a new Histogram component and refactors all existing chart components (ScatterPlot, LineChart, BarChart, PieChart) to use a shared set of chart primitives. The refactor centralizes color management, formatting, tooltips, legends, and responsive sizing logic, reducing duplication and improving consistency across the chart library.

Key Changes

New Components

  • Histogram - A new histogram/distribution chart component with linear and log scales, configurable binning, and overflow bucket support
  • BarList - A ranked list of horizontal bars for displaying sorted metrics

New Chart Primitives (src/components/ui/chart-primitives/)

  • chart-colors.ts - Centralized color palette management with support for 8 series slots, status colors, and a de-emphasis "other" color. Replaces inline color mappings in each chart.
  • chart-format.ts - Shared number formatting (thousands separators, float precision)
  • chart-scale.ts - Framework-free geometry and tick helpers (nice steps, axis generation, label thinning, log decade ticks)
  • chart-tooltip.tsx - Reusable floating tooltip component with positioning logic
  • chart-legend.tsx - Reusable legend component for multi-series charts
  • chart-data-table.tsx - Accessible data table for chart readouts
  • use-chart-width.ts - Hook for responsive chart sizing (width="auto" support)
  • responsive-chart.tsx - Container component for responsive charts
  • ChartPalette.stories.tsx - Storybook reference for the color palette

Chart Component Updates

All chart components now:

  • Import and use centralized color definitions from chart-colors.ts
  • Use useChartWidth hook for responsive sizing with width="auto" support
  • Export TooltipContext interfaces for custom tooltip rendering
  • Support seriesOrder prop to control color slot assignment by series id
  • Use shared formatting and scale utilities
  • Include legends for multi-series charts
  • Have improved accessibility with live regions and data tables

ScatterPlot:

  • Added yScale prop for log-scale support with decade ticks
  • Added per-point group prop to fade unrelated groups on hover
  • Added keyboard navigation (arrow keys to walk points)
  • Improved hover detection (24px hit radius)
  • Added ScatterPlotTooltipContext for custom tooltips

LineChart:

  • Added seriesOrder prop for stable color assignment
  • Improved legend and tooltip rendering
  • Added LineChartTooltipValue context interface

BarChart:

  • Refactored color rotation logic to use centralized palette
  • Added BarChartTooltipContext for custom tooltips
  • Improved responsive sizing

PieChart:

  • Refactored to use centralized color palette
  • Added legend support for multi-segment charts

Utility Additions

  • bin-values.ts - Framework-free histogram binning logic with linear and log scale support, including unit tests
  • use-measured-width.ts - Hook for measuring element width and responding to resize events
  • Sparkline - Updated to use new chart color system

Testing & CI

  • Added bun run test:unit command for framework-free unit tests
  • Added Unit-Tests job to GitHub Actions workflow
  • Included comprehensive tests for histogram binning logic

Documentation

  • Updated all chart Storybook stories with improved descriptions
  • Added interaction tests for tooltip and keyboard navigation
  • Added ChartPalette.stories.tsx as a reference for the color system
  • Updated CLAUDE.md with unit test command

Implementation Details

  • Color System: Moved from inline Tailwind class maps to CSS custom properties (--chart-1 through --chart-8, --chart-other) with fallback values for older theme versions
  • Responsive Sizing: Charts can now use width="auto" to fill their container and redraw on resize, with a placeholder during server

https://claude.ai/code/session_01HJCAfCNitnFTnnTMYTPiCd

…outs, Histogram and BarList

Apps no longer have to hand-roll chart sizing, colours, tooltips,
histograms and ranked bar lists (the /admin/traces dashboard in
schemavaults/auth#235 built all five itself).

Charts size to their container
- width="auto" on BarChart, LineChart, ScatterPlot, Sparkline and the new
  Histogram (PieChart: diameter="auto", capped at its size preset). The
  chart measures its root with a ResizeObserver and redraws on resize.
- Until the first measurement it renders an empty box at its height, so the
  server HTML holds no pixel width and no tick labels (no jump from a
  guessed width, no time-zone hydration mismatch). The root sets
  contain: inline-size so the drawing never holds its container open.
- Exported building blocks: useMeasuredWidth(fallbackWidth) and
  <ResponsiveChart height>{(width) => ...}</ResponsiveChart>.
- A numeric width keeps its pixel size.

Chart colour palette
- Default series colours are the chart palette (--chart-1 ... --chart-8,
  --chart-other from @schemavaults/theme 0.29.0), assigned in slot order and
  never cycled: a ninth series (a fourth on ScatterPlot) is "other". Every
  bar of a BarChart shares slot 1. Each reference carries the light hex as
  its var() fallback, so this works on theme 0.28.0 too (dark mode picks up
  its own steps once the theme is bumped).
- seriesOrder (LineChart, ScatterPlot) and segmentOrder (PieChart) make
  colour follow the id, so filtering a series out never repaints the rest.
- New colour ids chart-1 ... chart-8, other and brand; "default" now means
  slot 1 (it was the brand blue, now the explicit "brand").

Hover readouts
- Shared ChartTooltip / ChartTooltipRow (HTML, value first, series keyed by
  a short line, clamped inside the chart, flips below near the top),
  ChartLegend and a visually hidden table view on every chart.
- LineChart: crosshair that snaps to the nearest x with dots on every series
  and one readout for all of them; arrow keys, Home/End, Escape. Legend for
  two or more series. formatX / formatY / formatTooltip, xTickValues.
- ScatterPlot: nearest point within 24px (hitRadius), yScale="log" with
  decade ticks (zero on the bottom decade), per-point group emphasis,
  reference lines above the points with haloed labels, memoized points
  layer (group fading is a scoped style rule), so pointer moves on 5,000
  points touch only the readout.
- BarChart: the whole column is the hover/focus target, other bars dim,
  arrow keys; optional value axis with nice ticks (showValueAxis);
  formatValue / formatTooltip.
- PieChart: segment readout (value and share), arrow keys, optional legend.
- Nice ticks inside the domain (niceTicks, default on) and label thinning
  so axes stay readable at 360px.
- Mark rules: bars at most 24px thick (maxBarThickness) with a rounded free
  end and square baseline, 2px surface gaps, markers r >= 4 with a 2px ring,
  area fills as a light wash, loading state that keeps the previous render
  at reduced opacity.

New components
- Histogram: pre-binned buckets on a count axis, bucket-edge labels thinned
  to ~52px, touching bars, the tallest labelled, an overflow bucket in grey
  with a caption, hover/keyboard readout of range, count and share.
- binValues(values, { scale, minBinWidth }): linear nice-width bins with a
  long tail past the p95 folded into an overflow bucket, or 1-2-5 log edges;
  unit tests (bun run test:unit, new CI job).
- BarList: ranked horizontal bars in plain HTML for long names, truncated
  labels with the full text in title, right-aligned values, shared max,
  onSelect toggle buttons with aria-pressed.

Stories cover every new behaviour with play tests (keyboard and pointer
readouts, auto width, stable colours when filtering, 5,000 points without
touching the points layer, truncation at phone width), plus a Theme/Chart
Palette page.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01HJCAfCNitnFTnnTMYTPiCd
- binValues could loop forever when the bin width fell below the values'
  precision (epoch milliseconds a tenth of a ms apart); edges are now
  start + k*width rounded to the width's precision, and the walk stops if
  an edge fails to move forward. It also folded a phantom "long tail" when
  the data sat below zero: the tail test now measures from the minimum.
  New tests cover both.
- LineChart crashed when points had no explicit x and a formatX was given:
  nice ticks landed on fractional indices. Index-mode ticks are whole
  numbers now, and a formatter returning a non-string skips the tick.
- niceTicks defaults off for an axis given an xTickFormatter /
  yTickFormatter, so existing formatters keep their evenly spaced ticks.
- Screen readers: the visual tooltip is aria-hidden, and each chart keeps
  an always-mounted, visually hidden live region (new ChartLiveRegion)
  that announces keyboard moves only; a region mounted with its text is
  not read out, and hovers would flood it.
- ChartTooltip positions itself before every paint, so a readout whose
  size changed never shows a frame at the old width.
- useMeasuredWidth measures clientWidth minus padding, ignoring CSS
  transforms (a chart in a dialog that is zooming in).
- LineChart keeps the crosshair on its x value across data refreshes;
  Bar, Pie and Histogram treat a stale index as nothing active.
- Arrow keys move between clickable bars / pie segments, skipping ones
  that aren't clickable; PieChart segments keep their colour slot from
  their position in `segments` even when an earlier one is zero.
- ScatterPlot trend lines on a log scale are fitted on log10(y).
- BarChart docs: bar.className styles the painted shape, which doesn't take
  the pointer; use data-[active=true]: for hover styling.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01HJCAfCNitnFTnnTMYTPiCd
0.29.0 adds the chart palette tokens (--chart-1 ... --chart-8 and
--chart-other, with separate light and dark values) that the charts'
default series colours read. With it installed, dark mode draws the dark
steps instead of the light fallbacks the colour classes carry for older
themes. No component changes are needed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01HJCAfCNitnFTnnTMYTPiCd
@vercel

vercel Bot commented Sep 25, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
ui Ready Ready Preview Sep 25, 2026 7:36pm UTC

Request Review

@jalexw jalexw self-assigned this Sep 25, 2026
@jalexw
jalexw merged commit 759e8f7 into main Sep 26, 2026
12 checks passed
@jalexw
jalexw deleted the claude/inspiring-newton-uiri7c branch September 26, 2026 13:57
jalexw pushed a commit that referenced this pull request Sep 26, 2026
Brings in the chart upgrades from #122: the `chart-primitives` module (the
`--chart-1`..`--chart-8` categorical palette, scales, tooltips, legends,
responsive sizing), the new Histogram and BarList components, the upgrades to
BarChart / LineChart / PieChart / ScatterPlot / Sparkline, the `test:unit`
script and its CI job, and @schemavaults/theme 0.29.0.

Conflicts resolved:

- src/components/ui/index.ts - both sides appended exports after
  ./scatter-plot. Kept both; ./treemap now follows ./histogram and ./bar-list.
- package.json - main shipped 0.109.0 independently, so this branch moves to
  0.110.0 rather than reusing a published version.

TreeMap is unaffected by the theme bump: 0.29.0 adds the `--chart-*` tokens
without removing `--schemavaults-brand-blue`, `--warning` or `--destructive`,
which is what its palette maps resolve against.

Verified on the merge result: typecheck, typecheck:storybook, lint and build
clean; `bun run test:unit` 14/14; Storybook suite 136 suites / 1165 tests
passing.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Px7WyxT4i2wTrj5ZvabCr6

This branch was successfully deployed

1 active deployment
Preview — 91d6eba0 Deployed Sep 25, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants