semiotic 3.5.1 → 3.5.3
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 +26 -23
- package/README.md +27 -21
- package/ai/chartSuggestions.cjs +191 -3
- package/ai/componentMetadata.cjs +3 -3
- package/ai/dist/mcp-server.js +266 -48
- package/ai/examples.md +68 -0
- package/ai/schema.json +914 -1
- package/ai/system-prompt.md +4 -1
- package/dist/components/ThemeProvider.d.ts +2 -2
- package/dist/components/Tooltip/FlippingTooltip.d.ts +16 -1
- package/dist/components/charts/geo/FlowMap.d.ts +13 -4
- package/dist/components/charts/index.d.ts +6 -0
- package/dist/components/charts/network/OrbitDiagram.d.ts +5 -5
- package/dist/components/charts/network/ProcessSankey.d.ts +163 -0
- package/dist/components/charts/network/SankeyDiagram.d.ts +5 -1
- package/dist/components/charts/network/processSankey/algorithm.d.ts +193 -0
- package/dist/components/charts/network/processSankey/buildScenes.d.ts +51 -0
- package/dist/components/charts/network/processSankey/ribbonInputs.d.ts +32 -0
- package/dist/components/charts/network/processSankey/streamingLayout.d.ts +71 -0
- package/dist/components/charts/network/processSankey/tooltipUtils.d.ts +41 -0
- package/dist/components/charts/ordinal/BarChart.d.ts +12 -0
- package/dist/components/charts/ordinal/DotPlot.d.ts +9 -0
- package/dist/components/charts/ordinal/GaugeChart.d.ts +20 -0
- package/dist/components/charts/ordinal/SwimlaneChart.d.ts +5 -0
- package/dist/components/charts/realtime/RealtimeHeatmap.d.ts +2 -0
- package/dist/components/charts/realtime/RealtimeHistogram.d.ts +16 -11
- 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/realtime/defaultRealtimeTooltip.d.ts +19 -0
- package/dist/components/charts/shared/axisExtent.d.ts +59 -0
- package/dist/components/charts/shared/chartSpecs.d.ts +75 -0
- package/dist/components/charts/shared/colorUtils.d.ts +8 -2
- package/dist/components/charts/shared/networkUtils.d.ts +3 -5
- package/dist/components/charts/shared/radialGeometry.d.ts +99 -0
- package/dist/components/charts/shared/regressionUtils.d.ts +59 -0
- package/dist/components/charts/shared/selectionUtils.d.ts +8 -1
- package/dist/components/charts/shared/streamPropsHelpers.d.ts +5 -0
- package/dist/components/charts/shared/tooltipUtils.d.ts +11 -0
- package/dist/components/charts/shared/types.d.ts +19 -0
- package/dist/components/charts/shared/useAreaSeriesSetup.d.ts +78 -0
- package/dist/components/charts/shared/useChartSetup.d.ts +2 -0
- package/dist/components/charts/shared/useCustomChartSetup.d.ts +1 -0
- package/dist/components/charts/shared/useEncodingDomain.d.ts +48 -0
- package/dist/components/charts/shared/useFrameImperativeHandle.d.ts +1 -1
- package/dist/components/charts/shared/useNetworkChartSetup.d.ts +150 -0
- package/dist/components/charts/shared/useOrdinalPieceStyle.d.ts +87 -0
- package/dist/components/charts/shared/useSeriesFeatures.d.ts +57 -0
- package/dist/components/charts/shared/useStreamStatus.d.ts +33 -0
- package/dist/components/charts/shared/useXYLineStyle.d.ts +69 -0
- package/dist/components/charts/shared/useXYPointStyle.d.ts +87 -0
- package/dist/components/charts/shared/withChartWrapper.d.ts +10 -3
- package/dist/components/charts/xy/AreaChart.d.ts +36 -2
- package/dist/components/charts/xy/BubbleChart.d.ts +9 -0
- package/dist/components/charts/xy/ConnectedScatterplot.d.ts +16 -0
- package/dist/components/charts/xy/DifferenceChart.d.ts +172 -0
- package/dist/components/charts/xy/LineChart.d.ts +26 -2
- package/dist/components/charts/xy/Scatterplot.d.ts +39 -1
- package/dist/components/geometry/ribbonGeometry.d.ts +76 -0
- package/dist/components/semiotic-ai.d.ts +2 -0
- package/dist/components/semiotic-network.d.ts +4 -0
- package/dist/components/semiotic-realtime.d.ts +2 -0
- package/dist/components/semiotic-utils.d.ts +4 -0
- package/dist/components/semiotic-xy.d.ts +2 -0
- package/dist/components/semiotic.d.ts +4 -4
- package/dist/components/server/renderToStaticSVG.d.ts +2 -1
- package/dist/components/server/serverChartConfigs.d.ts +3 -0
- package/dist/components/server/staticAnnotations.d.ts +1 -1
- package/dist/components/server/themeResolver.d.ts +7 -1
- package/dist/components/store/ThemeStore.d.ts +7 -1
- package/dist/components/stream/AccessibleDataTable.d.ts +2 -2
- package/dist/components/stream/GeoPipelineStore.d.ts +21 -0
- package/dist/components/stream/OrdinalSVGOverlay.d.ts +8 -0
- package/dist/components/stream/PipelineStore.d.ts +25 -13
- package/dist/components/stream/SVGOverlay.d.ts +9 -18
- package/dist/components/stream/accessorUtils.d.ts +2 -1
- package/dist/components/stream/annotationAccessorResolver.d.ts +39 -0
- package/dist/components/stream/geoTypes.d.ts +12 -0
- package/dist/components/stream/layouts/hierarchyLayoutPlugin.d.ts +1 -1
- package/dist/components/stream/networkTypes.d.ts +11 -0
- package/dist/components/stream/ordinalTypes.d.ts +27 -1
- package/dist/components/stream/renderers/cornerRadii.d.ts +33 -0
- package/dist/components/stream/renderers/wedgePathBuilder.d.ts +56 -0
- package/dist/components/stream/types.d.ts +127 -11
- package/dist/components/stream/xySceneBuilders/areaGradient.d.ts +20 -0
- package/dist/components/stream/xySceneBuilders/barScene.d.ts +2 -2
- package/dist/components/stream/xySceneBuilders/candlestickScene.d.ts +2 -2
- package/dist/components/stream/xySceneBuilders/heatmapScene.d.ts +2 -2
- package/dist/components/stream/xySceneBuilders/lineScene.d.ts +4 -3
- package/dist/components/stream/xySceneBuilders/pointScene.d.ts +2 -2
- package/dist/components/stream/xySceneBuilders/ribbonScene.d.ts +107 -0
- package/dist/components/stream/xySceneBuilders/swarmScene.d.ts +2 -2
- package/dist/components/stream/xySceneBuilders/types.d.ts +9 -12
- package/dist/components/stream/xySceneBuilders/waterfallScene.d.ts +2 -2
- package/dist/components/types/legendTypes.d.ts +1 -1
- 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.d.ts +2 -0
- package/dist/semiotic-ai.min.js +1 -1
- package/dist/semiotic-ai.module.min.js +1 -1
- package/dist/semiotic-data.min.js +1 -1
- package/dist/semiotic-data.module.min.js +1 -1
- package/dist/semiotic-network.d.ts +4 -0
- package/dist/semiotic-realtime.d.ts +2 -0
- package/dist/semiotic-recipes.min.js +1 -1
- package/dist/semiotic-recipes.module.min.js +1 -1
- package/dist/semiotic-themes.min.js +1 -1
- package/dist/semiotic-themes.module.min.js +1 -1
- package/dist/semiotic-utils.d.ts +4 -0
- package/dist/semiotic-utils.min.js +1 -1
- package/dist/semiotic-utils.module.min.js +1 -1
- package/dist/semiotic-xy.d.ts +2 -0
- package/dist/semiotic.d.ts +4 -4
- 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 +23 -10
- package/dist/components/stream/xySceneBuilders/boundsScene.d.ts +0 -9
package/CLAUDE.md
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
## Quick Start
|
|
4
4
|
- Install: `npm install semiotic`
|
|
5
|
-
|
|
5
|
+
<!-- semiotic-bundle-sizes:start -->
|
|
6
|
+
<!-- Auto-generated by scripts/sync-bundle-sizes.mjs — do not edit by hand. -->
|
|
7
|
+
- **Use sub-path imports** — `semiotic/xy` (85KB gz), `semiotic/ordinal` (68KB gz), `semiotic/network` (63KB gz), `semiotic/geo` (51KB gz), `semiotic/realtime` (90KB gz), `semiotic/server` (117KB gz), `semiotic/utils` (22KB gz), `semiotic/recipes` (5KB gz), `semiotic/themes` (4KB gz), `semiotic/data` (3KB gz), `semiotic/ai` (188KB gz). Full `semiotic` is 186KB gz.
|
|
8
|
+
<!-- semiotic-bundle-sizes:end -->
|
|
6
9
|
- CLI: `npx semiotic-ai [--schema|--compact|--examples|--doctor]`
|
|
7
10
|
- MCP: `npx semiotic-mcp`
|
|
8
11
|
|
|
@@ -12,7 +15,7 @@
|
|
|
12
15
|
- Every HOC accepts `frameProps` for pass-through. TypeScript `strict: true`. Every HOC has error boundary + dev-mode validation.
|
|
13
16
|
|
|
14
17
|
## Common Props (all HOCs)
|
|
15
|
-
`title`, `description` (aria-label), `summary` (sr-only), `width` (600), `height` (400), `responsiveWidth`, `responsiveHeight`, `margin`, `className`, `color` (uniform fill), `stroke` (uniform stroke color — CSS var OK), `strokeWidth` (uniform stroke width in px), `opacity` (uniform 0–1 opacity), `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), `animate` (boolean | { duration?, easing?, intro? } — animated intro on first render + smooth transitions on data change; intro defaults to true when animate is enabled)
|
|
18
|
+
`title`, `description` (aria-label), `summary` (sr-only), `width` (600), `height` (400), `responsiveWidth`, `responsiveHeight`, `margin`, `className`, `color` (uniform fill), `stroke` (uniform stroke color — CSS var OK), `strokeWidth` (uniform stroke width in px), `opacity` (uniform 0–1 opacity), `enableHover` (true), `tooltip` (boolean | "multi" | function | config object), `showLegend`, `showGrid` (false), `frameProps`, `onObservation`, `onClick`, `chartId`, `loading` (false), `loadingContent` (ReactNode — replaces the default skeleton when `loading` is true; pass `false` to suppress the loading UI entirely), `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), `animate` (boolean | { duration?, easing?, intro? } — animated intro on first render + smooth transitions on data change; intro defaults to true when animate is enabled), `axisExtent` ("nice" default | "exact" — pins first/last tick to actual data min/max with equidistant intermediates. Applies to XY x/y axes and ordinal value axis only; no-op on network/geo/hierarchy. Explicit `tickValues` still wins.)
|
|
16
19
|
|
|
17
20
|
**Primitive styling props** (`color`, `stroke`, `strokeWidth`, `opacity`) apply to any shape the chart draws (bars, circles, lines, wedges, rects). Precedence: top-level prop > `frameProps.*Style` function return > HOC base > theme fallback. Use CSS variables (`stroke="var(--semiotic-border)"`) for theme-aware, cascade-overridable styling. For per-datum customization, keep using the function-form `frameProps.pieceStyle` / `pointStyle` / `lineStyle` etc. — the top-level prop overlays on top of whatever the function returns.
|
|
18
21
|
|
|
@@ -20,12 +23,13 @@
|
|
|
20
23
|
|
|
21
24
|
## XY Charts (`semiotic/xy`)
|
|
22
25
|
|
|
23
|
-
**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"), `tooltip="multi"` for hover-anywhere series comparison
|
|
24
|
-
**AreaChart** — LineChart props + `areaBy`, `y0Accessor`, `gradientFill`, `areaOpacity` (0.7), `showLine` (true), `tooltip="multi"` for hover-anywhere area comparison
|
|
25
|
-
**
|
|
26
|
-
**
|
|
27
|
-
**
|
|
28
|
-
**
|
|
26
|
+
**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`, `band` (asymmetric min/max envelope: `{ y0Accessor, y1Accessor, style?, perSeries?, interactive? }` or array for fan charts; participates in yExtent; non-interactive by default; hovered datum is enriched with `band: {y0,y1}` and `bands: [...]`), `directLabel`, `gapStrategy`, `xScaleType`/`yScaleType` ("linear"|"log"|"time"), `tooltip="multi"` for hover-anywhere series comparison
|
|
27
|
+
**AreaChart** — LineChart props + `areaBy`, `y0Accessor`, `gradientFill`, `areaOpacity` (0.7), `showLine` (true), `band` (same shape as LineChart — decorative envelope under the area), `tooltip="multi"` for hover-anywhere area comparison
|
|
28
|
+
**DifferenceChart** — Two-series A/B comparison. Fills the area between with `seriesAColor` where A > B and `seriesBColor` where B > A; crossovers interpolated. Props: `data`, `xAccessor` ("x"), `seriesAAccessor` ("a"), `seriesBAccessor` ("b"), `seriesALabel`/`seriesBLabel`, `seriesAColor` (var(--semiotic-danger))/`seriesBColor` (var(--semiotic-info)), `showLines` (true), `lineWidth` (1.5), `showPoints` (false), `pointRadius` (3), `curve` ("linear"), `areaOpacity` (0.6), `gradientFill`, `xExtent`/`yExtent`, `pointIdAccessor`, `windowSize` (max raw rows in push buffer; FIFO eviction). Push API via `ref.current.push({x, a, b})`. Accessor outputs coerce through `toNumber` so `Date` + numeric strings are accepted.
|
|
29
|
+
**StackedAreaChart** — flat array + `areaBy` (required), `colorBy`, `normalize`, `baseline` (`"zero"` default | `"wiggle"` streamgraph | `"silhouette"` centered), `stackOrder` (`"key"` alpha | `"insideOut"` largest-in-middle | `"asc"`/`"desc"`). Streamgraph: `baseline="wiggle"` + `stackOrder="insideOut"`. `baseline` ⊥ `normalize`. No `lineBy`/`lineDataAccessor`. `tooltip="multi"` lists every series at the hovered x (values interpolated between samples).
|
|
30
|
+
**Scatterplot** — `data`, `xAccessor`, `yAccessor`, `colorBy`, `sizeBy`, `sizeRange`, `pointRadius` (5), `pointOpacity` (0.8), `marginalGraphics`, `regression` (boolean | "linear" | "polynomial" | "loess" | RegressionConfig — sugar for a trend-annotation overlay; sits underneath user annotations)
|
|
31
|
+
**BubbleChart** — Scatterplot + `sizeBy` (required), `sizeRange` ([5,40]), `regression`
|
|
32
|
+
**ConnectedScatterplot** — + `orderAccessor`, `regression`
|
|
29
33
|
**QuadrantChart** — Scatterplot + `quadrants` (required), `xCenter`, `yCenter`
|
|
30
34
|
**MultiAxisLineChart** — Dual Y-axis. `series` (required: `[{ yAccessor, label?, color?, format?, extent? }]`). Falls back to multi-line if not 2 series.
|
|
31
35
|
**Heatmap** — `data`, `xAccessor`, `yAccessor`, `valueAccessor`, `colorScheme`, `showValues`, `cellBorderColor`
|
|
@@ -35,7 +39,7 @@
|
|
|
35
39
|
|
|
36
40
|
## Ordinal Charts (`semiotic/ordinal`)
|
|
37
41
|
|
|
38
|
-
**BarChart** — `data`, `categoryAccessor`, `valueAccessor`, `orientation`, `colorBy`, `sort`, `barPadding` (40), `roundedTop`, `gradientFill` (`true` | `{topOpacity, bottomOpacity}` | `{colorStops}` — same API as AreaChart; runs tip→base)
|
|
42
|
+
**BarChart** — `data`, `categoryAccessor`, `valueAccessor`, `orientation`, `colorBy`, `sort`, `barPadding` (40), `roundedTop`, `gradientFill` (`true` | `{topOpacity, bottomOpacity}` | `{colorStops}` — same API as AreaChart; runs tip→base), `regression` (boolean | "linear" | "polynomial" | "loess" | RegressionConfig — sugar for a trend-annotation overlay; categories regressed as category-index, line projects through band scale)
|
|
39
43
|
**StackedBarChart** — + `stackBy` (required), `normalize`, `sort` (default false — insertion order)
|
|
40
44
|
**GroupedBarChart** — + `groupBy` (required), `barPadding` (60), `sort` (default false — insertion order)
|
|
41
45
|
**SwarmPlot** — `colorBy`, `sizeBy`, `pointRadius`, `pointOpacity`
|
|
@@ -43,13 +47,13 @@
|
|
|
43
47
|
**Histogram** — + `bins` (25), `relative`. Always horizontal.
|
|
44
48
|
**ViolinPlot** — + `bins`, `curve`, `showIQR`
|
|
45
49
|
**RidgelinePlot** — + `bins`, `amplitude` (1.5)
|
|
46
|
-
**DotPlot** — + `sort` ("auto" — insertion order when streaming, value-desc on static), `dotRadius`, `showGrid` default true
|
|
50
|
+
**DotPlot** — + `sort` ("auto" — insertion order when streaming, value-desc on static), `dotRadius`, `showGrid` default true, `regression`
|
|
47
51
|
**PieChart** — `categoryAccessor`, `valueAccessor`, `colorBy`, `startAngle`
|
|
48
52
|
**DonutChart** — PieChart + `innerRadius` (60), `centerContent`
|
|
49
53
|
**FunnelChart** — `stepAccessor`, `valueAccessor`, `categoryAccessor` (optional), `connectorOpacity`, `orientation`
|
|
50
|
-
**SwimlaneChart** — `categoryAccessor`, `subcategoryAccessor` (required), `valueAccessor`, `colorBy` (defaults to subcategoryAccessor), `orientation`
|
|
54
|
+
**SwimlaneChart** — `categoryAccessor`, `subcategoryAccessor` (required), `valueAccessor`, `colorBy` (defaults to subcategoryAccessor), `orientation`, `roundedTop` (pixel radius applied to both outer ends of each lane — left+right for horizontal, top+bottom for vertical. Middle segments stay square so adjacent pieces butt against each other; single-segment lanes round all four corners.)
|
|
51
55
|
**LikertChart** — `categoryAccessor`, `valueAccessor`|`levelAccessor`+`countAccessor`, `levels` (required), `orientation`, `colorScheme`
|
|
52
|
-
**GaugeChart** — `value` (required), `min`, `max`, `thresholds`, `arcWidth`, `sweep`, `fillZones`, `showNeedle`, `centerContent`
|
|
56
|
+
**GaugeChart** — `value` (required), `min`, `max`, `thresholds`, `arcWidth`, `cornerRadius` (pixel radius for rounded segment ends — same semantics as DonutChart), `sweep`, `fillZones`, `showNeedle`, `centerContent`
|
|
53
57
|
|
|
54
58
|
All ordinal: `colorBy`, `colorScheme`, `categoryFormat` (string|ReactNode), `showCategoryTicks` (true).
|
|
55
59
|
|
|
@@ -57,6 +61,7 @@ All ordinal: `colorBy`, `colorScheme`, `categoryFormat` (string|ReactNode), `sho
|
|
|
57
61
|
|
|
58
62
|
**ForceDirectedGraph** — `nodes`, `edges`, `nodeIDAccessor`, `sourceAccessor`, `targetAccessor`, `colorBy`, `nodeSize`, `nodeSizeRange`, `edgeWidth`, `iterations` (300), `forceStrength` (0.1), `showLabels`, `nodeLabel`
|
|
59
63
|
**SankeyDiagram** — `edges`, `nodes`, `valueAccessor`, `nodeIdAccessor`, `colorBy`, `edgeColorBy`, `orientation`, `nodeAlign`, `nodeWidth`, `nodePaddingRatio`, `showLabels`
|
|
64
|
+
**ProcessSankey** — temporal sankey with a real time x-axis. `nodes`, `edges` (each with `startTime`/`endTime`), `domain` (required `[t0, t1]`), `axisTicks?`, `xExtentAccessor` (optional `[start, end]` lifetime per node — lane spans `min(xExtent[0], earliestEdge)` to `max(xExtent[1], latestEdge)`), `colorBy`/`colorScheme`/`showLegend`/`legendPosition`, `pairing` ("value"|"temporal"), `packing` ("off"|"reuse"), `laneOrder` ("crossing-min"|"inside-out"|"crossing-min+inside-out"|"insertion"), `lifetimeMode` ("full"|"half"), `ribbonLane` ("source"|"target"|"both"), `showLaneRails`, `showLabels` (default true), `showQualityReadout`, `showParticles` + `particleStyle` (same shape as SankeyDiagram, canvas + ParticlePool), `timeFormat`/`valueFormat`, push API via ref. Static-graph cycles are valid as long as edges move forward in time. Use for time-stamped flow events; use `SankeyDiagram` for static total-flow snapshots.
|
|
60
65
|
**ChordDiagram** — `edges`, `nodes`, `valueAccessor`, `edgeColorBy`, `padAngle`, `showLabels`
|
|
61
66
|
**TreeDiagram** — `data` (root), `layout`, `orientation`, `childrenAccessor`, `colorBy`, `colorByDepth`
|
|
62
67
|
**Treemap** — `data` (root), `childrenAccessor`, `valueAccessor`, `colorBy`, `colorByDepth`, `showLabels`
|
|
@@ -131,7 +136,7 @@ import {
|
|
|
131
136
|
<NetworkCustomChart nodes={nodes} edges={edges} layout={flextreeLayout} layoutConfig={{ ... }} />
|
|
132
137
|
```
|
|
133
138
|
|
|
134
|
-
**Recipes subpath** (`semiotic/recipes
|
|
139
|
+
**Recipes subpath** (`semiotic/recipes`) ships pure layout functions. They emit standard SceneNodes — no chart code. BYO heavy deps (`d3-flextree`, `dagre`) live in user code.
|
|
135
140
|
|
|
136
141
|
### Chrome (labels, axes, legends): the recipe owns it
|
|
137
142
|
|
|
@@ -170,11 +175,9 @@ const [hovered, setHovered] = useState(null)
|
|
|
170
175
|
/>
|
|
171
176
|
```
|
|
172
177
|
|
|
173
|
-
The same predicate hook is the integration point for richer interactions on the roadmap: a future `<ParallelCoordinatesBrushes>` overlay that drag-brushes per-axis ranges, and `useBrushSelection`-style linked brushing across coordinated charts (selection store would carry per-field range constraints, downstream charts consume them via the same hook).
|
|
174
|
-
|
|
175
178
|
### Built-in chrome works when the layout uses standard scales
|
|
176
179
|
|
|
177
|
-
|
|
180
|
+
Pass `showAxes` on `XYCustomChart` / `OrdinalCustomChart` to get the same x/y or o/r axes the built-in HOCs render — useful for custom layouts that overlay points/lines on regular scales. Recipe-managed chrome is for cases where standard axes don't fit (variable-width bars under a band scale, per-row independent scales, etc.).
|
|
178
181
|
|
|
179
182
|
**Notes:**
|
|
180
183
|
- Coords are plot-relative (the frame translates the canvas/SVG group by `margin`). Read `ctx.dimensions.plot` for the drawing rect. Radial ordinal projection is the one exception: `plot.x = -width/2`, `plot.y = -height/2` because the canvas ctx is center-translated.
|
|
@@ -219,7 +222,7 @@ Server SVGs include `role="img"`, `<title>`, `<desc>`, grid, legend, annotations
|
|
|
219
222
|
- **GroupedBarChart** — `data`, `categoryAccessor`, `valueAccessor`, `groupBy` (required).
|
|
220
223
|
- **PieChart/DonutChart** — `data`, `categoryAccessor`, `valueAccessor`.
|
|
221
224
|
- **FunnelChart** — `data`, `stepAccessor` ("step"), `valueAccessor` ("value"). Renders with trapezoid connectors, no axes.
|
|
222
|
-
- **GaugeChart** — `value`. Optional: `thresholds` (array of `{value, color, label}`), `min`, `max`, `sweep`, `arcWidth`.
|
|
225
|
+
- **GaugeChart** — `value`. Optional: `thresholds` (array of `{value, color, label}`), `min`, `max`, `sweep`, `arcWidth`, `cornerRadius`.
|
|
223
226
|
- **SwimlaneChart** — `data`, `categoryAccessor`, `subcategoryAccessor` (required), `valueAccessor`.
|
|
224
227
|
- **ForceDirectedGraph** — `nodes`, `edges` (both required). If deriving nodes from edge endpoints, materialize `nodes` before returning JSX/renderChart props.
|
|
225
228
|
- **SankeyDiagram** — `edges` (required), `valueAccessor`.
|
|
@@ -240,7 +243,7 @@ All HOCs accept `annotations` (array). Coordinates use data field names.
|
|
|
240
243
|
|
|
241
244
|
## Theming
|
|
242
245
|
|
|
243
|
-
CSS custom properties: `--semiotic-bg`, `--semiotic-text`, `--semiotic-text-secondary`, `--semiotic-border`, `--semiotic-grid`, `--semiotic-primary`, `--semiotic-secondary`, `--semiotic-surface`, `--semiotic-success`, `--semiotic-danger`, `--semiotic-warning`, `--semiotic-error`, `--semiotic-info`, `--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`.
|
|
246
|
+
CSS custom properties: `--semiotic-bg`, `--semiotic-text`, `--semiotic-text-secondary`, `--semiotic-border`, `--semiotic-grid`, `--semiotic-primary`, `--semiotic-secondary`, `--semiotic-surface`, `--semiotic-success`, `--semiotic-danger`, `--semiotic-warning`, `--semiotic-error`, `--semiotic-info`, `--semiotic-focus`, `--semiotic-font-family`, `--semiotic-annotation-color`, `--semiotic-legend-font-size`, `--semiotic-title-font-size`, `--semiotic-tick-font-family`, `--semiotic-tick-font-size` (10px default — drives axis tick text), `--semiotic-axis-label-font-size` (12px default — drives axis-label text + foreignObject ticks), `--semiotic-tooltip-bg`/`text`/`radius`/`font-size`/`shadow`.
|
|
244
247
|
|
|
245
248
|
```jsx
|
|
246
249
|
<ThemeProvider theme="tufte"> {/* Named preset */}
|
|
@@ -297,9 +300,8 @@ These rules are generated from `ai/behaviorContracts.cjs` and are consumed by `s
|
|
|
297
300
|
- **Legend**: "bottom" expands margin ~80px. MultiAxisLineChart: use `legendPosition="bottom"`.
|
|
298
301
|
- **Log scale**: Domain min clamped to 1e-6.
|
|
299
302
|
- **barPadding**: Pixel value (40/60 default). Reduce for small charts.
|
|
300
|
-
- **sort
|
|
301
|
-
- **
|
|
302
|
-
- **Tooltip format cascade**: `valueFormat` (ordinal) / `xFormat` / `yFormat` (XY) / `valueFormat` on Heatmap flow to the default tooltip automatically, so axis and tooltip read identically. Only applies to the default tooltip — a custom `tooltip` prop fully overrides; re-pass the formatter inside `Tooltip({format})` / `MultiLineTooltip({fields:[{format}]})` if you want it there. Bespoke-tooltip charts (Histogram, FunnelChart, LikertChart, GaugeChart) don't participate; customize via `tooltip`.
|
|
303
|
+
- **sort** (BarChart/StackedBarChart/GroupedBarChart/DotPlot): `false` preserves insertion order; `"auto"` = insertion-order while streaming, value-desc on static (DotPlot default — opt-in on others, avoids category shuffling under the push API). StackedBar/GroupedBar default to `false`; the underlying frame value-sorts when `oSort` is undefined, so always pass `sort` explicitly if order matters.
|
|
304
|
+
- **Tooltip format cascade**: `valueFormat`/`xFormat`/`yFormat` flow to the default tooltip automatically, so axis and tooltip read identically. A custom `tooltip` prop fully overrides — re-pass via `Tooltip({format})` / `MultiLineTooltip({fields:[{format}]})`. Bespoke-tooltip charts (Histogram, FunnelChart, LikertChart, GaugeChart) don't participate; customize via `tooltip`.
|
|
303
305
|
- **Horizontal bars**: Need wider left margin: `margin={{ left: 120 }}`.
|
|
304
306
|
- **Push API**: Omit `data` entirely. `data={[]}` clears on every render.
|
|
305
307
|
- **frameProps style functions**: Bypass HOC color resolution — use `colorBy` prop instead.
|
|
@@ -307,12 +309,13 @@ These rules are generated from `ai/behaviorContracts.cjs` and are consumed by `s
|
|
|
307
309
|
- **fillArea**: `fillArea={["seriesA"]}` fills named series only. Names must match `lineBy`/`colorBy` keys.
|
|
308
310
|
- **hoverHighlight**: Requires `colorBy` as a string field.
|
|
309
311
|
- **tooltip="multi"**: Shows all series at hovered X for LineChart, AreaChart, and StackedAreaChart. Custom fn receives `datum.allSeries`.
|
|
310
|
-
- **Axis config**: `frameProps.axes: [{ orient, includeMax, autoRotate, gridStyle, landmarkTicks }]`
|
|
312
|
+
- **Axis config**: `frameProps.axes: [{ orient, includeMax, autoRotate, gridStyle, landmarkTicks, tickAnchor }]`. `tickAnchor: "edges"` flips the first tick's `text-anchor` to `start` and the last to `end` on horizontal axes (and `dominant-baseline` to `hanging`/`auto` on vertical axes) so edge labels don't overflow the plot. Pairs naturally with `axisExtent: "exact"`.
|
|
313
|
+
- **Targeting individual axes from CSS**: every axis renders as its own `<g class="semiotic-axis semiotic-axis-{bottom|left|right|top}" data-orient="…">`. Style with `[data-orient="left"] text { font-size: 14px }` or via the class names — no `!important` needed because the CSS-var defaults are set inline via `var(--semiotic-tick-font-size, …)`, so cascade overrides win cleanly. Tick text carries `class="semiotic-axis-tick"`; labels carry `class="semiotic-axis-label"`; titles carry `class="semiotic-chart-title"`.
|
|
311
314
|
- **xScaleType: "time"**: Creates `scaleTime`. Required for landmark ticks with timestamps.
|
|
312
315
|
- **scalePadding**: Pixel inset on scale ranges. Pass via `frameProps={{ scalePadding: 12 }}`.
|
|
313
316
|
- **categoryFormat/xFormat/yFormat**: Can return ReactNode (renders in `<foreignObject>`).
|
|
314
317
|
- **Tick deduplication**: Adjacent identical labels auto-removed.
|
|
315
|
-
- **Composing overlays**: XY/Ordinal charts paint `--semiotic-bg` across the
|
|
318
|
+
- **Composing overlays**: XY/Ordinal charts paint `--semiotic-bg` across the canvas; stack with `frameProps={{ background: "transparent" }}` on the overlay. Network/Geo don't paint bg by default.
|
|
316
319
|
|
|
317
320
|
## Performance
|
|
318
321
|
|
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ dashboards when you need them. Structured schemas and an MCP server so
|
|
|
13
13
|
AI coding assistants generate correct chart code on the first try.
|
|
14
14
|
|
|
15
15
|
```jsx
|
|
16
|
-
import { LineChart } from "semiotic/xy"
|
|
16
|
+
import { LineChart } from "semiotic/xy"
|
|
17
17
|
|
|
18
18
|
<LineChart
|
|
19
19
|
data={salesData}
|
|
@@ -35,7 +35,7 @@ generate correct code without examples.
|
|
|
35
35
|
Semiotic ships with everything an AI coding assistant needs to generate
|
|
36
36
|
correct visualizations without trial and error:
|
|
37
37
|
|
|
38
|
-
- **`semiotic/ai`** — a single import with
|
|
38
|
+
- **`semiotic/ai`** — a single import with 41 HOC charts (XY, ordinal, network, realtime), optimized for LLM code generation. Geo charts are in `semiotic/geo` to keep d3-geo out of non-geo bundles.
|
|
39
39
|
- **`ai/schema.json`** — machine-readable prop schemas for every component
|
|
40
40
|
- **`npx semiotic-mcp`** — an MCP server for tool-based chart rendering in any MCP client
|
|
41
41
|
- **`npx semiotic-ai --doctor`** — validate component + props JSON from the command line with typo suggestions and anti-pattern detection
|
|
@@ -240,12 +240,12 @@ import { LineChart, BarChart } from "semiotic"
|
|
|
240
240
|
|
|
241
241
|
| Category | Components |
|
|
242
242
|
|---|---|
|
|
243
|
-
| **XY** | `LineChart` `AreaChart` `StackedAreaChart` `Scatterplot` `ConnectedScatterplot` `BubbleChart` `Heatmap` `QuadrantChart` `MultiAxisLineChart` `MinimapChart` |
|
|
244
|
-
| **Categorical** | `BarChart` `StackedBarChart` `GroupedBarChart` `LikertChart` `SwimlaneChart` `FunnelChart` `SwarmPlot` `BoxPlot` `Histogram` `ViolinPlot` `RidgelinePlot` `DotPlot` `PieChart` `DonutChart` |
|
|
245
|
-
| **Network** | `ForceDirectedGraph` `ChordDiagram` `SankeyDiagram` `TreeDiagram` `Treemap` `CirclePack` `OrbitDiagram` |
|
|
243
|
+
| **XY** | `LineChart` `AreaChart` `DifferenceChart` `StackedAreaChart` `Scatterplot` `ConnectedScatterplot` `BubbleChart` `Heatmap` `QuadrantChart` `MultiAxisLineChart` `MinimapChart` `CandlestickChart` `ScatterplotMatrix` |
|
|
244
|
+
| **Categorical** | `BarChart` `StackedBarChart` `GroupedBarChart` `LikertChart` `SwimlaneChart` `FunnelChart` `SwarmPlot` `BoxPlot` `Histogram` `ViolinPlot` `RidgelinePlot` `DotPlot` `PieChart` `DonutChart` `GaugeChart` |
|
|
245
|
+
| **Network** | `ForceDirectedGraph` `ChordDiagram` `SankeyDiagram` `ProcessSankey` `TreeDiagram` `Treemap` `CirclePack` `OrbitDiagram` |
|
|
246
246
|
| **Geo** | `ChoroplethMap` `ProportionalSymbolMap` `FlowMap` `DistanceCartogram` |
|
|
247
247
|
| **Realtime** | `RealtimeLineChart` `RealtimeHistogram` `RealtimeSwarmChart` `RealtimeWaterfallChart` `RealtimeHeatmap` |
|
|
248
|
-
| **Coordination** | `LinkedCharts`
|
|
248
|
+
| **Coordination** | `LinkedCharts` |
|
|
249
249
|
| **Layout** | `ChartGrid` `ContextLayout` `CategoryColorProvider` |
|
|
250
250
|
| **Frames** | `StreamXYFrame` `StreamOrdinalFrame` `StreamNetworkFrame` `StreamGeoFrame` |
|
|
251
251
|
|
|
@@ -280,21 +280,27 @@ for color, size, aggregation, and binning.
|
|
|
280
280
|
|
|
281
281
|
## Bundle Sizes
|
|
282
282
|
|
|
283
|
-
Semiotic ships
|
|
283
|
+
Semiotic ships 12 entry points. **Don't import from `"semiotic"` unless you need everything** — use the sub-path that matches your chart type:
|
|
284
|
+
|
|
285
|
+
<!-- semiotic-bundle-sizes:start -->
|
|
286
|
+
<!-- Auto-generated by `scripts/sync-bundle-sizes.mjs`. Edit dist/*, not this block. -->
|
|
284
287
|
|
|
285
288
|
| Entry Point | gzip | What's inside |
|
|
286
289
|
|---|---|---|
|
|
287
|
-
| `semiotic/xy` | **
|
|
288
|
-
| `semiotic/ordinal` | **
|
|
289
|
-
| `semiotic/network` | **
|
|
290
|
-
| `semiotic/geo` | **
|
|
291
|
-
| `semiotic/realtime` | **
|
|
292
|
-
| `semiotic/server` | **
|
|
293
|
-
| `semiotic/utils` | **
|
|
294
|
-
| `semiotic/
|
|
295
|
-
| `semiotic/
|
|
296
|
-
| `semiotic/
|
|
297
|
-
| `semiotic` | **
|
|
290
|
+
| `semiotic/xy` | **85 KB** | LineChart, AreaChart, Scatterplot, Heatmap, + 8 more XY charts |
|
|
291
|
+
| `semiotic/ordinal` | **68 KB** | BarChart, PieChart, BoxPlot, Histogram, + 11 more categorical charts |
|
|
292
|
+
| `semiotic/network` | **63 KB** | ForceDirectedGraph, SankeyDiagram, ProcessSankey, Treemap, + 4 more |
|
|
293
|
+
| `semiotic/geo` | **51 KB** | ChoroplethMap, FlowMap, DistanceCartogram, ProportionalSymbolMap |
|
|
294
|
+
| `semiotic/realtime` | **90 KB** | RealtimeLineChart, RealtimeHistogram, + 3 streaming charts |
|
|
295
|
+
| `semiotic/server` | **117 KB** | renderChart, renderDashboard, renderToImage, renderToAnimatedGif |
|
|
296
|
+
| `semiotic/utils` | **22 KB** | ThemeProvider, validators, serialization — no chart components |
|
|
297
|
+
| `semiotic/recipes` | **5 KB** | Pure layout functions (waffle, marimekko, flextree, dagre, …) |
|
|
298
|
+
| `semiotic/themes` | **4 KB** | Theme presets only (tufte, carbon, etc.) |
|
|
299
|
+
| `semiotic/data` | **3 KB** | bin, rollup, groupBy, pivot, fromVegaLite |
|
|
300
|
+
| `semiotic/ai` | **188 KB** | All 41 HOCs + validation — optimized for LLM code generation |
|
|
301
|
+
| `semiotic` | **186 KB** | Everything below (full bundle) |
|
|
302
|
+
|
|
303
|
+
<!-- semiotic-bundle-sizes:end -->
|
|
298
304
|
|
|
299
305
|
```jsx
|
|
300
306
|
// Import from the sub-path, not from "semiotic"
|
|
@@ -306,7 +312,7 @@ import { ChoroplethMap } from "semiotic/geo"
|
|
|
306
312
|
|
|
307
313
|
**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.
|
|
308
314
|
|
|
309
|
-
**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
|
|
315
|
+
**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 roughly the sum of every sub-path bundle above — see the `semiotic` row of the table for the current number.
|
|
310
316
|
|
|
311
317
|
## TypeScript
|
|
312
318
|
|
|
@@ -383,7 +389,7 @@ No API keys or authentication required. The server runs locally via stdio. HTTP
|
|
|
383
389
|
| Tool | Description |
|
|
384
390
|
|------|-------------|
|
|
385
391
|
| **`renderChart`** | Render a Semiotic chart to static SVG. Supports the components returned by `getSchema` that are marked `[renderable]`. Pass `{ component: "LineChart", props: { data: [...], xAccessor: "x", yAccessor: "y" } }`. Returns SVG string or validation errors with fix suggestions. |
|
|
386
|
-
| **`getSchema`** | Return the prop schema for a specific component. Pass `{ component: "LineChart" }` to get its props, or omit `component` to list all
|
|
392
|
+
| **`getSchema`** | Return the prop schema for a specific component. Pass `{ component: "LineChart" }` to get its props, or omit `component` to list all 45 chart schemas. Components marked `[renderable]` are available through `renderChart`; realtime charts require a browser/live environment. |
|
|
387
393
|
| **`suggestChart`** | Recommend chart types for a data sample. Pass `{ data: [{...}, ...] }` with 1–5 sample objects. Optionally include `intent` (`"comparison"`, `"trend"`, `"distribution"`, `"relationship"`, `"composition"`, `"geographic"`, `"network"`, `"hierarchy"`). Returns ranked suggestions with example props. |
|
|
388
394
|
| **`diagnoseConfig`** | Check a chart configuration for common problems — empty data, bad dimensions, missing accessors, wrong data shape, and more. Returns a human-readable diagnostic report with actionable fixes. |
|
|
389
395
|
| **`reportIssue`** | Generate a pre-filled GitHub issue URL for bug reports or feature requests. Pass `{ title: "...", body: "...", labels: ["bug"] }`. Returns a URL the user can open to submit. |
|
|
@@ -496,7 +502,7 @@ Semiotic is indexed by AI-coding-agent documentation tools so your assistant (Cl
|
|
|
496
502
|
|
|
497
503
|
Agent-facing API surface:
|
|
498
504
|
|
|
499
|
-
- **`CLAUDE.md`**, **`ai/schema.json`**, **`ai/behaviorContracts.cjs`** — bundled in the npm tarball (see `package.json#files`); agents that install Semiotic locally read these directly. `CLAUDE.md` is the quick-start cheat sheet (HOC props, push API, theming, usage notes); `ai/schema.json` is the JSON Schema for every chart's prop surface (
|
|
505
|
+
- **`CLAUDE.md`**, **`ai/schema.json`**, **`ai/behaviorContracts.cjs`** — bundled in the npm tarball (see `package.json#files`); agents that install Semiotic locally read these directly. `CLAUDE.md` is the quick-start cheat sheet (HOC props, push API, theming, usage notes); `ai/schema.json` is the JSON Schema for every chart's prop surface (45 charts); `ai/behaviorContracts.cjs` carries the agent-visible semantic rules (color precedence, push-mode requirements, ID-accessor contracts).
|
|
500
506
|
- [**`semiotic.nteract.io/llms.txt`**](https://semiotic.nteract.io/llms.txt) + [**`/llms-full.txt`**](https://semiotic.nteract.io/llms-full.txt) — deployed at the docs site per the [llms.txt standard](https://llmstxt.org). Agents fetch the navigation map (`llms.txt`) or the full inlined docs (`llms-full.txt`) over HTTP; they're not part of the npm package itself.
|
|
501
507
|
|
|
502
508
|
## Documentation
|
package/ai/chartSuggestions.cjs
CHANGED
|
@@ -1,11 +1,133 @@
|
|
|
1
1
|
"use strict"
|
|
2
2
|
|
|
3
|
+
const path = require("path")
|
|
4
|
+
|
|
3
5
|
const VALID_INTENTS = [
|
|
4
6
|
"comparison", "trend", "distribution", "relationship", "composition",
|
|
5
7
|
"geographic", "network", "hierarchy",
|
|
6
8
|
]
|
|
7
9
|
const MAX_SAMPLE_SIZE = 5
|
|
8
10
|
|
|
11
|
+
// Capability matrix loaded from `ai/capabilities.json` — generated
|
|
12
|
+
// from `chartSpecs.ts` via `npm run docs:capabilities`. Used to
|
|
13
|
+
// filter suggestions when the caller passes capability constraints.
|
|
14
|
+
// Loaded lazily so a test or build that hasn't generated the file
|
|
15
|
+
// yet still imports cleanly; if the JSON is missing AND a caller
|
|
16
|
+
// passes capability constraints, `suggestCharts` returns an explicit
|
|
17
|
+
// error rather than silently filtering everything out (would happen
|
|
18
|
+
// because `chartSatisfiesCapabilities` fails closed for unknown
|
|
19
|
+
// chart names).
|
|
20
|
+
//
|
|
21
|
+
// `__dirname` points at the cjs source location at runtime —
|
|
22
|
+
// when bundled into `ai/dist/mcp-server.js` `__dirname` is
|
|
23
|
+
// `ai/dist/`, so we also probe the parent directory. Mirrors the
|
|
24
|
+
// `schema.json` loader's two-candidate pattern.
|
|
25
|
+
//
|
|
26
|
+
// `_capabilityMatrixLoaded` differentiates "load attempted, file
|
|
27
|
+
// missing → empty {}" from "load not yet attempted". Without this
|
|
28
|
+
// flag, a build that's missing capabilities.json would set
|
|
29
|
+
// `_capabilityMatrix = {}` and every subsequent call would short-
|
|
30
|
+
// circuit on `if (_capabilityMatrix)` (because `{}` is truthy) and
|
|
31
|
+
// never re-probe — the right behavior for a steady state but
|
|
32
|
+
// incompatible with detecting "loader failed".
|
|
33
|
+
let _capabilityMatrix = null
|
|
34
|
+
let _capabilityMatrixLoaded = false
|
|
35
|
+
function loadCapabilityMatrix() {
|
|
36
|
+
if (_capabilityMatrixLoaded) return _capabilityMatrix
|
|
37
|
+
const candidates = [
|
|
38
|
+
path.join(__dirname, "capabilities.json"),
|
|
39
|
+
path.join(__dirname, "..", "capabilities.json"),
|
|
40
|
+
]
|
|
41
|
+
for (const candidate of candidates) {
|
|
42
|
+
try {
|
|
43
|
+
const json = require(candidate)
|
|
44
|
+
if (json && json.charts) {
|
|
45
|
+
_capabilityMatrix = json.charts
|
|
46
|
+
_capabilityMatrixLoaded = true
|
|
47
|
+
return _capabilityMatrix
|
|
48
|
+
}
|
|
49
|
+
} catch {
|
|
50
|
+
// try next candidate
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
_capabilityMatrixLoaded = true
|
|
54
|
+
return _capabilityMatrix
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Mapping from the capability arg names callers pass (push,
|
|
58
|
+
// linkedHover, ssr, selection, legend) to the chartSpecs field
|
|
59
|
+
// names in capabilities.json (supportsPush, supportsLinkedHover,
|
|
60
|
+
// etc.). Caller-side names are short for ergonomics; matrix-side
|
|
61
|
+
// names mirror the chartSpecs schema so search/grep finds both.
|
|
62
|
+
const CAPABILITY_KEY_MAP = {
|
|
63
|
+
push: "supportsPush",
|
|
64
|
+
linkedHover: "supportsLinkedHover",
|
|
65
|
+
ssr: "supportsSSR",
|
|
66
|
+
selection: "supportsSelection",
|
|
67
|
+
legend: "supportsLegend",
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const VALID_CAPABILITY_KEYS = Object.keys(CAPABILITY_KEY_MAP)
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Test a chart name against a capability constraint object. Returns
|
|
74
|
+
* `true` when the chart satisfies every constraint (or when no
|
|
75
|
+
* constraints are set). Unknown chart names fail closed — better to
|
|
76
|
+
* drop a suggestion than recommend a chart we can't confirm
|
|
77
|
+
* supports the caller's needs.
|
|
78
|
+
*
|
|
79
|
+
* `requirements` shape:
|
|
80
|
+
* { push?: boolean, linkedHover?: boolean, ssr?: boolean,
|
|
81
|
+
* selection?: boolean, legend?: boolean }
|
|
82
|
+
*
|
|
83
|
+
* Each key, when set to `true`, means "the chart MUST support this".
|
|
84
|
+
* `false` means "the chart MUST NOT support this" (rare, but
|
|
85
|
+
* symmetric — useful for "give me a chart that intentionally has no
|
|
86
|
+
* legend"). Unset keys are ignored.
|
|
87
|
+
*/
|
|
88
|
+
function chartSatisfiesCapabilities(chartName, requirements) {
|
|
89
|
+
if (!requirements || Object.keys(requirements).length === 0) return true
|
|
90
|
+
const matrix = loadCapabilityMatrix()
|
|
91
|
+
if (matrix == null) return false // matrix unavailable — fail closed
|
|
92
|
+
const spec = matrix[chartName]
|
|
93
|
+
if (!spec) return false // unknown chart — fail closed
|
|
94
|
+
for (const [shortKey, want] of Object.entries(requirements)) {
|
|
95
|
+
if (want == null) continue
|
|
96
|
+
const matrixKey = CAPABILITY_KEY_MAP[shortKey]
|
|
97
|
+
if (!matrixKey) continue // unknown short key — ignore (validated upstream)
|
|
98
|
+
const has = spec[matrixKey] === true
|
|
99
|
+
if (has !== want) return false
|
|
100
|
+
}
|
|
101
|
+
return true
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Explain why a chart doesn't satisfy a capability constraint set —
|
|
106
|
+
* used in the `filteredOut[].reason` payload so callers see the
|
|
107
|
+
* specific mismatched constraint(s) rather than the chart's
|
|
108
|
+
* data-shape rationale. Returns null when no mismatch (defensive;
|
|
109
|
+
* callers should only invoke this on charts that already failed
|
|
110
|
+
* `chartSatisfiesCapabilities`).
|
|
111
|
+
*/
|
|
112
|
+
function explainCapabilityMismatch(chartName, requirements) {
|
|
113
|
+
if (!requirements || Object.keys(requirements).length === 0) return null
|
|
114
|
+
const matrix = loadCapabilityMatrix()
|
|
115
|
+
if (matrix == null) return "capability matrix unavailable (run `npm run docs:capabilities`)"
|
|
116
|
+
const spec = matrix[chartName]
|
|
117
|
+
if (!spec) return `${chartName} not found in capability matrix`
|
|
118
|
+
const mismatches = []
|
|
119
|
+
for (const [shortKey, want] of Object.entries(requirements)) {
|
|
120
|
+
if (want == null) continue
|
|
121
|
+
const matrixKey = CAPABILITY_KEY_MAP[shortKey]
|
|
122
|
+
if (!matrixKey) continue
|
|
123
|
+
const has = spec[matrixKey] === true
|
|
124
|
+
if (has !== want) {
|
|
125
|
+
mismatches.push(`requires ${shortKey}=${want} but ${matrixKey}=${has}`)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return mismatches.length > 0 ? mismatches.join("; ") : null
|
|
129
|
+
}
|
|
130
|
+
|
|
9
131
|
function summarizeFields(data, keys) {
|
|
10
132
|
const numericFields = []
|
|
11
133
|
const stringFields = []
|
|
@@ -72,6 +194,7 @@ function uniqueNetworkNodes(data, sourceField, targetField) {
|
|
|
72
194
|
function suggestCharts(args = {}) {
|
|
73
195
|
const data = args.data
|
|
74
196
|
const intent = args.intent
|
|
197
|
+
const capabilities = args.capabilities
|
|
75
198
|
|
|
76
199
|
if (intent && !VALID_INTENTS.includes(intent)) {
|
|
77
200
|
return {
|
|
@@ -80,10 +203,35 @@ function suggestCharts(args = {}) {
|
|
|
80
203
|
}
|
|
81
204
|
}
|
|
82
205
|
|
|
206
|
+
if (capabilities) {
|
|
207
|
+
if (typeof capabilities !== "object" || Array.isArray(capabilities)) {
|
|
208
|
+
return {
|
|
209
|
+
ok: false,
|
|
210
|
+
error: "capabilities must be an object like { push: true, linkedHover: true, ssr: true, selection: true, legend: true }.",
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const unknown = Object.keys(capabilities).filter((k) => !VALID_CAPABILITY_KEYS.includes(k))
|
|
214
|
+
if (unknown.length > 0) {
|
|
215
|
+
return {
|
|
216
|
+
ok: false,
|
|
217
|
+
error: `Unknown capability key(s): ${unknown.join(", ")}. Expected: ${VALID_CAPABILITY_KEYS.join(", ")}.`,
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Probe matrix availability up front so callers see a clear error
|
|
221
|
+
// instead of an empty-suggestions silent-failure when
|
|
222
|
+
// capabilities.json is missing from the build.
|
|
223
|
+
if (loadCapabilityMatrix() == null) {
|
|
224
|
+
return {
|
|
225
|
+
ok: false,
|
|
226
|
+
error: "Capability matrix unavailable: ai/capabilities.json is missing. Run `npm run docs:capabilities` to generate it. (Capability filtering requires the matrix; suggestions without a `capabilities` arg still work.)",
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
83
231
|
if (!data || !Array.isArray(data) || data.length === 0) {
|
|
84
232
|
return {
|
|
85
233
|
ok: false,
|
|
86
|
-
error: "Pass { data: [{ ... }, ...] } with 1-5 sample data objects. Optionally include intent: 'comparison' | 'trend' | 'distribution' | 'relationship' | 'composition' | 'geographic' | 'network' | 'hierarchy'.",
|
|
234
|
+
error: "Pass { data: [{ ... }, ...] } with 1-5 sample data objects. Optionally include intent: 'comparison' | 'trend' | 'distribution' | 'relationship' | 'composition' | 'geographic' | 'network' | 'hierarchy', or capabilities: { push, linkedHover, ssr, selection, legend }.",
|
|
87
235
|
}
|
|
88
236
|
}
|
|
89
237
|
|
|
@@ -257,12 +405,37 @@ function suggestCharts(args = {}) {
|
|
|
257
405
|
})
|
|
258
406
|
}
|
|
259
407
|
|
|
408
|
+
// Apply capability filter as the last step so data-shape inference
|
|
409
|
+
// logic stays oblivious to it. Suggestions are kept in their
|
|
410
|
+
// confidence-ranked order; we just drop anything that doesn't
|
|
411
|
+
// satisfy the caller's constraints.
|
|
412
|
+
const filteredSuggestions = capabilities
|
|
413
|
+
? suggestions.filter((s) => chartSatisfiesCapabilities(s.component, capabilities))
|
|
414
|
+
: suggestions
|
|
415
|
+
|
|
260
416
|
return {
|
|
261
417
|
ok: true,
|
|
262
418
|
intent,
|
|
419
|
+
capabilities,
|
|
263
420
|
fieldSummary: `Fields: ${keys.join(", ")} (${numericFields.length} numeric, ${stringFields.length} categorical, ${dateFields.length} date)`,
|
|
264
421
|
fields,
|
|
265
|
-
suggestions,
|
|
422
|
+
suggestions: filteredSuggestions,
|
|
423
|
+
// Surface the pre-filter set when a capability constraint was
|
|
424
|
+
// applied — caller can see which suggestions were dropped and
|
|
425
|
+
// whether to relax the constraint.
|
|
426
|
+
...(capabilities && filteredSuggestions.length < suggestions.length && {
|
|
427
|
+
filteredOut: suggestions
|
|
428
|
+
.filter((s) => !chartSatisfiesCapabilities(s.component, capabilities))
|
|
429
|
+
.map((s) => ({
|
|
430
|
+
component: s.component,
|
|
431
|
+
// The `reason` here is the capability mismatch (which
|
|
432
|
+
// constraint failed), not the original data-shape rationale
|
|
433
|
+
// — callers debugging an empty result need to know which
|
|
434
|
+
// capability to relax, not why the chart was originally
|
|
435
|
+
// suggested.
|
|
436
|
+
reason: explainCapabilityMismatch(s.component, capabilities) || "did not satisfy capability constraints",
|
|
437
|
+
})),
|
|
438
|
+
}),
|
|
266
439
|
}
|
|
267
440
|
}
|
|
268
441
|
|
|
@@ -270,7 +443,10 @@ function formatSuggestionReport(result) {
|
|
|
270
443
|
if (!result.ok) return result.error
|
|
271
444
|
|
|
272
445
|
if (result.suggestions.length === 0) {
|
|
273
|
-
|
|
446
|
+
const tail = result.capabilities && result.filteredOut && result.filteredOut.length > 0
|
|
447
|
+
? `\n\nDropped by capability filter (${formatCapabilityConstraints(result.capabilities)}):\n${result.filteredOut.map((s) => `- ${s.component}: ${s.reason}`).join("\n")}\n\nRelax the capability constraints, or use getSchema to browse alternatives.`
|
|
448
|
+
: `\n\nTry providing intent ('${VALID_INTENTS.join("', '")}') to narrow recommendations, or use getSchema to browse available components.`
|
|
449
|
+
return `Could not confidently recommend a chart type.\n\n${result.fieldSummary}${tail}`
|
|
274
450
|
}
|
|
275
451
|
|
|
276
452
|
const lines = result.suggestions.map((suggestion, i) => {
|
|
@@ -284,8 +460,20 @@ function formatSuggestionReport(result) {
|
|
|
284
460
|
return lines.join("\n\n") + themingTip
|
|
285
461
|
}
|
|
286
462
|
|
|
463
|
+
function formatCapabilityConstraints(capabilities) {
|
|
464
|
+
return Object.entries(capabilities)
|
|
465
|
+
.filter(([, v]) => v != null)
|
|
466
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
467
|
+
.join(", ")
|
|
468
|
+
}
|
|
469
|
+
|
|
287
470
|
module.exports = {
|
|
288
471
|
VALID_INTENTS,
|
|
472
|
+
VALID_CAPABILITY_KEYS,
|
|
289
473
|
formatSuggestionReport,
|
|
290
474
|
suggestCharts,
|
|
475
|
+
// Exported for tests + callers that want to filter their own
|
|
476
|
+
// chart-name set without re-running the suggestion pipeline.
|
|
477
|
+
chartSatisfiesCapabilities,
|
|
478
|
+
explainCapabilityMismatch,
|
|
291
479
|
}
|
package/ai/componentMetadata.cjs
CHANGED
|
@@ -4,7 +4,7 @@ const CATEGORY_ORDER = ["xy", "ordinal", "network", "geo", "realtime"]
|
|
|
4
4
|
|
|
5
5
|
const COMPONENTS_BY_CATEGORY = {
|
|
6
6
|
xy: [
|
|
7
|
-
"LineChart", "AreaChart", "StackedAreaChart", "Scatterplot", "QuadrantChart",
|
|
7
|
+
"LineChart", "AreaChart", "DifferenceChart", "StackedAreaChart", "Scatterplot", "QuadrantChart",
|
|
8
8
|
"MultiAxisLineChart", "CandlestickChart", "BubbleChart", "Heatmap",
|
|
9
9
|
"ConnectedScatterplot", "ScatterplotMatrix", "MinimapChart",
|
|
10
10
|
],
|
|
@@ -14,8 +14,8 @@ const COMPONENTS_BY_CATEGORY = {
|
|
|
14
14
|
"DonutChart", "GaugeChart", "FunnelChart", "SwimlaneChart",
|
|
15
15
|
],
|
|
16
16
|
network: [
|
|
17
|
-
"ForceDirectedGraph", "SankeyDiagram", "
|
|
18
|
-
"Treemap", "CirclePack", "OrbitDiagram",
|
|
17
|
+
"ForceDirectedGraph", "SankeyDiagram", "ProcessSankey", "ChordDiagram",
|
|
18
|
+
"TreeDiagram", "Treemap", "CirclePack", "OrbitDiagram",
|
|
19
19
|
],
|
|
20
20
|
geo: [
|
|
21
21
|
"ChoroplethMap", "ProportionalSymbolMap", "FlowMap", "DistanceCartogram",
|