Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
d3637cb
feat!: enforce mark-specific authoring contracts
rileyhilliard Oct 17, 2026
cf651c2
feat!: consolidate v9 theme layering and authored CSS tokens
rileyhilliard Oct 17, 2026
2a526c3
fix(build): keep compiler API tools on hoisted dependencies
rileyhilliard Oct 17, 2026
85db07a
feat!: make edit transactions the sole chart editing contract
rileyhilliard Oct 17, 2026
0659aac
fix: preserve annotation click targets before a drag preview
rileyhilliard Oct 17, 2026
ee0a0c6
feat: expose unified graph updates through framework handles
rileyhilliard Oct 17, 2026
deb528d
feat!: adopt v9 chart defaults and annotation UX
rileyhilliard Oct 17, 2026
1f0e2a0
fix: preserve v9 themes and annotation behavior across updates
rileyhilliard Oct 17, 2026
5f4b200
docs: add actionable v9 upgrade steps and search patterns
rileyhilliard Oct 17, 2026
dd8a5f9
test: refresh reviewed v9 visual baselines
rileyhilliard Oct 17, 2026
3585ecb
test: align footer invariant with v9 theme spacing
rileyhilliard Oct 17, 2026
9311231
refactor!: single-owner figure frame, split god files, UTC date-only …
rileyhilliard Oct 18, 2026
9be168d
feat: add chart and map design foundations
rileyhilliard Oct 18, 2026
e049c87
docs: record design RFC review and verified foundation work
rileyhilliard Oct 18, 2026
ca0782a
feat: adopt reviewed chart and map design defaults
rileyhilliard Oct 19, 2026
eba4e6b
feat: add geographic labels and callouts with bounded placement
rileyhilliard Oct 19, 2026
483bb9e
feat!: complete v9 reader comparison and geographic composition
rileyhilliard Oct 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .claude/rules/frontend/react.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ paths:

The react package wraps vanilla renderers. Key components:

