semiotic 3.0.0-beta.5 → 3.0.0-beta.6

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 CHANGED
@@ -2,192 +2,122 @@
2
2
 
3
3
  ## Quick Start
4
4
  - Install: `npm install semiotic`
5
- - Import from `semiotic` or granular: `semiotic/xy`, `semiotic/ordinal`, `semiotic/network`, `semiotic/realtime`, `semiotic/ai`, `semiotic/data`
6
- - `semiotic/ai` exports HOC charts + TooltipProvider + MultiLineTooltip + ThemeProvider + exportChart + validateProps + useChartObserver + DetailsPanel + ChartContainer
7
- - `semiotic/data` exports: `bin`, `rollup`, `groupBy`, `pivot`
8
- - CLI: `npx semiotic-ai [--schema|--compact|--examples]` dump AI context to stdout
9
- - MCP: `npx semiotic-mcp` — MCP server rendering charts to static SVG
5
+ - Import: `semiotic`, `semiotic/xy`, `semiotic/ordinal`, `semiotic/network`, `semiotic/realtime`, `semiotic/ai`, `semiotic/data`
6
+ - CLI: `npx semiotic-ai [--schema|--compact|--examples|--doctor]`
7
+ - MCP: `npx semiotic-mcp`
8
+ - Every HOC has a built-in error boundary (never blanks the page) and dev-mode validation warnings
10
9
 
11
10
  ## Architecture
12
- - **HOC Charts** (recommended): Simple props, sensible defaults
13
- - **Stream Frames** (advanced): `StreamXYFrame`, `StreamOrdinalFrame`, `StreamNetworkFrame`
14
- - Every HOC accepts `frameProps` to pass through to the underlying Stream Frame
15
- - TypeScript `strict: true`; all charts have `role="img"` + `aria-label`
11
+ - **HOC Charts**: Simple props, sensible defaults. **Stream Frames**: Full control.
12
+ - Every HOC accepts `frameProps` to pass through. TypeScript `strict: true`.
16
13
 
17
- ## Component Reference
14
+ ## Common Props (all HOCs)
15
+ `title`, `width` (600), `height` (400), `responsiveWidth`, `responsiveHeight`, `margin`, `className`, `enableHover` (true), `tooltip`, `showLegend`, `showGrid` (false), `frameProps`, `onObservation`, `chartId`
18
16
 
19
- ### Common props (all HOCs unless noted)
20
- `title` (string), `width` (number, 600), `height` (number, 400), `responsiveWidth` (boolean, false), `responsiveHeight` (boolean, false), `margin` (object), `className` (string), `enableHover` (boolean, true), `tooltip` (fn), `showLegend` (boolean), `showGrid` (boolean, false), `frameProps` (object), `onObservation` (fn), `chartId` (string)
17
+ ## XY Charts (`semiotic/xy`)
21
18
 
22
- ### XY Charts (from "semiotic" or "semiotic/xy")
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)
23
20
 
24
- **LineChart** — `data` (required), `xAccessor` ("x"), `yAccessor` ("y"), `lineBy`, `lineDataAccessor` ("coordinates"), `colorBy`, `colorScheme` ("category10"), `curve` ("linear"|"monotoneX"|"monotoneY"|"step"|"stepAfter"|"stepBefore"|"basis"|"cardinal"|"catmullRom"), `lineWidth` (2), `showPoints` (false), `pointRadius` (3), `fillArea` (false), `areaOpacity` (0.3), `xLabel`, `yLabel`, `xFormat`, `yFormat`, `anomaly` (AnomalyConfig), `forecast` (ForecastConfig)
21
+ **AreaChart** — LineChart props + `areaBy`, `y0Accessor` (band/ribbon), `gradientFill` (boolean|{topOpacity,bottomOpacity}), `areaOpacity` (0.7), `showLine` (true)
25
22
 
26
- **AreaChart** — Same as LineChart plus: `areaBy`, `y0Accessor` (per-point lower bound for band/ribbon charts), `gradientFill` (boolean|{topOpacity,bottomOpacity} — fade fill from line to baseline), `areaOpacity` (0.7), `showLine` (true), curve default "monotoneX"
23
+ **StackedAreaChart** — AreaChart + `normalize` (false)
27
24
 
28
- **StackedAreaChart** — Same as AreaChart plus: `normalize` (false)
25
+ **Scatterplot** — `data`, `xAccessor`, `yAccessor`, `colorBy`, `sizeBy`, `sizeRange`, `pointRadius` (5), `pointOpacity` (0.8), `marginalGraphics`
29
26
 
