@perspective-dev/viewer-charts 4.5.0 → 4.5.1

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 (73) hide show
  1. package/LICENSE.md +193 -0
  2. package/dist/cdn/perspective-viewer-charts.js +2 -2
  3. package/dist/cdn/perspective-viewer-charts.js.map +3 -3
  4. package/dist/esm/axis/bar-axis.d.ts +9 -1
  5. package/dist/esm/axis/categorical-axis.d.ts +0 -2
  6. package/dist/esm/charts/cartesian/cartesian.d.ts +26 -0
  7. package/dist/esm/charts/common/category-axis-resolver.d.ts +43 -1
  8. package/dist/esm/charts/common/expand-domain.d.ts +20 -0
  9. package/dist/esm/charts/common/tree-chart.d.ts +7 -0
  10. package/dist/esm/charts/common/tree-chrome.d.ts +23 -1
  11. package/dist/esm/charts/common/tree-interact.d.ts +46 -0
  12. package/dist/esm/charts/series/glyphs/draw-lines.d.ts +11 -4
  13. package/dist/esm/charts/series/series-build.d.ts +38 -2
  14. package/dist/esm/charts/series/series-render.d.ts +1 -4
  15. package/dist/esm/charts/series/series-type.d.ts +19 -17
  16. package/dist/esm/charts/series/series.d.ts +16 -0
  17. package/dist/esm/charts/sunburst/sunburst-interact.d.ts +1 -1
  18. package/dist/esm/charts/treemap/treemap-interact.d.ts +1 -6
  19. package/dist/esm/interaction/host-sink-message.d.ts +10 -28
  20. package/dist/esm/interaction/raw-event-forwarder.d.ts +6 -7
  21. package/dist/esm/interaction/zoom-controller.d.ts +31 -20
  22. package/dist/esm/interaction/zoom-router.d.ts +3 -26
  23. package/dist/esm/perspective-viewer-charts.js +2 -2
  24. package/dist/esm/perspective-viewer-charts.js.map +3 -3
  25. package/dist/esm/plugin/plugin.d.ts +0 -1
  26. package/dist/esm/theme/palette.d.ts +0 -5
  27. package/dist/esm/transport/protocol.d.ts +2 -7
  28. package/dist/esm/worker/renderer.worker.d.ts +2 -4
  29. package/package.json +45 -45
  30. package/src/ts/axis/bar-axis.ts +74 -45
  31. package/src/ts/axis/categorical-axis.ts +0 -2
  32. package/src/ts/charts/candlestick/candlestick-render.ts +10 -7
  33. package/src/ts/charts/candlestick/candlestick.ts +10 -29
  34. package/src/ts/charts/candlestick/glyphs/draw-candlesticks.ts +36 -2
  35. package/src/ts/charts/candlestick/glyphs/draw-ohlc.ts +36 -2
  36. package/src/ts/charts/cartesian/cartesian-build.ts +143 -9
  37. package/src/ts/charts/cartesian/cartesian-render.ts +205 -30
  38. package/src/ts/charts/cartesian/cartesian.ts +43 -4
  39. package/src/ts/charts/cartesian/glyphs/density.ts +36 -41
  40. package/src/ts/charts/cartesian/glyphs/lines.ts +13 -15
  41. package/src/ts/charts/cartesian/glyphs/points.ts +12 -17
  42. package/src/ts/charts/chart-base.ts +20 -6
  43. package/src/ts/charts/chart.ts +1 -1
  44. package/src/ts/charts/common/category-axis-resolver.ts +135 -1
  45. package/src/ts/charts/common/expand-domain.ts +40 -0
  46. package/src/ts/charts/common/tree-chart.ts +16 -0
  47. package/src/ts/charts/common/tree-chrome.ts +86 -1
  48. package/src/ts/charts/common/tree-interact.ts +209 -0
  49. package/src/ts/charts/heatmap/heatmap-render.ts +9 -11
  50. package/src/ts/charts/series/glyphs/draw-areas.ts +30 -1
  51. package/src/ts/charts/series/glyphs/draw-lines.ts +151 -76
  52. package/src/ts/charts/series/series-build.ts +394 -21
  53. package/src/ts/charts/series/series-render.ts +159 -38
  54. package/src/ts/charts/series/series-type.ts +37 -17
  55. package/src/ts/charts/series/series.ts +63 -68
  56. package/src/ts/charts/sunburst/sunburst-interact.ts +18 -162
  57. package/src/ts/charts/sunburst/sunburst-render.ts +24 -89
  58. package/src/ts/charts/sunburst/sunburst.ts +1 -15
  59. package/src/ts/charts/treemap/treemap-interact.ts +22 -189
  60. package/src/ts/charts/treemap/treemap-render.ts +19 -46
  61. package/src/ts/charts/treemap/treemap.ts +1 -16
  62. package/src/ts/interaction/host-sink-message.ts +33 -22
  63. package/src/ts/interaction/raw-event-forwarder.ts +10 -12
  64. package/src/ts/interaction/zoom-controller.ts +120 -83
  65. package/src/ts/interaction/zoom-router.ts +3 -126
  66. package/src/ts/map/tile-layer.ts +13 -13
  67. package/src/ts/plugin/plugin.ts +100 -184
  68. package/src/ts/shaders/line-uniform.frag.glsl +2 -1
  69. package/src/ts/shaders/line-uniform.vert.glsl +19 -0
  70. package/src/ts/theme/palette.ts +1 -4
  71. package/src/ts/transport/protocol.ts +3 -8
  72. package/src/ts/worker/dispatch.ts +0 -1
  73. package/src/ts/worker/renderer.worker.ts +10 -46
