semiotic 3.2.3 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CLAUDE.md +118 -264
  2. package/README.md +37 -17
  3. package/ai/schema.json +1 -1
  4. package/ai/system-prompt.md +3 -3
  5. package/dist/components/charts/ordinal/SwimlaneChart.d.ts +4 -0
  6. package/dist/components/charts/realtime/RealtimeHeatmap.d.ts +2 -0
  7. package/dist/components/charts/realtime/RealtimeHistogram.d.ts +2 -0
  8. package/dist/components/charts/realtime/RealtimeLineChart.d.ts +2 -0
  9. package/dist/components/charts/realtime/RealtimeSwarmChart.d.ts +2 -0
  10. package/dist/components/charts/realtime/RealtimeWaterfallChart.d.ts +2 -0
  11. package/dist/components/charts/shared/hooks.d.ts +2 -1
  12. package/dist/components/charts/shared/types.d.ts +6 -2
  13. package/dist/components/charts/shared/useChartSetup.d.ts +1 -1
  14. package/dist/components/realtime/RingBuffer.d.ts +11 -0
  15. package/dist/components/realtime/types.d.ts +4 -0
  16. package/dist/components/semiotic-server.d.ts +6 -1
  17. package/dist/components/server/animatedGif.d.ts +78 -0
  18. package/dist/components/server/renderToStaticSVG.d.ts +85 -5
  19. package/dist/components/server/staticAnnotations.d.ts +40 -0
  20. package/dist/components/server/staticLegend.d.ts +39 -0
  21. package/dist/components/server/svgHatchPattern.d.ts +26 -0
  22. package/dist/components/server/themeResolver.d.ts +35 -0
  23. package/dist/components/stream/GeoPipelineStore.d.ts +6 -1
  24. package/dist/components/stream/NetworkPipelineStore.d.ts +20 -0
  25. package/dist/components/stream/OrdinalPipelineStore.d.ts +11 -0
  26. package/dist/components/stream/OrdinalSVGOverlay.d.ts +5 -0
  27. package/dist/components/stream/PipelineStore.d.ts +11 -0
  28. package/dist/components/stream/accessorUtils.d.ts +3 -3
  29. package/dist/components/stream/geoTypes.d.ts +2 -0
  30. package/dist/components/stream/networkTypes.d.ts +33 -0
  31. package/dist/components/stream/ordinalTypes.d.ts +30 -3
  32. package/dist/components/stream/renderers/resolveCSSColor.d.ts +17 -0
  33. package/dist/components/stream/types.d.ts +4 -0
  34. package/dist/geo.min.js +1 -1
  35. package/dist/geo.module.min.js +1 -1
  36. package/dist/network.min.js +1 -1
  37. package/dist/network.module.min.js +1 -1
  38. package/dist/ordinal.min.js +1 -1
  39. package/dist/ordinal.module.min.js +1 -1
  40. package/dist/realtime.min.js +1 -1
  41. package/dist/realtime.module.min.js +1 -1
  42. package/dist/semiotic-ai.min.js +1 -1
  43. package/dist/semiotic-ai.module.min.js +1 -1
  44. package/dist/semiotic-server.d.ts +6 -1
  45. package/dist/semiotic-utils.min.js +1 -1
  46. package/dist/semiotic-utils.module.min.js +1 -1
  47. package/dist/semiotic.min.js +1 -1
  48. package/dist/semiotic.module.min.js +1 -1
  49. package/dist/server.min.js +1 -1
  50. package/dist/server.module.min.js +1 -1
  51. package/dist/xy.min.js +1 -1
  52. package/dist/xy.module.min.js +1 -1
  53. package/package.json +14 -3
package/CLAUDE.md CHANGED
@@ -2,337 +2,191 @@
2
2
 
3
3
  ## Quick Start
4
4
  - Install: `npm install semiotic`
5
- - Import: `semiotic`, `semiotic/xy`, `semiotic/ordinal`, `semiotic/network`, `semiotic/geo`, `semiotic/realtime`, `semiotic/ai`, `semiotic/data`, `semiotic/server`, `semiotic/themes`, `semiotic/utils`
5
+ - **Use sub-path imports** — `semiotic/xy` (143KB gz), `semiotic/ordinal` (109KB), `semiotic/network` (98KB), `semiotic/geo` (93KB), `semiotic/realtime` (145KB), `semiotic/server` (100KB), `semiotic/utils` (31KB), `semiotic/themes` (5KB), `semiotic/data` (5KB). Full `semiotic` is 278KB gz.
6
6
  - CLI: `npx semiotic-ai [--schema|--compact|--examples|--doctor]`
7
7
  - MCP: `npx semiotic-mcp`
8
- - Every HOC has a built-in error boundary and dev-mode validation warnings
9
8
 
10
9
  ## Architecture
11
10
  - **HOC Charts**: Simple props, sensible defaults. **Stream Frames**: Full control.
12
- - **Always use HOC charts** unless you need control they don't expose. Stream Frames (`StreamNetworkFrame`, `StreamXYFrame`, `StreamOrdinalFrame`, `StreamGeoFrame`) are low-level — they pass `RealtimeNode`/`RealtimeEdge` wrappers in callbacks, not your data.
13
- - Every HOC accepts `frameProps` to pass through. TypeScript `strict: true`.
11
+ - **Always use HOC charts** unless you need control they don't expose. Stream Frames pass `RealtimeNode`/`RealtimeEdge` wrappers in callbacks, not your data.
12
+ - Every HOC accepts `frameProps` for pass-through. TypeScript `strict: true`. Every HOC has error boundary + dev-mode validation.
14
13
 
15
14
  ## Common Props (all HOCs)
16
- `title`, `description` (overrides aria-label), `summary` (sr-only note), `width` (600), `height` (400), `responsiveWidth`, `responsiveHeight`, `margin`, `className`, `color` (uniform fill — overrides theme/colorScheme), `enableHover` (true), `tooltip` (boolean | "multi" | `(datum) => ReactNode` | `{ fields?, title?, format?, style? }`), `showLegend`, `showGrid` (false), `frameProps`, `onObservation`, `onClick`, `chartId`, `loading` (false), `emptyContent`, `legendInteraction` ("none"|"highlight"|"isolate"), `legendPosition` ("right"|"left"|"top"|"bottom"), `emphasis` ("primary"|"secondary"), `annotations` (array), `accessibleTable` (true), `hoverHighlight` (boolean|"series" — dims non-hovered series on data mark hover), `hoverRadius` (number, default 30 — max pixel distance for hover/click hit testing)
15
+ `title`, `description` (aria-label), `summary` (sr-only), `width` (600), `height` (400), `responsiveWidth`, `responsiveHeight`, `margin`, `className`, `color` (uniform fill), `enableHover` (true), `tooltip` (boolean | "multi" | function | config object), `showLegend`, `showGrid` (false), `frameProps`, `onObservation`, `onClick`, `chartId`, `loading` (false), `emptyContent`, `legendInteraction` ("none"|"highlight"|"isolate"), `legendPosition` ("right"|"left"|"top"|"bottom"), `emphasis` ("primary"|"secondary"), `annotations` (array), `accessibleTable` (true), `hoverHighlight` (boolean — dims non-hovered series, requires `colorBy`), `hoverRadius` (30)
17
16
 
18
- `onClick` receives `(datum, { x, y })` the original datum and pixel coordinates. Works on lines, bars, areas, pie slices, nodes, and geo features.
19
-
20
- `onObservation` receives `{ type: "hover"|"hover-end"|"click"|"brush"|"selection", datum?, x?, y?, timestamp, chartType, chartId }`. The `datum` is your original data object.
17
+ `onClick` receives `(datum, { x, y })`. `onObservation` receives `{ type, datum?, x?, y?, timestamp, chartType, chartId }`.
21
18
 
22
19
  ## XY Charts (`semiotic/xy`)
23
20
 
