semiotic 3.2.2 → 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 (73) hide show
  1. package/CLAUDE.md +118 -222
  2. package/README.md +37 -17
  3. package/ai/schema.json +64 -1
  4. package/ai/system-prompt.md +3 -3
  5. package/dist/components/Tooltip/Tooltip.d.ts +6 -1
  6. package/dist/components/charts/index.d.ts +2 -0
  7. package/dist/components/charts/ordinal/BarChart.d.ts +2 -0
  8. package/dist/components/charts/ordinal/GaugeChart.d.ts +55 -0
  9. package/dist/components/charts/ordinal/GroupedBarChart.d.ts +1 -0
  10. package/dist/components/charts/ordinal/StackedBarChart.d.ts +1 -0
  11. package/dist/components/charts/ordinal/SwimlaneChart.d.ts +4 -0
  12. package/dist/components/charts/realtime/RealtimeHeatmap.d.ts +2 -0
  13. package/dist/components/charts/realtime/RealtimeHistogram.d.ts +2 -0
  14. package/dist/components/charts/realtime/RealtimeLineChart.d.ts +2 -0
  15. package/dist/components/charts/realtime/RealtimeSwarmChart.d.ts +2 -0
  16. package/dist/components/charts/realtime/RealtimeWaterfallChart.d.ts +2 -0
  17. package/dist/components/charts/shared/hooks.d.ts +5 -1
  18. package/dist/components/charts/shared/types.d.ts +14 -5
  19. package/dist/components/charts/shared/useChartSetup.d.ts +2 -0
  20. package/dist/components/charts/xy/AreaChart.d.ts +17 -2
  21. package/dist/components/charts/xy/LineChart.d.ts +14 -3
  22. package/dist/components/realtime/RingBuffer.d.ts +11 -0
  23. package/dist/components/realtime/types.d.ts +4 -0
  24. package/dist/components/semiotic-ordinal.d.ts +2 -0
  25. package/dist/components/semiotic-server.d.ts +6 -1
  26. package/dist/components/semiotic-utils.d.ts +2 -1
  27. package/dist/components/semiotic.d.ts +3 -3
  28. package/dist/components/server/animatedGif.d.ts +78 -0
  29. package/dist/components/server/renderToStaticSVG.d.ts +85 -5
  30. package/dist/components/server/staticAnnotations.d.ts +40 -0
  31. package/dist/components/server/staticLegend.d.ts +39 -0
  32. package/dist/components/server/svgHatchPattern.d.ts +26 -0
  33. package/dist/components/server/themeResolver.d.ts +35 -0
  34. package/dist/components/store/LinkedCrosshairStore.d.ts +6 -2
  35. package/dist/components/stream/CanvasHitTester.d.ts +13 -0
  36. package/dist/components/stream/GeoPipelineStore.d.ts +6 -1
  37. package/dist/components/stream/NetworkPipelineStore.d.ts +20 -0
  38. package/dist/components/stream/OrdinalPipelineStore.d.ts +11 -0
  39. package/dist/components/stream/OrdinalSVGOverlay.d.ts +6 -1
  40. package/dist/components/stream/PipelineStore.d.ts +28 -1
  41. package/dist/components/stream/SVGOverlay.d.ts +11 -5
  42. package/dist/components/stream/accessorUtils.d.ts +3 -3
  43. package/dist/components/stream/geoTypes.d.ts +2 -0
  44. package/dist/components/stream/hitTestUtils.d.ts +15 -0
  45. package/dist/components/stream/networkTypes.d.ts +33 -0
  46. package/dist/components/stream/ordinalTypes.d.ts +37 -4
  47. package/dist/components/stream/renderers/resolveCSSColor.d.ts +17 -0
  48. package/dist/components/stream/types.d.ts +58 -5
  49. package/dist/components/stream/xySceneBuilders/mixedScene.d.ts +12 -0
  50. package/dist/components/stream/xySceneBuilders/types.d.ts +15 -0
  51. package/dist/geo.min.js +1 -1
  52. package/dist/geo.module.min.js +1 -1
  53. package/dist/network.min.js +1 -1
  54. package/dist/network.module.min.js +1 -1
  55. package/dist/ordinal.min.js +1 -1
  56. package/dist/ordinal.module.min.js +1 -1
  57. package/dist/realtime.min.js +1 -1
  58. package/dist/realtime.module.min.js +1 -1
  59. package/dist/semiotic-ai.min.js +1 -1
  60. package/dist/semiotic-ai.module.min.js +1 -1
  61. package/dist/semiotic-ordinal.d.ts +2 -0
  62. package/dist/semiotic-server.d.ts +6 -1
  63. package/dist/semiotic-utils.d.ts +2 -1
  64. package/dist/semiotic-utils.min.js +1 -1
  65. package/dist/semiotic-utils.module.min.js +1 -1
  66. package/dist/semiotic.d.ts +3 -3
  67. package/dist/semiotic.min.js +1 -1
  68. package/dist/semiotic.module.min.js +1 -1
  69. package/dist/server.min.js +1 -1
  70. package/dist/server.module.min.js +1 -1
  71. package/dist/xy.min.js +1 -1
  72. package/dist/xy.module.min.js +1 -1
  73. package/package.json +21 -7