30
- **Scatterplot** — `data` (required), `xAccessor` ("x"), `yAccessor` ("y"), `colorBy`, `colorScheme`, `sizeBy`, `sizeRange` ([3,15]), `pointRadius` (5), `pointOpacity` (0.8), `xLabel`, `yLabel`, `marginalGraphics` ({top?,bottom?,left?,right?} — "histogram"|"violin"|"ridgeline"|"boxplot" or config object)
27
+ **BubbleChart** — Scatterplot + `sizeBy` (required), `sizeRange` ([5,40]), `bubbleOpacity` (0.6)
31
28
 
32
- **BubbleChart** — Like Scatterplot with `sizeBy` (required), `sizeRange` ([5,40]), `bubbleOpacity` (0.6), `bubbleStrokeWidth` (1), `bubbleStrokeColor` ("white"), `marginalGraphics`
29
+ **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.
33
30
 
34
- **Heatmap** — `data` (required), `xAccessor` ("x"), `yAccessor` ("y"), `valueAccessor` ("value"), `colorScheme` ("blues"|"reds"|"greens"|"viridis"|"custom"), `customColorScale`, `showValues` (false), `valueFormat`, `cellBorderColor` ("#fff"), `cellBorderWidth` (1)
31
+ **Heatmap** — `data`, `xAccessor`, `yAccessor`, `valueAccessor`, `colorScheme`, `showValues`, `cellBorderColor`
35
32
 
36
- ### Ordinal Charts (from "semiotic" or "semiotic/ordinal")
33
+ ## Ordinal Charts (`semiotic/ordinal`)
37
34
 
38
- **BarChart** — `data` (required), `categoryAccessor` ("category"), `valueAccessor` ("value"), `orientation` ("vertical"|"horizontal"), `colorBy`, `colorScheme`, `sort` (boolean|"asc"|"desc"|fn), `barPadding` (5), `categoryLabel`, `valueLabel`, `valueFormat`
35
+ **BarChart** — `data`, `categoryAccessor`, `valueAccessor`, `orientation`, `colorBy`, `sort`, `barPadding`
36
+ **StackedBarChart** — + `stackBy` (required), `normalize`
37
+ **GroupedBarChart** — + `groupBy` (required)
38
+ **SwarmPlot** — `data`, `categoryAccessor`, `valueAccessor`, `colorBy`, `sizeBy`, `pointRadius`, `pointOpacity`
39
+ **BoxPlot** — + `showOutliers`, `outlierRadius`
40
+ **Histogram** — + `bins` (25), `relative`. Always horizontal.
41
+ **ViolinPlot** — + `bins`, `curve`, `showIQR`
42
+ **DotPlot** — + `sort` (true), `dotRadius`, `showGrid` default true
43
+ **PieChart** — `data`, `categoryAccessor`, `valueAccessor`, `colorBy`, `startAngle`, `slicePadding`
44
+ **DonutChart** — PieChart + `innerRadius` (60), `centerContent`
39
45
 
40
- **StackedBarChart** BarChart props plus `stackBy` (required), `normalize` (false)
46
+ ## Network Charts (`semiotic/network`)
41
47
 
42
- **GroupedBarChart** — BarChart props plus `groupBy` (required)
48
+ **ForceDirectedGraph** — `nodes`, `edges`, `nodeIDAccessor`, `sourceAccessor`, `targetAccessor`, `colorBy`, `nodeSize`, `edgeWidth`, `iterations`, `showLabels`
49
+ **SankeyDiagram** — `edges`, `nodes`, `valueAccessor`, `edgeColorBy`, `orientation`, `nodeAlign`, `nodeWidth`, `showLabels`, `edgeOpacity`
50
+ **ChordDiagram** — `edges`, `nodes`, `valueAccessor`, `edgeColorBy`, `padAngle`, `groupWidth`, `showLabels`
51
+ **TreeDiagram** — `data` (root), `layout`, `orientation`, `childrenAccessor`, `colorBy`, `colorByDepth`, `edgeStyle`
52
+ **Treemap** — `data` (root), `childrenAccessor`, `valueAccessor`, `colorBy`, `colorByDepth`, `showLabels`, `labelMode`
53
+ **CirclePack** — `data` (root), `childrenAccessor`, `valueAccessor`, `colorBy`, `colorByDepth`, `circleOpacity`
43
54
 
44
- **SwarmPlot** `data` (required), `categoryAccessor`, `valueAccessor`, `orientation`, `colorBy`, `sizeBy`, `sizeRange` ([3,8]), `pointRadius` (4), `pointOpacity` (0.7), `categoryPadding` (20)
55
+ ## Realtime Charts (`semiotic/realtime`)
45
56
 
46
- **BoxPlot** `data` (required), `categoryAccessor`, `valueAccessor`, `orientation`, `colorBy`, `showOutliers` (true), `outlierRadius` (3), `categoryPadding` (20)
57
+ Push API: `chartRef.current.push({ time, value })`
47
58
 
