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.
Files changed (127) hide show
  1. package/CLAUDE.md +26 -23
  2. package/README.md +27 -21
  3. package/ai/chartSuggestions.cjs +191 -3
  4. package/ai/componentMetadata.cjs +3 -3
  5. package/ai/dist/mcp-server.js +266 -48
  6. package/ai/examples.md +68 -0
  7. package/ai/schema.json +914 -1
  8. package/ai/system-prompt.md +4 -1
  9. package/dist/components/ThemeProvider.d.ts +2 -2
  10. package/dist/components/Tooltip/FlippingTooltip.d.ts +16 -1
  11. package/dist/components/charts/geo/FlowMap.d.ts +13 -4
  12. package/dist/components/charts/index.d.ts +6 -0
  13. package/dist/components/charts/network/OrbitDiagram.d.ts +5 -5
  14. package/dist/components/charts/network/ProcessSankey.d.ts +163 -0
  15. package/dist/components/charts/network/SankeyDiagram.d.ts +5 -1
  16. package/dist/components/charts/network/processSankey/algorithm.d.ts +193 -0
  17. package/dist/components/charts/network/processSankey/buildScenes.d.ts +51 -0
  18. package/dist/components/charts/network/processSankey/ribbonInputs.d.ts +32 -0
  19. package/dist/components/charts/network/processSankey/streamingLayout.d.ts +71 -0
  20. package/dist/components/charts/network/processSankey/tooltipUtils.d.ts +41 -0
  21. package/dist/components/charts/ordinal/BarChart.d.ts +12 -0
  22. package/dist/components/charts/ordinal/DotPlot.d.ts +9 -0
  23. package/dist/components/charts/ordinal/GaugeChart.d.ts +20 -0
  24. package/dist/components/charts/ordinal/SwimlaneChart.d.ts +5 -0
  25. package/dist/components/charts/realtime/RealtimeHeatmap.d.ts +2 -0
  26. package/dist/components/charts/realtime/RealtimeHistogram.d.ts +16 -11
  27. package/dist/components/charts/realtime/RealtimeLineChart.d.ts +2 -0
  28. package/dist/components/charts/realtime/RealtimeSwarmChart.d.ts +2 -0
  29. package/dist/components/charts/realtime/RealtimeWaterfallChart.d.ts +2 -0
  30. package/dist/components/charts/realtime/defaultRealtimeTooltip.d.ts +19 -0
  31. package/dist/components/charts/shared/axisExtent.d.ts +59 -0
  32. package/dist/components/charts/shared/chartSpecs.d.ts +75 -0
  33. package/dist/components/charts/shared/colorUtils.d.ts +8 -2
  34. package/dist/components/charts/shared/networkUtils.d.ts +3 -5
  35. package/dist/components/charts/shared/radialGeometry.d.ts +99 -0
  36. package/dist/components/charts/shared/regressionUtils.d.ts +59 -0
  37. package/dist/components/charts/shared/selectionUtils.d.ts +8 -1
  38. package/dist/components/charts/shared/streamPropsHelpers.d.ts +5 -0
  39. package/dist/components/charts/shared/tooltipUtils.d.ts +11 -0
  40. package/dist/components/charts/shared/types.d.ts +19 -0
  41. package/dist/components/charts/shared/useAreaSeriesSetup.d.ts +78 -0
  42. package/dist/components/charts/shared/useChartSetup.d.ts +2 -0
  43. package/dist/components/charts/shared/useCustomChartSetup.d.ts +1 -0
  44. package/dist/components/charts/shared/useEncodingDomain.d.ts +48 -0
  45. package/dist/components/charts/shared/useFrameImperativeHandle.d.ts +1 -1
  46. package/dist/components/charts/shared/useNetworkChartSetup.d.ts +150 -0
  47. package/dist/components/charts/shared/useOrdinalPieceStyle.d.ts +87 -0
  48. package/dist/components/charts/shared/useSeriesFeatures.d.ts +57 -0
  49. package/dist/components/charts/shared/useStreamStatus.d.ts +33 -0
  50. package/dist/components/charts/shared/useXYLineStyle.d.ts +69 -0
  51. package/dist/components/charts/shared/useXYPointStyle.d.ts +87 -0
  52. package/dist/components/charts/shared/withChartWrapper.d.ts +10 -3
  53. package/dist/components/charts/xy/AreaChart.d.ts +36 -2
  54. package/dist/components/charts/xy/BubbleChart.d.ts +9 -0
  55. package/dist/components/charts/xy/ConnectedScatterplot.d.ts +16 -0
  56. package/dist/components/charts/xy/DifferenceChart.d.ts +172 -0
  57. package/dist/components/charts/xy/LineChart.d.ts +26 -2
  58. package/dist/components/charts/xy/Scatterplot.d.ts +39 -1
  59. package/dist/components/geometry/ribbonGeometry.d.ts +76 -0
  60. package/dist/components/semiotic-ai.d.ts +2 -0
  61. package/dist/components/semiotic-network.d.ts +4 -0
  62. package/dist/components/semiotic-realtime.d.ts +2 -0
  63. package/dist/components/semiotic-utils.d.ts +4 -0
  64. package/dist/components/semiotic-xy.d.ts +2 -0
  65. package/dist/components/semiotic.d.ts +4 -4
  66. package/dist/components/server/renderToStaticSVG.d.ts +2 -1
  67. package/dist/components/server/serverChartConfigs.d.ts +3 -0
  68. package/dist/components/server/staticAnnotations.d.ts +1 -1
  69. package/dist/components/server/themeResolver.d.ts +7 -1
  70. package/dist/components/store/ThemeStore.d.ts +7 -1
  71. package/dist/components/stream/AccessibleDataTable.d.ts +2 -2
  72. package/dist/components/stream/GeoPipelineStore.d.ts +21 -0
  73. package/dist/components/stream/OrdinalSVGOverlay.d.ts +8 -0
  74. package/dist/components/stream/PipelineStore.d.ts +25 -13
  75. package/dist/components/stream/SVGOverlay.d.ts +9 -18
  76. package/dist/components/stream/accessorUtils.d.ts +2 -1
  77. package/dist/components/stream/annotationAccessorResolver.d.ts +39 -0
  78. package/dist/components/stream/geoTypes.d.ts +12 -0
  79. package/dist/components/stream/layouts/hierarchyLayoutPlugin.d.ts +1 -1
  80. package/dist/components/stream/networkTypes.d.ts +11 -0
  81. package/dist/components/stream/ordinalTypes.d.ts +27 -1
  82. package/dist/components/stream/renderers/cornerRadii.d.ts +33 -0
  83. package/dist/components/stream/renderers/wedgePathBuilder.d.ts +56 -0
  84. package/dist/components/stream/types.d.ts +127 -11
  85. package/dist/components/stream/xySceneBuilders/areaGradient.d.ts +20 -0
  86. package/dist/components/stream/xySceneBuilders/barScene.d.ts +2 -2
  87. package/dist/components/stream/xySceneBuilders/candlestickScene.d.ts +2 -2
  88. package/dist/components/stream/xySceneBuilders/heatmapScene.d.ts +2 -2
  89. package/dist/components/stream/xySceneBuilders/lineScene.d.ts +4 -3
  90. package/dist/components/stream/xySceneBuilders/pointScene.d.ts +2 -2
  91. package/dist/components/stream/xySceneBuilders/ribbonScene.d.ts +107 -0
  92. package/dist/components/stream/xySceneBuilders/swarmScene.d.ts +2 -2
  93. package/dist/components/stream/xySceneBuilders/types.d.ts +9 -12
  94. package/dist/components/stream/xySceneBuilders/waterfallScene.d.ts +2 -2
  95. package/dist/components/types/legendTypes.d.ts +1 -1
  96. package/dist/geo.min.js +1 -1
  97. package/dist/geo.module.min.js +1 -1
  98. package/dist/network.min.js +1 -1
  99. package/dist/network.module.min.js +1 -1
  100. package/dist/ordinal.min.js +1 -1
  101. package/dist/ordinal.module.min.js +1 -1
  102. package/dist/realtime.min.js +1 -1
  103. package/dist/realtime.module.min.js +1 -1
  104. package/dist/semiotic-ai.d.ts +2 -0
  105. package/dist/semiotic-ai.min.js +1 -1
  106. package/dist/semiotic-ai.module.min.js +1 -1
  107. package/dist/semiotic-data.min.js +1 -1
  108. package/dist/semiotic-data.module.min.js +1 -1
  109. package/dist/semiotic-network.d.ts +4 -0
  110. package/dist/semiotic-realtime.d.ts +2 -0
  111. package/dist/semiotic-recipes.min.js +1 -1
  112. package/dist/semiotic-recipes.module.min.js +1 -1
  113. package/dist/semiotic-themes.min.js +1 -1
  114. package/dist/semiotic-themes.module.min.js +1 -1
  115. package/dist/semiotic-utils.d.ts +4 -0
  116. package/dist/semiotic-utils.min.js +1 -1
  117. package/dist/semiotic-utils.module.min.js +1 -1
  118. package/dist/semiotic-xy.d.ts +2 -0
  119. package/dist/semiotic.d.ts +4 -4
  120. package/dist/semiotic.min.js +1 -1
  121. package/dist/semiotic.module.min.js +1 -1
  122. package/dist/server.min.js +1 -1
  123. package/dist/server.module.min.js +1 -1
  124. package/dist/xy.min.js +1 -1
  125. package/dist/xy.module.min.js +1 -1
  126. package/package.json +23 -10
  127. package/dist/components/stream/xySceneBuilders/boundsScene.d.ts +0 -9
