@kind-ui/charts 0.1.0 → 0.2.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 (105) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +657 -865
  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-series.d.ts +11 -1
  14. package/dist/bar-series.js +65 -13
  15. package/dist/box-plot.js +2 -2
  16. package/dist/category-cells.d.ts +7 -1
  17. package/dist/category-cells.js +32 -2
  18. package/dist/chart-background-pattern.d.ts +23 -0
  19. package/dist/chart-background-pattern.js +31 -0
  20. package/dist/chart-context.d.ts +6 -2
  21. package/dist/chart-interaction.d.ts +94 -0
  22. package/dist/chart-interaction.js +224 -0
  23. package/dist/combo-chart.d.ts +8 -5
  24. package/dist/combo-chart.js +14 -12
  25. package/dist/configured-line-chart.d.ts +5 -1
  26. package/dist/configured-line-chart.js +8 -5
  27. package/dist/emphasis.d.ts +5 -2
  28. package/dist/emphasis.js +18 -4
  29. package/dist/fill-pattern.d.ts +25 -0
  30. package/dist/fill-pattern.js +23 -0
  31. package/dist/heatmap.d.ts +3 -1
  32. package/dist/heatmap.js +34 -12
  33. package/dist/histogram-chart.js +10 -3
  34. package/dist/index.d.ts +12 -3
  35. package/dist/index.js +8 -1
  36. package/dist/legend.d.ts +2 -0
  37. package/dist/legend.js +28 -7
  38. package/dist/line-chart.d.ts +13 -3
  39. package/dist/line-chart.js +54 -34
  40. package/dist/line-dash.d.ts +8 -0
  41. package/dist/line-dash.js +18 -0
  42. package/dist/line-series.d.ts +9 -1
  43. package/dist/line-series.js +38 -11
  44. package/dist/loading-cartesian-designs.d.ts +6 -0
  45. package/dist/loading-cartesian-designs.js +123 -0
  46. package/dist/loading-motion.d.ts +37 -0
  47. package/dist/loading-motion.js +88 -0
  48. package/dist/loading-polar-designs.d.ts +6 -0
  49. package/dist/loading-polar-designs.js +95 -0
  50. package/dist/loading-skeleton.d.ts +29 -0
  51. package/dist/loading-skeleton.js +143 -0
  52. package/dist/loading-standalone-designs.d.ts +5 -0
  53. package/dist/loading-standalone-designs.js +126 -0
  54. package/dist/percent-stack.d.ts +18 -0
  55. package/dist/percent-stack.js +33 -0
  56. package/dist/pie-chart.d.ts +7 -3
  57. package/dist/pie-chart.js +37 -6
  58. package/dist/pie-pin-identity.d.ts +4 -0
  59. package/dist/pie-pin-identity.js +10 -0
  60. package/dist/pie-series.d.ts +4 -0
  61. package/dist/pie-series.js +137 -14
  62. package/dist/pie-tooltip-pin.d.ts +4 -0
  63. package/dist/pie-tooltip-pin.js +52 -0
  64. package/dist/point-marker.d.ts +19 -0
  65. package/dist/point-marker.js +19 -0
  66. package/dist/polar-chart.d.ts +14 -5
  67. package/dist/polar-chart.js +41 -10
  68. package/dist/polar-series.js +68 -22
  69. package/dist/radar-interaction.js +10 -4
  70. package/dist/radial-category.d.ts +2 -0
  71. package/dist/reveal-clip.d.ts +16 -0
  72. package/dist/reveal-clip.js +18 -0
  73. package/dist/root.d.ts +5 -9
  74. package/dist/root.js +46 -11
  75. package/dist/sankey-chart.d.ts +5 -1
  76. package/dist/sankey-chart.js +64 -13
  77. package/dist/sankey-colors.d.ts +3 -0
  78. package/dist/sankey-interaction.d.ts +8 -0
  79. package/dist/sankey-interaction.js +32 -0
  80. package/dist/sankey-legend.d.ts +2 -1
  81. package/dist/sankey-legend.js +18 -3
  82. package/dist/sankey-node-label.d.ts +22 -0
  83. package/dist/sankey-node-label.js +34 -0
  84. package/dist/scatter-chart.d.ts +5 -3
  85. package/dist/scatter-chart.js +14 -4
  86. package/dist/scatter-series.js +5 -3
  87. package/dist/series-color.d.ts +10 -0
  88. package/dist/series-color.js +48 -0
  89. package/dist/series-interaction.d.ts +12 -0
  90. package/dist/series-interaction.js +82 -0
  91. package/dist/series-label.d.ts +7 -0
  92. package/dist/series-label.js +18 -0
  93. package/dist/series-paint.d.ts +8 -0
  94. package/dist/series-paint.js +27 -0
  95. package/dist/styles.css +151 -1
  96. package/dist/stylesheet-warning.d.ts +2 -0
  97. package/dist/stylesheet-warning.js +86 -0
  98. package/dist/tooltip-content.d.ts +8 -1
  99. package/dist/tooltip-content.js +15 -4
  100. package/dist/tooltip.d.ts +3 -1
  101. package/dist/tooltip.js +8 -5
  102. package/dist/types.d.ts +11 -2
  103. package/dist/waterfall-chart.d.ts +4 -0
  104. package/dist/waterfall-chart.js +7 -0
  105. package/package.json +2 -2
package/README.md CHANGED
@@ -1,966 +1,758 @@
1
1
  # Kind UI charts
2
2
 
3
- React components compose real Recharts lines, areas and bars with shared pointer/keyboard state, measured tooltip placement, metadata and controlled visibility. Each chart accepts `animate={false | true | config}` for coordinated reveal and hover animation. Consumers own data shape, reference lines, custom marks, copy and data alternatives. Explicit compositions also own scales, axes, grids and layout; configured Line composition provides overridable defaults.
3
+ Composable React charts built on Recharts and Motion, with shared interaction,
4
+ controlled visibility and optional animation. Start with a complete chart, then
5
+ customize its parts through public components and typed props.
4
6
 