@@ -18,6 +18,14 @@ export type BarCategoryAxis = {
18
18
  domain: AxisDomain;
19
19
  ticks: number[];
20
20
  };
21
+ export type BarValueAxis = {
22
+ mode: "category";
23
+ domain: CategoricalDomain;
24
+ } | {
25
+ mode: "numeric";
26
+ domain: AxisDomain;
27
+ ticks: number[];
28
+ };
21
29
  /**
22
30
  * Render a numeric date-aware axis along the bottom of the plot. Aliases
23
31
  * the bar-axis bottom variant so heatmap can share the implementation.
@@ -42,7 +50,7 @@ export interface BarAxesFormatters {
42
50
  /** Formatter for the numeric category axis (when `catAxis.mode === "numeric"`). */
43
51
  category?: (v: number) => string;
44
52
  }
45
- export declare function renderBarAxesChrome(canvas: Canvas2D, catAxis: BarCategoryAxis, valueDomain: AxisDomain, valueTicks: number[], layout: PlotLayout, theme: Theme, dpr: number, altDomain?: AxisDomain, altTicks?: number[], isHorizontal?: boolean, formatters?: BarAxesFormatters): void;
53
+ export declare function renderBarAxesChrome(canvas: Canvas2D, catAxis: BarCategoryAxis, valueAxis: BarValueAxis, layout: PlotLayout, theme: Theme, dpr: number, altAxis: BarValueAxis | undefined, isHorizontal?: boolean, formatters?: BarAxesFormatters): void;
46
54
  /**
47
55
  * Render gridlines at the numeric axis ticks. In vertical bar charts
48
56
  * the gridlines run horizontally at numeric Y ticks; in horizontal bar
@@ -16,8 +16,6 @@ interface LevelTickLayout {
16
16
  size: number;
17
17
  rotation: 0 | 45 | 90;
18
18
  }
19
- declare function categoryIndexToPixelX(layout: PlotLayout, index: number): number;
20
- export declare const categoryIndexToPixel: typeof categoryIndexToPixelX;
21
19
  export declare function measureCategoricalLevels(domain: CategoricalDomain, plotWidth: number): LevelTickLayout[];
22
20
  export declare function measureCategoricalLevelWidths(domain: CategoricalDomain): number[];
23
21
  export declare function measureCategoricalAxisHeight(domain: CategoricalDomain, plotWidth: number): number;
@@ -4,6 +4,7 @@ import { AbstractChart } from "../chart-base";
4
4
  import { SpatialHitTester } from "../../interaction/hit-test";
5
5
  import { PlotLayout } from "../../layout/plot-layout";
6
6
  import { type AxisDomain } from "../../axis/numeric-axis";
7
+ import type { CategoricalDomain } from "../../axis/categorical-axis";
7
8
  import type { GradientTextureCache } from "../../webgl/gradient-texture";
8
9
  import type { Glyph } from "./glyph";
9
10
  import type { LabelInterner } from "./label-interner";
@@ -93,6 +94,31 @@ export declare class CartesianChart extends AbstractChart {
93
94
  _sizeName: string;
94
95
  _labelName: string;
95
96
  _colorIsString: boolean;
97
+ /**
98
+ * When the X (or Y) axis source column is post-aggregation
99
+ * `string`-typed, the build pipeline writes per-row dictionary slot
100
+ * indices into `_xData` (or `_yData`) instead of numeric values,
101
+ * and the render pass dispatches `renderCategoricalXTicks` /
102
+ * `renderCategoricalYTicks` instead of the numeric axis painter.
103
+ *
104
+ * The companion `_xCategoryDictionary` / `_xCategorySeen` pair is
105
+ * built lazily during `processCartesianChunk` in first-seen row
106
+ * order; `(null)` is appended on first encounter of an invalid row
107
+ * rather than reserved at slot 0, so charts without missing values
108
+ * don't get a phantom slot.
109
+ *
110
+ * `_xCategoryDomain` is materialized once per frame in
111
+ * `cartesian-render` and held for chrome overlay redraws (same
112
+ * lifecycle as `_lastXDomain` on the numeric path).
113
+ */
114
+ _xIsString: boolean;
115
+ _yIsString: boolean;
116
+ _xCategoryDictionary: string[];
117
+ _yCategoryDictionary: string[];
118
+ _xCategorySeen: Map<string, number>;
119
+ _yCategorySeen: Map<string, number>;
120
+ _xCategoryDomain: CategoricalDomain | null;
121
+ _yCategoryDomain: CategoricalDomain | null;
96
122
  _splitGroups: SplitGroup[];