package/CLAUDE.md CHANGED
@@ -2,295 +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 | `(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)
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`, `areaOpacity` (0.3), `anomaly`, `forecast`, `directLabel`, `gapStrategy` ("break"|"interpolate"|"zero"), `xScaleType`/`yScaleType` ("linear"|"log")
25
- **AreaChart** — LineChart props + `areaBy`, `y0Accessor` (band/ribbon), `gradientFill` (boolean|{topOpacity,bottomOpacity}), `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.
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`
51
48
 
52
- All ordinal HOCs support `colorBy` and `colorScheme`. `categoryFormat` (`(label: string, index?: number) => string`) customizes individual tick labels (truncation, formatting). `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).
53
50
 
54
51
  ## Network Charts (`semiotic/network`)
55
52
 
56
- **ForceDirectedGraph** — `nodes`, `edges`, `nodeIDAccessor`, `sourceAccessor`, `targetAccessor`, `colorBy`, `colorScheme`, `nodeSize` (number|string|fn), `nodeSizeRange`, `edgeWidth`, `edgeColor`, `edgeOpacity`, `iterations` (300), `forceStrength` (0.1), `showLabels`, `nodeLabel`, `legendInteraction`
57
- **SankeyDiagram** — `edges`, `nodes`, `valueAccessor`, `nodeIdAccessor` ("id"), `sourceAccessor` ("source"), `targetAccessor` ("target"), `colorBy`, `edgeColorBy` ("source"|"target"|"gradient"|fn), `orientation`, `nodeAlign`, `nodeWidth`, `nodePaddingRatio`, `nodeLabel`, `showLabels`, `edgeOpacity`
58
- **ChordDiagram** — `edges`, `nodes`, `valueAccessor`, `edgeColorBy`, `padAngle`, `groupWidth`, `showLabels`
59
- **TreeDiagram** — `data` (root), `layout`, `orientation`, `childrenAccessor`, `colorBy`, `colorByDepth`, `edgeStyle`
60
- **Treemap** — `data` (root), `childrenAccessor`, `valueAccessor`, `colorBy`, `colorByDepth`, `showLabels`, `labelMode`
61
- **CirclePack** — `data` (root), `childrenAccessor`, `valueAccessor`, `colorBy`, `colorByDepth`, `circleOpacity`
62
- **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`
63
60
 
64
61
  ## Geo Charts (`semiotic/geo`)
65
62
 
66
63
  Import from `semiotic/geo` — NOT `semiotic` — to avoid pulling d3-geo into non-geo bundles.
67
64
 
68
- **ChoroplethMap** — `areas` (GeoJSON Feature[] or "world-110m"), `valueAccessor`, `colorScheme`, `areaOpacity` (1), `projection` ("equalEarth"), `graticule`, `tooltip`, `showLegend`
69
- **ProportionalSymbolMap** — `points`, `xAccessor` ("lon"), `yAccessor` ("lat"), `sizeBy`, `sizeRange` ([3,30]), `colorBy`, `areas` (optional background), `projection`
70
- **FlowMap** — `flows`, `nodes`, `xAccessor`, `yAccessor`, `nodeIdAccessor`, `valueAccessor`, `edgeColorBy`, `edgeOpacity` (0.6), `edgeWidthRange` ([1,8]), `lineType` ("geo"|"line"), `showParticles`, `particleStyle`
71
- **DistanceCartogram** — `points`, `center` (id), `costAccessor`, `strength` (0-1), `lineMode`, `showRings` (true|false|number[]), `ringStyle`, `showNorth`, `costLabel`, `transition`, `pointRadius`
72
-
73
- 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`
74
-
75
- **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.
76
- **Zoom**: Imperative: `ref.current.getZoom()`, `ref.current.resetZoom()`.
77
- **Reference geography**: `resolveReferenceGeography("world-110m"|"world-50m"|"land-110m"|"land-50m")` returns GeoJSON features.
78
- **mergeData(features, data, { featureKey, dataKey })** — join data into GeoJSON by key. World-atlas uses ISO numeric codes as `id`.
79
-
80
- ```jsx
81
- import { ChoroplethMap, resolveReferenceGeography, mergeData } from "semiotic/geo"
82
- const world = await resolveReferenceGeography("world-110m")
83
- const areas = mergeData(world, gdpData, { featureKey: "id", dataKey: "id" })
84
- <ChoroplethMap areas={areas} valueAccessor="gdpPerCapita" colorScheme="viridis"
85
- projection="equalEarth" zoomable tooltip />
86
- ```
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`
87
69
 
