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
5 changes: 5 additions & 0 deletions .changeset/control-hover-passthrough.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/charts': minor
---

Add a shared `hover: 'capture' | 'passthrough'` option to `brushX` and `zoomX`. With `passthrough`, idle hover over the plot still focuses points and shows the tooltip, so brush or zoom can live on the same plot as point tooltips. Drags, clicks, wheel, keyboard input, and the brush selection and handles stay with the control. The default `capture` keeps the current behavior.
29 changes: 29 additions & 0 deletions API-FRICTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,7 @@ Each entry records:
| F-312 | Tick stubs could not be styled independently | API | resolved |
| F-313 | Initial observer precision cancelled entrance motion | Tooling | resolved |
| F-314 | Framework ticks and immediate focus layers were unclear | Documentation | resolved |
| F-315 | Brush and zoom hid point focus on their own plot | API | resolved |

## Findings

Expand Down Expand Up @@ -9317,3 +9318,31 @@ Each entry records:
direction changes. A focus dot appears during entrance while the data path
and its running animation remain intact. Concurrent inline states remain a
separate feature; this addresses the documented alternatives requested.

### F-315 - Brush and zoom hid point focus on their own plot

- Status: resolved
- Severity: medium
- Owner: API
- Observed in: a consumer dashboard on 0.18.0 that combined `brushX` or
`zoomX` with point tooltips on one time-series plot
- Friction: with either control on the main plot, hovering the plot showed no
point focus or tooltip. The zoom hit rectangle and the brush overlay sit
above the plot and capture every pointer event, and the DOM host clears
point focus whenever the pointer target is inside a control. The
application moved brush and zoom into a separate overview chart to keep
tooltips working.
- Decision: add one shared `hover: 'capture' | 'passthrough'` option to both
controls, typed `ChartControlHover`. The default `capture` keeps the old
behavior. `passthrough` lets idle hover over the zoom surface, or over the
blank brush overlay, resolve point focus and the tooltip through the host's
control-containment check. Brush selections and handles, active drags and
touch gestures, clicks, wheel input, and keyboard input stay with the
control. Hover is the only forwarded input, so one click never both selects
a point and starts a brush.
- Verification: table-driven zoom and brush tests cover the default, explicit
`capture`, and `passthrough`: idle hover focuses the nearest point only with
`passthrough`, a brush selection keeps capturing, and focus clears during
an active pan or brush drag and returns on the next idle hover. The
interaction guide has a runnable one-plot `zoomX` and `brushX` example with
point tooltips.
16 changes: 8 additions & 8 deletions benchmarks/bundle-size/universal-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,24 @@
"gzip": 30414
},
"TanStack DOM host": {
"bytes": 82961,
"gzip": 29116
"bytes": 82995,
"gzip": 29135
},
"React adapter": {
"bytes": 85286,
"gzip": 30070
"bytes": 85320,
"gzip": 30095
},
"React line consumer": {
"bytes": 108945,
"gzip": 39420
"bytes": 108979,
"gzip": 39441
},
"Compact-scale line scene": {
"bytes": 35859,
"gzip": 12797
},
"React compact-scale line consumer": {
"bytes": 91443,
"gzip": 32305
"bytes": 91477,
"gzip": 32323
},
"Custom-scale line scene": {
"bytes": 34043,
Expand Down
126 changes: 63 additions & 63 deletions benchmarks/comparison/bundle-baseline.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"schemaVersion": 4,
"generatedAt": "2026-10-13T23:35:26.997Z",
"generatedAt": "2026-10-14T00:02:00.663Z",
"packageVersions": {
"tanstack": "1.1.0",
"chartjs": "4.5.1",
Expand All @@ -11,8 +11,8 @@
"sources": {
"tanstack": {
"kind": "workspace",
"revision": "a691036b07655adda97aa0dc3052428989e2b811",
"inputDigest": "sha256:390854b69e6ac7c4453ba8bcc75dfd5240f70bbd9f69efdc8d25c7fba528d951"
"revision": "3f579fd6a4057b444eb35baf7d3eb1d95347f66f",
"inputDigest": "sha256:a407dcfc0ad7cdda7e2869906d33cda8af55faa4a93d4a5512cd9b1ac1f1f337"
},
"chartjs": {
"kind": "package",
Expand Down Expand Up @@ -45,88 +45,88 @@
},
"bundles": {
"tanstack-line-basic": {
"minifiedBytes": 119233,
"gzipBytes": 43211,
"brotliBytes": 38200,
"incrementalGzipBytes": 43211,
"incrementalBrotliBytes": 38200
"minifiedBytes": 119267,
"gzipBytes": 43235,
"brotliBytes": 38218,
"incrementalGzipBytes": 43235,
"incrementalBrotliBytes": 38218
},
"tanstack-line-interactive": {
"minifiedBytes": 124980,
"gzipBytes": 45122,
"brotliBytes": 39727,
"incrementalGzipBytes": 45122,
"incrementalBrotliBytes": 39727
"minifiedBytes": 125014,
"gzipBytes": 45149,
"brotliBytes": 39762,
"incrementalGzipBytes": 45149,
"incrementalBrotliBytes": 39762
},
"tanstack-line-advanced": {
"minifiedBytes": 132176,
"gzipBytes": 47454,
"brotliBytes": 41771,
"incrementalGzipBytes": 47454,
"incrementalBrotliBytes": 41771
"minifiedBytes": 132210,
"gzipBytes": 47475,
"brotliBytes": 41789,
"incrementalGzipBytes": 47475,
"incrementalBrotliBytes": 41789
},
"tanstack-bar-basic": {
"minifiedBytes": 131670,
"gzipBytes": 47913,
"brotliBytes": 42147,
"incrementalGzipBytes": 47913,
"incrementalBrotliBytes": 42147
"minifiedBytes": 131706,
"gzipBytes": 47939,
"brotliBytes": 42142,
"incrementalGzipBytes": 47939,
"incrementalBrotliBytes": 42142
},
"tanstack-bar-interactive": {
"minifiedBytes": 136276,
"gzipBytes": 49307,
"brotliBytes": 43250,
"incrementalGzipBytes": 49307,
"incrementalBrotliBytes": 43250
"minifiedBytes": 136312,
"gzipBytes": 49330,
"brotliBytes": 43308,
"incrementalGzipBytes": 49330,
"incrementalBrotliBytes": 43308
},
"tanstack-bar-advanced": {
"minifiedBytes": 136615,
"gzipBytes": 49453,
"brotliBytes": 43349,
"incrementalGzipBytes": 49453,
"incrementalBrotliBytes": 43349
"minifiedBytes": 136651,
"gzipBytes": 49477,
"brotliBytes": 43410,
"incrementalGzipBytes": 49477,
"incrementalBrotliBytes": 43410
},
"tanstack-area-basic": {
"minifiedBytes": 124413,
"gzipBytes": 45239,
"brotliBytes": 39996,
"incrementalGzipBytes": 45239,
"incrementalBrotliBytes": 39996
"minifiedBytes": 124447,
"gzipBytes": 45263,
"brotliBytes": 40064,
"incrementalGzipBytes": 45263,
"incrementalBrotliBytes": 40064
},
"tanstack-area-interactive": {
"minifiedBytes": 130168,
"gzipBytes": 47088,
"brotliBytes": 41480,
"incrementalGzipBytes": 47088,
"incrementalBrotliBytes": 41480
"minifiedBytes": 130202,
"gzipBytes": 47108,
"brotliBytes": 41537,
"incrementalGzipBytes": 47108,
"incrementalBrotliBytes": 41537
},
"tanstack-area-advanced": {
"minifiedBytes": 137540,
"gzipBytes": 49496,
"brotliBytes": 43606,
"incrementalGzipBytes": 49496,
"incrementalBrotliBytes": 43606
"minifiedBytes": 137574,
"gzipBytes": 49519,
"brotliBytes": 43593,
"incrementalGzipBytes": 49519,
"incrementalBrotliBytes": 43593
},
"tanstack-scatter-basic": {
"minifiedBytes": 120286,
"gzipBytes": 43572,
"brotliBytes": 38499,
"incrementalGzipBytes": 43572,
"incrementalBrotliBytes": 38499
"minifiedBytes": 120320,
"gzipBytes": 43597,
"brotliBytes": 38595,
"incrementalGzipBytes": 43597,
"incrementalBrotliBytes": 38595
},
"tanstack-scatter-interactive": {
"minifiedBytes": 126037,
"gzipBytes": 45469,
"brotliBytes": 40055,
"incrementalGzipBytes": 45469,
"incrementalBrotliBytes": 40055
"minifiedBytes": 126071,
"gzipBytes": 45498,
"brotliBytes": 40045,
"incrementalGzipBytes": 45498,
"incrementalBrotliBytes": 40045
},
"tanstack-scatter-advanced": {
"minifiedBytes": 126053,
"gzipBytes": 45476,
"brotliBytes": 40107,
"incrementalGzipBytes": 45476,
"incrementalBrotliBytes": 40107
"minifiedBytes": 126087,
"gzipBytes": 45504,
"brotliBytes": 40133,
"incrementalGzipBytes": 45504,
"incrementalBrotliBytes": 40133
},
"chartjs-line-basic": {
"minifiedBytes": 137909,
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/conformance/previews/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"schemaVersion": 1,
"width": 288,
"height": 192,
"sourceHash": "82eecd6c1fbdca5e857b965e09bb53c550520b2e596fbc4e9850266f03fdb9b4",
"sourceHash": "2873c5f0f113e63c96e17411cd08efce587d73f98189c45dc6af35609b302168",
"assets": [
{
"id": "01-line-gaps",
Expand Down
14 changes: 7 additions & 7 deletions docs/comparison.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ turning untested behavior into a checkmark.

| Library | Package | Measured source |
| -------------------------------------------------------------------------------------- | -------------------- | ------------------- |
| [TanStack Charts](./overview.md) | `@tanstack/charts` | workspace `a691036` |
| [TanStack Charts](./overview.md) | `@tanstack/charts` | workspace `3f579fd` |
| [Chart.js](https://www.chartjs.org/docs/latest/) | `chart.js` | npm `4.5.1` |
| [Apache ECharts](https://echarts.apache.org/handbook/en/best-practices/canvas-vs-svg/) | `echarts` | npm `6.1.0` |
| [Recharts](https://recharts.github.io/en-US/) | `recharts` | npm `3.10.1` |
| [Observable Plot](https://observablehq.com/plot/features/plots) | `@observablehq/plot` | npm `0.6.17` |

The competitor versions are exact package pins, not latest versions inferred
at page render time. The measured TanStack workspace revision is `a691036`.
at page render time. The measured TanStack workspace revision is `3f579fd`.

## Capability matrix

Expand Down Expand Up @@ -90,7 +90,7 @@ output model.

## Bundle snapshot

Baseline date: `2026-10-13`.
Baseline date: `2026-10-14`.

Controlled ranges cover 12 independently built, minified browser consumers:
line, bar, area, and scatter at basic, interactive, and advanced tiers. Only
Expand All @@ -106,10 +106,10 @@ Vega-Lite, AG Charts, and uPlot main exports were read from Bundlephobia on July

| Library | Bundle size | React externalized | Evidence |
| ------------------ | -------------------------------------- | -------------------: | ---------------------------------------------------------- |
| TanStack Charts | 43.21–49.50 kB gzip | Not applicable | Controlled suite |
| TanStack Charts | 43.23–49.52 kB gzip | Not applicable | Controlled suite |
| D3 | 90 KB gzip | — | External main export |
| Chart.js | 45.78–59.61 kB gzip | — | Controlled suite |
| Apache ECharts | 156.77–177.34 kB gzip | — | Controlled suite |
| Chart.js | 45.78–59.61 kB gzip | Not applicable | Controlled suite |
| Apache ECharts | 156.77–177.34 kB gzip | Not applicable | Controlled suite |
| Recharts | 156.76–172.30 kB gzip | 97.24–112.60 kB gzip | Controlled suite |
| visx | 49 KB gzip | — | External `@visx/xychart` main export |
| Plotly.js | ~250 kB partial; ~3.6 MB full min+gzip | — | [Vendor distribution figures](https://plotly.com/graphs/) |
Expand All @@ -120,7 +120,7 @@ Vega-Lite, AG Charts, and uPlot main exports were read from Bundlephobia on July
| Victory | 105 KB gzip | — | External main export |
| uPlot | 22 KB gzip | — | External `uplot@1.6.32` main export |
| Vega-Lite | 87 KB gzip | — | External `vega-lite@6.4.3`; excludes the peer Vega runtime |
| Observable Plot | 85.34–94.15 kB gzip | — | Controlled suite |
| Observable Plot | 85.34–94.15 kB gzip | Not applicable | Controlled suite |
| Bklit UI | — | — | Registry-installed source; no fixed package bundle |
| AG Charts | 367 KB gzip | — | External `ag-charts-community@14.0.2` main export |

Expand Down
Loading