Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/bright-axis-titles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@tanstack/charts': minor
---

Allow Cartesian axis titles to configure font size, font weight, fill, and
opacity while retaining automatic guide layout.
7 changes: 7 additions & 0 deletions .changeset/focus-ring-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@tanstack/charts': minor
---

Allow definitions and themes to configure the built-in focus ring's radius,
stroke width, fill, and stroke while preserving the existing boolean behavior
and defaults.
6 changes: 6 additions & 0 deletions .changeset/line-cap-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@tanstack/charts': minor
---

Add `lineCap` and `lineJoin` options to `lineY` and `lineX`. Both options use
the renderer-supported `SceneStyle` values and continue to default to `round`.
9 changes: 9 additions & 0 deletions .changeset/rtl-axis-side-gutter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@tanstack/charts': patch
---

Reserve the correct gutter for a positioned axis in a right-to-left container.
A y scale with `side: 'right'` anchored its tick labels for a left-to-right
inline direction, so an RTL host painted them across the plot instead of beside
it. Automatic anchors, fallback measurement, Canvas, native rendering, and SVG
export now agree on logical `start` and `end` anchors for the host direction.
110 changes: 108 additions & 2 deletions API-FRICTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ observed difficulty from examples, production migrations, tests, and agent
evaluations so later API, documentation, and TanStack Intent skill work is
based on evidence.

Last updated: 2026-08-26
Last updated: 2026-09-03

## Triage rule

Expand Down Expand Up @@ -331,6 +331,10 @@ Each entry records:
| F-292 | Fixed preview paints ignored the selected site theme | Tooling | resolved |
| F-293 | Root scale slots blocked named axes | API | resolved |
| F-294 | Automatic mark renderers imposed shared host plumbing | API | resolved |
| F-295 | Line marks forced round endpoints | API | resolved |
| F-296 | Axis titles could not carry authored typography or paint | API | resolved |
| F-297 | Focus ring paint required generated SVG selectors | API | resolved |
| F-298 | Physical axis sides depended on logical text direction | API | resolved |

## Findings

Expand Down Expand Up @@ -7195,7 +7199,7 @@ Each entry records:
threshold generators, and consumer-called service methods as classified
exceptions.
- Verification: the public callback inventory follows exported types,
functions, and values into nested package-owned types. It classifies all 778
functions, and values into nested package-owned types. It classifies all 799
reachable callable surfaces, including Alpine's external directive protocol,
Vue's nested tooltip slot, live-chart interaction and presentation service
handles, cursor controllers and host sessions, focus-guide resolvers, the
Expand Down Expand Up @@ -7228,6 +7232,10 @@ Each entry records:
supplies one stable context shape and all first-party examples use it.
- Follow-up verification: transform regressions cover datum, index, and complete
data; the public callback inventory and root TypeScript pass.
- Follow-up verification: narrowing the public composable-responsive chart
callback exposed it as a separately reachable surface. It is classified as an
application callback with one `ChartBuildContext` object, and the full
799-surface inventory passes.

### F-239 — Example keys collapsed distinct source rows

Expand Down Expand Up @@ -8505,3 +8513,101 @@ Each entry records:
React Native Metro gates, and framework package checks pass. Bundle boundary
checks keep SVG-only entries free of Canvas and measure the opt-in mixed
representative and React consumers at 35.68 KiB and 41.63 KiB gzip.

### F-295 - Line marks forced round endpoints

- Status: resolved
- Severity: medium
- Owner: API
- Observed in: migrating Rewardo's composed statistics chart from Recharts
- Friction: `lineY` and `lineX` exposed stroke width, opacity, dash pattern,
and curve options, but hard-coded round caps and joins. Reproducing the
previous Recharts line, which inherited SVG's butt cap, required replacing
or patching rendered output.
- Decision: expose `lineCap` and `lineJoin` on the shared line options using
the values from `SceneStyle`. Preserve round as the default for both options.
- Verification: public type tests bind both line APIs to the scene renderer's
supported values. Scene, SVG, Canvas, motion, export, and React Native tests
preserve configured values. The Rewardo chart can use `lineCap: 'butt'` on
its existing pink `lineY` mark.

### F-296 - Axis titles could not carry authored typography or paint

- Status: resolved
- Severity: medium
- Owner: API
- Observed in: migrating Rewardo dashboard charts to TanStack Charts on the
`experimental-tanstack-charts` branch and issue #93
- Friction: tick labels accepted font size, weight, and opacity, but
`axis.label` accepted only text, offset, and motion. Rewardo's 14-pixel chart
typography and foreground treatment therefore required a generated-node CSS
override that Canvas and React Native could not honor.
- Decision: extend the existing axis-label object with `fontSize`,
`fontWeight`, `fill`, and `opacity`. Keep the string form and every omitted
object field on the existing defaults. Resolve the options into the shared
scene label so automatic guide measurement and every renderer use one
contract.
- Verification: scene layout covers configured typography, paint, default
compatibility, automatic margins, and shared facet titles. SVG, Canvas,
React Native, and motion tests exercise the same public definition. The
composed weather catalog case uses independently styled titles on three y
axes without renderer callbacks or CSS selectors. The reviewed shared-path
delta is 169 minified bytes and 29 gzip bytes for the locked line plus static
SVG consumer. A dedicated styled-title fixture adds 0.06 KiB gzip and no
retained modules over that consumer.

