@kind-ui/charts 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +683 -1
  3. package/dist/activity-rings.d.ts +2 -1
  4. package/dist/activity-rings.js +6 -3
  5. package/dist/animation.d.ts +11 -3
  6. package/dist/animation.js +18 -10
  7. package/dist/area-chart.d.ts +3 -1
  8. package/dist/area-chart.js +15 -10
  9. package/dist/area-series.d.ts +9 -1
  10. package/dist/area-series.js +35 -8
  11. package/dist/bar-chart.d.ts +10 -3
  12. package/dist/bar-chart.js +17 -4
  13. package/dist/bar-material.js +2 -3
  14. package/dist/bar-series.d.ts +11 -1
  15. package/dist/bar-series.js +65 -13
  16. package/dist/box-plot.js +2 -2
  17. package/dist/category-cells.d.ts +7 -1
  18. package/dist/category-cells.js +32 -2
  19. package/dist/chart-background-pattern.d.ts +23 -0
  20. package/dist/chart-background-pattern.js +31 -0
  21. package/dist/chart-context.d.ts +6 -2
  22. package/dist/chart-interaction.d.ts +94 -0
  23. package/dist/chart-interaction.js +224 -0
  24. package/dist/combo-chart.d.ts +8 -5
  25. package/dist/combo-chart.js +14 -12
  26. package/dist/configured-line-chart.d.ts +5 -1
  27. package/dist/configured-line-chart.js +8 -5
  28. package/dist/emphasis.d.ts +5 -2
  29. package/dist/emphasis.js +18 -4
  30. package/dist/fill-pattern.d.ts +25 -0
  31. package/dist/fill-pattern.js +23 -0
  32. package/dist/heatmap.d.ts +4 -2
  33. package/dist/heatmap.js +34 -12
  34. package/dist/histogram-chart.js +10 -3
  35. package/dist/histogram-material.js +1 -2
  36. package/dist/index.d.ts +12 -3
  37. package/dist/index.js +8 -1
  38. package/dist/legend.d.ts +2 -0
  39. package/dist/legend.js +28 -7
  40. package/dist/line-chart.d.ts +13 -3
  41. package/dist/line-chart.js +54 -34
  42. package/dist/line-dash.d.ts +8 -0
  43. package/dist/line-dash.js +18 -0
  44. package/dist/line-material.d.ts +1 -1
  45. package/dist/line-material.js +1 -1
  46. package/dist/line-series.d.ts +9 -1
  47. package/dist/line-series.js +37 -11
  48. package/dist/loading-cartesian-designs.d.ts +6 -0
  49. package/dist/loading-cartesian-designs.js +123 -0
  50. package/dist/loading-motion.d.ts +37 -0
  51. package/dist/loading-motion.js +88 -0
  52. package/dist/loading-polar-designs.d.ts +6 -0
  53. package/dist/loading-polar-designs.js +95 -0
  54. package/dist/loading-skeleton.d.ts +29 -0
  55. package/dist/loading-skeleton.js +143 -0
  56. package/dist/loading-standalone-designs.d.ts +5 -0
  57. package/dist/loading-standalone-designs.js +126 -0
  58. package/dist/percent-stack.d.ts +18 -0
  59. package/dist/percent-stack.js +33 -0
  60. package/dist/pie-chart.d.ts +7 -3
  61. package/dist/pie-chart.js +37 -6
  62. package/dist/pie-material.js +1 -1
  63. package/dist/pie-pin-identity.d.ts +4 -0
  64. package/dist/pie-pin-identity.js +10 -0
  65. package/dist/pie-series.d.ts +4 -0
  66. package/dist/pie-series.js +138 -15
  67. package/dist/pie-tooltip-pin.d.ts +4 -0
  68. package/dist/pie-tooltip-pin.js +52 -0
  69. package/dist/point-marker.d.ts +19 -0
  70. package/dist/point-marker.js +19 -0
  71. package/dist/polar-chart.d.ts +14 -5
  72. package/dist/polar-chart.js +41 -10
  73. package/dist/polar-material.js +1 -1
  74. package/dist/polar-series.js +68 -22
  75. package/dist/radar-interaction.js +10 -4
  76. package/dist/radial-category.d.ts +2 -0
  77. package/dist/reveal-clip.d.ts +16 -0
  78. package/dist/reveal-clip.js +18 -0
  79. package/dist/root.d.ts +5 -9
  80. package/dist/root.js +46 -11
  81. package/dist/sankey-chart.d.ts +5 -1
  82. package/dist/sankey-chart.js +64 -13
  83. package/dist/sankey-colors.d.ts +3 -0
  84. package/dist/sankey-finish.d.ts +1 -1
  85. package/dist/sankey-finish.js +1 -1
  86. package/dist/sankey-interaction.d.ts +8 -0
  87. package/dist/sankey-interaction.js +32 -0
  88. package/dist/sankey-legend.d.ts +2 -1
  89. package/dist/sankey-legend.js +18 -3
  90. package/dist/sankey-node-label.d.ts +22 -0
  91. package/dist/sankey-node-label.js +34 -0
  92. package/dist/scatter-chart.d.ts +5 -3
  93. package/dist/scatter-chart.js +14 -4
  94. package/dist/scatter-material.js +1 -1
  95. package/dist/scatter-series.js +5 -3
  96. package/dist/series-color.d.ts +10 -0
  97. package/dist/series-color.js +48 -0
  98. package/dist/series-interaction.d.ts +12 -0
  99. package/dist/series-interaction.js +82 -0
  100. package/dist/series-label.d.ts +7 -0
  101. package/dist/series-label.js +18 -0
  102. package/dist/series-paint.d.ts +8 -0
  103. package/dist/series-paint.js +27 -0
  104. package/dist/styles.css +151 -6
  105. package/dist/stylesheet-warning.d.ts +2 -0
  106. package/dist/stylesheet-warning.js +86 -0
  107. package/dist/surface-material.js +1 -1
  108. package/dist/tooltip-content.d.ts +8 -1
  109. package/dist/tooltip-content.js +15 -4
  110. package/dist/tooltip.d.ts +3 -1
  111. package/dist/tooltip.js +8 -5
  112. package/dist/types.d.ts +11 -2
  113. package/dist/waterfall-chart.d.ts +4 -0
  114. package/dist/waterfall-chart.js +7 -0
  115. package/package.json +2 -2
  116. package/dist/bar-paper.d.ts +0 -2
  117. package/dist/bar-paper.js +0 -6
