semiotic 3.0.0 → 3.1.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 (219) hide show
  1. package/CLAUDE.md +242 -29
  2. package/README.md +101 -66
  3. package/ai/cli.js +34 -21
  4. package/ai/dist/componentRegistry.js +2 -0
  5. package/ai/dist/mcp-server.js +54 -0
  6. package/ai/examples.md +433 -18
  7. package/ai/schema.json +134 -1
  8. package/ai/system-prompt.md +51 -10
  9. package/dist/{ChartGrid.d.ts → components/ChartGrid.d.ts} +9 -7
  10. package/dist/components/Legend.d.ts +9 -0
  11. package/dist/{LinkedCharts.d.ts → components/LinkedCharts.d.ts} +34 -1
  12. package/dist/{Tooltip → components/Tooltip}/Tooltip.d.ts +9 -2
  13. package/dist/components/charts/geo/ChoroplethMap.d.ts +53 -0
  14. package/dist/components/charts/geo/DistanceCartogram.d.ts +90 -0
  15. package/dist/components/charts/geo/FlowMap.d.ts +83 -0
  16. package/dist/components/charts/geo/ProportionalSymbolMap.d.ts +67 -0
  17. package/dist/components/charts/geo/index.d.ts +8 -0
  18. package/dist/{charts → components/charts}/index.d.ts +4 -0
  19. package/dist/{charts → components/charts}/network/ChordDiagram.d.ts +8 -5
  20. package/dist/{charts → components/charts}/network/CirclePack.d.ts +4 -2
  21. package/dist/{charts → components/charts}/network/ForceDirectedGraph.d.ts +10 -6
  22. package/dist/components/charts/network/OrbitDiagram.d.ts +79 -0
  23. package/dist/{charts → components/charts}/network/SankeyDiagram.d.ts +8 -5
  24. package/dist/{charts → components/charts}/network/TreeDiagram.d.ts +4 -2
  25. package/dist/{charts → components/charts}/network/Treemap.d.ts +4 -2
  26. package/dist/{charts → components/charts}/ordinal/BarChart.d.ts +9 -5
  27. package/dist/{charts → components/charts}/ordinal/BoxPlot.d.ts +9 -5
  28. package/dist/{charts → components/charts}/ordinal/DonutChart.d.ts +9 -5
  29. package/dist/{charts → components/charts}/ordinal/DotPlot.d.ts +9 -5
  30. package/dist/{charts → components/charts}/ordinal/GroupedBarChart.d.ts +9 -5
  31. package/dist/{charts → components/charts}/ordinal/Histogram.d.ts +8 -5
  32. package/dist/{charts → components/charts}/ordinal/PieChart.d.ts +9 -5
  33. package/dist/{charts → components/charts}/ordinal/RidgelinePlot.d.ts +2 -0
  34. package/dist/{charts → components/charts}/ordinal/StackedBarChart.d.ts +9 -5
  35. package/dist/{charts → components/charts}/ordinal/SwarmPlot.d.ts +9 -5
  36. package/dist/{charts → components/charts}/ordinal/ViolinPlot.d.ts +8 -5
  37. package/dist/{charts → components/charts}/realtime/RealtimeHeatmap.d.ts +24 -6
  38. package/dist/{charts → components/charts}/realtime/RealtimeHistogram.d.ts +28 -7
  39. package/dist/{charts → components/charts}/realtime/RealtimeLineChart.d.ts +23 -5
  40. package/dist/{charts → components/charts}/realtime/RealtimeSwarmChart.d.ts +24 -6
  41. package/dist/{charts → components/charts}/realtime/RealtimeWaterfallChart.d.ts +23 -5
  42. package/dist/{charts → components/charts}/shared/colorUtils.d.ts +5 -0
  43. package/dist/components/charts/shared/diagnoseConfig.d.ts +23 -0
  44. package/dist/{charts → components/charts}/shared/hooks.d.ts +36 -2
  45. package/dist/{charts → components/charts}/shared/legendUtils.d.ts +2 -3
  46. package/dist/{charts → components/charts}/shared/statisticalOverlays.d.ts +1 -2
  47. package/dist/components/charts/shared/statisticalOverlaysLazy.d.ts +10 -0
  48. package/dist/components/charts/shared/stringDistance.d.ts +11 -0
  49. package/dist/{charts → components/charts}/shared/tooltipUtils.d.ts +2 -2
  50. package/dist/{charts → components/charts}/shared/types.d.ts +16 -4
  51. package/dist/components/charts/shared/useChartSetup.d.ts +112 -0
  52. package/dist/components/charts/shared/useStreamingLegend.d.ts +65 -0
  53. package/dist/{charts → components/charts}/shared/withChartWrapper.d.ts +10 -0
  54. package/dist/{charts → components/charts}/xy/AreaChart.d.ts +18 -5
  55. package/dist/{charts → components/charts}/xy/BubbleChart.d.ts +18 -5
  56. package/dist/{charts → components/charts}/xy/ConnectedScatterplot.d.ts +10 -6
  57. package/dist/{charts → components/charts}/xy/Heatmap.d.ts +24 -5
  58. package/dist/{charts → components/charts}/xy/LineChart.d.ts +47 -5
  59. package/dist/{charts → components/charts}/xy/MinimapChart.d.ts +3 -0
  60. package/dist/components/charts/xy/QuadrantChart.d.ts +120 -0
  61. package/dist/{charts → components/charts}/xy/Scatterplot.d.ts +11 -5
  62. package/dist/{charts → components/charts}/xy/StackedAreaChart.d.ts +18 -5
  63. package/dist/{export → components/export}/exportChart.d.ts +6 -1
  64. package/dist/components/geo/mergeData.d.ts +18 -0
  65. package/dist/components/geo/referenceGeography.d.ts +10 -0
  66. package/dist/components/geo/useReferenceAreas.d.ts +13 -0
  67. package/dist/{realtime → components/realtime}/RingBuffer.d.ts +1 -0
  68. package/dist/{realtime → components/realtime}/types.d.ts +17 -0
  69. package/dist/components/semiotic-ai.d.ts +61 -0
  70. package/dist/components/semiotic-data.d.ts +8 -0
  71. package/dist/components/semiotic-geo.d.ts +16 -0
  72. package/dist/components/semiotic-network.d.ts +14 -0
  73. package/dist/components/semiotic-ordinal.d.ts +18 -0
  74. package/dist/components/semiotic-realtime.d.ts +22 -0
  75. package/dist/components/semiotic-server.d.ts +1 -0
  76. package/dist/components/semiotic-xy.d.ts +17 -0
  77. package/dist/components/semiotic.d.ts +57 -0
  78. package/dist/{server → components/server}/renderToStaticSVG.d.ts +11 -2
  79. package/dist/components/stream/AccessibleDataTable.d.ts +50 -0
  80. package/dist/{stream → components/stream}/CanvasHitTester.d.ts +8 -2
  81. package/dist/components/stream/DataSourceAdapter.d.ts +64 -0
  82. package/dist/components/stream/GeoCanvasHitTester.d.ts +19 -0
  83. package/dist/components/stream/GeoParticlePool.d.ts +46 -0
  84. package/dist/components/stream/GeoPipelineStore.d.ts +81 -0
  85. package/dist/components/stream/GeoTileRenderer.d.ts +31 -0
  86. package/dist/{stream → components/stream}/NetworkPipelineStore.d.ts +16 -4
  87. package/dist/{stream → components/stream}/NetworkSVGOverlay.d.ts +24 -1
  88. package/dist/{stream → components/stream}/OrdinalPipelineStore.d.ts +8 -4
  89. package/dist/{stream → components/stream}/OrdinalSVGOverlay.d.ts +31 -1
  90. package/dist/{stream → components/stream}/PipelineStore.d.ts +64 -5
  91. package/dist/components/stream/SVGOverlay.d.ts +98 -0
  92. package/dist/{stream → components/stream}/SceneGraph.d.ts +7 -3
  93. package/dist/components/stream/SceneToSVG.d.ts +22 -0
  94. package/dist/components/stream/StreamGeoFrame.d.ts +4 -0
  95. package/dist/{stream → components/stream}/accessorUtils.d.ts +1 -0
  96. package/dist/components/stream/canvasSetup.d.ts +26 -0
  97. package/dist/components/stream/geoTypes.d.ts +186 -0
  98. package/dist/components/stream/hitTestUtils.d.ts +23 -0
  99. package/dist/components/stream/layouts/forceLayoutPlugin.d.ts +2 -0
  100. package/dist/{stream → components/stream}/layouts/index.d.ts +2 -1
  101. package/dist/components/stream/layouts/orbitLayoutPlugin.d.ts +2 -0
  102. package/dist/components/stream/legendRenderer.d.ts +33 -0
  103. package/dist/{stream → components/stream}/networkTypes.d.ts +59 -3
  104. package/dist/{stream → components/stream}/ordinalTypes.d.ts +26 -10
  105. package/dist/components/stream/pipelineTransitionUtils.d.ts +42 -0
  106. package/dist/components/stream/renderers/areaCanvasRenderer.d.ts +2 -0
  107. package/dist/components/stream/renderers/geoCanvasRenderer.d.ts +9 -0
  108. package/dist/{stream → components/stream}/renderers/heatmapCanvasRenderer.d.ts +2 -1
  109. package/dist/{stream → components/stream}/renderers/lineCanvasRenderer.d.ts +1 -0
  110. package/dist/components/stream/renderers/renderPulse.d.ts +50 -0
  111. package/dist/{stream → components/stream}/types.d.ts +89 -3
  112. package/dist/components/stream/useStalenessCheck.d.ts +16 -0
  113. package/dist/components/types/legendTypes.d.ts +49 -0
  114. package/dist/geo.min.js +1 -0
  115. package/dist/geo.module.min.js +1 -0
  116. package/dist/network.min.js +1 -1
  117. package/dist/network.module.min.js +1 -1
  118. package/dist/ordinal.min.js +1 -1
  119. package/dist/ordinal.module.min.js +1 -1
  120. package/dist/realtime.min.js +1 -1
  121. package/dist/realtime.module.min.js +1 -1
  122. package/dist/semiotic-ai.d.ts +3 -0
  123. package/dist/semiotic-ai.min.js +1 -1
  124. package/dist/semiotic-ai.module.min.js +1 -1
  125. package/dist/semiotic-data.d.ts +1 -0
  126. package/dist/semiotic-data.min.js +1 -1
  127. package/dist/semiotic-data.module.min.js +1 -1
  128. package/dist/semiotic-geo.d.ts +16 -0
  129. package/dist/semiotic-network.d.ts +1 -0
  130. package/dist/semiotic-ordinal.d.ts +1 -0
  131. package/dist/semiotic-server.d.ts +1 -1
  132. package/dist/semiotic-xy.d.ts +1 -0
  133. package/dist/semiotic.d.ts +4 -4
  134. package/dist/semiotic.min.js +1 -1
  135. package/dist/semiotic.module.min.js +1 -1
  136. package/dist/server.min.js +1 -1
  137. package/dist/server.module.min.js +1 -1
  138. package/dist/test-utils/canvasMock.d.ts +23 -0
  139. package/dist/test-utils/frameMock.d.ts +78 -0
  140. package/dist/xy.min.js +1 -1
  141. package/dist/xy.module.min.js +1 -1
  142. package/package.json +34 -20
  143. package/dist/Legend.d.ts +0 -3
  144. package/dist/stream/DataSourceAdapter.d.ts +0 -35
  145. package/dist/stream/SVGOverlay.d.ts +0 -56
  146. package/dist/stream/layouts/forceLayoutPlugin.d.ts +0 -9
  147. package/dist/stream/renderers/areaCanvasRenderer.d.ts +0 -7
  148. package/dist/types/legendTypes.d.ts +0 -20
  149. /package/dist/{Annotation.d.ts → components/Annotation.d.ts} +0 -0
  150. /package/dist/{CategoryColors.d.ts → components/CategoryColors.d.ts} +0 -0
  151. /package/dist/{ChartContainer.d.ts → components/ChartContainer.d.ts} +0 -0
  152. /package/dist/{ChartErrorBoundary.d.ts → components/ChartErrorBoundary.d.ts} +0 -0
  153. /package/dist/{ContextLayout.d.ts → components/ContextLayout.d.ts} +0 -0
  154. /package/dist/{DetailsPanel.d.ts → components/DetailsPanel.d.ts} +0 -0
  155. /package/dist/{ThemeProvider.d.ts → components/ThemeProvider.d.ts} +0 -0
  156. /package/dist/{charts → components/charts}/shared/ChartError.d.ts +0 -0
  157. /package/dist/{charts → components/charts}/shared/annotationRules.d.ts +0 -0
  158. /package/dist/{charts → components/charts}/shared/formatUtils.d.ts +0 -0
  159. /package/dist/{charts → components/charts}/shared/loess.d.ts +0 -0
  160. /package/dist/{charts → components/charts}/shared/networkUtils.d.ts +0 -0
  161. /package/dist/{charts → components/charts}/shared/selectionUtils.d.ts +0 -0
  162. /package/dist/{charts → components/charts}/shared/validateChartData.d.ts +0 -0
  163. /package/dist/{charts → components/charts}/shared/validateProps.d.ts +0 -0
  164. /package/dist/{charts → components/charts}/xy/ScatterplotMatrix.d.ts +0 -0
  165. /package/dist/{data → components/data}/fromVegaLite.d.ts +0 -0
  166. /package/dist/{data → components/data}/transforms.d.ts +0 -0
  167. /package/dist/{export → components/export}/chartConfig.d.ts +0 -0
  168. /package/dist/{export → components/export}/selectionSerializer.d.ts +0 -0
  169. /package/dist/{geometry → components/geometry}/sankeyLinks.d.ts +0 -0
  170. /package/dist/{realtime → components/realtime}/BinAccumulator.d.ts +0 -0
  171. /package/dist/{realtime → components/realtime}/IncrementalExtent.d.ts +0 -0
  172. /package/dist/{realtime → components/realtime}/renderers/types.d.ts +0 -0
  173. /package/dist/{realtime → components/realtime}/renderers/waterfallRenderer.d.ts +0 -0
  174. /package/dist/{store → components/store}/ObservationStore.d.ts +0 -0
  175. /package/dist/{store → components/store}/SelectionStore.d.ts +0 -0
  176. /package/dist/{store → components/store}/ThemeStore.d.ts +0 -0
  177. /package/dist/{store → components/store}/TooltipStore.d.ts +0 -0
  178. /package/dist/{store → components/store}/createStore.d.ts +0 -0
  179. /package/dist/{store → components/store}/useObservation.d.ts +0 -0
  180. /package/dist/{store → components/store}/useSelection.d.ts +0 -0
  181. /package/dist/{stream → components/stream}/MarginalGraphics.d.ts +0 -0
  182. /package/dist/{stream → components/stream}/NetworkCanvasHitTester.d.ts +0 -0
  183. /package/dist/{stream → components/stream}/OrdinalCanvasHitTester.d.ts +0 -0
  184. /package/dist/{stream → components/stream}/ParticlePool.d.ts +0 -0
  185. /package/dist/{stream → components/stream}/StreamNetworkFrame.d.ts +0 -0
  186. /package/dist/{stream → components/stream}/StreamOrdinalFrame.d.ts +0 -0
  187. /package/dist/{stream → components/stream}/StreamXYFrame.d.ts +0 -0
  188. /package/dist/{stream → components/stream}/keyboardNav.d.ts +0 -0
  189. /package/dist/{stream → components/stream}/layouts/chordLayoutPlugin.d.ts +0 -0
  190. /package/dist/{stream → components/stream}/layouts/hierarchyLayoutPlugin.d.ts +0 -0
  191. /package/dist/{stream → components/stream}/layouts/sankeyLayoutPlugin.d.ts +0 -0
  192. /package/dist/{stream → components/stream}/ordinalSceneBuilders/barScene.d.ts +0 -0
  193. /package/dist/{stream → components/stream}/ordinalSceneBuilders/connectorScene.d.ts +0 -0
  194. /package/dist/{stream → components/stream}/ordinalSceneBuilders/pieScene.d.ts +0 -0
  195. /package/dist/{stream → components/stream}/ordinalSceneBuilders/pointScene.d.ts +0 -0
  196. /package/dist/{stream → components/stream}/ordinalSceneBuilders/statisticalScene.d.ts +0 -0
  197. /package/dist/{stream → components/stream}/ordinalSceneBuilders/timelineScene.d.ts +0 -0
  198. /package/dist/{stream → components/stream}/ordinalSceneBuilders/types.d.ts +0 -0
  199. /package/dist/{stream → components/stream}/renderers/barCanvasRenderer.d.ts +0 -0
  200. /package/dist/{stream → components/stream}/renderers/boxplotCanvasRenderer.d.ts +0 -0
  201. /package/dist/{stream → components/stream}/renderers/candlestickCanvasRenderer.d.ts +0 -0
  202. /package/dist/{stream → components/stream}/renderers/connectorCanvasRenderer.d.ts +0 -0
  203. /package/dist/{stream → components/stream}/renderers/networkArcRenderer.d.ts +0 -0
  204. /package/dist/{stream → components/stream}/renderers/networkCircleRenderer.d.ts +0 -0
  205. /package/dist/{stream → components/stream}/renderers/networkEdgeRenderer.d.ts +0 -0
  206. /package/dist/{stream → components/stream}/renderers/networkParticleRenderer.d.ts +0 -0
  207. /package/dist/{stream → components/stream}/renderers/networkRectRenderer.d.ts +0 -0
  208. /package/dist/{stream → components/stream}/renderers/pointCanvasRenderer.d.ts +0 -0
  209. /package/dist/{stream → components/stream}/renderers/swarmCanvasRenderer.d.ts +0 -0
  210. /package/dist/{stream → components/stream}/renderers/types.d.ts +0 -0
  211. /package/dist/{stream → components/stream}/renderers/violinCanvasRenderer.d.ts +0 -0
  212. /package/dist/{stream → components/stream}/renderers/waterfallCanvasRenderer.d.ts +0 -0
  213. /package/dist/{stream → components/stream}/renderers/wedgeCanvasRenderer.d.ts +0 -0
  214. /package/dist/{stream → components/stream}/useResponsiveSize.d.ts +0 -0
  215. /package/dist/{types → components/types}/annotationTypes.d.ts +0 -0
  216. /package/dist/{types → components/types}/generalTypes.d.ts +0 -0
  217. /package/dist/{types → components/types}/interactionTypes.d.ts +0 -0
  218. /package/dist/{types → components/types}/networkTypes.d.ts +0 -0
  219. /package/dist/{types → components/types}/ordinalTypes.d.ts +0 -0