24
- **LineChart** — `data`, `xAccessor` ("x"), `yAccessor` ("y"), `lineBy`, `lineDataAccessor` ("coordinates"), `colorBy`, `colorScheme`, `curve`, `lineWidth` (2), `showPoints`, `pointRadius` (3), `fillArea` (boolean|string[] — `true` fills all, `string[]` lists series names for per-series area fill), `areaOpacity` (0.3), `lineGradient` ({colorStops:[{offset,color}]} — horizontal gradient for line stroke), `anomaly`, `forecast`, `directLabel`, `gapStrategy` ("break"|"interpolate"|"zero"), `xScaleType`/`yScaleType` ("linear"|"log")
25
- **AreaChart** — LineChart props + `areaBy`, `y0Accessor` (band/ribbon), `gradientFill` (boolean|{topOpacity,bottomOpacity}|{colorStops:[{offset,color}]}), `lineGradient` ({colorStops:[{offset,color}]}), `areaOpacity` (0.7), `showLine` (true)
26
- **StackedAreaChart** — flat array + `areaBy` (required), `colorBy`, `normalize`. Do NOT use `lineBy` or `lineDataAccessor`.
21
+ **LineChart** — `data`, `xAccessor` ("x"), `yAccessor` ("y"), `lineBy`, `lineDataAccessor`, `colorBy`, `colorScheme`, `curve`, `lineWidth` (2), `showPoints`, `pointRadius` (3), `fillArea` (boolean|string[]), `areaOpacity` (0.3), `lineGradient`, `anomaly`, `forecast`, `directLabel`, `gapStrategy`, `xScaleType`/`yScaleType` ("linear"|"log"|"time")
22
+ **AreaChart** — LineChart props + `areaBy`, `y0Accessor`, `gradientFill`, `areaOpacity` (0.7), `showLine` (true)
23
+ **StackedAreaChart** — flat array + `areaBy` (required), `colorBy`, `normalize`. No `lineBy`/`lineDataAccessor`.
27
24
  **Scatterplot** — `data`, `xAccessor`, `yAccessor`, `colorBy`, `sizeBy`, `sizeRange`, `pointRadius` (5), `pointOpacity` (0.8), `marginalGraphics`
28
- **BubbleChart** — Scatterplot + `sizeBy` (required), `sizeRange` ([5,40]), `bubbleOpacity` (0.6)
29
- **ConnectedScatterplot** — `data`, `xAccessor`, `yAccessor`, `orderAccessor` (sequencing field), `pointRadius` (4)
30
- **QuadrantChart** — Scatterplot + `quadrants` (required: `{ topRight, topLeft, bottomRight, bottomLeft }` each `{ label, color, opacity? }`), `xCenter`, `yCenter`, `centerlineStyle`, `showQuadrantLabels` (true). Supports push API.
31
- **MultiAxisLineChart** — Dual Y-axis. `data`, `xAccessor` ("x"), `series` (required: array of `{ yAccessor, label?, color?, format?, extent? }`), `colorScheme`, `curve` ("monotoneX"), `lineWidth` (2). Data unitized to [0,1] internally; left axis=series[0], right axis=series[1] in original units. For push API, provide `series[].extent` for stable unitization. Falls back to standard multi-line if not exactly 2 series.
32
- **Heatmap** — `data`, `xAccessor`, `yAccessor`, `valueAccessor`, `colorScheme` ("blues"|"reds"|"greens"|"viridis"), `showValues`, `cellBorderColor`. Supports string/categorical axes.
25
+ **BubbleChart** — Scatterplot + `sizeBy` (required), `sizeRange` ([5,40])
26
+ **ConnectedScatterplot** — + `orderAccessor`
27
+ **QuadrantChart** — Scatterplot + `quadrants` (required), `xCenter`, `yCenter`
28
+ **MultiAxisLineChart** — Dual Y-axis. `series` (required: `[{ yAccessor, label?, color?, format?, extent? }]`). Falls back to multi-line if not 2 series.
29
+ **Heatmap** — `data`, `xAccessor`, `yAccessor`, `valueAccessor`, `colorScheme`, `showValues`, `cellBorderColor`
33
30
 
34
31
  ## Ordinal Charts (`semiotic/ordinal`)
35
32
 
36
33
  **BarChart** — `data`, `categoryAccessor`, `valueAccessor`, `orientation`, `colorBy`, `sort`, `barPadding` (40)
37
- **StackedBarChart** — + `stackBy` (required), `normalize`, `barPadding` (40)
34
+ **StackedBarChart** — + `stackBy` (required), `normalize`
38
35
  **GroupedBarChart** — + `groupBy` (required), `barPadding` (60)
39
- **SwarmPlot** — `data`, `categoryAccessor`, `valueAccessor`, `colorBy`, `sizeBy`, `pointRadius`, `pointOpacity`
36
+ **SwarmPlot** — `colorBy`, `sizeBy`, `pointRadius`, `pointOpacity`
40
37
  **BoxPlot** — + `showOutliers`, `outlierRadius`
41
- **Histogram** — + `bins` (25), `relative`. Always horizontal. `categoryAccessor` optional (defaults to `"category"`).
38
+ **Histogram** — + `bins` (25), `relative`. Always horizontal.
42
39
  **ViolinPlot** — + `bins`, `curve`, `showIQR`
43
- **RidgelinePlot** — + `bins`, `amplitude` (1.5, unitless multiplier of lane width)
40
+ **RidgelinePlot** — + `bins`, `amplitude` (1.5)
44
41
  **DotPlot** — + `sort` (true), `dotRadius`, `showGrid` default true
45
- **PieChart** — `data`, `categoryAccessor`, `valueAccessor`, `colorBy`, `startAngle`
46
- **DonutChart** — PieChart + `innerRadius` (60), `centerContent` (ReactNode)
47
- **FunnelChart** — `data`, `stepAccessor` ("step"), `valueAccessor` ("value"), `categoryAccessor` (optional), `colorBy`, `connectorOpacity` (0.3), `orientation` ("horizontal"|"vertical"). Horizontal: centered bars with trapezoid connectors. Vertical: bars with diagonal hatch for dropoff. Multi-category: `categoryAccessor="channel"` mirrors (horizontal) or groups (vertical).
48
- **SwimlaneChart** — `data`, `categoryAccessor` ("category"), `subcategoryAccessor` (required), `valueAccessor` ("value"), `colorBy` (defaults to subcategoryAccessor), `colorScheme`, `orientation` ("horizontal"|"vertical"), `barPadding` (40). Renders categorical lanes with items stacked sequentially — unlike StackedBarChart, the same subcategory can appear multiple times in the same lane. Items stack left-to-right (horizontal) or bottom-to-top (vertical) in data order. Wraps StreamOrdinalFrame with `chartType="swimlane"`. Supports push API for streaming.
49
-
50
- **LikertChart** — `data`, `categoryAccessor` ("question"), `valueAccessor` ("score", raw mode) or `levelAccessor`+`countAccessor` ("count", pre-aggregated mode), `levels` (required, ordered negative→positive), `orientation` ("horizontal"|"vertical"), `colorScheme`. Horizontal (default): diverging bar chart centered at 0% — negative levels extend left, positive right, neutral (odd count) split 50/50 across centerline. Vertical: stacked 100% bar chart. Supports any scale size (3-point to 7-point+). Raw mode aggregates integer scores automatically (1-based: score 1 → levels[0]). The `levels` array order defines polarity — first half negative, second half positive, center neutral if odd. Supports push API for streaming — accumulates raw data internally and re-aggregates percentages on each push.
51
-
52
- **GaugeChart** — `value` (required), `min` (0), `max` (100), `thresholds` (array of `{ value, color, label? }` defining zones), `arcWidth` (0.3, fraction of radius), `sweep` (240°), `fillZones` (true — when false, all zones render at full color and only needle moves; used for election-needle style displays), `showNeedle` (true), `needleColor`, `centerContent` (ReactNode or `(value, min, max) => ReactNode`), `valueFormat`, `showScaleLabels` (true), `backgroundColor`. Built on top of `StreamOrdinalFrame` with `projection="radial"` and `sweepAngle` (limits pie arc to < 360°). Reuses pie/donut rendering pipeline. Zones are sorted by threshold `value` before rendering. Arc auto-sizes to fill available space with negative margins for partial sweeps.
42
+ **PieChart** — `categoryAccessor`, `valueAccessor`, `colorBy`, `startAngle`
43
+ **DonutChart** — PieChart + `innerRadius` (60), `centerContent`
44
+ **FunnelChart** — `stepAccessor`, `valueAccessor`, `categoryAccessor` (optional), `connectorOpacity`, `orientation`
45
+ **SwimlaneChart** — `categoryAccessor`, `subcategoryAccessor` (required), `valueAccessor`, `colorBy` (defaults to subcategoryAccessor), `orientation`
46
+ **LikertChart** — `categoryAccessor`, `valueAccessor`|`levelAccessor`+`countAccessor`, `levels` (required), `orientation`, `colorScheme`
47
+ **GaugeChart** — `value` (required), `min`, `max`, `thresholds`, `arcWidth`, `sweep`, `fillZones`, `showNeedle`, `centerContent`
53
48
 
