semiotic 3.8.4 → 3.8.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 (222) hide show
  1. package/CLAUDE.md +9 -5
  2. package/README.md +52 -46
  3. package/ai/behaviorContracts.cjs +2 -2
  4. package/ai/componentMetadata.cjs +2 -2
  5. package/ai/dist/mcp-server.js +6 -2
  6. package/ai/examples.md +211 -2
  7. package/ai/schema.json +1388 -14
  8. package/ai/surface-manifest.json +8 -4
  9. package/ai/system-prompt.md +1 -1
  10. package/dist/chunk-chartContainerDataAudit-FEOD5GTQ.module.min.js +2 -0
  11. package/dist/chunk-chunk-2CHN2TA3.module.min.js +1 -0
  12. package/dist/chunk-chunk-2JGJ5GUG.module.min.js +1 -0
  13. package/dist/chunk-chunk-3NGIKFD6.module.min.js +1 -0
  14. package/dist/chunk-chunk-3SHB24AG.module.min.js +1 -0
  15. package/dist/chunk-chunk-4MMOOYTX.module.min.js +2 -0
  16. package/dist/chunk-chunk-75OTQSXS.module.min.js +1 -0
  17. package/dist/chunk-chunk-A5GRQUOF.module.min.js +1 -0
  18. package/dist/chunk-chunk-AAF5LZAU.module.min.js +2 -0
  19. package/dist/{chunk-chunk-LQBSXO7K.module.min.js → chunk-chunk-ARZGA4LJ.module.min.js} +1 -1
  20. package/dist/chunk-chunk-CAFLML4L.module.min.js +2 -0
  21. package/dist/chunk-chunk-CO5KQMGH.module.min.js +1 -0
  22. package/dist/chunk-chunk-FHBN7OEB.module.min.js +2 -0
  23. package/dist/{chunk-chunk-KQ546MKE.module.min.js → chunk-chunk-GFTVACTO.module.min.js} +1 -1
  24. package/dist/chunk-chunk-IP2UE74X.module.min.js +1 -0
  25. package/dist/chunk-chunk-ITDWLZXV.module.min.js +2 -0
  26. package/dist/chunk-chunk-LPSPATPC.module.min.js +1 -0
  27. package/dist/chunk-chunk-OM6NWS4H.module.min.js +2 -0
  28. package/dist/chunk-chunk-PX5YQGDP.module.min.js +2 -0
  29. package/dist/chunk-chunk-Q2BS46W2.module.min.js +2 -0
  30. package/dist/{chunk-chunk-FBGHXL72.module.min.js → chunk-chunk-RA7JCG7D.module.min.js} +1 -1
  31. package/dist/chunk-chunk-RNUHEXZA.module.min.js +2 -0
  32. package/dist/chunk-chunk-ROZWKEUG.module.min.js +1 -0
  33. package/dist/chunk-chunk-SMVD3EQJ.module.min.js +2 -0
  34. package/dist/chunk-chunk-T2FKJD4O.module.min.js +2 -0
  35. package/dist/chunk-chunk-UDREYOLV.module.min.js +1 -0
  36. package/dist/chunk-chunk-VOWE3SAP.module.min.js +2 -0
  37. package/dist/chunk-chunk-VQPNBUSW.module.min.js +2 -0
  38. package/dist/{chunk-chunk-7RYBCTK5.module.min.js → chunk-chunk-WAWCIHFJ.module.min.js} +1 -1
  39. package/dist/{chunk-chunk-F7WFPD4O.module.min.js → chunk-chunk-WYARAXHR.module.min.js} +1 -1
  40. package/dist/{chunk-chunk-JDXT6ORT.module.min.js → chunk-chunk-XP3OHHFX.module.min.js} +1 -1
  41. package/dist/chunk-chunk-XV3KGYTR.module.min.js +2 -0
  42. package/dist/chunk-chunk-YC2IVEPX.module.min.js +1 -0
  43. package/dist/chunk-chunk-ZXIXAPRE.module.min.js +2 -0
  44. package/dist/components/ChartContainer.d.ts +10 -0
  45. package/dist/components/ai/chartCapabilities.d.ts +3 -1
  46. package/dist/components/ai/chartCapabilityTypes.d.ts +14 -0
  47. package/dist/components/chartContainerDataAudit.d.ts +10 -0
  48. package/dist/components/charts/custom/XYCustomChart.d.ts +3 -0
  49. package/dist/components/charts/geo/DistanceCartogram.d.ts +30 -4
  50. package/dist/components/charts/index.d.ts +4 -0
  51. package/dist/components/charts/network/ForceDirectedGraph.d.ts +36 -1
  52. package/dist/components/charts/ordinal/BarChart.d.ts +7 -14
  53. package/dist/components/charts/ordinal/GaugeChart.d.ts +3 -0
  54. package/dist/components/charts/ordinal/GroupedBarChart.d.ts +3 -0
  55. package/dist/components/charts/ordinal/StackedBarChart.d.ts +3 -0
  56. package/dist/components/charts/ordinal/SwimlaneChart.d.ts +4 -14
  57. package/dist/components/charts/physics/CrucibleChart.capability.d.ts +8 -0
  58. package/dist/components/charts/physics/CrucibleChart.d.ts +63 -0
  59. package/dist/components/charts/physics/crucibleBodyRenderers.d.ts +27 -0
  60. package/dist/components/charts/physics/crucibleChartProps.d.ts +90 -0
  61. package/dist/components/charts/physics/crucibleChrome.d.ts +47 -0
  62. package/dist/components/charts/physics/crucibleController.d.ts +59 -0
  63. package/dist/components/charts/physics/crucibleEffects.d.ts +30 -0
  64. package/dist/components/charts/physics/cruciblePhysics.d.ts +54 -0
  65. package/dist/components/charts/physics/crucibleProgram.d.ts +38 -0
  66. package/dist/components/charts/physics/crucibleTypes.d.ts +415 -0
  67. package/dist/components/charts/shared/chartSpecCore.d.ts +10 -4
  68. package/dist/components/charts/shared/crucibleWireSchema.d.ts +5 -0
  69. package/dist/components/charts/shared/diagnoseTypes.d.ts +10 -0
  70. package/dist/components/charts/shared/gaugeGradient.d.ts +4 -8
  71. package/dist/components/charts/shared/gradient.d.ts +35 -0
  72. package/dist/components/charts/shared/hatchFill.d.ts +3 -10
  73. package/dist/components/charts/shared/hooks.d.ts +2 -50
  74. package/dist/components/charts/shared/knownChartComponents.d.ts +1 -1
  75. package/dist/components/charts/shared/streamPropsHelpers.d.ts +1 -2
  76. package/dist/components/charts/shared/useChartLegend.d.ts +58 -0
  77. package/dist/components/charts/shared/useChartSetup.d.ts +5 -0
  78. package/dist/components/charts/shared/useCustomChartSetup.d.ts +6 -1
  79. package/dist/components/charts/shared/useNetworkChartSetup.d.ts +1 -0
  80. package/dist/components/charts/shared/useStreamingLegend.d.ts +1 -1
  81. package/dist/components/charts/xy/AreaChart.d.ts +25 -42
  82. package/dist/components/charts/xy/BumpChart.capability.d.ts +11 -0
  83. package/dist/components/charts/xy/BumpChart.d.ts +161 -0
  84. package/dist/components/charts/xy/DifferenceChart.d.ts +3 -10
  85. package/dist/components/charts/xy/Heatmap.d.ts +3 -0
  86. package/dist/components/charts/xy/LineChart.d.ts +6 -8
  87. package/dist/components/data/auditData.d.ts +51 -0
  88. package/dist/components/data/dataAuditPresentation.d.ts +18 -0
  89. package/dist/components/data/dataAuditSources.d.ts +9 -0
  90. package/dist/components/data/numericContracts.d.ts +59 -0
  91. package/dist/components/data/numericFieldProfiler.d.ts +23 -0
  92. package/dist/components/geometry/bumpRibbonGeometry.d.ts +36 -0
  93. package/dist/components/legendLayout.d.ts +19 -1
  94. package/dist/components/recipes/netEnsembleLayout.d.ts +167 -0
  95. package/dist/components/recipes/netEnsembleOverlays.d.ts +20 -0
  96. package/dist/components/recipes/wordTrails.d.ts +156 -0
  97. package/dist/components/recipes/wordTrailsProgressiveReveal.d.ts +42 -0
  98. package/dist/components/semiotic-ai-core.d.ts +3 -0
  99. package/dist/components/semiotic-ai-data-audit.d.ts +4 -0
  100. package/dist/components/semiotic-ai.d.ts +9 -6
  101. package/dist/components/semiotic-geo.d.ts +1 -0
  102. package/dist/components/semiotic-network.d.ts +1 -0
  103. package/dist/components/semiotic-ordinal.d.ts +2 -0
  104. package/dist/components/semiotic-physics.d.ts +2 -0
  105. package/dist/components/semiotic-realtime-core.d.ts +1 -0
  106. package/dist/components/semiotic-recipes-core.d.ts +4 -0
  107. package/dist/components/semiotic-server-edge.d.ts +1 -1
  108. package/dist/components/semiotic-server.d.ts +1 -1
  109. package/dist/components/semiotic-utils-core.d.ts +3 -0
  110. package/dist/components/semiotic-xy.d.ts +4 -0
  111. package/dist/components/semiotic.d.ts +5 -5
  112. package/dist/components/server/renderEvidence.d.ts +27 -0
  113. package/dist/components/server/serverChartConfigs.d.ts +2 -0
  114. package/dist/components/server/serverChartConfigsPhysics.d.ts +9 -0
  115. package/dist/components/server/serverChartConfigsXY.d.ts +1 -0
  116. package/dist/components/server/staticAnnotations.d.ts +12 -0
  117. package/dist/components/server/staticLegend.d.ts +4 -2
  118. package/dist/components/server/staticSVGChrome.d.ts +4 -4
  119. package/dist/components/stream/GeoPipelineStore.d.ts +1 -0
  120. package/dist/components/stream/GeoSVGOverlay.d.ts +11 -10
  121. package/dist/components/stream/NetworkSVGOverlay.d.ts +2 -6
  122. package/dist/components/stream/OrdinalSVGOverlay.d.ts +2 -6
  123. package/dist/components/stream/SVGOverlay.d.ts +2 -6
  124. package/dist/components/stream/StalenessBadge.d.ts +2 -6
  125. package/dist/components/stream/geoCartogram.d.ts +7 -0
  126. package/dist/components/stream/geoTypes.d.ts +20 -8
  127. package/dist/components/stream/legendRenderer.d.ts +2 -6
  128. package/dist/components/stream/networkFrameHandleTypes.d.ts +4 -0
  129. package/dist/components/stream/ordinalTypes.d.ts +7 -31
  130. package/dist/components/stream/physics/PhysicsKernel.d.ts +1 -0
  131. package/dist/components/stream/physics/PhysicsSVGOverlay.d.ts +2 -6
  132. package/dist/components/stream/physics/PhysicsSettledSVG.d.ts +8 -0
  133. package/dist/components/stream/physics/StreamPhysicsTypes.d.ts +2 -6
  134. package/dist/components/stream/pipelineConfig.d.ts +7 -15
  135. package/dist/components/stream/renderers/canvasRenderHelpers.d.ts +9 -69
  136. package/dist/components/stream/renderers/cornerRadii.d.ts +2 -9
  137. package/dist/components/stream/renderers/thresholdLineSegments.d.ts +12 -0
  138. package/dist/components/stream/sceneToSVGShared.d.ts +3 -5
  139. package/dist/components/stream/types.d.ts +32 -56
  140. package/dist/components/stream/xySceneBuilders/areaGradient.d.ts +4 -20
  141. package/dist/components/stream/xySceneBuilders/types.d.ts +6 -6
  142. package/dist/components/types/legendTypes.d.ts +15 -6
  143. package/dist/components/useChartContainerDataAudit.d.ts +7 -0
  144. package/dist/geo.min.js +1 -1
  145. package/dist/geo.module.min.js +1 -1
  146. package/dist/network.min.js +1 -1
  147. package/dist/network.module.min.js +1 -1
  148. package/dist/ordinal.min.js +1 -1
  149. package/dist/ordinal.module.min.js +1 -1
  150. package/dist/physics.min.js +1 -1
  151. package/dist/physics.module.min.js +1 -1
  152. package/dist/physicsWorker.js +1 -1
  153. package/dist/realtime.min.js +1 -1
  154. package/dist/realtime.module.min.js +1 -1
  155. package/dist/semiotic-ai-core.d.ts +3 -0
  156. package/dist/semiotic-ai-core.min.js +1 -1
  157. package/dist/semiotic-ai-core.module.min.js +1 -1
  158. package/dist/semiotic-ai.d.ts +9 -6
  159. package/dist/semiotic-ai.min.js +1 -1
  160. package/dist/semiotic-ai.module.min.js +1 -1
  161. package/dist/semiotic-experimental.min.js +1 -1
  162. package/dist/semiotic-experimental.module.min.js +1 -1
  163. package/dist/semiotic-geo.d.ts +1 -0
  164. package/dist/semiotic-network.d.ts +1 -0
  165. package/dist/semiotic-ordinal.d.ts +2 -0
  166. package/dist/semiotic-physics.d.ts +2 -0
  167. package/dist/semiotic-realtime-core.d.ts +1 -0
  168. package/dist/semiotic-realtime-core.min.js +1 -1
  169. package/dist/semiotic-realtime-core.module.min.js +1 -1
  170. package/dist/semiotic-recipes-core.d.ts +4 -0
  171. package/dist/semiotic-recipes-core.min.js +1 -1
  172. package/dist/semiotic-recipes-core.module.min.js +1 -1
  173. package/dist/semiotic-recipes.min.js +1 -1
  174. package/dist/semiotic-recipes.module.min.js +1 -1
  175. package/dist/semiotic-server-edge.d.ts +1 -1
  176. package/dist/semiotic-server-edge.min.js +1 -1
  177. package/dist/semiotic-server-edge.module.min.js +1 -1
  178. package/dist/semiotic-server-node.min.js +1 -1
  179. package/dist/semiotic-server-node.module.min.js +1 -1
  180. package/dist/semiotic-server.d.ts +1 -1
  181. package/dist/semiotic-utils-core.d.ts +3 -0
  182. package/dist/semiotic-utils-core.min.js +1 -1
  183. package/dist/semiotic-utils-core.module.min.js +1 -1
  184. package/dist/semiotic-utils.min.js +1 -1
  185. package/dist/semiotic-utils.module.min.js +1 -1
  186. package/dist/semiotic-xy.d.ts +4 -0
  187. package/dist/semiotic.d.ts +5 -5
  188. package/dist/semiotic.min.js +1 -1
  189. package/dist/semiotic.module.min.js +1 -1
  190. package/dist/server.min.js +1 -1
  191. package/dist/server.module.min.js +1 -1
  192. package/dist/xy.min.js +1 -1
  193. package/dist/xy.module.min.js +1 -1
  194. package/package.json +2 -2
  195. package/dist/chunk-chunk-3ILKBVVQ.module.min.js +0 -1
  196. package/dist/chunk-chunk-6TPGFL7M.module.min.js +0 -2
  197. package/dist/chunk-chunk-7KZJUTKP.module.min.js +0 -2
  198. package/dist/chunk-chunk-ANYDWOZF.module.min.js +0 -1
  199. package/dist/chunk-chunk-AQPQN2HH.module.min.js +0 -2
  200. package/dist/chunk-chunk-ASKSE5F5.module.min.js +0 -1
  201. package/dist/chunk-chunk-AVBI6GDT.module.min.js +0 -1
  202. package/dist/chunk-chunk-B3DRZPBK.module.min.js +0 -2
  203. package/dist/chunk-chunk-BFHUYEOS.module.min.js +0 -2
  204. package/dist/chunk-chunk-C57LR3YG.module.min.js +0 -1
  205. package/dist/chunk-chunk-F5V4UIE6.module.min.js +0 -2
  206. package/dist/chunk-chunk-FP44FHFO.module.min.js +0 -2
  207. package/dist/chunk-chunk-HKXUAEPJ.module.min.js +0 -2
  208. package/dist/chunk-chunk-ICEXPNDC.module.min.js +0 -2
  209. package/dist/chunk-chunk-JMVEIZXP.module.min.js +0 -1
  210. package/dist/chunk-chunk-KHCOJMGK.module.min.js +0 -1
  211. package/dist/chunk-chunk-LRIFN2HK.module.min.js +0 -1
  212. package/dist/chunk-chunk-MFHF4J2Z.module.min.js +0 -1
  213. package/dist/chunk-chunk-N6VKKF3A.module.min.js +0 -2
  214. package/dist/chunk-chunk-NEVE562N.module.min.js +0 -1
  215. package/dist/chunk-chunk-NKZ75IM5.module.min.js +0 -2
  216. package/dist/chunk-chunk-PLIDPQNG.module.min.js +0 -2
  217. package/dist/chunk-chunk-QGZGZI4X.module.min.js +0 -1
  218. package/dist/chunk-chunk-RP7WTID5.module.min.js +0 -1
  219. package/dist/chunk-chunk-S7KLNUO6.module.min.js +0 -1
  220. package/dist/chunk-chunk-SR4ZXOWR.module.min.js +0 -2
  221. package/dist/chunk-statisticalOverlays-QGDLRUHX.module.min.js +0 -1
  222. package/dist/chunk-statisticalOverlays-T77OKLDY.module.min.js +0 -1