@@ -1,6 +1,9 @@
1
1
  # Semiotic — React Data Visualization
2
2
 
3
- Use sub-path imports: `semiotic/xy` (143KB gz), `semiotic/ordinal` (109KB), `semiotic/network` (98KB), `semiotic/geo` (93KB), `semiotic/realtime` (145KB). Or `semiotic/ai` for all 39 non-geo HOCs in one import (269KB).
3
+ <!-- semiotic-bundle-sizes:start -->
4
+ <!-- Auto-generated by scripts/sync-bundle-sizes.mjs — do not edit by hand. -->
5
+ **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.
6
+ <!-- semiotic-bundle-sizes:end -->
4
7
 
5
8
  ## Flat Array Data (`data: object[]`)
6
9
  - **LineChart** — `xAccessor`, `yAccessor`, `lineBy` (multi-line), `curve`
@@ -1,10 +1,10 @@
1
1
  import * as React from "react";
2
2
  import { LIGHT_THEME, DARK_THEME, HIGH_CONTRAST_THEME } from "./store/ThemeStore";
3
- import type { SemioticTheme } from "./store/ThemeStore";
3
+ import type { SemioticTheme, SemioticThemeUpdate } from "./store/ThemeStore";
4
4
  import type { ThemePresetName } from "./semiotic-themes";