54
- All ordinal HOCs support `colorBy` and `colorScheme`. `categoryFormat` (`(label: string, index?: number) => string | ReactNode`) customizes individual tick labels (truncation, formatting, or custom ReactNode rendering via `<foreignObject>`). `showCategoryTicks` (default true) hides per-tick labels when false — margins auto-adjust. For distribution charts with `colorBy`, set `showCategoryTicks={false}` since the legend identifies categories.
49
+ All ordinal: `colorBy`, `colorScheme`, `categoryFormat` (string|ReactNode), `showCategoryTicks` (true).
55
50
 
56
51
  ## Network Charts (`semiotic/network`)
57
52
 
58
- **ForceDirectedGraph** — `nodes`, `edges`, `nodeIDAccessor`, `sourceAccessor`, `targetAccessor`, `colorBy`, `colorScheme`, `nodeSize` (number|string|fn), `nodeSizeRange`, `edgeWidth`, `edgeColor`, `edgeOpacity`, `iterations` (300), `forceStrength` (0.1), `showLabels`, `nodeLabel`, `legendInteraction`
59
- **SankeyDiagram** — `edges`, `nodes`, `valueAccessor`, `nodeIdAccessor` ("id"), `sourceAccessor` ("source"), `targetAccessor` ("target"), `colorBy`, `edgeColorBy` ("source"|"target"|"gradient"|fn), `orientation`, `nodeAlign`, `nodeWidth`, `nodePaddingRatio`, `nodeLabel`, `showLabels`, `edgeOpacity`
60
- **ChordDiagram** — `edges`, `nodes`, `valueAccessor`, `edgeColorBy`, `padAngle`, `groupWidth`, `showLabels`
61
- **TreeDiagram** — `data` (root), `layout`, `orientation`, `childrenAccessor`, `colorBy`, `colorByDepth`, `edgeStyle`
62
- **Treemap** — `data` (root), `childrenAccessor`, `valueAccessor`, `colorBy`, `colorByDepth`, `showLabels`, `labelMode`
63
- **CirclePack** — `data` (root), `childrenAccessor`, `valueAccessor`, `colorBy`, `colorByDepth`, `circleOpacity`
64
- **OrbitDiagram** — animated radial/orbital hierarchy. `data` (root), `childrenAccessor`, `nodeIdAccessor`, `orbitMode` ("flat"|"solar"|"atomic"|number[]), `speed` (0.25), `revolution`, `eccentricity`, `orbitSize`, `nodeRadius`, `showRings`, `showLabels`, `animated` (true), `colorBy`, `colorByDepth`. For static radial trees, use `TreeDiagram layout="radial"`.
53
+ **ForceDirectedGraph** — `nodes`, `edges`, `nodeIDAccessor`, `sourceAccessor`, `targetAccessor`, `colorBy`, `nodeSize`, `nodeSizeRange`, `edgeWidth`, `iterations` (300), `forceStrength` (0.1), `showLabels`, `nodeLabel`
54
+ **SankeyDiagram** — `edges`, `nodes`, `valueAccessor`, `nodeIdAccessor`, `colorBy`, `edgeColorBy`, `orientation`, `nodeAlign`, `nodeWidth`, `nodePaddingRatio`, `showLabels`
55
+ **ChordDiagram** — `edges`, `nodes`, `valueAccessor`, `edgeColorBy`, `padAngle`, `showLabels`
56
+ **TreeDiagram** — `data` (root), `layout`, `orientation`, `childrenAccessor`, `colorBy`, `colorByDepth`
57
+ **Treemap** — `data` (root), `childrenAccessor`, `valueAccessor`, `colorBy`, `colorByDepth`, `showLabels`
58
+ **CirclePack** — `data` (root), `childrenAccessor`, `valueAccessor`, `colorBy`, `colorByDepth`
59
+ **OrbitDiagram** — `data` (root), `childrenAccessor`, `orbitMode`, `speed`, `animated` (true), `colorBy`
65
60
 
66
61
  ## Geo Charts (`semiotic/geo`)
67
62
 
68
63
  Import from `semiotic/geo` — NOT `semiotic` — to avoid pulling d3-geo into non-geo bundles.
69
64
 
70
- **ChoroplethMap** — `areas` (GeoJSON Feature[] or "world-110m"), `valueAccessor`, `colorScheme`, `areaOpacity` (1), `projection` ("equalEarth"), `graticule`, `tooltip`, `showLegend`
71
- **ProportionalSymbolMap** — `points`, `xAccessor` ("lon"), `yAccessor` ("lat"), `sizeBy`, `sizeRange` ([3,30]), `colorBy`, `areas` (optional background), `projection`
72
- **FlowMap** — `flows`, `nodes`, `xAccessor`, `yAccessor`, `nodeIdAccessor`, `valueAccessor`, `edgeColorBy`, `edgeOpacity` (0.6), `edgeWidthRange` ([1,8]), `lineType` ("geo"|"line"), `showParticles`, `particleStyle`
73
- **DistanceCartogram** — `points`, `center` (id), `costAccessor`, `strength` (0-1), `lineMode`, `showRings` (true|false|number[]), `ringStyle`, `showNorth`, `costLabel`, `transition`, `pointRadius`
65
+ **ChoroplethMap** — `areas` (GeoJSON Feature[] or "world-110m"), `valueAccessor`, `colorScheme`, `projection` ("equalEarth"), `graticule`, `tooltip`, `showLegend`
66
+ **ProportionalSymbolMap** — `points`, `xAccessor` ("lon"), `yAccessor` ("lat"), `sizeBy`, `sizeRange`, `colorBy`, `areas` (optional background)
67
+ **FlowMap** — `flows`, `nodes`, `valueAccessor`, `edgeColorBy`, `lineType`, `showParticles`
68
+ **DistanceCartogram** — `points`, `center`, `costAccessor`, `strength`, `showRings`
74
69
 
75
- All geo HOCs: `fitPadding` (0–1), `zoomable` (defaults true with tileURL), `zoomExtent` ([1,8]), `onZoom`, `dragRotate` (true for orthographic), `graticule`, `tileURL`, `tileAttribution`, `tileCacheSize`, `selection`, `linkedHover`, `onObservation`
76
-
77
- **Tiles**: `tileURL` accepts string template (`{z}/{x}/{y}`) or function. Mercator only. OSM tiles are dev-only — use commercial provider with env var key in production.
78
- **Zoom**: Imperative: `ref.current.getZoom()`, `ref.current.resetZoom()`.
79
- **Reference geography**: `resolveReferenceGeography("world-110m"|"world-50m"|"land-110m"|"land-50m")` returns GeoJSON features.
80
- **mergeData(features, data, { featureKey, dataKey })** — join data into GeoJSON by key. World-atlas uses ISO numeric codes as `id`.
81
-
82
- ```jsx
83
- import { ChoroplethMap, resolveReferenceGeography, mergeData } from "semiotic/geo"
84
- const world = await resolveReferenceGeography("world-110m")
85
- const areas = mergeData(world, gdpData, { featureKey: "id", dataKey: "id" })
86
- <ChoroplethMap areas={areas} valueAccessor="gdpPerCapita" colorScheme="viridis"
87
- projection="equalEarth" zoomable tooltip />
88
- ```
89
-
90
- **StreamGeoFrame** — low-level frame. Push API: `ref.current.push(datum)`, `.pushMany()`, `.clear()`. Props: `projection`, `areas`, `points`, `lines`, `xAccessor`, `yAccessor`, `areaStyle`, `pointStyle`, `lineStyle`, `graticule`, `zoomable`, `decay`, `pulse`, `transition`.
70
+ All geo: `fitPadding`, `zoomable`, `zoomExtent`, `onZoom`, `dragRotate`, `graticule`, `tileURL`, `tileAttribution`
71
+ Helpers: `resolveReferenceGeography("world-110m"|"world-50m")`, `mergeData(features, data, { featureKey, dataKey })`
91
72
 