package/CLAUDE.md CHANGED
@@ -2,25 +2,39 @@
2
2
 
3
3
  ## Quick Start
4
4
  - Install: `npm install semiotic`
5
- - Import: `semiotic`, `semiotic/xy`, `semiotic/ordinal`, `semiotic/network`, `semiotic/realtime`, `semiotic/ai`, `semiotic/data`
5
+ - Import: `semiotic`, `semiotic/xy`, `semiotic/ordinal`, `semiotic/network`, `semiotic/geo`, `semiotic/realtime`, `semiotic/ai`, `semiotic/data`, `semiotic/server`
6
6
  - CLI: `npx semiotic-ai [--schema|--compact|--examples|--doctor]`
7
7
  - MCP: `npx semiotic-mcp`
8
8
  - Every HOC has a built-in error boundary (never blanks the page) and dev-mode validation warnings
9
9
 
10
10
  ## Architecture
11
11
  - **HOC Charts**: Simple props, sensible defaults. **Stream Frames**: Full control.
12
+ - **Always use HOC charts** (`ForceDirectedGraph`, `SankeyDiagram`, `LineChart`, `RealtimeLineChart`, `ChoroplethMap`, etc.) unless you need sophisticated control they don't expose. Stream Frames (`StreamNetworkFrame`, `StreamXYFrame`, `StreamOrdinalFrame`, `StreamGeoFrame`) are low-level escape hatches — they accept raw `RealtimeNode`/`RealtimeEdge` wrappers in callbacks, not your data objects directly.
12
13
  - Every HOC accepts `frameProps` to pass through. TypeScript `strict: true`.