88
- **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 })`
89
72
 
90
73
  ## Realtime Charts (`semiotic/realtime`)
91
74
 
92
- Push API: `chartRef.current.push({ time, value })`
93
-
94
- **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.
95
76
 
96
- **RealtimeLineChart** `timeAccessor` ("time"), `valueAccessor` ("value"), `windowSize` (200), `windowMode`, `stroke`, `strokeWidth`
97
- **RealtimeHistogram** — `binSize` (required), `timeAccessor`, `valueAccessor`, `categoryAccessor`, `colors`, `brush` (boolean|"x"|object, defaults to `{ dimension: "x", snap: "bin" }` when `true`), `onBrush`, `linkedBrush` (cross-chart coordination)
98
- **RealtimeSwarmChart** — `timeAccessor`, `valueAccessor`, `categoryAccessor`, `radius`, `opacity`
99
- **RealtimeWaterfallChart** — `timeAccessor`, `valueAccessor`, `positiveColor`, `negativeColor`
100
- **RealtimeHeatmap** — `timeAccessor`, `valueAccessor`, `heatmapXBins`, `heatmapYBins`, `aggregation`
101
- **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`)
102
78
 
103
- Encoding: `decay`, `pulse`, `transition`, `staleness` — compose freely on all streaming charts.
104
-
105
- 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.
106
80
 
107
81
  ### Push API on HOC charts
108
- 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={[]}`.
109
83
  ```jsx
110
84
  const ref = useRef()