92
73
  ## Realtime Charts (`semiotic/realtime`)
93
74
 
94
- Push API: `chartRef.current.push({ time, value })`
95
-
96
- **IMPORTANT**: All pushed data must include a time field (default: `"time"`). Set `timeAccessor` if your field differs. Without valid time field, charts render blank.
75
+ Push API: `ref.current.push({ time, value })`. All pushed data **must** include a time field.
97
76
 
98
- **RealtimeLineChart** `timeAccessor` ("time"), `valueAccessor` ("value"), `windowSize` (200), `windowMode`, `stroke`, `strokeWidth`
99
- **RealtimeHistogram** — `binSize` (required), `timeAccessor`, `valueAccessor`, `categoryAccessor`, `colors`, `brush` (boolean|"x"|object, defaults to `{ dimension: "x", snap: "bin" }` when `true`), `onBrush`, `linkedBrush` (cross-chart coordination)
100
- **RealtimeSwarmChart** — `timeAccessor`, `valueAccessor`, `categoryAccessor`, `radius`, `opacity`
101
- **RealtimeWaterfallChart** — `timeAccessor`, `valueAccessor`, `positiveColor`, `negativeColor`
102
- **RealtimeHeatmap** — `timeAccessor`, `valueAccessor`, `heatmapXBins`, `heatmapYBins`, `aggregation`
103
- **Streaming Sankey** — `StreamNetworkFrame` with `chartType="sankey"`, `showParticles`, `particleStyle`. Push individual edges: `ref.current.push({ source, target, value })`.
77
+ **RealtimeLineChart**, **RealtimeHistogram** (+ `brush`, `onBrush`, `linkedBrush`), **RealtimeSwarmChart**, **RealtimeWaterfallChart**, **RealtimeHeatmap**, **Streaming Sankey** (StreamNetworkFrame + `showParticles`)
104
78
 
105
- Encoding: `decay`, `pulse`, `transition`, `staleness` — compose freely on all streaming charts.
106
-
107
- All Realtime* charts accept `data` props for static mode (no push API needed). RealtimeHistogram brush supports bin-snapping (`snap: "bin"`) and streaming tracking — the brush shrinks as selected bins scroll off and auto-clears when fully evicted. Bin snapping uses actual computed bin boundaries (data-driven), not a uniform grid — works with irregular bin widths. `snapDuring: true` enables continuous snap feedback during drag (not just on release).
79
+ Encoding: `decay`, `pulse`, `transition`, `staleness` — compose freely.
108
80
 
109
81
  ### Push API on HOC charts
110
- Most HOC charts support push via `forwardRef`. **Omit** `data`/`nodes`/`edges` — do NOT pass `data={[]}`.
82
+ Most HOCs support push via `forwardRef`. **Omit** `data` — do NOT pass `data={[]}`.
111
83
  ```jsx
112
84
  const ref = useRef()
113
- ref.current.push({ x: 1, y: 2 }) // single
114
- ref.current.pushMany([...points]) // batch
115
- ref.current.clear() // reset
116
- ref.current.getData() // read
117
- <Scatterplot ref={ref} xAccessor="x" yAccessor="y" />
85
+ ref.current.push({ id: "p1", x: 1, y: 2 })
86
+ ref.current.pushMany([...points])
87
+ ref.current.remove("p1") // by ID — requires pointIdAccessor
88
+ ref.current.remove(["p1", "p2"]) // batch remove
89
+ ref.current.update("p1", d => ({ ...d, y: 99 })) // in-place update — requires pointIdAccessor
90
+ ref.current.clear()
91
+ ref.current.getData()
92
+ <Scatterplot ref={ref} xAccessor="x" yAccessor="y" pointIdAccessor="id" />
118
93
  ```
119
- Supported: all XY, ordinal, network (Force, Sankey, Chord), geo point charts. **Not supported**: hierarchy charts (Tree, Treemap, CirclePack, Orbit), ChoroplethMap, FlowMap, ScatterplotMatrix.
120
-
121
- ## Stream Frame Callbacks (advanced)
122
- Frame callbacks (`nodeStyle`, `edgeStyle`, `nodeSize` as fn) receive `RealtimeNode`/`RealtimeEdge` wrappers. Access original data via `.data`:
123
- ```jsx
124
- // WRONG: nodeSize={(d) => d.weight} — d.weight is undefined
125
- // RIGHT: nodeSize={(d) => d.data?.weight} — or use string: nodeSize="weight"
126
- ```
127
- Same applies to `frameProps` style functions on HOCs. `customHoverBehavior`/`customClickBehavior` receive `{ type, data, x, y } | null`. `tooltipContent` receives `{ type, data }`.
128
-
129
- ## Hover Indicator
130
- The hover dot automatically matches the hovered element's color (line stroke, point fill, etc.). Override via `frameProps`:
131
- ```jsx
132
- <LineChart frameProps={{ hoverAnnotation: { pointColor: "#ff0000" } }} />
133
- ```
134
- Fallback chain: `pointColor` → element color → `--semiotic-primary` CSS var → `#007bff`.
94
+ `remove()` and `update()` require an ID accessor: `pointIdAccessor` on XY/realtime charts, `dataIdAccessor` on ordinal charts. Network HOC refs also use `remove(id)`/`update(id, updater)` (operates on nodes). For edge-level operations, use `StreamNetworkFrameHandle` directly: `removeNode(id)`, `removeEdge(sourceId, targetId)`, `updateNode(id, updater)`, `updateEdge(sourceId, targetId, updater)`.
95
+ Not supported: Tree, Treemap, CirclePack, Orbit, ChoroplethMap, FlowMap, ScatterplotMatrix.
135
96
 
136
97
  ## Coordinated Views
137
98
 
138
- **LinkedCharts** — `selections` (resolution: "union"|"intersect"|"crossfilter"), `showLegend`, `legendPosition`, `legendInteraction`, `legendSelectionName`, `legendField`
139
- **CategoryColorProvider** `colors` (map) or `categories` + `colorScheme`
140
- Chart props: `selection`, `linkedHover`, `linkedBrush`. Hooks: `useSelection`, `useLinkedHover`, `useBrushSelection`, `useFilteredData`
99
+ **LinkedCharts** — `selections`, **CategoryColorProvider** `colors`|`categories` + `colorScheme`
100
+ Chart props: `selection`, `linkedHover`, `linkedBrush`. Hooks: `useSelection`, `useLinkedHover`, `useBrushSelection`
101
+ **Linked crosshair**: `linkedHover={{ name: "sync", mode: "x-position", xField: "time" }}`. Click-to-lock: click locks crosshair (dashed white), click/Escape unlocks.
102
+ **ScatterplotMatrix**, **ChartContainer** (`title`, `subtitle`, `actions`), **ChartGrid** (`columns`, `gap`), **ContextLayout**
141
103
 
142
- **Linked crosshair** (coordinate-based hover sync): `linkedHover={{ name: "sync", mode: "x-position", xField: "time" }}` broadcasts the hovered X data value. Other charts with the same `linkedHover` name render a synced vertical crosshair at that X position. Each chart shows its own Y values independently. Use for dashboards with multiple time-series at different scales. **Click-to-lock**: click a chart to lock the crosshair at that X position; the locked line changes to a dashed white stroke. Click again or press Escape to unlock. Locking is automatic when `linkedHover` uses `x-position` mode.
143
- **ScatterplotMatrix** — `data`, `fields`, `colorBy`, `cellSize`, `hoverMode`, `brushMode`
144
- **ChartContainer** — `title`, `subtitle`, `height` (400), `width` ("100%"), `status`, `loading`, `error`, `errorBoundary`, `actions` ({ export, fullscreen, copyConfig, dataSummary }), `controls`
145
- **ChartGrid** — `columns` (number|"auto"), `minCellWidth` (300), `gap` (16). `emphasis="primary"` spans two columns.
146
- **ContextLayout** — `context` (ReactNode), `position`, `contextSize` (250)
104
+ ## Server-Side Rendering (`semiotic/server`)
147
105
 
