semiotic 3.3.0 → 3.4.0

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 (75) hide show
  1. package/CLAUDE.md +16 -7
  2. package/README.md +1 -1
  3. package/ai/dist/mcp-server.js +104 -9
  4. package/ai/schema.json +97 -1
  5. package/dist/components/LinkedCharts.d.ts +8 -0
  6. package/dist/components/charts/index.d.ts +1 -1
  7. package/dist/components/charts/ordinal/BarChart.d.ts +9 -1
  8. package/dist/components/charts/ordinal/DonutChart.d.ts +2 -0
  9. package/dist/components/charts/ordinal/DotPlot.d.ts +9 -1
  10. package/dist/components/charts/ordinal/GroupedBarChart.d.ts +4 -0
  11. package/dist/components/charts/ordinal/LikertChart.d.ts +5 -2
  12. package/dist/components/charts/ordinal/PieChart.d.ts +2 -0
  13. package/dist/components/charts/ordinal/StackedBarChart.d.ts +4 -0
  14. package/dist/components/charts/shared/hooks.d.ts +15 -2
  15. package/dist/components/charts/shared/selectionUtils.d.ts +9 -7
  16. package/dist/components/charts/shared/tooltipUtils.d.ts +13 -1
  17. package/dist/components/charts/shared/types.d.ts +5 -6
  18. package/dist/components/charts/shared/useChartSetup.d.ts +8 -0
  19. package/dist/components/charts/shared/useResolvedSelection.d.ts +2 -0
  20. package/dist/components/realtime/types.d.ts +54 -3
  21. package/dist/components/semiotic-geo.d.ts +4 -0
  22. package/dist/components/semiotic-network.d.ts +7 -0
  23. package/dist/components/semiotic-ordinal.d.ts +8 -0
  24. package/dist/components/semiotic-xy.d.ts +9 -0
  25. package/dist/components/server/renderToStaticSVG.d.ts +2 -1
  26. package/dist/components/server/serverChartConfigs.d.ts +17 -0
  27. package/dist/components/server/themeResolver.d.ts +4 -0
  28. package/dist/components/store/ThemeStore.d.ts +16 -0
  29. package/dist/components/stream/CanvasHitTester.d.ts +8 -3
  30. package/dist/components/stream/DataSourceAdapter.d.ts +17 -0
  31. package/dist/components/stream/GeoCanvasHitTester.d.ts +1 -1
  32. package/dist/components/stream/GeoPipelineStore.d.ts +19 -1
  33. package/dist/components/stream/NetworkPipelineStore.d.ts +12 -1
  34. package/dist/components/stream/OrdinalCanvasHitTester.d.ts +3 -1
  35. package/dist/components/stream/OrdinalPipelineStore.d.ts +38 -1
  36. package/dist/components/stream/ParticlePool.d.ts +4 -0
  37. package/dist/components/stream/PipelineStore.d.ts +17 -2
  38. package/dist/components/stream/StreamXYFrame.d.ts +17 -0
  39. package/dist/components/stream/geoTypes.d.ts +11 -4
  40. package/dist/components/stream/hoverUtils.d.ts +34 -0
  41. package/dist/components/stream/networkTypes.d.ts +33 -23
  42. package/dist/components/stream/ordinalTypes.d.ts +63 -12
  43. package/dist/components/stream/pipelineDecay.d.ts +6 -5
  44. package/dist/components/stream/pipelineTransitionUtils.d.ts +21 -0
  45. package/dist/components/stream/quadtreeHitTest.d.ts +22 -0
  46. package/dist/components/stream/renderers/resolveCSSColor.d.ts +23 -6
  47. package/dist/components/stream/types.d.ts +22 -0
  48. package/dist/components/stream/useFrame.d.ts +122 -0
  49. package/dist/geo.min.js +1 -1
  50. package/dist/geo.module.min.js +1 -1
  51. package/dist/network.min.js +1 -1
  52. package/dist/network.module.min.js +1 -1
  53. package/dist/ordinal.min.js +1 -1
  54. package/dist/ordinal.module.min.js +1 -1
  55. package/dist/realtime.min.js +1 -1
  56. package/dist/realtime.module.min.js +1 -1
  57. package/dist/semiotic-ai.min.js +1 -1
  58. package/dist/semiotic-ai.module.min.js +1 -1
  59. package/dist/semiotic-geo.d.ts +4 -0
  60. package/dist/semiotic-network.d.ts +7 -0
  61. package/dist/semiotic-ordinal.d.ts +8 -0
  62. package/dist/semiotic-themes.min.js +1 -1
  63. package/dist/semiotic-themes.module.min.js +1 -1
  64. package/dist/semiotic-utils.min.js +1 -1
  65. package/dist/semiotic-utils.module.min.js +1 -1
  66. package/dist/semiotic-xy.d.ts +9 -0
  67. package/dist/semiotic.min.js +1 -1
  68. package/dist/semiotic.module.min.js +1 -1
  69. package/dist/server.min.js +1 -1
  70. package/dist/server.module.min.js +1 -1
  71. package/dist/test-utils/canvasMock.d.ts +26 -0
  72. package/dist/test-utils/ordinalFixtures.d.ts +48 -0
  73. package/dist/xy.min.js +1 -1
  74. package/dist/xy.module.min.js +1 -1
  75. package/package.json +15 -14