5
- ## Installation
7
+ [Website](https://kindui.dev/charts) ·
8
+ [Documentation](https://kindui.dev/charts/docs/) ·
9
+ [Examples](https://github.com/bhaveshchow20/kind-ui/tree/main/examples/chart) ·
10
+ [GitHub](https://github.com/bhaveshchow20/kind-ui)
6
11
 
7
- ```sh
8
- npm install @kind-ui/charts react react-dom recharts motion
9
- ```
10
-
11
- Import components and types from `@kind-ui/charts` and import
12
- `@kind-ui/charts/styles.css` once at the application entry. The package is ESM
13
- and requires React/React DOM `^19.3.0`, Recharts `^3.10.1` and Motion `^13.4.6`.
14
- Motion remains required when animation is disabled. TypeScript consumers use
15
- NodeNext or Bundler resolution. Next applications need a client component
16
- boundary for callbacks, refs and state. Provide an accessible name and a
17
- host-owned data alternative.
18
-
19
- See the [changelog](CHANGELOG.md) for API changes.
20
-
21
- ## Identity, layout and activity components
22
-
23
- - `HeatmapGrid.layout` accepts `cellSize`, `gap`, `rowLabels` and `columnLabels`. Omission retains the fluid table. Visual label hiding preserves native header associations; custom content/styles can enlarge native table cells. Keep the horizontal scroll host width-constrained and import the stylesheet. This is programmatic Chromium validation, not manual screen-reader conformance.
24
- - `PieSeries.categoryKey` (with explicit series data) and `RadialBarChart.categoryKey` resolve own top-level fields or typed accessors to string keys in `Root.config`. Colors follow IDs through reorder/filtering; explicit series/datum/Cell paint remains authoritative. Category filtering remains consumer-owned. `SankeyChart.nodeConfig` supplies node metadata and source/target link colors, with standalone static `SankeyLegend`; it does not create flow selection or rewrite custom SVG renderers.
25
- - `ActivityRings` owns Root and accepts ordered `rings`, `config` and an accessible name. A ring has a unique config `key`, finite `value`, optional domain and native `cellProps`. Default domain is `[0, 100]`; visual progress clamps while raw values remain in tooltip payloads. `series`, `labels`, `legend`, `tooltip` and native chart geometry provide bounded overrides. Do not nest it inside Root; use explicit `RadialBarChart` composition for that case. Hosts still own data alternatives and sizing.
12
+ ## Chart types
26
13
 
27
- These additions are included in the packed changelog and checked through public tarball imports. They do not establish compatibility beyond the documented pinned and ordinary-consumer checks.
28
-
29
- ## Basic usage
14
+ | Family | Charts |
15
+ | --- | --- |
16
+ | Cartesian | Line, Area, Bar, Combo |
17
+ | Polar | Pie/Donut, Radar, Radial/ActivityRings |
18
+ | Relationships | Scatter/Bubble, Heatmap |
19
+ | Distributions | Histogram, BoxPlot |
20
+ | Flow & change | Sankey, Waterfall |
30
21
 
31
- ```tsx
32
- import { useState } from "react";
33
- import * as Chart from "@kind-ui/charts";
34
- import { ResponsiveContainer, XAxis } from "@kind-ui/charts";
35
- import "@kind-ui/charts/styles.css";
22
+ Donut and Bubble use `PieChart` and `ScatterChart` composition.
36
23
 
37
- const config = {
38
- tasks: { label: "Tasks", color: "#3659b8", formatValue: (value) => `${value} tasks` },
39
- } satisfies Chart.SeriesConfig;
24
+ ## Install
40
25
 
41
- export function TasksChart() {
42
- const [visible, setVisible] = useState<string[]>(["tasks"]);
43
- return (
44
- <Chart.Root config={config} visibleSeries={visible} onVisibleSeriesChange={setVisible}>
45
- <Chart.Legend aria-label="Visible series" />
46
- <ResponsiveContainer width="100%" height={240}>
47
- <Chart.LineChart data={[{ day: "Mon", tasks: 0 }, { day: "Tue", tasks: null }]} accessibilityLayer aria-label="Tasks by day">
48
- <XAxis dataKey="day" />
49
- <Chart.LineSeries dataKey="tasks" connectNulls={false} />
50
- <Chart.Tooltip />
51
- </Chart.LineChart>
52
- </ResponsiveContainer>
53
- </Chart.Root>
54
- );
55
- }
26
+ ```sh
27
+ npm install @kind-ui/charts
56
28
  ```
57
29
 
58
- `Chart` above is a normal ES module namespace import. Direct named imports also work: `import { Root, Legend, TooltipContent } from "@kind-ui/charts"`. The module exports `SeriesConfig` and each component's `*Props` type; there is no additional `Chart` object export.
59
-
60
- ## Composition Components and types
30
+ Import `@kind-ui/charts/styles.css` once at your application entry. In Next.js,
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.
61
35
 
62
- Supported chart compositions can import their chart parts and props through `@kind-ui/charts`:
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.
63
44
 
64
- | Purpose | Components |
65
- | --- | --- |
66
- | Axes, grids and sizing | `XAxis`, `YAxis`, `ZAxis`, `CartesianGrid`, `PolarAngleAxis`, `PolarRadiusAxis`, `PolarGrid`, `ResponsiveContainer` |
67
- | Labels and series composition | `Label`, `LabelList`, `Cell`, `BarStack` |
68
- | Reference marks and range selection | `ReferenceLine`, `ReferenceDot`, `ReferenceArea`, `Brush`, `ErrorBar` |
69
- | Custom shapes | `Dot`, `Curve`, `Rectangle`, `Sector`, `Polygon`, `Symbols`, `AreaRevealShape`, `LineDrawShape` |
45
+ ## Series labels
70
46
 
71
- Each Component has an exported native `*Props` type. These are direct reexports: generic signatures, refs, handlers, registration, sizing, defaults and styling retain the engine's semantics. They do not apply Kind loading behavior or axis/grid presets. Kind's `LineChart`, other chart roots, `Tooltip`, `Legend` and their props remain authoritative; there are no competing raw chart roots or wildcard engine exports.
47
+ Configuration keys must match the series `dataKey` (or its explicit `seriesKey`).
48
+ When `label` is omitted, built-in labels use the matching key: `visitors` becomes
49
+ `Visitors` and `monthlyVisitors` becomes `Monthly visitors`. Camel-case and acronym
50
+ boundaries, underscores and hyphens become spaces; words are lowercased, then the
51
+ first letter is capitalized. Supply an explicit label to preserve custom casing
52
+ or wording, including an intentionally empty label. Keys and colors are unchanged.
53
+ Category charts use their configured category identity for this inference.
72
54
 
73
- Custom marks can also import `BarShapeProps`, `PieSectorShapeProps`, `PieLabelRenderProps`, `ScatterShapeProps`, `RadialBarSectorProps`, `ActiveDotProps`, `DotItemDotProps`, `XAxisTickContentProps` and `YAxisTickContentProps`. Native tooltip callback props are `TooltipRenderProps<TValue, TName>`; `TooltipContentProps` continues to describe Kind's content Component. Supporting types include `DataKey`, `Coordinate`, `AxisDomainItem`, `NumberDomain`, `Margin`, `ScaleFunction`, `TooltipPayloadEntry` and `TooltipValueType`. The maintained custom-mark helpers `useXAxisScale`, `useYAxisScale`, `useChartWidth`, `useChartHeight` and `getRelativeCoordinate` are exported unchanged.
55
+ ## Quick start
74
56
 
75
- This consolidates application imports; Recharts remains a required peer dependency, alongside React, React DOM and Motion. Sankey's native tooltip and specialized engine-only integrations still use an explicit Recharts import; the bounded Cartesian Tooltip is not a Sankey adapter. Comparison fixtures deliberately retain native chart imports as independent test oracles.
76
-
77
- For Next, import the package from a client component when supplying callbacks, accessors, refs or stateful content. The packed entry retains `"use client"`; consolidation does not make function props serializable across a server boundary. Charts still need an accessible name and a host-owned data alternative; native SSR can render an empty chart wrapper before client layout.
78
-
79
- ## Configured Line Component
80
-
81
- Passing `config` opts the existing `LineChart` into package-owned composition. Provide `data`, `config` and an accessible name (`aria-label` or `aria-labelledby`); add `xDataKey` when generating parts and omit it when supplying explicit children. Do not add a surrounding `Root`.
57
+ Configured `LineChart` supplies responsive sizing, axes, series, tooltip and
58
+ legend. Give the chart an accessible name and include a data alternative:
82
59
 
83
60
  ```tsx
61
+ "use client";
62
+
84
63
  import { LineChart, type SeriesConfig } from "@kind-ui/charts";
85
64
  import "@kind-ui/charts/styles.css";
86
65
 
66
+ const data = [
67
+ { day: "Mon", tasks: 0 },
68
+ { day: "Tue", tasks: 12 },
69
+ { day: "Wed", tasks: 8 },
70
+ ];
87
71
  const config = {
88
72
  tasks: { label: "Tasks", color: "#3659b8" },
89
73
  } satisfies SeriesConfig;
90
74
 
91
- <LineChart
92
- data={[{ day: "Mon", tasks: 0 }, { day: "Tue", tasks: 12 }]}
93
- config={config}
94
- xDataKey="day"
95
- aria-label="Tasks by day"
96
- />;
97
- ```
98
-
99
- Configured mode owns Root, responsive sizing (100% width and 280px default height), default axes/grid/series/Tooltip/Legend and uncontrolled visibility. Override sizing through `className`, `style`, `width` or `height`; use `rootProps` for the surrounding Root. `defaultVisibleSeries` seeds uncontrolled visibility; `visibleSeries` and `onVisibleSeriesChange` provide controlled visibility. A controlled chart without a change handler is read-only. Keep controlled mode stable or remount with a new key.
100
-
101
- Omitted children generate parts. Override `xAxis`, `yAxis`, `grid`, `tooltip` or `legend` with their existing props or `false`; `curve` and `material` set series defaults. The optional typed `series` array supplies native LineSeries props with explicit `seriesKey` identities matching config, including function/numeric data keys. `ConfiguredLineChartProps<Row>` and `ConfiguredLineSeries<Row>` preserve row typing; the existing `LineChartProps` continues to describe explicit composition.
102
-
103
- Explicit children replace generated parts, including when children are `null` or empty. Omit `xDataKey` and generated-part options in this mode; compose native axes/grid and Kind series/Tooltip yourself. Explicit children do not add a legend unless `legend` props are supplied. Root and visibility remain owned by configured mode. Both paths share the existing implementations and native geometry. Configured motion defaults on, honors reduced motion and accepts `animate={false}`; explicit Root + LineChart composition retains its off default. The consumer still owns the accessible name and data alternative.
104
-
105
- ## Line ownership and API
106
-
107
- | Owner | Responsibilities |
108
- | --- | --- |
109
- | Kind | Root metadata and colors, LineSeries visibility, shared pointer/keyboard modality, measured tooltip bounds, optional reveal/default active-marker/tooltip motion |
110
- | Recharts | Geometry, curve interpolation, axes/scales, graphical-item registration, payload and active selection, keyboard traversal and Escape/blur dismissal |
111
- | Consumer | Data and ordering, controlled visibleSeries, sizing/part overrides (or explicit sizing and axes/grid), reference lines, custom dot/activeDot/shape/content, labels, styling, accessible name and data alternative; explicit compositions also own instructions |
112
-
113
- For legacy composition without `config`, render `LineChart` inside `Root`, and `LineSeries`/`Tooltip` inside `LineChart`. Engine children such as `XAxis`, `YAxis`, `CartesianGrid`, `ReferenceLine`, `LabelList` and `ErrorBar` keep their native composition path. There is no prescribed data schema, card or layout. The wrappers render registered Recharts components, rather than inspecting child display names or wrapping native composition parts.
114
-
115
- - `LineChart`: native Recharts chart props and SVG ref. Kind composes `onMouseMove`/`onMouseLeave` with its own pointer tracking and captures focus/keyboard changes without replacing Root handlers. The ref targets `SVGSVGElement`, including React 19 callback cleanup. The internal frame uses `display: contents` so sizing stays with the engine or `ResponsiveContainer`.
116
- - `LineSeries`: native `Line` props/children/custom `dot`, `activeDot`, `shape` and handlers. Motion owns animation, so `isAnimationActive` is excluded and Recharts animation is always disabled. `stroke` defaults to Root's color. A Root-hidden series stays hidden even with `hide={false}`; `hide={true}` additionally hides a series. String `dataKey` is the default metadata identity. Use `seriesKey` for function/numeric data keys, required with controlled visibility. Native tooltip payloads remain unchanged; Kind's default content resolves registered identities for metadata and filtering. Recharts Line has no public component ref in 3.10.1: use refs on your custom mark/shape nodes, retaining the engine shape's `pathRef` where needed.
117
- - `Tooltip`: native selection/formatting/cursor/trigger/style props and element/function `content`, plus `maxWidth` (180 by default), native `frameProps` and a ref to the measured div. Kind owns `position`, `isAnimationActive`, the chart portal and bounds/translation options (`allowEscapeViewBox`, `reverseDirection`, `useTranslate3d`); those props are excluded. `offset` is honored as a number or x/y pair, defaulting to 12. It measures width and height with ResizeObserver, follows the pointer, and uses the engine coordinate after keyboard interaction. Oversized content gets chart-sized width/height limits and scrolling. Frame styles/classes may customize presentation; overriding sizing, margins or transforms can change bounds. A custom content component receives native engine props and owns its accessible feedback. Defaults are `cursor={false}`, `filterNull={false}` and `TooltipContent`.
118
-
119
- Custom shape/content functions should be stable component types defined outside render when they retain state. Engine `wrapperStyle`, axes, IDs and native refs keep their upstream semantics. Custom tooltip portals/anchors can instead use a native Recharts Tooltip; it can share Root's `TooltipContent` metadata.
120
-
121
- ## Line animation
122
-
123
- Import the same components from `@kind-ui/charts` in every mode. `LineChart` accepts `animate`, defaulting to `false` in legacy Root + LineChart composition and `true` in configured mode: `false` renders immediately, `true` enables defaults, and a `LineAnimation` object enables animation with overrides. It exposes `revealDurationMs` (1000 by default), Motion `revealEasing` and `hoverTransition` (spring by default). Import the stylesheet for shared SVG reveal clipping:
124
-
125
- ```tsx
126
- import { Root, LineChart, LineSeries, Tooltip } from "@kind-ui/charts";
127
- import { XAxis } from "@kind-ui/charts";
128
- import "@kind-ui/charts/styles.css";
129
-
130
- <Root config={{ count: { label: "Count", color: "#345" } }}>
131
- <LineChart width={400} height={220} data={points}
132
- animate={{ revealDurationMs: 900, revealEasing: "easeOut", hoverTransition: { duration: 0.2 } }}>
133
- <XAxis dataKey="day" />
134
- <LineSeries dataKey="count" />
135
- <Tooltip />
136
- </LineChart>
137
- </Root>
138
- ```
139
-
140
- Motion is a required peer, including when `animate={false}`. This prop controls behavior; it does not remove Motion installation or bundle bytes. We use synchronous `motion/react` imports to keep component identities and customization stable across modes, without asynchronous loading/error states. Motion's [LazyMotion](https://motion.dev/docs/react-lazy-motion) can defer features, but that is a separate loading/bundle strategy, not a consequence of disabling animation. Use root imports with `animate` and `LineAnimation`; there is no `/motion` entry.
141
-
142
- The single-prop mode follows the familiar behavioral toggle in [Nivo](https://nivo.rocks/line/); [EvilCharts](https://evilcharts.com/docs/recharts/line-chart/static) also exposes a disabled intro mode. These are API references, not reused implementations or additional renderers. Recharts still owns geometry and selection. Motion owns one chart-space clip for all line strokes and resting dots, the default active marker and tooltip translation. Engine animation is forced off and excluded from `LineSeriesProps`. Custom marks/shapes/content retain consumer ownership; custom active dots replace the default animated mark. Arbitrary path morphing and animation of axes are outside this contract.
143
-
144
- The package subscribes reactively to reduced motion and starts disabled during server rendering. Reduced motion or explicit off snaps in-flight hover targets immediately. Focus, keyboard or pointer interaction finishes entrance; chart or per-series data/visibility identity, effective series visibility (including native `hide`), or measured size changes also cancel entrance, discard stale pointer pixels and snap existing hover targets. Hover motion resumes on the next pointer/keyboard input. Ordinary hover retargets the same mounted marks and tooltip. Entrance does not replay after interaction/update; remount to request a fresh entrance. Palette changes preserve chart state. Controlled Root visibility fades each line curve and resting dots over 180ms without replaying entrance or replacing the chart. Rapid reversals retarget the current opacity; hidden series leave tooltip content and pointer interaction immediately. Completion also invalidates geometry after an automatic-domain update. Off/reduced-motion modes snap visibility. Native `hide`, independently portaled labels/error bars and custom active marks retain native behavior. Keep an all-hidden message outside the mounted chart to preserve its interaction state. The clip requires the stylesheet; pointer/keyboard state and tooltip measurement work without it.
145
-
146
- ## Line materials
147
-
148
- The existing `/recipes.html` line showcase has a compact Plain/Paper/Clay/Glow selector alongside palette and Motion controls. It applies this public API to all eight existing recipes while retaining their data, markers, labels and visibility.
149
-
150
- `LineSeries material="plain" | "paper" | "clay" | "glow"` changes the default SVG curve's surface independently of color and animation. The exported `LineMaterial` type names these options. Plain is the existing default. Paper adds static fine fiber variation inside the ink stroke (2.5px by default); Clay adds a rounded, softly lit bevel, inner shade and a small neutral cast shadow (6px by default). Glow adds close and soft series-colored halos around a crisp 3px stroke, with a narrow neutral light core. All materials retain the engine's original quantitative path: no displacement, rough geometry or path morphing. Thin Clay strokes show less relief; use `strokeWidth` to choose thickness explicitly. Material defaults use round joins and solid-line caps; dashed lines use butt caps to keep their gaps visible. Native `strokeLinecap`, `strokeLinejoin`, dash patterns and handlers remain available.
151
-
152
- ```tsx
153
- <Root config={{ count: { label: "Count", color: "var(--color-count-ink)" } }}>
154
- <LineChart width={400} height={220} data={points} animate={false}>
155
- <LineSeries dataKey="count" material="clay" strokeWidth={5} dot={false} />
156
- <Tooltip />
157
- </LineChart>
158
- </Root>
159
- ```
160
-
161
- Keep colors in `SeriesConfig`, CSS variables or an explicit `stroke`. For Tailwind CSS 4, for example, define `@theme { --color-count-ink: #8b5040; }` in the host stylesheet. A material does not choose a palette or theme surrounding UI. Legend/tooltip indicators still represent the series' unlit color; when setting a different explicit `stroke`, keep the config color consistent if indicators should match it.
162
-
163
- | CSS variable (inherit from Root or host) | Default | Purpose |
164
- | --- | --- | --- |
165
- | `--kind-ui-line-paper-fiber` | `#fff` | Fiber tint, composited inside the ink |
166
- | `--kind-ui-line-paper-grain` | `0.38` | Fiber opacity, use a number from 0 to 1 |
167
- | `--kind-ui-line-clay-light` | `#fff` | Neutral bevel light |
168
- | `--kind-ui-line-clay-shade` | `#17212b` | Neutral inner and cast shade |
169
- | `--kind-ui-line-glow-light` | `#fff` | Narrow luminous core tint |
170
- | `--kind-ui-line-glow-opacity` | `0.5` | Close and outer halo opacity, use a number from 0 to 1 |
171
-
172
- These tokens work in plain CSS and Tailwind arbitrary properties, such as `[--kind-ui-line-paper-grain:0.2]` on Root. Existing component tokens independently style the restrained legend and tooltip. The defaults are original SVG compositions; [PaperCSS](https://www.getpapercss.com/) and [clay.css](https://github.com/codeAdrian/clay.css) are visual references for HTML surfaces, not dependencies or copied code. SVG [specular lighting](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/feSpecularLighting) supplies Clay's bevel; [Tailwind theme variables](https://tailwindcss.com/docs/theme) can supply host colors.
173
-
174
- Materials apply only to the default line shape. An explicit native `shape` or `filter` takes precedence, disables built-in material rendering and retains consumer ownership. Dots, active marks, error bars, axes, labels, tooltip content and legends keep their existing rendering; custom shapes need their own surface treatment. The filtered curve retains the engine pathRef and plot clipping, and Motion still controls the shared reveal clip and hover motion. Off/reduced-motion modes keep static materials. Hidden series allocate no rendered filters after their visibility exit completes. Each mounted series has its own filter ID independent of a supplied Line ID; separately mounted React roots should use React's `identifierPrefix` to avoid document-wide ID collisions.
175
-
176
- Only curves are filtered, with bounds padded by half the numeric `strokeWidth` plus 6 SVG units (a 12-unit width allowance for nonnumeric widths). Set a numeric `strokeWidth` for unusually wide strokes instead of overriding it only in CSS. Bounds use engine point extents, so custom curve interpolation that overshoots those extents may require a consumer shape/filter. Paper uses one static turbulence octave; Clay uses alpha blur and lighting; Glow uses two small static painted-stroke blurs and a light core composited inside the original stroke. No filter time animation, shader engine, additional dependency or canvas-wide filter is introduced. Filters add browser raster work proportional to each curve's bounding rectangle; many long overlapping series can be costly. Plain avoids this work. Current visual/interaction evidence covers Chromium, including narrow, flat and colored lines; other browsers, print/export renderers and dark host surfaces need their own verification.
177
-
178
- ## Contracts
179
-
180
- - `SeriesConfig`: a record keyed by a string `dataKey` or explicit `seriesKey`. Each entry has a string `label`, CSS `color`, optional `formatValue(value)` returning React content, optional decorative `icon: ComponentType`, and optional legend-only `legendShape` (see Scatter below). Built-in legend and tooltip content render the icon inside an aria-hidden wrapper; keep visible string labels meaningful. Keys start with a letter and contain letters, numbers, underscores, or hyphens. No data normalization or scales are introduced.
181
- - `Root`: scopes config and `--color-{key}` CSS variables. It forwards native div props/ref. The default stylesheet provides full width with `min-width: 0`. Set chart height explicitly through the underlying chart or `ResponsiveContainer`. Nested or adjacent containers keep separate metadata and colors.
182
- - `visibleSeries` is optional and consumer-owned. A callback requires this value; the legend requests the next array but never changes it itself. `LineSeries` applies this visibility automatically; keep native engine marks' `hide` props in sync when using them directly. Omit the callback for a static legend. An empty array means all series are hidden; the example owns that empty-state message.
183
- - `Legend`: renders a native list; with a callback, it renders native toggle buttons with `aria-pressed`. It forwards ul props/ref. Space/Enter work through normal button behavior; focus stays on the button. Style its root with `className`/`style`, or set `--chart-legend-background` on buttons. Marker priority is configured `icon`, then explicit `legendShape`, then the existing square color swatch. `hideIcon` bypasses both icon and shape and restores that square swatch. Optional `children={({ key, label, visible, marker }) => ...}` owns each item's final content inside the existing list item and button/static span; it may reuse or replace `marker`. Include meaningful label text; return noninteractive content only (no buttons, links, inputs, tab stops or click handlers). Kind retains the controlled toggle and keyboard ownership. Use a separate host legend when you need different interaction semantics.
184
- - `TooltipContent`: pass the upstream callback's props as `tooltip`. Native div props/ref, classes, and style remain separate and are forwarded. Upstream `formatter`, per-entry formatter, and `labelFormatter` work; per-entry formatters take precedence over the upstream formatter, which takes precedence over the config formatter. An upstream formatter returning null/undefined suppresses that entry. Formatters run for zero but not null/undefined; those display `missingValue` (default “No data”) alongside other available values. Inactive, empty, or entirely missing visible payloads render no tooltip; zero remains valid data. `hideLabel` omits the heading and its formatter, retaining series names/values and the live region. `indicator="dot" | "line" | "dashed"` changes the color marker; the default remains the existing slim `line`. Configured icons take precedence over marker choices; `hideIndicator` hides all decorative tooltip markers, including icons. These are content props: pass them to `TooltipContent` through the native `content` callback. Custom content keeps native formatter/content ownership and receives no injected presentation options.
185
- - `Tooltip` and `TooltipContent` accept optional `itemKey(entry)` to resolve metadata/visibility identity before registered series keys or native dataKey/name. Pass the same resolver to both when composing content for categories such as pie slices. Keep category IDs stable; `labelFormatter` owns heading presentation independently. This avoids adding a second nameKey/labelKey identity convention.
186
- - `valueAnimation="shuffle"` optionally rolls numeric tooltip digits vertically, matching a calendar's changing hover values. It is off by default and independent of chart entrance/position animation. `Tooltip` forwards it only to its built-in content; `TooltipContent`, `ScatterTooltip`, `ScatterTooltipContent`, and `HeatmapTooltip` accept the same prop. Finite numeric raw values with string/number formatted output are eligible; missing/nonfinite values, arrays, numeric strings and arbitrary React nodes retain their existing rendering. Non-ASCII digits and bidi strings fall back to plain text. Exact final formatter output is immediately accessible; decorative reels are hidden from assistive technology. Reduced motion (including changes during a transition) uses plain final text. Rapid updates stop and retarget the current reel, and leaving/unmounting cancels it. Repeated identical digits do not restart. The value retains its widest measured width during a visible session and smoothly grows for wider values; this avoids shrinking as digit counts change. Import the existing default stylesheet for the reel's clipping/layout.
187
-
188
- ```tsx
189
- <Chart.Tooltip valueAnimation="shuffle" />
190
- <Chart.ScatterTooltip valueAnimation="shuffle" />
191
- // The independent categorical-grid/calendar tooltip uses the same opt-in.
192
- <Chart.HeatmapChart rows={days} columns={weeks} data={activity} scale={scale}
193
- formatValue={(value) => `${value} commits`}>
194
- <Chart.HeatmapGrid caption="Activity by day" />
195
- <Chart.HeatmapTooltip valueAnimation="shuffle" />
196
- </Chart.HeatmapChart>
197
- ```
198
-
199
- Custom `Tooltip.content` and Heatmap `Content` remain fully consumer-owned and receive no injected animation prop. Compose `<TooltipContent tooltip={props} valueAnimation="shuffle" />` or `<ScatterTooltipContent ... />` explicitly if desired. Native/custom-owned Sankey tooltips, chart labels, legends and data tables are outside this optional behavior. The public-only calendar/line/Scatter preview is in `tests/fixtures/number-shuffle`; the package gate builds it from the installed tarball, with focused browser coverage in `tests/number-shuffle.spec.ts`.
200
- - Tooltip entries marked hidden or `type: "none"` are excluded, as are consumer-hidden series. Use `filterNull={false}` on the upstream Tooltip when missing values should appear. Unknown keys fall back to upstream names/colors/values. No tooltip payload is mutated.
201
- - Keyboard positioning, Escape/blur dismissal, focus and live-region orchestration remain with the upstream chart/Tooltip. Content supplies its default `role="status"` and live-region attributes when the upstream accessibility layer is enabled. Preserve equivalent feedback when overriding these attributes.
202
-
203
- Provide a named chart, keyboard instructions and a semantic data alternative appropriate to your application. The data alternative stays application-owned and should preserve hidden series in its table. Browser tests are evidence for this example, not a screen-reader compatibility or WCAG claim. No cross-framework or SSR/hydration support claim is made yet.
204
-
205
- Consumer checks use `strict: true` with `skipLibCheck: false` in NodeNext and Bundler modes. Adding `exactOptionalPropertyTypes: true` with full dependency checking currently fails in upstream declarations, even without importing Kind UI; that stricter combination is not claimed yet.
206
-
207
- The usage example has black monochrome and color palettes, a compact Tailwind CSS 4 layout, and a self-hosted Latin Geist font. Its series use solid/circle and dashed/diamond marks, so color is not the only distinction. Switching palettes preserves chart state. The font's OFL license is included with the example. Component defaults use host `border`, `radius`, `popover` and `popover-foreground` variables when present, with native CSS fallbacks. Tailwind and the font are example tooling, not runtime dependencies or required styling choices for package consumers.
208
-
209
- ## Styling
210
-
211
- Import `@kind-ui/charts/styles.css` once for the default appearance. The JavaScript entry does not import CSS, so Node consumers can still import the components directly. The stylesheet contains only scoped component rules in the `kind-ui` cascade layer; it adds no page reset, theme or font. Omitting it leaves presentation to the host. All component defaults ship in this one CSS asset.
212
-
213
- For plain CSS, import the stylesheet before your application stylesheet. Normal unlayered application rules override its layered defaults. With Tailwind CSS 4, declare the order before both imports in your application CSS:
214
-
215
- ```css
216
- @layer theme, base, kind-ui, components, utilities;
217
- @import "tailwindcss";
218
- @import "@kind-ui/charts/styles.css";
219
- ```
220
-
221
- This order lets normal utility classes override component defaults without `!important`. Low selector specificity alone cannot override an incorrectly ordered cascade layer. Native `style` props on `Root`, `Legend` and `TooltipContent` still take precedence over normal stylesheet rules.
222
-
223
- Theme the components with `--kind-ui-chart-border`, `--kind-ui-chart-radius`, `--kind-ui-chart-popover`, `--kind-ui-chart-popover-foreground` and `--kind-ui-chart-legend-background`. They fall back to the existing host tokens; `--chart-legend-background` remains supported. Fixed layout/spacing values can be overridden through classes or CSS rather than a variable for every declaration.
224
-
225
- Default legends use 8px markers and 11px labels; interactive legend buttons retain native semantics with a minimum 28px height. Hidden legend buttons use readable muted text and markers with no strikethrough, preserving `aria-pressed`. The default tooltip uses compact 12px text, slim series markers, aligned tabular values and measured content width capped by `maxWidth` (180px by default). Host metadata owns concise labels and units; formatter and custom content overrides remain supported. Override the scoped styles or theme tokens as needed. `Tooltip` supplies mouse-following placement with measured boundary clamping and keyboard fallback. A guide is independent of tooltip content: use `<Tooltip cursor={{ stroke: "var(--muted-foreground)", strokeDasharray: "2 4", strokeWidth: 1 }} />` for a dotted hover line or `cursor={false}` (the default) to hide only the guide. The existing recipes demonstrate this with a Hover guide toggle.
226
-
227
- Stable `data-kind-ui` hooks are `chart`, `chart-legend`, `chart-legend-item`, `chart-legend-button`, `chart-indicator`, `chart-tooltip`, `chart-tooltip-label`, `chart-tooltip-list`, `chart-tooltip-item` and `chart-tooltip-value`. Decorative configured glyph wrappers expose `chart-icon`; color markers expose `data-indicator` in tooltips. Line components additionally expose `line-frame`, `tooltip-frame`, `tooltip-motion` and `active-marker`. Legend and tooltip items also expose `data-series` with their series key. Use that identity for series-specific styles instead of positional selectors; tooltip entries may be reordered or hidden. Interactive legend buttons retain `aria-pressed` for state styling. For example:
228
-
229
- ```css
230
- .my-chart [data-series="tasks"] [data-kind-ui="chart-indicator"] {
231
- border-radius: 50%;
75
+ export function TasksChart() {
76
+ return (
77
+ <section>
78
+ <LineChart
79
+ data={data}
80
+ config={config}
81
+ xDataKey="day"
82
+ aria-label="Tasks by day"
83
+ />
84
+ <table>
85
+ <caption>Tasks by day</caption>
86
+ <thead>
87
+ <tr><th scope="col">Day</th><th scope="col">Tasks</th></tr>
88
+ </thead>
89
+ <tbody>
90
+ {data.map(({ day, tasks }) => (
91
+ <tr key={day}><th scope="row">{day}</th><td>{tasks}</td></tr>
92
+ ))}
93
+ </tbody>
94
+ </table>
95
+ </section>
96
+ );
232
97
  }
233
98
  ```
234
99
 
235
- Per-instance series colors and per-entry indicator color values remain inline CSS variables. They are data-dependent, not theme defaults. Chart dimensions, axes and other engine geometry stay application-owned.
236
-
237
- ## Develop
238
-
239
- At the repository root: `npm ci`, then `npm exec playwright install -- --with-deps chromium` (Linux dependencies may need administrator permission). Run `npm run dev:chart` for the example, or `npm run check` for library, packed-consumer, type and browser checks.
240
-
241
- The pack lifecycle rebuilds from clean output and rejects missing runtime modules, declarations, CSS or documentation. Recipe links below use the repository recipe pages so they remain available outside the repository. The packed check creates an actual tarball, installs it and the pinned peer/type dependencies into an isolated consumer, then checks public APIs with NodeNext and Bundler resolution. With the required Motion peer installed, it checks root imports/declarations and builds disabled and animated line/area consumers using the same exports; it also proves the removed `/motion` path cannot resolve. The bar consumer and a combined area/bar consumer use the same tarball and strict resolution checks. The combined browser proof checks native geometry, unique material filter IDs and independent visibility when both families share a page. These fixtures contain only public package imports and host data/extensions; no example implementation is copied into the proof. Migrated area and bar host recipes are typechecked separately. It also builds a plain-CSS production consumer for the browser checks, verifying CSS delivery and application overrides. Peer installation can require npm registry access; the package under test always comes from the local tarball, never a workspace link or registry copy.
242
-
243
- ## Area ownership and API
244
-
245
- `AreaChart` and `AreaSeries` use the same Root, Legend and Tooltip exports as lines. Recharts owns area geometry, native interpolation (`monotone`, `linear`, `stepAfter`), baseline (`baseValue`), stacking (`stackId`) and normalization (`stackOffset="expand"`). Kind owns registered metadata, Root-controlled visibility, bounded pointer/keyboard tooltip placement, and Motion reveal/default active marks. Hosts own labels, axes, data, formatting, gradients and reference thresholds.
246
-
247
- `AreaChart` accepts native Recharts AreaChart props, SVG ref, caller handlers and `animate={false | true | AreaAnimation}`. AreaAnimation has the same fields and defaults as LineAnimation. Data, size, controlled visibility and native `hide` changes discard stale pointer coordinates, cancel entrance and snap owned hover motion. Reduced-motion preference changes disable owned animation. Custom marks/content remain mounted when animation changes.
248
-
249
- `AreaSeries<DataPoint, Value>` accepts native Area props and children except `isAnimationActive`; Recharts animation is always disabled. String data keys identify Root metadata automatically. Use `seriesKey` for numeric/function keys; it is required for controlled visibility. Native `hide={true}` and Root visibility both apply. Stroke and fill default to Root color; explicit native overrides win. Custom `dot`, `activeDot`, `shape`, handlers and nested LabelList retain native composition. Recharts Area has no public component ref; place refs on host marks or shapes.
250
-
251
- ```tsx
252
- <Chart.Root config={{ visits: { label: "Visits", color: "#3659b8" } }}>
253
- <Chart.AreaChart width={400} height={220} data={points} animate={true} accessibilityLayer aria-label="Visits">
254
- <XAxis dataKey="day" />
255
- <Chart.AreaSeries dataKey="visits" type="monotone" connectNulls={false} fillOpacity={0.2} />
256
- <Chart.Tooltip />
257
- </Chart.AreaChart>
258
- </Chart.Root>
259
- ```
260
-
261
- Missing/null values remain gaps by default; numeric zero remains data. Hosts may explicitly choose `connectNulls`. The eight migrated area recipes are host compositions of these exports. Stacked/percent recipe examples use complete nonnegative inputs; percent axis labels and tooltip formatting belong to the host, use visible payload totals, and show “No share” for an all-zero row. The package does not impute, validate or mutate stack data.
262
-
263
- Implementation references: [Recharts AreaChart](https://recharts.github.io/en-US/api/AreaChart/), [Recharts Area](https://recharts.github.io/en-US/api/Area/) and [Motion values](https://motion.dev/docs/react-motion-value).
100
+ Use the [Line guide](https://kindui.dev/charts/docs/components/line/)
101
+ for composition and customization. The documentation covers chart selection,
102
+ peer requirements, styling, motion and accessibility; applications own their
103
+ data, domains and business state.
264
104
 
265
- ### Area materials
105
+ ### Loading chart data
266
106
 
267
- `AreaSeries.material` accepts `"plain"` (default), `"paper"`, `"clay"`, and `"glow"`, independently of color and `AreaChart.animate`. `AreaMaterial` exports that union. Paper adds fibers, Clay adds broad soft convex matte relief with upper-left light, gradual lower-right shading and a small external cast shadow, and Glow adds a colored halo with a light rim. The native Recharts area shape still owns interpolation, gaps, stacked/range baselines, stroke and fill paths. No displacement changes data geometry.
107
+ All chart families accept `loading?: boolean` alongside `animate`, plus
108
+ `loadingLabel?: string` (default “Loading chart”). Import the default stylesheet.
268
109
 
269
110
  ```tsx
270
- <Chart.AreaChart data={data} animate={false}>
271
- <Chart.AreaSeries dataKey="value" material="clay" fill="#db7093" fillOpacity={0.65} />
272
- </Chart.AreaChart>
111
+ <LineChart
112
+ config={config}
113
+ data={rows}
114
+ xDataKey="month"
115
+ height={280}
116
+ aria-label="Monthly sales"
117
+ animate={{ revealDurationMs: 650 }}
118
+ loading={pending}
119
+ loadingLabel="Loading monthly sales"
120
+ />
273
121
  ```
274
122
 
275
- Explicit `shape` or `filter` takes precedence over the material; consumer gradients, fill opacity, stroke widths and handlers remain intact. Low fill opacity also softens the finish. Filter IDs are unique per mounted series, independent of consumer IDs. Area filters use geometry bounds including baselines and remain inside the engine/chart clipping and Motion reveal. Materials are static SVG filters and add no animation. Use `--kind-ui-area-paper-fiber`, `--kind-ui-area-paper-grain`, `--kind-ui-area-clay-light`, `--kind-ui-area-clay-shade`, `--kind-ui-area-clay-highlight` (default `0.48`), `--kind-ui-area-clay-shadow` (default `0.28`), `--kind-ui-area-clay-cast` (default `0.12`), `--kind-ui-area-glow-light`, and `--kind-ui-area-glow-opacity` on the chart root to tune the finish.
276
-
277
- ## Bar ownership and API
278
-
279
- `BarChart` and `BarSeries` are maintained package components, imported from the same public entry point as `LineChart`. Kind owns metadata colors, controlled series visibility, pointer/keyboard modality, bounded tooltip placement and optional Motion. Recharts owns bar geometry, grouping/stacking, axes, labels, selection and keyboard traversal. The consumer owns data, ordering, domains, chart orientation, accessible names and a text/table alternative.
123
+ Pass the same props directly to composed chart roots inside the existing `Root`
124
+ and native sizing/composition. Supported families: Line, Area, Bar, Combo, Pie,
125
+ Radar, RadialBar, Scatter, Heatmap, Waterfall, Histogram, BoxPlot, ActivityRings
126
+ and Sankey. Each uses its own decorative chart silhouette independent of your data.
127
+ Pulse-based skeletons choose a clearly different bounded profile while fully hidden;
128
+ their geometry stays stable through renders and resize between pulses. A soft leading reveal and
129
+ trailing fade overlap, following the chart’s native entrance duration, easing and
130
+ direction: horizontal paths and left-to-right bar-family windows, angular sectors,
131
+ ordered point/cell opacity or directional flow. Radar keeps two six-vertex polygons
132
+ visible and smoothly morphs between bounded decorative shapes; reduced motion
133
+ holds both still. RadialBar keeps continuous angular velocity through its closing seam. Combo preserves separate family
134
+ reveal options. Pulse timing includes room for the trail to leave and a hidden
135
+ geometry swap; loading never delays actual completion.
136
+ Reduced motion displays a static silhouette.
137
+
138
+ Actual chart content remains mounted, sized, hidden and inert while pending.
139
+ The chart exposes `aria-busy` and a separate live status. Native axes, series,
140
+ refs, style and event ownership stay with the consumer. External legends remain
141
+ available. Heatmap uses its table viewport; Sankey uses its native flow container;
142
+ polar illustrations retain circular proportions. Skeletons have no values,
143
+ labels or tooltip targets.
144
+
145
+ Completion immediately restores actual data and rearms the family’s existing
146
+ entrance when `animate` enables it. There is no forced wait or queued completion.
147
+ Empty data does not imply loading: render an explicit empty-result message after
148
+ completion. Loading does not change validation of supplied chart data. Requests,
149
+ cancellation, retries, partial results, errors and portals outside the native
150
+ chart remain host-owned. Supply an accessible data alternative alongside charts.
151
+
152
+ Run `npm run dev:chart` and open `/loading.html` for all-family replay, load,
153
+ empty-input, resize and interrupted-update controls.
154
+
155
+
156
+ ## Selective Pie glow
157
+
158
+ `PieSeries` accepts `glowCategories?: readonly string[]` with `categoryKey` and
159
+ explicit data. For a rounded donut, add `glowCategories={["design"]}` to
160
+ `<PieSeries data={data} dataKey="hours" categoryKey="key" nameKey="key"
161
+ innerRadius={58} outerRadius={108} cornerRadius={8} paddingAngle={2} />`.
162
+ The IDs resolve through the existing category config contract. Unknown or removed
163
+ IDs do nothing; reorder/filter preserve colors and membership. Selected default
164
+ sectors use glow; others retain `material` (default plain). Native custom paint,
165
+ shapes and handlers retain ownership. Keep labels and a data alternative.
166
+
167
+ ## License
168
+
169
+ [MIT](LICENSE) © 2026 Bhavesh Chowdhury.
170
+
171
+ ### Patterned bar fills
172
+
173
+ `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.
280
174
 
281
175
  ```tsx
282
- import { Root, BarChart, BarSeries, Tooltip } from "@kind-ui/charts";
283
- import { XAxis, YAxis, LabelList } from "@kind-ui/charts";
284
- import "@kind-ui/charts/styles.css";
285
-
286
- <Root config={{ count: { label: "Tasks", color: "#3659b8" } }}>
287
- <BarChart width={480} height={240} data={[{ day: "Mon", count: 8 }, { day: "Tue", count: -3 }]} animate={{ revealDurationMs: 800 }} aria-label="Tasks by day">
288
- <XAxis dataKey="day" />
176
+ const config = {
177
+ actual: { color: "#789abc", pattern: { kind: "hatch" as const } },
178
+ planned: { color: "#ed79ae", pattern: { kind: "stripe" as const } },
179
+ };
180
+ <Root config={config}>
181
+ <Legend />
182
+ <BarChart data={rows} width={480} height={260}>
183
+ <XAxis dataKey="category" />
289
184
  <YAxis />
290
- <BarSeries dataKey="count" radius={3}>
291
- <LabelList dataKey="count" position="top" />
292
- </BarSeries>
293
- <Tooltip />
185
+ <BarSeries dataKey="actual" material="clay" />
186
+ <BarSeries dataKey="planned" pattern={{ kind: "duotone", color: "CanvasText" }} />
294
187
  </BarChart>
295
188
  </Root>
296
189
  ```
297
190
 
298
- - `BarChartProps` retains native Recharts chart props and the SVG ref, including `layout`, `barGap`, `barCategoryGap`, `barSize` and `stackOffset`. Use `layout="horizontal"` for vertical bars, or `layout="vertical"` with a numeric X axis and category Y axis for horizontal bars. Native chart mouse handlers run alongside Kind's tracking.
299
- - `BarSeriesProps` retains native Bar props, children, cells, shapes, active bars, backgrounds, label/error-bar composition and handlers. It excludes `isAnimationActive`; Motion owns animation and the engine tween is always disabled. `fill` defaults to the Root color, with explicit fill and Cell overrides retaining native semantics. String data keys supply the metadata identity; set `seriesKey` for controlled numeric/function keys. Root visibility and native `hide` combine exactly as on `LineSeries`. Recharts Bar has no public component ref in the tested version; refs belong on custom shape nodes.
300
- - Group bars by composing series; stack them with matching native axis IDs and `stackId`. Use `stackOffset="sign"` for separate positive/negative stacks, and native `LabelList`, `Cell`, `Rectangle`, `ReferenceLine` and `activeBar` for labels, category colors, highlights and signed baselines. The public API does not impose category/value field names or coerce null/zero values. Default tooltip content shows null as missing alongside available series, retains zero and signed values, and suppresses entirely missing categories.
301
-
302
- `BarChart animate` accepts `false` (default), `true`, or `BarAnimation` with the same `revealDurationMs`, `revealEasing` and `hoverTransition` options as line. Each series opens a plot clip outward from the engine's zero coordinate (or nearest domain edge when zero is excluded) on its own numeric axis ID; negative values and both orientations retain their geometry. Grouped and stacked series share chart timing. A scale without a finite zero coordinate renders immediately. Initial axis registration and automatic tick-width measurement settle before geometry changes count as entrance interruptions; later font/label measurements that move the plot still finish the entrance immediately. Ranged bars retain native geometry; their reveal also opens from numeric zero. Custom labels outside the plot appear fully when reveal completes.
191
+ 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.
303
192
 
304
- Pointer/focus interaction, data/size/domain/orientation changes and native or controlled visibility changes finish entrance motion and discard stale pointer coordinates. Completed/interrupted reveals do not restart on data updates or palette changes. Tooltip springs retarget through the existing shared implementation and settle immediately when `animate={false}` or reduced motion applies. Neither animation nor geometry invalidation remounts custom tooltip content. Mount a new chart to replay entrance motion. Import the default stylesheet; per-series dynamic clip variables account for Recharts' portal-rendered marks without wrapping or replacing custom shapes. No new dependency, gallery, publication or registry work is included.
193
+ 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.
305
194
 
306
- The existing [ten bar compositions](https://github.com/bhaveshchow20/kind-ui/blob/main/examples/chart/BARS.md) exercise vertical, horizontal, grouped, stacked, labels, custom labels, category colors, highlight, signed and interactive use. The isolated packed consumer copies only host data/composition fixtures, checks public imports and strict NodeNext/Bundler declarations, builds production output and runs Chromium contracts against the tarball. These checks do not establish screen-reader conformance.
195
+ 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.
307
196
 
308
- ## Bar materials
197
+ ### Theme-aware series colors
309
198
 
310
- `BarSeries material="plain" | "paper" | "clay" | "glow"` applies a static SVG finish independently of fill and `BarChart animate`. The exported `BarMaterial` type uses the same vocabulary as lines. Plain remains the default. Paper adds a lightly textured fill with an uneven inset pencil contour; it never displaces or blurs the data boundary. Clay adds matte convex volume through broad upper-left light, diffuse lower-right shade, a quiet static microtexture and a gentle exterior cast shadow. Cast shade follows the category axis, avoiding added value length and heavy shadows between stack segments. It retains native paint alpha and has no sharp specular rim. Glow adds a painted halo and light rim. Native rectangles retain signed geometry, radii, per-cell colors, gradients and handlers. A native `shape`, custom `activeBar`, series `filter`, or individual `Cell filter` retains consumer ownership. The highlighted recipe deliberately retains its custom shape.
199
+ `SeriesColor` accepts a CSS color string, a readonly nonempty array of CSS color
200
+ strings, or `{ light, dark }` containing either shape. Arrays are gradient stops,
201
+ not categorical palettes: supply separate config keys for category identities.
202
+ Stops are evenly distributed from 0 to 100%; a single stop is solid. Each theme's
203
+ spacing is preserved when stop counts differ (intermediate colors use CSS
204
+ `color-mix(in srgb, ...)`). Empty arrays, sparse arrays, empty strings and incomplete
205
+ or unknown theme fields throw. Errors identify the series and color property (including
206
+ the theme and stop index when applicable), without printing color values or unrelated
207
+ config metadata. For example, an empty second dark stop reports
208
+ `config["revenue"].color.dark[1] requires a nonempty color string`.
209
+ CSS color syntax remains the browser's responsibility: CSS variables, `currentColor`
210
+ and modern color functions pass through unchanged, including during server rendering.
311
211
 
312
- Corner geometry remains native: use `radius={8}` for soft standalone bars. The existing recipes select that larger radius for Clay. For complete stacks, compose native `<BarStack radius={8}>` around `<BarSeries radius={0}>` segments: the engine rounds the outer envelope, including exposed caps when an end segment is zero, while internal joins remain square. Kind does not infer stack caps or override an explicit radius. Automatic series rounding could create gaps in inherited native BarStack contexts, whose stack IDs are not available through the public Recharts hooks. The radius remains consumer-owned so arbitrary stack composition stays truthful.
313
-
314
- Each series owns a unique filter that native rectangles apply independently within bounded object-relative regions. Recharts plot/stack clipping and Kind reveal clipping remain in place, so halos at plot edges can be clipped. There is no displacement, animated noise or fabricated bar. Tiny bars show less relief and can clip the outer glow. Extreme stroke widths may need a custom filter with host bounds. Filters add raster work per rectangle; dense data can be more expensive than plain bars. The existing `/bars.html` recipes provide independent Material, Palette and Motion controls.
315
-
316
- Bar finish tokens are `--kind-ui-bar-paper-fiber` (white), `--kind-ui-bar-paper-grain` (0.14), `--kind-ui-bar-clay-light` (white), `--kind-ui-bar-clay-shade` (#17212b), `--kind-ui-bar-clay-highlight` (0.48), `--kind-ui-bar-clay-shadow` (0.26), `--kind-ui-bar-glow-light` (white), and `--kind-ui-bar-glow-opacity` (0.6). Opacity tokens accept numbers from 0 to 1. Clay/Paper lighting and ink are composited atop native paint, preserving every painted pixel’s translucent alpha, including antialiasing. Clay’s decorative cast shadow is excluded from that footprint; only fully transparent exterior pixels acquire shade, derived from native alpha at a fixed 0.16 opacity. This shadow does not change the native data path, radius or stack clip, and native clipping can trim it. Lighting uses a silhouette so translucent fills retain the same matte relief. Pixel-sized light/blur offsets are capped for chart marks; tiny bars can show less relief. The raised direction follows shipped line Clay, with [clay.css](https://github.com/codeAdrian/clay.css) and [Malewicz’s Claymorphism tutorial](https://hype4.academy/articles/design/claymorphism-in-user-interfaces) as soft-volume references, adapted without copying assets or adding dependencies. Native shapes retain engine zero-label and background filtering. Clay and Paper primitives are bar-local; Glow reuses the coordinated filled-surface helper. Bar material rendering leaves line and area outputs unchanged. Validation covers Chromium; other browsers and print/export renderers remain unverified.
212
+ ```tsx
213
+ const config = {
214
+ revenue: {
215
+ color: { light: ["var(--brand)", "#2563eb"], dark: ["#fef3c7", "#f59e0b", "#92400e"] },
216
+ },
217
+ } satisfies SeriesConfig;
317
218
 
318
- ## Scatter and bubble charts
219
+ // Change colorScheme on this host without changing Root's key or remounting it.
220
+ <div style={{ colorScheme: dark ? "dark" : "light", "--brand": "#f59e0b" }}>
221
+ <Root config={config}>
222
+ <Legend />
223
+ <LineChart data={data} width={480} height={240}>
224
+ <XAxis dataKey="month" />
225
+ <YAxis />
226
+ <LineSeries dataKey="revenue" />
227
+ <Tooltip content={(tooltip) => <TooltipContent tooltip={tooltip} />} />
228
+ </LineChart>
229
+ </Root>
230
+ </div>
231
+ ```
319
232
 
320
- `ScatterChart`, `ScatterSeries`, `ScatterTooltip`, and `ScatterTooltipContent` are public exports with colocated prop types; `ScatterAnimation` shares the familiar `revealDurationMs`, `revealEasing`, and `hoverTransition` options. `ScatterChart` accepts native Recharts ScatterChart props and refs plus `animate={false | true | config}`. `ScatterSeries` accepts native Scatter composition (Cells, labels, custom/active shapes, native handlers, axis IDs, lines and fits), except `isAnimationActive`: Kind owns animation. Set `seriesKey` for controlled visibility because numeric axis keys describe dimensions, not series. Explicit native `hide` remains authoritative.
233
+ Themes follow the inherited CSS `color-scheme`, using native `light-dark()`;
234
+ without a host scheme the browser defaults to light. Set `color-scheme: light dark`
235
+ on a host for system preference, or `light`/`dark` for an explicit application
236
+ choice.
237
+
238
+ Browser syntax requirements (from MDN compatibility data):
239
+
240
+ | Generated CSS | Chrome / Edge | Firefox | Safari / iOS Safari |
241
+ | --- | --- | --- | --- |
242
+ | [`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+ |
243
+ | [`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+ |
244
+ | [`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+ |
245
+
246
+ For themed gradients including matching swatches, use Chrome/Edge 123+, Firefox
247
+ 127+, or Safari/iOS Safari 17.5+. These are syntax minimums, not a tested-browser
248
+ matrix; supplied color values can have additional requirements.
249
+
250
+ There is no polyfill, feature detection or automatic light/first-stop fallback.
251
+ Unsupported functions make the consuming paint or swatch declaration invalid;
252
+ SVG paints/stops can use their CSS initial or inherited values, and gradient
253
+ swatches can lose their background image. For older browsers, supply supported
254
+ plain color strings (or CSS variables with host-controlled light/dark values)
255
+ instead of themed objects. Unthemed stop arrays avoid generated `light-dark()`
256
+ and `color-mix()`, but their swatches still require the gradient syntax above.
257
+
258
+ Root retains `--color-<key>` as the first stop, emits zero-based
259
+ `--kind-ui-series-<encoded-key>-<index>` stops and a `-gradient` CSS swatch
260
+ token, where encoded keys are hexadecimal Unicode code points joined by hyphens.
261
+ This namespace cannot collide with another series' legacy `--color-<key>`.
262
+ Built-in series/category paints use uniquely scoped resources in each chart SVG; gradients run
263
+ left to right across the SVG viewport (including flat line geometry). Legend and
264
+ solid tooltip markers display the full gradient; dashed tooltip borders use the
265
+ first stop. Explicit native stroke/fill, styles, Cells and shapes retain their
266
+ existing ownership; configured bar patterns take precedence and use the first
267
+ stop as their solid base ink (the legend pattern swatch does the same). Native
268
+ explicit pattern colors remain consumer-owned. Icons keep priority. Strings and one-stop definitions require
269
+ no SVG resource. Consumer Root styles override generated tokens as before; override
270
+ indexed stops to customize a gradient. No theme subscription or remount is needed.
271
+
272
+ The runnable `tests/fixtures/identity-colors` host exercises explicit light/dark
273
+ changes, unequal stops, CSS variable colors, multiple roots and native paint
274
+ precedence (`npm exec vite tests/fixtures/identity-colors`, open `/?theme`).
275
+
276
+ ### Area patterns
277
+
278
+ `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`.
321
279
 
322
280
  ```tsx
323
- import { Root, Legend, ScatterChart, ScatterSeries, ScatterTooltip } from "@kind-ui/charts";
324
- import { CartesianGrid, XAxis, YAxis, ZAxis } from "@kind-ui/charts";
325
-
326
- const taskShape = "circle";
327
-
328
- <Root config={{ tasks: { label: "Tasks", color: "#7c5ce7", legendShape: taskShape } }}>
281
+ const config = {
282
+ actual: { color: "#789abc", pattern: { kind: "dots" as const, width: 2 } },
283
+ planned: { color: "var(--area-planned)", pattern: { kind: "lines" as const } },
284
+ };
285
+ <Root config={config}>
329
286
  <Legend />
330
- <ScatterChart responsive style={{ width: "100%", height: 320 }} animate={false}>
331
- <CartesianGrid />
332
- <XAxis type="number" dataKey="latency" name="Latency" unit=" ms" domain={[0, 100]} />
333
- <YAxis type="number" dataKey="acceptance" name="Acceptance" unit="%" domain={[0, 100]} />
334
- <ZAxis dataKey="requests" name="Requests" unit="k" domain={[0, 300]} range={[35, 1200]} />
335
- <ScatterSeries seriesKey="tasks" data={observations} shape={taskShape} />
336
- <ScatterTooltip zDimension={{ dataKey: "requests", name: "Requests", unit: "k" }} pointLabel={(record) =>
337
- record && typeof record === "object" && "id" in record ? String(record.id) : "Task"
338
- } />
339
- </ScatterChart>
340
- </Root>
287
+ <AreaChart width={480} height={260} data={rows}>
288
+ <XAxis dataKey="month" />
289
+ <YAxis />
290
+ <AreaSeries dataKey="actual" stackId="total" material="clay" fillOpacity={0.4} />
291
+ <AreaSeries dataKey="planned" stackId="total" fillOpacity={0.4} />
292
+ </AreaChart>
293
+ </Root>;
341
294
  ```
342
295
 
343
- For shape encoding, explicitly share a native symbol between `SeriesConfig.legendShape` and `ScatterSeries.shape`: `circle` (the native default), `cross`, `diamond`, `square`, `star`, `triangle`, or `wye`. For example, Search can use `circle` and Social `diamond`, including with identical monochrome colors. The maintained two-series `/scatters.html` recipe reuses each shape value for its points and legend. Legend does not introspect Scatter children, register inferred shapes, or change native points. Omitting `legendShape` preserves the ordinary square swatch, including for Scatter. Config changes and entry order determine legend markers immediately, even when series are hidden or unmounted; separate Roots scope their own config. Multiple charts under one Root share that Root's legend config; use separate Roots or distinct series keys when shapes differ.
296
+ 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.
344
297
 
345
- Custom point callbacks/elements retain ownership. For a custom legend-only marker, use existing `Legend` children to return an aria-hidden SVG plus the visible `label`, optionally using `key` and `visible`; the callback's supplied `marker` follows the priority above. Use existing `SeriesConfig.icon` when a decorative glyph should also appear in tooltip content. `legendShape` never changes tooltip indicators or icon priority. Native legend symbols expose `chart-indicator` and `data-legend-shape`; their default 12px SVG contains the native Recharts symbol in a fixed viewBox, with series color and muted hidden-button fill. The existing square swatch stays 8px. Consumer CSS can override these defaults.
298
+ The existing Next integration fixture additionally checks real server-rendered
299
+ color resource IDs through hydration and a theme change.
346
300
 
347
- `ScatterSeries material="plain" | "paper" | "clay" | "glow"` (exported `ScatterMaterial`) is independent of consumer color and Motion. Plain is the default. Paper uses deterministic inset pencil contours and subtle grain without displacing the path. Clay uses upper-left diffuse light and lower-right shade for convex matte relief, entirely inside the native silhouette. Glow uses a gentle luminous tint, inset light rim and soft exterior halo; the halo is decorative light, **not quantitative bubble area**. Native symbol paths, transforms, Z sizes and paint alpha remain exact. Glow light uses a separate noninteractive `use` and native-symbol geometry exclusion mask with an exterior guard covering declared stroke extents plus 1px for antialiased edges to keep fully transparent gradient regions and stroke-only interiors transparent. Geometry paths inside SVG definitions are decoration machinery, not additional data marks. Body finishes compose atop the original paint, including translucent gradients and invisible paint. Labels and native connecting lines are never filtered.
301
+ ## Legend and mark interactions
348
302
 
349
- Materials apply to default/string symbols, including native boolean default-shape options and `Cell` paint/geometry overrides. Custom function/element/object shapes retain their own finish; custom active shapes retain ownership independently. Explicit `filter` or `style.filter` on a series or Cell opts that symbol out, even `none`. Every finish also retains unchanged native rendering for explicit point `clipPath` or `style.clipPath`, including `none`: filtering can rerasterize antialiased clip edges, and separate Glow light cannot safely inherit arbitrary native-local or object-bounding-box clip coordinates. This declared clipping fallback preserves native alpha and is tested for both coordinate systems and both prop/style ownership; ancestor plot clipping still applies to every finish. Refs and native handlers are forwarded. Each rendered symbol owns a unique filter ID, including duplicate consumer series IDs and active portals.
350
-
351
- Effects scale with the square root of native area, with upper bounds (pencil rim 0.9px, Clay offset 3px/blur 2px, Glow blur 1.8px), **no minimum radius or size substitution**. Subpixel marks retain exact geometry and alpha but cannot show a full grain/relief pattern; the small-scale effect becomes tonal and may be indistinguishable below raster resolution. Nonpositive/unresolved sizes render through native Symbols without a finish. A declared nonnumeric stroke width or a stroke extent exceeding the square root of native area also uses unchanged native rendering instead of cropping paint; stroke-aware exclusion can suppress a small Glow halo while retaining its luminous body. This bounded fallback is covered with a 12px transparent gradient stroke, including `style.strokeWidth`. Glow's filter region is limited to three times each native symbol's bounding box and keeps native plot clipping, so extreme consumer strokes, registered custom symbol factories or boundary marks may crop decorative light. This is a bounded SVG finish for the seven built-in symbols, not an arbitrary custom-renderer guarantee.
352
-
353
- Native Recharts owns numeric axes, ZAxis area mapping and per-point selection. Use `ScatterTooltip` instead of the category-oriented default `TooltipContent`: its default content displays the actual selected record's dimension values, names and units; a point label comes from the supplied callback or configured series title. Optional metadata for a dimension's string dataKey uses the same `Root.config` `label`/`formatValue` contract; native `entry.formatter`/tooltip `formatter` takes precedence. `Legend` shows all config entries, so use native axis names/units or a tooltip formatter when dimension entries should not appear in the legend. Custom `content`, `frameProps`, frame refs, bounded positioning and shared Motion are retained. Use `<ScatterTooltipContent tooltip={nativeContentProps} />` to compose the default UI inside your own native content.
354
-
355
- No missing/zero/negative values are coerced or deduplicated by Kind. Default native symbols omit unresolvable x/y coordinates; custom shapes receive native nullable geometry and must guard it themselves. Recharts 3.10.1 uses the minimum Z range and omits the Z tooltip entry for both zero and missing z; default content displays only the native entries and does not invent a z value. Supply `zDimension={{ dataKey: "requests", name: "Requests", unit: "k" }}` to recover zero or missing size in maintained default content from the actual point record. `ScatterSizeDimension<Row>` also accepts a typed `(record: Row) => number | null | undefined` accessor without casts; reuse the accessor used by your ZAxis. String keys read own top-level properties only; use a function for nested paths. When native Recharts supplies a nonzero Z entry it remains intact, with its native formatter/name/unit. Mapping name/unit supplies the omitted entry, existing tooltip/series formatter and dimension config formatting apply, and missing entries use `missingValue` (default “No data”). No field is guessed when mapping is omitted. A table remains appropriate for all observations. Negative z is passed to the native scale; validate count domains in the host. Duplicate coordinates overlap but keep distinct record payloads/indexes. Custom Cells may override native geometry, exactly as with Recharts Scatter.
356
-
357
- Motion fades native marks without moving their coordinates; native geometry animation is disabled to avoid two animation engines. Shared tooltip positioning can animate. Reduced motion renders final client geometry with Motion off. With the pinned Recharts 3.10.1, server rendering a fixed-size Scatter chart produces an empty chart wrapper, without SVG axes or point marks; geometry appears after client mount. Keep a host-owned table or summary available in the server HTML. Motion starts off during SSR; this is not an SSR geometry or hydration support guarantee. On the client, interaction, x/y geometry, data/visibility changes and resize settle entrance, and stale pointer placement is discarded on geometry changes. Off/config switches preserve consumer content and handlers. Entrance does not replay after interaction. Consumers own input validation, data tables, errors/loading states and immutable data updates.
358
-
359
- For custom X axis IDs, set the matching native `ScatterTooltip axisId` so Recharts can resolve keyboard navigation. Native keyboard navigation visits points in the first registered series, in data order. Hover can select any series; arrows do not perform spatial or all-series navigation. Provide a keyboard-accessible data alternative for every series, especially overlapping points and missing measurements. Browser coverage is Chromium at the pinned peer versions; it is not a screen-reader/browser conformance claim or a large-dataset performance promise. See the [four bounded recipes](https://github.com/bhaveshchow20/kind-ui/blob/main/examples/chart/SCATTERS.md) and the isolated tarball consumer/browser proof in `tests/fixtures/scatter` and `tests/packed-scatter.spec.ts`.
360
-
361
- The internal frame reuses the approved standalone generic `engine + chartProps` seam shared by polar and Combo charts.
362
-
363
- ## Combo / Composed charts
364
-
365
- `ComboChart`, `ComboChartProps`, and `ComboAnimation` are public root exports.
366
- `ComboChart` uses Recharts `ComposedChart` and accepts its native props, SVG ref,
367
- handlers, axes, margins, layout, stacks, and children. Compose the existing
368
- `LineSeries`, `AreaSeries`, and `BarSeries` under one `Root`; one `Tooltip` shows
369
- all registered visible series at the selected category. `Legend` uses the same
370
- consumer-owned `visibleSeries` and `onVisibleSeriesChange` contract.
303
+ Visibility remains the default. Opt into persistent focus with one Root owner:
371
304
 
372
305
  ```tsx
373
- <Root config={config} visibleSeries={visible} onVisibleSeriesChange={setVisible}>
374
- <Legend />
375
- <ResponsiveContainer width="100%" height={260}>
376
- <ComboChart data={data}
377
- animate={{ lineReveal: { revealDurationMs: 700 }, areaReveal: false,
378
- barReveal: { revealDurationMs: 900 } }}>
379
- <XAxis dataKey="period" />
380
- <YAxis yAxisId="count" />
381
- <YAxis yAxisId="ms" orientation="right" />
382
- <AreaSeries dataKey="queued" yAxisId="count" fillOpacity={0.2} />
383
- <BarSeries dataKey="completed" yAxisId="count" />
384
- <LineSeries dataKey="latency" yAxisId="ms" dot={false} />
385
- <Tooltip />
386
- </ComboChart>
387
- </ResponsiveContainer>
306
+ <Root config={config} interaction={{
307
+ kind: "series", mode: "focus", eligibleKeys: ["revenue", "costs"],
308
+ markActivation: "matching-legend", defaultSelected: "revenue",
309
+ onSelectionChange: (key) => console.log(key),
310
+ }}>
311
+ <BarChart data={rows} width={400} height={240}>
312
+ <BarSeries dataKey="revenue" /><BarSeries dataKey="costs" />
313
+ </BarChart>
314
+ <Legend emphasis="series" />
388
315
  </Root>
389
316
  ```
390
317
 
391
- `animate` defaults to `false`; `true` uses the existing family defaults. An
392
- object accepts the same `revealDurationMs`, `revealEasing`, and shared
393
- `hoverTransition` as `LineChart`. Optional `lineReveal`, `areaReveal`, and
394
- `barReveal` override entrance timing/easing for each family; `false` disables
395
- that family's entrance. Hover/line visibility Motion remains shared, rather
396
- than separately controlled by these entrance options. Reduced motion disables
397
- all Kind Motion, including when the preference changes after mounting.
398
-
399
- Use Recharts `ResponsiveContainer` to mount at measured dimensions, as the
400
- recipes do. Native `responsive` sizing is accepted; a percentage placeholder
401
- followed by its first measured size can finish an entrance through the normal
402
- resize cancellation path.
403
-
404
- Line/area entrances sweep across the plot. Bars reuse `BarSeries`' own
405
- axis-specific, clamped zero baseline reveal for positive, negative and signed
406
- stacks. One interaction cancellation path finishes all active entrances when
407
- pointer/focus/keyboard input, visibility, data, geometry, bar layout, or native
408
- children change. A changed children identity conservatively finishes entrances,
409
- even if the parent merely rerendered; updates render immediately rather than
410
- replaying an entrance. Family completion removes only that family's clip.
411
-
412
- Colors remain independently supplied by `Root.config` or native series props.
413
- Native Recharts children, custom marks, cells, labels, filters and event handlers
414
- retain their ownership. Native marks do not register with Kind visibility or
415
- metadata; use the maintained series for the shared legend/tooltip contract.
416
- Native child animation props remain consumer-owned; managed series disable
417
- Recharts' competing animation as in their standalone families.
418
-
419
- Stack only compatible units on the same axes. Recharts owns stack semantics;
420
- use `stackOffset="sign"` for signed bar stacks. Separate axis IDs preserve
421
- unrelated units. `null` stays missing and `0` stays zero; `connectNulls` retains
422
- its native meaning. Native ComposedChart offers axis selection, not item-only
423
- bar selection. Supply a data table or equivalent text alternative.
424
-
425
- See [Combo recipes](https://github.com/bhaveshchow20/kind-ui/blob/main/examples/chart/COMBOS.md) for two axes, signed stacks,
426
- missing values, custom markers, independent legends and entrance controls.
427
-
428
- ## Pie and donut
429
-
430
- `PieChart`, `PieSeries`, `PieChartProps`, `PieSeriesProps` and `PieAnimation` are maintained public exports. A donut is a `PieSeries` with native `innerRadius`; it uses the same component and animation contract. No additional dependency is introduced.
318
+ `interaction` binds a `kind` (`series`, `category`, or focus-only `node`) to a
319
+ settled `eligibleKeys` snapshot. Include hidden Root items and zero values; exclude
320
+ removed, filtered, unavailable, or native-hidden items. Config-only entries do not
321
+ satisfy the last-visible guard. New bindings require that snapshot; native series
322
+ registration provides compatibility eligibility for existing controlled legends.
323
+ `mode` defaults to `visibility`; `markActivation` defaults to `none`.
324
+
325
+ Focus accepts either `selected` with required `onSelectionChange`, or
326
+ `defaultSelected` with an optional callback. Persistent focus works with
327
+ `emphasis="none"`. Repeated activation or Escape clears it; transient inspection
328
+ never writes selection. Invalid uncontrolled selection clears without a callback.
329
+ An invalid controlled ID paints no selection and resumes if that ID becomes valid.
330
+ Visibility uses existing `visibleSeries`/`onVisibleSeriesChange`, or opt-in
331
+ `defaultVisibleSeries`. An externally empty visibility value remains valid.
332
+
333
+ For categories, use `kind: "category"` and explicitly set
334
+ `interactionBinding="root"` on `PieSeries` or `RadialBarChart`, with `categoryKey`
335
+ and explicit data. Stable unique string keys must match Root config. Hiding filters
336
+ original rows and their positional Cells before layout; Pie re-normalizes angles.
337
+ ActivityRings forwards this binding through category `rootProps.interaction`.
338
+ For Sankey node focus, bind both `SankeyChart` and `SankeyLegend` to a Root with
339
+ `kind: "node", mode: "focus"`; incident links/endpoints remain emphasized.
340
+ Sankey visibility, link selection, and persistent Heatmap cell selection are excluded.
341
+
342
+ `useChartInteraction()` exposes `selected`, `visible`, `mode`, `kind`, `eligible`,
343
+ `activate({kind, key}, "legend" | "mark", event?)`, and `reset(event?)` for custom
344
+ controls. Actions return whether accepted. Consumer native handlers run first;
345
+ `preventDefault()` or `onBeforeInteraction(request)` returning `false` vetoes.
346
+ Reset also runs the before hook. Accepted actions emit one mode-specific callback
347
+ and one scoped polite announcement. Rejected last-hide actions announce the reason
348
+ without changing state or calling the change callback. Custom renderers retain
349
+ ownership and can use the helper to bind their own controls/paint.
350
+
351
+ Radar's existing local `selection="series"` remains a compatibility path, also
352
+ independent of transient emphasis. Combining it or its selection callbacks with
353
+ Root focus throws: choose one owner. Selection ownership stays fixed while mounted;
354
+ remount when changing controlled/uncontrolled ownership.
355
+
356
+ ## Decorative chart backgrounds
357
+
358
+ `ChartBackgroundPattern` is optional Cartesian plot chrome, independent of
359
+ `FillPattern` series encoding, legends, visibility and materials. Its transparent
360
+ tiles contain only decorative ink; series are painted above it. It uses Recharts'
361
+ plot area (excluding margins and axes), a scoped clip path and SVG IDs, and the
362
+ [Recharts layer contract](https://recharts.github.io/en-US/guide/zIndex/) just below
363
+ the default grid. Custom negative series/grid z-index overrides remain caller-owned.
364
+ It adds no layout, axes, tooltip payload, animation or accessibility semantics.
365
+ The stylesheet prevents descendant pointer interception.
366
+
367
+ Generated configured LineChart opts in with:
431
368
 
432
369
  ```tsx
433
- const itemKey: NonNullable<Chart.TooltipProps["itemKey"]> = entry => String(entry.payload.id);
434
- <Chart.Root config={categoryConfig} visibleSeries={visibleIds} onVisibleSeriesChange={setVisibleIds}>
435
- <Chart.PieChart width={400} height={300} animate={false}>
436
- <Chart.PieSeries data={rows.filter(row => visibleIds.includes(row.id))}
437
- dataKey="value" nameKey="id" innerRadius="50%" outerRadius="80%">
438
- {rows.filter(row => visibleIds.includes(row.id)).map(row =>
439
- <Cell key={row.id} fill={`var(--color-${row.id})`} />)}
440
- <Label position="center" value="Capacity" />
441
- </Chart.PieSeries>
442
- <Chart.Tooltip itemKey={itemKey} />
443
- </Chart.PieChart>
444
- <Chart.Legend />
445
- </Chart.Root>
370
+ <Chart.LineChart
371
+ config={config}
372
+ data={data}
373
+ xDataKey="month"
374
+ aria-label="Monthly totals"
375
+ backgroundPattern={{ pattern: "pinpoints", opacity: 0.15 }}
376
+ />
446
377
  ```
447
378
 
448
- Import `Cell` and `Label` from `@kind-ui/charts`. Metadata keys identify **categories**, independently of the shared numeric `dataKey`. `TooltipProps.itemKey` and `TooltipContentProps.itemKey` optionally resolve the native payload entry to the containing Root's metadata/visibility key. The default remains registered series ID, then `dataKey`, then `name`. The bounded Tooltip applies the resolver before visibility filtering and passes it to default content. Custom content receives the filtered native payload and retains its own rendering and formatting; pass the same resolver when composing `TooltipContent` yourself.
449
-
450
- Category visibility and Cells are consumer-owned: filter data and generate Cells from that same array so index alignment survives filtering and reordering. Root/Legend never change polar data or silently recompute shares. `PieSeries` defaults to a continuous allocation: native padding/corner defaults remain zero, and its default stroke is `none`. Explicit series/Cell strokes, padding angles and corner radii remain consumer customizations. `PieSeries.hide` hides the whole native Pie independently of category state. Multiple native Pies, native Tooltip selection/`defaultIndex`/`trigger`, `nameKey`, function/numeric `dataKey`, numeric/percentage/function radii, angles, padding, corner radius, labels, custom shapes, Cells, SVG attributes and sector handlers remain available. Chart SVG refs retain the native ref contract. Recharts does not expose a Pie component ref.
451
-
452
- `animate={false | true | config}` uses the established duration/easing/tooltip hover transition shape. Recharts animation is disabled in `PieSeries`; Motion grows each default sector from its own start angle within its native angular footprint on entrance. Labels remain at their final native positions. Custom `shape`, `activeShape` and `inactiveShape` retain ownership; Kind does not animate those custom marks. Motion stops and snaps to final geometry on pointer/keyboard interaction, data/visibility/geometry changes, resize or disabling animation. Reduced motion renders final geometry and bounded tooltip placement without motion. Entrance does not replay after an interruption; remount the chart for an intentional new entrance.
453
-
454
- Use nonnegative, finite values for meaningful proportional data. Kind preserves native values rather than inventing allocations: empty/all-zero inputs paint no allocation, and zero/missing categories remain distinguishable in the consumer-owned table. A zero category has no visible angular area; expose it in the legend/data alternative rather than imposing a minimum fake share. Provide readable labels and a table/list; SVG plus tooltip alone is not a complete data alternative. [Recharts Pie API](https://recharts.github.io/en-US/api/Pie/) and the pinned `recharts@3.10.1` source (`polar/Pie.js`, `shape/Sector.d.ts`) informed the payload, Cell and polar geometry integration. Motion cancellation uses [animation playback controls](https://motion.dev/docs/animate).
455
-
456
- `examples/chart/pies.html` contains two bounded recipes: a pie allocation and a donut capacity summary. Both consume these public APIs and share existing tooltip/legend/formatting/accessibility behavior. The isolated tarball host in `tests/fixtures/pie` is separate from the recipes and is checked with strict NodeNext/Bundler declarations, a production build and browser contracts.
457
-
458
- ### Pie and donut finishes
459
-
460
- `PieSeries` accepts `material="plain" | "paper" | "clay" | "glow"` (`PieMaterial`),
461
- independently of native fill/Cells and the chart’s `animate` prop. Plain is the default.
462
- Paper adds faint fibers and an uneven inset pencil contour; Clay gives soft convex
463
- matte relief; Glow emits a soft colored halo around the crisp native sector. All finishes preserve native
464
- angles, radii, path, paint alpha, continuous zero-padding/no-stroke defaults and labels.
465
- Donuts use the same prop with native `innerRadius`.
466
-
467
- Custom shapes (including active/inactive shapes) remain consumer-owned. Explicit
468
- sector `filter` or `style.filter`, including Cell overrides, bypasses the built-in
469
- finish on that sector. Native gradients, clipping, IDs and handlers remain available.
470
- Paper/Clay remain inset. Glow’s decorative halo can overlap adjacent sectors/rings;
471
- it does not change quantitative geometry or native body alpha. No finish displaces paths.
472
- Per-sector filter/mask/source IDs are instance-scoped. Native paint masks retain
473
- Paper/Clay alpha at curved edges; Glow keeps the native body separate from its
474
- decorative halo. The halo follows the painted footprint, including gradient fades. Switching finishes during entrance snaps
475
- to final geometry using the existing interruption contract.
476
-
477
- Optional CSS variables: `--kind-ui-pie-clay-light`, `--kind-ui-pie-clay-highlight`,
478
- `--kind-ui-pie-clay-shade`, `--kind-ui-pie-clay-shadow`, `--kind-ui-pie-paper-fiber`,
479
- `--kind-ui-pie-paper-grain`, `--kind-ui-pie-paper-ink`,
480
- and `--kind-ui-pie-glow-opacity`. Lighting adapts locally to native radius and ring
481
- thickness. The pie/donut recipes expose the material control and keep category totals
482
- and selection consumer-controlled.
483
-
484
- ## Radar and radial bars
485
-
486
- `RadarChart` / `RadarSeries` and `RadialBarChart` / `RadialBarSeries` are maintained public exports. Their generic chart `*Props<DataPoint>` and series `*Props<DataPoint, Value>` types retain native typed data keys. The charts accept their native polar chart props and SVG refs, including `layout="centric" | "radial"`, centers, radii, angles, synchronization and event handlers. The series accept native shapes, dots/active marks, Cells, backgrounds, labels, axis IDs, stack IDs, z-order and handlers. Recharts 3.10.1 exposes no series component ref; place refs on custom SVG marks. Recharts owns polar geometry and native category payloads.
379
+ Explicit composition uses the part inside the chart. Configured explicit children
380
+ replace generated parts, so do not also pass `backgroundPattern`:
487
381
 
488
382
  ```tsx
489
- import * as Chart from "@kind-ui/charts";
490
- import { PolarAngleAxis, PolarGrid, PolarRadiusAxis } from "@kind-ui/charts";
491
- import "@kind-ui/charts/styles.css";
492
-
493
- <Chart.Root config={{ score: { label: "Score", color: "#3161bd" } }}>
494
- <Chart.RadarChart width={420} height={300} data={dimensions} animate={false}>
495
- <PolarGrid />
496
- <PolarAngleAxis dataKey="dimension" />
497
- <PolarRadiusAxis domain={[0, 100]} />
498
- <Chart.RadarSeries dataKey="score" fillOpacity={0.2} />
383
+ <Chart.Root config={config}>
384
+ <Chart.BarChart data={data} responsive style={{ width: "100%", height: 280 }}>
385
+ <Chart.ChartBackgroundPattern pattern="crossings" size={20} opacity={0.12} />
386
+ <Chart.XAxis dataKey="month" />
387
+ <Chart.YAxis />
388
+ <Chart.BarSeries dataKey="total" />
499
389
  <Chart.Tooltip />
500
- </Chart.RadarChart>
390
+ </Chart.BarChart>
501
391
  </Chart.Root>
502
392
  ```
503
393
 
504
- For radial progress, use `RadialBarChart` with a **numeric** `PolarAngleAxis` and an explicit domain such as `[0, 100]`, a categorical `PolarRadiusAxis dataKey="dimension"`, and `RadialBarSeries dataKey="score"`. Keep category ordering and any category filtering in your data. `Cell` styling and native category payloads remain intact. Kind's controlled `visibleSeries` and legend identify **series**, through string `dataKey` or explicit `seriesKey` for function/numeric keys. `hide={true}` additionally hides a series; `hide={false}` cannot override Root visibility. A controlled non-string key without `seriesKey` throws an actionable error. Explicit fill/stroke overrides Root color defaults.
505
-
506
- Both charts accept `animate={false | true | config}` using `RadarAnimation` / `RadialBarAnimation` (the existing `revealDurationMs`, `revealEasing`, `hoverTransition` contract). Motion fades each native series from 0 to 1 over 1000ms by default; it does not interpolate polar coordinates or category indices. This preserves quantitative geometry and consumer shapes. Shared tooltip motion and the default radar active marker use the existing hover transition. Native engine animation is disabled and `isAnimationActive` is excluded from series props. Animation callbacks/interpolation settings on native props remain available for compatibility but do not run while engine animation is disabled.
507
-
508
- Off/reduced-motion modes render full geometry immediately and stop in-flight opacity/hover movement. Pointer, focus and keyboard input finish entrance. Data/visibility/size changes, native polar chart geometry props, series axis/key/group props and child composition changes also finish entrance and clear stale pointer coordinates. Native labels/backgrounds/active marks can render through independent Recharts z-index portals and are not promised to fade. Series visibility changes snap in every mode; entrance does not replay after updates or interaction. Remount the chart to request a fresh entrance. A series mounted later may enter only while the chart's entrance remains uninterrupted. Custom tooltip content, active shapes, backgrounds and native data alternatives remain consumer-owned.
509
-
510
- The shared `Legend`, `Tooltip` and `TooltipContent` provide the same formatting, measured bounds and controlled visibility as Cartesian charts. Recharts 3.10.1 provides polar keyboard traversal with Left/Right, Enter toggling, and Escape dismissal. Give each chart an accessible name and provide a value table; native keyboard behavior is preserved rather than replaced. Empty and zero data retain native behavior.
511
-
512
- The `/polar.html` showcase uses public APIs for comparison, outline and range radar, grouped rings, stacked arcs and a half-circle gauge. It includes explicit domains, controlled legends, Motion/data/update controls and value tables. `tests/fixtures/polar` installs the actual tarball in an isolated consumer, checks strict NodeNext/Bundler declarations and production builds, and compares browser paths against native Recharts charts.
513
-
514
- First-party references: [Radar API](https://recharts.github.io/en-US/api/Radar/), [RadialBar API](https://recharts.github.io/en-US/api/RadialBar/), and the tested package's `types/polar` and `es6/polar` sources.
515
-
516
- ### Radial band labels
517
-
518
- Use native `LabelList dataKey="dimension" fill="white" content={<Chart.RadialBarLabel show={showText} />}` inside `RadialBarSeries`. `RadialBarLabel` is maintained label content, not a sector renderer. It consumes Recharts' native polar viewBox, follows the mid-radius within that sector's endpoints, reverses the path for upright reading, and checks measured glyph bounds. `fontSize` defaults to 11 numeric pixels, `minFontSize` to 9 and `padding` to 2. Thin/short/zero/invalid sectors and values that cannot fit omit or hide text. The native LabelList can inject fill; set its fill explicitly for contrast. `formatter`, SVG presentation/events and SVG text `ref`/`labelRef` (including React 19 cleanup) remain available. Layout coordinates/transform are excluded because the helper owns arc placement. Oversized CSS font overrides also fail the fit guard.
519
-
520
- `show={false}` controls only this visual label; it does not filter series, data, tooltip payload or Root metadata. Control tooltip visibility independently with native `Tooltip active={false}`. Labels render hidden during SSR until client SVG measurement; retain a value table for an immediate data alternative. Arbitrary geometry transforms or inherited letter/word styling can change available space; measurement conservatively hides labels when their glyph bounding boxes exceed the native sector.
521
-
522
- The [polar gallery audit](https://github.com/bhaveshchow20/kind-ui/blob/main/examples/chart/POLAR-GALLERY.md) maps all eighteen current first-party shadcn radar/radial variations to runnable public compositions.
523
-
524
- ## Histogram family
394
+ Presets are `pinpoints`, `crossings` and `waves`. `size` is a positive finite tile
395
+ size in SVG user units (default 16), and `opacity` is finite in [0, 1] (default
396
+ 0.15). Zero opacity is supported. `color` accepts CSS paint, including theme
397
+ variables; the default is `var(--kind-ui-chart-grid, CanvasText)`. Resize changes
398
+ the plot rectangle and clip, preserving tile density and IDs. Missing or empty
399
+ plot geometry renders nothing; invalid options throw even before geometry exists.
525
400
 
526
- `HistogramChart`, `HistogramSeries`, and `binHistogram` are maintained public exports, with `HistogramBin`, `HistogramMeasure`, `HistogramBinningResult`, chart/series props and `HistogramShapeProps` types.
401
+ Custom registration is consumer-owned and immutable: define reusable patterns in
402
+ a local module or catalog, then pass the definition directly. No global registry,
403
+ provider, series metadata or side-effect registration is required:
527
404
 
528
405
  ```tsx
529
- import * as Chart from "@kind-ui/charts";
530
- import { CartesianGrid } from "@kind-ui/charts";
531
-
532
- const result = Chart.binHistogram([0, 1, 2, 2, null, NaN, 9], [0, 1, 3]);
533
- <Chart.Root config={{ count: { label: "Density", color: "#167d77" } }}>
534
- <Chart.HistogramChart bins={result.bins} measure="density" width={480} height={260}
535
- xAxisProps={{ label: { value: "ms", position: "insideBottom" } }}>
536
- <CartesianGrid vertical={false} />
537
- <Chart.HistogramSeries />
538
- <Chart.Tooltip shared={false} labelFormatter={(_label, entries) => {
539
- const bin = entries[0]?.payload;
540
- return bin ? `[${bin.lower}, ${bin.upper}${bin.upper === 3 ? "]" : ")"} ms` : "";
541
- }} />
542
- </Chart.HistogramChart>
543
- </Chart.Root>;
406
+ const cornerMarks = Chart.defineChartBackgroundPattern(({ size, color, idPrefix }) => (
407
+ <g id={`${idPrefix}-tile`}>
408
+ <path d={`M0 ${size / 3}V0H${size / 3}`} fill="none" stroke={color} />
409
+ </g>
410
+ ));
411
+ const backgrounds = { cornerMarks }; // optional local catalog
412
+
413
+ <Chart.ChartBackgroundPattern pattern={backgrounds.cornerMarks} size={24} opacity={0.1} />;
544
414
  ```
545
415
 
546
- The helper owns raw-sample aggregation with **explicit edges**. Edges must be finite, strictly increasing, and have finite positive differences. Intervals are `[lower, upper)`, except the final upper edge is included, following [D3 bin](https://d3js.org/d3-array/bin) and [NumPy histogram](https://numpy.org/doc/stable/reference/generated/numpy.histogram.html). It returns every bin, including zero counts, plus `accepted`, `missing` (`null`/`undefined`), `nonfinite` (`NaN`/infinities), and `outOfRange` totals. It does not coerce, infer edges, round, impute, weight observations, or select a statistical binning estimator. Inputs are not mutated; work is O(samples × log(edges) + edges).
547
-
548
- For **pre-binned data**, callers supply ordered, nonoverlapping `{ lower, upper, count }` intervals and own aggregation and interval membership. Gaps are retained as quantitative space, rather than compressed into categories. Counts must be nonnegative safe integers; bounds, widths and overall domain span must be finite. Invalid bins, overlaps, unsafe totals, and nonfinite or underflowing positive density fail with actionable errors. Empty `bins=[]` uses a neutral `[0,1]` x-domain and no marks. All-zero bins retain zero height; density is zero when total count is zero.
549
-
550
- `measure` is always required. `count` means height = count. **With unequal widths, count-mode rectangle area does not represent frequency**; disclose that choice to readers. Prefer `density` for unequal bins: height = count / total count / bin width, so the sum of height × width is one when total is positive. Density units are the reciprocal of the input unit (e.g. ms⁻¹), rather than samples or percent. Counts remain available in the original tooltip payload and data table. No rounding is applied to bin edges or counts; native Recharts retains ownership of SVG coordinate serialization. Formatting belongs to the consumer.
551
-
552
- The chart reuses `BarChart`/`BarSeries`, visibility metadata, `Legend`, shared `Tooltip` presentation and interrupted/reduced Motion. It owns vertical layout and linear numeric axes (ID 0), maps x/width from actual bounds, and preserves the zero baseline. Use `xAxisProps`/`yAxisProps` for axis labels, ticks, styling and padding. Native `CartesianGrid`, `ReferenceLine`, SVG definitions, `Cell`, chart refs, attributes and handlers compose as children/props. `HistogramSeries.shape` receives native `BarShapeProps` with corrected x/width and `bin`; it owns its resulting SVG and can return a native `Rectangle`. Native bar Cells and events remain available. The default shape has square corners and exact numeric boundaries. `material="plain" | "paper" | "clay" | "glow"` adds static native-bin paint, independently of color and Motion. Paper reuses bar pencil/grain primitives; Clay reuses soft convex matte relief but crops its exterior cast shadow because both histogram axes are quantitative. Glow adds an exterior decorative halo and crisp inset light; the underlying body retains native translucent alpha. No finish rounds or displaces interval boundaries. All finishes use the documented `--kind-ui-bar-*` tokens. Each painted bin has its own filter ID and exact user-space bounds; filter padding resolves native CSS stroke widths on mounting and React updates. Stylesheet-only stroke transitions without a React render remain unverified. Custom `shape`, series `filter`/`style.filter`, and per-Cell `filter`/`style.filter` retain paint ownership. Custom shape callbacks still receive corrected geometry and original bins. The material gallery is `/histograms.html?materials`; zero bins retain zero geometry and no material-created marks. Native bar labels remain excluded by the core contract; use axis labels, tooltips, tables or the custom shape extension for bin annotations.
553
-
554
- Use `Tooltip shared={false}` for pointer hit testing against the actual bin rectangles; native shared-axis tooltip selection uses nearest midpoints. Native arrow-key selection remains available. Label the original interval and unit through `labelFormatter`, rather than presenting the midpoint as a bin boundary. A zero-height bin has no pointer target; keyboard selection and a complete table expose its value. The consumer owns the data alternative and units. The feature recipes at `/histograms.html` demonstrate raw rebinning with discard audit, unequal-bin density, native custom shapes/reference lines, controlled visibility, responsive layout and expandable tables.
555
-
556
- For labels, use the custom shape with its corrected geometry. Native Bar `LabelList` uses the engine’s original bar sizing and is not supported for unequal-bin edge positions.
557
-
558
- This bounded family does not support horizontal orientation, stacking, native bar sizing/minimum heights/backgrounds, automatic edges, weighted/fractional counts or nonlinear histogram axes. Do not add replacement primary axes or wrap the series in `BarStack`; native escape hatches remain the consumer's responsibility. Floating-point inputs follow JavaScript comparison without epsilon adjustments; extreme finite values that cannot produce a finite domain/density are rejected. Chromium and the pinned React/Recharts/Motion peers are the tested targets; there is no broader compatibility guarantee.
559
- ## Box plot: explicit statistics
560
-
561
- `BoxPlotChart` reuses Kind's `BarChart` (including optional `animate`) and public
562
- Recharts composition. Recharts has no native BoxPlot component: `BoxPlotSeries`
563
- registers a native range Bar and renders its summary with public axis-scale hooks.
564
- No new dependency or copied renderer is introduced.
416
+ The render escape hatch returns tile SVG children, not a full chart or pattern
417
+ element. Use the supplied `size`, `color`, and per-instance `idPrefix`; suffix
418
+ custom resource IDs and reference those scoped IDs. Keep custom output decorative:
419
+ no links, focusable elements, event handlers, portals or independent z-index
420
+ layers. The callback is trusted consumer code, not an SVG sanitizer.
421
+
422
+ Supported composition hosts are Line, Bar (including Waterfall), Area, Combo,
423
+ Scatter, Histogram and BoxPlot charts using the native Cartesian plot area.
424
+ Generated configuration is available only for LineChart. Polar, Pie, Sankey,
425
+ Heatmap and ActivityRings are outside this contract. Fixed-size chart SSR retains
426
+ the existing native empty shell; this part does not create server plot geometry.
427
+ ### Projected bar rows
428
+
429
+ `BarSeries<DataPoint, Value>.projection` is opt-in:
430
+ `{ isProjected: (datum: DataPoint) => boolean, pattern: FillPattern }`.
431
+ The caller supplies values and selects identity; Kind UI generates no forecasts.
432
+ For a trailing projection, capture the final row's stable ID before filtering or
433
+ reordering, then compare that ID in `isProjected`. It never implicitly marks the
434
+ new last visible row. Multiple selected identities are allowed.
565
435
 
566
436
  ```tsx
567
- const data = [{ group: "A", summary: {
568
- lowerWhisker: -5, q1: -2, median: 0, q3: 3, upperWhisker: 8,
569
- outliers: [-10, 20],
570
- } }];
571
- <Root config={{ spread: { label: "Distribution", color: "#16756c" } }}>
572
- <BoxPlotChart data={data} width={480} height={280}>
573
- <XAxis dataKey="group" />
574
- <YAxis type="number" domain={["dataMin", "dataMax"]} />
575
- <BoxPlotSeries dataKey="summary" seriesKey="spread" />
576
- </BoxPlotChart>
577
- </Root>
437
+ const projectedId = originalRows.at(-1)?.id;
438
+ const projection: BarProjection<Row> = {
439
+ isProjected: (row) => projectedId !== undefined && row.id === projectedId,
440
+ pattern: { kind: "hatch" },
441
+ };
442
+ // Use in grouped bars or share it across series with the same stackId.
443
+ <BarSeries<Row, number> dataKey="value" projection={projection} />;
444
+ <Tooltip content={(tooltip) => (
445
+ <TooltipContent tooltip={tooltip}
446
+ isProjected={(entry) => projection.isProjected(entry.payload)} />
447
+ )} />;
448
+ // In the consumer-owned table, alongside the unchanged numeric value:
449
+ <td>{projection.isProjected(row) ? "Projected" : "Observed"}</td>;
578
450
  ```
579
451
 
580
- - `BoxPlotSummary` requires finite numbers in the order
581
- `lowerWhisker <= q1 <= median <= q3 <= upperWhisker`.
582
- `outliers?: readonly number[]` must be finite and strictly outside the supplied
583
- whiskers. Duplicates are retained. Invalid present summaries throw actionable
584
- errors; `null`/`undefined` summaries render no marks, and an empty dataset renders
585
- no marks. Zero, negatives, equal quartiles and equal whiskers are valid.
586
- - The caller computes these statistics and chooses the quartile/fence convention.
587
- Kind does not ingest raw samples, classify observations, remove outliers, or
588
- fabricate statistics. `validateBoxPlotSummary(unknown)` returns the same summary
589
- or null; `boxPlotExtent(summary)` validates and returns the complete min/max,
590
- including every outlier. These helpers do not mutate input.
591
- - `BoxPlotSeries<Row>` accepts a direct property name or typed accessor in `dataKey`
592
- (no nested-path interpretation). `seriesKey` is required for metadata and
593
- controlled Root visibility. Native domain inference uses the complete extent,
594
- not just median or quartiles. Supply native axes and their IDs; for horizontal
595
- boxes use `layout="vertical"`, a numeric XAxis and categorical YAxis.
596
- - `barSize`, native series data, Cell paint, stroke, opacity, style, filter,
597
- clipPath, mask and visibility, LabelList, axis IDs, chart/series
598
- handlers, chart refs, and consumer children retain their native ownership.
599
- LabelList's native position refers to the enclosing range; supply a label
600
- dataKey or custom content if the label should describe a statistic.
601
- - `shape(props: BoxPlotShapeProps)` receives `summary`, `native: BarShapeProps`,
602
- mapped `coordinates`, category `center`/`size` and `orientation`. It owns its
603
- returned markup. `markProps` forwards SVG group attributes, styles, handlers and
604
- refs (one group per present row). `BoxPlotMark` is the exported SVG primitive;
605
- it accepts screen coordinates rather than statistical values. Degenerate boxes
606
- get a collapsed line without inflating the numeric IQR. `outlierRadius` controls
607
- the screen-space outlier symbol size.
608
- - Shared `Legend`, controlled visibility, `Tooltip`, and optional Motion remain
609
- available. A native tooltip value is the enclosing numeric range; use custom
610
- tooltip content to read the original payload and show quartiles/whiskers/
611
- outliers. The recipe demonstrates that and a full, always-available table.
612
- Recharts owns keyboard category selection; null rows have no numeric tooltip.
613
- Arrow keys and Escape are checked in Chromium. Tables provide complete numeric
614
- access independently of chart interaction and series visibility.
615
-
616
- Initial scope: linear numeric axes, categorical groups, two orientations, optional
617
- Bar reveal Motion with reduced-motion/interruption behavior. Stacking, minimum
618
- numeric sizes, native rectangle backgrounds/radius, native
619
- active-bar duplication, raw-sample estimators, weighted quartiles, notches,
620
- variable-width-by-sample-size boxes, and quantitative category positioning are
621
- outside this family. Nonlinear numeric axes, Brush, mixed-series composition and
622
- performance at large sample/group counts are not verified. Custom SVG marks are
623
- consumer-owned.
624
-
625
- References: [NIST box plot definitions and variants](https://www.itl.nist.gov/div898/handbook/eda/section3/boxplot.htm),
626
- [Recharts Bar](https://recharts.github.io/en-US/api/Bar/),
627
- [public X scale](https://recharts.github.io/en-US/api/useXAxisScale/),
628
- [public Y scale](https://recharts.github.io/en-US/api/useYAxisScale/).
629
- The sample estimator and whisker definition deliberately remain caller-owned.
630
-
631
-
632
- ### Box plot materials
633
-
634
- `BoxPlotSeries` and the screen-space `BoxPlotMark` accept `material="plain" | "paper" | "clay" | "glow"` (`BoxPlotMaterial`). Plain is the default. Paper uses the existing bar grain and uneven inset pencil contour; Clay uses bar soft convex matte relief; Glow adds an exterior painted halo and a crisp lightened body. These static finishes are independent of color and Motion and preserve every whisker, quartile, median and outlier coordinate. Native stroke width/dashes remain the input silhouette. No extra minimum extent is introduced: all-equal and tiny marks receive a line finish; missing rows still have no marks.
635
-
636
- Each present native mark has its own React-generated filter ID and user-space region, including outliers and resolved child stroke widths/miter limits, so line-only summaries do not require nonzero bounding boxes. Use React `identifierPrefix` for independently mounted roots. Box finishes reuse the `--kind-ui-bar-*` tokens documented above. Body alpha is preserved, including zero fill opacity; glow/cast effects can paint only outside the native footprint. Tiny marks have less room for visible grain/relief. Bounds are refreshed for React updates, stylesheet edits/loads, ancestor theme classes, viewport changes and pointer entry/exit. Direct CSSOM rule mutations without one of those signals are not observed. The default box fill remains `0.18`; an explicit `fillOpacity` (for example `0.65`) makes broad surfaces easier to see.
637
-
638
- An explicit series/Cell/mark `filter`, or `style.filter`, including `none`, disables the built-in finish for that mark. Custom `shape` owns its markup and is not automatically materialized; it may explicitly return a materialized `BoxPlotMark`. Cells, gradients, native paint/opacity, mark styles, clipping, masks, visibility, refs, handlers and labels retain ownership. Filters run on the original consumer mark group (parts remain direct children) and existing reveal/plot clips, which may trim decorative halos. Chromium is verified; other SVG engines and print renderers remain unverified. No shared helper changes, dependencies, workflow changes or releases accompany this material stack.
639
- ### Shared presentation example
640
-
641
- `examples/chart/presentation.html` demonstrates the same public options for line, area and bar, icon/swatch fallback, composed legend labels, native formatter tuples/suppression, custom content and light/dark host CSS variables. It enables motion by default while following live reduced-motion preferences, and includes keyboard instructions and all-series table values. Its source is also compiled against an independently installed tarball, with guarded public imports, strict NodeNext/Bundler checks, and Chromium interactions. Theme colors remain host-owned CSS variables; this change does not add automatic light/dark config mapping. String labels/colors remain required.
452
+ Selection applies to chart rows or explicitly supplied `BarSeries.data` in both
453
+ orientations. Empty chart data produces no marks. Native empty `BarSeries.data` overrides
454
+ inherit chart rows; projection follows the actual displayed payload. Null/undefined
455
+ rows are never passed
456
+ to the selector. Missing values retain native missing-bar behavior, and filtering
457
+ out a selected identity does not select a replacement. Zero remains zero. Keep
458
+ predicates pure and IDs unique; row indices do not offer reorder-stable identity.
459
+
460
+ Projection fills use the existing `FillPattern` seam and native Rectangle shape
461
+ props, so Brush slices cannot shift identity as positional Cells would. Unselected rows retain
462
+ configured/explicit series patterns and full gradient paints. Projection tiles
463
+ use the configured theme's solid first stop (`--color-key`), the same documented
464
+ fallback as ordinary Bar/Area pattern tiles. `pattern={false}` disables automatic
465
+ projection paint. Explicit series `fill`/`style.fill`, native shape options,
466
+ custom shapes/active shapes and any explicit Cell composition retain paint
467
+ ownership. Datum `fill`/`style.fill` also prevents automatic projection paint for
468
+ that row. Compose Cells yourself for custom per-row painting.
469
+
470
+ `TooltipContent.isProjected(entry)` shares caller selection but receives the
471
+ native tooltip entry, including its original payload. Projected items append
472
+ accessible text (`projectedLabel`, default `"Projected"`) without modifying
473
+ values, labels, formatter behavior or config. Custom tooltip content and data
474
+ alternatives must expose status themselves, even when custom paint overrides it.
475
+ The runnable public consumer example is the packed Bar fixture at
476
+ `/?projection` (add `&horizontal` for horizontal bars); it includes table status,
477
+ stacking, filtering, reorder and ownership controls. Configured series metadata
478
+ continues to own ordinary patterns; projection is a per-Series composition prop,
479
+ not global config or a generated-data recipe.
480
+
481
+ ### Percentage stack formatting
482
+
483
+ `createPercentStack({ values })` opts into formatting for scalar native
484
+ `stackOffset="expand"` Bar, Area and Combo stacks. Geometry, domains, axes and
485
+ stack membership remain caller-owned. It returns `tickFormatter` and
486
+ `normalizedValue`; no rows, series, native values or native tooltip payloads are
487
+ rewritten. Native Recharts still normalizes geometry exactly once.
642
488
 
643
489
  ```tsx
644
- <Chart.Legend hideIcon>
645
- {({ label, visible, marker }) => <>{marker}<span>{label}</span><small>{visible ? "Shown" : "Hidden"}</small></>}
646
- </Chart.Legend>
647
- <Chart.Tooltip content={(tooltip) => (
648
- <Chart.TooltipContent tooltip={tooltip} hideLabel indicator="dashed" />
649
- )} />
650
- ```
651
-
652
- ### Polar materials
653
-
654
- `RadarSeries` and `RadialBarSeries` accept `material="plain" | "paper" | "clay" | "glow"` (`PolarMaterial`), independently of consumer color and Motion. Plain is the default. Paper uses seeded inset pencil contours and subtle fiber grain without displacement; Clay adds broad upper-left convex matte relief; Glow adds a bright rim and exterior color light. All retain native polygon/sector paths, quantitative coordinates, gradients, fill/stroke opacity and zero-alpha paint. Paper and Clay retain native output alpha; Glow retains native alpha inside the mark and adds intentional decorative light outside it. The exterior halo is not a quantitative extent. Very thin/short marks have less room for interior relief. Chromium can rasterize curved antialiased edges differently when a native SVG filter uses spatial inputs; alpha regression checks require every covered pixel to stay within the independently measured native unfiltered/morphology/blur/offset-relief raster envelope (plus one byte for quantization), alongside untouched path/paint assertions and zero-alpha checks. Unfiltered and filtered edge rasters are not promised to be byte-identical.
655
-
656
- Custom Radar `shape`, RadialBar `shape` or custom `activeShape`, a series `filter`, or `style.filter` owns rendering and suppresses the material. Cell filter/style overrides retain native precedence. Dots, backgrounds, labels, refs and handlers remain native. Unique per-series filters use chart-space bounds so short/thin/empty arcs do not depend on nonzero object bounds. Native SVG and consumer clipping still apply. Optional CSS variables use `--kind-ui-polar-paper-{fiber,grain}`, `--kind-ui-polar-clay-{light,highlight,shade,shadow}` and `--kind-ui-polar-glow-{light,opacity}`.
657
-
658
- The polar recipes/gallery share a Material control and enable Motion by default, respecting reduced motion. Gauge text remains large in the center whitespace; ordinary radial labels remain at band center with independent Chart text visibility.
659
-
660
- ## Waterfall
661
-
662
- `computeWaterfallData(entries, initialBalance = 0)` returns fresh ordered rows for
663
- native numeric range bars. Each entry has a unique nonempty `id`, a `label`, and
664
- one of these explicit kinds:
665
-
666
- | Kind | Supplied value | Meaning |
667
- | --- | --- | --- |
668
- | `start`, `total`, `end` | finite number or `null` | Checkpoint: draw zero → value and establish the running balance. A checkpoint can intentionally disagree with the preceding balance. |
669
- | `delta` | finite signed number or `null` | Draw previous balance → balance + value. |
670
- | `subtotal` | none | Draw zero → current balance; do not add it again or reset it. |
671
-
672
- The default starting balance is explicitly zero; pass `null` for an unknown
673
- opening balance. Kind names express intent, not positional restrictions: an
674
- `end` is an explicit supplied total, never an automatically inferred final sum.
675
- A missing delta makes subsequent geometry/balances unknown until a known
676
- checkpoint restores them. Its known successors still retain their original
677
- values. A missing checkpoint also establishes an unknown balance. Zero remains
678
- numeric (`[balance, balance]` for a zero delta), with no invented minimum height.
679
- Nonfinite values, omitted values, duplicate/empty ids, supplied subtotal values,
680
- unknown kinds and arithmetic overflow throw. Inputs are not mutated.
681
-
682
- ```tsx
683
- import * as Chart from "@kind-ui/charts";
684
- import { Cell, ReferenceLine, XAxis, YAxis } from "@kind-ui/charts";
685
-
686
- const data = Chart.computeWaterfallData([
687
- { id: "opening", label: "Opening", kind: "start", value: 80 },
688
- { id: "cost", label: "Cost", kind: "delta", value: -100 },
689
- { id: "net", label: "Net", kind: "subtotal" },
690
- { id: "closing", label: "Closing", kind: "end", value: -20 },
691
- ]);
692
-
693
- <Chart.Root config={{ range: { label: "Balance", color: "#3478ae" } }}>
694
- <Chart.WaterfallChart data={data} width={480} height={280} animate>
695
- <XAxis dataKey="id" />
696
- <YAxis domain={["auto", "auto"]} />
697
- <ReferenceLine y={0} />
698
- <Chart.WaterfallConnectors data={data} />
699
- <Chart.WaterfallSeries material="paper">
700
- {data.map(row => <Cell key={row.id} fill={row.kind === "delta" ? "#b54d46" : "#3478ae"} />)}
701
- </Chart.WaterfallSeries>
702
- </Chart.WaterfallChart>
703
- </Chart.Root>;
490
+ const percent = createPercentStack({
491
+ values: (entry) => {
492
+ // Select this stack, excluding the Combo's latency line and other axes.
493
+ if (entry.dataKey !== "desktop" && entry.dataKey !== "mobile") return undefined;
494
+ const row = entry.payload as { desktop: number; mobile: number } | undefined;
495
+ return row ? [row.desktop, row.mobile] : undefined;
496
+ },
497
+ });
498
+ // Vertical columns / ordinary Areas: the numeric Y axis only.
499
+ <YAxis yAxisId="share" tickFormatter={percent.tickFormatter} />;
500
+ // Horizontal bars (layout="vertical"): the numeric X axis only.
501
+ <XAxis type="number" tickFormatter={percent.tickFormatter} />;
502
+ <Tooltip normalizedValue={percent.normalizedValue} />;
704
503
  ```
705
504
 
706
- `WaterfallChart` is the existing `BarChart` under a descriptive name, with its
707
- native props/ref, controlled visibility, interruption behavior, reduced-motion
708
- handling and opt-in Motion. `WaterfallSeries` binds `dataKey="range"` and
709
- `minPointSize={0}`. It accepts the remaining `BarSeries` extension points,
710
- including native shapes, cells, labels, active bars, filters, refs and events;
711
- `data`, `dataKey`, `stackId`, and `minPointSize` are excluded and rejected at
712
- runtime. Keep one unstacked Waterfall series on its axes. Supply the computed
713
- rows to the chart and the same rows to connectors, with the categorical axis
714
- using `id`. Custom range rows may be supplied directly by a host that owns its
715
- arithmetic. Brush-windowed connectors are not covered; subset the data for both
716
- primitives yourself.
717
-
718
- `WaterfallConnectors` uses native `ReferenceLine` segments, matching explicit
719
- `xAxisId`/`yAxisId` and horizontal (`layout="vertical"`) charts as well. Pass the
720
- same `seriesKey` (default `range`) and `hide` as the series. Connectors join only
721
- adjacent known balances that agree: no bridge over an unknown step, or from a
722
- computed balance to a differing checkpoint. They run between native category
723
- centers under bars, with default `zIndex={100}`, dashed stroke and no pointer
724
- capture; native `shape`, `stroke`, `position`, `zIndex`, labels and overflow props
725
- remain available. Use `position="middle"` to align center endpoints.
726
-
727
- Existing `BarMaterial` (`plain`, `paper`, `clay`, `glow`) applies independently to
728
- native floating rectangles without changing numeric geometry. Native custom
729
- shapes/filters retain material ownership, as with `BarSeries`; no additional
730
- material adapter or shared API change is needed.
731
-
732
- A native range tooltip reports range endpoints. For semantic values, compose
733
- `Tooltip` content using the original/computed row (as in
734
- [`waterfall-recipes.tsx`](https://github.com/bhaveshchow20/kind-ui/blob/main/examples/chart/waterfall-recipes.tsx)); use
735
- `filterNull={false}` when showing unknown steps. The host owns formatting,
736
- accessible data tables and source values. The responsive
737
- [`waterfalls.html`](https://github.com/bhaveshchow20/kind-ui/blob/main/examples/chart/waterfalls.html) recipe enables motion
738
- by default and includes a table, visibility/update controls and missing, zero,
739
- negative and crossing-zero examples.
740
-
741
- ### Sankey flows
742
-
743
- `SankeyChart` uses first-party Recharts `Sankey` for layout, native `node`/`link`
744
- object, element or callback renderers, child Tooltip, labels, SVG props and native
745
- events. `data` is `SankeyFlowData`: every node has a nonempty unique string `id`
746
- and string `name`; every link has its own unique `id`, a finite nonnegative
747
- `value`, and `source`/`target` as node IDs or integer array indices. String
748
- endpoints always mean IDs, including numeric-looking strings. No flow is
749
- synthesized, normalized or aggregated.
750
-
751
- `prepareSankeyData(data)` validates and copies input into native numeric
752
- endpoints. It rejects duplicate/empty identities, unknown IDs, out-of-range or
753
- fractional indices, missing/negative/nonfinite values, overflowing node totals
754
- and cycles, including zero links. Nodes with both incoming and outgoing links
755
- must balance to a relative tolerance of `1e-9`, with no absolute zero tolerance.
756
- Model losses/gains as explicit edges and boundary nodes. Boundary sources/sinks
757
- need no matching counterpart. Supply immutable data when changing a chart.
758
-
759
- Zero links and nodes without positive links remain in input and tables;
760
- `SankeyChart` excludes them from native layout. All-zero and empty charts display
761
- `empty` (default `No positive flows`). Native callback indices address this
762
- filtered rendering array. Renderer/event payloads retain typed `id` identities,
763
- including source/target node IDs on links. Native layout owns derived node
764
- values (maximum input/output). Never use a render index as an input identity.
765
-
766
- Finite values can still exceed native floating-point layout limits. At the
767
- native drawable height H, with N positive-flow nodes, the chart conservatively requires finite aggregate
768
- positive flow T, finite positive H/T, finite T*H and nonzero v*((H - (N-1)*padding)/T) for every
769
- positive link. It throws an explicit renderer-limit error rather than changing
770
- values. Supply explicitly rescaled units at your data boundary if necessary.
771
- Equal-value parallel positive links also throw a renderer-limit error because
772
- Recharts keys links by source, target and value. Distinct-value parallel links
773
- are supported; semantic validation and the table accept either. No duplicate
774
- flow is silently combined. These checks are separate from semantic validation. A frame too small for the
775
- conservative node-padding budget displays `Insufficient space for flows; use
776
- the data table` instead of negative native geometry. SSR and unmeasured frames
777
- also use this status until measured. Native ResponsiveContainer remains usable.
778
-
779
- `SankeyNode` and `SankeyLink` are optional native callback/element shapes, not
780
- series components. They accept SVG presentation/handlers and `rectProps` or
781
- `pathProps`. Computed coordinates, dimensions and link width win over supplied
782
- presentation attributes; link width also wins over inline CSS stroke width.
783
- `SankeyLink.material="solid" | "gradient"` remains the paint API, using native
784
- cubic coordinates and proportional stroke widths. Separately,
785
- `SankeyLink.finish` and `SankeyNode.finish` accept exported `SankeyFinish`:
786
- `"plain"` (default), `"paper"`, `"clay"`, or `"glow"`. No chart-level finish
787
- is injected into custom renderers. Set finishes explicitly on the optional marks:
505
+ Only attach the formatter to an axis whose units are fractions. Other axes keep
506
+ native ticks. A shared axis containing raw and fraction units needs a caller
507
+ chosen scale/domain; this helper cannot reconcile those units. Custom ticks and
508
+ `tickFormatter` stay native and caller-owned. `formatPercent(fraction)` is also
509
+ exported (one decimal at most, no locale-dependent output).
510
+
511
+ `values(entry)` must return the **raw members of that entry's own stack**, including
512
+ its value, or `undefined` for unrelated entries. Use native datum identity, stack
513
+ and axis selection where keys overlap. Match current native hidden-series
514
+ membership; do not sum the tooltip payload, which can contain unrelated stacks,
515
+ axes or lines. Update membership alongside controlled legends. Range values,
516
+ numeric strings and nonfinite values are outside the helper's scalar contract.
517
+
518
+ The helper uses the signed sum, matching native expand: missing members contribute
519
+ zero to the denominator but remain missing in content; all-zero rows display 0%;
520
+ negative fractions remain signed, with no absolute-value conversion or clamping.
521
+ A cancelling zero sum with nonzero members has no defined share and retains raw
522
+ formatting. Invalid/nonfinite totals or entries also retain raw formatting.
523
+ Native negative geometry/domain behavior remains native; this API does not promise
524
+ a 0–100% domain for negative data or fabricate absent values.
525
+
526
+ `Tooltip` and `TooltipContent` accept `normalizedValue(entry): number | undefined`.
527
+ Default content shows `25% (1)` with the original value in parentheses, using
528
+ configured `formatValue` for that raw text when present. Explicit entry/native
529
+ `formatter` takes precedence, including suppression and tuple results. Custom
530
+ content receives the unchanged native payload and owns its presentation; pass the
531
+ resolver explicitly when composing `TooltipContent`. Missing text, labels,
532
+ projection status, patterns and colors keep their existing contracts. For already
533
+ normalized source data, provide a resolver that returns the existing fraction;
534
+ do not compute another share or apply expand to data already transformed elsewhere.
535
+ Hosts still own raw data tables and accessible alternatives.
536
+
537
+ The source-only example at `/contracts.html` includes vertical/horizontal Bar,
538
+ Area and Combo with a separate raw latency axis and original data tables.
539
+ Recharts 3.10.1 applies native expand to every numeric axis. For mixed-unit Combo
540
+ charts, the example therefore uses caller-selected fraction `dataKey` accessors
541
+ for the stacked bars with `stackOffset="none"`, a `[0, 1]` share axis, and an
542
+ unchanged raw latency line on `[0, 10]`. The raw rows stay intact; an explicit
543
+ native tooltip formatter pairs each fraction with its original row value. Do not
544
+ apply expand or divide again to those fraction accessors. Other families use
545
+ native expand and the formatting helper. The Percent Area recipe retains integer
546
+ tooltip percentages and “No share” for a zero total.
547
+
548
+ ### Point marker styles
549
+
550
+ `LineSeries` and `AreaSeries` accept independent `pointStyle` and
551
+ `activePointStyle` values: `"default"`, `"border"`, or `"colored-border"`.
552
+ Omitted/default values keep the existing appearance (including Area's normally
553
+ hidden regular dots). Opting into a regular style enables regular dots. Border
554
+ uses a series-colored center with a surface-colored ring; colored-border uses a
555
+ surface-colored center with a series-colored ring. The surface is
556
+ `--kind-ui-chart-marker-surface`, falling back to `--card` then white; set it on
557
+ Root for your theme. Series paint follows the existing config/theme identity or
558
+ explicit series stroke. Styles introduce no SVG resources or additional motion.
788
559
 
789
560
  ```tsx
790
- <SankeyChart data={flows}
791
- node={(props) => <SankeyNode {...props} color="#cf5782" finish="clay" />}
792
- link={(props) => <SankeyLink {...props} material="gradient" finish="paper" />}
793
- />
561
+ <LineSeries dataKey="total" pointStyle="border" activePointStyle="colored-border" />
562
+ <AreaSeries dataKey="total" pointStyle="colored-border" activePointStyle="border" />
794
563
  ```
795
564
 
796
- Paper uses static subtle grain and an uneven inset pencil contour. Clay uses
797
- broad upper-left light and diffuse lower-right shading to suggest convex matte
798
- volume, with quiet grain and no cast shadow. Glow has a soft white interior
799
- rim and a restrained neutral exterior halo; blur is 0.85–1px (flow width/4, bounded to a nonzero native blur
800
- kernel), halo opacity is capped at 0.12. It is intentionally less expansive
801
- than line Glow so adjacent flows retain their quantitative reading. Tiny marks
802
- show less relief. No finish displaces, widens, offsets or blurs native geometry.
803
- Paint RGB/semantic gradients remain the base; neutral surface decoration modifies
804
- visible RGB while atop compositing preserves native body alpha. Exterior Glow
805
- is separately decorative, is not additional flow, and never adds hit targets.
806
-
807
- Explicit SVG `filter` or inline `style.filter` disables built-in finishes;
808
- stylesheet filters override the filter presentation attribute normally. Props,
809
- refs and handlers remain on the original rect/path; native clipping can trim
810
- exterior Glow. Node labels are consumer siblings, outside mark filters. Native
811
- custom node/link callbacks and elements retain complete rendering ownership.
812
- Separately mounted React roots should use `identifierPrefix` for unique IDs.
813
- Finishes are static and follow the chart's existing reduced-motion reveal rules.
814
- Validation targets Chromium; other browsers,
815
- print/export renderers and richer configurable material tokens are unverified.
816
-
817
- `SankeyTable` is an independently composable native table with required
818
- `caption`, all link identities, source/target names and exact zero values.
819
- `formatValue` controls units. Optional `onInspect` renders native buttons for
820
- Tab/Enter/Space inspection with controlled `activeLinkId` and `aria-pressed`.
821
- The callback receives the original link. Consumer state connects pointer/native
822
- events to the same table; table refs, attributes and handlers remain available.
823
- Pair diagrams with tables and a status description. Supply separate node
824
- metadata tables when isolated nodes carry information beyond flow quantities.
825
-
826
- `animate` defaults to false; recipes enable it. `animate={true}` uses 450ms;
827
- `animate={{ revealDurationMs: 800 }}` accepts a finite nonnegative duration. Motion reveals opacity only for
828
- 450ms, without changing proportional widths or moving flows. Pointer down,
829
- focus, changed immutable data/layout/renderers, native measured frame resize and live
830
- reduced-motion preference stop playback and show final geometry. Unmount stops
831
- playback. Examples retain a readable minimum diagram width in a keyboard
832
- scrollable region on phones and viewport-fitting tables. No topology morph or
833
- width tween is promised. See `examples/chart/SANKEYS.md` and `/sankeys.html`.
834
-
835
- ## Heatmap
836
-
837
- `HeatmapChart`, `HeatmapGrid`, `HeatmapLegend`, `HeatmapTooltip`, and `HeatmapDataTable` compose a two-dimensional categorical grid using native HTML table layout. They are independent of `Root` and Recharts chart contexts. The browser owns equal-cell geometry; ordered domains and data are consumer-owned. No new dependency or existing chart API change is required.
565
+ Any explicit native `dot` or `activeDot` value (including false, true, props,
566
+ renderer functions and elements) wins for its respective marker. Configured
567
+ LineChart accepts these options in its existing `series` objects; explicit
568
+ children retain ownership. `PointMarker` is a reusable native Dot renderer with
569
+ `variant` and native Dot props. Its variant paint wins the engine-supplied paint;
570
+ native radius/handlers remain intact, and SVG `style` can override its paint.
571
+ For example, `dot={<PointMarker variant="colored-border" style={{ fill: "white" }} />}`.
572
+ Native active-dot callbacks carry series paint in `fill`, while regular dots carry
573
+ it in `stroke`. When composing PointMarker as an active renderer, forward that
574
+ identity explicitly: `activeDot={(props) => <PointMarker {...props}
575
+ stroke={props.fill} variant="colored-border" />}`. The Series style API handles
576
+ this distinction automatically. Keyboard/pointer inspection remains chart-owned; the active mark retains its
577
+ existing non-intercepting behavior and reduced-motion policy. Radar's selection
578
+ dots and Scatter's symbols have separate contracts and do not accept these
579
+ series options. Bar, Pie and other shape families are outside this API.
580
+
581
+ Run `npm run dev:chart` and visit `/recipes.html#point-markers` for the marker gallery and
582
+ its accessible data table.
583
+
584
+ ### Directional Line and Area entrances
585
+
586
+ `LineAnimation` and `AreaAnimation` accept `revealDirection`:
587
+
588
+ - `"left-to-right"` (default): expand from the left edge.
589
+ - `"right-to-left"`: expand from the right edge.
590
+ - `"center-out"`: expand equally from the horizontal center.
591
+ - `"edges-in"`: expand two edge regions toward the horizontal center.
592
+
593
+ Directions are physical horizontal screen-space reveals for both native layouts;
594
+ they do not reverse data order or follow a vertical category axis. Timing remains
595
+ `revealDurationMs` / `revealEasing`. The temporary family clip leaves native paths,
596
+ axes, margins and transforms intact. Explicit directional entrances remove the clip
597
+ on completion or interruption (including resize/data changes). Line with an omitted
598
+ direction preserves its existing completed full-width clip until interruption;
599
+ Area/Combo retain their existing completion removal. Disabled/reduced motion shows
600
+ complete content.
601
+ Existing loading illustrations keep their independent design. Replay uses the
602
+ existing remount or loading-to-ready lifecycle, not hover or color updates.
838
603
 
839
604
  ```tsx
840
605
  import {
841
- createHeatmapScale, HeatmapChart, HeatmapGrid,
842
- HeatmapLegend, HeatmapTooltip, HeatmapDataTable,
606
+ AreaChart, AreaSeries, ComboChart, LineChart, LineSeries, Root,
607
+ type SeriesConfig,
843
608
  } from "@kind-ui/charts";
844
609
  import "@kind-ui/charts/styles.css";
845
610
 
846
- const scale = createHeatmapScale({
847
- domain: [-10, 10],
848
- colors: ["#3b6fa8", "#f5f5ee", "#bf5b38"],
849
- });
850
- <HeatmapChart
851
- rows={["API", "Worker"]}
852
- columns={["East", "West"]}
853
- data={[
854
- { row: "API", column: "East", value: -4 },
855
- { row: "API", column: "West", value: 0 },
856
- { row: "Worker", column: "East", value: null },
857
- ]}
858
- scale={scale}
859
- animate
860
- >
861
- <HeatmapGrid caption="Latency change by service and region" />
862
- <HeatmapTooltip />
863
- <HeatmapLegend label="Change in milliseconds" />
864
- <details>
865
- <summary>View values</summary>
866
- <HeatmapDataTable caption="Latency changes (ms)" />
867
- </details>
868
- </HeatmapChart>;
611
+ const data = [
612
+ { day: "Mon", total: 12, forecast: 16 },
613
+ { day: "Tue", total: 20, forecast: 24 },
614
+ ];
615
+ const config = {
616
+ total: { label: "Total", color: "#3659b8" },
617
+ forecast: { label: "Forecast", color: "#0d9488" },
618
+ } satisfies SeriesConfig;
619
+
620
+ export function DirectionalCharts() {
621
+ return (
622
+ <Root config={config}>
623
+ <LineChart data={data} width={480} height={240} aria-label="Daily total"
624
+ animate={{ revealDirection: "right-to-left", revealDurationMs: 800 }}>
625
+ <LineSeries dataKey="total" pointStyle="border" />
626
+ </LineChart>
627
+ <AreaChart data={data} width={480} height={240} aria-label="Daily forecast"
628
+ animate={{ revealDirection: "center-out" }}>
629
+ <AreaSeries dataKey="forecast" />
630
+ </AreaChart>
631
+ <ComboChart data={data} width={480} height={240} aria-label="Total and forecast"
632
+ animate={{
633
+ revealDirection: "center-out",
634
+ lineReveal: { revealDirection: "right-to-left" },
635
+ areaReveal: { revealDirection: "edges-in", revealDurationMs: 1200 },
636
+ barReveal: false,
637
+ }}>
638
+ <LineSeries dataKey="total" />
639
+ <AreaSeries dataKey="forecast" />
640
+ </ComboChart>
641
+ </Root>
642
+ );
643
+ }
869
644
  ```
870
645
 
871
- - `rows` and `columns` are explicit ordered unique string domains. Unknown coordinates, duplicate domain entries, undefined/nonfinite values and overflowed sums throw actionable errors. An empty domain renders an empty grid message. Domains containing categories with no records still render missing cells. Supply new array identities when updating data/domains; inputs are treated as immutable.
872
- - A datum is `{ row: string; column: string; value: number | null }`. Absent records and explicit `null` are missing, while `0` remains measured zero. `createHeatmapModel` exposes every domain coordinate, indices, resolved value and original `sources` for typed customization and inspection.
873
- - `duplicates` defaults to `"error"`. `"first"` and `"last"` preserve the corresponding record, including null. `"sum"` sums finite records, ignores null when numbers exist, and keeps all-null cells missing. Negative/positive cancellation remains zero. `sources` retains all records in input order for every policy.
874
- - `createHeatmapScale({ domain, colors })` requires finite ascending endpoints and at least two opaque `#rrggbb` colors. It interpolates evenly spaced stops in sRGB and clamps out-of-domain values. A constant domain uses the palette midpoint. Explicit domains make comparisons across updates meaningful; automatic rescaling is not performed. Consumers should label any clamping and choose a palette suited to sequential or diverging values. The legend uses the same stops/endpoints, includes a positioned zero marker with a separate label for signed domains, and a separate missing key.
875
- - `formatValue(number)` and `missingLabel` are shared by cell labels, tooltip, legend and table. Default labels show values, with contrast-selected black/white text for numeric fills. Set both `--heatmap-missing` and `--heatmap-missing-foreground` when changing the missing swatch colors. Custom content owns its own text contrast. `HeatmapGrid` accepts a typed `Cell: ComponentType<HeatmapCellContentProps>` receiving `{ cell, fill, formattedValue }`, plus `cellProps(cell)` for native td refs/styles/handlers, and `rowLabel`/`columnLabel` for visible header content. Preserve opaque fills and a meaningful text alternative when customizing; nested interactive content needs host-specific keyboard handling. Grid role, tab stops, coordinate identity, accessible cell labels and background color remain component-owned; cell handlers are composed and a cancelled key event suppresses grid navigation.
876
- - Native DOM props, styles, refs and handlers are forwarded on the chart div, grid table, legend fieldset, tooltip div and static table. `HeatmapTooltip` accepts a typed `Content` component with the same cell contract. Compose one grid and at most one tooltip per chart; create separate chart boundaries for independent grids. Tooltip values derive from the latest active coordinate, so data changes and reorder do not leave stale payloads.
877
- - The grid has one roving tab stop. Arrow keys move within the ordered domains, Home/End move to the row endpoints, Ctrl+Home/End to the corners. Focus and pointer inspection open the tooltip; Escape closes it and Tab exits the grid. Native cell focus scrolls narrow containers. Long row headers and default cell text are clipped visually to preserve equal rows; cell accessible labels and the static data table retain the full values. Removing the focused category falls back to the first cell on the next Tab entry. The optional tooltip is an in-flow readout; the static data table is consumer-placed and has no roving focus behavior.
878
- - `animate` defaults off in the library and on in the recipes. The existing Motion peer animates only a short frame translation; cell fills stay opaque and values do not tween. Reduced-motion preferences disable translation. Host/card styling belongs to the consumer through native `className` and `style`; it is not a chart material. `HeatmapGrid material` accepts `HeatmapMaterial`: `"plain"` (default), `"paper"`, `"clay"`, or `"glow"`. These are static per-cell edge treatments, never card styling. Paper adds a fibrous, irregular ink rim; Clay adds a soft top-lit convex matte bevel; Glow adds a luminous rim contained within the cell. Only the outer 8% on each side is decorated: the central 84% by 84% (70.56% of the rectangular cell area, before text) remains the exact opaque scale color. Compare this center to the unmodified legend, not the decorative edge. Missing cells retain their pattern and never receive a finish. No filter, opacity, shadow, geometry or animation is added. Consumer background-image/size/repeat overrides still win, and custom content and native cell styles/filters/refs/events remain owned by the consumer. Consumer paint overrides can invalidate the encoding guarantee. Full-face texture, glossy clay and an external glow halo are intentionally unsupported because they would alter or bleed the numeric encoding; these are bounded rim materials.
646
+ Combo inherits the chart direction for Line/Area unless the corresponding family
647
+ object overrides it; `false` disables that family entrance. Bar keeps its existing
648
+ entrance configuration. All managed series in a family share its entrance clip;
649
+ individual series rendering/visibility props remain available, but there is no
650
+ per-series direction prop. Explicit native children and consumer clip/shape
651
+ ownership retain their existing contracts. `RevealDirection` is exported for
652
+ consumer controls. The packed Line/Area motion fixtures accept `?direction=...`
653
+ and the Combo fixture accepts `?directional` to exercise the family overrides.
879
654
 
880
- See [responsive matrix and activity recipes](https://github.com/bhaveshchow20/kind-ui/blob/main/examples/chart/HEATMAPS.md) for renderer research, behavior, verification and limitations. Native tables render every cell; virtualization, editing, range selection, inferred domains and automatic aggregation are outside this API. Automated Chromium checks cover tested interaction/layout paths; manual screen-reader coverage remains unverified.
655
+ ### Animated dashed lines
881
656
 
882
- Pie finishes preserve consumer CSS transform ownership by rendering the original native Sector when an inline transform or a stylesheet transform overrides its SVG transform attribute. This fallback preserves antialiased paint, clipping and hit targets; it does not apply the requested finish. Ordinary CSS colors/classes/styles and explicit SVG `transform` attributes continue to support finishes. CSS individual `translate`, `rotate` and `scale` properties also retain native ownership. Ambient stylesheet/media/pseudo-class changes without a relevant React prop update do not refresh material ownership. Stylesheet ownership is sampled when the finish, center, outer radius, SVG transform, style, class or id changes. For transforms that change later through media queries, ancestor state or pseudo-classes, use a consumer `style` prop or change the finish/style/class/id to refresh ownership. Custom shapes and filters also retain native ownership.
657
+ `LineSeries` accepts `dashAnimation={ { durationMs: 1000, direction: "forward" } }`
658
+ (or `false`, the default). Supply a native numeric `strokeDasharray`, such as
659
+ `"6 4"`; duration is milliseconds per full pattern cycle. `reverse` reverses
660
+ travel. Zero, negative or non-finite duration and nonnumeric/CSS/percentage dash
661
+ patterns stay static. Odd lists repeat twice per cycle, matching SVG.
883
662
 
884
- Normal material alpha checks continue to compare actual live packed-consumer screenshots with the native body tolerance of one byte, existing Glow halo bounds, zero paint for transparent input and zero paint outside explicit clipping. Mutations of actual material source opacity, masks, gradients, clips and emission must fail those same live alpha assertions. The repeated Plain-baseline guard and CSS fallback ownership checks compare exact complete live SVG topology/resolved ancestor state and exact decoded RGBA from independently captured self-contained fixed-fixture SVG images; CSS fallback also requires no transient material definitions. This deterministic native paint-equivalence guard does not promise universal live-inline compositor stability or arbitrary HTML-to-SVG export fidelity. Material alpha, geometry, transforms, ownership, interactions and visual previews retain their live packed-consumer coverage.
663
+ ```tsx
664
+ <LineChart data={rows} animate>
665
+ <LineSeries dataKey="total" strokeDasharray="6 4" strokeDashoffset={3}
666
+ dashAnimation={{ durationMs: 800 }} material="glow" />
667
+ </LineChart>
668
+ <ComboChart data={rows} animate>
669
+ <AreaSeries dataKey="total" stroke="none" fillOpacity={0.2} />
670
+ <LineSeries dataKey="total" dot={false} strokeDasharray="3 2 1"
671
+ dashAnimation={{ durationMs: 1200, direction: "reverse" }} />
672
+ </ComboChart>
673
+ ```
885
674
 
886
- ### Selective emphasis (preview)
675
+ Load the package stylesheet. Motion stops with chart `animate={false}`, reduced
676
+ motion, loading, or hidden series. Disabling restores the native dash offset;
677
+ reenabling starts a fresh cycle. Use `dashAnimation={false}` to disable an individual series. Native width, dash array, offset and
678
+ styles remain intact; style dash values take precedence. Custom shapes own their
679
+ animation and are never decorated. Entrance clip reveal timing is independent.
680
+ No geometry or data is changed, and CSS requires no mount timers or cleanup.
681
+ Stylesheets overriding dash paint remain consumer-owned and can change appearance.
682
+
683
+ `AreaSeries` does not accept this option: its closed perimeter includes baseline
684
+ and closing edges. For an open animated outline, overlay `LineSeries` in a
685
+ `ComboChart` as above. Match data keys, interpolation and axes yourself; stacked
686
+ or range areas require an explicitly derived outline dataset. See
687
+ `/contracts.html#dashed-lines` for the interactive line and combo contract examples.
688
+
689
+ ### Initial Pie tooltip
690
+
691
+ `PieChart.defaultPinnedCategory="delivery"` opts into an initial tooltip for
692
+ one direct `PieSeries` (Fragments allowed) with explicit `data` and `categoryKey`.
693
+ Pair it with `Tooltip.itemKey={(entry) => entry.payload.id}` when `categoryKey="id"`;
694
+ caller labels, formatters, and the data table remain the source of truth.
695
+ Only Pie/donut compositions whose direct children are one Kind PieSeries and
696
+ one Kind Tooltip (optionally in Fragments) support this default. Native Pie, wrapped series, multiple rings, and other chart families
697
+ are outside this contract; missing category data/identity or multiple direct
698
+ series, duplicate/missing Tooltips, or unsupported direct children throw when resolving a pin.
699
+
700
+ The category string is captured on mount. Reorder resolves its current index;
701
+ unknown, duplicate, removed, hidden, or filtered categories clear the default
702
+ permanently. Restoring rows or changing the default prop does not re-pin; remount
703
+ explicitly to begin again. Pointer movement/down, focus, and any chart key press
704
+ clear the default and hand inspection/dismissal back to Recharts. Escape never
705
+ re-pins. No focus is moved or trapped. The existing tooltip is the sole readout
706
+ and live announcement; Kind adds no announcement region or hover selection.
707
+ Explicit Tooltip `active` and `defaultIndex` retain native ownership and take
708
+ precedence. Custom content owns its markup and accessibility. Omitted defaults
709
+ preserve existing behavior. See the weekly Pie in `examples/chart/pie-recipes.tsx`.
710
+
711
+ ### Sankey node labels
712
+
713
+ Compose `SankeyNodeLabel` beside `SankeyNode` in the native `node` callback:
887
714
 
888
- `Root emphasis="auto"` is the default; `"none"` disables transient emphasis.
889
- It does not change `visibleSeries`, engine inspection, selection, or click callbacks.
715
+ ```tsx
716
+ node={(node) => (
717
+ <g>
718
+ <SankeyNode {...node} />
719
+ <SankeyNodeLabel node={node} data={data} position="outside" showValues
720
+ valueFormatter={(value) => `${value} MWh`} />
721
+ </g>
722
+ )}
723
+ ```
890
724
 
891
- | Family | Automatic decoration in this preview |
892
- | --- | --- |
893
- | Bar | Default `BarChart emphasis="none"`. Opt in with `emphasis="category"` for an eligible native plot; grouped and stacked series stay together. |
894
- | Pie / Donut | Native default sectors emphasize the semantic `nameKey` value. |
895
- | Line / Area / Combo / Scatter / Bubble | Existing inspection; no automatic dimming. |
896
- | Radar / Radial / Sankey / Box / Histogram | Adapters unfinished; no automatic dimming. |
897
- | Heatmap / Waterfall | Existing cell or step inspection; numeric paint and chain remain unchanged. |
898
-
899
- Bar eligibility is conservative and applies to the whole visible plot. Every visible
900
- Kind `BarSeries` must use chart-level rows, a direct string numeric `dataKey`,
901
- complete finite values that differ from the numeric baseline, native default
902
- shape/activeBar, and unique string category-axis values with one domain entry per row, or a complete
903
- unique `emphasisKey` mapping. A resolver returning `undefined` makes the entire
904
- Bar plot ineligible; it does not partially dim remaining rows.
905
- Zero/missing/range/function/nested-key/per-series rows, ambiguous categories, and
906
- custom-shape peers fall back the entire plot to native rendering without dimming.
907
- Hidden peers do not block eligibility. Use Kind `BarSeries` for all peers in this
908
- opt-in comparison; raw engine/custom marks require explicit decoration. This
909
- protects native zero-size filtering and consumer labels. Eligibility changes
910
- clear removed paint targets; safe→sparse/custom→safe does not resurrect an old
911
- hover. Root `emphasis="none"` disables the opt-in as well.
912
-
913
- Bar numeric and index-only domains do not dim automatically. Supply
914
- `emphasisKey={(row) => ...}` on each participating `BarSeries` to return the same
915
- stable category ID across grouped/stacked series. Give Pie a semantic `nameKey`
916
- or an `emphasisKey` resolver. IDs must be unique within each category/sector scope;
917
- index values are not stable IDs. Independent plots inside one Root have separate
918
- category scopes. Removing a registered target clears its transient state.
919
-
920
- Custom shapes, consumer active/inactive shape overrides, and their portals retain
921
- ownership. To participate explicitly, wrap only their paint in
922
- `<EmphasisMark target={{kind: "category", key: data.id, scope: "my-plot", seriesKey: "sales"}}>…</EmphasisMark>`.
923
- Keep annotations and labels outside that wrapper. Share a `scope` only for marks
924
- that represent the same comparison. `useEmphasis(target, enabled?)` exposes
925
- `active`, `dimmed`, `factor`, `enter("pointer" | "keyboard")`, and `leave(channel)`
926
- for renderers needing their own decoration. Apply `factor` through an extra paint
927
- layer so existing opacity, transparent fills and motion multiply exactly once.
928
- Neither API uses descendant rewriting or global selectors.
929
-
930
- `Legend emphasis="series"` opts into series hover/focus emphasis for participating
931
- marks with `seriesKey`; controlled visibility click behavior and custom item content
932
- remain unchanged. Other chart families require an explicit mark adapter before
933
- legend emphasis paints them. The default Legend does not emphasize series.
934
-
935
- Pointer leave restores the retained keyboard candidate. Explicit legend/custom keyboard/focus
936
- emphasis supersedes hover; Escape clears transient emphasis without changing
937
- controlled selection. Touch has no new tap/click behavior. Dimming keeps marks
938
- hittable and uses a 160ms interruptible opacity transition; reduced motion removes
939
- the transition. Import `@kind-ui/charts/styles.css` for these defaults.
940
-
941
- The public packed recipe in `tests/fixtures/emphasis` demonstrates category,
942
- sector, custom portal, visibility, and independent-plot composition. Supported adapters are listed in the matrix above.
943
-
944
- Identity relationships: `dataKey` selects measured values; it is not a category ID.
945
- `seriesKey` selects Root metadata and controlled series visibility (or a string
946
- `dataKey` supplies that series key). `emphasisKey` selects the stable datum ID.
947
- Pie sector visibility remains consumer-owned through its supplied data/Cells;
948
- Root visibility does not rewrite Pie data. A custom mark must use `enabled={false}`
949
- when its owning consumer hides or removes it. Emphasis registration represents
950
- paint that is currently present, rather than a second persistent selection model.
951
-
952
- A pointer candidate temporarily takes precedence over a retained keyboard
953
- candidate. A new explicit legend/custom keyboard/focus action supersedes pointer emphasis;
954
- leaving that hover restores the retained candidate. Native chart focus/blur and
955
- Escape reconcile these transient candidates without invoking selection callbacks.
956
- Touch-induced focus does not create keyboard emphasis. Native active marks may
957
- move between Recharts portals; clearing uses semantic identity across that move.
958
-
959
- Native overlap limitation: Recharts' public tooltip inspection prioritizes an
960
- active mouse hover over keyboard state. When the pointer remains on category A
961
- and keyboard arrows run, native tooltip data and Kind emphasis can both remain
962
- on A. Clean keyboard entry advances normally. Kind does not maintain a second
963
- keyboard index, dispatch synthetic consumer events, or use private engine state.
964
- Full pointer/keyboard equivalence is not claimed. After pointer exit, retained
965
- keyboard emphasis represents the last eligible inspection candidate; the native
966
- tooltip may close. This bounded preview follows native selection ownership.
725
+ Optional `SankeyNodeConfig` entries accept an `icon: ReactNode`. Pass that config
726
+ explicitly as `SankeyNodeLabel`'s `nodeConfig` in your native callback (see the
727
+ energy example in `examples/chart/sankeys.tsx`). Icons use a square `iconSize`
728
+ (default 16 chart units) SVG viewport with `viewBox="0 0 24 24"`; supply SVG
729
+ content, or a nested SVG with its own viewBox. `iconGap` defaults to 4 units.
730
+ Outside icons sit nearest the node and shift the text by size plus gap on either
731
+ side. Inside icons stack above centered text and share its exact rectangle clip;
732
+ small nodes can clip both. Reserve outside margins for the combined content.
733
+ Missing, null, boolean or zero-size icons preserve the existing text layout.
734
+ Sizes and gaps must be finite and nonnegative. Icons are decorative (`aria-hidden`,
735
+ nonfocusable); data names and full name/value titles remain meaningful, and the
736
+ data table remains the accessible alternative. Config labels remain Legend metadata.
737
+ Custom text children compose with the icon; text props/ref still target the text.
738
+ A custom native node renderer owns all rendering: nothing is injected unless it
739
+ chooses this helper, and omitting `nodeConfig` opts out of configured icons.
740
+
741
+ Identity is resolved by `node.payload.id` against `data`, never callback index or
742
+ name. Supply the same data to the chart, label and `SankeyTable`, and reuse the
743
+ formatter as the table's `formatValue`. A node value is the maximum of incoming
744
+ and outgoing flow sums: sources use outgoing, sinks incoming, balanced intermediate
745
+ nodes count throughput once, and disconnected or measured-zero nodes total zero.
746
+ Existing data validation rejects unbalanced intermediate nodes outside its rounding
747
+ tolerance; the larger sum handles that tolerance consistently with native sizing.
748
+ No extra totals or inferred flows are added to the table.
749
+
750
+ `position="inside"` centers text and clips it to the exact node rectangle, including
751
+ small or zero-size nodes. It does not shrink text, expand geometry or avoid collisions.
752
+ Use outside labels for narrow nodes; they default right for sources/intermediates and
753
+ left for sinks. `side`, `offset`, native text props, styles and refs remain consumer-owned.
754
+ Reserve margins for outside text; the native SVG viewport still clips overflow.
755
+ The full name/value remains in a SVG title even when inside text clips. Keep the table
756
+ as the complete accessible data alternative. Custom `children` (including `tspan`)
757
+ replace visual text while preserving the default title. No label or animation is
758
+ installed implicitly. `/sankeys.html` demonstrates both positions.