package/CLAUDE.md CHANGED
@@ -4,7 +4,7 @@
4
4
  - Install: `npm install semiotic`
5
5
  <!-- semiotic-bundle-sizes:start -->
6
6
  <!-- Auto-generated by scripts/sync-bundle-sizes.mjs — do not edit by hand. -->
7
- - **Use sub-path imports** — `semiotic/xy` (138KB gz), `semiotic/ordinal` (117KB gz), `semiotic/network` (123KB gz), `semiotic/geo` (97KB gz), `semiotic/realtime` (150KB gz), `semiotic/realtime/core` (145KB gz), `semiotic/realtime/react` (1KB gz), `semiotic/server` (190KB gz), `semiotic/server/node` (190KB gz), `semiotic/server/edge` (208KB gz), `semiotic/utils` (76KB gz), `semiotic/utils/core` (74KB gz), `semiotic/utils/react` (6KB gz), `semiotic/recipes` (77KB gz), `semiotic/recipes/core` (77KB gz), `semiotic/recipes/react` (2KB gz), `semiotic/themes` (7KB gz), `semiotic/themes/core` (7KB gz), `semiotic/themes/react` (6KB gz), `semiotic/data` (4KB gz), `semiotic/value` (8KB gz), `semiotic/physics` (133KB gz), `semiotic/physics/matter` (1KB gz), `semiotic/physics/rapier` (1KB gz), `semiotic/ai` (432KB gz), `semiotic/ai/core` (70KB gz), `semiotic/controls` (12KB gz), `semiotic/rough` (4KB gz). Full `semiotic` is 349KB gz.
7
+ - **Use sub-path imports** — `semiotic/xy` (143KB gz), `semiotic/ordinal` (119KB gz), `semiotic/network` (125KB gz), `semiotic/geo` (98KB gz), `semiotic/realtime` (152KB gz), `semiotic/realtime/core` (146KB gz), `semiotic/realtime/react` (1KB gz), `semiotic/server` (207KB gz), `semiotic/server/node` (207KB gz), `semiotic/server/edge` (226KB gz), `semiotic/utils` (83KB gz), `semiotic/utils/core` (81KB gz), `semiotic/utils/react` (6KB gz), `semiotic/recipes` (84KB gz), `semiotic/recipes/core` (83KB gz), `semiotic/recipes/react` (2KB gz), `semiotic/themes` (7KB gz), `semiotic/themes/core` (7KB gz), `semiotic/themes/react` (6KB gz), `semiotic/data` (4KB gz), `semiotic/value` (8KB gz), `semiotic/physics` (154KB gz), `semiotic/physics/matter` (1KB gz), `semiotic/physics/rapier` (1KB gz), `semiotic/ai` (468KB gz), `semiotic/ai/core` (78KB gz), `semiotic/controls` (12KB gz), `semiotic/rough` (4KB gz). Full `semiotic` is 382KB gz.
8
8
  <!-- semiotic-bundle-sizes:end -->
9
9
  - CLI: `npx semiotic-ai [--schema|--compact|--examples|--doctor|--audit-a11y]` · MCP: `npx semiotic-mcp`
10
10
 
@@ -21,8 +21,9 @@ HOC Charts (simple, default) → Stream Frames (full control). Use HOCs unless y
21
21
  ## XY Charts (`semiotic/xy`)
22
22
 
