semiotic 3.0.1 → 3.1.1

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 (129) hide show
  1. package/CLAUDE.md +227 -27
  2. package/README.md +147 -11
  3. package/ai/dist/componentRegistry.js +5 -0
  4. package/ai/dist/mcp-server.js +305 -30
  5. package/ai/examples.md +358 -18
  6. package/ai/schema.json +64 -2
  7. package/ai/system-prompt.md +50 -12
  8. package/dist/components/Legend.d.ts +7 -1
  9. package/dist/components/charts/geo/ChoroplethMap.d.ts +53 -0
  10. package/dist/components/charts/geo/DistanceCartogram.d.ts +90 -0
  11. package/dist/components/charts/geo/FlowMap.d.ts +83 -0
  12. package/dist/components/charts/geo/ProportionalSymbolMap.d.ts +67 -0
  13. package/dist/components/charts/geo/index.d.ts +8 -0
  14. package/dist/components/charts/index.d.ts +2 -0
  15. package/dist/components/charts/network/ChordDiagram.d.ts +6 -5
  16. package/dist/components/charts/network/CirclePack.d.ts +2 -2
  17. package/dist/components/charts/network/ForceDirectedGraph.d.ts +9 -7
  18. package/dist/components/charts/network/OrbitDiagram.d.ts +21 -20
  19. package/dist/components/charts/network/SankeyDiagram.d.ts +6 -5
  20. package/dist/components/charts/network/TreeDiagram.d.ts +2 -2
  21. package/dist/components/charts/network/Treemap.d.ts +2 -2
  22. package/dist/components/charts/ordinal/BarChart.d.ts +7 -5
  23. package/dist/components/charts/ordinal/BoxPlot.d.ts +8 -6
  24. package/dist/components/charts/ordinal/DonutChart.d.ts +8 -6
  25. package/dist/components/charts/ordinal/DotPlot.d.ts +8 -6
  26. package/dist/components/charts/ordinal/GroupedBarChart.d.ts +7 -5
  27. package/dist/components/charts/ordinal/Histogram.d.ts +8 -5
  28. package/dist/components/charts/ordinal/PieChart.d.ts +8 -6
  29. package/dist/components/charts/ordinal/RidgelinePlot.d.ts +2 -0
  30. package/dist/components/charts/ordinal/StackedBarChart.d.ts +7 -5
  31. package/dist/components/charts/ordinal/SwarmPlot.d.ts +8 -6
  32. package/dist/components/charts/ordinal/ViolinPlot.d.ts +8 -5
  33. package/dist/components/charts/realtime/RealtimeHeatmap.d.ts +24 -6
  34. package/dist/components/charts/realtime/RealtimeHistogram.d.ts +28 -7
  35. package/dist/components/charts/realtime/RealtimeLineChart.d.ts +23 -5
  36. package/dist/components/charts/realtime/RealtimeSwarmChart.d.ts +24 -6
  37. package/dist/components/charts/realtime/RealtimeWaterfallChart.d.ts +23 -5
  38. package/dist/components/charts/shared/ChartError.d.ts +3 -1
  39. package/dist/components/charts/shared/colorUtils.d.ts +5 -0
  40. package/dist/components/charts/shared/hooks.d.ts +13 -1
  41. package/dist/components/charts/shared/legendUtils.d.ts +2 -3
  42. package/dist/components/charts/shared/statisticalOverlays.d.ts +1 -2
  43. package/dist/components/charts/shared/statisticalOverlaysLazy.d.ts +10 -0
  44. package/dist/components/charts/shared/tooltipUtils.d.ts +1 -1
  45. package/dist/components/charts/shared/types.d.ts +10 -4
  46. package/dist/components/charts/shared/useChartSetup.d.ts +112 -0
  47. package/dist/components/charts/shared/useStreamingLegend.d.ts +65 -0
  48. package/dist/components/charts/shared/withChartWrapper.d.ts +4 -3
  49. package/dist/components/charts/xy/AreaChart.d.ts +11 -6
  50. package/dist/components/charts/xy/BubbleChart.d.ts +11 -6
  51. package/dist/components/charts/xy/ConnectedScatterplot.d.ts +7 -6
  52. package/dist/components/charts/xy/Heatmap.d.ts +16 -5
  53. package/dist/components/charts/xy/LineChart.d.ts +21 -5
  54. package/dist/components/charts/xy/MinimapChart.d.ts +3 -0
  55. package/dist/components/charts/xy/QuadrantChart.d.ts +120 -0
  56. package/dist/components/charts/xy/Scatterplot.d.ts +9 -6
  57. package/dist/components/charts/xy/StackedAreaChart.d.ts +11 -6
  58. package/dist/components/geo/mergeData.d.ts +18 -0
  59. package/dist/components/geo/referenceGeography.d.ts +10 -0
  60. package/dist/components/geo/useReferenceAreas.d.ts +13 -0
  61. package/dist/components/realtime/RingBuffer.d.ts +1 -0
  62. package/dist/components/realtime/types.d.ts +17 -0
  63. package/dist/components/semiotic-data.d.ts +1 -0
  64. package/dist/components/semiotic-geo.d.ts +16 -0
  65. package/dist/components/semiotic-server.d.ts +1 -1
  66. package/dist/components/semiotic-xy.d.ts +1 -0
  67. package/dist/components/semiotic.d.ts +4 -4
  68. package/dist/components/server/renderToStaticSVG.d.ts +4 -2
  69. package/dist/components/stream/AccessibleDataTable.d.ts +50 -0
  70. package/dist/components/stream/CanvasHitTester.d.ts +8 -2
  71. package/dist/components/stream/DataSourceAdapter.d.ts +33 -4
  72. package/dist/components/stream/GeoCanvasHitTester.d.ts +19 -0
  73. package/dist/components/stream/GeoParticlePool.d.ts +46 -0
  74. package/dist/components/stream/GeoPipelineStore.d.ts +81 -0
  75. package/dist/components/stream/GeoTileRenderer.d.ts +31 -0
  76. package/dist/components/stream/NetworkPipelineStore.d.ts +16 -4
  77. package/dist/components/stream/NetworkSVGOverlay.d.ts +4 -1
  78. package/dist/components/stream/OrdinalPipelineStore.d.ts +8 -4
  79. package/dist/components/stream/OrdinalSVGOverlay.d.ts +23 -1
  80. package/dist/components/stream/PipelineStore.d.ts +57 -5
  81. package/dist/components/stream/SVGOverlay.d.ts +28 -1
  82. package/dist/components/stream/SceneGraph.d.ts +7 -3
  83. package/dist/components/stream/SceneToSVG.d.ts +2 -0
  84. package/dist/components/stream/StreamGeoFrame.d.ts +4 -0
  85. package/dist/components/stream/accessorUtils.d.ts +1 -0
  86. package/dist/components/stream/canvasSetup.d.ts +26 -0
  87. package/dist/components/stream/geoTypes.d.ts +186 -0
  88. package/dist/components/stream/layouts/forceLayoutPlugin.d.ts +0 -7
  89. package/dist/components/stream/layouts/index.d.ts +2 -1
  90. package/dist/components/stream/layouts/orbitLayoutPlugin.d.ts +2 -0
  91. package/dist/components/stream/legendRenderer.d.ts +33 -0
  92. package/dist/components/stream/networkTypes.d.ts +49 -1
  93. package/dist/components/stream/ordinalTypes.d.ts +10 -0
  94. package/dist/components/stream/pipelineTransitionUtils.d.ts +42 -0
  95. package/dist/components/stream/renderers/geoCanvasRenderer.d.ts +9 -0
  96. package/dist/components/stream/renderers/heatmapCanvasRenderer.d.ts +2 -1
  97. package/dist/components/stream/renderers/lineCanvasRenderer.d.ts +1 -0
  98. package/dist/components/stream/renderers/renderPulse.d.ts +50 -0
  99. package/dist/components/stream/types.d.ts +77 -3
  100. package/dist/components/types/legendTypes.d.ts +27 -3
  101. package/dist/geo.min.js +1 -0
  102. package/dist/geo.module.min.js +1 -0
  103. package/dist/network.min.js +1 -1
  104. package/dist/network.module.min.js +1 -1
  105. package/dist/ordinal.min.js +1 -1
  106. package/dist/ordinal.module.min.js +1 -1
  107. package/dist/realtime.min.js +1 -1
  108. package/dist/realtime.module.min.js +1 -1
  109. package/dist/semiotic-ai-statisticalOverlays-C1f7TYyD.js +1 -0
  110. package/dist/semiotic-ai.min.js +1 -1
  111. package/dist/semiotic-ai.module.min.js +1 -1
  112. package/dist/semiotic-data.d.ts +1 -0
  113. package/dist/semiotic-data.min.js +1 -1
  114. package/dist/semiotic-data.module.min.js +1 -1
  115. package/dist/semiotic-geo.d.ts +16 -0
  116. package/dist/semiotic-server.d.ts +1 -1
  117. package/dist/semiotic-statisticalOverlays-C1f7TYyD.js +1 -0
  118. package/dist/semiotic-xy.d.ts +1 -0
  119. package/dist/semiotic.d.ts +4 -4
  120. package/dist/semiotic.min.js +1 -1
  121. package/dist/semiotic.module.min.js +1 -1
  122. package/dist/server.min.js +1 -1
  123. package/dist/server.module.min.js +1 -1
  124. package/dist/test-utils/canvasMock.d.ts +3 -0
  125. package/dist/xy-statisticalOverlays-C1f7TYyD.js +1 -0
  126. package/dist/xy.min.js +1 -1
  127. package/dist/xy.module.min.js +1 -1
  128. package/package.json +76 -8
  129. package/dist/test/canvasMock.d.ts +0 -2
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`, `semiotic/server`
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`, `loading` (false), `emptyContent`, `legendInteraction` ("none"|"highlight"|"isolate"), `emphasis` ("primary"|"secondary")
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), `directLabel` (boolean|{position,fontSize}), `gapStrategy` ("break"|"interpolate"|"zero")
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,44 +42,143 @@
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`
54
- **OrbitDiagram** — `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)
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
+ ```
55
130
 
56
131
  ## Realtime Charts (`semiotic/realtime`)
57
132
 
58
133
  Push API: `chartRef.current.push({ time, value })`
59
134
 
60
- **RealtimeLineChart** `size`, `timeAccessor`, `valueAccessor`, `windowSize` (200), `windowMode`, `stroke`, `strokeWidth`
61
- **RealtimeHistogram** — + `binSize` (required), `categoryAccessor`, `colors`
62
- **RealtimeSwarmChart** + `categoryAccessor`, `radius`, `opacity`
63
- **RealtimeWaterfallChart** — + `positiveColor`, `negativeColor`
64
- **RealtimeHeatmap** — + `heatmapXBins`, `heatmapYBins`, `aggregation`
65
- **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.
66
145
 
67
146
  Realtime encoding: `decay`, `pulse`, `transition`, `staleness` — compose freely on all streaming charts.
68
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
+
69
182
  ## Coordinated Views
70
183
 
71
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)
@@ -73,6 +186,12 @@ Realtime encoding: `decay`, `pulse`, `transition`, `staleness` — compose freel
73
186
  Chart props: `selection`, `linkedHover`, `linkedBrush`. Hooks: `useSelection`, `useLinkedHover`, `useBrushSelection`, `useFilteredData`
74
187
  **ScatterplotMatrix** — `data`, `fields`, `colorBy`, `cellSize`, `hoverMode`, `brushMode`
75
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
+
76
195
  ## Layout & Composition
77
196
 
78
197
  **ChartGrid** — CSS Grid layout. `columns` (number|"auto"), `minCellWidth` (300), `gap` (16). Children with `emphasis="primary"` span two columns.
@@ -81,12 +200,30 @@ Chart props: `selection`, `linkedHover`, `linkedBrush`. Hooks: `useSelection`, `
81
200
  ## Key Patterns