97
123
  _xMin: number;
98
124
  _xMax: number;
@@ -1,5 +1,5 @@
1
1
  import type { ColumnDataMap, ColumnData } from "../../data/view-reader";
2
- import type { CategoricalLevel } from "../../axis/categorical-axis";
2
+ import type { CategoricalDomain, CategoricalLevel } from "../../axis/categorical-axis";
3
3
  export interface CategoryAxisResult {
4
4
  /**
5
5
  * Fully materialized hierarchical levels — labels and group runs are
@@ -88,3 +88,45 @@ export declare function synthesizeStringLevel(rp: ColumnData, numRows: number, l
88
88
  * from `rowPaths.length === 0`.
89
89
  */
90
90
  export declare function resolveCategoryAxis(columns: ColumnDataMap, numRows: number, groupByLen: number, levelTypes?: string[]): CategoryAxisResult;
91
+ export interface ValueCategoryColumn {
92
+ /**
93
+ * Source aggregate column name; used only for the axis label fallback.
94
+ */
95
+ name: string;
96
+ /**
97
+ * Post-aggregation perspective type string from `chart._columnTypes`
98
+ * (`"string"` is what triggers categorical mode).
99
+ */
100
+ type: string;
101
+ /**
102
+ * The actual `ColumnData` from the view. May be undefined when the
103
+ * caller couldn't resolve the column (treated as all-null).
104
+ */
105
+ data: ColumnData | undefined;
106
+ }
107
+ export interface ValueCategoryDomain {
108
+ /**
109
+ * Single-level `CategoricalDomain` shared across all input columns.
110
+ * `levels[0].labels` is the dictionary in slot order.
111
+ */
112
+ domain: CategoricalDomain;
113
+ /**
114
+ * Per-column slot-index buffers. Length === `numCategories`.
115
+ * Indexed in the same order as the input `columns` array.
116
+ */
117
+ perColumnSlots: Int32Array[];
118
+ }
119
+ /**
120
+ * Build a single shared categorical domain across one or more aggregate
121
+ * columns that land on the same axis side (primary or alt). Implements
122
+ * the "all-or-nothing per axis side" rule: returns `null` (= caller stays
123
+ * numeric) when any column is non-string; otherwise returns a single-
124
+ * level domain with the dictionary built in first-seen row order plus
125
+ * per-column slot indices the build pipeline writes into its pixel/slot
126
+ * buffer.
127
+ *
128
+ * Null / invalid rows surface as a `"(null)"` slot that's lazily added
129
+ * to the dictionary on first encounter — no reserved slot 0 when the
130
+ * data has no missing values.
131
+ */
132
+ export declare function resolveValueCategoryDomain(columns: ValueCategoryColumn[], numRows: number, rowOffset: number, axisLabel: string): ValueCategoryDomain | null;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Numeric extent — used by series + candlestick build pipelines for
3
+ * value / category axis domains.
4
+ */
5
+ export interface Domain {
6
+ min: number;
7
+ max: number;
8
+ }
9
+ /**
10
+ * Union `next` (a freshly-computed extent) with `prev` (the prior
11
+ * accumulator) IN PLACE on `next`, then return a fresh copy to store
12
+ * back as the new accumulator. Idempotent when `prev` is null — `next`
13
+ * is left untouched.
14
+ *
15
+ * Used by the `domain_mode: "expand"` mirror-back step in the series /
16
+ * candlestick / cartesian build pipelines: mutating `next` in place
17
+ * means every downstream assignment that reads from the pipeline
18
+ * result struct automatically picks up the grown extent.
19
+ */
20
+ export declare function expandDomainInPlace(prev: Domain | null, next: Domain): Domain;
@@ -1,6 +1,13 @@
1
1
  import { AbstractChart } from "../chart-base";
2
+ import type { ColumnDataMap } from "../../data/view-reader";
2
3
  import { NodeStore } from "./node-store";
3
4
  import { LazyTooltip } from "../../interaction/lazy-tooltip";
5
+ /**
6
+ * Sentinel fallback for the Size slot when the user hasn't picked one:
7
+ * use the first non-metadata column in the incoming view. Tree charts
8
+ * still need *some* numeric-ish column to size geometry.
9
+ */
10
+ export declare function firstNonMetadataColumn(columns: ColumnDataMap): string;
4
11
  /**
5
12
  * Shared state for hierarchical charts (treemap, sunburst). Holds the
6
13
  * tree store + streaming-insert scaffolding + per-row tooltip data
@@ -1,4 +1,8 @@
1
- import type { Context2D } from "../canvas-types";
1
+ import type { Canvas2D, Context2D } from "../canvas-types";
2
+ import type { PlotRect } from "../../layout/plot-layout";
3
+ import type { GradientStop } from "../../theme/gradient";
4
+ import type { Vec3 } from "../../theme/palette";
5
+ import type { Theme } from "../../theme/theme";
2
6
  import type { TreeChartBase } from "./tree-chart";
3
7
  /**
4
8
  * Click target for one breadcrumb segment. Tree-chart hit-testing
@@ -29,3 +33,21 @@ export declare function renderBreadcrumbs(chart: TreeChartBase & {
29
33
  * (arc-mid) charts only differ in how they compute the anchor.
30
34
  */
31
35
  export declare function renderTreeTooltip(chart: TreeChartBase, ctx: Context2D, nodeId: number, cx: number, cy: number, cssWidth: number, cssHeight: number, fontFamily: string): void;
36
+ /**
37
+ * Paint a color legend (categorical swatches or numeric gradient bar)
38
+ * for a tree chart. Shared by sunburst + treemap; both consult
39
+ * `_colorMode` / `_uniqueColorLabels.size` / `_colorMin..max` the same
40
+ * way.
41
+ *
42
+ * `categoricalRect`, when non-null, is used as the explicit rect for
43
+ * the categorical-swatch variant (sunburst's faceted mode passes
44
+ * `FacetGrid.legendRect` here). Numeric mode always derives from a
45
+ * synthetic single-plot `PlotLayout` to match the legacy per-chart
46
+ * branch — its gradient bar's vertical span doesn't fit the
47
+ * categorical legend's compact rect.
48
+ *
49
+ * Returns silently when the color slot is empty, when categorical mode
50
+ * has only one label, or when numeric mode has a degenerate
51
+ * (`min >= max`) extent.
52
+ */
53
+ export declare function renderTreeColorLegend(chart: TreeChartBase, canvas: Canvas2D, palette: Vec3[], stops: GradientStop[], theme: Theme, cssWidth: number, cssHeight: number, categoricalRect?: PlotRect | null): void;
@@ -0,0 +1,46 @@
1
+ import type { TreeChartBase } from "./tree-chart";
2
+ /**
3
+ * Common subset of `TreemapChart` / `SunburstChart` reached by the
4
+ * shared interaction helpers — anything that lives on `TreeChartBase`
5
+ * plus the pinned/hover/facet-drill state the two charts both declare
6
+ * with identical shape but on the subclass (so we type it as an
7
+ * intersection).
8
+ */
9
+ export type TreeInteractChart = TreeChartBase & {
10
+ _pinnedNodeId: number;
11
+ _hoveredNodeId: number;
12
+ _facetDrillRoots: Map<string, number>;
13
+ };
14
+ /**
15
+ * Emit `perspective-click` + `perspective-global-filter selected:true`
16
+ * for a treemap/sunburst node. The path is walked via `ancestorNames`
17
+ * and split into split-by prefix + group-by levels using
18
+ * `_splitBy.length` as the boundary; faceted mode keeps the depth-0
19
+ * ancestor as the split prefix.
20
+ */
21
+ export declare function emitTreeNodeEvent(chart: TreeInteractChart, nodeId: number, kind: "leaf" | "branch"): Promise<void>;
22
+ /**
23
+ * Build tooltip lines for `nodeId`: ancestor name path + aggregate
24
+ * value + (numeric) color value + per-row tooltip columns from
25
+ * `_lazyRows` for leaves. The leaf branch awaits the source-view row
26
+ * fetch; branch nodes have no underlying row so they emit a Children
27
+ * count instead.
28
+ */
29
+ export declare function buildTreeTooltipLines(chart: TreeInteractChart, nodeId: number): Promise<string[]>;
30
+ /**
31
+ * Pin a tooltip at the chart-supplied anchor. Lines are fetched lazily;
32
+ * the `_pinnedNodeId` check on resolve discards stale results from a
33
+ * prior pin or dismissal.
34
+ */
35
+ export declare function showTreePinnedTooltip(chart: TreeInteractChart, nodeId: number, anchor: {
36
+ cx: number;
37
+ cy: number;
38
+ }, renderChromeOverlay: () => void): void;
39
+ export declare function dismissTreePinnedTooltip(chart: TreeInteractChart): void;
40
+ /**
41
+ * Drill the clicked facet (or the whole chart in non-facet mode).
42
+ * Faceted drill walks up to the facet root (top-level child of
43
+ * `_rootId`), records the new drill node under that facet's label, and
44
+ * re-renders.
45
+ */
46
+ export declare function treeDrillTo(chart: TreeInteractChart, nodeId: number, renderFrame: () => void): void;
@@ -19,14 +19,21 @@ export declare class LineGlyph {
19
19
  * Rebuild the per-series GPU buffers for line glyphs. Called once
20
20
  * per data load (and once after `restyle()` because palette colors
21
21
  * are captured on the {@link LineSeriesEntry}). The buffer contents
22
- * encode `[x,y]` points in run-major order; one `bufferData` per
23
- * series. After this, every `draw` call rebinds + dispatches with
24
- * no further uploads until the next data load.
22
+ * encode `[x,y]` points for every cat in `[start, end]`; one
23
+ * `bufferData` per series. After this, every `draw` call rebinds +
24
+ * dispatches with no further uploads until the next data load.
25
+ *
26
+ * Gap behavior at synthesized cells is handled in the shader via
27
+ * `u_interp_alpha` (set per draw based on the series'
28
+ * `interpolateMode`): `skip` → 0 (invisible segments touching a
29
+ * synthesized endpoint), `solid` → 1, `transparent` → 0.5.
25
30
  */
26
31
  rebuildBuffers(chart: SeriesChart, glManager: WebGLContextManager): void;
27
32
  /**
28
33
  * Bind the persistent vertex buffers and dispatch one instanced draw
29
- * per (series, run). Skips hidden series via `_hiddenSeries`.
34
+ * per series. Skips hidden series via `_hiddenSeries`. Gap /
35
+ * transparency rendering is governed by `u_interp_alpha`, set per
36
+ * series.
30
37
  */
31
38
  draw(chart: SeriesChart, gl: GL, glManager: WebGLContextManager, projLeft: Float32Array, projRight: Float32Array): void;
32
39
  destroy(chart: SeriesChart): void;
@@ -1,7 +1,7 @@
1
1
  import type { ColumnDataMap } from "../../data/view-reader";
2
- import type { CategoricalLevel } from "../../axis/categorical-axis";
2
+ import type { CategoricalDomain, CategoricalLevel } from "../../axis/categorical-axis";
3
3
  import { type AxisMode, type NumericCategoryDomain } from "../common/category-axis-resolver";
4
- import { type ChartType, type ColumnChartConfig } from "./series-type";
4
+ import { type ChartType, type ColumnChartConfig, type InterpolateMode } from "./series-type";
5
5
  export interface SeriesInfo {
6
6
  seriesId: number;
7
7
  aggIdx: number;
@@ -13,6 +13,24 @@ export interface SeriesInfo {
13
13
  axis: 0 | 1;
14
14
  chartType: ChartType;
15
15
  stack: boolean;
16
+ /**
17
+ * First / last category index this series contributes data to, in
18
+ * the post-Pass-2 sample grid. For line+any mode and area+solid:
19
+ * every cell in `[start, end]` has a value (real or synthesized).
20
+ * For area+skip: `[start, end]` is the real-data extent; interior
21
+ * cells with `sampleValid=0` are gaps. `start = -1` (with `end = -1`)
22
+ * means the series has no real samples — downstream skips it.
23
+ */
24
+ start: number;
25
+ end: number;
26
+ /**
27
+ * Resolved interpolation mode for this aggregate. The build
28
+ * pipeline reads it to decide whether Pass 2 runs for area
29
+ * (and which fills to apply); the line glyph reads it at draw
30
+ * time to set `u_interp_alpha`. Always one of the three modes;
31
+ * never the legacy boolean form.
32
+ */
33
+ interpolateMode: InterpolateMode;
16
34
  }
17
35
  /**
18
36
  * Logical bar/area record. Synthesized on demand from {@link BarColumns}
@@ -215,6 +233,24 @@ export interface SeriesPipelineResult {
215
233
  max: number;
216
234
  } | null;
217
235
  hasRightAxis: boolean;
236
+ /**
237
+ * Per-axis-side value mode discriminator. `"category"` fires when
238
+ * every aggregate on that side is post-aggregation `string`-typed
239
+ * (all-or-nothing rule). Bar y0/y1 then hold dictionary slot
240
+ * indices and the chrome overlay paints a categorical axis on
241
+ * that side. `null` for the alt side when there are no series
242
+ * pinned to alt.
243
+ */
244
+ leftValueAxisMode: "numeric" | "category";
245
+ rightValueAxisMode: "numeric" | "category" | null;
246
+ /**
247
+ * Single-level `CategoricalDomain` shared across every aggregate
248
+ * on the corresponding side. Set only when that side's mode is
249
+ * `"category"`; the chrome renderer in `series-render` materializes
250
+ * the side's `BarCategoryAxis` from this.
251
+ */
252
+ leftValueCategoryDomain: CategoricalDomain | null;
253
+ rightValueCategoryDomain: CategoricalDomain | null;
218
254
  }
219
255
  /**
220
256
  * Pure pipeline: turn a raw `ColumnDataMap` into (a) columnar stacked
@@ -1,10 +1,7 @@
1
1
  import type { WebGLContextManager } from "../../webgl/context-manager";
2
2
  import { type SeriesChart } from "./series";
3
3
  /**
4
- * Upload bar instance buffers from the columnar `_bars` storage. Filters
5
- * to bar-typed records only (areas draw as triangle strips). Skips
6
- * hidden series. Re-called from data-load and legend-toggle paths; the
7
- * scratch buffers and `_visibleBarIndices` are reused across calls.
4
+ * Upload bar instance buffers from the columnar `_bars` storage.
8
5
  */
9
6
  export declare function uploadBarInstances(chart: SeriesChart, glManager: WebGLContextManager): void;
10
7
  /**
@@ -1,9 +1,10 @@
1
1
  export type ChartType = "bar" | "line" | "scatter" | "area";
2
2
  /**
3
- * Per-column entry inside the viewer's `columns_config` map. The map itself
4
- * is typed as `Record<string, any>` at the plugin boundary because
5
- * `columns_config` is shared across plugins; this interface documents the
6
- * keys the Y-bar glyph router consumes.
3
+ * Per-column interpolation mode for line / area glyphs.
4
+ */
5
+ export type InterpolateMode = "skip" | "solid" | "transparent";
6
+ /**
7
+ * Per-column entry inside the viewer's `columns_config` map.
7
8
  */
8
9
  export interface ColumnChartConfig {
9
10
  /**
@@ -11,39 +12,40 @@ export interface ColumnChartConfig {
11
12
  */
12
13
  chart_type?: string;
13
14
  /**
14
- * Explicit stack override. If omitted: bar / area stack by default,
15
- * line / scatter do not.
15
+ * Explicit stack override.
16
16
  */
17
17
  stack?: boolean;
18
18
  /**
19
19
  * Force this aggregate onto the secondary (right) Y axis,
20
20
  * independent of `autoAltYAxis` and the dual-axis ratio
21
- * heuristic. Missing / false → axis assignment is driven by
22
- * `autoAltYAxis` alone.
21
+ * heuristic.
23
22
  */
24
23
  alt_axis?: boolean;
24
+ /**
25
+ * Interpolation mode for line / area glyphs. See
26
+ * {@link InterpolateMode}. Legacy values `true` / `false` are also
27
+ * accepted by {@link resolveInterpolate} (mapped to `"solid"` /
28
+ * `"skip"`). Default `"skip"`. No effect on bar / scatter.
29
+ */
30
+ interpolate?: InterpolateMode;
25
31
  }
26
32
  /**
27
33
  * Resolve the render glyph for an aggregate base name. Lookup key is the
28
34
  * *base* (e.g. `"Sales"`); composite arrow columns like `"North|Sales"`
29
35
  * should strip the prefix before calling — the bar pipeline already
30
36
  * tracks aggregates as base names, so call sites pass the base directly.
31
- *
32
- * `fallback` is the plugin's default glyph (e.g. `"line"` for Y Line),
33
- * supplied by the plugin element via `setDefaultChartType`. Falls back to
34
- * `"bar"` when the plugin never set one.
35
37
  */
36
38
  export declare function resolveChartType(aggName: string, cfg: Record<string, ColumnChartConfig> | undefined, fallback?: ChartType): ChartType;
37
39
  /**
38
40
  * Resolve whether a series stacks with its aggregate siblings.
39
- * Default: `true` for bar/area, `false` for line/scatter. Overridable
40
- * per column via `columns_config[aggName].stack`.
41
41
  */
42
42
  export declare function resolveStack(aggName: string, chartType: ChartType, cfg: Record<string, ColumnChartConfig> | undefined): boolean;
43
43
  /**
44
44
  * Resolve whether a column is pinned to the secondary Y axis via
45
- * `columns_config[aggName].alt_axis`. Independent of `autoAltYAxis`:
46
- * when `true`, the per-column override forces axis 1 regardless of
47
- * the auto-split heuristic.
45
+ * `columns_config[aggName].alt_axis`.
48
46
  */
49
47
  export declare function resolveAltAxis(aggName: string, cfg: Record<string, ColumnChartConfig> | undefined): boolean;
48
+ /**
49
+ * Resolve the interpolation mode for this aggregate.
50
+ */
51
+ export declare function resolveInterpolate(aggName: string, chartType: ChartType, cfg: Record<string, ColumnChartConfig> | undefined): InterpolateMode;
@@ -4,6 +4,7 @@ import type { ZoomConfig } from "../../interaction/zoom-controller";
4
4
  import { CategoricalYChart } from "../common/categorical-y-chart";
5
5
  import { type PlotRect } from "../../layout/plot-layout";
6
6
  import { type AxisDomain } from "../../axis/numeric-axis";
7
+ import type { CategoricalDomain } from "../../axis/categorical-axis";
7
8
  import { type SeriesChartRecord, type NumericCategoryDomain, type SeriesInfo, type BarColumns } from "./series-build";
8
9
  import { LineGlyph } from "./glyphs/draw-lines";
9
10
  import { ScatterGlyph } from "./glyphs/draw-scatter";
@@ -90,6 +91,21 @@ export declare class SeriesChart extends CategoricalYChart {
90
91
  */
91
92
  _primaryValueLabel: string;
92
93
  _altValueLabel: string;
94
+ /**
95
+ * Per-side value-axis mode. `"category"` fires when every
96
+ * aggregate on that side is post-aggregation `string`-typed
97
+ * (all-or-nothing rule, evaluated independently for primary and
98
+ * alt). When set, `_bars[].y0`/`y1` carry dictionary slot indices
99
+ * instead of numeric values, and the chrome overlay paints a
100
+ * categorical axis on that side.
101
+ *
102
+ * Read by `series-render.ts` to construct the `BarCategoryAxis`
103
+ * descriptor for the value-axis sides.
104
+ */
105
+ _leftValueAxisMode: "numeric" | "category";
106
+ _rightValueAxisMode: "numeric" | "category" | null;
107
+ _leftValueCategoryDomain: CategoricalDomain | null;
108
+ _rightValueCategoryDomain: CategoricalDomain | null;
93
109
  /**
94
110
  * (seriesId * 1e9 + catIdx) → bar-record index in `_bars`. Built once
95
111
  * per pipeline run for area-strip lookups; rebuilt on hidden-toggle
@@ -4,4 +4,4 @@ export declare function handleSunburstHover(chart: SunburstChart, mx: number, my
4
4
  export declare function handleSunburstClick(chart: SunburstChart, mx: number, my: number): void;
5
5
  export declare function showSunburstPinnedTooltip(chart: SunburstChart, nodeId: number): void;
6
6
  export declare function dismissSunburstPinnedTooltip(chart: SunburstChart): void;
7
- export declare function buildSunburstTooltipLines(chart: SunburstChart, nodeId: number): Promise<string[]>;
7
+ export { buildTreeTooltipLines as buildSunburstTooltipLines } from "../common/tree-interact";
@@ -4,9 +4,4 @@ export declare function handleTreemapClick(chart: TreemapChart, mx: number, my:
4
4
  export declare function handleTreemapDblClick(chart: TreemapChart, mx: number, my: number): void;
5
5
  export declare function showTreemapPinnedTooltip(chart: TreemapChart, nodeId: number): void;
6
6
  export declare function dismissTreemapPinnedTooltip(chart: TreemapChart): void;
7
- /**
8
- * Build the tooltip for `nodeId`. The node's own name path + aggregate
9
- * value are derived from the tree; per-row tooltip columns come from
10
- * the `leafRowIdx` → column-buffer lookup (no per-node `Map`).
11
- */
12
- export declare function buildTreemapTooltipLines(chart: TreemapChart, nodeId: number): Promise<string[]>;
7
+ export { buildTreeTooltipLines as buildTreemapTooltipLines } from "../common/tree-interact";
@@ -1,36 +1,18 @@
1
+ import type { DismissTooltipMsg, PinTooltipMsg, SetCursorMsg, UserClickMsg, UserSelectMsg } from "../transport/protocol";
1
2
  import type { CssBounds, HostSink, UserClickPayload, UserSelectPayload } from "./tooltip-controller";
2
3
  /**
3
- * Envelope shape sent by `MessageHostSink`. The transport translates
4
- * each one into a corresponding `WorkerMsg` (`pinTooltip` /
5
- * `dismissTooltip` / `setCursor` / `userClick` / `userSelect`).
4
+ * The subset of `WorkerMsg`s that flow chart → host through a
5
+ * `MessageHostSink`. Identical to the worker-side post payloads so the
6
+ * sink can ship them straight to `WorkerRenderer.post` with no
7
+ * intermediate translation.
6
8
  */
7
- export type HostSinkEnvelope = {
8
- kind: "pin";
9
- payload: {
10
- lines: string[];
11
- pos: {
12
- px: number;
13
- py: number;
14
- };
15
- bounds: CssBounds;
16
- };
17
- } | {
18
- kind: "dismiss";
19
- } | {
20
- kind: "setCursor";
21
- cursor: string;
22
- } | {
23
- kind: "userClick";
24
- payload: UserClickPayload;
25
- } | {
26
- kind: "userSelect";
27
- payload: UserSelectPayload;
28
- };
9
+ export type HostSinkEnvelope = PinTooltipMsg | DismissTooltipMsg | SetCursorMsg | UserClickMsg | UserSelectMsg;
29
10
  /**
30
11
  * `HostSink` that posts pin / dismiss / setCursor / user-event intents
31
- * over a `postMessage`-style channel. The host-side transport listens
32
- * for these envelopes and drives a `DomHostSink` for pin/dismiss and
33
- * dispatches `CustomEvent`s on the viewer for user events.
12
+ * over a `postMessage`-style channel as `WorkerMsg`s. The host-side
13
+ * transport listens for these and drives a `DomHostSink` for
14
+ * pin/dismiss and dispatches `CustomEvent`s on the viewer for user
15
+ * events.
34
16
  */
35
17
  export declare class MessageHostSink implements HostSink {
36
18
  private _send;
@@ -1,15 +1,14 @@
1
1
  import type { InteractionEvent } from "../transport/protocol";
2
2
  /**
3
- * Worker-mode counterpart to {@link ZoomRouter}. Captures wheel /
4
- * pointer events on the GL canvas, normalizes coords to canvas-relative
5
- * CSS pixels, and emits semantic {@link InteractionEvent}s to the
6
- * Renderer over its transport. The Renderer (running in a Web Worker)
7
- * owns the `ZoomController`s and runs the actual hit-test + apply
8
- * logic — see `applyWheel` / `applyPan` in `zoom-router.ts`.
3
+ * Captures wheel / pointer events on the GL canvas, normalizes coords
4
+ * to canvas-relative CSS pixels, and emits semantic
5
+ * {@link InteractionEvent}s to the Renderer over its transport. The
6
+ * Renderer owns the `ZoomController`s and runs the actual hit-test +
7
+ * apply logic — see `applyWheel` / `applyPan` in `zoom-router.ts`.
9
8
  *
10
9
  * Pointer capture is set on `pointerdown` and released on `pointerup`
11
10
  * so drags continue to deliver `pointermove` events when the cursor
12
- * leaves the canvas, matching the in-process `ZoomRouter` behavior.
11
+ * leaves the canvas.
13
12
  */
14
13
  export declare class RawEventForwarder {
15
14
  private _element;
@@ -37,6 +37,8 @@ export declare class ZoomController {
37
37
  private _baseYMax;
38
38
  private _lockAxis;
39
39
  private _lockAspect;
40
+ private _xPinned;
41
+ private _yPinned;
40
42
  private _element;
41
43
  private _layout;
42
44
  private _onUpdate;
@@ -59,30 +61,37 @@ export declare class ZoomController {
59
61
  get baseXRange(): number;
60
62
  get baseYRange(): number;
61
63
  /**
62
- * Update the base (full-data) domain that this controller's
63
- * normalized translate is interpreted against, while preserving
64
- * the *absolute* center of any user-applied pan.
64
+ * Update the base (full-data) domain. Each axis is handled
65
+ * independently:
65
66
  *
66
- * `_normTX` / `_normTY` are stored as fractions of the base
67
- * range, not absolute coordinates. A naive "swap base, keep
68
- * normTranslate" update reinterprets the same fraction against a
69
- * new range, so when an external `draw()` updates the extent
70
- * (via `processCartesianChunk` `setZoomBaseDomain`) the user's
71
- * pan-offset visible center jumps to a different absolute
72
- * position. With concurrent pan events feeding in offsets that
73
- * were computed against the old base, the jump can project the
74
- * visible center past the data entirely, leaving `_fullRender`
75
- * to draw zero glyphs onto a freshly-cleared canvas — a blank
76
- * bitmap reaches the host as a flicker.
67
+ * - If the axis is at default (no user pan or zoom on this axis),
68
+ * just swap the new base in no further math needed.
69
+ * - If the axis has been explicitly *pinned* (`pinAxis("x" | "y")`),
70
+ * preserve its *absolute* visible center across the swap:
71
+ * re-solve `_normT` so the data-space center is unchanged.
72
+ * This is paused-frame-review semantics the user has marked a
73
+ * region of interest and wants it to stay put even as new data
74
+ * flows in. No current caller pins, but the API is here so the
75
+ * default rule below doesn't bake the choice in.
76
+ * - Otherwise (the default "follow"): keep `_normT` as-is and
77
+ * just swap the new base. The visible window's *fractional*
78
+ * position is preserved, so sliding windows slide with the data
79
+ * and extending windows grow proportionally with the data.
77
80
  *
78
- * When the user is in default state (no pan, no zoom — fresh
79
- * controller, or just-reset), no rebase is needed; just swap the
80
- * base and let the chart auto-fit to the new data. Otherwise
81
- * recompute `_normTX` / `_normTY` so the visible center stays at
82
- * the same absolute (data-coordinate) position before and after
83
- * the swap.
81
+ * Per-axis handling matters because `_scaleX/_normTX` and
82
+ * `_scaleY/_normTY` are independent. A user who panned X to scroll
83
+ * through time should not force Y onto the rebase path — Y data
84
+ * updates should still flow through cleanly.
84
85
  */
85
86
  setBaseDomain(xMin: number, xMax: number, yMin: number, yMax: number): void;
87
+ /**
88
+ * Mark an axis as "pinned" so subsequent `setBaseDomain` calls
89
+ * preserve its *absolute* visible center (paused-frame-review
90
+ * semantics). Default-cleared on construction; both axes follow
91
+ * data growth fractionally until explicitly pinned.
92
+ */
93
+ pinAxis(axis: "x" | "y"): void;
94
+ unpinAxis(axis: "x" | "y"): void;
86
95
  /**
87
96
  * Apply config. Called once by the chart during `setZoomController`.
88
97
  * Locking an axis snaps its `scale`/`translate` to identity so any
@@ -90,6 +99,8 @@ export declare class ZoomController {
90
99
  * pan events leave the locked axis alone.
91
100
  */
92
101
  configure(config: ZoomConfig): void;
102
+ isXDefault(): boolean;
103
+ isYDefault(): boolean;
93
104
  isDefault(): boolean;
94
105
  getVisibleDomain(): {
95
106
  xMin: number;