13
14
 
14
15
  ## Common Props (all HOCs)
15
- `title`, `width` (600), `height` (400), `responsiveWidth`, `responsiveHeight`, `margin`, `className`, `enableHover` (true), `tooltip`, `showLegend`, `showGrid` (false), `frameProps`, `onObservation`, `chartId`
16
+ `title`, `width` (600), `height` (400), `responsiveWidth`, `responsiveHeight`, `margin`, `className`, `enableHover` (true), `tooltip` (boolean | `(datum) => ReactNode` | config object), `showLegend`, `showGrid` (false), `frameProps`, `onObservation` (callback, see below), `chartId`, `loading` (false), `emptyContent`, `legendInteraction` ("none"|"highlight"|"isolate"), `legendPosition` ("right"|"left"|"top"|"bottom", default "right"), `emphasis` ("primary"|"secondary")
17
+
18
+ ### tooltip
19
+ `tooltip` accepts: `true` (default tooltip), `false` (disabled), a **function** `(datum: Record<string, any>) => ReactNode`, or a config `{ fields?: string[], title?: accessor, format?: fn, style?: CSSProperties }`. The function form receives your raw data object directly.
20
+
21
+ ### onObservation
22
+ `onObservation` receives a `ChartObservation` with `type` and event-specific fields:
23
+ - **hover**: `{ type: "hover", datum: <your data>, x, y, timestamp, chartType, chartId }`
24
+ - **hover-end**: `{ type: "hover-end", timestamp, chartType, chartId }`
25
+ - **click**: `{ type: "click", datum: <your data>, x, y, timestamp, chartType, chartId }`
26
+ - **brush**: `{ type: "brush", extent: { x: [min, max], y: [min, max] }, timestamp, chartType }`
27
+ - **selection**: `{ type: "selection", selection: { name, fields }, timestamp, chartType }`
28
+
29
+ The `datum` field contains your original data object (not a wrapper).
16
30
 
17
31
  ## XY Charts (`semiotic/xy`)
18
32
 
19
- **LineChart** — `data`, `xAccessor` ("x"), `yAccessor` ("y"), `lineBy`, `lineDataAccessor` ("coordinates"), `colorBy`, `colorScheme`, `curve`, `lineWidth` (2), `showPoints`, `pointRadius` (3), `fillArea`, `areaOpacity` (0.3), `anomaly` (AnomalyConfig), `forecast` (ForecastConfig)
33
+ **LineChart** — `data`, `xAccessor` ("x"), `yAccessor` ("y"), `lineBy`, `lineDataAccessor` ("coordinates"), `colorBy`, `colorScheme`, `curve`, `lineWidth` (2), `showPoints`, `pointRadius` (3), `fillArea`, `areaOpacity` (0.3), `anomaly` (AnomalyConfig), `forecast` (ForecastConfig), `directLabel` (boolean|{position,fontSize}), `gapStrategy` ("break"|"interpolate"|"zero"), `xScaleType` ("linear"|"log"), `yScaleType` ("linear"|"log")
20
34
 
21
35
  **AreaChart** — LineChart props + `areaBy`, `y0Accessor` (band/ribbon), `gradientFill` (boolean|{topOpacity,bottomOpacity}), `areaOpacity` (0.7), `showLine` (true)
22
36
 
23
- **StackedAreaChart** — AreaChart + `normalize` (false)
37
+ **StackedAreaChart** — flat array data + `areaBy` (required, groups into stacked areas), `colorBy`, `normalize` (false). Do NOT use `lineBy` or `lineDataAccessor` — those are LineChart props.
24
38
 
25
39
  **Scatterplot** — `data`, `xAccessor`, `yAccessor`, `colorBy`, `sizeBy`, `sizeRange`, `pointRadius` (5), `pointOpacity` (0.8), `marginalGraphics`
26
40
 
@@ -28,64 +42,188 @@
28
42
 
29
43
  **ConnectedScatterplot** — `data`, `xAccessor`, `yAccessor`, `orderAccessor` (number|Date field for sequencing), `pointRadius` (4). Viridis colored start→end, line width = point radius, white halo under lines when <100 points.
30
44
 
31
- **Heatmap** — `data`, `xAccessor`, `yAccessor`, `valueAccessor`, `colorScheme`, `showValues`, `cellBorderColor`
45
+ **QuadrantChart** — Scatterplot divided into four labeled, colored quadrants. `data`, `xAccessor`, `yAccessor`, `quadrants` (required: `{ topRight, topLeft, bottomRight, bottomLeft }` each with `label`, `color`, optional `opacity`), `xCenter` (vertical center line in data units), `yCenter` (horizontal center line), `centerlineStyle` (`{ stroke, strokeWidth, strokeDasharray }`), `showQuadrantLabels` (true), `quadrantLabelSize` (12), `colorBy`, `sizeBy`, `sizeRange`, `pointRadius` (5), `pointOpacity` (0.8). Supports push API. Quadrant fills and labels drawn via `canvasPreRenderers`.
46
+
47
+ **Heatmap** — `data`, `xAccessor`, `yAccessor`, `valueAccessor`, `colorScheme` ("blues"|"reds"|"greens"|"viridis" or custom), `showValues`, `cellBorderColor`. Accessors can be string field names (including string/categorical fields) or functions.
32
48
 
33
49
  ## Ordinal Charts (`semiotic/ordinal`)
34
50
 
35
- **BarChart** — `data`, `categoryAccessor`, `valueAccessor`, `orientation`, `colorBy`, `sort`, `barPadding`
36
- **StackedBarChart** — + `stackBy` (required), `normalize`
37
- **GroupedBarChart** — + `groupBy` (required)
51
+ **BarChart** — `data`, `categoryAccessor`, `valueAccessor`, `orientation`, `colorBy`, `sort`, `barPadding` (40)
52
+ **StackedBarChart** — + `stackBy` (required), `normalize`, `barPadding` (40)
53
+ **GroupedBarChart** — + `groupBy` (required), `barPadding` (60)
38
54
  **SwarmPlot** — `data`, `categoryAccessor`, `valueAccessor`, `colorBy`, `sizeBy`, `pointRadius`, `pointOpacity`
39
55
  **BoxPlot** — + `showOutliers`, `outlierRadius`