82
201
 
83
202
  ```jsx
84
- // 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
85
221
  <CategoryColorProvider categories={["North", "South", "East"]}>
86
222
  <LinkedCharts>
87
223
  <ChartGrid columns={2}>
88
- <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 />
89
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 />
90
227
  </ChartGrid>
91
228
  </LinkedCharts>
92
229
  </CategoryColorProvider>
@@ -100,13 +237,51 @@ Chart props: `selection`, `linkedHover`, `linkedBrush`. Hooks: `useSelection`, `
100
237
  <LineChart data={ml} xAccessor="time" yAccessor="value"
101
238
  forecast={{ isTraining: "isTraining", isForecast: "isForecast", isAnomaly: "isAnomaly", upperBounds: "upper", lowerBounds: "lower" }} />
102
239
 
103
- // Gradient area + percentile band
104
- <AreaChart data={d} xAccessor="x" yAccessor="p95" y0Accessor="p5" gradientFill />
105
-
106
- // Realtime
240
+ // Stacked area (flat array + areaBy, NOT lineBy)
241
+ <StackedAreaChart data={flatData} xAccessor="month" yAccessor="value"
242
+ areaBy="category" colorBy="category" />
243
+
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
107
256
  const ref = useRef()
108
257
  ref.current.push({ time: Date.now(), value: 42 })