48
- **Histogram** — `data` (required), `categoryAccessor`, `valueAccessor`, `bins` (25), `relative` (false), `categoryPadding` (20). Always horizontal.
59
+ **RealtimeLineChart** — `size`, `timeAccessor`, `valueAccessor`, `windowSize` (200), `windowMode`, `stroke`, `strokeWidth`
60
+ **RealtimeTemporalHistogram** — + `binSize` (required), `categoryAccessor`, `colors`
61
+ **RealtimeSwarmChart** — + `categoryAccessor`, `radius`, `opacity`
62
+ **RealtimeWaterfallChart** — + `positiveColor`, `negativeColor`
63
+ **RealtimeHeatmap** — + `heatmapXBins`, `heatmapYBins`, `aggregation`
64
+ **Streaming Sankey** — `StreamNetworkFrame` with `chartType="sankey"`, `showParticles`, `particleStyle`, `tensionConfig`, `thresholds`
49
65
 
50
- **ViolinPlot** `data` (required), `categoryAccessor`, `valueAccessor`, `orientation`, `bins` (25), `curve` ("catmullRom"), `showIQR` (true), `categoryPadding` (20)
66
+ Realtime encoding: `decay`, `pulse`, `transition`, `staleness` compose freely on all streaming charts.
51
67
 
52
- **DotPlot** `data` (required), `categoryAccessor`, `valueAccessor`, `orientation` ("horizontal"), `sort` (true), `dotRadius` (5), `categoryPadding` (10), `showGrid` default true
68
+ ## Coordinated Views
53
69
 
54
- **PieChart** — `data` (required), `categoryAccessor`, `valueAccessor`, `colorBy`, `startAngle` (0), `slicePadding` (2), width/height default 400
70
+ **LinkedCharts** — wraps charts. Props: `selections` (resolution: "union"|"intersect"|"crossfilter")
71
+ **CategoryColorProvider** — stable category→color mapping. Props: `colors` (map) or `categories` + `colorScheme`
72
+ Chart props: `selection`, `linkedHover`, `linkedBrush`. Hooks: `useSelection`, `useLinkedHover`, `useBrushSelection`, `useFilteredData`
73
+ **ScatterplotMatrix** — `data`, `fields`, `colorBy`, `cellSize`, `hoverMode`, `brushMode`
55
74
 
56
- **DonutChart** PieChart props plus `innerRadius` (60), `centerContent` (ReactNode)
75
+ ## Layout & Composition
57
76
 
58
- ### Network Charts (from "semiotic" or "semiotic/network")
77
+ **ChartGrid** CSS Grid layout. `columns` (number|"auto"), `minCellWidth` (300), `gap` (16)
78
+ **ContextLayout** — primary + context panel. `context` (ReactNode), `position`, `contextSize` (250)
59
79
 