40
- **Histogram** — + `bins` (25), `relative`. Always horizontal.
56
+ **Histogram** — + `bins` (25), `relative`. Always horizontal. `categoryAccessor` is optional (defaults to `"category"`) — for a single-group histogram, either omit it or ensure your data has a `category` field with a single value.
41
57
  **ViolinPlot** — + `bins`, `curve`, `showIQR`
42
58
  **DotPlot** — + `sort` (true), `dotRadius`, `showGrid` default true
43
59
  **PieChart** — `data`, `categoryAccessor`, `valueAccessor`, `colorBy`, `startAngle`, `slicePadding`
44
- **DonutChart** — PieChart + `innerRadius` (60), `centerContent`
60
+ **DonutChart** — PieChart + `innerRadius` (60), `centerContent` (ReactNode — any React element, e.g. `<div>50%</div>`)
45
61
 
46
62
  ## Network Charts (`semiotic/network`)
47
63
 
48
- **ForceDirectedGraph** — `nodes`, `edges`, `nodeIDAccessor`, `sourceAccessor`, `targetAccessor`, `colorBy`, `nodeSize`, `edgeWidth`, `iterations`, `showLabels`
64
+ **ForceDirectedGraph** — `nodes`, `edges`, `nodeIDAccessor`, `sourceAccessor`, `targetAccessor`, `colorBy`, `colorScheme`, `nodeSize` (number|string|fn), `nodeSizeRange`, `edgeWidth`, `edgeColor`, `edgeOpacity`, `iterations` (300), `forceStrength` (0.1), `showLabels`, `nodeLabel`, `tooltip`, `showLegend`, `legendInteraction`
49
65
  **SankeyDiagram** — `edges`, `nodes`, `valueAccessor`, `edgeColorBy`, `orientation`, `nodeAlign`, `nodeWidth`, `showLabels`, `edgeOpacity`
50
66
  **ChordDiagram** — `edges`, `nodes`, `valueAccessor`, `edgeColorBy`, `padAngle`, `groupWidth`, `showLabels`
51
67
  **TreeDiagram** — `data` (root), `layout`, `orientation`, `childrenAccessor`, `colorBy`, `colorByDepth`, `edgeStyle`
52
68
  **Treemap** — `data` (root), `childrenAccessor`, `valueAccessor`, `colorBy`, `colorByDepth`, `showLabels`, `labelMode`
53
69
  **CirclePack** — `data` (root), `childrenAccessor`, `valueAccessor`, `colorBy`, `colorByDepth`, `circleOpacity`
70
+ **OrbitDiagram** — animated radial/orbital hierarchy. Use this (not TreeDiagram) when you want animated orbiting nodes. `data` (root), `childrenAccessor`, `nodeIdAccessor`, `orbitMode` ("flat"|"solar"|"atomic"|number[]), `speed` (0.25), `revolution`, `eccentricity`, `orbitSize`, `nodeRadius`, `showRings`, `showLabels`, `animated` (true), `colorBy`, `colorByDepth`, `annotations` (widget annotations anchor by nodeId). For static radial trees, use `TreeDiagram layout="radial"` instead.
71
+
72
+ ## Geo Charts (`semiotic/geo`)
73
+
74
+ Geographic visualization with d3-geo projections. Canvas-rendered via `StreamGeoFrame`. Import from `semiotic/geo` to avoid adding d3-geo to non-geo bundles.
75
+
76
+ **ChoroplethMap** — `areas` (GeoJSON Feature[] or reference string like "world-110m"), `valueAccessor`, `colorScheme` ("blues"|"reds"|"greens"|"viridis"), `areaOpacity` (1), `projection` ("equalEarth"), `graticule`, `tooltip`, `showLegend`
77
+ **ProportionalSymbolMap** — `points`, `xAccessor` ("lon"), `yAccessor` ("lat"), `sizeBy`, `sizeRange` ([3,30]), `colorBy`, `areas` (optional background), `projection`
78
+ **FlowMap** — `flows` ({source, target, value}), `nodes`, `xAccessor`, `yAccessor`, `nodeIdAccessor` ("id"), `valueAccessor` ("value"), `edgeColorBy`, `edgeOpacity` (0.6), `edgeWidthRange` ([1,8]), `edgeLinecap` ("round"), `lineType` ("geo"|"line"), `areas` (optional background), `showParticles`, `particleStyle` ({ radius, color, opacity, speedMultiplier, maxPerLine, spawnRate }). Particle `color` accepts a string, `"source"` (inherit line stroke), or `(datum) => string`.
79
+ **DistanceCartogram** — `points`, `center` (id of center node), `costAccessor`, `strength` (0-1), `lineMode` ("straight"|"fractional"), `nodeIdAccessor` ("id"), `lines`, `projection`, `showRings` (true|false|number[]), `ringStyle` ({ stroke, strokeWidth, ... }), `showNorth` (true), `costLabel` (string for ring labels), `transition` (ms for smooth animation), `pointRadius`
80
+
81
+ All geo HOCs support: `selection`, `linkedHover`, `onObservation`, `showLegend`, `legendInteraction`, `tooltip`, `loading`, `emptyContent`, `frameProps`, `fitPadding` (0–1 fraction, insets auto-fit projection from edges), `zoomable` (defaults true with tileURL, false otherwise), `zoomExtent`, `onZoom`, `dragRotate`, `graticule`, `tileURL`, `tileAttribution`, `tileCacheSize`
82
+
83
+ **Zoom/Pan**: All geo charts accept `zoomable` (boolean), `zoomExtent` ([minZoom, maxZoom], default [1, 8]), and `onZoom` (callback with `{ projection, zoom }`). Re-renders projection directly on every zoom tick (no CSS transform). Imperative API: `ref.current.getZoom()`, `ref.current.resetZoom()`.
84
+
85
+ **Geo Particles**: `FlowMap` and `StreamGeoFrame` support `showParticles` (boolean) and `particleStyle` to animate dots flowing along line paths. Uses `GeoParticlePool` — an object-pool polyline particle system. Particle `color` accepts: `"source"` (inherit line stroke), a CSS string, or `(datum) => string` for per-line color.
86
+
87
+ **Drag Rotate (Globe Spinning)**: `dragRotate` (boolean) — when true, drag gestures rotate the projection (globe spinning) instead of panning. **Defaults to true for orthographic projection.** Scroll-wheel zoom still works normally. Explicitly set `dragRotate={false}` on orthographic to get standard pan behavior, or `dragRotate={true}` on other projections to enable rotation. Latitude rotation is clamped to [-90, 90] to prevent flipping.
88
+
89
+ **Tile Maps**: All geo charts accept `tileURL` (string template like `"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"` or `(z, x, y, dpr) => string`), `tileAttribution` (e.g., `"© OpenStreetMap contributors"`), `tileCacheSize` (default 256). Tiles render on a background canvas behind data layers. **Mercator projection only** — a dev warning is emitted for non-Mercator projections. Tiles update on zoom/pan. Retina support via `{r}` placeholder or DPR parameter. **Production**: OpenStreetMap tiles are for development/demo only. For production, use a commercial tile provider (Mapbox, MapTiler, Stadia Maps) with your own API key passed via environment variable (never hard-code keys in client code). Example: `tileURL={\`https://api.mapbox.com/styles/v1/mapbox/light-v11/tiles/{z}/{x}/{y}?access_token=\${process.env.MAPBOX_TOKEN}\`}`.
90
+
91
+ **StreamGeoFrame** — low-level frame with full control. Props: `projection`, `areas`, `points`, `lines`, `xAccessor`, `yAccessor`, `areaStyle`, `pointStyle`, `lineStyle`, `graticule`, `projectionTransform` (distance cartogram config), `projectionExtent`, `enableHover`, `tooltipContent`, `zoomable`, `zoomExtent`, `onZoom`, `tileURL`, `tileAttribution`, `tileCacheSize`, `decay`, `pulse`, `transition`. Push API: `ref.current.push(datum)`, `ref.current.pushMany(data)`, `ref.current.clear()`.
92
+
93
+ **Reference geography**: `resolveReferenceGeography("world-110m")` returns GeoJSON features from Natural Earth data (world-atlas). Supported: `"world-110m"`, `"world-50m"`, `"land-110m"`, `"land-50m"`. All geo HOCs accept `areas` as `GeoJSON.Feature[]` or a reference string.
94
+
95
+ **mergeData(features, data, { featureKey, dataKey })** — join external data into GeoJSON features by key field. Supports nested paths (e.g., `"properties.iso_a3"`). World-atlas uses ISO 3166-1 numeric codes as the `id` field. Also available from `semiotic/data` as a general join-by-key utility.
96
+
97
+ ```jsx
98
+ // World choropleth with reference geography + data joining
99
+ import { ChoroplethMap, resolveReferenceGeography, mergeData } from "semiotic/geo"
100
+ const world = await resolveReferenceGeography("world-110m")
101
+ const areas = mergeData(world, gdpData, { featureKey: "id", dataKey: "id" })
102
+ <ChoroplethMap areas={areas} valueAccessor="gdpPerCapita" colorScheme="viridis"
103
+ projection="equalEarth" zoomable tooltip />
104
+
105
+ // Distance cartogram (ORBIS-style) with concentric rings overlay
106
+ import { DistanceCartogram } from "semiotic/geo"
107
+ <DistanceCartogram
108
+ points={cities} center="rome" costAccessor="travelDays"
109
+ strength={0.8} lines={routes} showLegend zoomable
110
+ showRings costLabel="days" showNorth
111
+ ringStyle={{ stroke: "#999", strokeWidth: 0.5 }}
112
+ />
113
+
114
+ // Tile map basemap with proportional symbols
115
+ <ProportionalSymbolMap
116
+ points={earthquakes} xAccessor="lon" yAccessor="lat"
117
+ sizeBy="magnitude" sizeRange={[2, 20]}
118
+ projection="mercator" zoomable
119
+ tileURL="https://tile.openstreetmap.org/{z}/{x}/{y}.png"
120
+ tileAttribution="© OpenStreetMap contributors"
121
+ />
122
+
123
+ // Streaming geo points with zoom
124
+ const geoRef = useRef()
125
+ geoRef.current.push({ lon: -122.4, lat: 37.8, value: 42 })
126
+ <StreamGeoFrame ref={geoRef} projection="mercator" xAccessor="lon" yAccessor="lat"
127
+ runtimeMode="streaming" decay={{ type: "linear", minOpacity: 0.1 }}
128
+ zoomable zoomExtent={[1, 12]} onZoom={({ zoom }) => console.log(zoom)} />
129
+ ```
54
130
 