### F-297 - Focus ring paint required generated SVG selectors

- Status: resolved
- Severity: medium
- Owner: API
- Observed in: Rewardo's TanStack Charts migration and issue #94
- Friction: the built-in primary-point ring exposed only a boolean definition
option. Rewardo had to target `.ts-chart__focus-layer--default circle` to
reduce the fixed 5-pixel radius, tying application styling to generated SVG
structure and leaving Canvas and React Native without the same control.
- Decision: extend `focusRing` to accept a reusable `ChartFocusRingOptions`
object with radius, stroke width, fill, and stroke on both chart definitions
and themes. Preserve `true`, `false`, and every existing default. An omitted
stroke keeps each point's resolved series color. An explicitly supplied
definition value takes precedence over the theme default. Composed views and
facets take that default from their outer host and reject child ownership
instead of silently discarding it while rebuilding the shared focus layer.
- Verification: scene, SVG, Canvas, and React Native tests cover configured
geometry and paint, theme defaults, definition override and disable behavior,
invalid theme geometry, outer view propagation, rejected child ownership,
series-color fallback, keyboard focus, and hidden accessibility presentation.
The catalog's pointer-tooltip case uses the object form, and the reference
docs record the complete contract. Against the reviewed feature baseline,
theme fallback adds 7 minified bytes and 5 gzip bytes to the locked D3 line
scene. The isolated facet entry adds 0.09 KiB gzip for explicit child
ownership validation.

### F-298 - Physical axis sides depended on logical text direction

- Status: resolved
- Severity: high
- Owner: API
- Observed in: issue #117, with a right-side y axis inside an RTL host
- Friction: automatic tick-label anchors encoded a physical outward placement
with logical `start` and `end` values that change sides under RTL. Browser
layout could therefore measure a right-axis label over the plot and reserve
no right gutter. Canvas paint, fallback measurement, exported SVG, and React
Native also disagreed about whether those values were logical or physical.
- Decision: keep scene anchors logical, map every automatic physical text
placement through the resolved inline direction, and normalize at renderer
boundaries that do not support logical anchors. This covers Cartesian axes,
crosshair labels, static legends, and polar outside labels. Carry the
resolved direction with the scene so direct static SVG rendering preserves
the same layout. Explicit authored anchors retain their SVG meaning.
- Verification: layout tests cover both y-axis sides, both rotated x-axis
directions, crosshair labels, categorical and quantitative legends, polar
outside labels, explicit anchors, and direct static SVG. Estimator, DOM
fallback, Canvas, SVG export, and React Native tests cover their
direction-specific boundaries. A Chromium reproduction with the issue's
data measures the same 51.24 px right gutter in LTR and RTL, keeps labels 8
px beyond the plot, and produces byte-identical LTR and RTL Canvas output.
A second Chromium pass confirms that direct static SVG matches the live
mount, the crosshair label stays 8 px left of the plot, quantitative legend
endpoints stay inside their physical bounds, and polar outside labels stay
8 px beyond both physical edges.
4 changes: 4 additions & 0 deletions benchmarks/bundle-size/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ gzip ceiling. The locked shared-host entries record the reviewed integration
cost, while noninteractive consumers retain only the small scene-compiler
portion of that contract.

Cartesian axis-title styling is also part of the shared scene contract. Its
static SVG fixture adds no retained modules over the ordinary line consumer
and keeps authored title options under a 0.25 KiB incremental gzip ceiling.

Continuous viewports and the controlled interaction controller are also part
of the default scene and host contracts. Default static SVG consumes scene
clips and gradients. Their reviewed shared-path cost is recorded in the locked
Expand Down
40 changes: 20 additions & 20 deletions benchmarks/bundle-size/universal-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,44 +3,44 @@
"policy": "Exact minified and gzip output for entries that optional features must not affect. Review every change before updating.",
"bundles": {
"D3-scale line scene": {
"bytes": 50813,
"gzip": 19070
"bytes": 51668,
"gzip": 19321
},
"D3-scale line + static SVG": {
"bytes": 55544,
"gzip": 20776
"bytes": 56479,
"gzip": 21089
},
"Representative marks": {
"bytes": 75617,
"gzip": 27582
"bytes": 76547,
"gzip": 27858
},
"TanStack DOM host": {
"bytes": 74947,
"gzip": 26053
"bytes": 75837,
"gzip": 26325
},
"React adapter": {
"bytes": 77130,
"gzip": 26831
"bytes": 78017,
"gzip": 27096
},
"React line consumer": {
"bytes": 100766,
"gzip": 36222
"bytes": 101674,
"gzip": 36491
},
"Compact-scale line scene": {
"bytes": 33265,
"gzip": 11935
"bytes": 34121,
"gzip": 12168
},
"React compact-scale line consumer": {
"bytes": 83266,
"gzip": 29144
"bytes": 84176,
"gzip": 29414
},
"Custom-scale line scene": {
"bytes": 31447,
"gzip": 11193
"bytes": 32303,
"gzip": 11426
},
"D3 linear-scale line scene": {
"bytes": 50745,
"gzip": 19032
"bytes": 51600,
"gzip": 19283
}
}
}
Loading