Skip to content
Merged
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
14 changes: 14 additions & 0 deletions .changeset/explicit-d3-capabilities.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
'@tanstack/charts': patch
'@tanstack/charts-scales': patch
---

Remove incidental `d3-array` usage from nearest-point lookup, quantile legend
thresholds, and compact numeric ticks. These paths now use package-owned
implementations with D3 parity coverage, and compact scales no longer have a
production D3 dependency.

Numeric-bin and stack transforms, polar and curve features, and geo features
continue to own their tree-shakable `d3-array`, `d3-shape`, or `d3-geo`
implementations as normal dependencies. No peer dependency, caller-supplied
capability, or public API migration is required.
41 changes: 35 additions & 6 deletions API-FRICTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Each entry records:
| F-007 | Runtime and adapters bypassed strict scales | API | resolved |
| F-008 | D3 motion would currently burden every DOM host | API | resolved |
| F-009 | Color semantics were overloaded onto grouping and paint | API | resolved |
| F-010 | D3 curves require one TanStack grammar bridge | API | monitoring |
| F-010 | D3 curves require one TanStack grammar bridge | API | resolved |
| F-011 | Adapters performed dynamic preparation twice | API | resolved |
| F-012 | Render callbacks omit diagnostic metrics | API | monitoring |
| F-013 | Bar series identity also changed bar geometry | API | resolved |
Expand Down Expand Up @@ -200,6 +200,7 @@ Each entry records:
| F-162 | Focus styling required duplicate marks | API | resolved |
| F-163 | Cross-row transforms lacked a public ownership boundary | API | resolved |
| F-164 | Sankey widths required a custom scene renderer | API | resolved |
| F-165 | Incidental D3 utilities leaked into core paths | API/Tooling | resolved |

## Findings

Expand Down Expand Up @@ -417,15 +418,18 @@ Each entry records:

### F-010 — D3 curves require one TanStack grammar bridge

- Status: monitoring
- Status: resolved
- Severity: low
- Observed in: optional curve integration
- Friction: authors write `d3Curve(curveMonotoneX)` instead of supplying the D3
curve factory directly.
- Decision: keep the bridge while it prevents straight lines and areas from
importing `d3-shape` and gives one curve value both line and area semantics.
- Follow-up: measure authoring errors before considering a direct curve-factory
overload or a separate curved mark.
- Decision: keep the bridge while it gives one curve value both line and area
semantics. Keep the convenient root and universal exports alongside the exact
`@tanstack/charts/d3/shape` entry; supported bundlers remove the bridge and
its D3 line and area generators when unused.
- Verification: direct D3 curve entries, Cartesian curve tests, packed
declarations, and documentation examples cover both barrel and exact-subpath
forms.

### F-011 — Adapters performed dynamic preparation twice on mount

Expand Down Expand Up @@ -3957,3 +3961,28 @@ Each entry records:
graph passes its type, package, bundle, documentation, catalog, and framework
gates. Browser conformance passes both Sankey cases at 320px and 640px with
clean types and 98.3% mean frame-relative geometry similarity.

### F-165 — Incidental D3 utilities leaked into core paths