60
- **ForceDirectedGraph** `nodes` (required), `edges` (required), `nodeIDAccessor` ("id"), `sourceAccessor` ("source"), `targetAccessor` ("target"), `nodeLabel`, `colorBy`, `nodeSize` (8), `nodeSizeRange` ([5,20]), `edgeWidth` (1), `edgeColor` ("#999"), `edgeOpacity` (0.6), `iterations` (300), `forceStrength` (0.1), `showLabels` (false). Width/height default 600.
61
-
62
- **SankeyDiagram** — `edges` (required), `nodes` (optional), `sourceAccessor`, `targetAccessor`, `valueAccessor` ("value"), `nodeIdAccessor` ("id"), `colorBy`, `edgeColorBy` ("source"|"target"|"gradient"|fn), `orientation` ("horizontal"|"vertical"), `nodeAlign` ("justify"|"left"|"right"|"center"), `nodePaddingRatio` (0.05), `nodeWidth` (15), `nodeLabel`, `showLabels` (true), `edgeOpacity` (0.5), `edgeSort`. Default 800x600.
63
-
64
- **ChordDiagram** — `edges` (required), `nodes`, `sourceAccessor`, `targetAccessor`, `valueAccessor`, `nodeIdAccessor`, `colorBy`, `edgeColorBy`, `padAngle` (0.01), `groupWidth` (20), `sortGroups`, `nodeLabel`, `showLabels` (true), `edgeOpacity` (0.5)
65
-
66
- **TreeDiagram** — `data` (required, single root with children), `layout` ("tree"|"cluster"|"partition"|"treemap"|"circlepack"), `orientation` ("vertical"|"horizontal"|"radial"), `childrenAccessor` ("children"), `valueAccessor`, `nodeIdAccessor` ("name"), `colorBy`, `colorByDepth` (false), `edgeStyle` ("line"|"curve"), `nodeLabel`, `showLabels` (true), `nodeSize` (5)
67
-
68
- **Treemap** — `data` (required, root with children), `childrenAccessor`, `valueAccessor`, `nodeIdAccessor` ("name"), `colorBy`, `colorByDepth` (false), `showLabels` (true), `labelMode` ("leaf"|"parent"|"all"), `nodeLabel`, `padding` (4), `paddingTop` (0, auto 18 for "parent"). Hover shows ancestor breadcrumb.
69
-
70
- **CirclePack** — `data` (required), `childrenAccessor`, `valueAccessor`, `nodeIdAccessor`, `colorBy`, `colorByDepth` (false), `showLabels` (true), `nodeLabel`, `circleOpacity` (0.7), `padding` (4). Labels hidden below 15px radius. Hover shows ancestor breadcrumb.
71
-
72
- ### Realtime Charts (from "semiotic" or "semiotic/realtime")
73
-
74
- All use ref-based push API + canvas rendering: `chartRef.current.push({ time, value })`
75
-
76
- **RealtimeLineChart** — `size` ([500,300]), `timeAccessor`, `valueAccessor`, `windowSize` (200), `windowMode` ("sliding"|"stepping"), `arrowOfTime` ("left"|"right"), `stroke`, `strokeWidth`, `strokeDasharray`, `timeExtent`, `valueExtent`, `extentPadding`, `showAxes`, `background`, `enableHover`, `tooltipContent`, `onHover`, `annotations`, `svgAnnotationRules`, `tickFormatTime`, `tickFormatValue`
77
-
78
- **RealtimeTemporalHistogram** — RealtimeLineChart props plus `binSize` (required), `categoryAccessor`, `colors`, `fill`, `gap`, `decay`, `pulse`, `staleness`, `transition`
79
-
80
- **RealtimeSwarmChart** — RealtimeLineChart props plus `categoryAccessor`, `colors`, `radius`, `fill`, `opacity`
81
-
82
- **RealtimeWaterfallChart** — RealtimeLineChart props plus `positiveColor`, `negativeColor`, `connectorStroke`, `connectorWidth`, `gap`
83
-
84
- **RealtimeHeatmap** — RealtimeLineChart props plus `heatmapXBins` (20), `heatmapYBins` (20), `aggregation` ("count"|"sum"|"mean"), `linkedHover`, `decay`, `pulse`, `staleness`
85
-
86
- **Streaming Sankey** — Use `StreamNetworkFrame` with `chartType="sankey"`, `showParticles`, `particleStyle`, `tensionConfig`, `thresholds`, `onTopologyChange`. Ref: `push()`, `pushMany()`, `clear()`, `getTopology()`, `relayout()`, `getTension()`
87
-
88
- ### Realtime Visual Encoding (all streaming charts)
89
- - `decay` — older data fades (`{ type, halfLife, minOpacity }`)
90
- - `pulse` — new data flashes (`{ duration, color, glowRadius }`)
91
- - `transition` — smooth interpolation (`{ duration, easing }`)
92
- - `staleness` — stale feed detection (`{ threshold, dimOpacity, showBadge }`)
93
-
94
- ### Coordinated Views (from "semiotic" or "semiotic/ai")
95
-
96
- **LinkedCharts** — Wraps charts for coordination. Props: `selections` (Record with resolution: "union"|"intersect"|"crossfilter")
97
-
98
- Chart coordination props (on HOCs inside LinkedCharts):
99
- - `selection` — consume named selection
100
- - `linkedHover` — produce hover selections
101
- - `linkedBrush` — produce brush selections (Scatterplot/BubbleChart only)
102
-
103
- Hooks: `useSelection`, `useLinkedHover`, `useBrushSelection`, `useFilteredData`
104
-
105
- **ScatterplotMatrix** — `data` (required), `fields` (required), `fieldLabels`, `colorBy`, `cellSize` (150), `cellGap` (4), `pointRadius` (2), `pointOpacity` (0.5), `diagonal` ("histogram"|"density"|"label"), `histogramBins` (20), `hoverMode` (true), `brushMode` ("crossfilter"|"intersect"|false), `unselectedOpacity` (0.1)
106
-
107
- ## Key Usage Patterns
80
+ ## Key Patterns
108
81
 