5
5
  interface ThemeProviderProps {
6
6
  /** Theme preset name (e.g. "tufte", "pastels-dark", "bi-tool") or a partial SemioticTheme object. */
7
- theme?: ThemePresetName | Partial<SemioticTheme>;
7
+ theme?: ThemePresetName | SemioticThemeUpdate;
8
8
  children: React.ReactNode;
9
9
  }
10
10
  declare function ThemeProviderWrapper({ theme, children }: ThemeProviderProps): import("react/jsx-runtime").JSX.Element;
@@ -29,6 +29,21 @@ interface FlippingTooltipProps {
29
29
  * On first render, uses a heuristic (similar to the old 70%/30% thresholds).
30
30
  * After measuring the actual tooltip size via ref, repositions precisely to
31
31
  * prevent clipping against container edges.
32
+ *
33
+ * Two defensive behaviors:
34
+ *
35
+ * - **Chrome guarantee.** If the rendered tooltip content lacks the
36
+ * `semiotic-tooltip` className on its root, the wrapper applies
37
+ * `defaultTooltipStyle` to itself so the tooltip always has a
38
+ * visible background, padding, and shadow. Shared tooltip helpers
39
+ * keep working unchanged (their `semiotic-tooltip` class causes the
40
+ * wrapper to stay transparent).
41
+ * - **Non-finite position guard.** Returns `null` when `x` or `y` is
42
+ * `NaN` / `Infinity`. The frame's hover plumbing can occasionally
43
+ * produce a non-finite hit-test result during a scale rebuild or
44
+ * when a custom layout emits a degenerate vertex; without the
45
+ * guard, React throws `'NaN' is an invalid value for the 'top' css
46
+ * style property` and the entire frame stops rendering.
32
47
  */
33
- export declare function FlippingTooltip({ x, y, containerWidth, containerHeight, margin, children, className, zIndex }: FlippingTooltipProps): import("react/jsx-runtime").JSX.Element;
48
+ export declare function FlippingTooltip({ x, y, containerWidth, containerHeight, margin, children, className, zIndex }: FlippingTooltipProps): import("react/jsx-runtime").JSX.Element | null;
34
49
  export {};
@@ -1,5 +1,7 @@
1
1
  import type { Datum } from "../shared/datumTypes";
2
+ import * as React from "react";
2
3
  import type { StreamGeoFrameProps, ProjectionProp } from "../../stream/geoTypes";
4
+ import type { RealtimeFrameHandle } from "../../realtime/types";
3
5
  import type { BaseChartProps, ChartAccessor } from "../shared/types";
4
6
  import { type TooltipProp } from "../../Tooltip/Tooltip";
5
7
  import type { LegendInteractionMode, LegendPosition } from "../shared/hooks";
@@ -79,6 +81,13 @@ export interface FlowMapProps<TDatum extends Datum = Datum> extends BaseChartPro
79
81
  tileCacheSize?: number;
80
82
  /** Annotations */
81
83
  annotations?: Datum[];
84
+ /**
85
+ * ID accessor on flow records — required for `ref.current.remove(id)`
86
+ * and `ref.current.update(id, …)`. The accessor reads the same field
87
+ * the user supplies on each flow; the field is preserved on the
88
+ * resolved line entry so the frame's `removeLine` can match by id.
89
+ */
90
+ lineIdAccessor?: string | ((d: Datum) => string);
82
91
  /** Passthrough */
83
92
  frameProps?: Partial<Omit<StreamGeoFrameProps, "projection">>;
84
93
  }
@@ -132,7 +141,7 @@ export interface FlowMapProps<TDatum extends Datum = Datum> extends BaseChartPro
132
141
  * />