148
- ## Key Patterns
106
+ HOC charts render SVG automatically in server environments. For standalone generation:
149
107
 
150
- ```jsx
151
- // Cross-highlighting dashboard
152
- <CategoryColorProvider categories={["North", "South", "East"]}>
153
- <LinkedCharts>
154
- <ChartGrid columns={2}>
155
- <LineChart data={d} colorBy="region" linkedHover={{ name: "hl", fields: ["region"] }} selection={{ name: "hl" }} emphasis="primary" responsiveWidth />
156
- <BarChart data={d} colorBy="region" linkedHover={{ name: "hl", fields: ["region"] }} selection={{ name: "hl" }} responsiveWidth />
157
- </ChartGrid>
158
- </LinkedCharts>
159
- </CategoryColorProvider>
160
-
161
- // Forecast + anomaly
162
- <LineChart data={ts} xAccessor="time" yAccessor="value"
163
- forecast={{ trainEnd: 60, steps: 15, confidence: 0.95 }}
164
- anomaly={{ threshold: 2 }} />
165
-
166
- // Pre-computed forecast bounds
167
- <LineChart data={ml} xAccessor="time" yAccessor="value"
168
- forecast={{ isTraining: "isTraining", isForecast: "isForecast", isAnomaly: "isAnomaly", upperBounds: "upper", lowerBounds: "lower" }} />
169
-
170
- // Percentile band — layer AreaChart + LineChart
171
- <>
172
- <AreaChart data={d} xAccessor="x" yAccessor="p95" y0Accessor="p5"
173
- showLine={false} areaOpacity={0.3} gradientFill />
174
- <LineChart data={d} xAccessor="x" yAccessor="p50" lineWidth={2} />
175
- </>
176
-
177
- // Streaming sankey with particles
178
- const sankeyRef = useRef()
179
- sankeyRef.current.push({ source: "Web", target: "API", value: 1 })
180
- <StreamNetworkFrame ref={sankeyRef} chartType="sankey"
181
- showParticles particleStyle={{ radius: 2, colorBy: "source" }}
182
- width={600} height={400} />
183
-
184
- // SSR
185
- import { renderOrdinalToStaticSVG } from "semiotic/server"
186
- const svg = renderOrdinalToStaticSVG({ data, categoryAccessor: "cat", valueAccessor: "val", width: 600, height: 400 })
108
+ ```ts
109
+ import { renderChart, renderToImage, renderToAnimatedGif, renderDashboard } from "semiotic/server"
110
+
111
+ const svg = renderChart("BarChart", { data, categoryAccessor: "region", valueAccessor: "revenue", theme: "tufte", showLegend: true, showGrid: true, annotations: [...] })
112
+ const png = await renderToImage("LineChart", { data, ... }, { format: "png", scale: 2 }) // requires sharp
113
+ const gif = await renderToAnimatedGif("line", data, { xAccessor: "x", yAccessor: "y", theme: "dark" }, { fps: 12, transitionFrames: 4, decay: { type: "linear" } }) // requires sharp + gifenc
114
+ const dashboard = renderDashboard([{ component: "BarChart", props: {...} }, { component: "PieChart", colSpan: 2, props: {...} }], { title: "Q1", theme: "dark", layout: { columns: 2 } })
187
115
  ```
188
116
 
189
- ## Annotations
117
+ All render functions accept `theme` (preset name or object). Theme categorical colors flow to data marks automatically. `generateFrameSVGs()` returns frame SVGs without sharp/gifenc (sync, for client preview).
118
+ AnimatedGifOptions: `fps`, `stepSize`, `windowSize`, `frameCount`, `xExtent`/`yExtent` (lock axes), `transitionFrames`, `easing`, `decay`, `loop`, `scale`.
119
+ Server SVGs include `role="img"`, `<title>`, `<desc>`, grid, legend, annotations (y-threshold, x-threshold, band, label, text, category-highlight). SVG groups have `id` attributes for Figma layer naming: `data-area`, `axes`, `grid`, `annotations`, `legend`, `chart-title`.
120
+
121
+ **`renderChart` required props by component:**
122
+ - **Sparkline** — `data`, `xAccessor`, `yAccessor`. No axes/grid/legend/title by default. Margin defaults to 2px.
123
+ - **LineChart/AreaChart** — `data`, `xAccessor`, `yAccessor`. Optional: `lineBy`/`areaBy`, `colorBy`, `colorScheme`.
124
+ - **StackedAreaChart** — `data`, `xAccessor`, `yAccessor`, `areaBy` (required).
125
+ - **Scatterplot/BubbleChart** — `data`, `xAccessor`, `yAccessor`. BubbleChart requires `sizeBy`.
126
+ - **Heatmap** — `data`, `xAccessor`, `yAccessor`, `valueAccessor`.
127
+ - **BarChart** — `data`, `categoryAccessor`, `valueAccessor`.
128
+ - **StackedBarChart** — `data`, `categoryAccessor`, `valueAccessor`, `stackBy` (required).
129
+ - **GroupedBarChart** — `data`, `categoryAccessor`, `valueAccessor`, `groupBy` (required).
130
+ - **PieChart/DonutChart** — `data`, `categoryAccessor`, `valueAccessor`.
131
+ - **FunnelChart** — `data`, `stepAccessor` ("step"), `valueAccessor` ("value"). Renders with trapezoid connectors, no axes.
132
+ - **GaugeChart** — `value`, `thresholds` (array of `{value, color, label}`). Optional: `min`, `max`, `sweep`, `arcWidth`.
133
+ - **SwimlaneChart** — `data`, `categoryAccessor`, `subcategoryAccessor` (required), `valueAccessor`.
134
+ - **ForceDirectedGraph** — `edges` (required). `nodes` optional (inferred from edges).
135
+ - **SankeyDiagram** — `edges` (required), `valueAccessor`.
136
+ - **ChoroplethMap** — `areas` (GeoJSON features, pre-resolved).
137
+
138
+ All components accept: `width`, `height`, `theme`, `title`, `description`, `showLegend`, `showGrid`, `background`, `annotations`, `margin`, `colorScheme`, `colorBy`, `legendPosition`. Pass additional frame-level props via `frameProps`.
190
139
 
191
- All HOCs accept `annotations` (array). Coordinates use your data field names. Network/orbit use `nodeId`.
140
+ ## Annotations
192
141
 
193
- **Positioning**: `widget` (React content at data coords — v3 replacement for v2 `htmlAnnotationRules`; props: `content`, `dx`, `dy`, `width`, `height`, `anchor`), `label` (callout with connector), `callout` (circle + label), `text` (plain text), `bracket`
194
- **Reference lines**: `y-threshold` (`value`, `label`, `color`, `labelPosition`: "left"|"center"|"right", `strokeDasharray`), `x-threshold` (`labelPosition`: "top"|"center"|"bottom"), `band` (`y0`, `y1`, `label`, `fill`)
195
- **Ordinal**: `category-highlight` (`category`, `color`, `opacity`, `label`) — highlights a category column/row. Works on BarChart, StackedBarChart, etc. `y-threshold` also works on vertical ordinal charts.
196
- **Enclosures**: `enclose` (circle around `coordinates`), `rect-enclose`, `highlight` (`filter` fn or `field`+`value`)
197
- **Statistical** (XY): `trend` (`method`: linear/polynomial/loess), `envelope`, `anomaly-band`, `forecast`
198
- **Streaming anchors**: `"fixed"` (default), `"latest"` (tracks newest datum), `"sticky"` (freezes when evicted)
142
+ All HOCs accept `annotations` (array). Coordinates use data field names.
199
143
 
200
- Custom rendering: `frameProps.svgAnnotationRules = (annotation, index, context) => ReactNode | null`. Context has `scales`, `width`, `height`, `data`. Colors inherit from theme (`--semiotic-primary`, `--semiotic-text-secondary`).
201
-
202
- ```jsx
203
- <LineChart data={data} xAccessor="time" yAccessor="latency"
204
- annotations={[
205
- { type: "y-threshold", value: 200, label: "SLA limit", color: "#e45050" },
206
- { type: "widget", time: 42, latency: 850, dy: -30, content: <span>Incident</span> },
207
- ]} />
208
- ```
144
+ **Positioning**: `widget`, `label`, `callout`, `text`, `bracket`
145
+ **Reference lines**: `y-threshold` (`value`, `label`, `color`, `labelPosition`), `x-threshold`, `band` (`y0`, `y1`)
146
+ **Ordinal**: `category-highlight`
147
+ **Enclosures**: `enclose`, `rect-enclose`, `highlight`
148
+ **Statistical**: `trend`, `envelope`, `anomaly-band`, `forecast`
149
+ **Streaming anchors**: `"fixed"` | `"latest"` | `"sticky"`
209
150
 
210
151
  ## Theming
211
152
 
212
- Charts are themeable via CSS custom properties on any ancestor element. Key vars: `--semiotic-bg`, `--semiotic-text`, `--semiotic-text-secondary`, `--semiotic-border`, `--semiotic-grid`, `--semiotic-primary`, `--semiotic-focus`, `--semiotic-font-family`, `--semiotic-border-radius`, `--semiotic-tooltip-bg`/`text`/`radius`/`font-size`/`shadow`, `--semiotic-selection-color`/`opacity`, `--semiotic-diverging`.
153
+ CSS custom properties: `--semiotic-bg`, `--semiotic-text`, `--semiotic-text-secondary`, `--semiotic-border`, `--semiotic-grid`, `--semiotic-primary`, `--semiotic-focus`, `--semiotic-font-family`, `--semiotic-tooltip-bg`/`text`/`radius`/`font-size`/`shadow`.
213
154
 
214
155
  ```jsx