109
82
  ```jsx
110
- // Multi-line data
111
- <LineChart data={[{ id: "A", coordinates: [{x:0,y:1},{x:1,y:2}] }]} lineBy="id" xAccessor="x" yAccessor="y" />
112
-
113
- // Hierarchical data (TreeDiagram, Treemap, CirclePack) — single root with children
114
- <Treemap data={rootNode} childrenAccessor="children" valueAccessor="value" />
115
-
116
- // Network data
117
- <SankeyDiagram nodes={nodes} edges={edges} valueAccessor="value" />
118
-
119
- // Tooltips
120
- <LineChart ... tooltip={MultiLineTooltip({ title: "name", fields: ["value"] })} />
121
-
122
- // Coordinated views
83
+ // Cross-highlighting dashboard
84
+ <CategoryColorProvider categories={["North", "South", "East"]}>
123
85
  <LinkedCharts>
124
- <Scatterplot data={d} linkedHover={{ name: "hl", fields: ["cat"] }} selection={{ name: "hl" }} />
125
- <BarChart data={agg} selection={{ name: "hl" }} />
86
+ <ChartGrid columns={2}>
87
+ <LineChart data={d} colorBy="region" linkedHover={{ name: "hl", fields: ["region"] }} selection={{ name: "hl" }} responsiveWidth />
88
+ <BarChart data={d} colorBy="region" linkedHover={{ name: "hl", fields: ["region"] }} selection={{ name: "hl" }} responsiveWidth />
89
+ </ChartGrid>
126
90
  </LinkedCharts>
127
-
128
- // Marginal graphics
129
- <Scatterplot data={d} marginalGraphics={{ top: "histogram", right: "violin" }} />
130
-
131
- // Theming
132
- <ThemeProvider theme="dark"><LineChart ... /></ThemeProvider>
133
-
134
- // Shared category colors across charts
135
- <CategoryColorProvider colors={{ North: "#e41a1c", South: "#377eb8" }}>
136
- <LineChart data={d1} colorBy="region" />
137
- <BarChart data={d2} colorBy="region" /> {/* same colors */}
138
91
  </CategoryColorProvider>
139
92
 
140
- // Data transforms
141
- import { bin, rollup, groupBy, pivot } from "semiotic/data"
93
+ // Forecast + anomaly (auto)
94
+ <LineChart data={ts} xAccessor="time" yAccessor="value"
95
+ forecast={{ trainEnd: 60, steps: 15, confidence: 0.95 }}
96
+ anomaly={{ threshold: 2 }} />
142
97
 
143
- // Browser export
144
- await exportChart(el, { format: "png", scale: 2 })
98
+ // Forecast (pre-computed ML bounds)
99
+ <LineChart data={ml} xAccessor="time" yAccessor="value"
100
+ forecast={{ isTraining: "isTraining", isForecast: "isForecast", isAnomaly: "isAnomaly", upperBounds: "upper", lowerBounds: "lower" }} />
101
+
102
+ // Gradient area + percentile band
103
+ <AreaChart data={d} xAccessor="x" yAccessor="p95" y0Accessor="p5" gradientFill />
145
104
 
146
105
  // Realtime
147
106
  const ref = useRef()
148
107
  ref.current.push({ time: Date.now(), value: 42 })
149
108
  <RealtimeLineChart ref={ref} timeAccessor="time" valueAccessor="value" />
150
-
151
- // Forecast + anomaly detection (LineChart only)
152
- // Auto mode: training=dashed, observed=solid, forecast=dotted with confidence envelope
153
- <LineChart data={timeSeries} xAccessor="time" yAccessor="value"
154
- forecast={{ trainEnd: 60, steps: 15, confidence: 0.95 }}
155
- anomaly={{ threshold: 2, anomalyColor: "#ef4444" }} />
156
-
157
- // Pre-computed mode: bring your own bounds from an ML model
158
- // Data: { time, value, isTraining?, isForecast?, isAnomaly?, upperBounds?, lowerBounds? }
159
- // Envelope follows per-point bounds (non-rectilinear)
160
- <LineChart data={mlOutput} xAccessor="time" yAccessor="value"
161
- forecast={{
162
- isTraining: "isTraining", isForecast: "isForecast",
163
- isAnomaly: "isAnomaly", upperBounds: "upper", lowerBounds: "lower",
164
- }} />
165
109
  ```
166
110
 
167
111
  ## AI Features