55
131
  ## Realtime Charts (`semiotic/realtime`)
56
132
 
57
133
  Push API: `chartRef.current.push({ time, value })`
58
134
 
59
- **RealtimeLineChart** `size`, `timeAccessor`, `valueAccessor`, `windowSize` (200), `windowMode`, `stroke`, `strokeWidth`
60
- **RealtimeHistogram** — + `binSize` (required), `categoryAccessor`, `colors`
61
- **RealtimeSwarmChart** + `categoryAccessor`, `radius`, `opacity`
62
- **RealtimeWaterfallChart** — + `positiveColor`, `negativeColor`
63
- **RealtimeHeatmap** — + `heatmapXBins`, `heatmapYBins`, `aggregation`
64
- **Streaming Sankey** — `StreamNetworkFrame` with `chartType="sankey"`, `showParticles`, `particleStyle`, `tensionConfig`, `thresholds`
135
+ **IMPORTANT**: All pushed data must include a time field (default: `"time"`). If your data uses a different field name, set `timeAccessor` explicitly. Without a valid time field, charts render blank with no error.
136
+
137
+ Sizing: all Realtime HOCs accept both `size={[600, 400]}` (tuple) and `width={600} height={400}`. Either works.
138
+
139
+ **RealtimeLineChart** — `size`|`width`+`height`, **`timeAccessor`** ("time"), **`valueAccessor`** ("value"), `windowSize` (200), `windowMode`, `stroke`, `strokeWidth`
140
+ **RealtimeHistogram** — **`binSize`** (required), **`timeAccessor`** ("time"), **`valueAccessor`** ("value"), `categoryAccessor`, `colors`. Time field is required even though this shows a distribution — it's used for windowing.
141
+ **RealtimeSwarmChart** — **`timeAccessor`** ("time"), **`valueAccessor`** ("value"), `categoryAccessor`, `radius`, `opacity`
142
+ **RealtimeWaterfallChart** — **`timeAccessor`** ("time"), **`valueAccessor`** ("value"), `positiveColor`, `negativeColor`
143
+ **RealtimeHeatmap** — **`timeAccessor`** ("time"), **`valueAccessor`** ("value"), `heatmapXBins`, `heatmapYBins`, `aggregation`. Both accessors must match your data fields or the chart renders blank.
144
+ **Streaming Sankey** — `StreamNetworkFrame` with `chartType="sankey"`, `showParticles` (boolean), `particleStyle` (`{ radius, opacity, speedMultiplier, maxPerEdge, colorBy }`), `tensionConfig`, `thresholds`. Push **individual edges**: `ref.current.push({ source: "A", target: "B", value: 42 })`. Use `ref.current.pushMany([...edges])` for batches.
65
145
 
66
146
  Realtime encoding: `decay`, `pulse`, `transition`, `staleness` — compose freely on all streaming charts.
67
147
 
148
+ ### Realtime data shape
149
+ ```jsx
150
+ // Every pushed datum should have a time field
151
+ ref.current.push({ time: Date.now(), value: 42 }) // line, waterfall
152
+ ref.current.push({ time: Date.now(), value: 42, category: "A" }) // histogram, swarm
153
+ ref.current.push({ time: Date.now(), value: 42 }) // heatmap (time=x, value=y)
154
+ ```
155
+
156
+ ### Push API on HOC charts
157
+ Many HOC charts support the push API via `forwardRef`. Omit the `data` prop and push data imperatively:
158
+ ```jsx
159
+ const chartRef = useRef()
160
+ chartRef.current.push({ x: 1, y: 2 }) // single point
161
+ chartRef.current.pushMany([...points]) // batch
162
+ chartRef.current.clear() // reset
163
+ chartRef.current.getData() // read current data
164
+ <Scatterplot ref={chartRef} xAccessor="x" yAccessor="y" />
165
+ ```
166
+ **IMPORTANT**: When using the push API, **omit** the `data`/`nodes`/`edges` prop entirely — do NOT pass `data={[]}`, which clears pushed data on every render. Streaming-specific props (`windowSize`, `decay`, `pulse`) go in `frameProps`.
167
+
168
+ Supported: all XY charts (LineChart, AreaChart, Scatterplot, etc.), all ordinal charts (BarChart, Histogram, etc.), network charts (ForceDirectedGraph, SankeyDiagram, ChordDiagram), and geo point charts (ProportionalSymbolMap, DistanceCartogram). **Not supported**: hierarchy charts (TreeDiagram, Treemap, CirclePack, OrbitDiagram) — their root-object data shape is incompatible with flat push. ChoroplethMap (area-based, not point-based), FlowMap (line-based), and ScatterplotMatrix also do not support push.
169
+
170
+ ## Stream Frame Callbacks (advanced — prefer HOCs)
171
+ Stream Frame callbacks (`nodeStyle`, `edgeStyle`, `nodeSize` as function, `colorBy` as function, `nodeLabel` as function) receive **`RealtimeNode`/`RealtimeEdge`** wrappers, NOT your raw data. Access your original data via `.data`:
172
+ ```jsx
173
+ // WRONG: nodeSize={(d) => d.weight} — d is RealtimeNode, d.weight is undefined
174
+ // RIGHT: nodeSize={(d) => d.data?.weight} — d.data is your original node object
175
+ // RIGHT: nodeSize="weight" — string accessor handles this automatically
176
+ // WRONG: nodeStyle={(d) => ({ fill: d.datum.color })} — .datum does not exist
177
+ // RIGHT: nodeStyle={(d) => ({ fill: d.data?.color })} — use .data
178
+ ```
179
+ `customHoverBehavior` and `customClickBehavior` receive `{ type: "node"|"edge", data: <your raw object>, x, y } | null`.
180
+ `tooltipContent` receives `{ type: "node"|"edge", data: <your raw object> }`.
181
+
68
182
  ## Coordinated Views
69
183
 
70
- **LinkedCharts** — wraps charts. Props: `selections` (resolution: "union"|"intersect"|"crossfilter")
184
+ **LinkedCharts** — wraps charts. Props: `selections` (resolution: "union"|"intersect"|"crossfilter"), `showLegend` (auto when CategoryColorProvider present), `legendPosition` ("top"|"bottom"), `legendInteraction` ("highlight"|"isolate"|"none"), `legendSelectionName` (selection name for legend-driven cross-highlighting), `legendField` (data field for legend selections)
71
185
  **CategoryColorProvider** — stable category→color mapping. Props: `colors` (map) or `categories` + `colorScheme`
72
186
  Chart props: `selection`, `linkedHover`, `linkedBrush`. Hooks: `useSelection`, `useLinkedHover`, `useBrushSelection`, `useFilteredData`
73
187
  **ScatterplotMatrix** — `data`, `fields`, `colorBy`, `cellSize`, `hoverMode`, `brushMode`
74
188
 
189
+ ## ChartContainer
190
+
191
+ **ChartContainer** — wrapper with title, subtitle, status indicator, toolbar actions. Props: `title`, `subtitle`, `height` (default **400** — set this to match your chart's height or you'll get extra whitespace), `width` (default "100%"), `status` ("live"|"stale"|"error"), `loading`, `error`, `errorBoundary`, `actions` (`{ export, fullscreen, copyConfig }`), `controls`, `style`, `className`
192
+
193
+ When using `ChartContainer` with a chart that has `size={[w, h]}`, always set `height={h}` on the container to avoid a mismatch.
194
+
75
195
  ## Layout & Composition