23
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`, `band` ({y0Accessor, y1Accessor, style?, perSeries?, interactive?} or array for fan charts; participates in yExtent; non-interactive by default), `directLabel`, `gapStrategy`, `xScaleType`/`yScaleType` ("linear"|"log"|"time"), `styleRules` (per-series), `tooltip="multi"` for hover-anywhere
24
- **AreaChart** — LineChart props + `areaBy`, `y0Accessor`, `gradientFill`, `areaOpacity` (0.7), `showLine` (true), `band`, `tooltip="multi"`
24
+ **AreaChart** — LineChart props + `areaBy`, `y0Accessor`, `gradientFill`, `semanticGradient` (`{stops: [{offset: 0–1, color?, opacity?}]}`; offset follows the resolved y-domain), `semanticLine` (true with semanticGradient; false keeps the normal stroke/lineGradient), `areaOpacity` (0.7), `showLine` (true), `band`, `tooltip="multi"`
25
25
  **DifferenceChart** — Two-series A/B. Fills between with `seriesAColor` where A>B, `seriesBColor` where B>A; crossovers interpolated. `data`, `xAccessor`, `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`. Push via `ref.push({x,a,b})`. Accessor outputs coerce through `toNumber`.
26
+ **BumpChart** — Ranking-over-x. Each x-column ranks series by `yAccessor`; rank is vertical position. `data`, `xAccessor` ("x"), `yAccessor` ("y"), `lineBy` ("series"), `rankDirection` ("descending"|"ascending"), `ribbon` (false — encode magnitude as true perpendicular-offset ribbon width instead of fixed-width lines; line↔ribbon share centerline geometry so `animate` tweens width only), `curve` ("smooth"|"linear"), `ribbonSizeRange` ([4,28]), `samplesPerSegment` (12), `lineWidth` (3), `highlightTop` (color only the N best series by mean rank; the rest share `neutralColor`), `neutralColor`, `styleRules` (per-series; resolve against each series' first observation), `showPoints`, `showLabels` (true|"start"|"end"|"both"), `hoverHighlight`, `annotations` (x may use original x values incl. `Date`). Wraps XYCustomChart.
26
27
  **StackedAreaChart** — flat array + `areaBy` (required), `colorBy`, `normalize`, `baseline` ("zero"|"wiggle" streamgraph|"silhouette" centered), `stackOrder` ("key"|"insideOut"|"asc"|"desc"). Streamgraph: `baseline="wiggle"` + `stackOrder="insideOut"`. `baseline` ⊥ `normalize`. No `lineBy`. `tooltip="multi"` interpolates between samples.
27
28
  **Scatterplot** — `xAccessor`, `yAccessor`, `colorBy`, `sizeBy`, `sizeRange`, `symbolBy` (categorical field → glyph **shape**: each mark becomes a d3-shape glyph; size still tracks `sizeBy`/`pointRadius`), `symbolMap` ({category → shape}; unmapped auto-assign — pass it for legend-matchable shapes), `pointRadius` (5), `pointOpacity` (0.8), `marginalGraphics`, `styleRules` (per-point; `axis:"x"`/`"y"` thresholds), `regression` (boolean | "linear"|"polynomial"|"loess" | RegressionConfig — sugar for trend overlay)
28
29
  **BubbleChart** — Scatterplot + `sizeBy` (required), `sizeRange` ([5,40]), `regression`
@@ -36,7 +37,7 @@ HOC Charts (simple, default) → Stream Frames (full control). Use HOCs unless y
36
37
 
37
38
  ## Ordinal Charts (`semiotic/ordinal`)
38
39
 
39
- **BarChart** — `categoryAccessor`, `valueAccessor`, `orientation`, `colorBy`, `sort`, `barPadding` (40), `roundedTop`, `gradientFill` (true | {topOpacity, bottomOpacity} | {colorStops}; tip→base), `styleRules`, `regression`
40
+ **BarChart** — `categoryAccessor`, `valueAccessor`, `orientation`, `colorBy`, `sort`, `barPadding` (40), `roundedTop`, `gradientFill` (`{stops: [{offset: 0–1, color?, opacity?}]}`; tip→base), `styleRules`, `regression`
40
41
  **StackedBarChart** — + `stackBy` (required), `normalize`, `sort` (false default — insertion order), `styleRules`
41
42
  **GroupedBarChart** — + `groupBy` (required), `barPadding` (60), `sort` (false default), `styleRules`
42
43
  **SwarmPlot** — `colorBy`, `sizeBy`, `symbolBy` (categorical field → glyph shape, like Scatterplot), `symbolMap`, `pointRadius`, `pointOpacity`
@@ -58,7 +59,7 @@ All ordinal: `colorBy`, `colorScheme`, `categoryFormat` (string|ReactNode), `sho
58
59
 
59
60
  ## Network Charts (`semiotic/network`)
60
61
 
61
- **ForceDirectedGraph** — `nodes`, `edges`, `nodeIDAccessor`, `sourceAccessor`, `targetAccessor`, `colorBy`, `nodeSize`, `nodeSizeRange`, `edgeWidth`, `iterations` (300), `forceStrength` (0.1 — link-attraction multiplier), `layoutExecution` ("auto" default | "worker" | "sync" — auto runs big layouts in a Web Worker by estimated cost, sync fallback everywhere), `layoutLoadingContent` (ReactNode while worker layout pends; `false` suppresses), `onLayoutStateChange` (`"pending"|"ready"|"error"`), `showLabels`, `nodeLabel`, `styleRules` (style groups of nodes — rules see the raw node; `ctx.category` = colorBy group)
62
+ **ForceDirectedGraph** — `nodes`, `edges`, `nodeIDAccessor`, `sourceAccessor`, `targetAccessor`, `colorBy`, `nodeSize`, `nodeSizeRange`, `nodeStroke`/`nodeStrokeWidth` (node-only outline), `edgeWidth`, `edgeColor`/`edgeOpacity` (edge-only stroke), `iterations` (300), `forceStrength` (0.1 — link-attraction multiplier), `layoutExecution` ("auto" default | "worker" | "sync" — auto runs big layouts in a Web Worker by estimated cost, sync fallback everywhere), `layoutLoadingContent` (ReactNode while worker layout pends; `false` suppresses), `onLayoutStateChange` (`"pending"|"ready"|"error"`), `showLabels`, `nodeLabel`, `styleRules` (style groups of nodes — rules see the raw node; `ctx.category` = colorBy group). **Node vs edge stroking**: the generic `stroke`/`strokeWidth`/`opacity` style *all* marks uniformly; to stroke nodes and edges separately use node-only `nodeStroke`/`nodeStrokeWidth` (e.g. `nodeStroke="none"` drops the node ring) and edge-only `edgeColor`/`edgeWidth`/`edgeOpacity`. Precedence per property: specific > generic > built-in default.
62
63
  **SankeyDiagram** — `edges`, `nodes`, `valueAccessor`, `nodeIdAccessor`, `colorBy`, `edgeColorBy`, `orientation`, `nodeAlign`, `nodeWidth`, `nodePaddingRatio`, `showLabels`
63
64
  **ProcessSankey** — temporal sankey with real time x-axis. `nodes`, `edges` (each with `startTime`/`endTime`), `domain` (req `[t0, t1]`), `axisTicks?`, `xExtentAccessor` (optional `[start, end]` lifetime per node), `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` (true), `showQualityReadout`, `showParticles` + `particleStyle`, `timeFormat`/`valueFormat`, push API via ref. Static-graph cycles OK as long as edges move forward in time. Use ProcessSankey for time-stamped events; SankeyDiagram for static snapshots.
64
65
  **ChordDiagram** — `edges`, `nodes`, `valueAccessor`, `edgeColorBy`, `padAngle`, `showLabels`
@@ -92,6 +93,7 @@ Process/arrival/distribution charts backed by `StreamPhysicsFrame`. The settled
92
93
  **ProcessFlowChart** — multi-body workflow lane. `data`, `stages` (required: `[{id, label?, force?, damping?, capacity?, pressure?, portal?, absorb?, share?}]`), `stageAccessor` ("stage"), `idAccessor`, `groupBy` (optional feature key; completion when all members hit an absorb stage), `groupLabelAccessor`, `workAccessor`, `radiusAccessor`, `ballRadius` (6), `colorBy`, `groupCompletion` ("allAbsorbed"|"none"), `groupAnchorAlong` (0.55), `showProjection` (true), `showChrome` (true — processChrome kit), `liveCapacity` (true — FIFO queues at `unitsPerSecond`), `onCapacityChange` (queue depth / processed), `bodyLimit` (soft stream budget + oldest eviction), `bodyMark` ("circle"|"halo"|"faceted"|"pill"|"diamond"|"square" or per-row `datum.__physicsMark`), `selection`, `settle`, `seed`, `size`, `paused`, `frameProps`. Settled projection is stage occupancy + capacity badges; use for review queues / triage / merge pipelines. Prefer **GauntletChart** for one compound plan with timed gate effects.
93
94
  **Physics controllers** (`createCapacityQueueController`, `createPortalController`, `composePhysicsControllers`) — process plugins via `controllers`. Capacity `getSnapshot()` → queueDepth/processedCount; emit `physics-capacity-processed`. **processChrome** (`semiotic/physics` / `semiotic/recipes`) — stage bays, capacity badges, feature sockets (theme: `--semiotic-process-*`). `PhysicsCustomChart`: `layout()` may return `regionEffects`, `controllers`, `bodyForces`; `layoutConfig` hot path without re-enqueue. Guide: `/features/physics-process-guide`. Contracts: `PhysicsContracts.test.tsx`.
94
95
  **GauntletChart** — compound project core + tethered positive/negative property bodies + timed gate events. `positiveProperties`/`negativeProperties` (req), `gates`, `events`, `showChrome` (true), `showProjection` (true — viability/outcome strip), `showTethers` (true), `onStateChange`, `frameProps`. Bodies clamp inside walls (`clampGauntletPoint`). Not for multi-item factory floors (use ProcessFlowChart).
