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
@@ -1,6 +1,7 @@
1
1
  import type React from "react";
2
2
  import type { MarginType } from "../../types/generalTypes";
3
3
  import type { OnObservationCallback } from "../../store/ObservationStore";
4
+ import type { AnimateProp } from "../../stream/pipelineTransitionUtils";
4
5
  /**
5
6
  * Selection consumption config — makes this chart react to a named selection
6
7
  */
@@ -103,12 +104,10 @@ export interface BaseChartProps {
103
104
  dataIdAccessor?: string | ((d: any) => string);
104
105
  /** Visual emphasis level for dashboard hierarchy. "primary" spans two columns in ChartGrid. */
105
106
  emphasis?: "primary" | "secondary";
106
- /** Enable declarative bounded animation (enter/exit/update transitions).
107
- * `true` uses defaults (300ms ease-out). Object form allows customization. */
108
- animate?: boolean | {
109
- duration?: number;
110
- easing?: "linear" | "ease-out";
111
- };
107
+ /** Enable declarative bounded animation (enter/exit/update transitions + intro).
108
+ * `true` uses defaults (300ms ease-out, intro enabled). Object form allows customization.
109
+ * Set `{ intro: false }` to disable the animated intro on first render. */
110
+ animate?: AnimateProp;
112
111
  }
113
112
  /**
114
113
  * Axis configuration props
@@ -104,6 +104,14 @@ export interface ChartSetupResult {
104
104
  linkedCrosshairName: string;
105
105
  linkedCrosshairSourceId: string;
106
106
  } | undefined;
107
+ /**
108
+ * Selection config merged with theme-level defaults. HOCs should pass this
109
+ * to `wrapStyleWithSelection` instead of the raw `selection` prop so that
110
+ * the current theme's `colors.selectionOpacity` becomes the effective
111
+ * unselected-opacity fallback. Per-chart `selection.unselectedOpacity`
112
+ * still takes priority over the theme value.
113
+ */
114
+ resolvedSelection: SelectionConfig | undefined;
107
115
  }
108
116
  /**
109
117
  * Hook that consolidates the shared boilerplate across all HOC charts:
@@ -0,0 +1,2 @@
1
+ import type { SelectionConfig } from "./types";
2
+ export declare function useResolvedSelection(selection: SelectionConfig | undefined): SelectionConfig | undefined;
@@ -64,11 +64,50 @@ export interface HoverAnnotationConfig {
64
64
  pointColor?: string;
65
65
  }
66
66
  export interface HoverData {
67
- data: Record<string, any>;
68
- time: number;
69
- value: number;
67
+ /** The raw datum from the user's data array (may be an object, array, or null for exit nodes) */
68
+ data: any;
69
+ /** Pixel X coordinate of the hovered element */
70
70
  x: number;
71
+ /** Pixel Y coordinate of the hovered element */
71
72
  y: number;
73
+ /** Pixel X coordinate, aliased as "time" for backwards compatibility with realtime charts */
74
+ time: number;
75
+ /** Pixel Y coordinate, aliased as "value" for backwards compatibility with realtime charts */
76
+ value: number;
77
+ /** All series values at hovered X (multi-point tooltip mode) */
78
+ allSeries?: Array<{
79
+ group: string;
80
+ value: number;
81
+ valuePx?: number;
82
+ color: string;
83
+ datum: any;
84
+ }>;
85
+ /** Pixel X of hover position (may differ from x for multi-point snap) */
86
+ xPx?: number;
87
+ /** Raw X domain value at hover position */
88
+ xValue?: any;
89
+ /** Distribution statistics for boxplot/violin/ridgeline */
90
+ stats?: {
91
+ n: number;
92
+ min: number;
93
+ q1: number;
94
+ median: number;
95
+ q3: number;
96
+ max: number;
97
+ mean: number;
98
+ };
99
+ /** Category label of hovered element */
100
+ category?: string;
101
+ /** @internal Category accessor name for tooltip rendering */
102
+ __oAccessor?: string;
103
+ /** @internal Value accessor name for tooltip rendering */
104
+ __rAccessor?: string;
105
+ /** @internal Chart type hint for tooltip rendering */
106
+ __chartType?: string;
107
+ /** Whether the hovered element is a node or edge */
108
+ nodeOrEdge?: "node" | "edge";
109
+ /** GeoJSON feature properties (flattened for convenience) */
110
+ properties?: Record<string, any>;
72
111
  }
