semiotic 3.0.0-beta.4 → 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.
Files changed (42) hide show
  1. package/CLAUDE.md +81 -126
  2. package/README.md +6 -3
  3. package/ai/cli.js +72 -0
  4. package/dist/CategoryColors.d.ts +48 -0
  5. package/dist/ChartGrid.d.ts +37 -0
  6. package/dist/ContextLayout.d.ts +38 -0
  7. package/dist/charts/index.d.ts +2 -0
  8. package/dist/charts/shared/statisticalOverlays.d.ts +76 -0
  9. package/dist/charts/shared/types.d.ts +4 -0
  10. package/dist/charts/shared/validateChartData.d.ts +3 -0
  11. package/dist/charts/shared/withChartWrapper.d.ts +18 -0
  12. package/dist/charts/xy/AreaChart.d.ts +22 -1
  13. package/dist/charts/xy/ConnectedScatterplot.d.ts +60 -0
  14. package/dist/charts/xy/LineChart.d.ts +12 -0
  15. package/dist/network.min.js +1 -1
  16. package/dist/network.module.min.js +1 -1
  17. package/dist/ordinal.min.js +1 -1
  18. package/dist/ordinal.module.min.js +1 -1
  19. package/dist/realtime.min.js +1 -1
  20. package/dist/realtime.module.min.js +1 -1
  21. package/dist/semiotic-ai.d.ts +8 -0
  22. package/dist/semiotic-ai.min.js +1 -1
  23. package/dist/semiotic-ai.module.min.js +1 -1
  24. package/dist/semiotic-data.min.js +1 -1
  25. package/dist/semiotic-data.module.min.js +1 -1
  26. package/dist/semiotic-xy.d.ts +1 -0
  27. package/dist/semiotic.d.ts +9 -3
  28. package/dist/semiotic.min.js +1 -1
  29. package/dist/semiotic.module.min.js +1 -1
  30. package/dist/server.min.js +1 -1
  31. package/dist/server.module.min.js +1 -1
  32. package/dist/stream/DataSourceAdapter.d.ts +3 -0
  33. package/dist/stream/PipelineStore.d.ts +6 -0
  34. package/dist/stream/SceneGraph.d.ts +1 -1
  35. package/dist/stream/keyboardNav.d.ts +39 -0
  36. package/dist/stream/networkTypes.d.ts +2 -0
  37. package/dist/stream/ordinalTypes.d.ts +2 -0
  38. package/dist/stream/types.d.ts +24 -0
  39. package/dist/stream/useResponsiveSize.d.ts +10 -0
  40. package/dist/xy.min.js +1 -1
  41. package/dist/xy.module.min.js +1 -1
  42. package/package.json +6 -2
package/CLAUDE.md CHANGED
@@ -1,141 +1,106 @@
1
1
  # Semiotic — AI Assistant Guide
2
2
 
3
3
  ## Quick Start
4
- - Install: `npm install semiotic@3.0.0-beta.4`
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
4
+ - Install: `npm install semiotic`
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), `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`
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`, `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>
91
+ </CategoryColorProvider>
127
92
 
128
- // Marginal graphics
129
- <Scatterplot data={d} marginalGraphics={{ top: "histogram", right: "violin" }} />
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 }} />
130
97
 
131
- // Theming
132
- <ThemeProvider theme="dark"><LineChart ... /></ThemeProvider>
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" }} />
133
101
 
134
- // Data transforms
135
- import { bin, rollup, groupBy, pivot } from "semiotic/data"
136
-
137
- // Browser export
138
- await exportChart(el, { format: "png", scale: 2 })
102
+ // Gradient area + percentile band
103
+ <AreaChart data={d} xAccessor="x" yAccessor="p95" y0Accessor="p5" gradientFill />
139
104
 
140
105
  // Realtime
141
106
  const ref = useRef()