96
+ **CrucibleChart** — bounded peer components undergo authored `phases` and `events`, form declared `products`, and settle into reason-labelled `outlets` with source lineage. The ledger/projection is authoritative; motion never infers classification, timing, membership, loss, or routing. `buildCrucibleProductEvents({productId, form, contributions?, complete})` is pure authoring sugar for `combine → contribute* → complete-product`; every source/relation id, event position, reason, and outlet remains caller-supplied. The ref handle's `replay()` atomically restarts the deterministic tape even mid-run (`reset()` restores-and-pauses; `rerunMS` repeats after settlement). `playbackRate` changes presentation only. No push/live-event API.
95
97
  **Pop (body-removal burst)** — every physics HOC ref is a `PhysicsFrameHandle` (extends the shared push handle) exposing **`popBodies(ids, options?)`** (`StreamPhysicsPopOptions` = `{ color?, durationMs?, radius? }`): removes the bodies and plays a burst — expanding ring + inner glow + radial sparks fading over `durationMs` (`drawPopAnimations`) — returning the removed ids. It reads as a *departure*, the physics/exit-emphasis counterpart to realtime **`pulse`**'s data-*arrival* glow (the same transient-emphasis metaphor on opposite ends of a datum's life). GauntletChart also fires it internally on gate-driven property removal; `/examples/nimby` (civic-value balloons) and `/examples/merge-pressure` (merge-risk traits) drive it that way.
96
98
 
97
99
  ## Value Charts (`semiotic/value`)