73
112
  export interface BarStyle {
74
113
  fill?: string;
@@ -137,6 +176,18 @@ export interface RealtimeFrameHandle {
137
176
  update(id: string | string[], updater: (d: Record<string, any>) => Record<string, any>): Record<string, any>[];
138
177
  clear(): void;
139
178
  getData(): Record<string, any>[];
179
+ /** Returns the frame's resolved scales, or null if unavailable.
180
+ *
181
+ * The concrete scales object differs by frame type — XY charts
182
+ * expose `{ x, y }`, ordinal charts expose `{ o, r, projection }`,
183
+ * network/geo don't have a meaningful scale concept and may not
184
+ * implement this method at all.
185
+ *
186
+ * Typed as `unknown` so the shared handle stays compatible across
187
+ * chart families. HOCs that want a narrower return type should
188
+ * export a chart-specific handle (e.g. `LikertChartHandle`) that
189
+ * extends this interface and narrows `getScales()`. */
190
+ getScales?(): unknown | null;
140
191
  }
141
192
  export interface RealtimeScales {
142
193
  time: ScaleLinear<number, number>;
@@ -14,3 +14,7 @@ export { mergeData } from "./geo/mergeData";
14
14
  export { resolveReferenceGeography } from "./geo/referenceGeography";
15
15
  export type { ReferenceGeography } from "./geo/referenceGeography";
16
16
  export type { AreasProp } from "./geo/useReferenceAreas";
17
+ export type { ChoroplethMapProps } from "./charts/geo/ChoroplethMap";
18
+ export type { ProportionalSymbolMapProps } from "./charts/geo/ProportionalSymbolMap";
19
+ export type { FlowMapProps } from "./charts/geo/FlowMap";
20
+ export type { DistanceCartogramProps } from "./charts/geo/DistanceCartogram";
@@ -12,3 +12,10 @@ export { Treemap } from "./charts/network/Treemap";
12
12
  export { CirclePack } from "./charts/network/CirclePack";
13
13
  export { OrbitDiagram } from "./charts/network/OrbitDiagram";
14
14
  export type { StreamNetworkFrameProps, StreamNetworkFrameHandle, NetworkChartType, NetworkSceneNode, NetworkSceneEdge, NetworkLabel, ThresholdAlertConfig } from "./stream/networkTypes";
15
+ export type { ForceDirectedGraphProps } from "./charts/network/ForceDirectedGraph";
16
+ export type { SankeyDiagramProps } from "./charts/network/SankeyDiagram";
17
+ export type { ChordDiagramProps } from "./charts/network/ChordDiagram";
18
+ export type { TreeDiagramProps } from "./charts/network/TreeDiagram";
19
+ export type { TreemapProps } from "./charts/network/Treemap";
20
+ export type { CirclePackProps } from "./charts/network/CirclePack";
21
+ export type { OrbitDiagramProps } from "./charts/network/OrbitDiagram";
@@ -23,3 +23,11 @@ export { LikertChart } from "./charts/ordinal/LikertChart";
23
23
  export { createHatchPattern } from "./charts/shared/hatchPattern";
24
24
  export type { HatchPatternOptions } from "./charts/shared/hatchPattern";
25
25
  export type { StreamOrdinalFrameProps, StreamOrdinalFrameHandle, OrdinalChartType, OrdinalScales, OrdinalSceneNode } from "./stream/ordinalTypes";
26
+ export type { BarChartProps } from "./charts/ordinal/BarChart";
27
+ export type { StackedBarChartProps } from "./charts/ordinal/StackedBarChart";
28
+ export type { GroupedBarChartProps } from "./charts/ordinal/GroupedBarChart";
29
+ export type { SwimlaneChartProps } from "./charts/ordinal/SwimlaneChart";
30
+ export type { PieChartProps } from "./charts/ordinal/PieChart";
31
+ export type { DonutChartProps } from "./charts/ordinal/DonutChart";
32
+ export type { FunnelChartProps } from "./charts/ordinal/FunnelChart";
33
+ export type { LikertChartProps } from "./charts/ordinal/LikertChart";
@@ -16,3 +16,12 @@ export { MinimapChart } from "./charts/xy/MinimapChart";
16
16
  export { QuadrantChart } from "./charts/xy/QuadrantChart";
17
17
  export { MultiAxisLineChart } from "./charts/xy/MultiAxisLineChart";
18
18
  export type { StreamXYFrameProps, StreamXYFrameHandle } from "./stream/types";
19
+ export type { LineChartProps } from "./charts/xy/LineChart";
20
+ export type { AreaChartProps } from "./charts/xy/AreaChart";
21
+ export type { StackedAreaChartProps } from "./charts/xy/StackedAreaChart";
22
+ export type { ScatterplotProps } from "./charts/xy/Scatterplot";
23
+ export type { ConnectedScatterplotProps } from "./charts/xy/ConnectedScatterplot";
24
+ export type { BubbleChartProps } from "./charts/xy/BubbleChart";
25
+ export type { HeatmapProps } from "./charts/xy/Heatmap";
26
+ export type { QuadrantChartProps } from "./charts/xy/QuadrantChart";
27
+ export type { MultiAxisLineChartProps } from "./charts/xy/MultiAxisLineChart";
@@ -29,6 +29,7 @@ interface ThemeAwareProps {
29
29
  description?: string;
30
30
  background?: string;
31
31
  className?: string;
32
+ legendPosition?: "right" | "left" | "top" | "bottom";
32
33
  /** Prefix for SVG element IDs — used by renderDashboard to avoid collisions */
33
34
  _idPrefix?: string;
34
35
  }
@@ -38,7 +39,7 @@ export declare function renderOrdinalToStaticSVG(props: StreamOrdinalFrameProps
38
39
  export declare function renderNetworkToStaticSVG(props: StreamNetworkFrameProps & ThemeAwareProps): string;
39
40
  export declare function renderGeoToStaticSVG(props: StreamGeoFrameProps & ThemeAwareProps): string;
40
41
  /** Chart component name to frame type + props mapping */
41
- type ChartName = "LineChart" | "AreaChart" | "StackedAreaChart" | "Scatterplot" | "BubbleChart" | "ConnectedScatterplot" | "Heatmap" | "Sparkline" | "BarChart" | "StackedBarChart" | "GroupedBarChart" | "PieChart" | "DonutChart" | "SwimlaneChart" | "Histogram" | "BoxPlot" | "ViolinPlot" | "SwarmPlot" | "DotPlot" | "RidgelinePlot" | "FunnelChart" | "GaugeChart" | "ForceDirectedGraph" | "SankeyDiagram" | "ChordDiagram" | "TreeDiagram" | "Treemap" | "CirclePack" | "ChoroplethMap" | "ProportionalSymbolMap";
42
+ type ChartName = "LineChart" | "AreaChart" | "StackedAreaChart" | "Scatterplot" | "BubbleChart" | "ConnectedScatterplot" | "Heatmap" | "Sparkline" | "BarChart" | "StackedBarChart" | "GroupedBarChart" | "PieChart" | "DonutChart" | "SwimlaneChart" | "Histogram" | "BoxPlot" | "ViolinPlot" | "SwarmPlot" | "DotPlot" | "RidgelinePlot" | "LikertChart" | "FunnelChart" | "GaugeChart" | "ForceDirectedGraph" | "SankeyDiagram" | "ChordDiagram" | "TreeDiagram" | "Treemap" | "CirclePack" | "ChoroplethMap" | "ProportionalSymbolMap" | "FlowMap";
42
43
  interface RenderChartOptions {
43
44
  /** Output format — currently only "svg" is synchronous */
44
45
  format?: "svg";
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Chart-specific prop mapping for renderChart().
3
+ *
4
+ * Each entry maps HOC-level props (categoryAccessor, valueAccessor, etc.)
5
+ * to frame-level props (oAccessor, rAccessor, etc.) for server rendering.
6
+ *
7
+ * Extracted from renderToStaticSVG.tsx's 400-line switch statement to make
8
+ * each chart type independently readable and testable.
9
+ */
10
+ type FrameType = "xy" | "ordinal" | "network" | "geo";
11
+ interface ChartConfig {
12
+ frameType: FrameType;
13
+ /** Build frame props from HOC-level props */
14
+ buildProps: (data: any, colorBy: any, colorScheme: any, common: Record<string, any>, rest: Record<string, any>) => Record<string, any>;
15
+ }
16
+ export declare const CHART_CONFIGS: Record<string, ChartConfig>;
17
+ export {};
@@ -32,4 +32,8 @@ export declare function themeStyles(theme: SemioticTheme): {
32
32
  labelSize: number;
33
33
  tickSize: number;
34
34
  categorical: string[];
35
+ annotation: string;
36
+ legendSize: number;
37
+ titleFontSize: number;
38
+ tickFontFamily: string;
35
39
  };
@@ -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: {
@@ -16,12 +18,20 @@ export interface SemioticTheme {
16
18
  selection?: string;
17
19
  /** Opacity for non-selected (dimmed) elements, 0–1 */
18
20
  selectionOpacity?: number;
21
+ /** Default annotation text/marker color. Falls back to `text` if unset. */
22
+ annotation?: string;
19
23
  };
20
24
  typography: {
21
25
  fontFamily: string;
22
26
  titleSize: number;
23
27
  labelSize: number;
24
28
  tickSize: number;
29
+ /** Font size for legend text. Falls back to `labelSize` if unset. */
30
+ legendSize?: number;
31
+ /** Font family for axis tick labels. Use monospace for aligned numerics. Falls back to `fontFamily`. */
32
+ tickFontFamily?: string;
33
+ /** Font size for chart title. Falls back to `titleSize` if unset. */
34
+ titleFontSize?: number;
25
35
  };
26
36
  tooltip?: {
27
37
  background?: string;
@@ -31,6 +41,12 @@ export interface SemioticTheme {
31
41
  shadow?: string;
32
42
  };
33
43
  borderRadius?: string;
44
+ accessibility?: {
45
+ /** Auto-swap to color-blind safe palette when true */
46
+ colorBlindSafe?: boolean;
47
+ /** Enforce minimum 3:1 contrast ratios */
48
+ highContrast?: boolean;
49
+ };
34
50
  }
35
51
  /** Color-blind safe categorical palette (8 colors).
36
52
  * Derived from Wong (2011) "Points of view: Color blindness" — safe for
@@ -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;
@@ -145,6 +150,12 @@ export declare class NetworkPipelineStore {
145
150
  * Handles parallel edges (multiple edges between the same pair).
146
151
  * Returns true if at least one edge was removed.
147
152
  */
148
- removeEdge(sourceId: string, targetId: string): boolean;
153
+ /**
154
+ * Remove edges by source+target IDs, or by edge ID when edgeIdAccessor is configured.
155
+ *
156
+ * - `removeEdge(sourceId, targetId)` — removes all parallel edges between endpoints
157
+ * - `removeEdge(edgeId)` — removes the edge matching edgeIdAccessor (requires config.edgeIdAccessor)
158
+ */
159
+ removeEdge(sourceIdOrEdgeId: string, targetId?: string): boolean;
149
160
  clear(): void;
150
161
  }
@@ -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,7 +1,8 @@
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 { HoverAnnotationConfig } from "../realtime/types";
4
+ import type { AnimateProp } from "./pipelineTransitionUtils";
5
+ import type { HoverAnnotationConfig, HoverData } from "../realtime/types";
5
6
  import type { GeoParticleStyle } from "./GeoParticlePool";
6
7
  export type ProjectionProp = GeoProjection | ProjectionName | ProjectionConfig;
7
8
  export type ProjectionName = "mercator" | "equalEarth" | "albersUsa" | "orthographic" | "naturalEarth" | "equirectangular";
@@ -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
  }
@@ -139,13 +142,17 @@ export interface StreamGeoFrameProps<T = Record<string, any>> {
139
142
  colorScheme?: string | string[];
140
143
  enableHover?: boolean;
141
144
  hoverAnnotation?: boolean | HoverAnnotationConfig;
142
- tooltipContent?: (d: any) => ReactNode;
143
- customClickBehavior?: (d: any) => void;
144
- customHoverBehavior?: (d: any) => void;
145
+ tooltipContent?: (d: HoverData) => ReactNode;
146
+ customClickBehavior?: (d: HoverData | null) => void;
147
+ customHoverBehavior?: (d: HoverData | null) => void;
145
148
  annotations?: 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;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Shared hover data utilities for stream frames.
3
+ *
4
+ * Centralizes the common HoverData construction pattern used by
5
+ * XY, Ordinal, and Network stream frames for hover and click events.
6
+ * Geo frame and keyboard navigation use variants with additional
7
+ * property flattening that are handled at the call site.
8
+ */
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
+ }
22
+ /**
23
+ * Spread raw datum properties onto HoverData if it's a non-null,
24
+ * non-array object. Class instances (Date, etc.) are included —
25
+ * this matches the historical behavior where all datum fields are
26
+ * accessible directly on the hover object (d.fieldName).
27
+ */
28
+ export declare function spreadDatum(rawDatum: any): Record<string, any>;
29
+ /**
30
+ * Build a HoverData object from a raw datum and pixel coordinates.
31
+ * Spreads plain-object datum properties for backwards compatibility
32
+ * (consumers can access d.fieldName directly in addition to d.data.fieldName).
33
+ */
34
+ export declare function buildHoverData(rawDatum: any, x: number, y: number, extra?: Partial<HoverData>): HoverData;