215
- import { ThemeProvider } from "semiotic"
216
156
  <ThemeProvider theme="tufte"> {/* Named preset */}
217
- <ThemeProvider theme={{ colors: { primary: "#ff6b6b", categorical: [...] } }}> {/* Custom */}
157
+ <ThemeProvider theme={{ mode: "dark", colors: { categorical: [...] } }}> {/* Merge onto dark base */}
218
158
  ```
219
159
 
220
- **Color resolution priority** (when `colorBy` is set): explicit `colorScheme` prop > ThemeProvider `colors.categorical` > `"category10"` fallback. This means ThemeProvider categorical colors automatically apply to all charts — no need to pass `colorScheme` on every component.
221
-
222
- Presets: `light`, `dark`, `high-contrast`, `pastels`, `pastels-dark`, `bi-tool`, `bi-tool-dark`, `italian`, `italian-dark`, `tufte`, `tufte-dark`, `journalist`, `journalist-dark`, `playful`, `playful-dark`, `carbon`, `carbon-dark`.
223
-
224
- Serialization (`semiotic/themes`): `themeToCSS(theme, selector)`, `themeToTokens(theme)`, `resolveThemePreset(name)`.
225
- Color-blind palette: `import { COLOR_BLIND_SAFE_CATEGORICAL } from "semiotic"` (8-color Wong 2011).
226
- IBM Carbon palette: `import { CARBON_CATEGORICAL_14, CARBON_ALERT } from "semiotic"` (14-color categorical + 4 alert colors).
227
-
228
- **`semiotic/utils`** (~137KB, ~10% of full bundle) — Lightweight entry point for utilities without any chart components:
229
- - **Theme**: `ThemeProvider`, `useTheme`, `LIGHT_THEME`, `DARK_THEME`, `HIGH_CONTRAST_THEME`, `COLOR_BLIND_SAFE_CATEGORICAL`, `CARBON_CATEGORICAL_14`, `CARBON_ALERT`, `themeToCSS`, `themeToTokens`, `resolveThemePreset`, `THEME_PRESETS`
230
- - **Format**: `adaptiveTimeTicks`, `smartTickFormat`
231
- - **Color**: `darkenColor`, `lightenColor`
232
- - **Patterns**: `createHatchPattern`
233
- - **Validation**: `validateProps`, `diagnoseConfig`
234
- - **Serialization**: `toConfig`, `fromConfig`, `toURL`, `fromURL`, `copyConfig`, `configToJSX`, `serializeSelections`, `deserializeSelections`, `exportChart`
235
- - **Vega-Lite**: `fromVegaLite` — convert Vega-Lite specs to Semiotic configs
236
- - **Data structures**: `RingBuffer`, `IncrementalExtent`
237
- - **Tooltip**: `normalizeTooltip`
238
-
239
- Key: `ThemeProvider` sets CSS vars on a wrapper div (no React context). Canvas charts read vars via `getComputedStyle`. `exportChart` inlines computed styles.
240
-
241
- **Dark/light mode merge rules:** String preset (e.g. `"dark"`) → full replacement with that preset's theme. Object with `mode` (e.g. `{ mode: "dark", colors: { categorical: [...] } }`) → merges onto the matching base theme (`DARK_THEME` or `LIGHT_THEME`), so background/text/grid adapt while your overrides are preserved. Object without `mode` → shallow-merges onto the current theme (partial override). ThemeProvider is reactive — changing the `theme` prop re-applies immediately.
242
-
243
- **CSS interop:** Host app `--semiotic-*` vars on `:root` are overridden by ThemeProvider's closer wrapper div. To let app tokens flow through, either skip ThemeProvider and set `--semiotic-*` vars in CSS, or use the hybrid approach (ThemeProvider for palette only, CSS vars for chrome).
244
-
245
- ## Server-Side Rendering
246
- - HOC charts and Frames render SVG automatically in server environments
247
- - `renderXYToStaticSVG(props)`, `renderOrdinalToStaticSVG(props)`, `renderNetworkToStaticSVG(props)`, `renderGeoToStaticSVG(props)` from `semiotic/server`
248
- - `frameType` is `"xy"|"ordinal"|"network"|"geo"` (NOT component names)
249
- - Geo SSR requires pre-resolved features (synchronous — call `resolveReferenceGeography` first)
250
- - Works with Next.js App Router, Remix, Astro
160
+ **Color priority** (with `colorBy`): explicit `colorScheme` > ThemeProvider `colors.categorical` > `"category10"`.
161
+ Presets: `light`, `dark`, `high-contrast`, `pastels`(-dark), `bi-tool`(-dark), `italian`(-dark), `tufte`(-dark), `journalist`(-dark), `playful`(-dark), `carbon`(-dark).
162
+ Serialization: `themeToCSS(theme, selector)`, `themeToTokens(theme)`, `resolveThemePreset(name)`.
251
163
 
252
164
  ## AI Features
253
- - `onObservation` / `useChartObserver` structured events across charts
254
- - `toConfig`/`fromConfig`/`toURL`/`fromURL`/`copyConfig`/`configToJSX` — serialization
255
- - `DetailsPanel` — click-driven detail panel in `ChartContainer`
256
- - `validateProps(componentName, props)` — prop validation with typo suggestions
257
- - `diagnoseConfig(componentName, props)` — anti-pattern detector (13+ checks)
258
- - `exportChart(containerDiv, { format: "png"|"svg" })` — pass wrapper div, composites canvas+SVG
259
- - `npx semiotic-ai --doctor` — CLI validation
260
-
261
- ## Canvas Pattern Fills
262
-
263
- `createHatchPattern({ background, stroke, lineWidth, spacing, angle })` from `semiotic` — returns `CanvasPattern | null` for use as `fill` in style functions. Used by FunnelChart vertical mode for dropoff bars.
165
+ `onObservation`/`useChartObserver`, `toConfig`/`fromConfig`/`toURL`/`fromURL`/`copyConfig`/`configToJSX`, `validateProps(component, props)`, `diagnoseConfig(component, props)`, `exportChart(div, { format })`, `npx semiotic-ai --doctor`
264
166
 
265
167
  ## Accessibility
266
168
 
267
- Charts render with `role="group"` (outer interactive wrapper, keyboard/focus) and `role="img"` (inner canvas, read by assistive tech). SVG overlays include `<title>` and `<desc>`.
268
-
269
- **Keyboard navigation**: Arrow keys navigate data points. In XY/ordinal charts, ArrowRight/Left moves within a series, ArrowUp/Down switches series. In network charts, arrows move to the spatially nearest node in the pressed direction; Enter cycles edge-connected neighbors. Home/End jump to first/last. PageUp/PageDown skip 10%. Escape clears focus.
270
-
271
- **Focus ring**: Shape-adaptive dashed ring (circle for points, rect for bars, arc for wedges). Color: `--semiotic-focus` CSS var.
272
-
273
- **Data summary**: `accessibleTable` (default true) renders a sr-only summary. Activate via keyboard focus or `actions.dataSummary` in ChartContainer. JIT-computed — no render cost until activated.
274
-
275
- **Reduced motion**: `prefers-reduced-motion` auto-detected. Transitions skip to end state, orbit stops, pulse/decay disabled.
276
-
277
- **High contrast**: `forced-colors` / `prefers-contrast: more` auto-detected. ThemeProvider applies `HIGH_CONTRAST_THEME` automatically.
278
-
279
- **Hooks** (from `semiotic`): `useReducedMotion()`, `useHighContrast()` — SSR-safe, return `false` on server.
169
+ `role="group"` (outer) + `role="img"` (inner canvas). Keyboard: arrows navigate points, Enter cycles neighbors, Home/End/PageUp/PageDown. Shape-adaptive focus ring (`--semiotic-focus`). `accessibleTable` (default true) for sr-only data summary. Auto-detects `prefers-reduced-motion` and `forced-colors`. Hooks: `useReducedMotion()`, `useHighContrast()`.
280
170
 
281
171
  ## Known Pitfalls
282
172
 
283
- - **Tooltip datum shape**: HOC tooltip functions get raw data. Frame `tooltipContent` gets wrapped data — use `d.data`.
284
- - **Tooltip positioning**: Tooltips auto-flip when near container edges (right→left, bottom→top). Custom `tooltip` content should not add its own background — the wrapper provides `--semiotic-tooltip-bg`, `--semiotic-tooltip-text`, etc. Override wrapper styles via CSS custom properties, not inline styles.
285
- - **Legend positioning**: "bottom" auto-expands margin ~80px. For narrow charts (<400px), prefer "bottom" or "top".
286
- - **MultiAxisLineChart legend**: Always use `legendPosition="bottom"` (or `"top"`) the right-hand axis occupies the space where a right-side legend would go.
287
- - **Log scale**: Clamps domain min to 1e-6 (log(0) undefined).
288
- - **barPadding**: Pixel value, defaults 40/60. Reduce for small charts.
289
- - **Horizontal bars**: Need wider left margin with long labels: `margin={{ left: 120 }}`.
290
- - **LinkedCharts legends**: `CategoryColorProvider` suppresses child legends. Force with `showLegend={true}`.
291
- - **Push API**: Omit `data` prop entirely. `data={[]}` clears pushed data every render.
292
- - **frameProps style functions**: Bypass HOC color resolution — use `colorBy` prop instead. Frame style functions receive `(datum, categoryName)`, not `(datum, index)`.
293
- - **v2 migration**: `htmlAnnotationRules` `widget` annotations + `svgAnnotationRules`. v2 `summaryStyle` index-based coloring → v3 category-string-based.
294
- - **accessibleTable**: Direct prop on HOCs. Set `accessibleTable={false}` to disable the sr-only data summary.
295
- - **Format functions returning ReactNode**: `xFormat`, `yFormat`, and `categoryFormat` can return `string | ReactNode`. ReactNode labels render inside `<foreignObject>` (SVG interop). Useful for rotated, multi-line, or icon-decorated tick labels:
296
- ```jsx
297
- <BarChart categoryFormat={(label) => <span style={{ color: "red" }}>{label}</span>} />
298
- ```
299
- - **Per-series fillArea**: `fillArea={["seriesA", "seriesB"]}` on LineChart fills only named series while others stay as lines. Series names must match `lineBy`/`colorBy` group keys. Uses a dedicated `"mixed"` chart type internally:
300
- ```jsx
301
- <LineChart data={data} lineBy="series" colorBy="series" fillArea={["Revenue", "Cost"]} />
302
- ```
303
- - **Hover highlight**: `hoverHighlight="series"` dims non-hovered series on data mark hover (not just legend). Requires `colorBy` as a string field. Works on all XY and ordinal HOCs.
304
- - **Click-to-lock crosshair**: In `linkedHover` x-position mode, clicking locks the crosshair (dashed white line). Hover updates are ignored while locked. Click again or press Escape to unlock. Multi-chart safe — unmounting one chart doesn't unlock another's crosshair.
305
- - **Multi-color gradientFill**: `gradientFill={{ colorStops: [{offset, color}] }}` on AreaChart for semantic color bands. Supports `transparent`. Requires at least 2 stops. Offsets clamped to [0,1]:
306
- ```jsx
307
- <AreaChart gradientFill={{ colorStops: [{ offset: 0, color: "red" }, { offset: 0.5, color: "transparent" }] }} />
308
- ```
309
- - **Line stroke gradient**: `lineGradient={{ colorStops: [{offset, color}] }}` on LineChart/AreaChart for horizontal gradient strokes. Gradient runs from first to last X point.
310
- - **Multi-point tooltip**: `tooltip="multi"` on LineChart shows all series values at hovered X with color swatches (legend-in-tooltip). Custom tooltip functions receive `datum.allSeries: Array<{group, value, color}>`:
311
- ```jsx
312
- <LineChart data={data} lineBy="series" colorBy="series" tooltip="multi" />
313
- ```
314
- - **Axis config** (`frameProps.axes`): Per-axis options: `includeMax: true` forces domain-max tick. `autoRotate: true` rotates bottom-axis labels 45° when crowded. `gridStyle: "dashed" | "dotted" | string` sets strokeDasharray on grid lines (requires `showGrid`):
315
- ```jsx
316
- <LineChart showGrid frameProps={{ axes: [{ orient: "bottom", includeMax: true, autoRotate: true, gridStyle: "dashed" }] }} />
317
- ```
318
- - **Bar baseline alignment**: Ordinal axis baseline aligns with `rScale(0)`, not chart edge. `baselinePadding={true}` restores the old padded look; default `false` is flush.
319
- - **hoverRadius**: Max pixel distance for hover/click hit testing (default 30px across all frames — XY, network, geo, ordinal). Available on all XY HOCs and `StreamXYFrameProps`:
320
- ```jsx
321
- <Scatterplot hoverRadius={60} tooltip /> {/* Larger hit area for sparse data */}
322
- ```
323
- - **Landmark ticks**: `landmarkTicks: true` on bottom/left axis config bolds tick labels at month/year boundaries. Works with `xScaleType: "time"` for Date-aware ticks. Custom function: `landmarkTicks: (value, index) => boolean`.
324
- - **xScaleType: "time"**: Creates `scaleTime` for the X axis. Ticks land on real calendar boundaries (weeks, months) instead of round numbers. Required for landmark ticks with timestamp data.
325
- - **Tick deduplication**: Adjacent identical tick labels are automatically removed. Prevents duplicate labels when tick format has insufficient resolution (e.g. month-only format on weekly ticks).
326
-
327
- ## Performance
328
-
329
- - **scalePadding**: Pixel inset on scale ranges to prevent glyph clipping at chart edges. Insets both X and Y ranges — edge data points map to N pixels from the chart boundary instead of 0px. Domain and tick values unchanged (rendered positions shift inward with the range). On `StreamXYFrame`, use `scalePadding` directly; on HOC charts, pass via `frameProps`:
330
- ```jsx
331
- <StreamXYFrame chartType="candlestick" scalePadding={12} ... />
332
- <Scatterplot data={data} pointRadius={8} frameProps={{ scalePadding: 12 }} />
333
- ```
334
- - **Range/dumbbell plot**: Use `chartType="candlestick"` on StreamXYFrame with only `highAccessor` + `lowAccessor` (omit `openAccessor`/`closeAccessor`). Auto-detects range mode: no body rect, endpoint dots, single `rangeColor` via `candlestickStyle={{ rangeColor: "#6366f1" }}`. When `bodyWidth === 0`, body rect is skipped entirely (no invisible DOM elements).
173
+ - **Tooltip datum shape**: HOC tooltips get raw data. Frame `tooltipContent` gets wrapped — use `d.data`.
174
+ - **Legend**: "bottom" expands margin ~80px. MultiAxisLineChart: use `legendPosition="bottom"`.
175
+ - **Log scale**: Domain min clamped to 1e-6.
176
+ - **barPadding**: Pixel value (40/60 default). Reduce for small charts.
177
+ - **Horizontal bars**: Need wider left margin: `margin={{ left: 120 }}`.
178
+ - **Push API**: Omit `data` entirely. `data={[]}` clears on every render.
179
+ - **frameProps style functions**: Bypass HOC color resolution use `colorBy` prop instead.
180
+ - **Geo imports**: Always `semiotic/geo`, never `semiotic`, to avoid d3-geo in non-geo bundles.
181
+ - **fillArea**: `fillArea={["seriesA"]}` fills named series only. Names must match `lineBy`/`colorBy` keys.
182
+ - **hoverHighlight**: Requires `colorBy` as a string field.
183
+ - **tooltip="multi"**: Shows all series at hovered X. Custom fn receives `datum.allSeries`.
184
+ - **Axis config**: `frameProps.axes: [{ orient, includeMax, autoRotate, gridStyle, landmarkTicks }]`
185
+ - **xScaleType: "time"**: Creates `scaleTime`. Required for landmark ticks with timestamps.
186
+ - **scalePadding**: Pixel inset on scale ranges. Pass via `frameProps={{ scalePadding: 12 }}`.
187
+ - **categoryFormat/xFormat/yFormat**: Can return ReactNode (renders in `<foreignObject>`).
188
+ - **Tick deduplication**: Adjacent identical labels auto-removed.
335
189
 
336
190
  ## Performance
337
191
 
338
- Prefer string accessors (`xAccessor="value"`) over function accessors — always referentially stable. If you must use functions, memoize with `useCallback` or define outside the component. The pipeline uses `.toString()` comparison for inline arrows but this fails for closures capturing changing variables.
192
+ Prefer string accessors (`xAccessor="value"`) — always referentially stable. Memoize function accessors with `useCallback`.
package/README.md CHANGED
@@ -12,7 +12,7 @@ dashboards when you need them. Structured schemas and an MCP server so
12
12
  AI coding assistants generate correct chart code on the first try.
13
13
 
14
14
  ```jsx