111
- ref.current.push({ x: 1, y: 2 }) // single
112
- ref.current.pushMany([...points]) // batch
113
- ref.current.clear() // reset
114
- ref.current.getData() // read
115
- <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" />
116
93
  ```
117
- Supported: all XY, ordinal, network (Force, Sankey, Chord), geo point charts. **Not supported**: hierarchy charts (Tree, Treemap, CirclePack, Orbit), ChoroplethMap, FlowMap, ScatterplotMatrix.
118
-
119
- ## Stream Frame Callbacks (advanced)
120
- Frame callbacks (`nodeStyle`, `edgeStyle`, `nodeSize` as fn) receive `RealtimeNode`/`RealtimeEdge` wrappers. Access original data via `.data`:
121
- ```jsx
122
- // WRONG: nodeSize={(d) => d.weight} — d.weight is undefined
123
- // RIGHT: nodeSize={(d) => d.data?.weight} — or use string: nodeSize="weight"
124
- ```
125
- Same applies to `frameProps` style functions on HOCs. `customHoverBehavior`/`customClickBehavior` receive `{ type, data, x, y } | null`. `tooltipContent` receives `{ type, data }`.
126
-
127
- ## Hover Indicator
128
- The hover dot automatically matches the hovered element's color (line stroke, point fill, etc.). Override via `frameProps`:
129
- ```jsx
130
- <LineChart frameProps={{ hoverAnnotation: { pointColor: "#ff0000" } }} />
131
- ```
132
- 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.
133
96
 
134
97
  ## Coordinated Views
135
98
 
136
- **LinkedCharts** — `selections` (resolution: "union"|"intersect"|"crossfilter"), `showLegend`, `legendPosition`, `legendInteraction`, `legendSelectionName`, `legendField`
137
- **CategoryColorProvider** `colors` (map) or `categories` + `colorScheme`
138
- 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**
139
103
 
140
- **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.
141
- **ScatterplotMatrix** — `data`, `fields`, `colorBy`, `cellSize`, `hoverMode`, `brushMode`
142
- **ChartContainer** — `title`, `subtitle`, `height` (400), `width` ("100%"), `status`, `loading`, `error`, `errorBoundary`, `actions` ({ export, fullscreen, copyConfig, dataSummary }), `controls`
143
- **ChartGrid** — `columns` (number|"auto"), `minCellWidth` (300), `gap` (16). `emphasis="primary"` spans two columns.
144
- **ContextLayout** — `context` (ReactNode), `position`, `contextSize` (250)
104
+ ## Server-Side Rendering (`semiotic/server`)
145
105
 
146
- ## Key Patterns
106
+ HOC charts render SVG automatically in server environments. For standalone generation:
147
107
 
148
- ```jsx
149
- // Cross-highlighting dashboard
150
- <CategoryColorProvider categories={["North", "South", "East"]}>
151
- <LinkedCharts>
152
- <ChartGrid columns={2}>
153
- <LineChart data={d} colorBy="region" linkedHover={{ name: "hl", fields: ["region"] }} selection={{ name: "hl" }} emphasis="primary" responsiveWidth />
154
- <BarChart data={d} colorBy="region" linkedHover={{ name: "hl", fields: ["region"] }} selection={{ name: "hl" }} responsiveWidth />
155
- </ChartGrid>
156
- </LinkedCharts>
157
- </CategoryColorProvider>
158
-
159
- // Forecast + anomaly
160
- <LineChart data={ts} xAccessor="time" yAccessor="value"
161
- forecast={{ trainEnd: 60, steps: 15, confidence: 0.95 }}
162
- anomaly={{ threshold: 2 }} />
163
-
164
- // Pre-computed forecast bounds
165
- <LineChart data={ml} xAccessor="time" yAccessor="value"
166
- forecast={{ isTraining: "isTraining", isForecast: "isForecast", isAnomaly: "isAnomaly", upperBounds: "upper", lowerBounds: "lower" }} />
167
-
168
- // Percentile band — layer AreaChart + LineChart
169
- <>
170
- <AreaChart data={d} xAccessor="x" yAccessor="p95" y0Accessor="p5"
171
- showLine={false} areaOpacity={0.3} gradientFill />
172
- <LineChart data={d} xAccessor="x" yAccessor="p50" lineWidth={2} />
173
- </>
174
-
175
- // Streaming sankey with particles
176
- const sankeyRef = useRef()
177
- sankeyRef.current.push({ source: "Web", target: "API", value: 1 })
178
- <StreamNetworkFrame ref={sankeyRef} chartType="sankey"
179
- showParticles particleStyle={{ radius: 2, colorBy: "source" }}
180
- width={600} height={400} />
181
-
182
- // SSR
183
- import { renderOrdinalToStaticSVG } from "semiotic/server"
184
- const svg = renderOrdinalToStaticSVG({ data, categoryAccessor: "cat", valueAccessor: "val", width: 600, height: 400 })
185
- ```
108
+ ```ts
109
+ import { renderChart, renderToImage, renderToAnimatedGif, renderDashboard } from "semiotic/server"
186
110
 
187
- ## Annotations
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 } })
115
+ ```
188
116
 
189
- All HOCs accept `annotations` (array). Coordinates use your data field names. Network/orbit use `nodeId`.
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
- **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`
192
- **Reference lines**: `y-threshold` (`value`, `label`, `color`, `labelPosition`: "left"|"center"|"right", `strokeDasharray`), `x-threshold` (`labelPosition`: "top"|"center"|"bottom"), `band` (`y0`, `y1`, `label`, `fill`)
193
- **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.
194
- **Enclosures**: `enclose` (circle around `coordinates`), `rect-enclose`, `highlight` (`filter` fn or `field`+`value`)
195
- **Statistical** (XY): `trend` (`method`: linear/polynomial/loess), `envelope`, `anomaly-band`, `forecast`
196
- **Streaming anchors**: `"fixed"` (default), `"latest"` (tracks newest datum), `"sticky"` (freezes when evicted)
140
+ ## Annotations
197
141
 
198
- Custom rendering: `frameProps.svgAnnotationRules = (annotation, index, context) => ReactNode | null`. Context has `scales`, `width`, `height`, `data`. Colors inherit from theme (`--semiotic-primary`, `--semiotic-text-secondary`).
142
+ All HOCs accept `annotations` (array). Coordinates use data field names.
199
143
 