168
-
169
- **onObservation** — callback on all HOCs emitting structured events: hover, hover-end, click, click-end, brush, brush-end, selection, selection-end. Each includes `{ type, datum?, x?, y?, timestamp, chartType, chartId? }`.
170
-
171
- **useChartObserver**aggregates observations across LinkedCharts. Options: `limit` (50), `types`, `chartId`.
172
-
173
- **Chart serialization** — `toConfig(name, props)` / `fromConfig(config)` for JSON round-trip. `toURL`/`fromURL` for permalinks. `copyConfig(config, "jsx")` for clipboard. `configToJSX(config)` for code gen. String accessors survive; functions are stripped.
174
-
175
- **DetailsPanel** — selection-driven detail panel. Props: `children` (render fn), `position` ("right"|"bottom"|"overlay"), `size` (300), `trigger` ("click"|"hover"), `chartId`, `dismissOnEmpty`, `showClose`. Use inside ChartContainer via `detailsPanel` prop.
176
-
177
- **ChartGrid** — responsive grid layout for multiple charts. Props: `columns` (number|"auto"), `minCellWidth` (300), `gap` (16). Works with LinkedCharts.
178
-
179
- **ContextLayout** places a primary chart alongside context chart(s). Props: `context` (ReactNode), `position` ("right"|"left"|"top"|"bottom"), `contextSize` (250), `gap` (12). Context charts use `mode="context"` for compact rendering.
180
-
181
- **ChartErrorBoundary** — `fallback` (ReactNode), `onError` (fn)
182
-
183
- **validateProps(componentName, props)** — returns `{ valid, errors }`
184
-
185
- ## What Semiotic Does That Others Don't
186
- - Network visualization (force, sankey, chord, tree, treemap, circlepack) with same clean API
187
- - Streaming data (canvas 60fps push API) + streaming Sankey with particles
188
- - Realtime visual encoding (decay, pulse, transitions, staleness)
189
- - Coordinated views (LinkedCharts, crossfilter brushing, ScatterplotMatrix)
190
- - Statistical summaries (box, violin, swarm, histogram, marginal graphics)
191
- - AI observation hooks + chart state serialization
192
- - Server-side SVG via `renderToStaticSVG()` (from "semiotic/server")
193
- - Global theming with ThemeProvider
112
+ - `onObservation` — structured events (hover, click, brush, selection) on all HOCs
113
+ - `useChartObserver` aggregates observations across LinkedCharts
114
+ - `toConfig`/`fromConfig`/`toURL`/`fromURL`/`copyConfig`/`configToJSX` — chart state serialization
115
+ - `DetailsPanel` click-driven detail panel inside `ChartContainer`
116
+ - `validateProps(componentName, props)` — prop validation
117
+ - `ChartErrorBoundary` error boundary
118
+ - `exportChart(el, { format: "png"|"svg" })` — browser export
119
+ - `renderToStaticSVG()` server-side SVG (from `semiotic/server`)
120
+ - `npx semiotic-ai --doctor` — validate component + props JSON from CLI
121
+
122
+ ## Differentiators
123
+ Network viz, streaming canvas, realtime encoding, coordinated views, statistical summaries, AI hooks, chart serialization, global theming, keyboard navigation
package/README.md CHANGED
@@ -84,8 +84,10 @@ snapshots, or coordinated views across chart types.
84
84
  **AI-ready.** Semiotic ships with structured schemas (`ai/schema.json`), an
85
85
  `import from "semiotic/ai"` entry point, and an MCP server — all designed for
86
86
  LLM code generation. AI coding assistants can generate correct Semiotic code on
87
- the first try. Run `npx semiotic-ai --help` for CLI options or add `semiotic-mcp`
88
- to your MCP client config for tool-based chart rendering.
87
+ the first try. Run `npx semiotic-ai --help` for CLI options, `npx semiotic-ai --doctor`
88
+ to validate props from the command line, or add `semiotic-mcp` to your MCP client
89
+ config for tool-based chart rendering. Every HOC chart includes a built-in error
90
+ boundary and dev-mode validation warnings with actionable fix suggestions.
89
91
 
90
92
  **Vega-Lite compatible.** Have existing Vega-Lite specs? `fromVegaLite(spec)`
91
93
  translates them to Semiotic chart configs — instant onboarding from notebooks,
@@ -100,7 +102,7 @@ highlights new services as they appear. Visualization as product navigation.
100
102
  ## Install
101
103
 
102
104
  ```bash
103
- npm install semiotic@3.0.0-beta.4
105
+ npm install semiotic@3.0.0-beta.6
104
106
  ```
105
107
 
106
108
  Requires React 18.1 or later.
@@ -218,6 +220,7 @@ import { LineChart, BarChart } from "semiotic"
218
220
  | **Network** | `ForceDirectedGraph` `ChordDiagram` `SankeyDiagram` `TreeDiagram` `Treemap` `CirclePack` |
219
221
  | **Realtime** | `RealtimeLineChart` `RealtimeHistogram` `RealtimeSwarmChart` `RealtimeWaterfallChart` `RealtimeHeatmap` |
220
222
  | **Coordination** | `LinkedCharts` `ScatterplotMatrix` |
223
+ | **Layout** | `ChartGrid` `ContextLayout` `CategoryColorProvider` |
221
224
  | **Frames** | `StreamXYFrame` `StreamOrdinalFrame` `StreamNetworkFrame` |
222
225
 
223
226
  ### Vega-Lite Translation