@@ -2,7 +2,8 @@ import type { ReactNode } from "react";
2
2
  import type { OnObservationCallback } from "../store/ObservationStore";
3
3
  import type { HoverData, AnnotationContext } from "../realtime/types";
4
4
  import type { LegendGroup } from "../types/legendTypes";
5
- import type { Style, DecayConfig, PulseConfig, StalenessConfig } from "./types";
5
+ import type { Style, DecayConfig, PulseConfig, TransitionConfig, StalenessConfig } from "./types";
6
+ import type { AnimateProp } from "./pipelineTransitionUtils";
6
7
  export interface TensionConfig {
7
8
  weightChange: number;
8
9
  newEdge: number;
@@ -52,6 +53,8 @@ export interface RealtimeEdge {
52
53
  _targetY0?: number;
53
54
  _targetY1?: number;
54
55
  _targetSankeyWidth?: number;
56
+ /** Set during intro animation to allow edge interpolation from width 0 */
57
+ _introFromZero?: boolean;
55
58
  direction?: string;
56
59
  circular?: boolean;
57
60
  circularPathData?: any;
@@ -168,13 +171,16 @@ export interface NetworkRectNode {
168
171
  _pulseGlowRadius?: number;
169
172
  }
170
173
  /** Arc node — used by chord */
174
+ /** Arc node — used by chord. Angles in canvas convention (0 = 3 o'clock). */
171
175
  export interface NetworkArcNode {
172
176
  type: "arc";
173
177
  cx: number;
174
178
  cy: number;
175
179
  innerR: number;
176
180
  outerR: number;
181
+ /** Start angle in radians, canvas convention (0 = 3 o'clock, positive = clockwise) */
177
182
  startAngle: number;
183
+ /** End angle in radians, canvas convention */
178
184
  endAngle: number;
179
185
  style: Style;
180
186
  datum: any;
@@ -205,6 +211,9 @@ export interface NetworkBezierEdge {
205
211
  datum: any;
206
212
  _pulseIntensity?: number;
207
213
  _pulseColor?: string;
214
+ /** Lazily-built Path2D for hit testing; invalidated when pathD changes. */
215
+ _cachedPath2D?: Path2D;
216
+ _cachedPath2DSource?: string;
208
217
  }
209
218
  /** Ribbon edge — used by chord */
210
219
  export interface NetworkRibbonEdge {
@@ -214,6 +223,8 @@ export interface NetworkRibbonEdge {
214
223
  datum: any;
215
224
  _pulseIntensity?: number;
216
225
  _pulseColor?: string;
226
+ _cachedPath2D?: Path2D;
227
+ _cachedPath2DSource?: string;
217
228
  }
218
229
  /** Curved edge — used by tree, cluster */
219
230
  export interface NetworkCurvedEdge {
@@ -223,6 +234,8 @@ export interface NetworkCurvedEdge {
223
234
  datum: any;
224
235
  _pulseIntensity?: number;
225
236
  _pulseColor?: string;
237
+ _cachedPath2D?: Path2D;
238
+ _cachedPath2DSource?: string;
226
239
  }
227
240
  export type NetworkSceneNode = NetworkCircleNode | NetworkRectNode | NetworkArcNode;
228
241
  export type NetworkSceneEdge = NetworkLineEdge | NetworkBezierEdge | NetworkRibbonEdge | NetworkCurvedEdge;
@@ -290,8 +303,10 @@ export interface NetworkPipelineConfig {
290
303
  sourceAccessor?: string | ((d: any) => string);
291
304
  targetAccessor?: string | ((d: any) => string);
292
305
  valueAccessor?: string | ((d: any) => number);
306
+ /** Edge ID accessor for removeEdge(edgeId) — enables single-ID edge removal */
307
+ edgeIdAccessor?: string | ((d: any) => string);
293
308
  childrenAccessor?: string | ((d: any) => any[]);
294
- hierarchySum?: (d: any) => number;
309
+ hierarchySum?: string | ((d: any) => number);
295
310
  orientation?: "horizontal" | "vertical";
296
311
  nodeAlign?: "justify" | "left" | "right" | "center";
297
312
  nodePaddingRatio?: number;
@@ -323,6 +338,9 @@ export interface NetworkPipelineConfig {
323
338
  nodeSizeRange?: [number, number];
324
339
  decay?: DecayConfig;
325
340
  pulse?: PulseConfig;
341
+ transition?: TransitionConfig;
342
+ /** Whether to animate elements on first render (nodes scale up, edges fade in) */
343
+ introAnimation?: boolean;
326
344
  staleness?: StalenessConfig;
327
345
  thresholds?: ThresholdAlertConfig;
328
346
  /** Ring arrangement mode: "flat" (all children in one ring), "solar" (one per ring),
@@ -370,8 +388,10 @@ export interface StreamNetworkFrameProps<T = Record<string, any>> {
370
388
  sourceAccessor?: string | ((d: T) => string);
371
389
  targetAccessor?: string | ((d: T) => string);
372
390
  valueAccessor?: string | ((d: T) => number);
391
+ /** Edge ID accessor for removeEdge(edgeId) single-ID removal */
392
+ edgeIdAccessor?: string | ((d: any) => string);
373
393
  childrenAccessor?: string | ((d: T) => T[]);
374
- hierarchySum?: (d: T) => number;
394
+ hierarchySum?: string | ((d: T) => number);
375
395
  orientation?: "horizontal" | "vertical";
376
396
  nodeAlign?: "justify" | "left" | "right" | "center";
377
397
  nodePaddingRatio?: number;
@@ -413,24 +433,9 @@ export interface StreamNetworkFrameProps<T = Record<string, any>> {
413
433
  className?: string;
414
434
  background?: string;
415
435
  enableHover?: boolean;
416
- tooltipContent?: (d: {
417
- type: "node" | "edge";
418
- data: any;
419
- x: number;
420
- y: number;
421
- }) => ReactNode;
422
- customHoverBehavior?: (d: {
423
- type: "node" | "edge";
424
- data: any;
425
- x: number;
426
- y: number;
427
- } | null) => void;
428
- customClickBehavior?: (d: {
429
- type: "node" | "edge";
430
- data: any;
431
- x: number;
432
- y: number;
433
- } | null) => void;
436
+ tooltipContent?: (d: HoverData) => ReactNode;
437
+ customHoverBehavior?: (d: HoverData | null) => void;
438
+ customClickBehavior?: (d: HoverData | null) => void;
434
439
  /** Observation callback — emits hover/click events to the ObservationStore and this callback */
435
440
  onObservation?: OnObservationCallback;
436
441
  /** Chart instance identifier for observation filtering */
@@ -455,6 +460,11 @@ export interface StreamNetworkFrameProps<T = Record<string, any>> {
455
460
  backgroundGraphics?: ReactNode;
456
461
  decay?: DecayConfig;
457
462
  pulse?: PulseConfig;
463
+ transition?: TransitionConfig;
464
+ /** Declarative animation: `true` for defaults (300ms ease-out), or config object.
465
+ * When enabled, charts animate on first render (intro) and on data change.
466
+ * Set `{ intro: false }` to disable the intro animation. */
467
+ animate?: AnimateProp;
458
468
  staleness?: StalenessConfig;
459
469
  thresholds?: ThresholdAlertConfig;
460
470
  orbitMode?: "flat" | "solar" | "atomic" | number[];
@@ -477,8 +487,8 @@ export interface StreamNetworkFrameHandle {
477
487
  pushMany(edges: EdgePush[]): void;
478
488
  /** Remove a node by ID. Also removes connected edges. */
479
489
  removeNode(id: string): boolean;
480
- /** Remove all edges between source and target node IDs. */
481
- removeEdge(sourceId: string, targetId: string): boolean;
490
+ /** Remove edges by source+target, or by edge ID when edgeIdAccessor is configured. */
491
+ removeEdge(sourceIdOrEdgeId: string, targetId?: string): boolean;
482
492
  /** Update a node's data by ID. Returns previous data. */
483
493
  updateNode(id: string, updater: (data: Record<string, any>) => Record<string, any>): Record<string, any> | null;
484
494
  /** Update all edges between source+target. Returns array of previous data. */
@@ -2,6 +2,7 @@ import type { ReactNode } from "react";
2
2
  import type { ScaleLinear, ScaleBand } from "d3-scale";
3
3
  import type { ArrowOfTime, WindowMode, HoverAnnotationConfig, HoverData, AnnotationContext } from "../realtime/types";
4
4
  import type { Style, DecayConfig, PulseConfig, TransitionConfig, StalenessConfig } from "./types";
5
+ import type { AnimateProp } from "./pipelineTransitionUtils";
5
6
  import type { LegendGroup } from "../types/legendTypes";
6
7
  export type OrdinalChartType = "bar" | "clusterbar" | "point" | "swarm" | "pie" | "donut" | "boxplot" | "violin" | "histogram" | "ridgeline" | "timeline" | "funnel" | "bar-funnel" | "swimlane";
7
8
  export interface OrdinalScales {
@@ -15,8 +16,12 @@ export interface WedgeSceneNode {
15
16
  cy: number;
16
17
  innerRadius: number;
17
18
  outerRadius: number;
19
+ /** Start angle in radians, canvas convention (0 = 3 o'clock, positive = clockwise) */
18
20
  startAngle: number;
21
+ /** End angle in radians, canvas convention */
19
22
  endAngle: number;
23
+ /** Corner radius for rounded wedge arcs (d3-shape arc.cornerRadius) */
24
+ cornerRadius?: number;
20
25
  style: Style;
21
26
  datum: any;
22
27
  category?: string;
@@ -24,6 +29,8 @@ export interface WedgeSceneNode {
24
29
  _pulseColor?: string;
25
30
  _pulseGlowRadius?: number;
26
31
  _targetOpacity?: number;
32
+ _targetStartAngle?: number;
33
+ _targetEndAngle?: number;
27
34
  _transitionKey?: string;
28
35
  }
29
36
  export interface BoxplotSceneNode {
@@ -149,21 +156,27 @@ export interface OrdinalPipelineConfig {
149
156
  windowMode: WindowMode;
150
157
  extentPadding: number;
151
158
  projection: "vertical" | "horizontal" | "radial";
152
- oAccessor?: string | ((d: any) => string);
153
- rAccessor?: string | ((d: any) => number) | Array<string | ((d: any) => number)>;
159
+ categoryAccessor?: string | ((d: any) => string);
160
+ valueAccessor?: string | ((d: any) => number) | Array<string | ((d: any) => number)>;
154
161
  colorAccessor?: string | ((d: any) => string);
155
162
  stackBy?: string | ((d: any) => string);
156
163
  groupBy?: string | ((d: any) => string);
157
- multiAxis?: boolean;
158
164
  timeAccessor?: string | ((d: any) => number);
159
- valueAccessor?: string | ((d: any) => number);
160
- categoryAccessor?: string | ((d: any) => string);
165
+ /** @deprecated Use categoryAccessor */
166
+ oAccessor?: string | ((d: any) => string);
167
+ /** @deprecated Use valueAccessor */
168
+ rAccessor?: string | ((d: any) => number) | Array<string | ((d: any) => number)>;
169
+ multiAxis?: boolean;
161
170
  rExtent?: [number?, number?];
162
171
  oExtent?: string[];
163
172
  barPadding?: number;
173
+ /** Rounded top corner radius for bar charts. Only the end away from the baseline is rounded. For stacked bars, only the topmost segment gets rounded. */
174
+ roundedTop?: number;
164
175
  /** When true, adds padding below the 0 baseline. When false (default), bars are flush with the axis line. */
165
176
  baselinePadding?: boolean;
166
177
  innerRadius?: number;
178
+ /** Corner radius for rounded wedge arcs (pie/donut) */
179
+ cornerRadius?: number;
167
180
  normalize?: boolean;
168
181
  startAngle?: number;
169
182
  /** Total arc sweep in degrees (default 360 = full circle). Used by GaugeChart for partial arcs. */
@@ -174,34 +187,41 @@ export interface OrdinalPipelineConfig {
174
187
  amplitude?: number;
175
188
  connectorOpacity?: number;
176
189
  showLabels?: boolean;
177
- oSort?: ((a: any, b: any) => number) | boolean | "asc" | "desc";
190
+ oSort?: ((a: string, b: string) => number) | boolean | "asc" | "desc" | "auto";
178
191
  connectorAccessor?: string | ((d: any) => string);
179
192
  connectorStyle?: Style | ((d: any) => Style);
180
193
  dynamicColumnWidth?: string | ((data: any[]) => number);
181
194
  pieceStyle?: (d: any, category?: string) => Style;
182
195
  summaryStyle?: (d: any, category?: string) => Style;
183
196
  colorScheme?: string | string[];
197
+ themeCategorical?: string[];
184
198
  barColors?: Record<string, string>;
185
199
  /** ID accessor for remove() — extracts a unique identifier from each datum */
186
200
  dataIdAccessor?: string | ((d: any) => string);
187
201
  decay?: DecayConfig;
188
202
  pulse?: PulseConfig;
189
203
  transition?: TransitionConfig;
204
+ /** Whether to animate elements on first render (bars grow from baseline, wedges sweep in) */
205
+ introAnimation?: boolean;
190
206
  staleness?: StalenessConfig;
191
207
  }
192
208
  export interface StreamOrdinalFrameProps<T = Record<string, any>> {
193
209
  chartType: OrdinalChartType;
194
210
  runtimeMode?: "bounded" | "streaming";
195
211
  data?: T[];
196
- oAccessor?: string | ((d: T) => string);
197
- rAccessor?: string | ((d: T) => number) | Array<string | ((d: T) => number)>;
212
+ /** Category field — the ordinal dimension (replaces oAccessor) */
213
+ categoryAccessor?: string | ((d: T) => string);
214
+ /** Value field — the quantitative dimension (replaces rAccessor). Can be array for multiAxis. */
215
+ valueAccessor?: string | ((d: T) => number) | Array<string | ((d: T) => number)>;
198
216
  colorAccessor?: string | ((d: T) => string);
199
217
  stackBy?: string | ((d: T) => string);
200
218
  groupBy?: string | ((d: T) => string);
201
- multiAxis?: boolean;
202
219
  timeAccessor?: string | ((d: T) => number);
203
- valueAccessor?: string | ((d: T) => number);
204
- categoryAccessor?: string | ((d: T) => string);
220
+ /** @deprecated Use categoryAccessor instead */
221
+ oAccessor?: string | ((d: T) => string);
222
+ /** @deprecated Use valueAccessor instead */
223
+ rAccessor?: string | ((d: T) => number) | Array<string | ((d: T) => number)>;
224
+ multiAxis?: boolean;
205
225
  projection?: "vertical" | "horizontal" | "radial";
206
226
  size?: [number, number];
207
227
  responsiveWidth?: boolean;
@@ -213,8 +233,10 @@ export interface StreamOrdinalFrameProps<T = Record<string, any>> {
213
233
  left?: number;
214
234
  };
215
235
  barPadding?: number;
236
+ roundedTop?: number;
216
237
  baselinePadding?: boolean;
217
238
  innerRadius?: number;
239
+ cornerRadius?: number;
218
240
  normalize?: boolean;
219
241
  startAngle?: number;
220
242
  sweepAngle?: number;
@@ -228,7 +250,7 @@ export interface StreamOrdinalFrameProps<T = Record<string, any>> {
228
250
  rExtent?: [number?, number?];
229
251
  oExtent?: string[];
230
252
  extentPadding?: number;
231
- oSort?: ((a: any, b: any) => number) | boolean | "asc" | "desc";
253
+ oSort?: ((a: string, b: string) => number) | boolean | "asc" | "desc" | "auto";
232
254
  arrowOfTime?: ArrowOfTime;
233
255
  windowMode?: WindowMode;
234
256
  windowSize?: number;
@@ -246,9 +268,21 @@ export interface StreamOrdinalFrameProps<T = Record<string, any>> {
246
268
  barColors?: Record<string, string>;
247
269
  showAxes?: boolean;
248
270
  showCategoryTicks?: boolean;
271
+ /** Category axis label */
272
+ categoryLabel?: string;
273
+ /** Value axis label */
274
+ valueLabel?: string;
275
+ /** Category tick formatter */
276
+ categoryFormat?: (d: string, index?: number) => string | ReactNode;
277
+ /** Value tick formatter */
278
+ valueFormat?: (d: number | string) => string;
279
+ /** @deprecated Use categoryLabel */
249
280
  oLabel?: string;
281
+ /** @deprecated Use valueLabel */
250
282
  rLabel?: string;
283
+ /** @deprecated Use categoryFormat */
251
284
  oFormat?: (d: string, index?: number) => string | ReactNode;
285
+ /** @deprecated Use valueFormat */
252
286
  rFormat?: (d: number | string) => string;
253
287
  enableHover?: boolean;
254
288
  hoverAnnotation?: boolean | HoverAnnotationConfig;
@@ -288,6 +322,10 @@ export interface StreamOrdinalFrameProps<T = Record<string, any>> {
288
322
  decay?: DecayConfig;
289
323
  pulse?: PulseConfig;
290
324
  transition?: TransitionConfig;
325
+ /** Declarative animation: `true` for defaults (300ms ease-out), or config object.
326
+ * When enabled, charts animate on first render (intro) and on data change.
327
+ * Set `{ intro: false }` to disable the intro animation. */
328
+ animate?: AnimateProp;
291
329
  staleness?: StalenessConfig;
292
330
  /** Render a visually-hidden data table from the scene graph for screen readers */
293
331
  accessibleTable?: boolean;
@@ -299,6 +337,19 @@ export interface StreamOrdinalFrameProps<T = Record<string, any>> {
299
337
  export interface StreamOrdinalFrameHandle<T = Record<string, any>> {
300
338
  push(datum: T): void;
301
339
  pushMany(data: T[]): void;
340
+ /** Replace all data. Unlike `clear() + pushMany()`, `replace()` preserves
341
+ * the previous scene's position snapshot so data-change transitions fire.
342
+ * Use when you need to swap in a full new dataset (e.g. re-aggregated
343
+ * values from streaming input) and want the bars/points/etc. to animate
344
+ * between the old and new positions.
345
+ *
346
+ * Note: for datasets within the DataSourceAdapter chunk threshold (the
347
+ * common case for aggregator HOCs like LikertChart) the replacement
348
+ * lands in a single changeset. Larger datasets fall through to the
349
+ * progressive-chunked path used by the `data` prop — the first chunk
350
+ * resets + seeds the buffer, subsequent chunks append on successive
351
+ * animation frames, so replacement is not instantaneous for large N. */
352
+ replace(data: T[]): void;
302
353
  /** Remove data items by ID. Requires dataIdAccessor. */
303
354
  remove(id: string | string[]): T[];
304
355
  /** Update data items by ID in place. Requires dataIdAccessor. Returns previous values. */
@@ -1,11 +1,12 @@
1
1
  /**
2
- * Decay encoding for XY pipeline scene nodes.
2
+ * Shared decay encoding utilities for all pipeline stores.
3
3
  *
4
- * Applies age-based opacity fade to scene nodes linear, exponential, or step.
5
- * Per-vertex decay for line/area nodes, uniform for discrete nodes (point, rect, etc.).
4
+ * `computeDecayOpacity()` core algorithm used by all four pipeline stores
5
+ * (XY, Ordinal, Network, Geo) to compute age-based opacity.
6
6
  *
7
- * Dependencies: types (SceneNode, DecayConfig)
8
- * Consumed by: PipelineStore.computeScene (after scene build, before transitions)
7
+ * `applyDecay()` XY-specific application that handles per-vertex decay
8
+ * for line/area nodes. Other stores call `computeDecayOpacity()` directly
9
+ * with their own node iteration logic.
9
10
  */
10
11
  import type { SceneNode, DecayConfig } from "./types";
11
12
  /**
@@ -40,3 +40,24 @@ export declare function lerp(from: number, to: number, t: number): number;
40
40
  * Get the current timestamp in a way that works both in browser and Node.
41
41
  */
42
42
  export declare function now(): number;
43
+ /** The animate prop type accepted by all HOCs and Stream Frames.
44
+ * Single source of truth — re-exported by types.ts, ordinalTypes.ts, networkTypes.ts, geoTypes.ts. */
45
+ export type AnimateProp = boolean | {
46
+ duration?: number;
47
+ easing?: "linear" | "ease-out";
48
+ intro?: boolean;
49
+ };
50
+ /**
51
+ * Resolve the declarative `animate` prop into a concrete TransitionConfig.
52
+ * Used by all 4 Stream Frames. `animate` takes precedence over `transitionProp`.
53
+ */
54
+ export declare function resolveAnimateConfig(animate: AnimateProp | undefined, transitionProp: {
55
+ duration?: number;
56
+ easing?: "ease-out" | "linear";
57
+ } | undefined): {
58
+ transition: {
59
+ duration?: number;
60
+ easing?: "ease-out" | "linear";
61
+ } | undefined;
62
+ introEnabled: boolean;
63
+ };
@@ -0,0 +1,22 @@
1
+ import type { Quadtree } from "d3-quadtree";
2
+ export interface QuadtreeHit<T> {
3
+ node: T;
4
+ distance: number;
5
+ }
6
+ /**
7
+ * Find the closest point whose own hit radius contains the cursor.
8
+ *
9
+ * Uses `quadtree.visit()` to enumerate every candidate within the widened
10
+ * search radius (maxDistance extended by the largest point radius in the
11
+ * scene). `quadtree.find()` alone is insufficient because it returns the
12
+ * nearest candidate by center-to-center distance — a farther point with a
13
+ * much larger visual radius can still be a valid hit that `find()` would
14
+ * hide.
15
+ *
16
+ * `T` must expose `x`, `y`, and `r` — the standard `PointSceneNode` shape.
17
+ */
18
+ export declare function findHitPointInQuadtree<T extends {
19
+ x: number;
20
+ y: number;
21
+ r: number;
22
+ }>(qt: Quadtree<T>, px: number, py: number, maxDistance: number, maxPointRadius: number): QuadtreeHit<T> | null;
@@ -5,13 +5,30 @@
5
5
  * reads the computed value from the canvas element. Otherwise returns
6
6
  * the input unchanged.
7
7
  *
8
- * Per-canvas cache avoids repeated getComputedStyle calls within a single
9
- * paint cycle. The cache is cleared on theme changes via clearCSSColorCache(),
10
- * which each Stream Frame calls in its theme-change useEffect.
8
+ * Per-canvas cache avoids repeated `getComputedStyle` calls. Cached entries
9
+ * are tagged with a global version counter that's bumped whenever a theme
10
+ * change is detected either through `clearCSSColorCache()` (called by
11
+ * Stream Frames on `currentTheme` change) or via the global observer below
12
+ * (catches external class toggles on `<html>` and `prefers-color-scheme`
13
+ * media-query changes that bypass React).
11
14
  */
12
15
  export declare function resolveCSSColor(ctx: CanvasRenderingContext2D, value: string | undefined): string | undefined;
13
16
  /**
14
- * Clear the per-canvas CSS variable cache. Called by Stream Frames
15
- * when the theme changes so the next paint reads fresh computed values.
17
+ * Invalidate the CSS variable cache. Stream Frames call this from their
18
+ * `currentTheme` `useEffect` so the next paint reads fresh computed values.
19
+ *
20
+ * The `canvas` argument is accepted for backward compatibility but ignored —
21
+ * invalidation is global because theme changes are global.
22
+ */
23
+ export declare function clearCSSColorCache(_canvas?: HTMLCanvasElement): void;
24
+ /**
25
+ * Test-only: reset all cache state, including disconnecting any installed
26
+ * observer/matchMedia listeners. Required for test isolation — without it,
27
+ * observers accumulate across files and bump `currentVersion` more than once
28
+ * per real DOM mutation.
29
+ *
30
+ * `currentVersion` is *incremented* (not reset to zero) so any WeakMap entries
31
+ * that survive from a previous test can't accidentally be re-validated by a
32
+ * version collision.
16
33
  */
17
- export declare function clearCSSColorCache(canvas: HTMLCanvasElement): void;
34
+ export declare function _resetCSSColorCacheForTest(): void;
@@ -1,5 +1,6 @@
1
1
  import type { ReactNode } from "react";
2
2
  import type { ScaleLinear } from "d3-scale";
3
+ import type { AnimateProp } from "./pipelineTransitionUtils";
3
4
  import type { ArrowOfTime, WindowMode, LineStyle, BarStyle, WaterfallStyle, SwarmStyle, HoverAnnotationConfig, HoverData, AnnotationContext, AnnotationAnchorMode } from "../realtime/types";
4
5
  export interface DecayConfig {
5
6
  type: "linear" | "exponential" | "step";
@@ -107,6 +108,8 @@ export interface LineSceneNode {
107
108
  _prevPath?: [number, number][];
108
109
  /** Target path coordinates for interpolation during transitions */
109
110
  _targetPath?: [number, number][];
111
+ /** Intro clip fraction (0→1): reveals line from left to right via canvas clip */
112
+ _introClipFraction?: number;
110
113
  }
111
114
  export interface AreaSceneNode {
112
115
  type: "area";
@@ -156,6 +159,8 @@ export interface AreaSceneNode {
156
159
  _prevBottomPath?: [number, number][];
157
160
  /** Target bottom path coordinates for interpolation during transitions */
158
161
  _targetBottomPath?: [number, number][];
162
+ /** Intro clip fraction (0→1): reveals area from left to right via canvas clip */
163
+ _introClipFraction?: number;
159
164
  }
160
165
  export interface PointSceneNode {
161
166
  type: "point";
@@ -187,6 +192,10 @@ export interface RectSceneNode {
187
192
  y: number;
188
193
  w: number;
189
194
  h: number;
195
+ /** Rounded corner radius on the end away from the baseline */
196
+ roundedTop?: number;
197
+ /** Which edge to round: "top"/"bottom" (vertical), "right"/"left" (horizontal) */
198
+ roundedEdge?: "top" | "bottom" | "right" | "left";
190
199
  style: Style;
191
200
  datum: any;
192
201
  group?: string;
@@ -274,6 +283,15 @@ export interface Changeset<T = Record<string, any>> {
274
283
  bounded: boolean;
275
284
  /** Hint: total dataset size when progressively chunking bounded data */
276
285
  totalSize?: number;
286
+ /** When true on a bounded changeset, the store replaces the buffer
287
+ * contents but does NOT clear its category insertion-order memory
288
+ * and marks itself as having received streaming-sourced data. Used
289
+ * by aggregator HOCs (LikertChart, future density/bin charts) that
290
+ * re-derive their full dataset from streaming input on every push —
291
+ * the user perceives it as a stream even though the transport is
292
+ * a wholesale replacement. Without this, re-aggregation would wipe
293
+ * the category order and categories would shuffle on every tick. */
294
+ preserveCategoryOrder?: boolean;
277
295
  }
278
296
  /**
279
297
  * Note: when xScaleType="time", the x scale is a d3.scaleTime at runtime
@@ -463,6 +481,10 @@ export interface StreamXYFrameProps<T = Record<string, any>> {
463
481
  pulse?: PulseConfig;
464
482
  /** Smooth position transitions on data change */
465
483
  transition?: TransitionConfig;
484
+ /** Declarative animation: `true` for defaults (300ms ease-out), or config object.
485
+ * When enabled, charts animate on first render (intro) and on data change.
486
+ * Set `{ intro: false }` to disable the intro animation. */
487
+ animate?: AnimateProp;
466
488
  /** Frame-level data liveness indicator */
467
489
  staleness?: StalenessConfig;
468
490
  /** Marginal distribution plots in axis margins (histogram, violin, ridgeline, boxplot) */
@@ -0,0 +1,122 @@
1
+ import * as React from "react";
2
+ import type { ReactNode } from "react";
3
+ import type { SemioticTheme } from "../store/ThemeStore";
4
+ import { useResponsiveSize } from "./useResponsiveSize";
5
+ import { resolveAnimateConfig } from "./pipelineTransitionUtils";
6
+ import type { AnimateProp } from "./pipelineTransitionUtils";
7
+ import type { TransitionConfig } from "./types";
8
+ import type { HoverPointerCoords } from "./hoverUtils";
9
+ /**
10
+ * Frame-supplied margin defaults. Each Stream Frame has its own — XY's
11
+ * differs from Ordinal's, Network has both a default and a CENTERED variant
12
+ * for radial chart types. The hook accepts the resolved default and
13
+ * shallow-merges user margin on top.
14
+ */
15
+ export interface FrameMargin {
16
+ top: number;
17
+ right: number;
18
+ bottom: number;
19
+ left: number;
20
+ }
21
+ /**
22
+ * Foreground/background graphics can be a ReactNode (rendered as-is) or a
23
+ * function that receives the current `{ size, margin }` and returns one.
24
+ * The function form lets users place chrome relative to the chart area.
25
+ */
26
+ export type FrameGraphicsProp = ReactNode | ((ctx: {
27
+ size: number[];
28
+ margin: FrameMargin;
29
+ }) => ReactNode);
30
+ export interface UseFrameInput {
31
+ /** Resolved size `[width, height]`. Each frame defaults its `size` prop
32
+ * before calling, so this is never undefined. */
33
+ sizeProp: [number, number];
34
+ /** Frame's `responsiveWidth` prop. */
35
+ responsiveWidth: boolean | undefined;
36
+ /** Frame's `responsiveHeight` prop. */
37
+ responsiveHeight: boolean | undefined;
38
+ /** Frame's user-supplied margin (always partial — each side optional).
39
+ * Matches the `margin?:` prop type on every Stream Frame. */
40
+ userMargin: Partial<FrameMargin> | undefined;
41
+ /** Frame's family-default margin. Shallow-merged with `userMargin`. */
42
+ marginDefault: FrameMargin;
43
+ /** Frame's `foregroundGraphics` prop. */
44
+ foregroundGraphics?: FrameGraphicsProp;
45
+ /** Frame's `backgroundGraphics` prop. */
46
+ backgroundGraphics?: FrameGraphicsProp;
47
+ /** Frame's `animate` prop. */
48
+ animate?: AnimateProp;
49
+ /** Frame's `transition` prop (legacy / explicit form). */
50
+ transitionProp?: TransitionConfig;
51
+ /**
52
+ * Frame's `dirtyRef` (the flag that forces a full canvas redraw on the
53
+ * next paint). When provided, useFrame installs a theme-change effect
54
+ * that bumps it to `true`, clears the CSS-var cache, and queues a
55
+ * render — the pattern that all four frames duplicated.
56
+ *
57
+ * Optional because this is a Tier B add-on; before the migration the
58
+ * frames installed this themselves. The hook keeps the input optional
59
+ * so a frame can opt in independently, but in practice all four pass
60
+ * it.
61
+ *
62
+ * `dirtyRef` is owned by the frame (not the hook) because its initial
63
+ * value differs — only `StreamXYFrame` inits to `false`; Ordinal,
64
+ * Network, and Geo all init to `true` (load-bearing for first-paint
65
+ * timing on those three).
66
+ */
67
+ themeDirtyRef?: React.MutableRefObject<boolean>;
68
+ }
69
+ export interface UseFrameResult {
70
+ /** Reduced-motion preference at last render (for re-render gating). */
71
+ reducedMotion: boolean;
72
+ /** Reduced-motion ref-mirror so render closures see the latest value
73
+ * without depending on it. */
74
+ reducedMotionRef: React.MutableRefObject<boolean>;
75
+ /** Ref to attach to the responsive container. */
76
+ responsiveRef: ReturnType<typeof useResponsiveSize>[0];
77
+ /** Resolved size `[width, height]` accounting for `responsiveWidth/Height`. */
78
+ size: [number, number];
79
+ /** Effective margin (`marginDefault` ⊕ `userMargin`). */
80
+ margin: FrameMargin;
81
+ /** `size[0] - margin.left - margin.right`. */
82
+ adjustedWidth: number;
83
+ /** `size[1] - margin.top - margin.bottom`. */
84
+ adjustedHeight: number;
85
+ /** Resolved foreground (function-or-node, evaluated). */
86
+ resolvedForeground: ReactNode;
87
+ /** Resolved background (function-or-node, evaluated). */
88
+ resolvedBackground: ReactNode;
89
+ /** Current theme from the ThemeStore — re-renders on theme change. */
90
+ currentTheme: SemioticTheme;
91
+ /** Resolved transition config from `animate`/`transition` props. */
92
+ transition: ReturnType<typeof resolveAnimateConfig>["transition"];
93
+ /** Whether the intro animation should run on first render. */
94
+ introEnabled: boolean;
95
+ /** Stable id for the AccessibleDataTable region (hash-suffixed). */
96
+ tableId: string;
97
+ /** Token of the pending rAF, or 0 if none. */
98
+ rafRef: React.MutableRefObject<number>;
99
+ /** Frame assigns its render closure here. */
100
+ renderFnRef: React.MutableRefObject<() => void>;
101
+ /** Queue a render on the next animation frame. Coalesces. */
102
+ scheduleRender: () => void;
103
+ /** Frame assigns its hover handler closure here. */
104
+ hoverHandlerRef: React.MutableRefObject<(coords: HoverPointerCoords) => void>;
105
+ /** Frame assigns its pointer-leave closure here. */
106
+ hoverLeaveRef: React.MutableRefObject<() => void>;
107
+ /** Stable callback to attach to canvas's onPointerMove (or onMouseMove).
108
+ * Captures the coords and queues a single rAF to drain into hoverHandlerRef. */
109
+ onPointerMove: (e: {
110
+ clientX: number;
111
+ clientY: number;
112
+ }) => void;
113
+ /** Stable callback to attach to canvas's onPointerLeave (or onMouseLeave).
114
+ * Cancels any pending hover rAF and invokes hoverLeaveRef. */
115
+ onPointerLeave: () => void;
116
+ }
117
+ /**
118
+ * Bundles the universally-shared setup boilerplate that opens every
119
+ * Stream Frame. See `FRAME_COMPOSITION_LOG.md` for which concerns are
120
+ * inside this hook vs. left frame-specific.
121
+ */
122
+ export declare function useFrame(input: UseFrameInput): UseFrameResult;