200
- ```jsx
201
- <LineChart data={data} xAccessor="time" yAccessor="latency"
202
- annotations={[
203
- { type: "y-threshold", value: 200, label: "SLA limit", color: "#e45050" },
204
- { type: "widget", time: 42, latency: 850, dy: -30, content: <span>Incident</span> },
205
- ]} />
206
- ```
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"`
207
150
 
208
151
  ## Theming
209
152
 
210
- 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`.
211
154
 
212
155
  ```jsx
213
- import { ThemeProvider } from "semiotic"
214
156
  <ThemeProvider theme="tufte"> {/* Named preset */}
215
- <ThemeProvider theme={{ colors: { primary: "#ff6b6b", categorical: [...] } }}> {/* Custom */}
157
+ <ThemeProvider theme={{ mode: "dark", colors: { categorical: [...] } }}> {/* Merge onto dark base */}
216
158
  ```
217
159
 
218
- **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.
219
-
220
- 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`.
221
-
222
- Serialization (`semiotic/themes`): `themeToCSS(theme, selector)`, `themeToTokens(theme)`, `resolveThemePreset(name)`.
223
- Color-blind palette: `import { COLOR_BLIND_SAFE_CATEGORICAL } from "semiotic"` (8-color Wong 2011).
224
- IBM Carbon palette: `import { CARBON_CATEGORICAL_14, CARBON_ALERT } from "semiotic"` (14-color categorical + 4 alert colors).
225
-
226
- **`semiotic/utils`** (~137KB, ~10% of full bundle) — Lightweight entry point for utilities without any chart components:
227
- - **Theme**: `ThemeProvider`, `useTheme`, `LIGHT_THEME`, `DARK_THEME`, `HIGH_CONTRAST_THEME`, `COLOR_BLIND_SAFE_CATEGORICAL`, `CARBON_CATEGORICAL_14`, `CARBON_ALERT`, `themeToCSS`, `themeToTokens`, `resolveThemePreset`, `THEME_PRESETS`
228
- - **Format**: `adaptiveTimeTicks`, `smartTickFormat`
229
- - **Color**: `darkenColor`, `lightenColor`
230
- - **Patterns**: `createHatchPattern`
231
- - **Validation**: `validateProps`, `diagnoseConfig`
232
- - **Serialization**: `toConfig`, `fromConfig`, `toURL`, `fromURL`, `copyConfig`, `configToJSX`, `serializeSelections`, `deserializeSelections`, `exportChart`
233
- - **Vega-Lite**: `fromVegaLite` — convert Vega-Lite specs to Semiotic configs
234
- - **Data structures**: `RingBuffer`, `IncrementalExtent`
235
- - **Tooltip**: `normalizeTooltip`
236
-
237
- Key: `ThemeProvider` sets CSS vars on a wrapper div (no React context). Canvas charts read vars via `getComputedStyle`. `exportChart` inlines computed styles.
238
-
239
- **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.
240
-
241
- **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).
242
-
243
- ## Server-Side Rendering
244
- - HOC charts and Frames render SVG automatically in server environments
245
- - `renderXYToStaticSVG(props)`, `renderOrdinalToStaticSVG(props)`, `renderNetworkToStaticSVG(props)`, `renderGeoToStaticSVG(props)` from `semiotic/server`
246
- - `frameType` is `"xy"|"ordinal"|"network"|"geo"` (NOT component names)
247
- - Geo SSR requires pre-resolved features (synchronous — call `resolveReferenceGeography` first)
248
- - 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)`.
249
163
 
250
164
  ## AI Features
251
- - `onObservation` / `useChartObserver` structured events across charts
252
- - `toConfig`/`fromConfig`/`toURL`/`fromURL`/`copyConfig`/`configToJSX` — serialization
253
- - `DetailsPanel` — click-driven detail panel in `ChartContainer`
254
- - `validateProps(componentName, props)` — prop validation with typo suggestions
255
- - `diagnoseConfig(componentName, props)` — anti-pattern detector (13+ checks)
256
- - `exportChart(containerDiv, { format: "png"|"svg" })` — pass wrapper div, composites canvas+SVG
257
- - `npx semiotic-ai --doctor` — CLI validation
258
-
259
- ## Canvas Pattern Fills
260
-
261
- `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`
262
166
 
263
167
  ## Accessibility
264
168
 
265
- 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>`.
266
-
267
- **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.
268
-
269
- **Focus ring**: Shape-adaptive dashed ring (circle for points, rect for bars, arc for wedges). Color: `--semiotic-focus` CSS var.
270
-
271
- **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.
272
-
273
- **Reduced motion**: `prefers-reduced-motion` auto-detected. Transitions skip to end state, orbit stops, pulse/decay disabled.
274
-
275
- **High contrast**: `forced-colors` / `prefers-contrast: more` auto-detected. ThemeProvider applies `HIGH_CONTRAST_THEME` automatically.
276
-
277
- **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()`.
278
170
 
279
171
  ## Known Pitfalls
280
172
 
281
- - **Tooltip datum shape**: HOC tooltip functions get raw data. Frame `tooltipContent` gets wrapped data — use `d.data`.
282
- - **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.
283
- - **Legend positioning**: "bottom" auto-expands margin ~80px. For narrow charts (<400px), prefer "bottom" or "top".
284
- - **MultiAxisLineChart legend**: Always use `legendPosition="bottom"` (or `"top"`) the right-hand axis occupies the space where a right-side legend would go.
285
- - **Log scale**: Clamps domain min to 1e-6 (log(0) undefined).
286
- - **barPadding**: Pixel value, defaults 40/60. Reduce for small charts.
287
- - **Horizontal bars**: Need wider left margin with long labels: `margin={{ left: 120 }}`.
288
- - **LinkedCharts legends**: `CategoryColorProvider` suppresses child legends. Force with `showLegend={true}`.
289
- - **Push API**: Omit `data` prop entirely. `data={[]}` clears pushed data every render.
290
- - **frameProps style functions**: Bypass HOC color resolution — use `colorBy` prop instead. Frame style functions receive `(datum, categoryName)`, not `(datum, index)`.
291
- - **v2 migration**: `htmlAnnotationRules` `widget` annotations + `svgAnnotationRules`. v2 `summaryStyle` index-based coloring → v3 category-string-based.
292
- - **accessibleTable**: Direct prop on HOCs. Set `accessibleTable={false}` to disable the sr-only data summary.
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.
293
189
 
294
190
  ## Performance
295
191
 
296
- 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
  {
@@ -2778,6 +2778,69 @@
2778
2778
  }
2779
2779
  }
2780
2780
  },
2781
+ {
2782
+ "type": "function",
2783
+ "function": {
2784
+ "name": "GaugeChart",
2785
+ "description": "Single-value gauge with threshold zones, needle indicator, and configurable sweep angle. Built on StreamOrdinalFrame radial projection.",
2786
+ "parameters": {
2787
+ "type": "object",
2788
+ "properties": {
2789
+ "value": {
2790
+ "type": "number",
2791
+ "description": "Current gauge value"
2792
+ },
2793
+ "min": {
2794
+ "type": "number",
2795
+ "default": 0
2796
+ },
2797
+ "max": {
2798
+ "type": "number",
2799
+ "default": 100
2800
+ },
2801
+ "thresholds": {
2802
+ "type": "array",
2803
+ "description": "Array of { value, color, label? } defining threshold zones. Last value should equal max.",
2804
+ "items": { "type": "object" }
2805
+ },
2806
+ "arcWidth": {
2807
+ "type": "number",
2808
+ "description": "Arc thickness as fraction of radius (0-1)",
2809
+ "default": 0.3
2810
+ },
2811
+ "sweep": {
2812
+ "type": "number",
2813
+ "description": "Arc sweep angle in degrees (gap centered at bottom)",
2814
+ "default": 240
2815
+ },
2816
+ "showNeedle": {
2817
+ "type": "boolean",
2818
+ "default": true
2819
+ },
2820
+ "needleColor": {
2821
+ "type": "string"
2822
+ },
2823
+ "valueFormat": {
2824
+ "type": "string",
2825
+ "description": "Format function for center value label"
2826
+ },
2827
+ "showScaleLabels": {
2828
+ "type": "boolean",
2829
+ "default": true
2830
+ },
2831
+ "width": {
2832
+ "type": "number",
2833
+ "default": 300
2834
+ },
2835
+ "height": {
2836
+ "type": "number",
2837
+ "default": 250
2838
+ }
2839
+ },
2840
+ "required": ["value"]
2841
+ }
2842
+ }
2843
+ },
2781
2844
  {
2782
2845
  "type": "function",
2783
2846
  "function": {