- `<Chart spec={spec} />` - renders a chart from a VizSpec
- `<Chart spec={spec} />` - renders a chart from a `ChartSpec | LayerSpec` (use `<Visualization>` when the spec type varies)
- `<DataTable spec={spec} />` - renders a data table from a TableSpec
- `<Graph spec={spec} />` - renders a network graph from a GraphSpec
- `<VizThemeProvider theme={theme}>` - provides theme context to descendants
Expand Down
2 changes: 1 addition & 1 deletion .claude/rules/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ When fixing flaky tests, load the ce:writing-tests skill (flaky-test guidance is
Collision and fitting logic (label rotation, thinning, tick density) is
font-size- and width-dependent. Tests for it must sweep both dimensions:

- Font: `axisTick: 11` (default) AND `axisTick: 14` (the deployed blog theme)
- Font: `axisTick: 12` (default) AND `axisTick: 14` (the deployed blog theme)
- Width: ~330 (blog mobile), ~360 (small phone), ~680 (desktop-ish)

Why: the 7.9.x dropped-label bug passed every default-theme test across three
Expand Down
2 changes: 1 addition & 1 deletion .claude/rules/visual-regression.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Don't rely on it for:

## How it works

`playwright.config.ts` at the repo root spawns the Ladle dev server on port 61000, then two specs screenshot the canonical story set: `e2e/visual/stories.spec.ts` (60 desktop entries) and `e2e/visual/stories-mobile.spec.ts` (12 entries at a mobile viewport). Both go through the shared harness in `e2e/visual/capture.ts`. Baselines live in `e2e/visual/__screenshots__/<spec>-snapshots/` and are committed to git.
`playwright.config.ts` at the repo root spawns the Ladle dev server on port 61000, then two specs screenshot the canonical story set: `e2e/visual/stories.spec.ts` (94 desktop entries) and `e2e/visual/stories-mobile.spec.ts` (21 entries at a mobile viewport). Both go through the shared harness in `e2e/visual/capture.ts`. Baselines live in `e2e/visual/__screenshots__/<spec>-snapshots/` and are committed to git.

Before each screenshot the harness disables animations via an injected stylesheet and hides the Ladle dev overlays, then waits for fonts to settle, so timing and layout are deterministic.

Expand Down
136 changes: 136 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,142 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this

## [Unreleased]

### Chart and map design

- Keep the two endpoint date labels readable when a slope chart has a narrow plot,
without moving the data anchors or overriding explicit tick values.

- Add optional detail geography with zoom thresholds and explicit topology-object
selection, preserving the primary fitted projection. Validate decoded source
planes for identity maps before updates mutate the figure.
- Add explicitly covered, independently labeled geographic insets with scoped
cameras, callouts and mark events across vanilla and all wrappers. Share place
identity and scale semantics across views; export all current view cameras.

- Add opt-in date-slice A/B comparison and geographic reader controllers, with
host-placed readouts, local place search/ranking, keyboard inspection and explicit
Show on map actions. Missing observations remain distinct from zero; controlled
selection updates are silent in vanilla and React/Vue/Svelte.
- Add optional active-value and distribution map keys without changing authored
scale domains or class boundaries. Size side gutters to their content, use compact
quantitative range lists, and remove unnecessary backgrounds from reserved keys.
- Measure source, byline, footer and branding together across chart, map, bar list,
Sankey, tile map, table and graph. Stack long attribution when it fits better,
preserve custom brands, and use a consistent 12px default watermark. This can
change footer wrapping and available plot space in migrated documents.
- Add opt-in geographic `text`/`textPriority` channels and keyed `callouts`, with
polygon-interior anchors, bounded collision placement, fixed note capacity and
constant-screen-size labels through camera movement.
- Preserve every authored place name and callout in an accessible inventory; add
a host-placed `labelInventory` disclosure in vanilla and all GeoMap wrappers.
- Include settled geographic labels and full inventory descriptions in SVG export
and GeoMap static rendering; add stable `idPrefix` for multiple static figures.

- Add measured bar value placement (`adaptive`, `inside`, `outside`, `column`),
including shared value columns in facets and diagnostics when labels cannot fit.
- Add opt-in `rowHover` across vanilla and all Chart wrappers: quiet full-row
inspection for simple horizontal bars, shared with keyboard focus and passive
around annotations, legends and editing.
- Add horizontal y-axis unit headers through `titleAngle: 0` and `titleAnchor: 'end'`.
- Align endpoint precision across a displayed column, preserve source values and
missing observations, and measure formatted labels with the host font metrics.
- Budget both endpoint columns, gaps and date context against the measured axes;
automatic layouts retain a usable plot, while impossible authored fits diagnose.
- Default stacked-area endpoints to their band midpoint; retain explicit boundary
anchors and opt-in fit-tested interior series names. Hover geometry is unchanged.
- Preserve complete series names and band leaders in independent-scale layers;
resolve stacked domains from accumulated bands and separate clipped endpoint anchors.
- Measure the complete independent-layer legend before placing either axis, so
wrapped rows and side keys reserve space for every eligible series.
- Honor the resolved label font on bars, columns, dots and ranges; determine
column label direction from the source number instead of its formatted text.
- Add useful endpoint typography/spacing and map ink theme controls. Keep current
bar proportions and endpoint spacing; use quieter ordinary gridlines and stronger
existing zero rules in SVG and canvas without changing scale domains.
- Adopt quieter map borders while retaining the current no-data hatch. Measure
static map key titles/units/context before fitting geography, and place size keys
using their full bounds and geographic obstacles.
- Preserve expressible v8 grid, band-anchor and border styling in migration; report
the unavoidable new map-key layout behavior.
- Keep enlarged bar labels within available space, and keep enlarged rotated
axis titles inside the viewport.
- Place top point-color keys in their reserved band; reserve wrapped category
rows accurately and avoid double-counting mixed bottom key heights.
- Honor right and bottom-right map legends with measured gutters, narrow-layout
fallback diagnostics, and camera clipping that keeps geography out of the key.
- Describe actual map scale intervals in accessible legend text and accept the
built-in diverging palettes for quantitative geographic color encodings.

## [9.0.0] - 2026-10-17

### Annotation and figure defaults

- Adopt 12px small/axis/source/byline/footer text and 16px/12px chrome spacing.
Background stays transparent; default axis and eyebrow colors are unchanged.
- Default callouts to measured wrapping, regular primary text, 12/13 subtitle
sizing, and curves without implied arrows. Explicit author settings still win.
- Default reference labels to automatic placement and reference/range labels to
theme small text. Process automatic annotations by priority then source order,
and score connector crossings when selecting placements.
- Keep one eligible inline callout at compact widths by default. Per-annotation
`responsive: false` stays pinned; existing chart-level responsive opt-outs retain
their compact behavior. Null still disables count and wrap limits.
- Grow omitted `chromeLayout` by the note band only. Explicit `subtract` keeps an
exact outer budget; `grow` grows by all chrome. Resolved heights avoid double
counting in mounts and exports, including layered and faceted figures.
- Render complete wrapped rich-text notes with source-order numbering. Number
gaps are intentional and facet instances share source identity. Notes and
markers support transient linked focus/click/tap emphasis.
- Retune default reference-rule and range ink; derive marker and plate fills from
the resolved neutral surface while preserving authored colors.

### Breaking API and authoring changes

- Make `onEdit(EditTransaction)` the sole durable editing callback. Remove
`ElementEdit`, `onEditTransaction`, `onAnnotationEdit`, and `onTextEdit`; use
`applyEditTransaction` for atomic application, stale-target rejection, and undo.
- In edit mode, arrows nudge 1px, Shift+arrows nudge 10px, and Tab/Shift+Tab cycle
selection. Native text editing keeps its normal keyboard behavior.
- Remove the inert `onDataPointClick`, graph `updateVisuals()`, and the Chart
mount/wrapper `responsive` resize alias. Use `onMarkClick`, `update()`, and
`autoResize`, respectively; spec `responsive` is unchanged.
- Enforce the discriminated `MarkDef` union and reject inapplicable authored mark
options before sugar expansion, with matching TypeScript/schema/runtime checks.
- Reject retired table `compact`, Sankey/map/barlist `valueFormat`, annotation
`rule`, spec `darkMode`, theme chrome color strings, arc-family `encoding.y`
aliases, dead encoding channels, `mark.render`, and explicit `legacy` animation
presets. Tilemap `valueFormat` and ordinary chrome text shorthand remain valid.
- Remove legacy line-end series labels, manual series-label offsets/dragging, and
series-label element refs. Use the endpoint column; warn when both identifiers
are explicitly disabled on a multi-series line chart.
- Remove `footnoteBandHeight` helpers and narrow-breakpoint constant aliases.
Read `layout.chrome.noteBandHeight` and use `NARROW_VIEWPORT_MAX`.
- Unify builder/compiler field inference: sample up to 50 rows, require more than
80% agreement, and recognize numeric strings before dates. Explicit field types
remain authoritative.

### Themes and migration

- Deep-merge spec/provider/host themes; leaf themes override parent themes and
mount/compile options have highest precedence. Arrays and color pairs replace
atomically; partial rules inherit and `rule: null` clears a preset rule.
- Prefer authored chrome font sizes over general font propagation. Remove public
`Theme` and the empty `editorial` preset; use `ThemeConfig` / `ResolvedTheme` and
omit the preset. Deprecate `clarity`, retaining its remaining color choices.
- Stamp only authored CSS theme overrides through one renderer helper. Host CSS
can override omitted tokens; dark defaults include all registered tokens and
SVG/raster exports retain live CSS token values.
- Keep layer font measurement and font-load updates aligned with the primary
leaf theme; keep hidden annotation notes out of keyboard navigation.
- Normalize breakpoint annotations and apply endpoint-label patches consistently
with root specs, including migrated legacy labels.
- Extend `migrateSpecV8` with `{ styling: 'v9' }` for alias-only migration. Default
migration materializes former v8 styling, including annotation colors/spacing,
and reports repairs without mutating input. Layout pixel parity is not promised.
- Add [the v9 migration guide](docs/migrating-v9.md), including persistence handler,
exact-height embed, theme, CSS, and mark-union changes.


## [8.9.1] - 2026-10-17

### Annotation layout and theme options
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ function App() {

That's the whole interface. `encoding` maps data fields to visual channels. `chrome` adds editorial framing. The engine figures out the rest.

The fixed-height wrapper is optional. Without one, the chart sizes itself: a 400px budget for the visualization (axes + plot), and titles, legends, and other chrome grow the figure on top of that. Give the wrapper an explicit height when you need the chart to fit a fixed slot.
The fixed-height wrapper is optional. Without one, the chart sizes itself: a 400px budget for the visualization (axes + plot), and titles, legends, and other chrome grow the figure on top of that. Give the wrapper an explicit height and set `chromeLayout: 'subtract'` when the chart must fit an exact slot; otherwise annotation notes can grow the figure.

## Features

Expand Down Expand Up @@ -115,6 +115,7 @@ Release candidates publish under the `next` dist-tag, so a plain install always
| [Accessibility](docs/accessibility.md) | What's automatic, what's opt-in (alt text, pattern fills, contrast warnings), WCAG mapping |
| [Agent patterns](docs/agent-patterns.md) | Visualization patterns and cookbook for LLM-generated charts |
| [Architecture](docs/architecture.md) | Compilation pipeline, package boundaries, design decisions |
| [Migrating to v9](docs/migrating-v9.md) | Annotation defaults, transaction editing, theme precedence, and retired APIs |
| [Migrating to v8](docs/migrating-v8.md) | Breaking changes, before/after specs, and jq codemod recipes for the v8 upgrade |
| [Theming tokens](docs/theming-tokens.md) | Design token system, `TokenValue` light/dark pairs, `seriesStrategy`, named presets |
| [Contributing](CONTRIBUTING.md) | Setup, running tests, adding chart types, PR guidelines |
Expand Down
4 changes: 4 additions & 0 deletions bunfig.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[install]
# Keep tools that require TypeScript's JS API on the root JS compiler alias.
# Isolated installs hoist the native CLI-only package into .bun/node_modules.
linker = "hoisted"
2 changes: 1 addition & 1 deletion docs/accessibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Every chart gets these without configuration, in the vanilla adapter and all fra
- **Alt text**: a generated description of the chart (mark type, title, data range, series, point count) set as `aria-label` on the SVG with `role="img"`.
- **Screen reader data table**: a visually hidden HTML `<table>` with the chart's data rendered alongside the SVG, so screen reader users can read the actual values.
- **Per-mark ARIA labels**: each bar, slice, and point carries a descriptive label for assistive technology.
- **Keyboard navigation**: the chart container is focusable; arrow keys move between marks, Enter/Space shows the tooltip, Escape dismisses it. Focus indicators use the `--oc-focus` token, which has distinct light and dark values so the ring is visible in both modes.
- **Keyboard navigation**: the chart container is focusable; arrow keys move between marks, Enter/Space shows the tooltip, Escape dismisses it. In edit mode (`interaction: 'edit'`, or an `onEdit` handler) arrows nudge the selected annotation instead, so reader navigation is off; `'read'` and `'select'` keep it. Focus indicators use the `--oc-focus` token, which has distinct light and dark values so the ring is visible in both modes.
- **Dismissible tooltips**: Escape hides a visible tooltip even while hovering, without moving the pointer (WCAG 1.4.13).
- **Data tables**: semantic HTML (`<table>`, `<th scope="col">`), ARIA grid roles, sort state attributes, and a live region announcing sort/search changes.

Expand Down
6 changes: 4 additions & 2 deletions docs/agent-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ All specs use realistic data with plausible numbers. Copy and adapt.
}
```

**Multi-series variant:** Add a `color` encoding to split by category. Keep to 5 series max for readability.
**Multi-series variant:** Add a `color` encoding to split by category. Keep to 5 series max for readability. Use the legend or `endpointLabels` to identify series; legacy line-end labels and manual series-label offsets are removed.

```json
{
Expand Down Expand Up @@ -182,7 +182,7 @@ All specs use realistic data with plausible numbers. Copy and adapt.
{ "browser": "Other", "share": 8.4 }
],
"encoding": {
"y": { "field": "share", "type": "quantitative" },
"theta": { "field": "share", "type": "quantitative" },
"color": { "field": "browser", "type": "nominal" }
},
"chrome": {
Expand Down Expand Up @@ -553,6 +553,8 @@ All specs use realistic data with plausible numbers. Copy and adapt.
| `range` | Time periods, regions, phases | Use low `opacity` (0.05-0.1) so data is still visible. |
| `refline` | Thresholds, baselines, averages | Use `dashed` or `dotted` style to visually separate from data. |

**Responsive annotation defaults:** Omit offsets and anchors when automatic placement is sufficient. Lower `priority` values stay inline first; compact charts keep one eligible text callout and put the rest in wrapped notes, including subtitles. `responsive: false` pins a callout inline and can cause overlap at narrow widths. Use `maxWidth: null` or `responsive.compactAnnotationLimit: null` only when an unlimited layout is intentional. Source-order note numbers may have gaps. Curves do not imply arrows; use `{ "type": "curve", "arrow": true }` explicitly.

**Combining annotations:** Layer them. Ranges go behind the data (low opacity). Reference lines mark horizontal/vertical thresholds. Text callouts highlight specific inflection points or notable data values. This layering creates visual depth.

---
Expand Down
Loading
Loading