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