109
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
+ })
110
285
  ```
111
286
 
112
287
  ## Annotations
@@ -118,8 +293,11 @@ annotations={[{ type: "widget", month: 4, revenue: 32, dy: -4, content: <MyAlert
118
293
 
119
294
  ## Server-Side Rendering
120
295
  - All HOC charts and Stream Frames render SVG automatically in server environments (no window/document)
121
- - `renderToStaticSVG()`, `renderXYToStaticSVG()`, `renderOrdinalToStaticSVG()`, `renderNetworkToStaticSVG()` — standalone SVG generation from `semiotic/server`
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 })`
122
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`.
123
301
 
124
302
  ## AI Features
125
303
  - `onObservation` — structured events (hover, click, brush, selection) on all HOCs
@@ -129,8 +307,30 @@ annotations={[{ type: "widget", month: 4, revenue: 32, dy: -4, content: <MyAlert
129
307
  - `validateProps(componentName, props)` — prop validation with Levenshtein typo suggestions
130
308
  - `diagnoseConfig(componentName, props)` — anti-pattern detector (12 checks: empty data, bad dimensions, missing accessors, margin overflow, etc.)
131
309
  - `ChartErrorBoundary` — error boundary
132
- - `exportChart(el, { format: "png"|"svg" })` — browser export (default: PNG, composites canvas + SVG layers)
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
133
311
  - `npx semiotic-ai --doctor` — validate component + props JSON from CLI (uses both validateProps and diagnoseConfig)
134
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.
334
+
135
335
  ## Differentiators
136
- Network viz, 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
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
@@ -33,7 +33,7 @@ generate correct code without examples.
33
33
  Semiotic ships with everything an AI coding assistant needs to generate
34
34
  correct visualizations without trial and error:
35
35
 
36
- - **`semiotic/ai`** — a single import with all 28 chart components, optimized for LLM code generation
36
+ - **`semiotic/ai`** — a single import with all 37 chart components, optimized for LLM code generation
37
37
  - **`ai/schema.json`** — machine-readable prop schemas for every component
38
38
  - **`npx semiotic-mcp`** — an MCP server for tool-based chart rendering in any MCP client
39
39
  - **`npx semiotic-ai --doctor`** — validate component + props JSON from the command line with typo suggestions and anti-pattern detection
@@ -41,8 +41,10 @@ correct visualizations without trial and error:
41
41
  - **`CLAUDE.md`** — instruction files auto-synced for Claude, Cursor, Copilot, Windsurf, and Cline
42
42
  - **`llms.txt`** — machine-readable documentation following the emerging standard
43
43
 
44
- Every chart includes a built-in error boundary and dev-mode validation
45
- warnings with typo suggestions, so AI-generated code fails gracefully with
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
46
48
  actionable diagnostics instead of a blank screen.
47
49
 
48
50
  ### Beyond standard charts
@@ -59,6 +61,10 @@ monitoring dashboards.
59
61
  brush cross-filtering, and selection synchronization across any combination
60
62
  of chart types — zero wiring.
61
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
+
62
68
  **Statistical summaries.** Box plots, violin plots, swarm plots, histograms,
63
69
  LOESS smoothing, forecast with confidence envelopes, and anomaly detection.
64
70
  Marginal distribution graphics on scatterplot axes with a single prop.
@@ -159,6 +165,30 @@ import { ForceDirectedGraph, SankeyDiagram } from "semiotic"
159
165
  />
160
166
  ```