- Status: resolved
- Severity: high
- Owner: API/Tooling
- Observed in: auditing whether ordinary chart paths retain incidental D3
- Friction: nearest-point lookup and legend thresholds used small `d3-array`
utilities, while compact linear scales imported D3 tick math. Those imports
leaked D3 into common paths despite being easy to own locally. Numeric-bin,
stack, polar, geo, and curve modules are deliberately D3-backed and expose
strict D3 semantics rather than credible interchangeable APIs.
- Decision: implement nearest-point, quantile, and compact tick math locally.
Keep `d3-array` inside numeric-bin transforms, `d3-shape` inside stack,
polar, and curve features, and `d3-geo` inside geo features as normal
dependencies, not peers or caller-supplied `use` capabilities. Keep the
tree-shakable D3 curve bridges available from the root, universal, and exact
barrels. Compact scales declare no production D3 dependency.
- Verification: core, geo, polar, curve, compact-scale, and type-contract tests
pass. The independently structured tick helpers match `d3-array` across
fixed edge cases and 2,000 deterministic generated domains. Bundle
retained-input gates reject D3 geometry from ordinary root consumers and
every `d3-*` module plus `internmap` from compact consumers, while selected
transform, polar, geo, and curve features retain their owned D3
implementation.
28 changes: 14 additions & 14 deletions benchmarks/bundle-size/universal-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,43 @@
"bundles": {
"D3-scale line scene": {
"bytes": 39086,
"gzip": 15178
"gzip": 15175
},
"D3-scale line + static SVG": {
"bytes": 42027,
"gzip": 16267
"gzip": 16268
},
"Representative marks": {
"bytes": 57688,
"gzip": 21392
"gzip": 21428
},
"TanStack DOM host": {
"bytes": 39048,
"gzip": 14334
"bytes": 38770,
"gzip": 14229
},
"React adapter": {
"bytes": 41216,
"gzip": 15037
"bytes": 40937,
"gzip": 14924
},
"React line consumer": {
"bytes": 63725,
"gzip": 24054
"bytes": 63516,
"gzip": 23973
},
"Compact-scale line scene": {
"bytes": 21523,
"gzip": 8112
"bytes": 21550,
"gzip": 8118
},
"React compact-scale line consumer": {
"bytes": 46285,
"gzip": 16986
"bytes": 46033,
"gzip": 16876
},
"Custom-scale line scene": {
"bytes": 19734,
"gzip": 7371
},
"D3 linear-scale line scene": {
"bytes": 39018,
"gzip": 15142
"gzip": 15139
}
}
}
126 changes: 63 additions & 63 deletions benchmarks/comparison/bundle-baseline.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"schemaVersion": 3,
"generatedAt": "2026-07-31T21:26:25.210Z",
"generatedAt": "2026-07-31T21:48:07.702Z",
"packageVersions": {
"tanstack": "0.2.0",
"tanstack": "0.3.0",
"chartjs": "4.5.1",
"echarts": "6.1.0",
"recharts": "3.10.1",
Expand All @@ -11,7 +11,7 @@
"sources": {
"tanstack": {
"kind": "workspace",
"revision": "e997c89bb84d1d468c12e58e44bd45c6d50fd5c3"
"revision": "c422a2ce45799d4edd63fdbde7ecb31daa3dae31"
},
"chartjs": {
"kind": "package",
Expand Down Expand Up @@ -44,88 +44,88 @@
},
"bundles": {
"tanstack-line-basic": {
"minifiedBytes": 72320,
"gzipBytes": 27357,
"brotliBytes": 24261,
"incrementalGzipBytes": 27357,
"incrementalBrotliBytes": 24261
"minifiedBytes": 72114,
"gzipBytes": 27318,
"brotliBytes": 24182,
"incrementalGzipBytes": 27318,
"incrementalBrotliBytes": 24182
},
"tanstack-line-interactive": {
"minifiedBytes": 77498,
"gzipBytes": 29075,
"brotliBytes": 25671,
"incrementalGzipBytes": 29075,
"incrementalBrotliBytes": 25671
"minifiedBytes": 77285,
"gzipBytes": 28970,
"brotliBytes": 25539,
"incrementalGzipBytes": 28970,
"incrementalBrotliBytes": 25539
},
"tanstack-line-advanced": {
"minifiedBytes": 84679,
"gzipBytes": 31380,
"brotliBytes": 27671,
"incrementalGzipBytes": 31380,
"incrementalBrotliBytes": 27671
"minifiedBytes": 84466,
"gzipBytes": 31307,
"brotliBytes": 27553,
"incrementalGzipBytes": 31307,
"incrementalBrotliBytes": 27553
},
"tanstack-bar-basic": {
"minifiedBytes": 79327,
"gzipBytes": 30044,
"brotliBytes": 26537,
"incrementalGzipBytes": 30044,
"incrementalBrotliBytes": 26537
"minifiedBytes": 79117,
"gzipBytes": 29968,
"brotliBytes": 26390,
"incrementalGzipBytes": 29968,
"incrementalBrotliBytes": 26390
},
"tanstack-bar-interactive": {
"minifiedBytes": 83364,
"gzipBytes": 31284,
"brotliBytes": 27529,
"incrementalGzipBytes": 31284,
"incrementalBrotliBytes": 27529
"minifiedBytes": 83147,
"gzipBytes": 31184,
"brotliBytes": 27466,
"incrementalGzipBytes": 31184,
"incrementalBrotliBytes": 27466
},
"tanstack-bar-advanced": {
"minifiedBytes": 83703,
"gzipBytes": 31434,
"brotliBytes": 27623,
"incrementalGzipBytes": 31434,
"incrementalBrotliBytes": 27623
"minifiedBytes": 83486,
"gzipBytes": 31333,
"brotliBytes": 27583,
"incrementalGzipBytes": 31333,
"incrementalBrotliBytes": 27583
},
"tanstack-area-basic": {
"minifiedBytes": 76134,
"gzipBytes": 28817,
"brotliBytes": 25536,
"incrementalGzipBytes": 28817,
"incrementalBrotliBytes": 25536
"minifiedBytes": 75923,
"gzipBytes": 28763,
"brotliBytes": 25453,
"incrementalGzipBytes": 28763,
"incrementalBrotliBytes": 25453
},
"tanstack-area-interactive": {
"minifiedBytes": 81316,
"gzipBytes": 30565,
"brotliBytes": 26893,
"incrementalGzipBytes": 30565,
"incrementalBrotliBytes": 26893
"minifiedBytes": 81098,
"gzipBytes": 30458,
"brotliBytes": 26812,
"incrementalGzipBytes": 30458,
"incrementalBrotliBytes": 26812
},
"tanstack-area-advanced": {
"minifiedBytes": 88679,
"gzipBytes": 32946,
"brotliBytes": 28983,
"incrementalGzipBytes": 32946,
"incrementalBrotliBytes": 28983
"minifiedBytes": 88465,
"gzipBytes": 32852,
"brotliBytes": 28903,
"incrementalGzipBytes": 32852,
"incrementalBrotliBytes": 28903
},
"tanstack-scatter-basic": {
"minifiedBytes": 72025,
"gzipBytes": 27255,
"brotliBytes": 24145,
"incrementalGzipBytes": 27255,
"incrementalBrotliBytes": 24145
"minifiedBytes": 71819,
"gzipBytes": 27220,
"brotliBytes": 24141,
"incrementalGzipBytes": 27220,
"incrementalBrotliBytes": 24141
},
"tanstack-scatter-interactive": {
"minifiedBytes": 77203,
"gzipBytes": 28999,
"brotliBytes": 25586,
"incrementalGzipBytes": 28999,
"incrementalBrotliBytes": 25586
"minifiedBytes": 76990,
"gzipBytes": 28892,
"brotliBytes": 25445,
"incrementalGzipBytes": 28892,
"incrementalBrotliBytes": 25445
},
"tanstack-scatter-advanced": {
"minifiedBytes": 77219,
"gzipBytes": 29004,
"brotliBytes": 25583,
"incrementalGzipBytes": 29004,
"incrementalBrotliBytes": 25583
"minifiedBytes": 77006,
"gzipBytes": 28897,
"brotliBytes": 25468,
"incrementalGzipBytes": 28897,
"incrementalBrotliBytes": 25468
},
"chartjs-line-basic": {
"minifiedBytes": 137909,
Expand Down
6 changes: 3 additions & 3 deletions docs/comparison.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ evidence without turning untested behavior into a checkmark.

| Library | Package | Measured source |
| -------------------------------------------------------------------------------------- | -------------------- | ------------------- |
| [TanStack Charts](./overview.md) | `@tanstack/charts` | workspace `e997c89` |
| [TanStack Charts](./overview.md) | `@tanstack/charts` | workspace `c422a2c` |
| [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 `e997c89`.
at page render time. The measured TanStack workspace revision is `c422a2c`.

## Capability matrix

Expand Down Expand Up @@ -59,7 +59,7 @@ that lane externalizes React and React DOM.

| Library | Full cold-page gzip | React externalized |
| --------------- | ------------------: | -----------------: |
| TanStack Charts | 26.62–32.17 KiB | — |
| TanStack Charts | 26.58–32.08 KiB | — |
| Chart.js | 44.70–58.21 KiB | — |
| Apache ECharts | 153.10–173.18 KiB | — |
| Recharts | 153.08–168.27 KiB | 94.96–109.96 KiB |
Expand Down
6 changes: 6 additions & 0 deletions docs/concepts/scales-and-d3.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ TanStack Charts uses an explicit algorithm layer:
Both scale implementations use callable, copyable scale objects. There is no
hidden D3 umbrella import.

`@tanstack/charts` declares `d3-array`, `d3-shape`, and `d3-geo` because its
numeric-bin and stack transforms, polar and D3 curve features, and geo features
own those implementations. They are not peers and require no `use`
configuration. Bundlers tree-shake unused algorithms and geometry, and exact
feature subpaths remain available when an application wants a narrower import.

## Direct dependency ownership

If application source imports a `d3-*` module, declare that module and its matching TypeScript package directly:
Expand Down
6 changes: 6 additions & 0 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ the output of D3 transforms directly. Your application must declare every
`d3-*` module that its source imports. Strict package managers do not expose
transitive dependencies as an application import contract.

The core package declares the `d3-array`, `d3-shape`, and `d3-geo`
implementations owned by its numeric-bin and stack transforms, polar and D3
curve features, and geo features. They are normal dependencies, not peer
requirements, and bundlers remove unused algorithms and geometry from
application bundles.

A typical cartesian chart uses:

```sh
Expand Down
6 changes: 3 additions & 3 deletions packages/charts-core/docs/comparison.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ evidence without turning untested behavior into a checkmark.

| Library | Package | Measured source |
| -------------------------------------------------------------------------------------- | -------------------- | ------------------- |
| [TanStack Charts](./overview.md) | `@tanstack/charts` | workspace `e997c89` |
| [TanStack Charts](./overview.md) | `@tanstack/charts` | workspace `c422a2c` |
| [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 `e997c89`.
at page render time. The measured TanStack workspace revision is `c422a2c`.

## Capability matrix

Expand Down Expand Up @@ -59,7 +59,7 @@ that lane externalizes React and React DOM.

| Library | Full cold-page gzip | React externalized |
| --------------- | ------------------: | -----------------: |
| TanStack Charts | 26.62–32.17 KiB | — |
| TanStack Charts | 26.58–32.08 KiB | — |
| Chart.js | 44.70–58.21 KiB | — |
| Apache ECharts | 153.10–173.18 KiB | — |
| Recharts | 153.08–168.27 KiB | 94.96–109.96 KiB |
Expand Down
Loading