semiotic 3.1.2 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +134 -216
- package/LICENSE +197 -10
- package/README.md +1 -0
- package/ai/dist/componentRegistry.js +6 -0
- package/ai/dist/mcp-server.js +115 -5
- package/ai/examples.md +93 -0
- package/ai/schema.json +3916 -878
- package/ai/system-prompt.md +27 -0
- package/dist/components/ThemeProvider.d.ts +5 -3
- package/dist/components/charts/geo/ChoroplethMap.d.ts +1 -1
- package/dist/components/charts/index.d.ts +8 -1
- package/dist/components/charts/ordinal/BarChart.d.ts +1 -0
- package/dist/components/charts/ordinal/BoxPlot.d.ts +1 -0
- package/dist/components/charts/ordinal/DonutChart.d.ts +1 -0
- package/dist/components/charts/ordinal/DotPlot.d.ts +1 -0
- package/dist/components/charts/ordinal/FunnelChart.d.ts +55 -0
- package/dist/components/charts/ordinal/GroupedBarChart.d.ts +1 -0
- package/dist/components/charts/ordinal/Histogram.d.ts +1 -0
- package/dist/components/charts/ordinal/PieChart.d.ts +1 -0
- package/dist/components/charts/ordinal/RidgelinePlot.d.ts +1 -0
- package/dist/components/charts/ordinal/StackedBarChart.d.ts +1 -0
- package/dist/components/charts/ordinal/SwarmPlot.d.ts +1 -0
- package/dist/components/charts/ordinal/ViolinPlot.d.ts +1 -0
- package/dist/components/charts/shared/colorManipulation.d.ts +15 -0
- package/dist/components/charts/shared/formatUtils.d.ts +28 -0
- package/dist/components/charts/shared/hatchPattern.d.ts +35 -0
- package/dist/components/charts/shared/hooks.d.ts +16 -1
- package/dist/components/charts/shared/legendUtils.d.ts +2 -1
- package/dist/components/charts/shared/selectionUtils.d.ts +11 -0
- package/dist/components/charts/shared/statisticalOverlays.d.ts +49 -5
- package/dist/components/charts/shared/types.d.ts +4 -1
- package/dist/components/charts/xy/Heatmap.d.ts +1 -1
- package/dist/components/charts/xy/MultiAxisLineChart.d.ts +71 -0
- package/dist/components/realtime/types.d.ts +2 -0
- package/dist/components/semiotic-ai.d.ts +3 -0
- package/dist/components/semiotic-ordinal.d.ts +3 -0
- package/dist/components/semiotic-themes.d.ts +64 -0
- package/dist/components/semiotic-xy.d.ts +1 -0
- package/dist/components/semiotic.d.ts +11 -5
- package/dist/components/store/ThemeStore.d.ts +22 -2
- package/dist/components/stream/OrdinalSVGOverlay.d.ts +1 -0
- package/dist/components/stream/PipelineStore.d.ts +2 -0
- package/dist/components/stream/SVGOverlay.d.ts +5 -3
- package/dist/components/stream/accessorUtils.d.ts +14 -0
- package/dist/components/stream/networkTypes.d.ts +2 -0
- package/dist/components/stream/ordinalSceneBuilders/barFunnelScene.d.ts +27 -0
- package/dist/components/stream/ordinalSceneBuilders/funnelScene.d.ts +26 -0
- package/dist/components/stream/ordinalTypes.d.ts +16 -2
- package/dist/components/stream/renderers/barFunnelCanvasRenderer.d.ts +12 -0
- package/dist/components/stream/renderers/trapezoidCanvasRenderer.d.ts +15 -0
- package/dist/components/stream/sceneUtils.d.ts +10 -0
- package/dist/components/stream/types.d.ts +10 -3
- package/dist/geo.min.js +1 -1
- package/dist/geo.module.min.js +1 -1
- package/dist/network.min.js +1 -1
- package/dist/network.module.min.js +1 -1
- package/dist/ordinal.min.js +1 -1
- package/dist/ordinal.module.min.js +1 -1
- package/dist/realtime.min.js +1 -1
- package/dist/realtime.module.min.js +1 -1
- package/dist/semiotic-ai-statisticalOverlays-C2PPlmXv.js +1 -0
- package/dist/semiotic-ai.d.ts +3 -0
- package/dist/semiotic-ai.min.js +1 -1
- package/dist/semiotic-ai.module.min.js +1 -1
- package/dist/semiotic-ordinal.d.ts +3 -0
- package/dist/semiotic-statisticalOverlays-D8LhSbQt.js +1 -0
- package/dist/semiotic-themes.d.ts +64 -0
- package/dist/semiotic-themes.min.js +1 -0
- package/dist/semiotic-themes.module.min.js +1 -0
- package/dist/semiotic-xy.d.ts +1 -0
- package/dist/semiotic.d.ts +11 -5
- package/dist/semiotic.min.js +1 -1
- package/dist/semiotic.module.min.js +1 -1
- package/dist/server.min.js +1 -1
- package/dist/server.module.min.js +1 -1
- package/dist/xy-statisticalOverlays-C2PPlmXv.js +1 -0
- package/dist/xy.min.js +1 -1
- package/dist/xy.module.min.js +1 -1
- package/package.json +24 -5
- package/dist/semiotic-ai-statisticalOverlays-C1f7TYyD.js +0 -1
- package/dist/semiotic-statisticalOverlays-C1f7TYyD.js +0 -1
- package/dist/xy-statisticalOverlays-C1f7TYyD.js +0 -1
package/CLAUDE.md
CHANGED
|
@@ -2,49 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
## Quick Start
|
|
4
4
|
- Install: `npm install semiotic`
|
|
5
|
-
- Import: `semiotic`, `semiotic/xy`, `semiotic/ordinal`, `semiotic/network`, `semiotic/geo`, `semiotic/realtime`, `semiotic/ai`, `semiotic/data`, `semiotic/server`
|
|
5
|
+
- Import: `semiotic`, `semiotic/xy`, `semiotic/ordinal`, `semiotic/network`, `semiotic/geo`, `semiotic/realtime`, `semiotic/ai`, `semiotic/data`, `semiotic/server`, `semiotic/themes`
|
|
6
6
|
- CLI: `npx semiotic-ai [--schema|--compact|--examples|--doctor]`
|
|
7
7
|
- MCP: `npx semiotic-mcp`
|
|
8
|
-
- Every HOC has a built-in error boundary
|
|
8
|
+
- Every HOC has a built-in error boundary 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**
|
|
12
|
+
- **Always use HOC charts** unless you need control they don't expose. Stream Frames (`StreamNetworkFrame`, `StreamXYFrame`, `StreamOrdinalFrame`, `StreamGeoFrame`) are low-level — they pass `RealtimeNode`/`RealtimeEdge` wrappers in callbacks, not your data.
|
|
13
13
|
- Every HOC accepts `frameProps` to pass through. TypeScript `strict: true`.
|
|
14
14
|
|
|
15
15
|
## Common Props (all HOCs)
|
|
16
|
-
`title`, `width` (600), `height` (400), `responsiveWidth`, `responsiveHeight`, `margin`, `className`, `enableHover` (true), `tooltip` (boolean | `(datum) => ReactNode` |
|
|
16
|
+
`title`, `width` (600), `height` (400), `responsiveWidth`, `responsiveHeight`, `margin`, `className`, `color` (uniform fill — overrides theme/colorScheme), `enableHover` (true), `tooltip` (boolean | `(datum) => ReactNode` | `{ fields?, title?, format?, style? }`), `showLegend`, `showGrid` (false), `frameProps`, `onObservation`, `chartId`, `loading` (false), `emptyContent`, `legendInteraction` ("none"|"highlight"|"isolate"), `legendPosition` ("right"|"left"|"top"|"bottom"), `emphasis` ("primary"|"secondary"), `annotations` (array)
|
|
17
17
|
|
|
18
|
-
|
|
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).
|
|
18
|
+
`onObservation` receives `{ type: "hover"|"hover-end"|"click"|"brush"|"selection", datum?, x?, y?, timestamp, chartType, chartId }`. The `datum` is your original data object.
|
|
30
19
|
|
|
31
20
|
## XY Charts (`semiotic/xy`)
|
|
32
21
|
|
|
33
|
-
**LineChart** — `data`, `xAccessor` ("x"), `yAccessor` ("y"), `lineBy`, `lineDataAccessor` ("coordinates"), `colorBy`, `colorScheme`, `curve`, `lineWidth` (2), `showPoints`, `pointRadius` (3), `fillArea`, `areaOpacity` (0.3), `anomaly
|
|
34
|
-
|
|
22
|
+
**LineChart** — `data`, `xAccessor` ("x"), `yAccessor` ("y"), `lineBy`, `lineDataAccessor` ("coordinates"), `colorBy`, `colorScheme`, `curve`, `lineWidth` (2), `showPoints`, `pointRadius` (3), `fillArea`, `areaOpacity` (0.3), `anomaly`, `forecast`, `directLabel`, `gapStrategy` ("break"|"interpolate"|"zero"), `xScaleType`/`yScaleType` ("linear"|"log")
|
|
35
23
|
**AreaChart** — LineChart props + `areaBy`, `y0Accessor` (band/ribbon), `gradientFill` (boolean|{topOpacity,bottomOpacity}), `areaOpacity` (0.7), `showLine` (true)
|
|
36
|
-
|
|
37
|
-
**StackedAreaChart** — flat array data + `areaBy` (required, groups into stacked areas), `colorBy`, `normalize` (false). Do NOT use `lineBy` or `lineDataAccessor` — those are LineChart props.
|
|
38
|
-
|
|
24
|
+
**StackedAreaChart** — flat array + `areaBy` (required), `colorBy`, `normalize`. Do NOT use `lineBy` or `lineDataAccessor`.
|
|
39
25
|
**Scatterplot** — `data`, `xAccessor`, `yAccessor`, `colorBy`, `sizeBy`, `sizeRange`, `pointRadius` (5), `pointOpacity` (0.8), `marginalGraphics`
|
|
40
|
-
|
|
41
26
|
**BubbleChart** — Scatterplot + `sizeBy` (required), `sizeRange` ([5,40]), `bubbleOpacity` (0.6)
|
|
42
|
-
|
|
43
|
-
**
|
|
44
|
-
|
|
45
|
-
**
|
|
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.
|
|
27
|
+
**ConnectedScatterplot** — `data`, `xAccessor`, `yAccessor`, `orderAccessor` (sequencing field), `pointRadius` (4)
|
|
28
|
+
**QuadrantChart** — Scatterplot + `quadrants` (required: `{ topRight, topLeft, bottomRight, bottomLeft }` each `{ label, color, opacity? }`), `xCenter`, `yCenter`, `centerlineStyle`, `showQuadrantLabels` (true). Supports push API.
|
|
29
|
+
**MultiAxisLineChart** — Dual Y-axis. `data`, `xAccessor` ("x"), `series` (required: array of `{ yAccessor, label?, color?, format?, extent? }`), `colorScheme`, `curve` ("monotoneX"), `lineWidth` (2). Data unitized to [0,1] internally; left axis=series[0], right axis=series[1] in original units. For push API, provide `series[].extent` for stable unitization. Falls back to standard multi-line if not exactly 2 series.
|
|
30
|
+
**Heatmap** — `data`, `xAccessor`, `yAccessor`, `valueAccessor`, `colorScheme` ("blues"|"reds"|"greens"|"viridis"), `showValues`, `cellBorderColor`. Supports string/categorical axes.
|
|
48
31
|
|
|
49
32
|
## Ordinal Charts (`semiotic/ordinal`)
|
|
50
33
|
|
|
@@ -53,284 +36,219 @@ The `datum` field contains your original data object (not a wrapper).
|
|
|
53
36
|
**GroupedBarChart** — + `groupBy` (required), `barPadding` (60)
|
|
54
37
|
**SwarmPlot** — `data`, `categoryAccessor`, `valueAccessor`, `colorBy`, `sizeBy`, `pointRadius`, `pointOpacity`
|
|
55
38
|
**BoxPlot** — + `showOutliers`, `outlierRadius`
|
|
56
|
-
**Histogram** — + `bins` (25), `relative`. Always horizontal. `categoryAccessor`
|
|
39
|
+
**Histogram** — + `bins` (25), `relative`. Always horizontal. `categoryAccessor` optional (defaults to `"category"`).
|
|
57
40
|
**ViolinPlot** — + `bins`, `curve`, `showIQR`
|
|
41
|
+
**RidgelinePlot** — + `bins`, `amplitude` (1.5, unitless multiplier of lane width)
|
|
58
42
|
**DotPlot** — + `sort` (true), `dotRadius`, `showGrid` default true
|
|
59
43
|
**PieChart** — `data`, `categoryAccessor`, `valueAccessor`, `colorBy`, `startAngle`, `slicePadding`
|
|
60
|
-
**DonutChart** — PieChart + `innerRadius` (60), `centerContent` (ReactNode
|
|
44
|
+
**DonutChart** — PieChart + `innerRadius` (60), `centerContent` (ReactNode)
|
|
45
|
+
**FunnelChart** — `data`, `stepAccessor` ("step"), `valueAccessor` ("value"), `categoryAccessor` (optional), `colorBy`, `connectorOpacity` (0.3), `orientation` ("horizontal"|"vertical"). Horizontal: centered bars with trapezoid connectors. Vertical: bars with diagonal hatch for dropoff. Multi-category: `categoryAccessor="channel"` mirrors (horizontal) or groups (vertical).
|
|
46
|
+
|
|
47
|
+
All ordinal HOCs support `colorBy` and `colorScheme`. `showCategoryTicks` (default true) hides per-tick labels when false — margins auto-adjust. For distribution charts with `colorBy`, set `showCategoryTicks={false}` since the legend identifies categories.
|
|
61
48
|
|
|
62
49
|
## Network Charts (`semiotic/network`)
|
|
63
50
|
|
|
64
|
-
**ForceDirectedGraph** — `nodes`, `edges`, `nodeIDAccessor`, `sourceAccessor`, `targetAccessor`, `colorBy`, `colorScheme`, `nodeSize` (number|string|fn), `nodeSizeRange`, `edgeWidth`, `edgeColor`, `edgeOpacity`, `iterations` (300), `forceStrength` (0.1), `showLabels`, `nodeLabel`, `
|
|
65
|
-
**SankeyDiagram** — `edges`, `nodes`, `valueAccessor`, `
|
|
51
|
+
**ForceDirectedGraph** — `nodes`, `edges`, `nodeIDAccessor`, `sourceAccessor`, `targetAccessor`, `colorBy`, `colorScheme`, `nodeSize` (number|string|fn), `nodeSizeRange`, `edgeWidth`, `edgeColor`, `edgeOpacity`, `iterations` (300), `forceStrength` (0.1), `showLabels`, `nodeLabel`, `legendInteraction`
|
|
52
|
+
**SankeyDiagram** — `edges`, `nodes`, `valueAccessor`, `nodeIdAccessor` ("id"), `sourceAccessor` ("source"), `targetAccessor` ("target"), `colorBy`, `edgeColorBy` ("source"|"target"|"gradient"|fn), `orientation`, `nodeAlign`, `nodeWidth`, `nodePaddingRatio`, `nodeLabel`, `showLabels`, `edgeOpacity`
|
|
66
53
|
**ChordDiagram** — `edges`, `nodes`, `valueAccessor`, `edgeColorBy`, `padAngle`, `groupWidth`, `showLabels`
|
|
67
54
|
**TreeDiagram** — `data` (root), `layout`, `orientation`, `childrenAccessor`, `colorBy`, `colorByDepth`, `edgeStyle`
|
|
68
55
|
**Treemap** — `data` (root), `childrenAccessor`, `valueAccessor`, `colorBy`, `colorByDepth`, `showLabels`, `labelMode`
|
|
69
56
|
**CirclePack** — `data` (root), `childrenAccessor`, `valueAccessor`, `colorBy`, `colorByDepth`, `circleOpacity`
|
|
70
|
-
**OrbitDiagram** — animated radial/orbital hierarchy.
|
|
57
|
+
**OrbitDiagram** — animated radial/orbital hierarchy. `data` (root), `childrenAccessor`, `nodeIdAccessor`, `orbitMode` ("flat"|"solar"|"atomic"|number[]), `speed` (0.25), `revolution`, `eccentricity`, `orbitSize`, `nodeRadius`, `showRings`, `showLabels`, `animated` (true), `colorBy`, `colorByDepth`. For static radial trees, use `TreeDiagram layout="radial"`.
|
|
71
58
|
|
|
72
59
|
## Geo Charts (`semiotic/geo`)
|
|
73
60
|
|
|
74
|
-
|
|
61
|
+
Import from `semiotic/geo` — NOT `semiotic` — to avoid pulling d3-geo into non-geo bundles.
|
|
75
62
|
|
|
76
|
-
**ChoroplethMap** — `areas` (GeoJSON Feature[] or
|
|
63
|
+
**ChoroplethMap** — `areas` (GeoJSON Feature[] or "world-110m"), `valueAccessor`, `colorScheme`, `areaOpacity` (1), `projection` ("equalEarth"), `graticule`, `tooltip`, `showLegend`
|
|
77
64
|
**ProportionalSymbolMap** — `points`, `xAccessor` ("lon"), `yAccessor` ("lat"), `sizeBy`, `sizeRange` ([3,30]), `colorBy`, `areas` (optional background), `projection`
|
|
78
|
-
**FlowMap** — `flows
|
|
79
|
-
**DistanceCartogram** — `points`, `center` (id
|
|
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()`.
|
|
65
|
+
**FlowMap** — `flows`, `nodes`, `xAccessor`, `yAccessor`, `nodeIdAccessor`, `valueAccessor`, `edgeColorBy`, `edgeOpacity` (0.6), `edgeWidthRange` ([1,8]), `lineType` ("geo"|"line"), `showParticles`, `particleStyle`
|
|
66
|
+
**DistanceCartogram** — `points`, `center` (id), `costAccessor`, `strength` (0-1), `lineMode`, `showRings` (true|false|number[]), `ringStyle`, `showNorth`, `costLabel`, `transition`, `pointRadius`
|
|
84
67
|
|
|
85
|
-
|
|
68
|
+
All geo HOCs: `fitPadding` (0–1), `zoomable` (defaults true with tileURL), `zoomExtent` ([1,8]), `onZoom`, `dragRotate` (true for orthographic), `graticule`, `tileURL`, `tileAttribution`, `tileCacheSize`, `selection`, `linkedHover`, `onObservation`
|
|
86
69
|
|
|
87
|
-
**
|
|
88
|
-
|
|
89
|
-
**
|
|
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.
|
|
70
|
+
**Tiles**: `tileURL` accepts string template (`{z}/{x}/{y}`) or function. Mercator only. OSM tiles are dev-only — use commercial provider with env var key in production.
|
|
71
|
+
**Zoom**: Imperative: `ref.current.getZoom()`, `ref.current.resetZoom()`.
|
|
72
|
+
**Reference geography**: `resolveReferenceGeography("world-110m"|"world-50m"|"land-110m"|"land-50m")` returns GeoJSON features.
|
|
73
|
+
**mergeData(features, data, { featureKey, dataKey })** — join data into GeoJSON by key. World-atlas uses ISO numeric codes as `id`.
|
|
96
74
|
|
|
97
75
|
```jsx
|
|
98
|
-
// World choropleth with reference geography + data joining
|
|
99
76
|
import { ChoroplethMap, resolveReferenceGeography, mergeData } from "semiotic/geo"
|
|
100
77
|
const world = await resolveReferenceGeography("world-110m")
|
|
101
78
|
const areas = mergeData(world, gdpData, { featureKey: "id", dataKey: "id" })
|
|
102
79
|
<ChoroplethMap areas={areas} valueAccessor="gdpPerCapita" colorScheme="viridis"
|
|
103
80
|
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
81
|
```
|
|
130
82
|
|
|
83
|
+
**StreamGeoFrame** — low-level frame. Push API: `ref.current.push(datum)`, `.pushMany()`, `.clear()`. Props: `projection`, `areas`, `points`, `lines`, `xAccessor`, `yAccessor`, `areaStyle`, `pointStyle`, `lineStyle`, `graticule`, `zoomable`, `decay`, `pulse`, `transition`.
|
|
84
|
+
|
|
131
85
|
## Realtime Charts (`semiotic/realtime`)
|
|
132
86
|
|
|
133
87
|
Push API: `chartRef.current.push({ time, value })`
|
|
134
88
|
|
|
135
|
-
**IMPORTANT**: All pushed data must include a time field (default: `"time"`).
|
|
89
|
+
**IMPORTANT**: All pushed data must include a time field (default: `"time"`). Set `timeAccessor` if your field differs. Without valid time field, charts render blank.
|
|
136
90
|
|
|
137
|
-
|
|
91
|
+
**RealtimeLineChart** — `timeAccessor` ("time"), `valueAccessor` ("value"), `windowSize` (200), `windowMode`, `stroke`, `strokeWidth`
|
|
92
|
+
**RealtimeHistogram** — `binSize` (required), `timeAccessor`, `valueAccessor`, `categoryAccessor`, `colors`
|
|
93
|
+
**RealtimeSwarmChart** — `timeAccessor`, `valueAccessor`, `categoryAccessor`, `radius`, `opacity`
|
|
94
|
+
**RealtimeWaterfallChart** — `timeAccessor`, `valueAccessor`, `positiveColor`, `negativeColor`
|
|
95
|
+
**RealtimeHeatmap** — `timeAccessor`, `valueAccessor`, `heatmapXBins`, `heatmapYBins`, `aggregation`
|
|
96
|
+
**Streaming Sankey** — `StreamNetworkFrame` with `chartType="sankey"`, `showParticles`, `particleStyle`. Push individual edges: `ref.current.push({ source, target, value })`.
|
|
138
97
|
|
|
139
|
-
|
|
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.
|
|
98
|
+
Encoding: `decay`, `pulse`, `transition`, `staleness` — compose freely on all streaming charts.
|
|
145
99
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
### Realtime data shape
|
|
100
|
+
### Push API on HOC charts
|
|
101
|
+
Most HOC charts support push via `forwardRef`. **Omit** `data`/`nodes`/`edges` — do NOT pass `data={[]}`.
|
|
149
102
|
```jsx
|
|
150
|
-
|
|
151
|
-
ref.current.push({
|
|
152
|
-
ref.current.
|
|
153
|
-
ref.current.
|
|
103
|
+
const ref = useRef()
|
|
104
|
+
ref.current.push({ x: 1, y: 2 }) // single
|
|
105
|
+
ref.current.pushMany([...points]) // batch
|
|
106
|
+
ref.current.clear() // reset
|
|
107
|
+
ref.current.getData() // read
|
|
108
|
+
<Scatterplot ref={ref} xAccessor="x" yAccessor="y" />
|
|
154
109
|
```
|
|
110
|
+
Supported: all XY, ordinal, network (Force, Sankey, Chord), geo point charts. **Not supported**: hierarchy charts (Tree, Treemap, CirclePack, Orbit), ChoroplethMap, FlowMap, ScatterplotMatrix.
|
|
155
111
|
|
|
156
|
-
|
|
157
|
-
|
|
112
|
+
## Stream Frame Callbacks (advanced)
|
|
113
|
+
Frame callbacks (`nodeStyle`, `edgeStyle`, `nodeSize` as fn) receive `RealtimeNode`/`RealtimeEdge` wrappers. Access original data via `.data`:
|
|
158
114
|
```jsx
|
|
159
|
-
|
|
160
|
-
|
|
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" />
|
|
115
|
+
// WRONG: nodeSize={(d) => d.weight} — d.weight is undefined
|
|
116
|
+
// RIGHT: nodeSize={(d) => d.data?.weight} — or use string: nodeSize="weight"
|
|
165
117
|
```
|
|
166
|
-
|
|
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.
|
|
118
|
+
Same applies to `frameProps` style functions on HOCs. `customHoverBehavior`/`customClickBehavior` receive `{ type, data, x, y } | null`. `tooltipContent` receives `{ type, data }`.
|
|
169
119
|
|
|
170
|
-
##
|
|
171
|
-
|
|
120
|
+
## Hover Indicator
|
|
121
|
+
The hover dot automatically matches the hovered element's color (line stroke, point fill, etc.). Override via `frameProps`:
|
|
172
122
|
```jsx
|
|
173
|
-
|
|
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
|
|
123
|
+
<LineChart frameProps={{ hoverAnnotation: { pointColor: "#ff0000" } }} />
|
|
178
124
|
```
|
|
179
|
-
|
|
180
|
-
`tooltipContent` receives `{ type: "node"|"edge", data: <your raw object> }`.
|
|
125
|
+
Fallback chain: `pointColor` → element color → `--semiotic-primary` CSS var → `#007bff`.
|
|
181
126
|
|
|
182
127
|
## Coordinated Views
|
|
183
128
|
|
|
184
|
-
**LinkedCharts** —
|
|
185
|
-
**CategoryColorProvider** —
|
|
129
|
+
**LinkedCharts** — `selections` (resolution: "union"|"intersect"|"crossfilter"), `showLegend`, `legendPosition`, `legendInteraction`, `legendSelectionName`, `legendField`
|
|
130
|
+
**CategoryColorProvider** — `colors` (map) or `categories` + `colorScheme`
|
|
186
131
|
Chart props: `selection`, `linkedHover`, `linkedBrush`. Hooks: `useSelection`, `useLinkedHover`, `useBrushSelection`, `useFilteredData`
|
|
187
132
|
**ScatterplotMatrix** — `data`, `fields`, `colorBy`, `cellSize`, `hoverMode`, `brushMode`
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
195
|
-
## Layout & Composition
|
|
196
|
-
|
|
197
|
-
**ChartGrid** — CSS Grid layout. `columns` (number|"auto"), `minCellWidth` (300), `gap` (16). Children with `emphasis="primary"` span two columns.
|
|
198
|
-
**ContextLayout** — primary + context panel. `context` (ReactNode), `position`, `contextSize` (250)
|
|
133
|
+
**ChartContainer** — `title`, `subtitle`, `height` (400), `width` ("100%"), `status`, `loading`, `error`, `errorBoundary`, `actions` ({ export, fullscreen, copyConfig }), `controls`
|
|
134
|
+
**ChartGrid** — `columns` (number|"auto"), `minCellWidth` (300), `gap` (16). `emphasis="primary"` spans two columns.
|
|
135
|
+
**ContextLayout** — `context` (ReactNode), `position`, `contextSize` (250)
|
|
199
136
|
|
|
200
137
|
## Key Patterns
|
|
201
138
|
|
|
202
139
|
```jsx
|
|
203
|
-
//
|
|
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
|
|
140
|
+
// Cross-highlighting dashboard
|
|
221
141
|
<CategoryColorProvider categories={["North", "South", "East"]}>
|
|
222
142
|
<LinkedCharts>
|
|
223
143
|
<ChartGrid columns={2}>
|
|
224
144
|
<LineChart data={d} colorBy="region" linkedHover={{ name: "hl", fields: ["region"] }} selection={{ name: "hl" }} emphasis="primary" responsiveWidth />
|
|
225
145
|
<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 />
|
|
227
146
|
</ChartGrid>
|
|
228
147
|
</LinkedCharts>
|
|
229
148
|
</CategoryColorProvider>
|
|
230
149
|
|
|
231
|
-
// Forecast + anomaly
|
|
150
|
+
// Forecast + anomaly
|
|
232
151
|
<LineChart data={ts} xAccessor="time" yAccessor="value"
|
|
233
152
|
forecast={{ trainEnd: 60, steps: 15, confidence: 0.95 }}
|
|
234
153
|
anomaly={{ threshold: 2 }} />
|
|
235
154
|
|
|
236
|
-
//
|
|
155
|
+
// Pre-computed forecast bounds
|
|
237
156
|
<LineChart data={ml} xAccessor="time" yAccessor="value"
|
|
238
157
|
forecast={{ isTraining: "isTraining", isForecast: "isForecast", isAnomaly: "isAnomaly", upperBounds: "upper", lowerBounds: "lower" }} />
|
|
239
158
|
|
|
240
|
-
//
|
|
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:
|
|
159
|
+
// Percentile band — layer AreaChart + LineChart
|
|
247
160
|
<>
|
|
248
161
|
<AreaChart data={d} xAccessor="x" yAccessor="p95" y0Accessor="p5"
|
|
249
162
|
showLine={false} areaOpacity={0.3} gradientFill />
|
|
250
163
|
<LineChart data={d} xAccessor="x" yAccessor="p50" lineWidth={2} />
|
|
251
164
|
</>
|
|
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
|
|
256
|
-
const ref = useRef()
|
|
257
|
-
ref.current.push({ time: Date.now(), value: 42 })
|
|
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
165
|
|
|
265
|
-
// Streaming sankey with particles
|
|
166
|
+
// Streaming sankey with particles
|
|
266
167
|
const sankeyRef = useRef()
|
|
267
|
-
sankeyRef.current.push({ source: "Web", target: "API", value: 1 })
|
|
268
|
-
sankeyRef
|
|
269
|
-
{
|
|
270
|
-
{
|
|
271
|
-
|
|
272
|
-
|
|
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
|
|
168
|
+
sankeyRef.current.push({ source: "Web", target: "API", value: 1 })
|
|
169
|
+
<StreamNetworkFrame ref={sankeyRef} chartType="sankey"
|
|
170
|
+
showParticles particleStyle={{ radius: 2, colorBy: "source" }}
|
|
171
|
+
width={600} height={400} />
|
|
172
|
+
|
|
173
|
+
// SSR
|
|
281
174
|
import { renderOrdinalToStaticSVG } from "semiotic/server"
|
|
282
|
-
const svg = renderOrdinalToStaticSVG({
|
|
283
|
-
data, categoryAccessor: "category", valueAccessor: "value", width: 600, height: 400
|
|
284
|
-
})
|
|
175
|
+
const svg = renderOrdinalToStaticSVG({ data, categoryAccessor: "cat", valueAccessor: "val", width: 600, height: 400 })
|
|
285
176
|
```
|
|
286
177
|
|
|
287
178
|
## Annotations
|
|
288
|
-
|
|
179
|
+
|
|
180
|
+
All HOCs accept `annotations` (array). Coordinates use your data field names. Network/orbit use `nodeId`.
|
|
181
|
+
|
|
182
|
+
**Positioning**: `widget` (React content at data coords — v3 replacement for v2 `htmlAnnotationRules`; props: `content`, `dx`, `dy`, `width`, `height`, `anchor`), `label` (callout with connector), `callout` (circle + label), `text` (plain text), `bracket`
|
|
183
|
+
**Reference lines**: `y-threshold` (`value`, `label`, `color`), `x-threshold`, `band` (`y0`, `y1`, `label`, `fill`)
|
|
184
|
+
**Enclosures**: `enclose` (circle around `coordinates`), `rect-enclose`, `highlight` (`filter` fn or `field`+`value`)
|
|
185
|
+
**Statistical** (XY): `trend` (`method`: linear/polynomial/loess), `envelope`, `anomaly-band`, `forecast`
|
|
186
|
+
**Streaming anchors**: `"fixed"` (default), `"latest"` (tracks newest datum), `"sticky"` (freezes when evicted)
|
|
187
|
+
|
|
188
|
+
Custom rendering: `frameProps.svgAnnotationRules = (annotation, index, context) => ReactNode | null`. Context has `scales`, `width`, `height`, `data`. Colors inherit from theme (`--semiotic-primary`, `--semiotic-text-secondary`).
|
|
189
|
+
|
|
289
190
|
```jsx
|
|
290
|
-
|
|
291
|
-
|
|
191
|
+
<LineChart data={data} xAccessor="time" yAccessor="latency"
|
|
192
|
+
annotations={[
|
|
193
|
+
{ type: "y-threshold", value: 200, label: "SLA limit", color: "#e45050" },
|
|
194
|
+
{ type: "widget", time: 42, latency: 850, dy: -30, content: <span>Incident</span> },
|
|
195
|
+
]} />
|
|
292
196
|
```
|
|
293
197
|
|
|
294
|
-
##
|
|
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`.
|
|
198
|
+
## Theming
|
|
301
199
|
|
|
302
|
-
|
|
303
|
-
- `onObservation` — structured events (hover, click, brush, selection) on all HOCs
|
|
304
|
-
- `useChartObserver` — aggregates observations across LinkedCharts
|
|
305
|
-
- `toConfig`/`fromConfig`/`toURL`/`fromURL`/`copyConfig`/`configToJSX` — chart state serialization
|
|
306
|
-
- `DetailsPanel` — click-driven detail panel inside `ChartContainer`
|
|
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.)
|
|
309
|
-
- `ChartErrorBoundary` — error boundary
|
|
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)
|
|
200
|
+
Charts are themeable via CSS custom properties on any ancestor element. Key vars: `--semiotic-bg`, `--semiotic-text`, `--semiotic-text-secondary`, `--semiotic-border`, `--semiotic-grid`, `--semiotic-primary`, `--semiotic-focus`, `--semiotic-font-family`, `--semiotic-border-radius`, `--semiotic-tooltip-bg`/`text`/`radius`/`font-size`/`shadow`, `--semiotic-selection-color`/`opacity`, `--semiotic-diverging`.
|
|
312
201
|
|
|
313
|
-
|
|
202
|
+
```jsx
|
|
203
|
+
import { ThemeProvider } from "semiotic"
|
|
204
|
+
<ThemeProvider theme="tufte"> {/* Named preset */}
|
|
205
|
+
<ThemeProvider theme={{ colors: { primary: "#ff6b6b", categorical: [...] } }}> {/* Custom */}
|
|
206
|
+
```
|
|
314
207
|
|
|
315
|
-
|
|
208
|
+
Presets: `light`, `dark`, `high-contrast`, `pastels`, `pastels-dark`, `bi-tool`, `bi-tool-dark`, `italian`, `italian-dark`, `tufte`, `tufte-dark`, `journalist`, `journalist-dark`, `playful`, `playful-dark`.
|
|
316
209
|
|
|
317
|
-
|
|
210
|
+
Serialization (`semiotic/themes`): `themeToCSS(theme, selector)`, `themeToTokens(theme)`, `resolveThemePreset(name)`.
|
|
211
|
+
Color-blind palette: `import { COLOR_BLIND_SAFE_CATEGORICAL } from "semiotic"` (8-color Wong 2011).
|
|
318
212
|
|
|
319
|
-
|
|
213
|
+
Key: `ThemeProvider` sets CSS vars on a wrapper div (no React context). Canvas charts read vars via `getComputedStyle`. `exportChart` inlines computed styles.
|
|
320
214
|
|
|
321
|
-
**
|
|
215
|
+
**Dark/light mode merge rules:** String preset (e.g. `"dark"`) → full replacement with that preset's theme. Object with `mode` (e.g. `{ mode: "dark", colors: { categorical: [...] } }`) → merges onto the matching base theme (`DARK_THEME` or `LIGHT_THEME`), so background/text/grid adapt while your overrides are preserved. Object without `mode` → shallow-merges onto the current theme (partial override). ThemeProvider is reactive — changing the `theme` prop re-applies immediately.
|
|
322
216
|
|
|
323
|
-
**
|
|
217
|
+
**CSS interop:** Host app `--semiotic-*` vars on `:root` are overridden by ThemeProvider's closer wrapper div. To let app tokens flow through, either skip ThemeProvider and set `--semiotic-*` vars in CSS, or use the hybrid approach (ThemeProvider for palette only, CSS vars for chrome).
|
|
324
218
|
|
|
325
|
-
|
|
219
|
+
## Server-Side Rendering
|
|
220
|
+
- HOC charts and Frames render SVG automatically in server environments
|
|
221
|
+
- `renderXYToStaticSVG(props)`, `renderOrdinalToStaticSVG(props)`, `renderNetworkToStaticSVG(props)`, `renderGeoToStaticSVG(props)` from `semiotic/server`
|
|
222
|
+
- `frameType` is `"xy"|"ordinal"|"network"|"geo"` (NOT component names)
|
|
223
|
+
- Geo SSR requires pre-resolved features (synchronous — call `resolveReferenceGeography` first)
|
|
224
|
+
- Works with Next.js App Router, Remix, Astro
|
|
225
|
+
|
|
226
|
+
## AI Features
|
|
227
|
+
- `onObservation` / `useChartObserver` — structured events across charts
|
|
228
|
+
- `toConfig`/`fromConfig`/`toURL`/`fromURL`/`copyConfig`/`configToJSX` — serialization
|
|
229
|
+
- `DetailsPanel` — click-driven detail panel in `ChartContainer`
|
|
230
|
+
- `validateProps(componentName, props)` — prop validation with typo suggestions
|
|
231
|
+
- `diagnoseConfig(componentName, props)` — anti-pattern detector (13+ checks)
|
|
232
|
+
- `exportChart(containerDiv, { format: "png"|"svg" })` — pass wrapper div, composites canvas+SVG
|
|
233
|
+
- `npx semiotic-ai --doctor` — CLI validation
|
|
326
234
|
|
|
327
|
-
|
|
235
|
+
## Canvas Pattern Fills
|
|
328
236
|
|
|
329
|
-
|
|
237
|
+
`createHatchPattern({ background, stroke, lineWidth, spacing, angle })` from `semiotic` — returns `CanvasPattern | null` for use as `fill` in style functions. Used by FunnelChart vertical mode for dropoff bars.
|
|
238
|
+
|
|
239
|
+
## Known Pitfalls
|
|
330
240
|
|
|
331
|
-
**
|
|
241
|
+
- **Tooltip datum shape**: HOC tooltip functions get raw data. Frame `tooltipContent` gets wrapped data — use `d.data`.
|
|
242
|
+
- **Legend positioning**: "bottom" auto-expands margin ~80px. For narrow charts (<400px), prefer "bottom" or "top".
|
|
243
|
+
- **MultiAxisLineChart legend**: Always use `legendPosition="bottom"` (or `"top"`) — the right-hand axis occupies the space where a right-side legend would go.
|
|
244
|
+
- **Log scale**: Clamps domain min to 1e-6 (log(0) undefined).
|
|
245
|
+
- **barPadding**: Pixel value, defaults 40/60. Reduce for small charts.
|
|
246
|
+
- **Horizontal bars**: Need wider left margin with long labels: `margin={{ left: 120 }}`.
|
|
247
|
+
- **LinkedCharts legends**: `CategoryColorProvider` suppresses child legends. Force with `showLegend={true}`.
|
|
248
|
+
- **Push API**: Omit `data` prop entirely. `data={[]}` clears pushed data every render.
|
|
249
|
+
- **frameProps style functions**: Bypass HOC color resolution — use `colorBy` prop instead. Frame style functions receive `(datum, categoryName)`, not `(datum, index)`.
|
|
250
|
+
- **v2 migration**: `htmlAnnotationRules` → `widget` annotations + `svgAnnotationRules`. v2 `summaryStyle` index-based coloring → v3 category-string-based.
|
|
332
251
|
|
|
333
|
-
|
|
252
|
+
## Performance
|
|
334
253
|
|
|
335
|
-
|
|
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
|
|
254
|
+
Prefer string accessors (`xAccessor="value"`) over function accessors — always referentially stable. If you must use functions, memoize with `useCallback` or define outside the component. The pipeline uses `.toString()` comparison for inline arrows but this fails for closures capturing changing variables.
|