@@ -144,25 +109,15 @@ ref.current.push({ time: Date.now(), value: 42 })
144
109
  ```
145
110
 
146
111
  ## AI Features
147
-
148
- **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? }`.
149
-
150
- **useChartObserver**aggregates observations across LinkedCharts. Options: `limit` (50), `types`, `chartId`.
151
-
152
- **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.
153
-
154
- **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.
155
-
156
- **ChartErrorBoundary** — `fallback` (ReactNode), `onError` (fn)
157
-
158
- **validateProps(componentName, props)** returns `{ valid, errors }`
159
-
160
- ## What Semiotic Does That Others Don't
161
- - Network visualization (force, sankey, chord, tree, treemap, circlepack) with same clean API
162
- - Streaming data (canvas 60fps push API) + streaming Sankey with particles
163
- - Realtime visual encoding (decay, pulse, transitions, staleness)
164
- - Coordinated views (LinkedCharts, crossfilter brushing, ScatterplotMatrix)
165
- - Statistical summaries (box, violin, swarm, histogram, marginal graphics)
166
- - AI observation hooks + chart state serialization
167
- - Server-side SVG via `renderToStaticSVG()` (from "semiotic/server")
168
- - 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) {
@@ -0,0 +1,48 @@
1
+ import * as React from "react";
2
+ /**
3
+ * Category→color mapping. Maps category values (like "North", "error", "active")
4
+ * to fixed color strings. Charts inside a CategoryColorProvider will use these
5
+ * colors consistently regardless of what subset of categories each chart displays.
6
+ */
7
+ export type CategoryColorMap = Record<string, string>;
8
+ export interface CategoryColorProviderProps {
9
+ /** Explicit category→color mapping */
10
+ colors?: CategoryColorMap;
11
+ /**
12
+ * Category values to auto-assign colors from a scheme.
13
+ * Use when you want consistent colors but don't need specific assignments.
14
+ */
15
+ categories?: string[];
16
+ /** Color scheme to use for auto-assignment. Default: "category10" */
17
+ colorScheme?: string | string[];
18
+ children: React.ReactNode;
19
+ }
20
+ /**
21
+ * CategoryColorProvider — assigns stable colors to category values
22
+ * shared across all Semiotic charts within its subtree.
23
+ *
24
+ * Two modes:
25
+ * - **Explicit**: pass a `colors` map of category→color
26
+ * - **Auto**: pass a `categories` array and optional `colorScheme`
27
+ *
28
+ * Charts with `colorBy` inside this provider will use the mapped colors
29
+ * instead of computing their own color scale per-chart.
30
+ *
31
+ * ```tsx
32
+ * <CategoryColorProvider colors={{ North: "#e41a1c", South: "#377eb8", East: "#4daf4a" }}>
33
+ * <ChartGrid columns={2}>
34
+ * <LineChart data={d1} colorBy="region" responsiveWidth />
35
+ * <BarChart data={d2} colorBy="region" responsiveWidth />
36
+ * </ChartGrid>
37
+ * </CategoryColorProvider>
38
+ * ```
39
+ */
40
+ export declare function CategoryColorProvider({ colors, categories, colorScheme, children, }: CategoryColorProviderProps): React.JSX.Element;
41
+ export declare namespace CategoryColorProvider {
42
+ var displayName: string;
43
+ }
44
+ /**
45
+ * Hook to access the category color map from the nearest provider.
46
+ * Returns null if no provider is present.
47
+ */
48
+ export declare function useCategoryColors(): CategoryColorMap | null;
@@ -0,0 +1,37 @@
1
+ import * as React from "react";
2
+ export interface ChartGridProps {
3
+ /** Chart components to arrange in the grid */
4
+ children: React.ReactNode;
5
+ /** Number of columns. Default: "auto" (auto-fill based on minCellWidth) */
6
+ columns?: number | "auto";
7
+ /** Minimum cell width for auto columns. Default: 300 */
8
+ minCellWidth?: number;
9
+ /** Gap between cells in pixels. Default: 16 */
10
+ gap?: number;
11
+ /** CSS class for the grid container */
12
+ className?: string;
13
+ /** Inline style overrides */
14
+ style?: React.CSSProperties;
15
+ }
16
+ /**
17
+ * ChartGrid — responsive grid layout for multiple Semiotic charts.
18
+ *
19
+ * Arranges child charts in a CSS Grid that reflows based on available space.
20
+ * Each cell automatically gets `responsiveWidth` behavior since the grid
21
+ * manages the cell dimensions.
22
+ *
23
+ * Works naturally with `LinkedCharts` for coordinated views:
24
+ *
25
+ * ```tsx
26
+ * <LinkedCharts>
27
+ * <ChartGrid columns={2}>
28
+ * <LineChart data={d} xAccessor="x" yAccessor="y" responsiveWidth />
29
+ * <BarChart data={d} categoryAccessor="cat" valueAccessor="val" responsiveWidth />
30
+ * </ChartGrid>
31
+ * </LinkedCharts>
32
+ * ```
33
+ */
34
+ export declare function ChartGrid({ children, columns, minCellWidth, gap, className, style, }: ChartGridProps): React.JSX.Element;
35
+ export declare namespace ChartGrid {
36
+ var displayName: string;
37
+ }
@@ -0,0 +1,38 @@
1
+ import * as React from "react";
2
+ export interface ContextLayoutProps {
3
+ /** The main chart (renders at full size in the primary slot) */
4
+ children: React.ReactNode;
5
+ /** Context chart(s) displayed alongside the primary chart */
6
+ context: React.ReactNode;
7
+ /** Position of the context panel. Default: "right" */
8
+ position?: "right" | "left" | "bottom" | "top";
9
+ /** Size of the context panel in pixels. Default: 250 */
10
+ contextSize?: number;
11
+ /** Gap between primary and context panels in pixels. Default: 12 */
12
+ gap?: number;
13
+ /** CSS class for the layout container */
14
+ className?: string;
15
+ /** Inline style overrides */
16
+ style?: React.CSSProperties;
17
+ }
18
+ /**
19
+ * ContextLayout — places a primary chart alongside one or more context charts.
20
+ *
21
+ * The primary chart fills available space while the context panel has a fixed
22
+ * width (or height for top/bottom). Context charts should use `mode="context"`
23
+ * for compact rendering without axes or labels.
24
+ *
25
+ * ```tsx
26
+ * <ContextLayout
27
+ * context={<Treemap data={hierarchy} mode="context" responsiveWidth colorByDepth />}
28
+ * position="right"
29
+ * contextSize={250}
30
+ * >
31
+ * <LineChart data={timeSeries} xAccessor="time" yAccessor="value" responsiveWidth />
32
+ * </ContextLayout>
33
+ * ```
34
+ */
35
+ export declare function ContextLayout({ children, context, position, contextSize, gap, className, style, }: ContextLayoutProps): React.JSX.Element;
36
+ export declare namespace ContextLayout {
37
+ var displayName: string;
38
+ }
@@ -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";
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Statistical overlay processing for LineChart.
3
+ *
4
+ * Two modes:
5
+ * **Auto mode** — provide `trainEnd` + optional `steps`/`confidence`.
6
+ * The module computes regression, generates forecast points, and builds
7
+ * annotations (envelope, anomaly band, boundary line).
8
+ *
9
+ * **Pre-computed mode** — provide field accessors (`isTraining`, `isForecast`,
10
+ * `isAnomaly`, `upperBounds`, `lowerBounds`). The module reads segment/bounds
11
+ * from the data and generates annotations without any statistical computation.
12
+ * Use this when bounds come from an external ML model.
13
+ */
14
+ export interface AnomalyConfig {
15
+ /** Standard deviation multiplier for anomaly bounds. Default: 2 */
16
+ threshold?: number;
17
+ /** Show shaded anomaly band. Default: true */
18
+ showBand?: boolean;
19
+ /** Band fill color. Default: "#6366f1" */
20
+ bandColor?: string;
21
+ /** Band fill opacity. Default: 0.1 */
22
+ bandOpacity?: number;
23
+ /** Outlier dot color. Default: "#ef4444" */
24
+ anomalyColor?: string;
25
+ /** Outlier dot radius. Default: 6 */
26
+ anomalyRadius?: number;
27
+ /** Label for the band */
28
+ label?: string;
29
+ }
30
+ export interface ForecastConfig {
31
+ /** X-value where training data ends. Required for auto mode. */
32
+ trainEnd?: number;
33
+ /** Number of forecast steps beyond last data point. Default: 10 */
34
+ steps?: number;
35
+ /** Regression method. Default: "linear" */
36
+ method?: "linear" | "loess";
37
+ /** LOESS bandwidth (only for method="loess"). Default: 0.3 */
38
+ bandwidth?: number;
39
+ /** Confidence level for prediction interval (0-1). Default: 0.95 */
40
+ confidence?: number;
41
+ /** Field or function marking training data points */
42
+ isTraining?: string | ((d: Record<string, any>) => boolean);
43
+ /** Field or function marking forecast data points */
44
+ isForecast?: string | ((d: Record<string, any>) => boolean);
45
+ /** Field or function marking anomalous data points */
46
+ isAnomaly?: string | ((d: Record<string, any>) => boolean);
47
+ /** Field or function for upper envelope bound per data point */
48
+ upperBounds?: string | ((d: Record<string, any>) => number);
49
+ /** Field or function for lower envelope bound per data point */
50
+ lowerBounds?: string | ((d: Record<string, any>) => number);
51
+ /** Color for forecast line and envelope. Default: "#6366f1" */
52
+ color?: string;
53
+ /** Envelope fill opacity. Default: 0.15 */
54
+ bandOpacity?: number;
55
+ /** Dash pattern for training line segment. Default: "8,4" */
56
+ trainDasharray?: string;
57
+ /** Dash pattern for forecast line segment. Default: "4,4" */
58
+ forecastDasharray?: string;
59
+ /** Outlier dot color (pre-computed mode). Default: "#ef4444" */
60
+ anomalyColor?: string;
61
+ /** Outlier dot radius (pre-computed mode). Default: 6 */
62
+ anomalyRadius?: number;
63
+ /** Label for the forecast/envelope region */
64
+ label?: string;
65
+ }
66
+ /** Internal segment marker added to each datum */
67
+ export declare const SEGMENT_FIELD: "__forecastSegment";
68
+ export type SegmentType = "training" | "observed" | "forecast";
69
+ export declare function buildAnomalyAnnotations(config: AnomalyConfig): Record<string, any>[];
70
+ interface ForecastResult {
71
+ processedData: Record<string, any>[];
72
+ annotations: Record<string, any>[];
73
+ }
74
+ export declare function buildForecast(data: Record<string, any>[], xAccessor: string, yAccessor: string, forecastConfig: ForecastConfig, anomalyConfig?: AnomalyConfig): ForecastResult;
75
+ export declare function createSegmentLineStyle(baseStyle: (d: Record<string, any>) => Record<string, any>, forecastConfig: ForecastConfig): (d: Record<string, any>) => Record<string, any>;
76
+ export {};
@@ -50,6 +50,10 @@ export interface BaseChartProps {
50
50
  height?: number;
51
51
  /** Margin around the chart. Can be number (same on all sides) or object specifying each side */
52
52
  margin?: MarginType;
53
+ /** Auto-match width to parent container. Default: false */
54
+ responsiveWidth?: boolean;
55
+ /** Auto-match height to parent container (requires parent with explicit height). Default: false */
56
+ responsiveHeight?: boolean;
53
57
  /** CSS class name for the chart container */
54
58
  className?: string;
55
59
  /** Chart title displayed at the top */
@@ -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 {};