161
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
+
162
192
  ### Streaming System Monitor
163
193
 
164
194
  Live service topology with threshold alerting and click-to-inspect:
@@ -208,13 +238,14 @@ import { LineChart, BarChart } from "semiotic"
208
238
 
209
239
  | Category | Components |
210
240
  |---|---|
211
- | **XY** | `LineChart` `AreaChart` `StackedAreaChart` `Scatterplot` `ConnectedScatterplot` `BubbleChart` `Heatmap` |
212
- | **Categorical** | `BarChart` `StackedBarChart` `GroupedBarChart` `SwarmPlot` `BoxPlot` `Histogram` `ViolinPlot` `DotPlot` `PieChart` `DonutChart` |
241
+ | **XY** | `LineChart` `AreaChart` `StackedAreaChart` `Scatterplot` `ConnectedScatterplot` `BubbleChart` `Heatmap` `QuadrantChart` `MinimapChart` |
242
+ | **Categorical** | `BarChart` `StackedBarChart` `GroupedBarChart` `SwarmPlot` `BoxPlot` `Histogram` `ViolinPlot` `RidgelinePlot` `DotPlot` `PieChart` `DonutChart` |
213
243
  | **Network** | `ForceDirectedGraph` `ChordDiagram` `SankeyDiagram` `TreeDiagram` `Treemap` `CirclePack` `OrbitDiagram` |