15
- import { LineChart } from "semiotic"
15
+ import { LineChart } from "semiotic/xy" // 143 KB gzip
16
16
 
17
17
  <LineChart
18
18
  data={salesData}
@@ -277,20 +277,35 @@ configToJSX(config)
277
277
  Supports bar, line, area, point, rect, arc, tick marks with encoding translation
278
278
  for color, size, aggregation, and binning.
279
279
 
280
- ## Smaller Bundles
280
+ ## Bundle Sizes
281
+
282
+ Semiotic ships 11 entry points. **Don't import from `"semiotic"` unless you need everything** — use the sub-path that matches your chart type:
281
283
 
282
- Import only what you need:
284
+ | Entry Point | gzip | What's inside |
285
+ |---|---|---|
286
+ | `semiotic/xy` | **143 KB** | LineChart, AreaChart, Scatterplot, Heatmap, + 7 more XY charts |
287
+ | `semiotic/ordinal` | **109 KB** | BarChart, PieChart, BoxPlot, Histogram, + 11 more categorical charts |
288
+ | `semiotic/network` | **98 KB** | ForceDirectedGraph, SankeyDiagram, Treemap, + 4 more |
289
+ | `semiotic/geo` | **93 KB** | ChoroplethMap, FlowMap, DistanceCartogram, ProportionalSymbolMap |
290
+ | `semiotic/realtime` | **145 KB** | RealtimeLineChart, RealtimeHistogram, + 3 streaming charts |
291
+ | `semiotic/server` | **100 KB** | renderChart, renderDashboard, renderToImage, renderToAnimatedGif |
292
+ | `semiotic/utils` | **31 KB** | ThemeProvider, validators, serialization — no chart components |
293
+ | `semiotic/themes` | **5 KB** | Theme presets only (tufte, carbon, etc.) |
294
+ | `semiotic/data` | **5 KB** | bin, rollup, groupBy, pivot, fromVegaLite |
295
+ | `semiotic/ai` | **269 KB** | All 38 HOCs + validation — optimized for LLM code generation |
296
+ | `semiotic` | **278 KB** | Everything above (full bundle) |
283
297
 
