semiotic 3.3.1 → 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 (70) hide show
  1. package/CLAUDE.md +10 -4
  2. package/README.md +1 -1
  3. package/ai/dist/mcp-server.js +104 -9
  4. package/ai/schema.json +1 -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 +7 -1
  8. package/dist/components/charts/ordinal/DotPlot.d.ts +9 -1
  9. package/dist/components/charts/ordinal/GroupedBarChart.d.ts +2 -2
  10. package/dist/components/charts/ordinal/LikertChart.d.ts +5 -2
  11. package/dist/components/charts/ordinal/StackedBarChart.d.ts +2 -2
  12. package/dist/components/charts/shared/hooks.d.ts +15 -2
  13. package/dist/components/charts/shared/selectionUtils.d.ts +9 -7
  14. package/dist/components/charts/shared/tooltipUtils.d.ts +13 -1
  15. package/dist/components/charts/shared/types.d.ts +5 -6
  16. package/dist/components/charts/shared/useChartSetup.d.ts +8 -0
  17. package/dist/components/charts/shared/useResolvedSelection.d.ts +2 -0
  18. package/dist/components/realtime/types.d.ts +12 -0
  19. package/dist/components/semiotic-geo.d.ts +4 -0
  20. package/dist/components/semiotic-network.d.ts +7 -0
  21. package/dist/components/semiotic-ordinal.d.ts +8 -0
  22. package/dist/components/semiotic-xy.d.ts +9 -0
  23. package/dist/components/server/renderToStaticSVG.d.ts +1 -1
  24. package/dist/components/store/ThemeStore.d.ts +2 -0
  25. package/dist/components/stream/CanvasHitTester.d.ts +8 -3
  26. package/dist/components/stream/DataSourceAdapter.d.ts +17 -0
  27. package/dist/components/stream/GeoCanvasHitTester.d.ts +1 -1
  28. package/dist/components/stream/GeoPipelineStore.d.ts +19 -1
  29. package/dist/components/stream/NetworkPipelineStore.d.ts +5 -0
  30. package/dist/components/stream/OrdinalCanvasHitTester.d.ts +3 -1
  31. package/dist/components/stream/OrdinalPipelineStore.d.ts +38 -1
  32. package/dist/components/stream/ParticlePool.d.ts +4 -0
  33. package/dist/components/stream/PipelineStore.d.ts +17 -2
  34. package/dist/components/stream/StreamXYFrame.d.ts +17 -0
  35. package/dist/components/stream/geoTypes.d.ts +7 -0
  36. package/dist/components/stream/hoverUtils.d.ts +12 -0
  37. package/dist/components/stream/networkTypes.d.ts +19 -1
  38. package/dist/components/stream/ordinalTypes.d.ts +53 -12
  39. package/dist/components/stream/pipelineTransitionUtils.d.ts +21 -0
  40. package/dist/components/stream/quadtreeHitTest.d.ts +22 -0
  41. package/dist/components/stream/renderers/resolveCSSColor.d.ts +23 -6
  42. package/dist/components/stream/types.d.ts +18 -0
  43. package/dist/components/stream/useFrame.d.ts +122 -0
  44. package/dist/geo.min.js +1 -1
  45. package/dist/geo.module.min.js +1 -1
  46. package/dist/network.min.js +1 -1
  47. package/dist/network.module.min.js +1 -1
  48. package/dist/ordinal.min.js +1 -1
  49. package/dist/ordinal.module.min.js +1 -1
  50. package/dist/realtime.min.js +1 -1
  51. package/dist/realtime.module.min.js +1 -1
  52. package/dist/semiotic-ai.min.js +1 -1
  53. package/dist/semiotic-ai.module.min.js +1 -1
  54. package/dist/semiotic-geo.d.ts +4 -0
  55. package/dist/semiotic-network.d.ts +7 -0
  56. package/dist/semiotic-ordinal.d.ts +8 -0
  57. package/dist/semiotic-themes.min.js +1 -1
  58. package/dist/semiotic-themes.module.min.js +1 -1
  59. package/dist/semiotic-utils.min.js +1 -1
  60. package/dist/semiotic-utils.module.min.js +1 -1
  61. package/dist/semiotic-xy.d.ts +9 -0
  62. package/dist/semiotic.min.js +1 -1
  63. package/dist/semiotic.module.min.js +1 -1
  64. package/dist/server.min.js +1 -1
  65. package/dist/server.module.min.js +1 -1
  66. package/dist/test-utils/canvasMock.d.ts +26 -0
  67. package/dist/test-utils/ordinalFixtures.d.ts +48 -0
  68. package/dist/xy.min.js +1 -1
  69. package/dist/xy.module.min.js +1 -1
  70. package/package.json +15 -14