package/ai/cli.js CHANGED
@@ -21,6 +21,7 @@ Usage:
21
21
  npx semiotic-ai --schema Print ai/schema.json (tool definitions)
22
22
  npx semiotic-ai --compact Print ai/system-prompt.md (compact prompt)
23
23
  npx semiotic-ai --examples Print ai/examples.md (copy-paste examples)
24
+ npx semiotic-ai --doctor Validate component + props JSON from stdin
24
25
  npx semiotic-ai --help Show this help message
25
26
  `.trim()
26
27
 
@@ -31,6 +32,77 @@ if (flag === "--help" || flag === "-h") {
31
32
  process.exit(0)
32
33
  }
33
34
 
35
+ // --doctor: validate component + props from stdin or argv
36
+ if (flag === "--doctor") {
37
+ let input = ""
38
+ if (process.argv[3]) {
39
+ // npx semiotic-ai --doctor '{"component":"LineChart","props":{...}}'
40
+ input = process.argv.slice(3).join(" ")
41
+ } else if (!process.stdin.isTTY) {
42
+ // echo '...' | npx semiotic-ai --doctor
43
+ input = fs.readFileSync("/dev/stdin", "utf-8")
44
+ } else {
45
+ console.error("Usage: npx semiotic-ai --doctor '{\"component\":\"LineChart\",\"props\":{\"data\":[...]}}'")
46
+ console.error(" echo '{...}' | npx semiotic-ai --doctor")
47
+ process.exit(1)
48
+ }
49
+
50
+ try {
51
+ const { component, props } = JSON.parse(input)
52
+ if (!component || !props) {
53
+ console.error("Input must be JSON with { component, props } fields.")
54
+ process.exit(1)
55
+ }
56
+
57
+ // Load validateProps from dist
58
+ const distPath = path.join(pkgRoot, "dist", "semiotic-ai.min.js")
59
+ let validateProps
60
+ try {
61
+ const mod = require(distPath)
62
+ validateProps = mod.validateProps
63
+ } catch (e) {
64
+ console.error("Could not load semiotic/ai dist. Run 'npm run dist' first.")
65
+ process.exit(1)
66
+ }
67
+
68
+ if (!validateProps) {
69
+ console.error("validateProps not found in semiotic/ai exports.")
70
+ process.exit(1)
71
+ }
72
+
73
+ const result = validateProps(component, props)
74
+ if (result.valid) {
75
+ console.log(`✓ ${component}: props are valid.`)
76
+ // Additional data shape checks
77
+ if (props.data && Array.isArray(props.data) && props.data.length > 0) {
78
+ const sample = props.data[0]
79
+ console.log(` Data shape: ${props.data.length} items, keys: [${Object.keys(sample).join(", ")}]`)
80
+ if (props.xAccessor && typeof props.xAccessor === "string" && !(props.xAccessor in sample)) {
81
+ console.log(` ⚠ xAccessor "${props.xAccessor}" not in data keys. Available: ${Object.keys(sample).join(", ")}`)
82
+ }
83
+ if (props.yAccessor && typeof props.yAccessor === "string" && !(props.yAccessor in sample)) {
84
+ console.log(` ⚠ yAccessor "${props.yAccessor}" not in data keys. Available: ${Object.keys(sample).join(", ")}`)
85
+ }
86
+ if (props.categoryAccessor && typeof props.categoryAccessor === "string" && !(props.categoryAccessor in sample)) {
87
+ console.log(` ⚠ categoryAccessor "${props.categoryAccessor}" not in data keys. Available: ${Object.keys(sample).join(", ")}`)
88
+ }
89
+ if (props.valueAccessor && typeof props.valueAccessor === "string" && !(props.valueAccessor in sample)) {
90
+ console.log(` ⚠ valueAccessor "${props.valueAccessor}" not in data keys. Available: ${Object.keys(sample).join(", ")}`)
91
+ }
92
+ }
93
+ } else {
94
+ console.log(`✗ ${component}: validation failed.`)
95
+ for (const err of result.errors) {
96
+ console.log(` • ${err}`)
97
+ }
98
+ }
99
+ } catch (err) {
100
+ console.error(`Failed to parse input: ${err.message}`)
101
+ process.exit(1)
102
+ }
103
+ process.exit(0)
104
+ }
105
+
34
106
  const filePath = flag ? FILES[flag] : FILES.default
35
107
 
36
108
  if (!filePath) {
@@ -8,6 +8,8 @@
8
8
  */
9
9
  export { Scatterplot } from "./xy/Scatterplot";
10
10
  export type { ScatterplotProps } from "./xy/Scatterplot";
11
+ export { ConnectedScatterplot } from "./xy/ConnectedScatterplot";
12
+ export type { ConnectedScatterplotProps } from "./xy/ConnectedScatterplot";
11
13
  export { LineChart } from "./xy/LineChart";
12
14
  export type { LineChartProps } from "./xy/LineChart";
13
15
  export { AreaChart } from "./xy/AreaChart";
@@ -2,6 +2,9 @@
2
2
  * Validates chart data and accessors at render time.
3
3
  * Returns null if data is valid, or an error message string if not.
4
4
  *
5
+ * Error messages are designed for AI-agent consumption:
6
+ * they include the problem, the available fields, AND the suggested fix.
7
+ *
5
8
  * Samples first, last, and a middle element to catch common mistakes
6
9
  * (wrong field names, missing data) without iterating the entire dataset.
7
10
  */
@@ -0,0 +1,18 @@
1
+ import * as React from "react";
2
+ interface SafeRenderProps {
3
+ componentName: string;
4
+ width: number;
5
+ height: number;
6
+ children: React.ReactNode;
7
+ }
8
+ /**
9
+ * Wraps a chart's rendered output with an error boundary.
10
+ * If the chart throws during render, shows a visible error box
11
+ * with the component name and error message instead of crashing the page.
12
+ */
13
+ export declare function SafeRender({ componentName, width, height, children }: SafeRenderProps): React.JSX.Element;
14
+ /** Warn if a string accessor isn't found in the first data element */
15
+ export declare function warnMissingField(componentName: string, data: any[] | undefined, accessorName: string, accessorValue: any): void;
16
+ /** Warn if data looks like the wrong shape for this chart type */
17
+ export declare function warnDataShape(componentName: string, data: any[] | undefined, expectedKeys: string[], hint: string): void;
18
+ export {};
@@ -0,0 +1,60 @@
1
+ import * as React from "react";
2
+ import type { StreamXYFrameProps } from "../../stream/types";
3
+ import type { BaseChartProps, AxisConfig, ChartAccessor } from "../shared/types";
4
+ import { type TooltipProp } from "../../Tooltip/Tooltip";
5
+ /**
6
+ * ConnectedScatterplot component props
7
+ */
8
+ export interface ConnectedScatterplotProps<TDatum extends Record<string, any> = Record<string, any>> extends BaseChartProps, AxisConfig {
9
+ /** Array of data points. Each point needs x and y properties. */
10
+ data: TDatum[];
11
+ /** Field name or function to access x values @default "x" */
12
+ xAccessor?: ChartAccessor<TDatum, number>;
13
+ /** Field name or function to access y values @default "y" */
14
+ yAccessor?: ChartAccessor<TDatum, number>;
15
+ /**
16
+ * Field name or function that determines point ordering.
17
+ * Data is sorted by this value (ascending) before connecting.
18
+ * Supports numbers and Dates. Shown in tooltip. @default undefined (use data array order)
19
+ */
20
+ orderAccessor?: string | ((d: TDatum) => number | Date);
21
+ /** Label for the ordering metric in tooltips @default "Order" or the accessor field name */
22
+ orderLabel?: string;
23
+ /** Point radius @default 4 */
24
+ pointRadius?: number;
25
+ /** Enable hover annotations @default true */
26
+ enableHover?: boolean;
27
+ /** Show grid lines @default false */
28
+ showGrid?: boolean;
29
+ /** Tooltip configuration */
30
+ tooltip?: TooltipProp;
31
+ /** Accessor for unique point IDs, used by point-anchored annotations */
32
+ pointIdAccessor?: ChartAccessor<TDatum, string>;
33
+ /** Annotation objects to render on the chart */
34
+ annotations?: Record<string, any>[];
35
+ /** Additional StreamXYFrame props for advanced customization */
36
+ frameProps?: Partial<Omit<StreamXYFrameProps, "chartType" | "data" | "size">>;
37
+ }
38
+ /**
39
+ * ConnectedScatterplot — points connected in sequence by lines.
40
+ *
41
+ * Points are colored using viridis from start (purple) to end (yellow).
42
+ * Lines match the color of their source point and have the same width
43
+ * as the point radius, so the 2×radius circle remains distinctive.
44
+ * When fewer than 100 points, a 50% transparent white halo is drawn
45
+ * under each connecting line for legibility.
46
+ *
47
+ * @example
48
+ * ```tsx
49
+ * <ConnectedScatterplot
50
+ * data={trajectory}
51
+ * xAccessor="gdp"
52
+ * yAccessor="lifeExpectancy"
53
+ * pointRadius={4}
54
+ * />
55
+ * ```
56
+ */
57
+ export declare function ConnectedScatterplot<TDatum extends Record<string, any> = Record<string, any>>(props: ConnectedScatterplotProps<TDatum>): React.JSX.Element;
58
+ export declare namespace ConnectedScatterplot {
59
+ var displayName: string;
60
+ }