133
142
  * ```
134
143
  */
135
- export declare function FlowMap<TDatum extends Datum = Datum>(props: FlowMapProps<TDatum>): import("react/jsx-runtime").JSX.Element;
136
- export declare namespace FlowMap {
137
- var displayName: string;
138
- }
144
+ export declare const FlowMap: {
145
+ <TDatum extends Datum = Datum>(props: FlowMapProps<TDatum> & React.RefAttributes<RealtimeFrameHandle>): React.ReactElement | null;
146
+ displayName?: string;
147
+ };
@@ -14,6 +14,8 @@ export { LineChart } from "./xy/LineChart";
14
14
  export type { LineChartProps } from "./xy/LineChart";
15
15
  export { AreaChart } from "./xy/AreaChart";
16
16
  export type { AreaChartProps } from "./xy/AreaChart";
17
+ export { DifferenceChart } from "./xy/DifferenceChart";
18
+ export type { DifferenceChartProps } from "./xy/DifferenceChart";
17
19
  export { StackedAreaChart } from "./xy/StackedAreaChart";
18
20
  export type { StackedAreaChartProps } from "./xy/StackedAreaChart";
19
21
  export { Heatmap } from "./xy/Heatmap";
@@ -72,6 +74,10 @@ export { ChordDiagram } from "./network/ChordDiagram";
72
74
  export type { ChordDiagramProps } from "./network/ChordDiagram";
73
75
  export { SankeyDiagram } from "./network/SankeyDiagram";
74
76
  export type { SankeyDiagramProps } from "./network/SankeyDiagram";
77
+ export { ProcessSankey } from "./network/ProcessSankey";
78
+ export type { ProcessSankeyProps, ProcessSankeyTick } from "./network/ProcessSankey";
79
+ export { validateProcessSankey, formatProcessSankeyIssue, } from "./network/processSankey/algorithm";
80
+ export type { ProcessSankeyNode as ProcessSankeyValidatorNode, ProcessSankeyEdge as ProcessSankeyValidatorEdge, ProcessSankeyIssue, } from "./network/processSankey/algorithm";
75
81
  export { TreeDiagram } from "./network/TreeDiagram";
76
82
  export type { TreeDiagramProps } from "./network/TreeDiagram";
77
83
  export { Treemap } from "./network/Treemap";
@@ -4,7 +4,7 @@ import type { StreamNetworkFrameProps } from "../../stream/networkTypes";
4
4
  import type { BaseChartProps } from "../shared/types";
5
5
  import { type TooltipProp } from "../../Tooltip/Tooltip";
6
6
  export interface OrbitNode {
7
- datum: any;
7
+ datum: Datum;
8
8
  x: number;
9
9
  y: number;
10
10
  ring: number;
@@ -38,11 +38,11 @@ export interface OrbitDiagramProps<TDatum extends Datum = Datum> extends BaseCha
38
38
  */
39
39
  orbitMode?: OrbitMode;
40
40
  /** Ring size divisor per depth. Larger = tighter orbits. @default 2.95 */
41
- orbitSize?: number | ((node: any) => number);
41
+ orbitSize?: number | ((node: Datum) => number);
42
42
  /** Orbit speed in degrees per frame @default 0.25 */
43
43
  speed?: number;
44
44
  /** Per-node speed modifier @default (node) => 1 / (node.depth + 1) */
45
- revolution?: (node: any) => number;
45
+ revolution?: (node: Datum) => number;
46
46
  /**
47
47
  * Built-in revolution style presets:
48
48
  * - "locked": children rotate with parent at decreasing speed (default)
@@ -53,11 +53,11 @@ export interface OrbitDiagramProps<TDatum extends Datum = Datum> extends BaseCha
53
53
  */
54
54
  revolutionStyle?: "locked" | "decay" | "alternate";
55
55
  /** Vertical squash for elliptical orbits. 1 = circle, 0.5 = ellipse @default 1 */
56
- eccentricity?: number | ((node: any) => number);
56
+ eccentricity?: number | ((node: Datum) => number);
57
57
  /** Show orbital ring paths @default true */
58
58
  showRings?: boolean;
59
59
  /** Node radius. Number or function of node. @default 6 */
60
- nodeRadius?: number | ((node: any) => number);
60
+ nodeRadius?: number | ((node: Datum) => number);
61
61
  /** Show node labels @default false */
62
62
  showLabels?: boolean;
63
63
  /** Enable animation @default true */
@@ -0,0 +1,163 @@
1
+ import * as React from "react";
2
+ import type { Datum } from "../shared/datumTypes";
3
+ import type { BaseChartProps, ChartAccessor } from "../shared/types";
4
+ import type { RealtimeFrameHandle } from "../../realtime/types";
5
+ import type { StreamNetworkFrameProps, ParticleStyle } from "../../stream/networkTypes";
6
+ import { type TooltipProp } from "../../Tooltip/Tooltip";
7
+ type TimeLike = number | Date | string;
8
+ export interface ProcessSankeyTick {
9
+ date: TimeLike;
10
+ label: string;
11
+ }
12
+ export interface ProcessSankeyProps<TNode extends Datum = Datum, TEdge extends Datum = Datum> extends BaseChartProps {
13
+ nodes?: TNode[];
14
+ edges?: TEdge[];
15
+ /** [tStart, tEnd] of the chart's x-axis. Required. */
16
+ domain: [TimeLike, TimeLike];
17
+ /** Optional axis ticks. Each tick: { date, label }. */
18
+ axisTicks?: ProcessSankeyTick[];
19
+ nodeIdAccessor?: ChartAccessor<TNode, string>;
20
+ sourceAccessor?: ChartAccessor<TEdge, string>;
21
+ targetAccessor?: ChartAccessor<TEdge, string>;
22
+ valueAccessor?: ChartAccessor<TEdge, number>;
23
+ startTimeAccessor?: ChartAccessor<TEdge, TimeLike>;
24
+ endTimeAccessor?: ChartAccessor<TEdge, TimeLike>;
25
+ /**
26
+ * Optional accessor for the per-edge "system in" time — when
27
+ * `value` is added to the SOURCE node's mass. Use when the
28
+ * source node has an intake event distinct from when this edge
29
+ * departs (e.g. a patient is admitted to the ER at 7pm and
30
+ * transferred out at 9pm — the ER node carries the patient's
31
+ * weight between 7pm and 9pm). Without this, the source node's
32
+ * mass is synthesized as a flat baseline and the band reads as
33
+ * "always there." Set it and the node band climbs / falls as
34
+ * units arrive and depart — a true staircase profile.
35
+ */
36
+ systemInTimeAccessor?: ChartAccessor<TEdge, TimeLike>;
37
+ /**
38
+ * Optional accessor for the per-edge "system out" time — when
39
+ * `value` is removed from the TARGET node's mass. Mirror of
40
+ * `systemInTimeAccessor` for the inbound side.
41
+ */
42
+ systemOutTimeAccessor?: ChartAccessor<TEdge, TimeLike>;
43
+ /**
44
+ * Accessor for a node's explicit lifetime extent — a `[start, end]`
45
+ * tuple of time-likes. Lane spans
46
+ * `min(xExtent[0], earliestEdge)` to `max(xExtent[1], latestEdge)`.
47
+ */
48
+ xExtentAccessor?: ChartAccessor<TNode, [TimeLike, TimeLike]>;
49
+ edgeIdAccessor?: ChartAccessor<TEdge, string>;
50
+ colorBy?: ChartAccessor<TNode, string>;
51
+ colorScheme?: string | string[];
52
+ /** Show a swatch + label legend. Defaults to `true` when `colorBy` is set. */
53
+ showLegend?: boolean;
54
+ /** Legend position. Default `"right"`. */
55
+ legendPosition?: "right" | "left" | "top" | "bottom";
56
+ /**
57
+ * Format function for time values — applied to axis tick labels and
58
+ * to time fields in the default tooltip. Same convention as
59
+ * `xFormat` on XY charts.
60
+ */
61
+ timeFormat?: (d: number | Date) => string | React.ReactNode;
62
+ /** Format function for the `value` field. Mirrors `yFormat` on XY charts. */
63
+ valueFormat?: (d: number) => string | React.ReactNode;
64
+ pairing?: "value" | "temporal";
65
+ packing?: "off" | "reuse";
66
+ laneOrder?: "insertion" | "crossing-min" | "inside-out" | "crossing-min+inside-out";
67
+ ribbonLane?: "source" | "target" | "both";
68
+ lifetimeMode?: "full" | "half";
69
+ showLaneRails?: boolean;
70
+ showQualityReadout?: boolean;
71
+ /** Render the per-band node id label at the band's left edge.
72
+ * Default `true`. Set `false` for dense layouts where labels
73
+ * would overlap, or when the legend already names every band. */
74
+ showLabels?: boolean;
75
+ edgeOpacity?: number;
76
+ /** Tooltip content. `false` disables, `true` uses the default,
77
+ * or pass a `Tooltip(...)` / custom function for full control. */
78
+ tooltip?: TooltipProp;
79
+ enableHover?: boolean;
80
+ onClick?: (datum: Datum, position?: {
81
+ x: number;
82
+ y: number;
83
+ }) => void;
84
+ showParticles?: boolean;
85
+ /** Style config for the particle overlay — same shape
86
+ * StreamNetworkFrame consumes from SankeyDiagram. Defaults
87
+ * (radius 3, opacity 0.7, spawnRate 0.1, maxPerEdge 50) live in
88
+ * `DEFAULT_PARTICLE_STYLE`. */
89
+ particleStyle?: ParticleStyle;
90
+ /** Pass-through to the underlying StreamNetworkFrame. */
91
+ frameProps?: Partial<Omit<StreamNetworkFrameProps, "nodes" | "edges" | "chartType" | "size" | "customNetworkLayout" | "layoutConfig">>;
92
+ }
93
+ /**
94
+ * ProcessSankey — temporal flow between nodes with an actual time x-axis.
95
+ *
96
+ * Built on top of `StreamNetworkFrame` via the `customNetworkLayout`
97
+ * escape hatch. Bands and ribbons emit as `bezier` scene-edges; the
98
+ * Frame handles canvas painting, hit testing, accessibility, theme
99
+ * cascade, and the push API.
100
+ *
101
+ * **Differs from SankeyDiagram in three ways:**
102
+ *
103
+ * 1. **Edges carry time.** Each edge has `startTime` / `endTime`.
104
+ * 2. **Nodes have lifetimes, not ranks.** A node's vertical lane spans
105
+ * `min(xExtent[0], earliestEdge)` to `max(xExtent[1], latestEdge)`.
106
+ * 3. **Static-graph cycles are valid** as long as edges move forward in time.
107
+ *
108
+ * @example
109
+ * ```tsx
110
+ * // Static fixture: pre-built nodes + timed edges, categorical colors.
111
+ * <ProcessSankey
112
+ * nodes={[
113
+ * { id: "Alice", category: "Person", xExtent: ["2026-01-06", "2026-01-06"] },
114
+ * { id: "Bob", category: "Person", xExtent: ["2026-02-01", "2026-02-01"] },
115
+ * { id: "Eng", category: "Team" },
116
+ * { id: "Release", category: "Milestone", xExtent: ["2026-04-15", "2026-05-30"] },
117
+ * ]}
118
+ * edges={[
119
+ * { id: "alice-eng", source: "Alice", target: "Eng", value: 8,
120
+ * startTime: "2026-01-20", endTime: "2026-02-10" },
121
+ * { id: "bob-eng", source: "Bob", target: "Eng", value: 5,
122
+ * startTime: "2026-02-15", endTime: "2026-03-15" },
123
+ * { id: "eng-rel", source: "Eng", target: "Release", value: 13,
124
+ * startTime: "2026-04-15", endTime: "2026-05-15" },
125
+ * ]}
126
+ * domain={["2026-01-01", "2026-05-31"]}
127
+ * colorBy="category"
128
+ * showLegend
129
+ * />
130
+ * ```
131
+ *
132
+ * @example
133
+ * ```tsx
134
+ * // Push mode: omit `edges`, mutate via the ref. Same chart shape, but
135
+ * // edges arrive over time. Particles depict throughput.
136
+ * const ref = useRef<RealtimeFrameHandle>(null)
137
+ *
138
+ * useEffect(() => {
139
+ * const id = setInterval(() => {
140
+ * ref.current?.push({
141
+ * source: "API", target: "DB", value: Math.random() * 10,
142
+ * startTime: Date.now(), endTime: Date.now() + 1500,
143
+ * })
144
+ * }, 800)
145
+ * return () => clearInterval(id)
146
+ * }, [])
147
+ *
148
+ * return (
149
+ * <ProcessSankey
150
+ * ref={ref}
151
+ * domain={[t0, t0 + 60_000]}
152
+ * showParticles
153
+ * colorBy="category"
154
+ * showLegend
155
+ * />
156
+ * )
157
+ * ```
158
+ */
159
+ export declare const ProcessSankey: {
160
+ <TNode extends Datum = Datum, TEdge extends Datum = Datum>(props: ProcessSankeyProps<TNode, TEdge> & React.RefAttributes<RealtimeFrameHandle>): React.ReactElement | null;
161
+ displayName?: string;
162
+ };
163
+ export default ProcessSankey;
@@ -4,7 +4,7 @@ import type { StreamNetworkFrameProps } from "../../stream/networkTypes";
4
4
  import type { RealtimeFrameHandle } from "../../realtime/types";
5
5
  import type { BaseChartProps, ChartAccessor } from "../shared/types";
6
6
  import { type TooltipProp } from "../../Tooltip/Tooltip";
7
- import type { LegendInteractionMode } from "../shared/hooks";
7
+ import type { LegendInteractionMode, LegendPosition } from "../shared/hooks";
8
8
  /**
9
9
  * SankeyDiagram component props
10
10
  */
@@ -25,6 +25,10 @@ export interface SankeyDiagramProps<TNode extends Datum = Datum, TEdge extends D
25
25
  nodeLabel?: ChartAccessor<TNode, string>;
26
26
  showLabels?: boolean;
27
27
  enableHover?: boolean;
28
+ /** Show a swatch + label legend. Defaults to `true` when `colorBy` is set. */
29
+ showLegend?: boolean;
30
+ /** Legend position. Default `"right"`. */
31
+ legendPosition?: LegendPosition;
28
32
  legendInteraction?: LegendInteractionMode;
29
33
  edgeOpacity?: number;
30
34
  edgeSort?: (a: any, b: any) => number;
@@ -0,0 +1,193 @@
1
+ export interface ProcessSankeyNode {
2
+ id: string;
3
+ /** Optional explicit lifetime bound [start, end]. Lifetime is
4
+ * `min(xExtent[0], earliestEdge)` to `max(xExtent[1], latestEdge)`. */
5
+ xExtent?: [number, number];
6
+ }
7
+ export interface ProcessSankeyEdge {
8
+ id: string;
9
+ source: string;
10
+ target: string;
11
+ value: number;
12
+ startTime: number;
13
+ endTime: number;
14
+ /** Optional: time at which this unit of mass actually "arrived" at
15
+ * the SOURCE node (e.g., the hospital admit time for an ER patient
16
+ * whose transfer to ICU happens later at `startTime`). Purely a
17
+ * rendering hint — the layout/mass profile is unchanged. The
18
+ * renderer cuts a rectangular slot out of the source node's band
19
+ * from the node's left edge up to the scaled `systemInTime`, with
20
+ * height equal to this edge's ribbon thickness. Edges without
21
+ * `systemInTime` are drawn as-is. Result: a staircase profile on
22
+ * the source side as units enter the system one by one.
23
+ * Default: undefined. */
24
+ systemInTime?: number;
25
+ /** Optional: time at which this unit of mass leaves the TARGET node
26
+ * (e.g., the discharge time for a patient who arrived at the ward
27
+ * at `endTime`). Symmetric to `systemInTime`: the renderer cuts a
28
+ * rectangular slot out of the target node's band from the scaled
29
+ * `systemOutTime` to the node's right edge, with height equal to
30
+ * this edge's ribbon thickness. Layout/mass profile unchanged.
31
+ * Default: undefined. */
32
+ systemOutTime?: number;
33
+ }
34
+ export interface ProcessSankeyIssue {
35
+ kind: string;
36
+ id?: string;
37
+ source?: string;
38
+ target?: string;
39
+ endpoint?: string;
40
+ nodeId?: string;
41
+ }
42
+ export interface ProcessSankeySample {
43
+ t: number;
44
+ topMass: number;
45
+ botMass: number;
46
+ }
47
+ export type AttachmentSide = "top" | "bot";
48
+ export type AttachmentKind = "in" | "out";
49
+ export interface ProcessSankeyAttachment {
50
+ side: AttachmentSide;
51
+ time: number;
52
+ sideMassBefore: number;
53
+ sideMassAfter: number;
54
+ kind: AttachmentKind;
55
+ value: number;
56
+ }
57
+ export interface ProcessSankeyNodeData {
58
+ samples: ProcessSankeySample[];
59
+ peak: number;
60
+ topPeak: number;
61
+ botPeak: number;
62
+ localAttachments: Map<string, ProcessSankeyAttachment>;
63
+ }
64
+ export interface ProcessSankeySlotPeak {
65
+ topPeak: number;
66
+ botPeak: number;
67
+ }
68
+ export interface ProcessSankeySlotOccupant {
69
+ id: string;
70
+ end: number;
71
+ }
72
+ export interface ProcessSankeySlot {
73
+ peak: ProcessSankeySlotPeak;
74
+ occupants: ProcessSankeySlotOccupant[];
75
+ }
76
+ export interface ProcessSankeyLaneLifetime {
77
+ start: number | null;
78
+ end: number | null;
79
+ }
80
+ export interface ProcessSankeySideRecord {
81
+ sourceSide?: AttachmentSide;
82
+ targetSide?: AttachmentSide;
83
+ }
84
+ export interface ProcessSankeyLayout {
85
+ nodeData: Record<string, ProcessSankeyNodeData>;
86
+ sides: Map<string, ProcessSankeySideRecord>;
87
+ valueScale: number;
88
+ padding: number;
89
+ compressedPadding: boolean;
90
+ centerlines: Record<string, number>;
91
+ laneLifetime: Record<string, ProcessSankeyLaneLifetime>;
92
+ slots: ProcessSankeySlot[];
93
+ slotByNode: Record<string, number>;
94
+ crossingsBefore: number | null;
95
+ crossingsAfter: number | null;
96
+ lengthBefore: number | null;
97
+ lengthAfter: number | null;
98
+ }
99
+ export interface ProcessSankeyOptions {
100
+ plotH: number;
101
+ pairing?: "value" | "temporal";
102
+ packing?: "off" | "reuse";
103
+ laneOrder?: "insertion" | "crossing-min" | "inside-out" | "crossing-min+inside-out";
104
+ lifetimeMode?: "full" | "half";
105
+ }
106
+ export interface ProcessSankeyEdgeIndex {
107
+ incoming: Record<string, ProcessSankeyEdge[]>;
108
+ outgoing: Record<string, ProcessSankeyEdge[]>;
109
+ }
110
+ type Domain = [number, number] | null | undefined;
111
+ export declare function validateProcessSankey(nodes: ProcessSankeyNode[], edges: ProcessSankeyEdge[], domain: [number, number]): ProcessSankeyIssue[];
112
+ export declare function formatProcessSankeyIssue(issue: ProcessSankeyIssue): string;
113
+ export declare function buildEdgeIndex(nodes: ProcessSankeyNode[], edges: ProcessSankeyEdge[]): ProcessSankeyEdgeIndex;
114
+ export declare function assignSides(nodes: ProcessSankeyNode[], edges: ProcessSankeyEdge[], edgeIndex: ProcessSankeyEdgeIndex, pairing?: "value" | "temporal"): Map<string, ProcessSankeySideRecord>;
115
+ export declare function computeNode(node: ProcessSankeyNode, edgeIndex: ProcessSankeyEdgeIndex, sides: Map<string, ProcessSankeySideRecord>): ProcessSankeyNodeData;
116
+ export declare function clampTime(t: number, domain: Domain): number;
117
+ export declare function clampSamples(samples: ProcessSankeySample[], domain: Domain): ProcessSankeySample[];
118
+ export declare function attachmentYRange(att: ProcessSankeyAttachment, cl: number, S: number): [number, number];
119
+ export declare function buildBandPath(samples: ProcessSankeySample[], cl: number, S: number, xScale: (t: number) => number, domain: Domain): string | null;
120
+ /**
121
+ * One 20-px gradient stub at an attachment with `systemInTime` /
122
+ * `systemOutTime`. Rendered as its own bezier scene-edge with a
123
+ * horizontal gradient, painted underneath the band. The band
124
+ * paints with `fill: none` whenever any stubs are present, so the
125
+ * stub gradients are the only colored regions inside the band's
126
+ * outline.
127
+ */
128
+ export interface BandGradientStub {
129
+ /** Rect path (M-L-L-L-Z). */
130
+ pathD: string;
131
+ /** Gradient extent in screen pixels. */
132
+ x0: number;
133
+ x1: number;
134
+ /** Color stops — 0 = transparent end, 1 = band-color end. */
135
+ from: 0 | 1;
136
+ to: 0 | 1;
137
+ }
138
+ /**
139
+ * Build the per-edge 20-px gradient stubs that visualize
140
+ * `systemInTime` / `systemOutTime` on a node band. Each stub is
141
+ * a rect on the edge's attachment slot, painted with a horizontal
142
+ * gradient that fades the band color in (or out) over 20 screen
143
+ * pixels and saturates through the rest of the rect.
144
+ *
145
+ * The rect is clipped to the band's outline bounds (so cutouts don't
146
+ * spill outside the node shape), but the gradient extent stays at
147
+ * its natural `[xSysIn - FADE_PX, xSysIn]` / `[xSysOut, xSysOut + FADE_PX]`
148
+ * range. The canvas renderer uses pad-mode clamping for color stops
149
+ * outside the rect, so a stub whose fade region falls past the band's
150
+ * edge still paints solid band-color where it's visible — instead of
151
+ * collapsing to a degenerate gradient that the renderer would clamp
152
+ * to transparent.
153
+ *
154
+ * Pure rendering hint — layout/mass-profile unchanged. Returns an
155
+ * empty array when the node has no qualifying edges.
156
+ */
157
+ export declare function buildBandCutoutsForNode(nodeId: string, edges: ProcessSankeyEdge[], layout: ProcessSankeyLayout, xScale: (t: number) => number, domain: Domain): BandGradientStub[];
158
+ type SlotByNode = Record<string, number>;
159
+ export declare function countCrossings(slotByNode: SlotByNode, edges: ProcessSankeyEdge[]): number;
160
+ export declare function totalEdgeLength(slotByNode: SlotByNode, edges: ProcessSankeyEdge[]): number;
161
+ interface LaneLayoutOptions {
162
+ plotH: number;
163
+ padding: number;
164
+ valueScale: number;
165
+ packing?: "off" | "reuse";
166
+ laneOrder?: "insertion" | "crossing-min" | "inside-out" | "crossing-min+inside-out";
167
+ lifetimeMode?: "full" | "half";
168
+ }
169
+ interface LaneLayoutResult {
170
+ effectiveSlotsHeight: number;
171
+ centerlines: Record<string, number>;
172
+ laneLifetime: Record<string, ProcessSankeyLaneLifetime>;
173
+ slots: ProcessSankeySlot[];
174
+ slotByNode: SlotByNode;
175
+ slotCenter: number[];
176
+ crossingsBefore: number | null;
177
+ crossingsAfter: number | null;
178
+ lengthBefore: number | null;
179
+ lengthAfter: number | null;
180
+ }
181
+ export declare function computeLaneLayout(nodes: ProcessSankeyNode[], edges: ProcessSankeyEdge[], nodeData: Record<string, ProcessSankeyNodeData>, edgeIndex: ProcessSankeyEdgeIndex, opts: LaneLayoutOptions): LaneLayoutResult;
182
+ /**
183
+ * Compute the full Process Sankey layout for a given dataset and
184
+ * configuration. Pure function, no side effects.
185
+ *
186
+ * The chart's time domain isn't a layout opt — domain handling lives
187
+ * in the geometry helpers (`buildBandPath`, `buildRibbonGeometry`,
188
+ * `clampSamples`) which receive an `xScale` and a domain pair from
189
+ * the caller. The layout itself is timeless apart from the per-node
190
+ * sample/event timestamps.
191
+ */
192
+ export declare function computeProcessSankeyLayout(nodes: ProcessSankeyNode[], edges: ProcessSankeyEdge[], opts: ProcessSankeyOptions): ProcessSankeyLayout;
193
+ export {};
@@ -0,0 +1,51 @@
1
+ import { scaleTime } from "d3-scale";
2
+ import { type ProcessSankeyOptions, type ProcessSankeyLayout, type ProcessSankeyIssue } from "./algorithm";
3
+ import type { ProcessSankeyLayoutConfig } from "./streamingLayout";
4
+ import type { Datum } from "../../shared/datumTypes";
5
+ export interface ProcessSankeyNormalizedNode {
6
+ id: string;
7
+ xExtent?: [number, number];
8
+ __raw?: Datum;
9
+ }
10
+ export interface ProcessSankeyNormalizedEdge {
11
+ id: string;
12
+ source: string;
13
+ target: string;
14
+ value: number;
15
+ startTime: number;
16
+ endTime: number;
17
+ /** Optional render-only hint: when this unit of mass actually
18
+ * entered the source node. Triggers a cutout in the source band. */
19
+ systemInTime?: number;
20
+ /** Optional render-only hint: when this unit of mass left the
21
+ * target node. Triggers a cutout in the target band. */
22
+ systemOutTime?: number;
23
+ __raw?: Datum;
24
+ }
25
+ export interface BuildScenesInput {
26
+ nodes: ProcessSankeyNormalizedNode[];
27
+ edges: ProcessSankeyNormalizedEdge[];
28
+ domain: [number, number];
29
+ plotW: number;
30
+ plotH: number;
31
+ ribbonLane: "source" | "target" | "both";
32
+ edgeOpacity: number;
33
+ /** Resolves a node's color by id+index (lets the caller plug in
34
+ * the same theme/colorScheme/colorBy resolution the HOC uses). */
35
+ colorOf: (id: string, idx: number) => string;
36
+ layoutOpts: Pick<ProcessSankeyOptions, "pairing" | "packing" | "laneOrder" | "lifetimeMode">;
37
+ }
38
+ export interface BuildScenesResult {
39
+ layout: ProcessSankeyLayout | null;
40
+ layoutConfig: ProcessSankeyLayoutConfig;
41
+ issues: ProcessSankeyIssue[];
42
+ /** Used downstream for tooltips (mass-history) and overlays. */
43
+ xScale: ReturnType<typeof scaleTime>;
44
+ }
45
+ /**
46
+ * Run the full ProcessSankey layout pipeline. Returns the algorithm
47
+ * output, the bands/ribbons specs ready for `customNetworkLayout`, and
48
+ * the validation issues (caller decides whether to render an error
49
+ * gate or fall through). Pure: no DOM, no React, no rAF.
50
+ */
51
+ export declare function buildProcessSankeyScenes(input: BuildScenesInput): BuildScenesResult;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Compute the `RibbonGeometryInput` shape for a ProcessSankey ribbon
3
+ * from its source/target attachment data. Both the HOC (CSR) and the
4
+ * pure scene builder (SSR) call this so the coords feeding into the
5
+ * shared `buildRibbonGeometry` helper match between the two paths.
6
+ *
7
+ * Replaces `algorithm.js`'s `buildRibbonPath` — the path-D formula
8
+ * itself moved into `buildRibbonGeometry` so SankeyDiagram and
9
+ * ProcessSankey emit identical M-C-L-C-Z shapes.
10
+ */
11
+ import type { RibbonGeometryInput } from "../../../geometry/ribbonGeometry";
12
+ type Side = "top" | "bot";
13
+ type Kind = "in" | "out";
14
+ interface AttachmentLike {
15
+ side: Side;
16
+ time: number;
17
+ sideMassBefore: number;
18
+ sideMassAfter: number;
19
+ kind: Kind;
20
+ value: number;
21
+ }
22
+ type RibbonLane = "source" | "target" | "both";
23
+ type XScale = (t: number) => number;
24
+ /**
25
+ * Build the geometry inputs for a single ProcessSankey ribbon. The
26
+ * source attachment is assumed to be `kind: "out"` (the value leaves
27
+ * the source on its outgoing side); the target attachment is
28
+ * `kind: "in"`. attachmentYRange's formula is inlined here to keep
29
+ * this module pure TS (the `algorithm.js` version is JS-only).
30
+ */
31
+ export declare function computeProcessSankeyRibbonInputs(srcAtt: AttachmentLike, srcCenterline: number, tgtAtt: AttachmentLike, tgtCenterline: number, valueScale: number, xScale: XScale, lane: RibbonLane, domain: [number, number] | null): RibbonGeometryInput;
32
+ export {};