package/README.md CHANGED
@@ -28,7 +28,38 @@ npm install @kind-ui/charts
28
28
  ```
29
29
 
30
30
  Import `@kind-ui/charts/styles.css` once at your application entry. In Next.js,
31
- place interactive chart code in a client component.
31
+ import it in the root layout and place interactive chart code in a client component.
32
+ Development builds warn once per document if mounted chart roots lack the stylesheet,
33
+ after the document and pending stylesheets load. The warning does not run during SSR
34
+ or in production. Keep the import in copied examples.
35
+
36
+ CSS remains explicit: importing it from the ESM entry would break plain Node imports;
37
+ runtime injection would change CSS layer ordering and require inline-style CSP permission.
38
+ No styles are injected or fetched. Core styles and tokens remain in the stylesheet;
39
+ consumer overrides still win. If you intentionally replace all chart styles, set
40
+ `--kind-ui-styles-loaded: 1` on your chart roots to acknowledge that setup.
41
+ The diagnostic conservatively waits on unresolved stylesheet links. A link which
42
+ failed before mounting after document load can therefore defer the warning; it
43
+ does not guess a timeout and warn while a slow stylesheet is still loading.
44
+
45
+ ## Materials
46
+
47
+ Charts support Default, Clay and Glow. The Default material keeps the public
48
+ `plain` token and native chart paint; `material="clay"` and `material="glow"`
49
+ opt into decorative finishes. Sankey uses the same tokens through `finish`.
50
+ Omitting either prop selects Default. Paper has been removed in the 0.3.0 API;
51
+ replace `material="paper"` or `finish="paper"` with `"plain"`, `"clay"` or `"glow"`.
52
+ Consumer-supplied shapes, filters and paint retain ownership.
53
+
54
+ ## Series labels
55
+
56
+ Configuration keys must match the series `dataKey` (or its explicit `seriesKey`).
57
+ When `label` is omitted, built-in labels use the matching key: `visitors` becomes
58
+ `Visitors` and `monthlyVisitors` becomes `Monthly visitors`. Camel-case and acronym
59
+ boundaries, underscores and hyphens become spaces; words are lowercased, then the
60
+ first letter is capitalized. Supply an explicit label to preserve custom casing
61
+ or wording, including an intentionally empty label. Keys and colors are unchanged.
62
+ Category charts use their configured category identity for this inference.
32
63
 
33
64
  ## Quick start
34
65
 
@@ -80,6 +111,657 @@ for composition and customization. The documentation covers chart selection,
80
111
  peer requirements, styling, motion and accessibility; applications own their
81
112
  data, domains and business state.
82
113
 
114
+ ### Loading chart data
115
+
116
+ All chart families accept `loading?: boolean` alongside `animate`, plus
117
+ `loadingLabel?: string` (default “Loading chart”). Import the default stylesheet.
118
+
119
+ ```tsx
120
+ <LineChart
121
+ config={config}
122
+ data={rows}
123
+ xDataKey="month"
124
+ height={280}
125
+ aria-label="Monthly sales"
126
+ animate={{ revealDurationMs: 650 }}
127
+ loading={pending}
128
+ loadingLabel="Loading monthly sales"
129
+ />
130
+ ```
131
+
132
+ Pass the same props directly to composed chart roots inside the existing `Root`
133
+ and native sizing/composition. Supported families: Line, Area, Bar, Combo, Pie,
134
+ Radar, RadialBar, Scatter, Heatmap, Waterfall, Histogram, BoxPlot, ActivityRings
135
+ and Sankey. Each uses its own decorative chart silhouette independent of your data.
136
+ Pulse-based skeletons choose a clearly different bounded profile while fully hidden;
137
+ their geometry stays stable through renders and resize between pulses. A soft leading reveal and
138
+ trailing fade overlap, following the chart’s native entrance duration, easing and
139
+ direction: horizontal paths and left-to-right bar-family windows, angular sectors,
140
+ ordered point/cell opacity or directional flow. Radar keeps two six-vertex polygons
141
+ visible and smoothly morphs between bounded decorative shapes; reduced motion
142
+ holds both still. RadialBar keeps continuous angular velocity through its closing seam. Combo preserves separate family
143
+ reveal options. Pulse timing includes room for the trail to leave and a hidden
144
+ geometry swap; loading never delays actual completion.
145
+ Reduced motion displays a static silhouette.
146
+
147
+ Actual chart content remains mounted, sized, hidden and inert while pending.
148
+ The chart exposes `aria-busy` and a separate live status. Native axes, series,
149
+ refs, style and event ownership stay with the consumer. External legends remain
150
+ available. Heatmap uses its table viewport; Sankey uses its native flow container;
151
+ polar illustrations retain circular proportions. Skeletons have no values,
152
+ labels or tooltip targets.
153
+
154
+ Completion immediately restores actual data and rearms the family’s existing
155
+ entrance when `animate` enables it. There is no forced wait or queued completion.
156
+ Empty data does not imply loading: render an explicit empty-result message after
157
+ completion. Loading does not change validation of supplied chart data. Requests,
158
+ cancellation, retries, partial results, errors and portals outside the native
159
+ chart remain host-owned. Supply an accessible data alternative alongside charts.
160
+
161
+ Run `npm run dev:chart` and open `/loading.html` for all-family replay, load,
162
+ empty-input, resize and interrupted-update controls.
163
+
164
+
165
+ ## Selective Pie glow
166
+
167
+ `PieSeries` accepts `glowCategories?: readonly string[]` with `categoryKey` and
168
+ explicit data. For a rounded donut, add `glowCategories={["design"]}` to
169
+ `<PieSeries data={data} dataKey="hours" categoryKey="key" nameKey="key"
170
+ innerRadius={58} outerRadius={108} cornerRadius={8} paddingAngle={2} />`.
171
+ The IDs resolve through the existing category config contract. Unknown or removed
172
+ IDs do nothing; reorder/filter preserve colors and membership. Selected default
173
+ sectors use glow; others retain `material` (Default, token `plain`). Native custom paint,
174
+ shapes and handlers retain ownership. Keep labels and a data alternative.
175
+
83
176
  ## License
84
177
 
85
178
  [MIT](LICENSE) © 2026 Bhavesh Chowdhury.
179
+
180
+ ### Patterned bar fills
181
+
182
+ `FillPattern` is static SVG paint independent of `material`: `{ kind: "hatch" | "stripe" | "duotone", color?, size?, width?, angle? }`. `size` is a positive SVG-unit tile size (8 by default); `width` is positive and at most `size` (1 for hatch, 2 for stripe). `angle` defaults to 45 degrees for hatch and 0 otherwise. Duotone divides the tile equally between the series color and second ink; width does not affect its split.
183
+
184
+ ```tsx
185
+ const config = {
186
+ actual: { color: "#789abc", pattern: { kind: "hatch" as const } },
187
+ planned: { color: "#ed79ae", pattern: { kind: "stripe" as const } },
188
+ };
189
+ <Root config={config}>
190
+ <Legend />
191
+ <BarChart data={rows} width={480} height={260}>
192
+ <XAxis dataKey="category" />
193
+ <YAxis />
194
+ <BarSeries dataKey="actual" material="clay" />
195
+ <BarSeries dataKey="planned" pattern={{ kind: "duotone", color: "CanvasText" }} />
196
+ </BarChart>
197
+ </Root>
198
+ ```
199
+
200
+ Config patterns supply implicit bar paint and legend swatches. `BarSeries.pattern` overrides config; `false` opts out. For an explicit override, compose `FillPatternSwatch` through `Legend.children` with the same pattern and color. Icons and native legend symbols take precedence; `hideIcon` requests solid swatches. Explicit series `fill` (including gradients), `style.fill`, and `Cell` fills retain native ownership. Custom shapes or custom active bars disable automatic series patterns; compose your own SVG paint for those shapes. Existing material/filter rules apply independently.
201
+
202
+ Grouped/stacked and horizontal/vertical charts share the same user-space tile; changing orientation does not rotate the encoding automatically. The base ink keeps the configured CSS color. Second ink defaults to `CanvasText`, following the host's `color-scheme`; choose contrasting theme-aware colors deliberately. With the stylesheet, forced colors use `Canvas`/`CanvasText` while retaining the pattern geometry. Patterns are decorative, static and unchanged by reduced motion or print; printer color settings can still affect contrast. Keep text labels and a data alternative, and verify the chosen ink combination in print and each theme.
203
+
204
+ Resources use React IDs, independently of consumer series IDs, and are stable through matching SSR/hydration trees. Hosts using multiple independent React roots must supply distinct `identifierPrefix` values to server rendering and hydration, as required by React. Recharts retains its native SSR shell; a server-visible legend and data alternative do not imply server-rendered bar geometry.
205
+
206
+ ### Theme-aware series colors
207
+
208
+ `SeriesColor` accepts a CSS color string, a readonly nonempty array of CSS color
209
+ strings, or `{ light, dark }` containing either shape. Arrays are gradient stops,
210
+ not categorical palettes: supply separate config keys for category identities.
211
+ Stops are evenly distributed from 0 to 100%; a single stop is solid. Each theme's
212
+ spacing is preserved when stop counts differ (intermediate colors use CSS
213
+ `color-mix(in srgb, ...)`). Empty arrays, sparse arrays, empty strings and incomplete
214
+ or unknown theme fields throw. Errors identify the series and color property (including
215
+ the theme and stop index when applicable), without printing color values or unrelated
216
+ config metadata. For example, an empty second dark stop reports
217
+ `config["revenue"].color.dark[1] requires a nonempty color string`.
218
+ CSS color syntax remains the browser's responsibility: CSS variables, `currentColor`
219
+ and modern color functions pass through unchanged, including during server rendering.
220
+
221
+ ```tsx
222
+ const config = {
223
+ revenue: {
224
+ color: { light: ["var(--brand)", "#2563eb"], dark: ["#fef3c7", "#f59e0b", "#92400e"] },
225
+ },
226
+ } satisfies SeriesConfig;
227
+
228
+ // Change colorScheme on this host without changing Root's key or remounting it.
229
+ <div style={{ colorScheme: dark ? "dark" : "light", "--brand": "#f59e0b" }}>
230
+ <Root config={config}>
231
+ <Legend />
232
+ <LineChart data={data} width={480} height={240}>
233
+ <XAxis dataKey="month" />
234
+ <YAxis />
235
+ <LineSeries dataKey="revenue" />
236
+ <Tooltip content={(tooltip) => <TooltipContent tooltip={tooltip} />} />
237
+ </LineChart>
238
+ </Root>
239
+ </div>
240
+ ```
241
+
242
+ Themes follow the inherited CSS `color-scheme`, using native `light-dark()`;
243
+ without a host scheme the browser defaults to light. Set `color-scheme: light dark`
244
+ on a host for system preference, or `light`/`dark` for an explicit application
245
+ choice.
246
+
247
+ Browser syntax requirements (from MDN compatibility data):
248
+
249
+ | Generated CSS | Chrome / Edge | Firefox | Safari / iOS Safari |
250
+ | --- | --- | --- | --- |
251
+ | [`light-dark()`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/color_value/light-dark) for every `{ light, dark }` definition | 123+ | 120+ | 17.5+ |
252
+ | [`color-mix()`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/color_value/color-mix) for interpolated stops when theme spacing differs | 111+ | 113+ | 16.2+ |
253
+ | [`linear-gradient(... in srgb, ...)`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/gradient/linear-gradient#browser_compatibility) for gradient legend/tooltip swatches | 111+ | 127+ | 16.2+ |
254
+
255
+ For themed gradients including matching swatches, use Chrome/Edge 123+, Firefox
256
+ 127+, or Safari/iOS Safari 17.5+. These are syntax minimums, not a tested-browser
257
+ matrix; supplied color values can have additional requirements.
258
+
259
+ There is no polyfill, feature detection or automatic light/first-stop fallback.
260
+ Unsupported functions make the consuming paint or swatch declaration invalid;
261
+ SVG paints/stops can use their CSS initial or inherited values, and gradient
262
+ swatches can lose their background image. For older browsers, supply supported
263
+ plain color strings (or CSS variables with host-controlled light/dark values)
264
+ instead of themed objects. Unthemed stop arrays avoid generated `light-dark()`
265
+ and `color-mix()`, but their swatches still require the gradient syntax above.
266
+
267
+ Root retains `--color-<key>` as the first stop, emits zero-based
268
+ `--kind-ui-series-<encoded-key>-<index>` stops and a `-gradient` CSS swatch
269
+ token, where encoded keys are hexadecimal Unicode code points joined by hyphens.
270
+ This namespace cannot collide with another series' legacy `--color-<key>`.
271
+ Built-in series/category paints use uniquely scoped resources in each chart SVG; gradients run
272
+ left to right across the SVG viewport (including flat line geometry). Legend and
273
+ solid tooltip markers display the full gradient; dashed tooltip borders use the
274
+ first stop. Explicit native stroke/fill, styles, Cells and shapes retain their
275
+ existing ownership; configured bar patterns take precedence and use the first
276
+ stop as their solid base ink (the legend pattern swatch does the same). Native
277
+ explicit pattern colors remain consumer-owned. Icons keep priority. Strings and one-stop definitions require
278
+ no SVG resource. Consumer Root styles override generated tokens as before; override
279
+ indexed stops to customize a gradient. No theme subscription or remount is needed.
280
+
281
+ The runnable `tests/fixtures/identity-colors` host exercises explicit light/dark
282
+ changes, unequal stops, CSS variable colors, multiple roots and native paint
283
+ precedence (`npm exec vite tests/fixtures/identity-colors`, open `/?theme`).
284
+
285
+ ### Area patterns
286
+
287
+ `AreaSeries` accepts the shared `FillPattern` through `pattern` or `Root.config[key].pattern`, independently of `material`. The additive `dots` kind uses `width` as dot diameter (default 1); `lines` uses stroke width (default 1) and defaults to angle 0. Both use size 8 by default. Existing hatch, stripe and duotone encodings retain their defaults. These kinds also work with bars and `FillPatternSwatch`.
288
+
289
+ ```tsx
290
+ const config = {
291
+ actual: { color: "#789abc", pattern: { kind: "dots" as const, width: 2 } },
292
+ planned: { color: "var(--area-planned)", pattern: { kind: "lines" as const } },
293
+ };
294
+ <Root config={config}>
295
+ <Legend />
296
+ <AreaChart width={480} height={260} data={rows}>
297
+ <XAxis dataKey="month" />
298
+ <YAxis />
299
+ <AreaSeries dataKey="actual" stackId="total" material="clay" fillOpacity={0.4} />
300
+ <AreaSeries dataKey="planned" stackId="total" fillOpacity={0.4} />
301
+ </AreaChart>
302
+ </Root>;
303
+ ```
304
+
305
+ Configured `StackedArea`, `PercentArea` and `InteractiveArea` host recipes also accept these patterns through their `config`. Omit `stackId` for unstacked explicit areas. Explicit composition can instead use `pattern={{ kind: "hatch", angle: 45 }}` on each `AreaSeries`. Series patterns override configuration; `pattern={false}` opts out. Explicit `fill` (including gradients), `style.fill`, and custom `shape` retain ownership and disable automatic pattern resources. With a configured gradient, pattern tiles use the solid first-stop `--color-key` ink; unpatterned areas use the complete chart-local gradient. Explicit stroke still supplies the pattern base when provided. Native `fillOpacity`, filters, geometry and existing material rules remain in effect. Configuration drives default legend swatches; when overriding a series pattern, compose `FillPatternSwatch` through `Legend.children` with the matching pattern/color. Icon/symbol priority and theme/forced-colors behavior follow the shared bar pattern contract above. The mounted packed area fixture at `static.html?patterns` demonstrates overrides, materials, stacking, themes and two independent charts.
306
+
307
+ The existing Next integration fixture additionally checks real server-rendered
308
+ color resource IDs through hydration and a theme change.
309
+
310
+ ## Legend and mark interactions
311
+
312
+ Visibility remains the default. Opt into persistent focus with one Root owner:
313
+
314
+ ```tsx
315
+ <Root config={config} interaction={{
316
+ kind: "series", mode: "focus", eligibleKeys: ["revenue", "costs"],
317
+ markActivation: "matching-legend", defaultSelected: "revenue",
318
+ onSelectionChange: (key) => console.log(key),
319
+ }}>
320
+ <BarChart data={rows} width={400} height={240}>
321
+ <BarSeries dataKey="revenue" /><BarSeries dataKey="costs" />
322
+ </BarChart>
323
+ <Legend emphasis="series" />
324
+ </Root>
325
+ ```
326
+
327
+ `interaction` binds a `kind` (`series`, `category`, or focus-only `node`) to a
328
+ settled `eligibleKeys` snapshot. Include hidden Root items and zero values; exclude
329
+ removed, filtered, unavailable, or native-hidden items. Config-only entries do not
330
+ satisfy the last-visible guard. New bindings require that snapshot; native series
331
+ registration provides compatibility eligibility for existing controlled legends.
332
+ `mode` defaults to `visibility`; `markActivation` defaults to `none`.
333
+
334
+ Focus accepts either `selected` with required `onSelectionChange`, or
335
+ `defaultSelected` with an optional callback. Persistent focus works with
336
+ `emphasis="none"`. Repeated activation or Escape clears it; transient inspection
337
+ never writes selection. Invalid uncontrolled selection clears without a callback.
338
+ An invalid controlled ID paints no selection and resumes if that ID becomes valid.
339
+ Visibility uses existing `visibleSeries`/`onVisibleSeriesChange`, or opt-in
340
+ `defaultVisibleSeries`. An externally empty visibility value remains valid.
341
+
342
+ For categories, use `kind: "category"` and explicitly set
343
+ `interactionBinding="root"` on `PieSeries` or `RadialBarChart`, with `categoryKey`
344
+ and explicit data. Stable unique string keys must match Root config. Hiding filters
345
+ original rows and their positional Cells before layout; Pie re-normalizes angles.
346
+ ActivityRings forwards this binding through category `rootProps.interaction`.
347
+ For Sankey node focus, bind both `SankeyChart` and `SankeyLegend` to a Root with
348
+ `kind: "node", mode: "focus"`; incident links/endpoints remain emphasized.
349
+ Sankey visibility, link selection, and persistent Heatmap cell selection are excluded.
350
+
351
+ `useChartInteraction()` exposes `selected`, `visible`, `mode`, `kind`, `eligible`,
352
+ `activate({kind, key}, "legend" | "mark", event?)`, and `reset(event?)` for custom
353
+ controls. Actions return whether accepted. Consumer native handlers run first;
354
+ `preventDefault()` or `onBeforeInteraction(request)` returning `false` vetoes.
355
+ Reset also runs the before hook. Accepted actions emit one mode-specific callback
356
+ and one scoped polite announcement. Rejected last-hide actions announce the reason
357
+ without changing state or calling the change callback. Custom renderers retain
358
+ ownership and can use the helper to bind their own controls/paint.
359
+
360
+ Radar's existing local `selection="series"` remains a compatibility path, also
361
+ independent of transient emphasis. Combining it or its selection callbacks with
362
+ Root focus throws: choose one owner. Selection ownership stays fixed while mounted;
363
+ remount when changing controlled/uncontrolled ownership.
364
+
365
+ ## Decorative chart backgrounds
366
+
367
+ `ChartBackgroundPattern` is optional Cartesian plot chrome, independent of
368
+ `FillPattern` series encoding, legends, visibility and materials. Its transparent
369
+ tiles contain only decorative ink; series are painted above it. It uses Recharts'
370
+ plot area (excluding margins and axes), a scoped clip path and SVG IDs, and the
371
+ [Recharts layer contract](https://recharts.github.io/en-US/guide/zIndex/) just below
372
+ the default grid. Custom negative series/grid z-index overrides remain caller-owned.
373
+ It adds no layout, axes, tooltip payload, animation or accessibility semantics.
374
+ The stylesheet prevents descendant pointer interception.
375
+
376
+ Generated configured LineChart opts in with:
377
+
378
+ ```tsx
379
+ <Chart.LineChart
380
+ config={config}
381
+ data={data}
382
+ xDataKey="month"
383
+ aria-label="Monthly totals"
384
+ backgroundPattern={{ pattern: "pinpoints", opacity: 0.15 }}
385
+ />
386
+ ```
387
+
388
+ Explicit composition uses the part inside the chart. Configured explicit children
389
+ replace generated parts, so do not also pass `backgroundPattern`:
390
+
391
+ ```tsx
392
+ <Chart.Root config={config}>
393
+ <Chart.BarChart data={data} responsive style={{ width: "100%", height: 280 }}>
394
+ <Chart.ChartBackgroundPattern pattern="crossings" size={20} opacity={0.12} />
395
+ <Chart.XAxis dataKey="month" />
396
+ <Chart.YAxis />
397
+ <Chart.BarSeries dataKey="total" />
398
+ <Chart.Tooltip />
399
+ </Chart.BarChart>
400
+ </Chart.Root>
401
+ ```
402
+
403
+ Presets are `pinpoints`, `crossings` and `waves`. `size` is a positive finite tile
404
+ size in SVG user units (default 16), and `opacity` is finite in [0, 1] (default
405
+ 0.15). Zero opacity is supported. `color` accepts CSS paint, including theme
406
+ variables; the default is `var(--kind-ui-chart-grid, CanvasText)`. Resize changes
407
+ the plot rectangle and clip, preserving tile density and IDs. Missing or empty
408
+ plot geometry renders nothing; invalid options throw even before geometry exists.
409
+
410
+ Custom registration is consumer-owned and immutable: define reusable patterns in
411
+ a local module or catalog, then pass the definition directly. No global registry,
412
+ provider, series metadata or side-effect registration is required:
413
+
414
+ ```tsx
415
+ const cornerMarks = Chart.defineChartBackgroundPattern(({ size, color, idPrefix }) => (
416
+ <g id={`${idPrefix}-tile`}>
417
+ <path d={`M0 ${size / 3}V0H${size / 3}`} fill="none" stroke={color} />
418
+ </g>
419
+ ));
420
+ const backgrounds = { cornerMarks }; // optional local catalog
421
+
422
+ <Chart.ChartBackgroundPattern pattern={backgrounds.cornerMarks} size={24} opacity={0.1} />;
423
+ ```
424
+
425
+ The render escape hatch returns tile SVG children, not a full chart or pattern
426
+ element. Use the supplied `size`, `color`, and per-instance `idPrefix`; suffix
427
+ custom resource IDs and reference those scoped IDs. Keep custom output decorative:
428
+ no links, focusable elements, event handlers, portals or independent z-index
429
+ layers. The callback is trusted consumer code, not an SVG sanitizer.
430
+
431
+ Supported composition hosts are Line, Bar (including Waterfall), Area, Combo,
432
+ Scatter, Histogram and BoxPlot charts using the native Cartesian plot area.
433
+ Generated configuration is available only for LineChart. Polar, Pie, Sankey,
434
+ Heatmap and ActivityRings are outside this contract. Fixed-size chart SSR retains
435
+ the existing native empty shell; this part does not create server plot geometry.
436
+ ### Projected bar rows
437
+
438
+ `BarSeries<DataPoint, Value>.projection` is opt-in:
439
+ `{ isProjected: (datum: DataPoint) => boolean, pattern: FillPattern }`.
440
+ The caller supplies values and selects identity; Kind UI generates no forecasts.
441
+ For a trailing projection, capture the final row's stable ID before filtering or
442
+ reordering, then compare that ID in `isProjected`. It never implicitly marks the
443
+ new last visible row. Multiple selected identities are allowed.
444
+
445
+ ```tsx
446
+ const projectedId = originalRows.at(-1)?.id;
447
+ const projection: BarProjection<Row> = {
448
+ isProjected: (row) => projectedId !== undefined && row.id === projectedId,
449
+ pattern: { kind: "hatch" },
450
+ };
451
+ // Use in grouped bars or share it across series with the same stackId.
452
+ <BarSeries<Row, number> dataKey="value" projection={projection} />;
453
+ <Tooltip content={(tooltip) => (
454
+ <TooltipContent tooltip={tooltip}
455
+ isProjected={(entry) => projection.isProjected(entry.payload)} />
456
+ )} />;
457
+ // In the consumer-owned table, alongside the unchanged numeric value:
458
+ <td>{projection.isProjected(row) ? "Projected" : "Observed"}</td>;
459
+ ```
460
+
461
+ Selection applies to chart rows or explicitly supplied `BarSeries.data` in both
462
+ orientations. Empty chart data produces no marks. Native empty `BarSeries.data` overrides
463
+ inherit chart rows; projection follows the actual displayed payload. Null/undefined
464
+ rows are never passed
465
+ to the selector. Missing values retain native missing-bar behavior, and filtering
466
+ out a selected identity does not select a replacement. Zero remains zero. Keep
467
+ predicates pure and IDs unique; row indices do not offer reorder-stable identity.
468
+
469
+ Projection fills use the existing `FillPattern` seam and native Rectangle shape
470
+ props, so Brush slices cannot shift identity as positional Cells would. Unselected rows retain
471
+ configured/explicit series patterns and full gradient paints. Projection tiles
472
+ use the configured theme's solid first stop (`--color-key`), the same documented
473
+ fallback as ordinary Bar/Area pattern tiles. `pattern={false}` disables automatic
474
+ projection paint. Explicit series `fill`/`style.fill`, native shape options,
475
+ custom shapes/active shapes and any explicit Cell composition retain paint
476
+ ownership. Datum `fill`/`style.fill` also prevents automatic projection paint for
477
+ that row. Compose Cells yourself for custom per-row painting.
478
+
479
+ `TooltipContent.isProjected(entry)` shares caller selection but receives the
480
+ native tooltip entry, including its original payload. Projected items append
481
+ accessible text (`projectedLabel`, default `"Projected"`) without modifying
482
+ values, labels, formatter behavior or config. Custom tooltip content and data
483
+ alternatives must expose status themselves, even when custom paint overrides it.
484
+ The runnable public consumer example is the packed Bar fixture at
485
+ `/?projection` (add `&horizontal` for horizontal bars); it includes table status,
486
+ stacking, filtering, reorder and ownership controls. Configured series metadata
487
+ continues to own ordinary patterns; projection is a per-Series composition prop,
488
+ not global config or a generated-data recipe.
489
+
490
+ ### Percentage stack formatting
491
+
492
+ `createPercentStack({ values })` opts into formatting for scalar native
493
+ `stackOffset="expand"` Bar, Area and Combo stacks. Geometry, domains, axes and
494
+ stack membership remain caller-owned. It returns `tickFormatter` and
495
+ `normalizedValue`; no rows, series, native values or native tooltip payloads are
496
+ rewritten. Native Recharts still normalizes geometry exactly once.
497
+
498
+ ```tsx
499
+ const percent = createPercentStack({
500
+ values: (entry) => {
501
+ // Select this stack, excluding the Combo's latency line and other axes.
502
+ if (entry.dataKey !== "desktop" && entry.dataKey !== "mobile") return undefined;
503
+ const row = entry.payload as { desktop: number; mobile: number } | undefined;
504
+ return row ? [row.desktop, row.mobile] : undefined;
505
+ },
506
+ });
507
+ // Vertical columns / ordinary Areas: the numeric Y axis only.
508
+ <YAxis yAxisId="share" tickFormatter={percent.tickFormatter} />;
509
+ // Horizontal bars (layout="vertical"): the numeric X axis only.
510
+ <XAxis type="number" tickFormatter={percent.tickFormatter} />;
511
+ <Tooltip normalizedValue={percent.normalizedValue} />;
512
+ ```
513
+
514
+ Only attach the formatter to an axis whose units are fractions. Other axes keep
515
+ native ticks. A shared axis containing raw and fraction units needs a caller
516
+ chosen scale/domain; this helper cannot reconcile those units. Custom ticks and
517
+ `tickFormatter` stay native and caller-owned. `formatPercent(fraction)` is also
518
+ exported (one decimal at most, no locale-dependent output).
519
+
520
+ `values(entry)` must return the **raw members of that entry's own stack**, including
521
+ its value, or `undefined` for unrelated entries. Use native datum identity, stack
522
+ and axis selection where keys overlap. Match current native hidden-series
523
+ membership; do not sum the tooltip payload, which can contain unrelated stacks,
524
+ axes or lines. Update membership alongside controlled legends. Range values,
525
+ numeric strings and nonfinite values are outside the helper's scalar contract.
526
+
527
+ The helper uses the signed sum, matching native expand: missing members contribute
528
+ zero to the denominator but remain missing in content; all-zero rows display 0%;
529
+ negative fractions remain signed, with no absolute-value conversion or clamping.
530
+ A cancelling zero sum with nonzero members has no defined share and retains raw
531
+ formatting. Invalid/nonfinite totals or entries also retain raw formatting.
532
+ Native negative geometry/domain behavior remains native; this API does not promise
533
+ a 0–100% domain for negative data or fabricate absent values.
534
+
535
+ `Tooltip` and `TooltipContent` accept `normalizedValue(entry): number | undefined`.
536
+ Default content shows `25% (1)` with the original value in parentheses, using
537
+ configured `formatValue` for that raw text when present. Explicit entry/native
538
+ `formatter` takes precedence, including suppression and tuple results. Custom
539
+ content receives the unchanged native payload and owns its presentation; pass the
540
+ resolver explicitly when composing `TooltipContent`. Missing text, labels,
541
+ projection status, patterns and colors keep their existing contracts. For already
542
+ normalized source data, provide a resolver that returns the existing fraction;
543
+ do not compute another share or apply expand to data already transformed elsewhere.
544
+ Hosts still own raw data tables and accessible alternatives.
545
+
546
+ The source-only example at `/contracts.html` includes vertical/horizontal Bar,
547
+ Area and Combo with a separate raw latency axis and original data tables.
548
+ Recharts 3.10.1 applies native expand to every numeric axis. For mixed-unit Combo
549
+ charts, the example therefore uses caller-selected fraction `dataKey` accessors
550
+ for the stacked bars with `stackOffset="none"`, a `[0, 1]` share axis, and an
551
+ unchanged raw latency line on `[0, 10]`. The raw rows stay intact; an explicit
552
+ native tooltip formatter pairs each fraction with its original row value. Do not
553
+ apply expand or divide again to those fraction accessors. Other families use
554
+ native expand and the formatting helper. The Percent Area recipe retains integer
555
+ tooltip percentages and “No share” for a zero total.
556
+
557
+ ### Point marker styles
558
+
559
+ `LineSeries` and `AreaSeries` accept independent `pointStyle` and
560
+ `activePointStyle` values: `"default"`, `"border"`, or `"colored-border"`.
561
+ Omitted/default values keep the existing appearance (including Area's normally
562
+ hidden regular dots). Opting into a regular style enables regular dots. Border
563
+ uses a series-colored center with a surface-colored ring; colored-border uses a
564
+ surface-colored center with a series-colored ring. The surface is
565
+ `--kind-ui-chart-marker-surface`, falling back to `--card` then white; set it on
566
+ Root for your theme. Series paint follows the existing config/theme identity or
567
+ explicit series stroke. Styles introduce no SVG resources or additional motion.
568
+
569
+ ```tsx
570
+ <LineSeries dataKey="total" pointStyle="border" activePointStyle="colored-border" />
571
+ <AreaSeries dataKey="total" pointStyle="colored-border" activePointStyle="border" />
572
+ ```
573
+
574
+ Any explicit native `dot` or `activeDot` value (including false, true, props,
575
+ renderer functions and elements) wins for its respective marker. Configured
576
+ LineChart accepts these options in its existing `series` objects; explicit
577
+ children retain ownership. `PointMarker` is a reusable native Dot renderer with
578
+ `variant` and native Dot props. Its variant paint wins the engine-supplied paint;
579
+ native radius/handlers remain intact, and SVG `style` can override its paint.
580
+ For example, `dot={<PointMarker variant="colored-border" style={{ fill: "white" }} />}`.
581
+ Native active-dot callbacks carry series paint in `fill`, while regular dots carry
582
+ it in `stroke`. When composing PointMarker as an active renderer, forward that
583
+ identity explicitly: `activeDot={(props) => <PointMarker {...props}
584
+ stroke={props.fill} variant="colored-border" />}`. The Series style API handles
585
+ this distinction automatically. Keyboard/pointer inspection remains chart-owned; the active mark retains its
586
+ existing non-intercepting behavior and reduced-motion policy. Radar's selection
587
+ dots and Scatter's symbols have separate contracts and do not accept these
588
+ series options. Bar, Pie and other shape families are outside this API.
589
+
590
+ Run `npm run dev:chart` and visit `/recipes.html#point-markers` for the marker gallery and
591
+ its accessible data table.
592
+
593
+ ### Directional Line and Area entrances
594
+
595
+ `LineAnimation` and `AreaAnimation` accept `revealDirection`:
596
+
597
+ - `"left-to-right"` (default): expand from the left edge.
598
+ - `"right-to-left"`: expand from the right edge.
599
+ - `"center-out"`: expand equally from the horizontal center.
600
+ - `"edges-in"`: expand two edge regions toward the horizontal center.
601
+
602
+ Directions are physical horizontal screen-space reveals for both native layouts;
603
+ they do not reverse data order or follow a vertical category axis. Timing remains
604
+ `revealDurationMs` / `revealEasing`. The temporary family clip leaves native paths,
605
+ axes, margins and transforms intact. Explicit directional entrances remove the clip
606
+ on completion or interruption (including resize/data changes). Line with an omitted
607
+ direction preserves its existing completed full-width clip until interruption;
608
+ Area/Combo retain their existing completion removal. Disabled/reduced motion shows
609
+ complete content.
610
+ Existing loading illustrations keep their independent design. Replay uses the
611
+ existing remount or loading-to-ready lifecycle, not hover or color updates.
612
+
613
+ ```tsx
614
+ import {
615
+ AreaChart, AreaSeries, ComboChart, LineChart, LineSeries, Root,
616
+ type SeriesConfig,
617
+ } from "@kind-ui/charts";
618
+ import "@kind-ui/charts/styles.css";
619
+
620
+ const data = [
621
+ { day: "Mon", total: 12, forecast: 16 },
622
+ { day: "Tue", total: 20, forecast: 24 },
623
+ ];
624
+ const config = {
625
+ total: { label: "Total", color: "#3659b8" },
626
+ forecast: { label: "Forecast", color: "#0d9488" },
627
+ } satisfies SeriesConfig;
628
+
629
+ export function DirectionalCharts() {
630
+ return (
631
+ <Root config={config}>
632
+ <LineChart data={data} width={480} height={240} aria-label="Daily total"
633
+ animate={{ revealDirection: "right-to-left", revealDurationMs: 800 }}>
634
+ <LineSeries dataKey="total" pointStyle="border" />
635
+ </LineChart>
636
+ <AreaChart data={data} width={480} height={240} aria-label="Daily forecast"
637
+ animate={{ revealDirection: "center-out" }}>
638
+ <AreaSeries dataKey="forecast" />
639
+ </AreaChart>
640
+ <ComboChart data={data} width={480} height={240} aria-label="Total and forecast"
641
+ animate={{
642
+ revealDirection: "center-out",
643
+ lineReveal: { revealDirection: "right-to-left" },
644
+ areaReveal: { revealDirection: "edges-in", revealDurationMs: 1200 },
645
+ barReveal: false,
646
+ }}>
647
+ <LineSeries dataKey="total" />
648
+ <AreaSeries dataKey="forecast" />
649
+ </ComboChart>
650
+ </Root>
651
+ );
652
+ }
653
+ ```
654
+
655
+ Combo inherits the chart direction for Line/Area unless the corresponding family
656
+ object overrides it; `false` disables that family entrance. Bar keeps its existing
657
+ entrance configuration. All managed series in a family share its entrance clip;
658
+ individual series rendering/visibility props remain available, but there is no
659
+ per-series direction prop. Explicit native children and consumer clip/shape
660
+ ownership retain their existing contracts. `RevealDirection` is exported for
661
+ consumer controls. The packed Line/Area motion fixtures accept `?direction=...`
662
+ and the Combo fixture accepts `?directional` to exercise the family overrides.
663
+
664
+ ### Animated dashed lines
665
+
666
+ `LineSeries` accepts `dashAnimation={ { durationMs: 1000, direction: "forward" } }`
667
+ (or `false`, the default). Supply a native numeric `strokeDasharray`, such as
668
+ `"6 4"`; duration is milliseconds per full pattern cycle. `reverse` reverses
669
+ travel. Zero, negative or non-finite duration and nonnumeric/CSS/percentage dash
670
+ patterns stay static. Odd lists repeat twice per cycle, matching SVG.
671
+
672
+ ```tsx
673
+ <LineChart data={rows} animate>
674
+ <LineSeries dataKey="total" strokeDasharray="6 4" strokeDashoffset={3}
675
+ dashAnimation={{ durationMs: 800 }} material="glow" />
676
+ </LineChart>
677
+ <ComboChart data={rows} animate>
678
+ <AreaSeries dataKey="total" stroke="none" fillOpacity={0.2} />
679
+ <LineSeries dataKey="total" dot={false} strokeDasharray="3 2 1"
680
+ dashAnimation={{ durationMs: 1200, direction: "reverse" }} />
681
+ </ComboChart>
682
+ ```
683
+
684
+ Load the package stylesheet. Motion stops with chart `animate={false}`, reduced
685
+ motion, loading, or hidden series. Disabling restores the native dash offset;
686
+ reenabling starts a fresh cycle. Use `dashAnimation={false}` to disable an individual series. Native width, dash array, offset and
687
+ styles remain intact; style dash values take precedence. Custom shapes own their
688
+ animation and are never decorated. Entrance clip reveal timing is independent.
689
+ No geometry or data is changed, and CSS requires no mount timers or cleanup.
690
+ Stylesheets overriding dash paint remain consumer-owned and can change appearance.
691
+
692
+ `AreaSeries` does not accept this option: its closed perimeter includes baseline
693
+ and closing edges. For an open animated outline, overlay `LineSeries` in a
694
+ `ComboChart` as above. Match data keys, interpolation and axes yourself; stacked
695
+ or range areas require an explicitly derived outline dataset. See
696
+ `/contracts.html#dashed-lines` for the interactive line and combo contract examples.
697
+
698
+ ### Initial Pie tooltip
699
+
700
+ `PieChart.defaultPinnedCategory="delivery"` opts into an initial tooltip for
701
+ one direct `PieSeries` (Fragments allowed) with explicit `data` and `categoryKey`.
702
+ Pair it with `Tooltip.itemKey={(entry) => entry.payload.id}` when `categoryKey="id"`;
703
+ caller labels, formatters, and the data table remain the source of truth.
704
+ Only Pie/donut compositions whose direct children are one Kind PieSeries and
705
+ one Kind Tooltip (optionally in Fragments) support this default. Native Pie, wrapped series, multiple rings, and other chart families
706
+ are outside this contract; missing category data/identity or multiple direct
707
+ series, duplicate/missing Tooltips, or unsupported direct children throw when resolving a pin.
708
+
709
+ The category string is captured on mount. Reorder resolves its current index;
710
+ unknown, duplicate, removed, hidden, or filtered categories clear the default
711
+ permanently. Restoring rows or changing the default prop does not re-pin; remount
712
+ explicitly to begin again. Pointer movement/down, focus, and any chart key press
713
+ clear the default and hand inspection/dismissal back to Recharts. Escape never
714
+ re-pins. No focus is moved or trapped. The existing tooltip is the sole readout
715
+ and live announcement; Kind adds no announcement region or hover selection.
716
+ Explicit Tooltip `active` and `defaultIndex` retain native ownership and take
717
+ precedence. Custom content owns its markup and accessibility. Omitted defaults
718
+ preserve existing behavior. See the weekly Pie in `examples/chart/pie-recipes.tsx`.
719
+
720
+ ### Sankey node labels
721
+
722
+ Compose `SankeyNodeLabel` beside `SankeyNode` in the native `node` callback:
723
+
724
+ ```tsx
725
+ node={(node) => (
726
+ <g>
727
+ <SankeyNode {...node} />
728
+ <SankeyNodeLabel node={node} data={data} position="outside" showValues
729
+ valueFormatter={(value) => `${value} MWh`} />
730
+ </g>
731
+ )}
732
+ ```
733
+
734
+ Optional `SankeyNodeConfig` entries accept an `icon: ReactNode`. Pass that config
735
+ explicitly as `SankeyNodeLabel`'s `nodeConfig` in your native callback (see the
736
+ energy example in `examples/chart/sankeys.tsx`). Icons use a square `iconSize`
737
+ (default 16 chart units) SVG viewport with `viewBox="0 0 24 24"`; supply SVG
738
+ content, or a nested SVG with its own viewBox. `iconGap` defaults to 4 units.
739
+ Outside icons sit nearest the node and shift the text by size plus gap on either
740
+ side. Inside icons stack above centered text and share its exact rectangle clip;
741
+ small nodes can clip both. Reserve outside margins for the combined content.
742
+ Missing, null, boolean or zero-size icons preserve the existing text layout.
743
+ Sizes and gaps must be finite and nonnegative. Icons are decorative (`aria-hidden`,
744
+ nonfocusable); data names and full name/value titles remain meaningful, and the
745
+ data table remains the accessible alternative. Config labels remain Legend metadata.
746
+ Custom text children compose with the icon; text props/ref still target the text.
747
+ A custom native node renderer owns all rendering: nothing is injected unless it
748
+ chooses this helper, and omitting `nodeConfig` opts out of configured icons.
749
+
750
+ Identity is resolved by `node.payload.id` against `data`, never callback index or
751
+ name. Supply the same data to the chart, label and `SankeyTable`, and reuse the
752
+ formatter as the table's `formatValue`. A node value is the maximum of incoming
753
+ and outgoing flow sums: sources use outgoing, sinks incoming, balanced intermediate
754
+ nodes count throughput once, and disconnected or measured-zero nodes total zero.
755
+ Existing data validation rejects unbalanced intermediate nodes outside its rounding
756
+ tolerance; the larger sum handles that tolerance consistently with native sizing.
757
+ No extra totals or inferred flows are added to the table.
758
+
759
+ `position="inside"` centers text and clips it to the exact node rectangle, including
760
+ small or zero-size nodes. It does not shrink text, expand geometry or avoid collisions.
761
+ Use outside labels for narrow nodes; they default right for sources/intermediates and
762
+ left for sinks. `side`, `offset`, native text props, styles and refs remain consumer-owned.
763
+ Reserve margins for outside text; the native SVG viewport still clips overflow.
764
+ The full name/value remains in a SVG title even when inside text clips. Keep the table
765
+ as the complete accessible data alternative. Custom `children` (including `tspan`)
766
+ replace visual text while preserving the default title. No label or animation is
767
+ installed implicitly. `/sankeys.html` demonstrates both positions.