284
298
  ```jsx
285
- import { LineChart } from "semiotic/xy" // ~123 KB gzip
286
- import { BarChart } from "semiotic/ordinal" // ~88 KB gzip
287
- import { ForceDirectedGraph } from "semiotic/network" // ~89 KB gzip
288
- import { ChoroplethMap } from "semiotic/geo" // ~82 KB gzip
289
- import { LineChart } from "semiotic/ai" // ~236 KB gzip (all HOCs)
299
+ // Import from the sub-path, not from "semiotic"
300
+ import { LineChart } from "semiotic/xy"
301
+ import { BarChart } from "semiotic/ordinal"
302
+ import { SankeyDiagram } from "semiotic/network"
303
+ import { ChoroplethMap } from "semiotic/geo"
290
304
  ```
291
305
 
292
- Granular entry points export only v3 Stream Frames and HOC charts no legacy
293
- utilities or backwards-compatibility shims.
306
+ **Tree-shaking**: Each sub-path is a self-contained bundle with `"sideEffects": false`. Bundlers (webpack, Rollup, Vite, esbuild) will tree-shake unused exports. If you only use `LineChart` from `semiotic/xy`, the bar/pie/network code is never included.
307
+
308
+ **When to use `"semiotic"`**: Only if your app uses charts from 3+ categories (XY + ordinal + network) and you'd rather have one import than three. The full bundle is 278 KB gzip — comparable to a single D3 import.
294
309
 
295
310
  ## TypeScript
296
311
 
@@ -321,17 +336,22 @@ import { LineChart } from "semiotic"
321
336
  <LineChart data={data} xAccessor="date" yAccessor="value" />
322
337
  ```
323
338
 
324
- For standalone SVG generation (email, OG images, PDF), use the server entry point:
339
+ For standalone SVG/PNG/GIF generation (email, OG images, PDF, Slack), use the server entry point:
325
340
 
326
341
  ```js
327
- import { renderToStaticSVG } from "semiotic/server"
342
+ import { renderChart, renderToImage, renderToAnimatedGif } from "semiotic/server"
328
343
 
329
- const svg = renderToStaticSVG("xy", {
330
- lines: [{ coordinates: data }],
331
- xAccessor: "date",
332
- yAccessor: "value",
333
- size: [600, 400],
344
+ // SVG sync, no dependencies
345
+ const svg = renderChart("LineChart", {
346
+ data, xAccessor: "date", yAccessor: "value",
347
+ theme: "tufte", title: "Revenue Trend",
334
348
  })
349
+
350
+ // PNG — async, requires sharp
351
+ const png = await renderToImage("BarChart", { data, ... }, { format: "png", scale: 2 })
352
+
353
+ // Animated GIF — async, requires sharp + gifenc
354
+ const gif = await renderToAnimatedGif("line", data, { ... }, { fps: 12 })
335
355
  ```
336
356
 
337
357
  ## MCP Server
package/ai/schema.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "name": "semiotic",
4
- "version": "3.2.2",
4
+ "version": "3.3.0",
5
5
  "description": "React data visualization library for charts, networks, and beyond",
6
6
  "tools": [
7
7
  {