76
196
 
77
- **ChartGrid** — CSS Grid layout. `columns` (number|"auto"), `minCellWidth` (300), `gap` (16)
197
+ **ChartGrid** — CSS Grid layout. `columns` (number|"auto"), `minCellWidth` (300), `gap` (16). Children with `emphasis="primary"` span two columns.
78
198
  **ContextLayout** — primary + context panel. `context` (ReactNode), `position`, `contextSize` (250)
79
199
 
80
200
  ## Key Patterns
81
201
 
82
202
  ```jsx
83
- // Cross-highlighting dashboard
203
+ // Force-directed graph with custom sizing and hover
204
+ <ForceDirectedGraph
205
+ nodes={[{ id: "A", group: "eng", weight: 10 }, { id: "B", group: "design", weight: 5 }]}
206
+ edges={[{ source: "A", target: "B" }]}
207
+ colorBy="group"
208
+ nodeSize="weight" // string accessor → reads node.weight, scales to nodeSizeRange
209
+ nodeSizeRange={[5, 25]}
210
+ showLabels
211
+ showLegend
212
+ tooltip={(d) => <div>{d.data.id}: {d.data.weight}</div>}
213
+ frameProps={{
214
+ customClickBehavior: (d) => { if (d?.type === "node") console.log(d.data) },
215
+ background: "#f5f5f5",
216
+ }}
217
+ />
218
+
219
+ // Cross-highlighting dashboard with column spanning
220
+ // emphasis="primary" makes a chart span 2 columns in ChartGrid
84
221
  <CategoryColorProvider categories={["North", "South", "East"]}>
85
222
  <LinkedCharts>
86
223
  <ChartGrid columns={2}>
87
- <LineChart data={d} colorBy="region" linkedHover={{ name: "hl", fields: ["region"] }} selection={{ name: "hl" }} responsiveWidth />
224
+ <LineChart data={d} colorBy="region" linkedHover={{ name: "hl", fields: ["region"] }} selection={{ name: "hl" }} emphasis="primary" responsiveWidth />
88
225
  <BarChart data={d} colorBy="region" linkedHover={{ name: "hl", fields: ["region"] }} selection={{ name: "hl" }} responsiveWidth />
226
+ <Scatterplot data={d} colorBy="region" linkedHover={{ name: "hl", fields: ["region"] }} selection={{ name: "hl" }} responsiveWidth />
89
227
  </ChartGrid>
90
228
  </LinkedCharts>
91
229
  </CategoryColorProvider>
@@ -99,25 +237,100 @@ Chart props: `selection`, `linkedHover`, `linkedBrush`. Hooks: `useSelection`, `
99
237
  <LineChart data={ml} xAccessor="time" yAccessor="value"
100
238
  forecast={{ isTraining: "isTraining", isForecast: "isForecast", isAnomaly: "isAnomaly", upperBounds: "upper", lowerBounds: "lower" }} />
101
239
 
102
- // Gradient area + percentile band
103
- <AreaChart data={d} xAccessor="x" yAccessor="p95" y0Accessor="p5" gradientFill />
240
+ // Stacked area (flat array + areaBy, NOT lineBy)
241
+ <StackedAreaChart data={flatData} xAccessor="month" yAccessor="value"
242
+ areaBy="category" colorBy="category" />
104
243
 
105
- // Realtime
244
+ // Percentile band (p5–p95) with main line (p50) — MUST layer two charts
245
+ // AreaChart with y0Accessor renders the band; showLine only draws the TOP edge (p95), not p50
246
+ // To show a separate main line, add a LineChart on top:
247
+ <>
248
+ <AreaChart data={d} xAccessor="x" yAccessor="p95" y0Accessor="p5"
249
+ showLine={false} areaOpacity={0.3} gradientFill />
250
+ <LineChart data={d} xAccessor="x" yAccessor="p50" lineWidth={2} />
251
+ </>
252
+ // Simple gradient area (no band):
253
+ <AreaChart data={d} xAccessor="x" yAccessor="y" gradientFill />
254
+
255
+ // Realtime — always include time field in pushed data
106
256
  const ref = useRef()
107
257
  ref.current.push({ time: Date.now(), value: 42 })
108
258
  <RealtimeLineChart ref={ref} timeAccessor="time" valueAccessor="value" />
259
+
260
+ // Realtime histogram — time field required even for distribution charts
261
+ const histRef = useRef()
262
+ histRef.current.push({ time: Date.now(), value: Math.abs(delta) })
263
+ <RealtimeHistogram ref={histRef} timeAccessor="time" valueAccessor="value" binSize={100} />
264
+
265
+ // Streaming sankey with particles — push individual edges, NOT full snapshots
266
+ const sankeyRef = useRef()
267
+ sankeyRef.current.push({ source: "Web", target: "API", value: 1 }) // one edge at a time
268
+ sankeyRef.current.pushMany([ // or batch
269
+ { source: "Web", target: "API", value: 3 },
270
+ { source: "API", target: "DB", value: 2 },
271
+ ])
272
+ <StreamNetworkFrame
273
+ ref={sankeyRef}
274
+ chartType="sankey"
275
+ showParticles={true}
276
+ particleStyle={{ radius: 2, colorBy: "source", speedMultiplier: 1.5 }}
277
+ width={600} height={400}
278
+ />
279
+
280
+ // SSR — renderToStaticSVG takes frame type string, not component name
281
+ import { renderOrdinalToStaticSVG } from "semiotic/server"
282
+ const svg = renderOrdinalToStaticSVG({
283
+ data, categoryAccessor: "category", valueAccessor: "value", width: 600, height: 400
284
+ })
285
+ ```
286
+
287
+ ## Annotations
288
+ - `type: "widget"` — place any React element at data coordinates. Works on all frame types. XY/ordinal use data coordinates (`x`/`y` or field names). Network/orbit use `nodeId`. Default: info emoji. Renders as HTML overlay (not SVG) so popups/threads overflow freely.
289
+ ```jsx
290
+ annotations={[{ type: "widget", month: 4, revenue: 32, dy: -4, content: <MyAlertButton /> }]}
291
+ // OrbitDiagram: annotations={[{ type: "widget", nodeId: "Pipeline", content: <Alert /> }]}
109
292
  ```
110
293
 
294
+ ## Server-Side Rendering
295
+ - All HOC charts and Stream Frames render SVG automatically in server environments (no window/document)
296
+ - `renderToStaticSVG(frameType, props)` — standalone SVG string from `semiotic/server`. `frameType` is `"xy"` | `"ordinal"` | `"network"` | `"geo"` (NOT a component name like "BarChart")
297
+ - Type-specific shortcuts: `renderXYToStaticSVG(props)`, `renderOrdinalToStaticSVG(props)`, `renderNetworkToStaticSVG(props)`, `renderGeoToStaticSVG(props)`
298
+ - For a bar chart: `renderOrdinalToStaticSVG({ data, categoryAccessor: "cat", valueAccessor: "val", width: 600, height: 400 })`
299
+ - Works with Next.js App Router, Remix, Astro — same component on server and client
300
+ - **Geo SSR requires pre-resolved features**: `renderGeoToStaticSVG` is synchronous — pass GeoJSON features directly, not reference strings like `"world-110m"`. Call `await resolveReferenceGeography("world-110m")` first and pass the result as `areas`.
301
+
111
302
  ## AI Features
112
303
  - `onObservation` — structured events (hover, click, brush, selection) on all HOCs
113
304
  - `useChartObserver` — aggregates observations across LinkedCharts
114
305
  - `toConfig`/`fromConfig`/`toURL`/`fromURL`/`copyConfig`/`configToJSX` — chart state serialization
115
306
  - `DetailsPanel` — click-driven detail panel inside `ChartContainer`
116
- - `validateProps(componentName, props)` — prop validation
307
+ - `validateProps(componentName, props)` — prop validation with Levenshtein typo suggestions
308
+ - `diagnoseConfig(componentName, props)` — anti-pattern detector (12 checks: empty data, bad dimensions, missing accessors, margin overflow, etc.)
117
309
  - `ChartErrorBoundary` — error boundary
118
- - `exportChart(el, { format: "png"|"svg" })` — browser export
119
- - `renderToStaticSVG()` — server-side SVG (from `semiotic/server`)
120
- - `npx semiotic-ai --doctor` — validate component + props JSON from CLI
310
+ - `exportChart(containerDiv, { format: "png"|"svg" })` — pass the **wrapper div** (not the SVG element); it finds canvas + SVG internally. Default: PNG, composites canvas + SVG layers
311
+ - `npx semiotic-ai --doctor` — validate component + props JSON from CLI (uses both validateProps and diagnoseConfig)
312
+
313
+ ## Known Pitfalls
314
+
315
+ **Tooltip datum shape**: HOC tooltip functions receive your raw data object. When using `frameProps.tooltipContent` on Stream Frames, the datum may be wrapped — access your data via `d.data`. HOC `tooltip` functions don't need this.
316
+
317
+ **Legend positioning**: `legendPosition` controls where the legend renders. When set to "bottom", the chart automatically expands the bottom margin to ~80px to clear axis labels. For "top", margin expands to ~50px. If you need more space, override `margin` explicitly. For charts narrower than ~400px, prefer `legendPosition="bottom"` or `"top"` (bottom is more common) to avoid squeezing the chart area. Similarly, for short charts (~250px or less), a side legend may compress the chart too much — use top or bottom instead.
318
+
319
+ **Log scale and zero**: `xScaleType="log"` / `yScaleType="log"` clamp domain minimums to 1e-6 because log(0) is undefined. Data with zero or negative values will be clamped.
320
+
321
+ **Heatmap with string axes**: Heatmap supports string/categorical x and y values (e.g., weekday names, hour labels). The `colorScheme` prop accepts d3-scale-chromatic names: "blues", "reds", "greens", "viridis".
322
+
323
+ **barPadding is in pixels**: `barPadding` on ordinal charts is an absolute pixel value divided by the chart width to compute a band scale padding ratio. The defaults (40 for bar/stacked, 60 for grouped) work well at 600px width. For very small charts, you may need to reduce it.
324
+
325
+ **Horizontal bar charts need wider left margins**: When using `orientation="horizontal"` with long category labels, increase the left margin manually: `margin={{ left: 120 }}`. There is no auto-measurement of label width.
326
+
327
+ **LinkedCharts suppresses child legends**: When a `CategoryColorProvider` wraps `LinkedCharts`, individual chart legends are suppressed in favor of a unified legend. To force a child chart to show its own legend, set `showLegend={true}` explicitly.
328
+
329
+ **Geo bundle isolation**: `semiotic/geo` is a separate entry point. Do NOT import geo components from `semiotic` — use `import { ChoroplethMap } from "semiotic/geo"` to avoid pulling d3-geo (~30KB) into non-geo bundles.
330
+
331
+ **Push API: omit data, don't pass empty array**: When using `ref.current.push()` on HOCs, **omit** the `data`/`nodes`/`edges` prop entirely. Passing `data={[]}` clears pushed data on every render because the HOC forwards it to the Stream Frame's `setBoundedData([])`. Similarly, `data={undefined}` is fine (prop not present), but `data={null}` is treated the same as omitted.
332
+
333
+ **`diagnoseConfig` catches common mistakes**: Run `diagnoseConfig("BarChart", props)` to check for empty data, bad dimensions, missing accessors, margin overflow, invisible bar padding, and more. Use `npx semiotic-ai --doctor` from CLI.
121
334
 
122
335
  ## Differentiators
123
- Network viz, streaming canvas, realtime encoding, coordinated views, statistical summaries, AI hooks, chart serialization, global theming, keyboard navigation
336
+ Network viz, geographic viz (choropleth, flow maps, distance cartograms), streaming canvas, realtime encoding, coordinated views, statistical summaries, AI hooks, chart serialization, global theming, keyboard navigation, interactive legends (highlight/isolate), direct labeling, gap handling, empty/loading states, landmark tick labels, LinkedCharts unified legend
package/README.md CHANGED
@@ -4,10 +4,11 @@
4
4
  [![npm version](https://img.shields.io/npm/v/semiotic.svg)](https://www.npmjs.com/package/semiotic)
5
5
  [![TypeScript](https://img.shields.io/badge/TypeScript-built--in-blue.svg)](https://www.typescriptlang.org/)
6
6
 
7
- A React data visualization library for charts, networks, and beyond.
7
+ A React data visualization library designed for AI-assisted development.
8
8
 
9
- Simple charts in 5 lines. Force-directed graphs, Sankey diagrams, treemaps,
10
- and chord diagrams when you need them. Full D3-level control when you want it.
9
+ Simple charts in 5 lines. Network graphs, streaming data, and coordinated
10
+ dashboards when you need them. Structured schemas and an MCP server so
11
+ AI coding assistants generate correct chart code on the first try.
11
12
 
12
13
  ```jsx