244
+ | **Geo** | `ChoroplethMap` `ProportionalSymbolMap` `FlowMap` `DistanceCartogram` |
214
245
  | **Realtime** | `RealtimeLineChart` `RealtimeHistogram` `RealtimeSwarmChart` `RealtimeWaterfallChart` `RealtimeHeatmap` |
215
246
  | **Coordination** | `LinkedCharts` `ScatterplotMatrix` |
216
247
  | **Layout** | `ChartGrid` `ContextLayout` `CategoryColorProvider` |
217
- | **Frames** | `StreamXYFrame` `StreamOrdinalFrame` `StreamNetworkFrame` |
248
+ | **Frames** | `StreamXYFrame` `StreamOrdinalFrame` `StreamNetworkFrame` `StreamGeoFrame` |
218
249
 
219
250
  ### Vega-Lite Translation
220
251
 
@@ -250,10 +281,11 @@ for color, size, aggregation, and binning.
250
281
  Import only what you need:
251
282
 
252
283
  ```jsx
253
- import { LineChart } from "semiotic/xy" // 124 KB
254
- import { BarChart } from "semiotic/ordinal" // 100 KB
255
- import { ForceDirectedGraph } from "semiotic/network" // 104 KB
256
- 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)
257
289
  ```
258
290
 
259
291
  Granular entry points export only v3 Stream Frames and HOC charts — no legacy
@@ -301,12 +333,116 @@ const svg = renderToStaticSVG("xy", {
301
333
  })