@@ -1,3 +1,5 @@
1
+ /** Apply accessibility flags to a resolved theme. Shared by ThemeStore and server themeResolver. */
2
+ export declare function applyThemeAccessibility(theme: SemioticTheme): SemioticTheme;
1
3
  export interface SemioticTheme {
2
4
  mode: "light" | "dark" | "auto";
3
5
  colors: {
@@ -13,11 +13,16 @@ export interface HitResult {
13
13
  * Dispatches to type-specific hit testers for optimal performance.
14
14
  *
15
15
  * When a quadtree spatial index is provided (for scatter/bubble charts with
16
- * many points), point hit testing uses O(log n) quadtree.find() instead of
17
- * iterating all nodes. Non-point node types (line, rect, area, etc.) still
16
+ * many points), point hit testing routes through `findHitPointInQuadtree`,
17
+ * which visits every candidate within the widened search radius (using
18
+ * `maxPointRadius` so variable-size points like BubbleChart can't hide
19
+ * behind a nearer non-hit). The visit is authoritative — when it returns
20
+ * null, no point hit exists and the linear point loop is skipped.
21
+ *
22
+ * Non-point node types (line, rect, area, heatcell, candlestick) still
18
23
  * use the linear scan.
19
24
  */
20
- export declare function findNearestNode(scene: SceneNode[], px: number, py: number, maxDistance?: number, pointQuadtree?: Quadtree<PointSceneNode> | null): HitResult | null;
25
+ export declare function findNearestNode(scene: SceneNode[], px: number, py: number, maxDistance?: number, pointQuadtree?: Quadtree<PointSceneNode> | null, maxPointRadius?: number): HitResult | null;
21
26
  /**
22
27
  * Find all line/area nodes at a given X pixel coordinate.
23
28
  * For each node, interpolates the Y value at px using the path data.
@@ -32,6 +32,23 @@ export declare class DataSourceAdapter<T = Record<string, any>> {
32
32
  * animation frames (bounded: false so they append without clearing).
33
33
  */
34
34
  setBoundedData(data: T[]): void;
35
+ /**
36
+ * Replace the buffer contents without clearing category insertion-order
37
+ * memory. Intended for aggregator HOCs (e.g. LikertChart) that re-derive
38
+ * a full dataset from streaming input on every push — the transport is
39
+ * wholesale replacement, but the user perceives it as a live stream
40
+ * where categories should stay put across updates.
41
+ *
42
+ * Small datasets (≤ chunkThreshold) emit a single synchronous
43
+ * changeset — the common aggregator case. Larger datasets fall
44
+ * through to progressive chunking so an unexpectedly-huge replacement
45
+ * doesn't block the main thread. Only the first chunk carries
46
+ * `preserveCategoryOrder: true` (it's the one that resets the
47
+ * buffer and seeds the category Set); subsequent chunks are plain
48
+ * append-only streaming changesets, which preserve order anyway via
49
+ * the streaming-mode branch.
50
+ */
51
+ setReplacementData(data: T[]): void;
35
52
  /**
36
53
  * Flush all buffered push data as a single changeset.
37
54
  * Called automatically via microtask after push()/pushMany().
@@ -16,4 +16,4 @@ export interface GeoHitResult {
16
16
  * The `hitCtx` parameter is a shared offscreen canvas context used for
17
17
  * isPointInPath checks without polluting the visible canvas.
18
18
  */
19
- export declare function findNearestGeoNode(nodes: GeoSceneNode[], mouseX: number, mouseY: number, maxDistance: number, hitCtx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, pointQuadtree?: Quadtree<PointSceneNode>): GeoHitResult | null;
19
+ export declare function findNearestGeoNode(nodes: GeoSceneNode[], mouseX: number, mouseY: number, maxDistance: number, hitCtx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, pointQuadtree?: Quadtree<PointSceneNode> | null, maxPointRadius?: number): GeoHitResult | null;
@@ -1,12 +1,18 @@
1
1
  import type { ZoomTransform } from "d3-zoom";
2
+ import { type Quadtree } from "d3-quadtree";
2
3
  import type { GeoPipelineConfig, GeoScales, GeoSceneNode } from "./geoTypes";
3
- import type { StreamLayout } from "./types";
4
+ import type { PointSceneNode, StreamLayout } from "./types";
4
5
  import type { ActiveTransition } from "./pipelineTransitionUtils";
5
6
  export declare class GeoPipelineStore {
6
7
  config: GeoPipelineConfig;
7
8
  scene: GeoSceneNode[];
8
9
  scales: GeoScales | null;
9
10
  version: number;
11
+ private static readonly QUADTREE_THRESHOLD;
12
+ private _quadtree;
13
+ /** Largest visual point radius in the current scene; used to widen quadtree
14
+ * hit-test radius when points are larger than the default maxDistance. */
15
+ private _maxPointRadius;
10
16
  private projection;
11
17
  private geoPath;
12
18
  private baseScale;
@@ -28,6 +34,7 @@ export declare class GeoPipelineStore {
28
34
  private timestampBuffer;
29
35
  activeTransition: ActiveTransition | null;
30
36
  private prevPositions;
37
+ private _hasRenderedOnce;
31
38
  constructor(config: GeoPipelineConfig);
32
39
  updateConfig(config: Partial<GeoPipelineConfig>): void;
33
40
  setAreas(features: GeoJSON.Feature[]): void;
@@ -76,6 +83,17 @@ export declare class GeoPipelineStore {
76
83
  translate: [number, number];
77
84
  };
78
85
  getPoints(): Record<string, any>[];
86
+ /**
87
+ * Build (or clear) the quadtree spatial index for point scene nodes.
88
+ * Only built when the point count exceeds QUADTREE_THRESHOLD; below that
89
+ * a linear scan is faster than indexing overhead. Also tracks the largest
90
+ * point radius so the hit tester can widen its query when symbols are big.
91
+ */
92
+ private rebuildQuadtree;
93
+ /** Quadtree spatial index for point hit testing, or null when below threshold. */
94
+ get quadtree(): Quadtree<PointSceneNode> | null;
95
+ /** Largest visual point radius in the current scene. */
96
+ get maxPointRadius(): number;
79
97
  private buildSceneNodes;
80
98
  private applyCartogramTransform;
81
99
  private applyDecay;
@@ -22,6 +22,11 @@ export declare class NetworkPipelineStore {
22
22
  private config;
23
23
  private tensionConfig;
24
24
  transition: ActiveTransition | null;
25
+ private _hasRenderedOnce;
26
+ /** Snapshot of node positions from before bounded re-ingestion cleared the maps */
27
+ private _boundedPrevSnapshot;
28
+ /** Snapshot of edge positions from before bounded re-ingestion cleared the maps */
29
+ private _boundedEdgeSnapshot;
25
30
  lastIngestTime: number;
26
31
  private nodeTimestamps;
27
32
  private edgeTimestamps;
@@ -1,4 +1,6 @@
1
1
  import type { OrdinalSceneNode } from "./ordinalTypes";
2
+ import type { PointSceneNode } from "./types";
3
+ import type { Quadtree } from "d3-quadtree";
2
4
  export interface OrdinalHitResult {
3
5
  datum: any;
4
6
  x: number;
@@ -7,4 +9,4 @@ export interface OrdinalHitResult {
7
9
  category?: string;
8
10
  stats?: import("./ordinalTypes").DistributionStats;
9
11
  }
10
- export declare function findNearestOrdinalNode(scene: OrdinalSceneNode[], px: number, py: number, maxDistance?: number): OrdinalHitResult | null;
12
+ export declare function findNearestOrdinalNode(scene: OrdinalSceneNode[], px: number, py: number, maxDistance?: number, pointQuadtree?: Quadtree<PointSceneNode> | null, maxPointRadius?: number): OrdinalHitResult | null;
@@ -15,8 +15,9 @@
15
15
  * Consumed by: StreamOrdinalFrame (sole consumer).
16
16
  */
17
17
  import { type ScaleLinear } from "d3-scale";
18
+ import { type Quadtree } from "d3-quadtree";
18
19
  import type { OrdinalPipelineConfig, OrdinalScales, OrdinalSceneNode, OrdinalColumn, OrdinalLayout } from "./ordinalTypes";
19
- import type { Changeset } from "./types";
20
+ import type { Changeset, PointSceneNode } from "./types";
20
21
  import type { ActiveTransition } from "./pipelineTransitionUtils";
21
22
  export declare class OrdinalPipelineStore {
22
23
  private buffer;
@@ -52,6 +53,17 @@ export declare class OrdinalPipelineStore {
52
53
  scene: OrdinalSceneNode[];
53
54
  columns: Record<string, OrdinalColumn>;
54
55
  version: number;
56
+ /** Bumped whenever the buffer is mutated. Used to invalidate per-frame caches. */
57
+ private _dataVersion;
58
+ private static readonly QUADTREE_THRESHOLD;
59
+ private _pointQuadtree;
60
+ /** Largest visual point radius in the current scene. */
61
+ private _maxPointRadius;
62
+ /** Cached datum→index map for applyDecay/applyPulse. Keyed by `_dataVersion`. */
63
+ private _datumIndexCache;
64
+ /** Cached category→indices map for applyPulse wedge path. Keyed by `_dataVersion`. */
65
+ private _categoryIndexCache;
66
+ private _hasRenderedOnce;
55
67
  constructor(config: OrdinalPipelineConfig);
56
68
  ingest(changeset: Changeset): boolean;
57
69
  private pushValueExtent;
@@ -69,9 +81,34 @@ export declare class OrdinalPipelineStore {
69
81
  private getColorFromScheme;
70
82
  private resolveSummaryStyle;
71
83
  computeDecayOpacity(bufferIndex: number, bufferSize: number): number;
84
+ /**
85
+ * Build (or return cached) datum→buffer-index map. Cached against
86
+ * `_dataVersion` so the per-frame applyDecay/applyPulse calls don't
87
+ * rebuild it during animation when the buffer hasn't changed.
88
+ */
89
+ private getDatumIndexMap;
90
+ /**
91
+ * Build (or return cached) category→[indices] map used by applyPulse for
92
+ * wedge nodes. Cached against `_dataVersion` so the per-wedge inner loop
93
+ * collapses from O(data) to O(matches-for-this-category).
94
+ */
95
+ private getCategoryIndexMap;
96
+ /**
97
+ * Build (or clear) a quadtree spatial index for point scene nodes.
98
+ * Useful for swarm plots — other ordinal types (bar/wedge/box/violin) are
99
+ * not indexed because they're typically few in number or already O(1) hit
100
+ * tests via bbox checks.
101
+ */
102
+ private rebuildPointQuadtree;
103
+ /** Quadtree spatial index for point hit testing, or null when below threshold. */
104
+ get pointQuadtree(): Quadtree<PointSceneNode> | null;
105
+ /** Largest visual point radius in the current scene. */
106
+ get maxPointRadius(): number;
72
107
  private applyDecay;
73
108
  private applyPulse;
74
109
  get hasActivePulses(): boolean;
110
+ /** Synthesize a zero-state prevPositionMap for animated intro (first render). */
111
+ private synthesizeIntroPositions;
75
112
  /** Build a stable identity key for a scene node based on its content, not array index */
76
113
  private getNodeKey;
77
114
  private snapshotPositions;
@@ -4,10 +4,14 @@ import type { Particle, RealtimeEdge } from "./networkTypes";
4
4
  *
5
5
  * Pre-allocates a fixed-size array to avoid GC pressure.
6
6
  * Particles travel along bezier paths within link bands.
7
+ *
8
+ * `_freeIndices` is a stack of currently-free slot indices. Spawn pops; step
9
+ * pushes when a particle expires. This makes spawn O(1) instead of O(capacity).
7
10
  */
8
11
  export declare class ParticlePool {
9
12
  particles: Particle[];
10
13
  private capacity;
14
+ private _freeIndices;
11
15
  constructor(capacity: number);
12
16
  /**
13
17
  * Spawn a new particle on the given edge.
@@ -76,11 +76,15 @@ export interface PipelineConfig {
76
76
  strokeWidth?: number;
77
77
  };
78
78
  colorScheme?: string | string[];
79
+ /** Theme categorical palette — used as fallback when colorScheme is not an explicit array */
80
+ themeCategorical?: string[];
79
81
  barColors?: Record<string, string>;
80
82
  annotations?: Record<string, any>[];
81
83
  decay?: DecayConfig;
82
84
  pulse?: PulseConfig;
83
85
  transition?: TransitionConfig;
86
+ /** Whether to animate elements on first render (points scale up, lines/areas clip from left, rects grow from baseline) */
87
+ introAnimation?: boolean;
84
88
  staleness?: StalenessConfig;
85
89
  heatmapAggregation?: "count" | "sum" | "mean";
86
90
  heatmapXBins?: number;
@@ -111,6 +115,7 @@ export declare class PipelineStore {
111
115
  private getPointId;
112
116
  private timestampBuffer;
113
117
  activeTransition: ActiveTransition | null;
118
+ private _hasRenderedOnce;
114
119
  private prevPositionMap;
115
120
  /** Previous line/area path arrays for path interpolation */
116
121
  private prevPathMap;
@@ -140,6 +145,10 @@ export declare class PipelineStore {
140
145
  /** True when the x accessor returns Date objects (auto-detected on first data ingestion) */
141
146
  xIsDate: boolean;
142
147
  private _quadtree;
148
+ /** Largest visual point radius in the current scene. The hit tester uses
149
+ * this to widen its quadtree query so points with big radii (bubble) don't
150
+ * fall outside the search region. */
151
+ private _maxPointRadius;
143
152
  private static readonly QUADTREE_THRESHOLD;
144
153
  constructor(config: PipelineConfig);
145
154
  /**
@@ -161,6 +170,8 @@ export declare class PipelineStore {
161
170
  * Returns null when chart type is not scatter/bubble or point count is below threshold.
162
171
  */
163
172
  get quadtree(): Quadtree<PointSceneNode> | null;
173
+ /** Largest visual point radius in the current scene. */
174
+ get maxPointRadius(): number;
164
175
  /**
165
176
  * Remap existing scene node coordinates for a new layout size.
166
177
  * Proportionally scales all pixel coordinates without rebuilding from data.
@@ -174,13 +185,17 @@ export declare class PipelineStore {
174
185
  get hasActivePulses(): boolean;
175
186
  private get transitionContext();
176
187
  private snapshotPositions;
188
+ /** Synthesize a zero-state prevPositionMap/prevPathMap for animated intro (first render). */
189
+ private synthesizeIntroPositions;
177
190
  private startTransition;
178
191
  advanceTransition(now: number): boolean;
179
192
  private groupData;
180
193
  /**
181
194
  * Resolve a category→color map from data using the colorAccessor.
182
- * Caches the result in _colorMapCache keyed by sorted category set —
183
- * only rebuilds when the set of categories changes.
195
+ * Caches the result in _colorMapCache keyed by `_ingestVersion` (fast path)
196
+ * and a sorted-category fingerprint (so palette/scheme changes still
197
+ * invalidate). Multiple scene builders within one frame skip the data scan
198
+ * entirely after the first call.
184
199
  */
185
200
  private resolveColorMap;
186
201
  private resolveLineStyle;
@@ -1,4 +1,21 @@
1
1
  import * as React from "react";
2
2
  import type { StreamXYFrameProps, StreamXYFrameHandle } from "./types";
3
+ /**
4
+ * Append a 2-char hex alpha to an existing CSS color, returning a valid
5
+ * CSS color string. The naive `${color}${alpha}` concatenation only works
6
+ * when `color` is a 6-char `#rrggbb`; shorthand `#rgb` produces the
7
+ * invalid 5-char `#rgbXX`, which `ctx.strokeStyle`/`fillStyle` silently
8
+ * rejects (falling back to `#000000` — black, invisible on dark themes).
9
+ * The /cookbook/marginal-graphics crosshair invisibility was caused
10
+ * precisely by `--semiotic-text-secondary: "#aaa"` hitting this path.
11
+ *
12
+ * Handles:
13
+ * • 3-char hex (`#abc`) → expanded to 6-char then concatenated
14
+ * • 6-char hex (`#aabbcc`) → concatenated directly
15
+ * • `rgb(...)` → repacked as `rgba(..., a)` with numeric alpha
16
+ * Any other form (named colors, hsl(), oklch(), etc.) falls back to the
17
+ * raw color without alpha — degrades gracefully.
18
+ */
19
+ export declare function withAlpha(color: string, alphaHex: string): string;
3
20
  declare const StreamXYFrame: React.ForwardRefExoticComponent<StreamXYFrameProps<Record<string, any>> & React.RefAttributes<StreamXYFrameHandle<Record<string, any>>>>;
4
21
  export default StreamXYFrame;
@@ -1,6 +1,7 @@
1
1
  import type { ReactNode } from "react";
2
2
  import type { GeoProjection, GeoPath, GeoPermissibleObjects } from "d3-geo";
3
3
  import type { Style, DecayConfig, PulseConfig, TransitionConfig, StalenessConfig, PointSceneNode, LineSceneNode } from "./types";
4
+ import type { AnimateProp } from "./pipelineTransitionUtils";
4
5
  import type { HoverAnnotationConfig, HoverData } from "../realtime/types";
5
6
  import type { GeoParticleStyle } from "./GeoParticlePool";
6
7
  export type ProjectionProp = GeoProjection | ProjectionName | ProjectionConfig;
@@ -75,6 +76,8 @@ export interface GeoPipelineConfig {
75
76
  decay?: DecayConfig;
76
77
  pulse?: PulseConfig;
77
78
  transition?: TransitionConfig;
79
+ /** Whether to animate elements on first render */
80
+ introAnimation?: boolean;
78
81
  annotations?: Record<string, any>[];
79
82
  pointIdAccessor?: string | ((d: any) => string);
80
83
  }
@@ -146,6 +149,10 @@ export interface StreamGeoFrameProps<T = Record<string, any>> {
146
149
  decay?: DecayConfig;
147
150
  pulse?: PulseConfig;
148
151
  transition?: TransitionConfig;
152
+ /** Declarative animation: `true` for defaults (300ms ease-out), or config object.
153
+ * When enabled, charts animate on first render (intro) and on data change.
154
+ * Set `{ intro: false }` to disable the intro animation. */
155
+ animate?: AnimateProp;
149
156
  staleness?: StalenessConfig;
150
157
  backgroundGraphics?: ReactNode;
151
158
  foregroundGraphics?: ReactNode;
@@ -7,6 +7,18 @@
7
7
  * property flattening that are handled at the call site.
8
8
  */
9
9
  import type { HoverData } from "../realtime/types";
10
+ /**
11
+ * Minimal shape a Stream Frame's internal hover handler needs from a pointer
12
+ * event. React.MouseEvent satisfies this structurally, as does the plain
13
+ * `{clientX, clientY}` object the rAF-coalescing path synthesizes. Using this
14
+ * narrower type on `hoverHandlerRef` (instead of `React.MouseEvent`) prevents
15
+ * downstream code from reading event fields — `currentTarget`, `target`,
16
+ * `preventDefault` — that wouldn't survive the coalescing cast.
17
+ */
18
+ export interface HoverPointerCoords {
19
+ clientX: number;
20
+ clientY: number;
21
+ }
10
22
  /**
11
23
  * Spread raw datum properties onto HoverData if it's a non-null,
12
24
  * non-array object. Class instances (Date, etc.) are included —
@@ -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;
@@ -208,6 +211,9 @@ export interface NetworkBezierEdge {
208
211
  datum: any;
209
212
  _pulseIntensity?: number;
210
213
  _pulseColor?: string;
214
+ /** Lazily-built Path2D for hit testing; invalidated when pathD changes. */
215
+ _cachedPath2D?: Path2D;
216
+ _cachedPath2DSource?: string;
211
217
  }
212
218
  /** Ribbon edge — used by chord */
213
219
  export interface NetworkRibbonEdge {
@@ -217,6 +223,8 @@ export interface NetworkRibbonEdge {
217
223
  datum: any;
218
224
  _pulseIntensity?: number;
219
225
  _pulseColor?: string;
226
+ _cachedPath2D?: Path2D;
227
+ _cachedPath2DSource?: string;
220
228
  }
221
229
  /** Curved edge — used by tree, cluster */
222
230
  export interface NetworkCurvedEdge {
@@ -226,6 +234,8 @@ export interface NetworkCurvedEdge {
226
234
  datum: any;
227
235
  _pulseIntensity?: number;
228
236
  _pulseColor?: string;
237
+ _cachedPath2D?: Path2D;
238
+ _cachedPath2DSource?: string;
229
239
  }
230
240
  export type NetworkSceneNode = NetworkCircleNode | NetworkRectNode | NetworkArcNode;
231
241
  export type NetworkSceneEdge = NetworkLineEdge | NetworkBezierEdge | NetworkRibbonEdge | NetworkCurvedEdge;
@@ -328,6 +338,9 @@ export interface NetworkPipelineConfig {
328
338
  nodeSizeRange?: [number, number];
329
339
  decay?: DecayConfig;
330
340
  pulse?: PulseConfig;
341
+ transition?: TransitionConfig;
342
+ /** Whether to animate elements on first render (nodes scale up, edges fade in) */
343
+ introAnimation?: boolean;
331
344
  staleness?: StalenessConfig;
332
345
  thresholds?: ThresholdAlertConfig;
333
346
  /** Ring arrangement mode: "flat" (all children in one ring), "solar" (one per ring),
@@ -447,6 +460,11 @@ export interface StreamNetworkFrameProps<T = Record<string, any>> {
447
460
  backgroundGraphics?: ReactNode;
448
461
  decay?: DecayConfig;
449
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;
450
468
  staleness?: StalenessConfig;
451
469
  thresholds?: ThresholdAlertConfig;
452
470
  orbitMode?: "flat" | "solar" | "atomic" | number[];
@@ -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 {
@@ -28,6 +29,8 @@ export interface WedgeSceneNode {
28
29
  _pulseColor?: string;
29
30
  _pulseGlowRadius?: number;
30
31
  _targetOpacity?: number;
32
+ _targetStartAngle?: number;
33
+ _targetEndAngle?: number;
31
34
  _transitionKey?: string;
32
35
  }
33
36
  export interface BoxplotSceneNode {
@@ -153,15 +156,17 @@ export interface OrdinalPipelineConfig {
153
156
  windowMode: WindowMode;
154
157
  extentPadding: number;
155
158
  projection: "vertical" | "horizontal" | "radial";
156
- oAccessor?: string | ((d: any) => string);
157
- 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)>;
158
161
  colorAccessor?: string | ((d: any) => string);
159
162
  stackBy?: string | ((d: any) => string);
160
163
  groupBy?: string | ((d: any) => string);
161
- multiAxis?: boolean;
162
164
  timeAccessor?: string | ((d: any) => number);
163
- valueAccessor?: string | ((d: any) => number);
164
- 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;
165
170
  rExtent?: [number?, number?];
166
171
  oExtent?: string[];
167
172
  barPadding?: number;
@@ -182,34 +187,41 @@ export interface OrdinalPipelineConfig {
182
187
  amplitude?: number;
183
188
  connectorOpacity?: number;
184
189
  showLabels?: boolean;
185
- oSort?: ((a: any, b: any) => number) | boolean | "asc" | "desc";
190
+ oSort?: ((a: string, b: string) => number) | boolean | "asc" | "desc" | "auto";
186
191
  connectorAccessor?: string | ((d: any) => string);
187
192
  connectorStyle?: Style | ((d: any) => Style);
188
193
  dynamicColumnWidth?: string | ((data: any[]) => number);
189
194
  pieceStyle?: (d: any, category?: string) => Style;
190
195
  summaryStyle?: (d: any, category?: string) => Style;
191
196
  colorScheme?: string | string[];
197
+ themeCategorical?: string[];
192
198
  barColors?: Record<string, string>;
193
199
  /** ID accessor for remove() — extracts a unique identifier from each datum */
194
200
  dataIdAccessor?: string | ((d: any) => string);
195
201
  decay?: DecayConfig;
196
202
  pulse?: PulseConfig;
197
203
  transition?: TransitionConfig;
204
+ /** Whether to animate elements on first render (bars grow from baseline, wedges sweep in) */
205
+ introAnimation?: boolean;
198
206
  staleness?: StalenessConfig;
199
207
  }
200
208
  export interface StreamOrdinalFrameProps<T = Record<string, any>> {
201
209
  chartType: OrdinalChartType;
202
210
  runtimeMode?: "bounded" | "streaming";
203
211
  data?: T[];
204
- oAccessor?: string | ((d: T) => string);
205
- 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)>;
206
216
  colorAccessor?: string | ((d: T) => string);
207
217
  stackBy?: string | ((d: T) => string);
208
218
  groupBy?: string | ((d: T) => string);
209
- multiAxis?: boolean;
210
219
  timeAccessor?: string | ((d: T) => number);
211
- valueAccessor?: string | ((d: T) => number);
212
- 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;
213
225
  projection?: "vertical" | "horizontal" | "radial";
214
226
  size?: [number, number];
215
227
  responsiveWidth?: boolean;
@@ -238,7 +250,7 @@ export interface StreamOrdinalFrameProps<T = Record<string, any>> {
238
250
  rExtent?: [number?, number?];
239
251
  oExtent?: string[];
240
252
  extentPadding?: number;
241
- oSort?: ((a: any, b: any) => number) | boolean | "asc" | "desc";
253
+ oSort?: ((a: string, b: string) => number) | boolean | "asc" | "desc" | "auto";
242
254
  arrowOfTime?: ArrowOfTime;
243
255
  windowMode?: WindowMode;
244
256
  windowSize?: number;
@@ -256,9 +268,21 @@ export interface StreamOrdinalFrameProps<T = Record<string, any>> {
256
268
  barColors?: Record<string, string>;
257
269
  showAxes?: boolean;
258
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 */
259
280
  oLabel?: string;
281
+ /** @deprecated Use valueLabel */
260
282
  rLabel?: string;
283
+ /** @deprecated Use categoryFormat */
261
284
  oFormat?: (d: string, index?: number) => string | ReactNode;
285
+ /** @deprecated Use valueFormat */
262
286
  rFormat?: (d: number | string) => string;
263
287
  enableHover?: boolean;
264
288
  hoverAnnotation?: boolean | HoverAnnotationConfig;
@@ -298,6 +322,10 @@ export interface StreamOrdinalFrameProps<T = Record<string, any>> {
298
322
  decay?: DecayConfig;
299
323
  pulse?: PulseConfig;
300
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;
301
329
  staleness?: StalenessConfig;
302
330
  /** Render a visually-hidden data table from the scene graph for screen readers */
303
331
  accessibleTable?: boolean;
@@ -309,6 +337,19 @@ export interface StreamOrdinalFrameProps<T = Record<string, any>> {
309
337
  export interface StreamOrdinalFrameHandle<T = Record<string, any>> {
310
338
  push(datum: T): void;
311
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;
312
353
  /** Remove data items by ID. Requires dataIdAccessor. */
313
354
  remove(id: string | string[]): T[];
314
355
  /** Update data items by ID in place. Requires dataIdAccessor. Returns previous values. */
@@ -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;