13
14
  import { LineChart } from "semiotic"
@@ -21,31 +22,52 @@ import { LineChart } from "semiotic"
21
22
 
22
23
  ## Why Semiotic
23
24
 
24
- Most React charting libraries give you bar charts, line charts, and pie charts.
25
- Semiotic gives you those too but it's built for the projects where those
26
- aren't enough.
25
+ Semiotic is a data visualization library for React that combines broad chart
26
+ coverage with first-class AI tooling. It handles the chart types that most
27
+ libraries skip — network graphs, streaming data, statistical distributions,
28
+ coordinated views — and ships with machine-readable schemas so LLMs can
29
+ generate correct code without examples.
27
30
 
28
- ### When you need more than standard charts
31
+ ### Built for AI-assisted development
29
32
 
30
- **Network visualization.** Show how things connect org charts,
31
- dependency graphs, budget flows, taxonomies. Semiotic has force-directed
32
- graphs, Sankey diagrams, chord diagrams, tree layouts, treemaps, and circle
33
- packing as React components with the same prop API as LineChart.
33
+ Semiotic ships with everything an AI coding assistant needs to generate
34
+ correct visualizations without trial and error:
34
35
 
35
- **Streaming data.** Monitor live systems server metrics, sensor feeds,
36
- financial tickers. Semiotic's realtime charts render on canvas at 60fps with
37
- a ref-based push API. Old data fades out (decay), new data flashes in
38
- (pulse), and stale feeds are flagged automatically.
36
+ - **`semiotic/ai`** a single import with all 37 chart components, optimized for LLM code generation
37
+ - **`ai/schema.json`** machine-readable prop schemas for every component
38
+ - **`npx semiotic-mcp`** an MCP server for tool-based chart rendering in any MCP client
39
+ - **`npx semiotic-ai --doctor`** validate component + props JSON from the command line with typo suggestions and anti-pattern detection
40
+ - **`diagnoseConfig(component, props)`** — programmatic anti-pattern detector with 12 checks and actionable fixes
41
+ - **`CLAUDE.md`** — instruction files auto-synced for Claude, Cursor, Copilot, Windsurf, and Cline
42
+ - **`llms.txt`** — machine-readable documentation following the emerging standard
39
43
 
40
- **Coordinated dashboards.** Hover one chart, highlight matching data in
41
- others. Brush a scatterplot, filter a bar chart. Semiotic's `LinkedCharts`
42
- and `ScatterplotMatrix` provide crossfilter coordination that other libraries
43
- leave you to build from scratch.
44
+ Every chart includes a built-in error boundary, dev-mode validation
45
+ warnings with typo suggestions, and accessibility features (canvas
46
+ `aria-label`, keyboard-navigable legends, `aria-live` tooltips, SVG
47
+ `<title>`/`<desc>`) so AI-generated code fails gracefully with
48
+ actionable diagnostics instead of a blank screen.
44
49
 
45
- **Statistical summaries.** Box plots, violin plots, swarm plots, ridgeline
46
- plots, histograms — the distribution charts that data scientists need and
47
- most charting libraries skip. Add marginal distribution graphics (histogram,
48
- violin, ridgeline, boxplot) to scatterplot margins with a single prop.
50
+ ### Beyond standard charts
51
+
52
+ **Network visualization.** Force-directed graphs, Sankey diagrams, chord
53
+ diagrams, tree layouts, treemaps, circle packing, and orbit diagrams all
54
+ as React components with the same prop API as LineChart.
55
+
56
+ **Streaming data.** Realtime charts render on canvas at 60fps with a
57
+ ref-based push API. Built-in decay, pulse, and staleness encoding for
58
+ monitoring dashboards.
59
+
60
+ **Coordinated views.** `LinkedCharts` provides hover cross-highlighting,
61
+ brush cross-filtering, and selection synchronization across any combination
62
+ of chart types — zero wiring.
63
+
64
+ **Geographic visualization.** Choropleth maps, proportional symbol maps, flow
65
+ maps with animated particles, and distance cartograms — all canvas-rendered
66
+ with d3-geo projections, zoom/pan, tile basemaps, and drag-rotate globe spinning.
67
+
68
+ **Statistical summaries.** Box plots, violin plots, swarm plots, histograms,
69
+ LOESS smoothing, forecast with confidence envelopes, and anomaly detection.
70
+ Marginal distribution graphics on scatterplot axes with a single prop.
49
71
 
50
72
  ### Start simple, go deep
51
73
 
@@ -57,6 +79,13 @@ violin, ridgeline, boxplot) to scatterplot margins with a single prop.
57
79
  Every Chart component accepts a `frameProps` prop to access the underlying
58
80
  Frame API without leaving the simpler interface.
59
81
 
82
+ ### Serialization and interop
83
+
84
+ Charts serialize to JSON and back: `toConfig`, `fromConfig`, `toURL`,
85
+ `copyConfig`, `configToJSX`. Have Vega-Lite specs? `fromVegaLite(spec)`
86
+ translates them to Semiotic configs — works with `configToJSX()` for
87
+ full round-trip from notebooks and AI-generated specs.
88
+
60
89
  ### When to use something else