@@ -169,7 +171,9 @@ Layout signature differs by family:
169
171
  - **Decoration**: `linearAxis` (tick axis + gridlines from *any* scale — the bespoke-scale escape hatch `showAxes` can't cover), `legendSwatches` (portable SVG legend for `overlays` — fill/line/shape/hatch swatches; sibling to `legendGroupsFrom` which feeds `frameProps.legend`), `hatchFill` (`{def, fill}` SVG `<pattern>` for percentile/uncertainty bands — the SVG analogue of `createHatchPattern`).
170
172
  - **`unwrapDatum`** (`semiotic/recipes` + `semiotic/utils`): collapse the wrapped-vs-raw datum split — always the raw user object (handles both `.data` wrappers and `.datum` nesting). **The** unwrap path for `onObservation` handlers AND `frameProps.tooltipContent` renderers: call it once on the incoming value; never pre-unwrap the argument (`unwrapDatum(x?.data ?? x)` double-unwraps).
171
173
 
172
- `semiotic/recipes` ships pure layout functions (`waffleLayout`, `calendarLayout`, `marimekkoLayout`, `bulletLayout`, `parallelCoordinatesLayout`, `intervalLanesLayout`, `flextreeLayout`, `dagreLayout`, `lineageDagLayout`, `axisFixedForceLayout`, `packedClusterMatrix`, `isometricLandmarkLayout`, `forceLayout`, `arcLayout`, `adjacencyMatrix`, `circularLayout`). **Network-analysis kit** (`semiotic/recipes`, pure graph algorithms): `buildAdjacency`, `bfsDistances`, `shortestPath`, `egoNetwork`, `degree`/`betweenness`(Brandes)/`closeness`/`clustering` (+ `normalizeScores`), and `proximityProblem` — the "spatial problem" layout diagnostic that flags nodes drawn closer than their graph distance warrants. Pair with network charts to size by centrality, highlight an ego network on hover, trace a shortest path, or diagnose a misleading layout. `forceLayout(nodes, edges, {seed})` is a **seeded, deterministic** positioner returning normalized `{id:{x,y}}` for `NetworkCustomChart` (same seed ⇒ same layout; re-seed for a "re-run the layout" interaction); `forceLayoutAsync(nodes, edges, {execution?, workerThreshold?, signal?})` is its Promise sibling that runs large layouts in a short-lived module Web Worker (identical deterministic output; graceful sync fallback), and `useForceLayout(nodes, edges, options)` → `{positions, status, error}` is the React wrapper — SSR and first hydration stay synchronous for markup parity, client graph changes go async while previous positions stay visible, and settled positions are memoized by node/edge array identity + options so remounting the same module-constant graph is "ready" immediately (no loading flash). `arcLayout`/`adjacencyMatrix`/`circularLayout` (+ `orderByGroupDegree`, `arcPath`) are the classic physics-free network forms. `allocateCells` is the largest-remainder grid allocator behind `waffleLayout` (turn `{key, weight}[]` + a cell count into integer cells with no rounding drift; `minPerCategory` keeps small categories visible) — reusable for any feature-mix / proportional waffle. **`unitize(value, {unit, maxUnits?, minFraction?})` / `unitizeRange(value, rangeValue, opts)`** is the counting sibling: the pictogram/tally allocator (value → repeated unit signs with a fractional final sign — ISOTYPE: symbols repeat, they never grow). Returns `{units: [{index, fraction, start, end, value}], total, shown, overflow}` — `maxUnits` caps with an `overflow` flag, `minFraction` drops trailing slivers while `total` vs `shown` keeps the ledger honest; `unitizeRange` extends the tally to a projected/scenario endpoint (`rangeUnits` drawn hatched), sharing a mid-sign boundary exactly via `startFraction`. Feeds `glyph`-node `fraction`s directly (unit charts, sign stacks, arrow bundles; `allocateCells` divides fixed cells, `unitize` counts). **Tokenized reasoning helpers**: `generateTokens(input, tokenEncoding)` wraps `unitize` plus `actual`, `fixed-denominator`, `quantile`, `posterior-sample`/`sample`, and seeded `random-sample` strategies with explicit `tokenType` (`dot`/`icon`/`glyph`), `tokenSemantics` (`observed-unit`, `unitized-measure`, `risk-case`, `possible-outcome`, etc.), and `countStrategy`; `{ value, rangeValue }` yields `rangeTokens` for scenario/projection tallies. `layoutTokenGrid` places the resulting tokens for icon/glyph arrays; `normalizeTokenEncoding` keeps legacy `token`/`unit` aliases working while canonical configs use `icon`/`unitValue`; `diagnoseTokenEncoding`, `suggestTokenEncoding`, and `tokenTaskIntentToCapabilityIntents` expose IDID-style warnings, task-aware defaults, and a bridge to `suggestCharts` intents. Built-in token glyph names include `person`, `server`, `chip`, `bolt`, and `bus`. `intervalLanesLayout` (ordinal) packs concurrent `{start,end,lane}` records into stacked Gantt sub-tracks per lane with period bands + lane labels + a time axis — packing runs in rendered-pixel space and honors `minBarWidth` (2) so zero/short-duration events stay visible without overlapping same-track neighbors; `axisFixedForceLayout` (network) pins one axis from a field and settles the other (rect-aware collision). BYO heavy deps (`d3-flextree`, `dagre`) in user code. `packedClusterMatrix` (network) bins records into a column×row matrix of **densely-packed beeswarm clusters** (deterministic self-contained packing, geometry cached) and emits **multi-channel glyphs** — hue (`colorAccessor`/`colorMap`), size (`sizeAccessor`, area), shade (`shadeAccessor`, CIELAB lightness), plus EITHER shape-encoding (`symbolAccessor`/`symbolMap` — the base mark becomes that shape) OR the **composite-glyph** model (`iconAccessor`/`iconMap` — base is a filled circle, only mapped values get a stroked inner icon). `rowMode:"banded"` (default) gives aligned global orbit-bands (row labels align, one enclosure spans the columns per band, columns vary in height) vs `"stacked"` (per-column cell heights ∝ count). `callouts:[{field,value,label}]` draws leader lines to named marks. `cellSizing:"proportional"` makes area ∝ count. **Recipe decoration kit** (exported from `semiotic/recipes`, for any custom-layout's `overlays`): `roundedEnclosure`/`boundsOf` (group/band borders), `bandLabel` (overflow-aware axis/band labels), `markCallout` (leader-line callout to a mark), `readField` (`node.data`-wrapper reader), `groupBy`, `dimFor` (the highlight/dim opacity rule — `{predicate?, highlight?, baseOpacity?, dimOpacity?, brighten?}`; `matchesHighlight` is its `{field,value}[]` matcher), `signatureKey`/`LayoutCache` (content-signature geometry cache so re-styling never re-runs an expensive layout — key by *content*, never by `ctx.nodes` identity), `legendGroupsFrom` (`{colorMap|keys, symbolMap?, sizeStops?}` → `LegendGroup[]` for `frameProps.legend`), `shade`/`makeShade`, `symbolPathString`/`symbolRadius`/`symbolExtent`/`SYMBOL_SEQUENCE`, and the small numeric/color one-liners every layout re-declares `clamp`, `mean`, `withAlpha`, `nonNegativeFinite` (hex→`rgba()` so a hover-dim can ride a recipe's `resolveColor` callback). (`bandLabel`/`dimFor` are adopted by marimekko/bullet/parallelCoordinates/packedClusterMatrix.) `lineageDagLayout` renders a pre-positioned **layered lineage/DAG** (reads logical layer/row coords, no re-layout) with composite node glyphs (one hit-rect per node + icon/label/store-chip decoration in `overlays`), level-of-detail collapse (full→compact→icon→dot), distinct dashed back-edges, and host-driven reach-dimming (`layoutConfig.reachableIds`) + selection (`layoutConfig.selectedId` / shared `ctx.selection`).
174
+ **Word Trails** (`wordTrailsLayout`) is the quantitatively anchored word-cloud recipe: column = category, segment = ordered vertical position, weight = font size, with overlap-free stable placement. `wordColor`/`wordOpacity` receive `WordTrailsWordInfo`: canonical `word`/`column`/`weight`/`segment` plus the exact source `datum`, `dataIndex`, `columnIndex`, and `resolvedColumnColor`, so derived encodings do not need brittle compound-key lookup maps. Spread `wordTrailsProgressiveReveal({currentSegment, segmentDomain, oldestOpacity?, currentOpacity?, futureOpacity?, combineWeightOpacity?})` into `layoutConfig` for future hiding + linearly faded history without reflow; zero-opacity rows reserve geometry but emit no glyph or hit target. Analysis-derived color/distinctiveness remains a source-data field, not something the layout infers.
175
+
176
+ `semiotic/recipes` ships pure layout functions (`waffleLayout`, `calendarLayout`, `marimekkoLayout`, `bulletLayout`, `parallelCoordinatesLayout`, `intervalLanesLayout`, `flextreeLayout`, `dagreLayout`, `lineageDagLayout`, `netEnsembleLayout`, `axisFixedForceLayout`, `packedClusterMatrix`, `isometricLandmarkLayout`, `forceLayout`, `arcLayout`, `adjacencyMatrix`, `circularLayout`). **Network-analysis kit** (`semiotic/recipes`, pure graph algorithms): `buildAdjacency`, `bfsDistances`, `shortestPath`, `egoNetwork`, `degree`/`betweenness`(Brandes)/`closeness`/`clustering` (+ `normalizeScores`), `analyzeNetEnsemble` (the headless net census — see `netEnsembleLayout` below), and `proximityProblem` — the "spatial problem" layout diagnostic that flags nodes drawn closer than their graph distance warrants. Pair with network charts to size by centrality, highlight an ego network on hover, trace a shortest path, or diagnose a misleading layout. `forceLayout(nodes, edges, {seed})` is a **seeded, deterministic** positioner returning normalized `{id:{x,y}}` for `NetworkCustomChart` (same seed ⇒ same layout; re-seed for a "re-run the layout" interaction); `forceLayoutAsync(nodes, edges, {execution?, workerThreshold?, signal?})` is its Promise sibling that runs large layouts in a short-lived module Web Worker (identical deterministic output; graceful sync fallback), and `useForceLayout(nodes, edges, options)` → `{positions, status, error}` is the React wrapper — SSR and first hydration stay synchronous for markup parity, client graph changes go async while previous positions stay visible, and settled positions are memoized by node/edge array identity + options so remounting the same module-constant graph is "ready" immediately (no loading flash). `arcLayout`/`adjacencyMatrix`/`circularLayout` (+ `orderByGroupDegree`, `arcPath`) are the classic physics-free network forms. `allocateCells` is the largest-remainder grid allocator behind `waffleLayout` (turn `{key, weight}[]` + a cell count into integer cells with no rounding drift; `minPerCategory` keeps small categories visible) — reusable for any feature-mix / proportional waffle. **`unitize(value, {unit, maxUnits?, minFraction?})` / `unitizeRange(value, rangeValue, opts)`** is the counting sibling: the pictogram/tally allocator (value → repeated unit signs with a fractional final sign — ISOTYPE: symbols repeat, they never grow). Returns `{units: [{index, fraction, start, end, value}], total, shown, overflow}` — `maxUnits` caps with an `overflow` flag, `minFraction` drops trailing slivers while `total` vs `shown` keeps the ledger honest; `unitizeRange` extends the tally to a projected/scenario endpoint (`rangeUnits` drawn hatched), sharing a mid-sign boundary exactly via `startFraction`. Feeds `glyph`-node `fraction`s directly (unit charts, sign stacks, arrow bundles; `allocateCells` divides fixed cells, `unitize` counts). **Tokenized reasoning helpers**: `generateTokens(input, tokenEncoding)` wraps `unitize` plus `actual`, `fixed-denominator`, `quantile`, `posterior-sample`/`sample`, and seeded `random-sample` strategies with explicit `tokenType` (`dot`/`icon`/`glyph`), `tokenSemantics` (`observed-unit`, `unitized-measure`, `risk-case`, `possible-outcome`, etc.), and `countStrategy`; `{ value, rangeValue }` yields `rangeTokens` for scenario/projection tallies. `layoutTokenGrid` places the resulting tokens for icon/glyph arrays; `normalizeTokenEncoding` keeps legacy `token`/`unit` aliases working while canonical configs use `icon`/`unitValue`; `diagnoseTokenEncoding`, `suggestTokenEncoding`, and `tokenTaskIntentToCapabilityIntents` expose IDID-style warnings, task-aware defaults, and a bridge to `suggestCharts` intents. Built-in token glyph names include `person`, `server`, `chip`, `bolt`, and `bus`. `intervalLanesLayout` (ordinal) packs concurrent `{start,end,lane}` records into stacked Gantt sub-tracks per lane with period bands + lane labels + a time axis — packing runs in rendered-pixel space and honors `minBarWidth` (2) so zero/short-duration events stay visible without overlapping same-track neighbors; `axisFixedForceLayout` (network) pins one axis from a field and settles the other (rect-aware collision). BYO heavy deps (`d3-flextree`, `dagre`) in user code. `packedClusterMatrix` (network) bins records into a column×row matrix of **densely-packed beeswarm clusters** (deterministic self-contained packing, geometry cached) and emits **multi-channel glyphs** — hue (`colorAccessor`/`colorMap`), size (`sizeAccessor`, area), shade (`shadeAccessor`, CIELAB lightness), plus EITHER shape-encoding (`symbolAccessor`/`symbolMap` — the base mark becomes that shape) OR the **composite-glyph** model (`iconAccessor`/`iconMap` — base is a filled circle, only mapped values get a stroked inner icon). `rowMode:"banded"` (default) gives aligned global orbit-bands (row labels align, one enclosure spans the columns per band, columns vary in height) vs `"stacked"` (per-column cell heights ∝ count). `callouts:[{field,value,label}]` draws leader lines to named marks. `cellSizing:"proportional"` makes area ∝ count. **Recipe decoration kit** (exported from `semiotic/recipes`, for any custom-layout's `overlays`): `roundedEnclosure`/`boundsOf` (group/band borders), `bandLabel` (overflow-aware axis/band labels), `markCallout` (leader-line callout to a mark), `readField` (`node.data`-wrapper reader), `groupBy`, `dimFor` (the highlight/dim opacity rule — `{predicate?, highlight?, baseOpacity?, dimOpacity?, brighten?}`; `matchesHighlight` is its `{field,value}[]` matcher), `signatureKey`/`LayoutCache` (content-signature geometry cache so re-styling never re-runs an expensive layout — key by *content*, never by `ctx.nodes` identity), `legendGroupsFrom` (`{colorMap|keys, symbolMap?, sizeStops?}` → `LegendGroup[]` for `frameProps.legend`), `shade`/`makeShade`, `symbolPathString`/`symbolRadius`/`symbolExtent`/`SYMBOL_SEQUENCE`, and the small numeric/color one-liners every layout re-declares — `clamp`, `mean`, `withAlpha`, `nonNegativeFinite` (hex→`rgba()` so a hover-dim can ride a recipe's `resolveColor` callback). (`bandLabel`/`dimFor` are adopted by marimekko/bullet/parallelCoordinates/packedClusterMatrix.) `lineageDagLayout` renders a pre-positioned **layered lineage/DAG** (reads logical layer/row coords, no re-layout) with composite node glyphs (one hit-rect per node + icon/label/store-chip decoration in `overlays`), level-of-detail collapse (full→compact→icon→dot), distinct dashed back-edges, and host-driven reach-dimming (`layoutConfig.reachableIds`) + selection (`layoutConfig.selectedId` / shared `ctx.selection`). `netEnsembleLayout` (network) is the complement — for **ensembles of disconnected/trivially-connected DAGs** (a "bag of little graphs" force layout scatters and dagre/flextree can't place). It splits into weakly-connected components, tests each for **directedness** (borrowing the mathematical *net*/directed-set idea: for a weakly-connected DAG, a single sink ⟹ everything converges to one limit; ≥2 sinks ⟹ it branches), fingerprints components with Weisfeiler–Leman refinement so **order-isomorphic motifs** group together, lays each out converging toward its sink(s) at the bottom, and arranges the ensemble as small multiples in motif bands (collapsing to one census glyph per component when cells get tiny). Config: `colorMode` (`"directedness"|"motif"|"category"`), `groupByMotif`, `sort`, `fingerprintRounds`, `minCellForFull`. `analyzeNetEnsemble(nodes, edges)` is the pure headless census (per-component `directed`/sink/source counts + motif classes) with no rendering. Demo + lay explanation at `/recipes/net-ensemble`.
173
177
 
174
178
  `tokenLayer({input, encoding, options})` is the high-level tokenized-rendering helper for custom layouts: it runs `generateTokens`, applies row/column/grid/waffle/dotplot/bar-segment/quantile-strip placement (or `positionToken` for scale/map-driven placement), and returns ordinary dot/symbol/glyph scene nodes with `pointId`/transition identity. Use it for ISOTYPE, icon arrays, risk grids, quantile dotplots, strips, and hybrid token overlays; pass `includeRange` to render `rangeTokens`; drop down to `generateTokens` when a bespoke layout only needs records.
175
179
 
package/README.md CHANGED
@@ -16,17 +16,21 @@ AI coding assistants generate correct chart code on the first try.
16
16
  <img src="./docs/public/assets/img/semiotic-release-dashboard.svg" alt="Semiotic release dashboard showing chart count, bundle sizes, capability coverage, chart families, and documentation growth" width="100%">
17
17
  <!-- semiotic-readme-dashboard:end -->
18
18
 
19
- ## What's New in 3.8.4
20
-
21
- 3.8.4 closes the remaining server/browser render gaps and hardens the physics and network runtimes:
22
-
23
- - Static SVG and `renderChart` now match the browser for line+area fills, semantic gradients, line
24
- bands, pie/donut start angles, swimlane extents, treemap `colorBy`, range candlesticks,
25
- streamgraph baselines, and the LineChart series features—each guarded by parity tests.
26
- - Physics simulations settle and rerun reliably via quiescence-based `atRest` detection, and network
27
- frames rebuild scenes without the duplicate builds that could flash during hydration.
28
- - New `layoutSequence` and `gridObservatory` recipes, a report-style Semiotic Standard example, and
29
- lazy-loaded brush overlays that trim the entry graph.
19
+ ## What's New in 3.8.6
20
+
21
+ 3.8.6 adds a ranking chart and semantic-line area bands, then refines legend layout across the
22
+ client and server renderers:
23
+
24
+ - New `BumpChart` (`semiotic/xy`): ranks every series within each x-column and connects each
25
+ series' rank across columns. Set `ribbon` to encode the original magnitude as true
26
+ perpendicular-offset ribbon width instead of a fixed-width line both modes share one
27
+ centerline, so the toggle animates as a pure width change.
28
+ - `AreaChart` gains `semanticLine` (default on) for hard, value-banded color segments along the
29
+ area's top stroke, and `gradientFill`/`lineGradient`/`semanticGradient` now share one
30
+ `{ stops }` config (with `GradientConfig` and friends exported).
31
+ - Legend layout gains `legendLayout.sideGutter` (space between a side legend and axis chrome) and
32
+ a `legendDistance` control, with content-measured side-legend widths and tightened defaults,
33
+ aligned across the client and SSR renderers.
30
34
 
31
35
  ```jsx
32
36
  import { LineChart } from "semiotic/xy"
@@ -56,6 +60,7 @@ correct visualizations without trial and error:
56
60
  - **`npx semiotic-mcp`** — an MCP server for tool-based chart rendering in any MCP client
57
61
  - **`npx semiotic-ai --doctor`** — validate component + props JSON from the command line with typo suggestions and anti-pattern detection
58
62
  - **`diagnoseConfig(component, props)`** — programmatic anti-pattern detector with actionable fixes, spanning validation, encoding, accessibility, and misleading-design (deception) checks
63
+ - **`auditData(component, props, data?)`** — chart-aware numeric preflight for inputs that pass schema validation but break the math: non-finite values, zero-span domains, invalid log inputs, negative size geometry, unsafe normalized totals, and scale-dominating outliers. Returns bounded row evidence and flows into `diagnoseConfig`, Chart Clinic, CLI doctor, and opt-in `ChartContainer` notifications
59
64
  - **`CLAUDE.md`** — instruction files auto-synced for Claude, Cursor, Copilot, Windsurf, and Cline
60
65
  - **`llms.txt`** — machine-readable documentation following the emerging standard
61
66
 
@@ -72,8 +77,9 @@ diagrams, tree layouts, treemaps, circle packing, and orbit diagrams — all
72
77
  as React components with the same prop API as LineChart.
73
78
 
74
79
  **Streaming data.** Realtime charts render on canvas at 60fps with a
75
- ref-based push API. Built-in decay, pulse, and staleness encoding for
76
- monitoring dashboards.
80
+ ref-based push API. Rapid network edge pushes coalesce into one layout per
81
+ animation frame, while read/mutation methods preserve synchronous read-after-write
82
+ semantics. Built-in decay, pulse, and staleness encoding for monitoring dashboards.
77
83
 
78
84
  **Coordinated views.** `LinkedCharts` provides hover cross-highlighting,
79
85
  brush cross-filtering, coordinate-based linked crosshairs, and selection
@@ -373,35 +379,35 @@ The numbers below are **first-party artifact cost**: the gzip size of Semiotic's
373
379
 
374
380
  | Entry Point | gzip | What's inside |
375
381
  |---|---|---|
376
- | `semiotic/xy` | **138 KB** | LineChart, AreaChart, Scatterplot, Heatmap, + 8 more XY charts |
377
- | `semiotic/ordinal` | **117 KB** | BarChart, PieChart, BoxPlot, Histogram, + 11 more categorical charts |
378
- | `semiotic/network` | **123 KB** | ForceDirectedGraph, SankeyDiagram, ProcessSankey, Treemap, + 4 more |
379
- | `semiotic/geo` | **97 KB** | ChoroplethMap, FlowMap, DistanceCartogram, ProportionalSymbolMap |
380
- | `semiotic/realtime` | **150 KB** | RealtimeLineChart, RealtimeHistogram, + 4 streaming charts |
381
- | `semiotic/realtime/core` | **145 KB** | Streaming chart types, HOCs, and buffer helpers |
382
+ | `semiotic/xy` | **143 KB** | LineChart, AreaChart, Scatterplot, Heatmap, + 8 more XY charts |
383
+ | `semiotic/ordinal` | **119 KB** | BarChart, PieChart, BoxPlot, Histogram, + 11 more categorical charts |
384
+ | `semiotic/network` | **125 KB** | ForceDirectedGraph, SankeyDiagram, ProcessSankey, Treemap, + 4 more |
385
+ | `semiotic/geo` | **98 KB** | ChoroplethMap, FlowMap, DistanceCartogram, ProportionalSymbolMap |
386
+ | `semiotic/realtime` | **152 KB** | RealtimeLineChart, RealtimeHistogram, + 4 streaming charts |
387
+ | `semiotic/realtime/core` | **146 KB** | Streaming chart types, HOCs, and buffer helpers |
382
388
  | `semiotic/realtime/react` | **1 KB** | Stream status and synced push hooks |
383
- | `semiotic/server` | **190 KB** | renderChart, renderDashboard, renderToImage, renderToAnimatedGif |
384
- | `semiotic/server/node` | **190 KB** | renderChart, renderDashboard, renderToImage, renderToAnimatedGif |
385
- | `semiotic/server/edge` | **208 KB** | renderChart, renderChartWithEvidence, renderToStaticSVG, renderDashboard |
386
- | `semiotic/utils` | **76 KB** | ThemeProvider, validators, serialization — no chart components |
387
- | `semiotic/utils/core` | **74 KB** | Theme helpers and serialization utilities |
389
+ | `semiotic/server` | **207 KB** | renderChart, renderDashboard, renderToImage, renderToAnimatedGif |
390
+ | `semiotic/server/node` | **207 KB** | renderChart, renderDashboard, renderToImage, renderToAnimatedGif |
391
+ | `semiotic/server/edge` | **226 KB** | renderChart, renderChartWithEvidence, renderToStaticSVG, renderDashboard |
392
+ | `semiotic/utils` | **83 KB** | ThemeProvider, numeric/accessibility audits, serialization — no chart components |
393
+ | `semiotic/utils/core` | **81 KB** | Pure theme helpers, numeric/accessibility audits, and serialization |
388
394
  | `semiotic/utils/react` | **6 KB** | ThemeProvider, useTheme, useReducedMotion, useHighContrast, useStreamStatus |
389
- | `semiotic/recipes` | **77 KB** | Pure layout functions (waffle, marimekko, flextree, dagre, …) |
390
- | `semiotic/recipes/core` | **77 KB** | Pure layout functions (waffle, marimekko, flextree, dagre, …) |
395
+ | `semiotic/recipes` | **84 KB** | Pure layout functions (waffle, marimekko, flextree, dagre, …) |
396
+ | `semiotic/recipes/core` | **83 KB** | Pure layout functions (waffle, marimekko, flextree, dagre, …) |
391
397
  | `semiotic/recipes/react` | **2 KB** | Glyph and React layout-selection helpers |
392
398
  | `semiotic/themes` | **7 KB** | Theme presets only (tufte, carbon, etc.) |
393
399
  | `semiotic/themes/core` | **7 KB** | Theme presets and token helpers |
394
400
  | `semiotic/themes/react` | **6 KB** | ThemeProvider/useTheme and hooks |
395
401
  | `semiotic/data` | **4 KB** | bin, rollup, groupBy, pivot, fromVegaLite |
396
402
  | `semiotic/value` | **8 KB** | BigNumber — focal-value KPI / scorecard (SingleValueFrame POC) |
397
- | `semiotic/physics` | **133 KB** | GaltonBoardChart, EventDropChart, PhysicsPileChart, CollisionSwarmChart, PhysicalFlowChart, PhysicsCustomChart |
403
+ | `semiotic/physics` | **154 KB** | GaltonBoardChart, EventDropChart, PhysicsPileChart, CollisionSwarmChart, PhysicalFlowChart, PhysicsCustomChart |
398
404
  | `semiotic/physics/matter` | **1 KB** | Matter.js migration helpers + optional peer guard (no chart components) |
399
405
  | `semiotic/physics/rapier` | **1 KB** | Rapier peer guard + adapter decision metadata (no chart components) |
400
- | `semiotic/ai` | **432 KB** | All schema-backed charts + validation — optimized for LLM code generation |
401
- | `semiotic/ai/core` | **70 KB** | suggestCharts, validateProps, describeChart, repairChartConfig, tool adapters — no chart components |
406
+ | `semiotic/ai` | **468 KB** | All schema-backed charts + validation — optimized for LLM code generation |
407
+ | `semiotic/ai/core` | **78 KB** | suggestCharts, auditData, describeChart, repairChartConfig, tool adapters — no chart components |
402
408
  | `semiotic/controls` | **12 KB** | DirectManipulationControl, CircularBrush, MobileStandardControls, auditVisualizationControls — no frame renderer |
403
409
  | `semiotic/rough` | **4 KB** | Optional deterministic Rough.js paint backend — exact Semiotic geometry remains authoritative |
404
- | `semiotic` | **349 KB** | Everything below (full bundle) |
410
+ | `semiotic` | **382 KB** | Everything below (full bundle) |
405
411
 
406
412
  <!-- semiotic-bundle-sizes:end -->
407
413
 
@@ -422,28 +428,28 @@ Method: fresh `npm pack --ignore-scripts` tarball → temporary consumer → min
422
428
 
423
429
  | Public named import | Runtime | gzip cold-consumer bundle |
424
430
  |---|---:|---:|
425
- | `import { LineChart } from "semiotic"` | browser | **147.9 KiB** |
426
- | `import { LineChart } from "semiotic/xy"` | browser | **148.0 KiB** |
427
- | `import { BarChart } from "semiotic/ordinal"` | browser | **122.1 KiB** |
428
- | `import { SankeyDiagram } from "semiotic/network"` | browser | **127.1 KiB** |
429
- | `import { RealtimeLineChart } from "semiotic/realtime"` | browser | **121.3 KiB** |
430
- | `import { RingBuffer } from "semiotic/realtime/core"` | browser | **169.6 KiB** |
431
+ | `import { LineChart } from "semiotic"` | browser | **152.8 KiB** |
432
+ | `import { LineChart } from "semiotic/xy"` | browser | **152.8 KiB** |
433
+ | `import { BarChart } from "semiotic/ordinal"` | browser | **122.9 KiB** |
434
+ | `import { SankeyDiagram } from "semiotic/network"` | browser | **127.8 KiB** |
435
+ | `import { RealtimeLineChart } from "semiotic/realtime"` | browser | **122.5 KiB** |
436
+ | `import { RingBuffer } from "semiotic/realtime/core"` | browser | **170.9 KiB** |
431
437
  | `import { useStreamStatus } from "semiotic/realtime/react"` | browser | **0.6 KiB** |
432
- | `import { GaltonBoardChart } from "semiotic/physics"` | browser | **118.7 KiB** |
438
+ | `import { GaltonBoardChart } from "semiotic/physics"` | browser | **138.0 KiB** |
433
439
  | `import { MATTER_PHYSICS_CAPABILITIES } from "semiotic/physics/matter"` | browser | **0.2 KiB** |
434
440
  | `import { RAPIER_PHYSICS_CAPABILITIES } from "semiotic/physics/rapier"` | browser | **0.2 KiB** |
435
- | `import { renderChart } from "semiotic/server"` | node | **249.5 KiB** |
436
- | `import { generateFrameSVGs } from "semiotic/server/edge"` | node | **165.3 KiB** |
437
- | `import { renderToImage } from "semiotic/server/node"` | node | **249.9 KiB** |
438
- | `import { suggestCharts } from "semiotic/ai"` | browser | **207.3 KiB** |
439
- | `import { suggestCharts } from "semiotic/ai/core"` | browser | **32.2 KiB** |
441
+ | `import { renderChart } from "semiotic/server"` | node | **266.6 KiB** |
442
+ | `import { generateFrameSVGs } from "semiotic/server/edge"` | node | **165.0 KiB** |
443
+ | `import { renderToImage } from "semiotic/server/node"` | node | **267.0 KiB** |
444
+ | `import { suggestCharts } from "semiotic/ai"` | browser | **219.7 KiB** |
445
+ | `import { suggestCharts } from "semiotic/ai/core"` | browser | **34.7 KiB** |
440
446
  | `import { bin } from "semiotic/data"` | browser | **0.4 KiB** |
441
- | `import { ChoroplethMap } from "semiotic/geo"` | browser | **106.6 KiB** |
447
+ | `import { ChoroplethMap } from "semiotic/geo"` | browser | **108.2 KiB** |
442
448
  | `import { createRoughRenderMode } from "semiotic/rough"` | browser | **3.4 KiB** |
443
449
  | `import { resolveThemePreset } from "semiotic/themes"` | browser | **3.4 KiB** |
444
450
  | `import { resolveThemePreset } from "semiotic/themes/core"` | browser | **3.4 KiB** |
445
451
  | `import { ThemeProvider } from "semiotic/themes/react"` | browser | **4.4 KiB** |
446
- | `import { validateProps } from "semiotic/utils"` | browser | **6.9 KiB** |
452
+ | `import { validateProps } from "semiotic/utils"` | browser | **7.4 KiB** |
447
453
  | `import { smartTickFormat } from "semiotic/utils/core"` | browser | **0.2 KiB** |
448
454
  | `import { useReducedMotion } from "semiotic/utils/react"` | browser | **0.3 KiB** |
449
455
  | `import { waffleLayout } from "semiotic/recipes"` | browser | **2.5 KiB** |
@@ -464,7 +470,7 @@ import { ChoroplethMap } from "semiotic/geo"
464
470
 
465
471
  **Tree-shaking & multi-subpath imports**: Family entries (`semiotic/xy`, `semiotic/network`, `semiotic/ai`, …) are built as one ESM graph with **shared chunks**. Stream frames, renderers, and other common code ship once and are imported by every entry that needs them — so combining `semiotic/ai` + `semiotic/xy` + `semiotic/network` does **not** mean paying for three full copies of the runtime. The package is marked `"sideEffects": false`, so modern bundlers keep only the named exports you retain (e.g. `LineChart` + `suggestCharts`). Prefer family subpaths for clarity; import AI helpers from `semiotic/ai` or the lighter `semiotic/ai/core` when you do not need the chart catalog.
466
472
 
467
- **When to use `"semiotic"`**: Fine when you want one import for mixed families. With shared chunks it is no longer “sum every subpath” the cold-consumer table above is the better guide for a single named import, and multi-family apps should not see the old double-pay tax.
473
+ **When to use `"semiotic"`**: Fine when you want one import for mixed families. Shared chunks prevent duplicated runtime code across family subpaths; the cold-consumer table above is the better guide for a single named import.
468
474
 
469
475
  ## TypeScript
470
476
 
@@ -551,7 +557,7 @@ host-header allowlisting. For ChatGPT Apps domain verification, set
551
557
  | **`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 plus a "Render evidence" JSON block (mark counts by scene type, resolved axis domains, empty flag, annotation count, accessible name) so agents can verify the chart drew data marks, or validation errors with fix suggestions. |
552
558
  | **`renderInteractiveChart`** | Render a static-data chart as a ChatGPT Apps widget. Uses the same Semiotic server render path as `renderChart`, then hydrates an iframe UI with fit, zoom, data, hover, and render-evidence controls. |
553
559
  | **`getSchema`** | Return the prop schema for a specific component. Pass `{ component: "LineChart" }` to get its props, or omit `component` to list the complete schema-backed catalog. Components marked `[renderable]` are available through `renderChart`; realtime charts require a browser/live environment. |
554
- | **`suggestChart`** | Legacy sample-row recommender. Pass `{ data: [{...}, ...] }` with 1–5 sample objects plus optional broad intent/capability filters. |
560
+ | **`suggestChart`** | Sample-row recommender. Pass `{ data: [{...}, ...] }` with 1–5 sample objects plus optional broad intent/capability filters. |
555
561
  | **`suggestCharts`** | Capability-based recommender for bounded row data. Returns ranked chart suggestions with scores, reasons, caveats, import paths, and ready-to-use props. |
556
562
  | **`suggestStreamCharts`** | Recommend realtime charts from a stream schema, throughput, and retention hints. |
557
563
  | **`suggestDashboard`** | Build a multi-panel dashboard suggestion that covers distinct analytical intents. |
@@ -27,8 +27,8 @@ const DOC_MARKER_END = "<!-- semiotic-behavior-contracts:end -->"
27
27
  // diagnoseConfig as "OK" in static mode. Keying off the presence of a `data`
28
28
  // property catches them (and still includes the charts that DO list `data`).
29
29
  //
30
- // `STATIC_DATA_COMPONENTS` stays exported as a Set for test/legacy callers
31
- // that probe the surface, and is rebuilt from disk at module load time.
30
+ // `STATIC_DATA_COMPONENTS` is exported as a Set and rebuilt from disk at
31
+ // module load time.
32
32
  function loadStaticDataComponentsFromSchema() {
33
33
  // Source layout has this file at `<repo>/ai/behaviorContracts.cjs` and the
34
34
  // schema at `<repo>/ai/schema.json` — `__dirname` works directly.
@@ -4,7 +4,7 @@ const CATEGORY_ORDER = ["xy", "ordinal", "network", "geo", "realtime", "physics"
4
4
 
5
5
  const COMPONENTS_BY_CATEGORY = {
6
6
  xy: [
7
- "LineChart", "AreaChart", "DifferenceChart", "StackedAreaChart", "Scatterplot", "QuadrantChart",
7
+ "LineChart", "BumpChart", "AreaChart", "DifferenceChart", "StackedAreaChart", "Scatterplot", "QuadrantChart",
8
8
  "MultiAxisLineChart", "CandlestickChart", "BubbleChart", "Heatmap",
9
9
  "ConnectedScatterplot", "ScatterplotMatrix", "MinimapChart",
10
10
  ],
@@ -26,7 +26,7 @@ const COMPONENTS_BY_CATEGORY = {
26
26
  ],
27
27
  physics: [
28
28
  "GaltonBoardChart", "EventDropChart", "PhysicsPileChart", "CollisionSwarmChart",
29
- "PhysicalFlowChart", "ProcessFlowChart", "GauntletChart",
29
+ "PhysicalFlowChart", "ProcessFlowChart", "GauntletChart", "CrucibleChart",
30
30
  ],
31
31
  value: [
32
32
  "BigNumber",
@@ -6898,6 +6898,7 @@ var require_componentMetadata = __commonJS({
6898
6898
  var COMPONENTS_BY_CATEGORY = {
6899
6899
  xy: [
6900
6900
  "LineChart",
6901
+ "BumpChart",
6901
6902
  "AreaChart",
6902
6903
  "DifferenceChart",
6903
6904
  "StackedAreaChart",
@@ -6959,7 +6960,8 @@ var require_componentMetadata = __commonJS({
6959
6960
  "CollisionSwarmChart",
6960
6961
  "PhysicalFlowChart",
6961
6962
  "ProcessFlowChart",
6962
- "GauntletChart"
6963
+ "GauntletChart",
6964
+ "CrucibleChart"
6963
6965
  ],
6964
6966
  value: [
6965
6967
  "BigNumber"
@@ -33015,6 +33017,7 @@ var import_geo = require("semiotic/geo");
33015
33017
  var COMPONENT_REGISTRY = {
33016
33018
  LineChart: { component: import_ai.LineChart, category: "xy" },
33017
33019
  AreaChart: { component: import_ai.AreaChart, category: "xy" },
33020
+ BumpChart: { component: import_ai.BumpChart, category: "xy" },
33018
33021
  DifferenceChart: { component: import_ai.DifferenceChart, category: "xy" },
33019
33022
  StackedAreaChart: { component: import_ai.StackedAreaChart, category: "xy" },
33020
33023
  Scatterplot: { component: import_ai.Scatterplot, category: "xy" },
@@ -33060,7 +33063,8 @@ var COMPONENT_REGISTRY = {
33060
33063
  CollisionSwarmChart: { component: import_ai.CollisionSwarmChart, category: "physics" },
33061
33064
  PhysicalFlowChart: { component: import_ai.PhysicalFlowChart, category: "physics" },
33062
33065
  ProcessFlowChart: { component: import_ai.ProcessFlowChart, category: "physics" },
33063
- GauntletChart: { component: import_ai.GauntletChart, category: "physics" }
33066
+ GauntletChart: { component: import_ai.GauntletChart, category: "physics" },
33067
+ CrucibleChart: { component: import_ai.CrucibleChart, category: "physics" }
33064
33068
  };
33065
33069
 
33066
33070
  // ai/renderHOCToSVG.tsx
package/ai/examples.md CHANGED
@@ -147,7 +147,12 @@ const data = [
147
147
  data={data}
148
148
  xAccessor="month"
149
149
  yAccessor="revenue"
150
- gradientFill
150
+ gradientFill={{
151
+ stops: [
152
+ { offset: 0, opacity: 0.8 },
153
+ { offset: 1, opacity: 0.05 }
154
+ ]
155
+ }}
151
156
  xLabel="Month"
152
157
  yLabel="Revenue ($)"
153
158
  />
@@ -180,7 +185,12 @@ const data = [
180
185
  y0Accessor="p5"
181
186
  showLine={false}
182
187
  areaOpacity={0.3}
183
- gradientFill
188
+ gradientFill={{
189
+ stops: [
190
+ { offset: 0, opacity: 0.8 },
191
+ { offset: 1, opacity: 0.05 }
192
+ ]
193
+ }}
184
194
  width={600}
185
195
  height={400}
186
196
  />
@@ -235,6 +245,38 @@ Fills the region between two series with a color that switches based on which se
235
245
 
236
246
  Key props: `seriesALabel` / `seriesBLabel` (legend + tooltip), `seriesAColor` / `seriesBColor` (defaults to `var(--semiotic-danger)` / `var(--semiotic-info)`), `showLines` (default `true` — draws both series on top of the fill), `areaOpacity` (0.6), `gradientFill` (same shape as AreaChart), `windowSize` (max raw rows in push buffer; FIFO eviction). Push API: `ref.current.push({ x, a, b })` — accessor outputs coerce through a `toNumber` helper so `Date` (time series) and numeric strings (CSV/JSON) work transparently.
237
247
 
248
+ ### BumpChart
249
+
250
+ ```jsx
251
+ import { BumpChart } from "semiotic/ai"
252
+
253
+ const data = [
254
+ { quarter: "Q1", team: "North", sales: 420 },
255
+ { quarter: "Q1", team: "South", sales: 380 },
256
+ { quarter: "Q1", team: "East", sales: 510 },
257
+ { quarter: "Q1", team: "West", sales: 290 },
258
+ { quarter: "Q2", team: "North", sales: 460 },
259
+ { quarter: "Q2", team: "South", sales: 500 },
260
+ { quarter: "Q2", team: "East", sales: 480 },
261
+ { quarter: "Q2", team: "West", sales: 350 },
262
+ { quarter: "Q3", team: "North", sales: 610 },
263
+ { quarter: "Q3", team: "South", sales: 470 },
264
+ { quarter: "Q3", team: "East", sales: 520 },
265
+ { quarter: "Q3", team: "West", sales: 540 }
266
+ ]
267
+
268
+ <BumpChart
269
+ data={data}
270
+ xAccessor="quarter"
271
+ yAccessor="sales"
272
+ lineBy="team"
273
+ highlightTop={2}
274
+ xLabel="Quarter"
275
+ />
276
+ ```
277
+
278
+ Ranks every series within each x-column and connects each series' rank across columns — the classic "who's leading over time" chart. `yAccessor` is the magnitude used to rank (highest value = rank 1 by default; use `rankDirection="ascending"` when lower is better). `highlightTop` colors only the N best series by mean rank and greys the rest via `neutralColor`. Set `ribbon={true}` to encode the raw magnitude as true perpendicular-offset ribbon width instead of fixed-width lines — line and ribbon share the same centerline geometry, so `animate` tweens only the width. Endpoint labels show by default (`showLabels`).
279
+
238
280
  ### StackedAreaChart
239
281
 
240
282
  ```jsx
@@ -1184,6 +1226,86 @@ Key props: `y-threshold` works on vertical ordinal charts. `category-highlight`
1184
1226
 
1185
1227
  ---
1186
1228
 
1229
+ ## Recipe Layouts — Explicit Semantics Without Bespoke Geometry
1230
+
1231
+ ### Word Trails (source-aware color + progressive reveal)
1232
+
1233
+ ```jsx
1234
+ import { useState } from "react"
1235
+ import { OrdinalCustomChart } from "semiotic/ordinal"
1236
+ import { wordTrailsLayout, wordTrailsProgressiveReveal } from "semiotic/recipes"
1237
+
1238
+ // One source row per topic / word / recorded model iteration.
1239
+ const topicWordRuns = [
1240
+ { topic: "Topic A", word: "archive", iteration: 0, probability: 0.08, distinctiveness: 0.28 },
1241
+ { topic: "Topic A", word: "archive", iteration: 1, probability: 0.14, distinctiveness: 0.56 },
1242
+ { topic: "Topic A", word: "archive", iteration: 2, probability: 0.19, distinctiveness: 0.84 },
1243
+ { topic: "Topic A", word: "document", iteration: 2, probability: 0.13, distinctiveness: 0.62 },
1244
+ { topic: "Topic B", word: "labor", iteration: 0, probability: 0.09, distinctiveness: 0.35 },
1245
+ { topic: "Topic B", word: "labor", iteration: 1, probability: 0.16, distinctiveness: 0.72 },
1246
+ { topic: "Topic B", word: "factory", iteration: 2, probability: 0.18, distinctiveness: 0.91 },
1247
+ ]
1248
+
1249
+ const topicColors = { "Topic A": "#2563eb", "Topic B": "#db2777" }
1250
+
1251
+ function distinctivenessColor({ datum, resolvedColumnColor }) {
1252
+ // The callback receives the exact source row, plus canonical layout fields.
1253
+ const strength =
1254
+ datum.distinctiveness >= 0.75 ? 100 :
1255
+ datum.distinctiveness >= 0.5 ? 50 :
1256
+ datum.distinctiveness >= 0.25 ? 25 : 0
1257
+ return `color-mix(in srgb, ${resolvedColumnColor} ${strength}%, var(--surface-0, white))`
1258
+ }
1259
+
1260
+ export default function ProgressiveTopicTrails() {
1261
+ const [iteration, setIteration] = useState(2)
1262
+
1263
+ return (
1264
+ <>
1265
+ <label>
1266
+ Model iteration {iteration}
1267
+ <input
1268
+ type="range"
1269
+ min={0}
1270
+ max={2}
1271
+ value={iteration}
1272
+ onChange={(event) => setIteration(Number(event.target.value))}
1273
+ />
1274
+ </label>
1275
+ <OrdinalCustomChart
1276
+ data={topicWordRuns}
1277
+ layout={wordTrailsLayout}
1278
+ layoutConfig={{
1279
+ textAccessor: "word",
1280
+ weightAccessor: "probability",
1281
+ columnAccessor: "topic",
1282
+ segmentAccessor: "iteration",
1283
+ segmentDomain: [0, 2],
1284
+ columnOrder: ["Topic A", "Topic B"],
1285
+ repeatWords: true,
1286
+ wordColor: distinctivenessColor,
1287
+ columnColor: (topic) => topicColors[topic],
1288
+ ...wordTrailsProgressiveReveal({
1289
+ currentSegment: iteration,
1290
+ segmentDomain: [0, 2],
1291
+ oldestOpacity: 0.25,
1292
+ }),
1293
+ }}
1294
+ categoryAccessor="topic"
1295
+ valueAccessor="probability"
1296
+ width={760}
1297
+ height={460}
1298
+ tooltip
1299
+ />
1300
+ </>
1301
+ )
1302
+ }
1303
+ ```
1304
+
1305
+ Key APIs: `WordTrailsWordInfo` gives `word`, `column`, `weight`, and `segment` plus the exact `datum`, `dataIndex`, `columnIndex`, and `resolvedColumnColor`. `wordTrailsProgressiveReveal` hides future segments, fades reached history, and reserves every row's layout slot so playback never reflows. Set `combineWeightOpacity: true` only when opacity should also encode word weight.
1306
+
1307
+ ---
1308
+
1187
1309
  ## Physics Charts — Motion With A Settled Projection
1188
1310
 
1189
1311
  ### GaltonBoardChart (distribution drop)
@@ -1356,6 +1478,93 @@ import { GauntletChart } from "semiotic/physics"
1356
1478
 
1357
1479
  Key props: `positiveProperties` / `negativeProperties` define satellite marks on each project core, `gates` place timed obstacles along the route, and the settled projection strip summarizes viability. Prefer GauntletChart for one plan with many attached attributes; use ProcessFlowChart for many independent work items.
1358
1480
 
1481
+ ### CrucibleChart (explicit product lifecycle + immediate replay)
1482
+
1483
+ ```jsx
1484
+ import { useRef } from "react"
1485
+ import { CrucibleChart, buildCrucibleProductEvents } from "semiotic/physics"
1486
+
1487
+ const evidence = [
1488
+ { id: "deploy-log", label: "Deploy log", kind: "record" },
1489
+ { id: "trace", label: "Request trace", kind: "telemetry" },
1490
+ { id: "rollback", label: "Rollback result", kind: "experiment" },
1491
+ { id: "traffic", label: "Traffic spike", kind: "counterclaim" },
1492
+ ]
1493
+
1494
+ const phases = [
1495
+ { id: "charge", label: "Charge", duration: 1, motion: "charge" },
1496
+ { id: "test", label: "Test mechanism", duration: 2.4, motion: "mix" },
1497
+ { id: "publish", label: "Publish", duration: 1.6, motion: "pour" },
1498
+ ]
1499
+
1500
+ const products = [
1501
+ { id: "finding", label: "Deploy caused regression", outletId: "findings" },
1502
+ ]
1503
+
1504
+ const events = [
1505
+ ...buildCrucibleProductEvents({
1506
+ productId: "finding",
1507
+ form: {
1508
+ at: { phaseId: "test", progress: 0.25 },
1509
+ sourceIds: ["deploy-log", "trace"],
1510
+ label: "Chronology and mechanism agree",
1511
+ },
1512
+ contributions: [{
1513
+ at: { phaseId: "test", progress: 0.7 },
1514
+ sourceIds: ["rollback"],
1515
+ label: "Rollback supplies the counterfactual",
1516
+ }],
1517
+ complete: {
1518
+ at: { phaseId: "publish", progress: 0.55 },
1519
+ outletId: "findings",
1520
+ reason: "Three authored observations support the finding",
1521
+ },
1522
+ }),
1523
+ {
1524
+ id: "reject-traffic",
1525
+ at: { phaseId: "test", progress: 0.82 },
1526
+ effects: [{
1527
+ type: "eject",
1528
+ select: { ids: ["traffic"] },
1529
+ outletId: "contradicted",
1530
+ reason: "The regression also occurs at ordinary traffic levels",
1531
+ }],
1532
+ },
1533
+ ]
1534
+
1535
+ export default function IncidentEvidenceCrucible() {
1536
+ const chartRef = useRef(null)
1537
+
1538
+ return (
1539
+ <>
1540
+ <button type="button" onClick={() => chartRef.current?.replay()}>
1541
+ Replay evidence
1542
+ </button>
1543
+ <CrucibleChart
1544
+ ref={chartRef}
1545
+ data={evidence}
1546
+ phases={phases}
1547
+ products={products}
1548
+ events={events}
1549
+ outlets={[
1550
+ { id: "findings", label: "Supported finding", side: "bottom" },
1551
+ { id: "contradicted", label: "Contradicted", side: "right" },
1552
+ ]}
1553
+ idAccessor="id"
1554
+ labelAccessor="label"
1555
+ categoryAccessor="kind"
1556
+ projection={{ groupBy: "outlet", measure: "count" }}
1557
+ playbackRate={0.8}
1558
+ controls={{ playPause: true, reset: true, stepPhase: true, speed: true }}
1559
+ size={[820, 400]}
1560
+ />
1561
+ </>
1562
+ )
1563
+ }
1564
+ ```
1565
+
1566
+ Key APIs: `buildCrucibleProductEvents` emits the explicit `combine → contribute* → complete-product` grammar and deterministic fallback event ids. It never infers analysis, timing, product membership, reasons, or routing. The handle's `replay()` atomically restarts the same bounded tape even during a run; `reset()` restores the origin and pauses, while `rerunMS` schedules repetition after settlement.
1567
+
1359
1568
  ---
1360
1569
 
1361
1570
  ## Value Charts — One Number Is The Visualization