302
334
  ```
303
335
 
336
+ ## MCP Server
337
+
338
+ Semiotic ships with an [MCP server](https://modelcontextprotocol.io) that lets AI coding assistants render charts, diagnose configuration problems, discover schemas, and get chart recommendations via tool calls.
339
+
340
+ ### Setup
341
+
342
+ Add to your MCP client config (e.g. `claude_desktop_config.json` for Claude Desktop):
343
+
344
+ ```json
345
+ {
346
+ "mcpServers": {
347
+ "semiotic": {
348
+ "command": "npx",
349
+ "args": ["semiotic-mcp"]
350
+ }
351
+ }
352
+ }
353
+ ```
354
+
355
+ No API keys or authentication required. The server runs locally via stdio.
356
+
357
+ ### Tools
358
+
359
+ | Tool | Description |
360
+ |------|-------------|
361
+ | **`renderChart`** | Render a Semiotic chart to static SVG. Supports the components returned by `getSchema` that are marked `[renderable]`. Pass `{ component: "LineChart", props: { data: [...], xAccessor: "x", yAccessor: "y" } }`. Returns SVG string or validation errors with fix suggestions. |
362
+ | **`getSchema`** | Return the prop schema for a specific component. Pass `{ component: "LineChart" }` to get its props, or omit `component` to list all 30 chart types. Use before `renderChart` to look up valid props. |
363
+ | **`suggestChart`** | Recommend chart types for a data sample. Pass `{ data: [{...}, ...] }` with 1–5 sample objects. Optionally include `intent` (`"comparison"`, `"trend"`, `"distribution"`, `"relationship"`, `"composition"`, `"geographic"`, `"network"`, `"hierarchy"`). Returns ranked suggestions with example props. |
364
+ | **`diagnoseConfig`** | Check a chart configuration for common problems — empty data, bad dimensions, missing accessors, wrong data shape, and more. Returns a human-readable diagnostic report with actionable fixes. |
365
+ | **`reportIssue`** | Generate a pre-filled GitHub issue URL for bug reports or feature requests. Pass `{ title: "...", body: "...", labels: ["bug"] }`. Returns a URL the user can open to submit. |
366
+
367
+ ### Example: get schema for a component
368
+
369
+ ```
370
+ Tool: getSchema
371
+ Args: { "component": "LineChart" }
372
+ → Returns: { "name": "LineChart", "description": "...", "parameters": { "properties": { "data": ..., "xAccessor": ..., ... } } }
373
+ ```
374
+
375
+ ### Example: suggest a chart for your data
376
+
377
+ ```
378
+ Tool: suggestChart
379
+ Args: {
380
+ "data": [
381
+ { "month": "Jan", "revenue": 120, "region": "East" },
382
+ { "month": "Feb", "revenue": 180, "region": "West" }
383
+ ]
384
+ }
385
+ → Returns:
386
+ 1. BarChart (high confidence) — categorical field (region) with values (revenue)
387
+ 2. StackedBarChart (medium confidence) — two categorical fields (month, region)
388
+ 3. DonutChart (medium confidence) — 2 categories — proportional composition
389
+ ```
390
+
391
+ ### Example: render a chart
392
+
393
+ ```
394
+ Tool: renderChart
395
+ Args: {
396
+ "component": "BarChart",
397
+ "props": {
398
+ "data": [
399
+ { "category": "Q1", "revenue": 120 },
400
+ { "category": "Q2", "revenue": 180 },
401
+ { "category": "Q3", "revenue": 150 }
402
+ ],
403
+ "categoryAccessor": "category",
404
+ "valueAccessor": "revenue"
405
+ }
406
+ }
407
+ → Returns: <svg>...</svg>
408
+ ```
409
+
410
+ ### Example: diagnose a broken config
411
+
412
+ ```
413
+ Tool: diagnoseConfig
414
+ Args: { "component": "LineChart", "props": { "data": [] } }
415
+ → Returns: ✗ [EMPTY_DATA] data is an empty array — Fix: provide at least one data point
416
+ ```
417
+
418
+ ### Example: report an issue
419
+
420
+ ```
421
+ Tool: reportIssue
422
+ Args: {
423
+ "title": "Bug: BarChart tooltip shows undefined for custom accessor",
424
+ "body": "When using valueAccessor='amount', tooltip displays 'undefined'.\n\ndiagnoseConfig output: ✓ no issues detected.",
425
+ "labels": ["bug"]
426
+ }
427
+ → Returns: Open this URL to submit the issue: https://github.com/nteract/semiotic/issues/new?...
428
+ ```
429
+
430
+ ### CLI alternative
431
+
432
+ For quick validation without an MCP client:
433
+
434
+ ```bash
435
+ npx semiotic-ai --doctor # validate component + props JSON
436
+ npx semiotic-ai --schema # dump all chart schemas
437
+ npx semiotic-ai --compact # compact schema (fewer tokens)
438
+ ```
439
+
304
440
  ## Documentation
305
441
 
306
442
  [Interactive docs and examples](https://semiotic.nteract.io)
307
443
 
308
444
  - [Getting Started](https://semiotic.nteract.io/getting-started)
309
- - [Charts](https://semiotic.nteract.io/charts) — all 28 chart types with live examples
445
+ - [Charts](https://semiotic.nteract.io/charts) — all 37 chart types with live examples
310
446
  - [Frames](https://semiotic.nteract.io/frames) — full Frame API reference
311
447
  - [Features](https://semiotic.nteract.io/features) — axes, annotations, tooltips, styling, Vega-Lite translator
312
448
  - [Cookbook](https://semiotic.nteract.io/cookbook) — advanced patterns and recipes
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.COMPONENT_REGISTRY = void 0;
4
4
  const ai_1 = require("semiotic/ai");
5
+ const geo_1 = require("semiotic/geo");
5
6
  exports.COMPONENT_REGISTRY = {
6
7
  LineChart: { component: ai_1.LineChart, category: "xy" },
7
8
  AreaChart: { component: ai_1.AreaChart, category: "xy" },
@@ -25,4 +26,8 @@ exports.COMPONENT_REGISTRY = {
25
26
  Treemap: { component: ai_1.Treemap, category: "network" },
26
27
  CirclePack: { component: ai_1.CirclePack, category: "network" },
27
28
  OrbitDiagram: { component: ai_1.OrbitDiagram, category: "network" },
29
+ ChoroplethMap: { component: geo_1.ChoroplethMap, category: "geo" },
30
+ ProportionalSymbolMap: { component: geo_1.ProportionalSymbolMap, category: "geo" },
31
+ FlowMap: { component: geo_1.FlowMap, category: "geo" },
32
+ DistanceCartogram: { component: geo_1.DistanceCartogram, category: "geo" },
28
33
  };