61
90
 
62
91
  Need a standard bar or line chart for a dashboard you'll never need to
@@ -69,40 +98,10 @@ Semiotic is for projects that outgrow those libraries — when you need
69
98
  network graphs alongside time series, streaming data alongside static
70
99
  snapshots, or coordinated views across chart types.
71
100
 
72
- ### Bundle size comparison
73
-
74
- | Library | Packed | Unpacked | What you get |
75
- |---|---|---|---|
76
- | Victory | 393 KB | 2.3 MB | Charts only |
77
- | Lightweight Charts | 586 KB | 3.0 MB | Financial charts only |
78
- | **Semiotic** | **668 KB** | **2.5 MB** | **Charts + networks + streaming + coordination** |
79
- | Recharts | 1.4 MB | 6.4 MB | Charts only |
80
- | Chart.js | 1.6 MB | 6.2 MB | Charts only, no React |
81
- | ApexCharts | 1.8 MB | 8.4 MB | Charts only |
82
- | ECharts | 11.4 MB | 57.6 MB | Everything, no React |
83
-
84
- **AI-ready.** Semiotic ships with structured schemas (`ai/schema.json`), an
85
- `import from "semiotic/ai"` entry point, and an MCP server — all designed for
86
- LLM code generation. AI coding assistants can generate correct Semiotic code on
87
- the first try. Run `npx semiotic-ai --help` for CLI options, `npx semiotic-ai --doctor`
88
- to validate props from the command line, or add `semiotic-mcp` to your MCP client
89
- config for tool-based chart rendering. Every HOC chart includes a built-in error
90
- boundary and dev-mode validation warnings with actionable fix suggestions.
91
-
92
- **Vega-Lite compatible.** Have existing Vega-Lite specs? `fromVegaLite(spec)`
93
- translates them to Semiotic chart configs — instant onboarding from notebooks,
94
- dashboards, or AI-generated specs. Composes with `configToJSX()`,
95
- `copyConfig()`, and `toURL()` for full round-trip interop.
96
-
97
- **Streaming system models.** Turn a streaming Sankey into a live system monitor:
98
- click-to-inspect `DetailsPanel`, particle speed proportional to edge throughput,
99
- threshold alerting with animated glow, and automatic topology diffing that
100
- highlights new services as they appear. Visualization as product navigation.
101
-
102
101
  ## Install
103
102
 
104
103
  ```bash
105
- npm install semiotic@3.0.0
104
+ npm install semiotic
106
105
  ```
107
106
 
108
107
  Requires React 18.1+ or React 19.
@@ -166,6 +165,30 @@ import { ForceDirectedGraph, SankeyDiagram } from "semiotic"
166
165
  />
167
166
  ```
168
167
 
168
+ ### Geographic Visualization
169
+
170
+ Choropleth maps, flow maps, and distance cartograms with canvas rendering,
171
+ zoom/pan, tile basemaps, and animated particles:
172
+
173
+ ```jsx
174
+ import { ChoroplethMap, FlowMap, DistanceCartogram } from "semiotic/geo"
175
+
176
+ <ChoroplethMap
177
+ areas={geoJsonFeatures} valueAccessor="gdp"
178
+ colorScheme="viridis" projection="equalEarth" zoomable tooltip
179
+ />
180
+
181
+ <FlowMap
182
+ nodes={airports} flows={routes} valueAccessor="passengers"
183
+ showParticles particleStyle={{ color: "source", speedMultiplier: 1.5 }}
184
+ />
185
+
186
+ <DistanceCartogram
187
+ points={cities} center="rome" costAccessor="travelDays"
188
+ showRings costLabel="days" lines={routes}
189
+ />
190
+ ```
191
+
169
192
  ### Streaming System Monitor
170
193
 
171
194
  Live service topology with threshold alerting and click-to-inspect:
@@ -215,13 +238,14 @@ import { LineChart, BarChart } from "semiotic"
215
238
 
216
239
  | Category | Components |
217
240
  |---|---|
218
- | **XY** | `LineChart` `AreaChart` `StackedAreaChart` `Scatterplot` `BubbleChart` `Heatmap` |
219
- | **Categorical** | `BarChart` `StackedBarChart` `GroupedBarChart` `SwarmPlot` `BoxPlot` `Histogram` `ViolinPlot` `DotPlot` `PieChart` `DonutChart` |
220
- | **Network** | `ForceDirectedGraph` `ChordDiagram` `SankeyDiagram` `TreeDiagram` `Treemap` `CirclePack` |
241
+ | **XY** | `LineChart` `AreaChart` `StackedAreaChart` `Scatterplot` `ConnectedScatterplot` `BubbleChart` `Heatmap` `QuadrantChart` `MinimapChart` |
242
+ | **Categorical** | `BarChart` `StackedBarChart` `GroupedBarChart` `SwarmPlot` `BoxPlot` `Histogram` `ViolinPlot` `RidgelinePlot` `DotPlot` `PieChart` `DonutChart` |
243
+ | **Network** | `ForceDirectedGraph` `ChordDiagram` `SankeyDiagram` `TreeDiagram` `Treemap` `CirclePack` `OrbitDiagram` |
244
+ | **Geo** | `ChoroplethMap` `ProportionalSymbolMap` `FlowMap` `DistanceCartogram` |
221
245
  | **Realtime** | `RealtimeLineChart` `RealtimeHistogram` `RealtimeSwarmChart` `RealtimeWaterfallChart` `RealtimeHeatmap` |
222
246
  | **Coordination** | `LinkedCharts` `ScatterplotMatrix` |
223
247
  | **Layout** | `ChartGrid` `ContextLayout` `CategoryColorProvider` |
224
- | **Frames** | `StreamXYFrame` `StreamOrdinalFrame` `StreamNetworkFrame` |
248
+ | **Frames** | `StreamXYFrame` `StreamOrdinalFrame` `StreamNetworkFrame` `StreamGeoFrame` |
225
249
 
226
250
  ### Vega-Lite Translation
227
251
 
@@ -257,10 +281,11 @@ for color, size, aggregation, and binning.
257
281
  Import only what you need:
258
282
 
259
283
  ```jsx
260
- import { LineChart } from "semiotic/xy" // 123 KB
261
- import { BarChart } from "semiotic/ordinal" // 118 KB
262
- import { ForceDirectedGraph } from "semiotic/network" // 127 KB
263
- import { LineChart } from "semiotic/ai" // HOC-only surface for AI generation
284
+ import { LineChart } from "semiotic/xy" // ~156 KB
285
+ import { BarChart } from "semiotic/ordinal" // ~124 KB
286
+ import { ForceDirectedGraph } from "semiotic/network" // ~123 KB
287
+ import { ChoroplethMap } from "semiotic/geo" // ~102 KB (+ d3-geo peer)
288
+ import { LineChart } from "semiotic/ai" // ~397 KB (all HOCs)
264
289
  ```
265
290
 
266
291
  Granular entry points export only v3 Stream Frames and HOC charts — no legacy
@@ -283,7 +308,19 @@ interface Sale { month: number; revenue: number }
283
308
 
284
309
  ## Server-Side Rendering
285
310
 
286
- Static SVG generation for Node.js (email, OG images, PDF):
311
+ All chart components render SVG automatically in server environments no
312
+ special imports or configuration needed:
313
+
314
+ ```jsx
315
+ // Works in Next.js App Router, Remix, Astro — same component, same props
316
+ import { LineChart } from "semiotic"
317
+
318
+ // Server: renders <svg> with path/circle/rect elements
319
+ // Client: renders <canvas> with SVG overlay for axes
320
+ <LineChart data={data} xAccessor="date" yAccessor="value" />
321
+ ```
322
+
323
+ For standalone SVG generation (email, OG images, PDF), use the server entry point:
287
324
 
288
325
  ```js
289
326
  import { renderToStaticSVG } from "semiotic/server"
@@ -296,14 +333,12 @@ const svg = renderToStaticSVG("xy", {
296
333
  })
297
334
  ```
298
335
 
299
- Works with Next.js App Router, Remix, and Astro via `"use client"` directives.
300
-
301
336
  ## Documentation
302
337
 
303
338
  [Interactive docs and examples](https://semiotic.nteract.io)
304
339
 
305
340
  - [Getting Started](https://semiotic.nteract.io/getting-started)
306
- - [Charts](https://semiotic.nteract.io/charts) — all 27 chart types with live examples
341
+ - [Charts](https://semiotic.nteract.io/charts) — all 37 chart types with live examples
307
342
  - [Frames](https://semiotic.nteract.io/frames) — full Frame API reference
308
343
  - [Features](https://semiotic.nteract.io/features) — axes, annotations, tooltips, styling, Vega-Lite translator
309
344
  - [Cookbook](https://semiotic.nteract.io/cookbook) — advanced patterns and recipes