@perspective-dev/viewer-charts 5.3.0 → 5.3.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 (92) hide show
  1. package/dist/cdn/perspective-viewer-charts.js +1 -2
  2. package/dist/cdn/perspective-viewer-charts.js.map +4 -4
  3. package/dist/esm/axis/legend.d.ts +53 -5
  4. package/dist/esm/axis/map-ticks.d.ts +15 -0
  5. package/dist/esm/charts/candlestick/candlestick-interact.d.ts +2 -6
  6. package/dist/esm/charts/cartesian/cartesian.d.ts +6 -0
  7. package/dist/esm/charts/cartesian/glyph.d.ts +2 -1
  8. package/dist/esm/charts/cartesian/glyphs/density.d.ts +2 -1
  9. package/dist/esm/charts/cartesian/glyphs/lines.d.ts +2 -1
  10. package/dist/esm/charts/cartesian/glyphs/points.d.ts +2 -1
  11. package/dist/esm/charts/cartesian/tooltip-lines.d.ts +1 -1
  12. package/dist/esm/charts/chart-base.d.ts +2 -0
  13. package/dist/esm/charts/chart.d.ts +77 -6
  14. package/dist/esm/charts/common/draw-tooltip-box.d.ts +2 -1
  15. package/dist/esm/charts/common/tree-interact.d.ts +1 -1
  16. package/dist/esm/charts/series/series-build.d.ts +7 -0
  17. package/dist/esm/charts/series/series-interact.d.ts +1 -1
  18. package/dist/esm/charts/series/series.d.ts +6 -0
  19. package/dist/esm/index.d.ts +2 -0
  20. package/dist/esm/interaction/axis-indicators.d.ts +11 -0
  21. package/dist/esm/interaction/host-sink-dom.d.ts +3 -2
  22. package/dist/esm/interaction/host-sink-message.d.ts +3 -2
  23. package/dist/esm/interaction/lazy-tooltip.d.ts +2 -2
  24. package/dist/esm/interaction/legend-controller.d.ts +246 -0
  25. package/dist/esm/interaction/tooltip-controller.d.ts +20 -95
  26. package/dist/esm/interaction/tooltip-grid.d.ts +33 -0
  27. package/dist/esm/layout/facet-grid.d.ts +6 -0
  28. package/dist/esm/layout/ticks.d.ts +1 -0
  29. package/dist/esm/map/tile-source.d.ts +77 -8
  30. package/dist/esm/perspective-viewer-charts.js +1 -2
  31. package/dist/esm/perspective-viewer-charts.js.map +4 -4
  32. package/dist/esm/plugin/charts.d.ts +2 -0
  33. package/dist/esm/plugin/format-defaults.d.ts +9 -0
  34. package/dist/esm/plugin/plugin.d.ts +20 -0
  35. package/dist/esm/theme/gradient.d.ts +1 -0
  36. package/dist/esm/transport/protocol.d.ts +45 -3
  37. package/dist/esm/transport/renderer-transport.d.ts +3 -0
  38. package/dist/esm/worker/renderer.worker.d.ts +19 -1
  39. package/package.json +1 -1
  40. package/src/css/perspective-viewer-charts.css +0 -1
  41. package/src/ts/axis/bar-axis.ts +2 -7
  42. package/src/ts/axis/legend.ts +351 -24
  43. package/src/ts/axis/map-ticks.ts +94 -0
  44. package/src/ts/axis/numeric-axis.ts +2 -9
  45. package/src/ts/charts/candlestick/candlestick-interact.ts +46 -40
  46. package/src/ts/charts/candlestick/candlestick-render.ts +73 -10
  47. package/src/ts/charts/cartesian/cartesian-interact.ts +9 -3
  48. package/src/ts/charts/cartesian/cartesian-render.ts +369 -75
  49. package/src/ts/charts/cartesian/cartesian.ts +9 -0
  50. package/src/ts/charts/cartesian/glyph.ts +6 -2
  51. package/src/ts/charts/cartesian/glyphs/density.ts +2 -2
  52. package/src/ts/charts/cartesian/glyphs/lines.ts +15 -11
  53. package/src/ts/charts/cartesian/glyphs/points.ts +9 -5
  54. package/src/ts/charts/cartesian/tooltip-lines.ts +8 -8
  55. package/src/ts/charts/chart-base.ts +34 -32
  56. package/src/ts/charts/chart.ts +106 -7
  57. package/src/ts/charts/common/draw-tooltip-box.ts +13 -30
  58. package/src/ts/charts/common/tree-chrome.ts +102 -23
  59. package/src/ts/charts/common/tree-interact.ts +18 -17
  60. package/src/ts/charts/heatmap/heatmap-interact.ts +65 -7
  61. package/src/ts/charts/heatmap/heatmap-render.ts +100 -24
  62. package/src/ts/charts/map/map.ts +3 -5
  63. package/src/ts/charts/series/series-build.ts +14 -2
  64. package/src/ts/charts/series/series-interact.ts +12 -11
  65. package/src/ts/charts/series/series-render.ts +305 -132
  66. package/src/ts/charts/series/series.ts +10 -0
  67. package/src/ts/charts/sunburst/sunburst-render.ts +7 -1
  68. package/src/ts/charts/treemap/treemap-render.ts +7 -1
  69. package/src/ts/data/view-reader.ts +1 -4
  70. package/src/ts/index.ts +2 -0
  71. package/src/ts/interaction/axis-indicators.ts +137 -0
  72. package/src/ts/interaction/host-sink-dom.ts +66 -2
  73. package/src/ts/interaction/host-sink-message.ts +8 -2
  74. package/src/ts/interaction/lazy-tooltip.ts +5 -5
  75. package/src/ts/interaction/legend-controller.ts +1084 -0
  76. package/src/ts/interaction/tooltip-controller.ts +43 -131
  77. package/src/ts/interaction/tooltip-grid.ts +187 -0
  78. package/src/ts/layout/facet-grid.ts +8 -1
  79. package/src/ts/layout/ticks.ts +12 -0
  80. package/src/ts/map/tile-loader.ts +16 -1
  81. package/src/ts/map/tile-source.ts +199 -53
  82. package/src/ts/map/tile-sources.json +18 -0
  83. package/src/ts/plugin/charts.ts +40 -6
  84. package/src/ts/plugin/format-defaults.ts +37 -0
  85. package/src/ts/plugin/plugin.ts +236 -67
  86. package/src/ts/shaders/line.vert.glsl +5 -2
  87. package/src/ts/shaders/scatter.vert.glsl +0 -7
  88. package/src/ts/theme/gradient.ts +24 -0
  89. package/src/ts/transport/protocol.ts +46 -2
  90. package/src/ts/transport/renderer-transport.ts +83 -4
  91. package/src/ts/worker/dispatch.ts +6 -2
  92. package/src/ts/worker/renderer.worker.ts +78 -2
@@ -0,0 +1,246 @@
1
+ import { type PluginConfig } from "../charts/chart";
2
+ import type { PlotRect } from "../layout/plot-layout";
3
+ import type { InteractionEvent } from "../transport/protocol";
4
+ /** Minimum legend width for any mode (drag clamp + config clamp). */
5
+ export declare const LEGEND_MIN_WIDTH = 48;
6
+ /** Maximum configurable legend width (further clamped to canvas/2). */
7
+ export declare const LEGEND_MAX_WIDTH = 512;
8
+ /** Minimum floating-panel height. */
9
+ export declare const LEGEND_MIN_HEIGHT = 48;
10
+ /** Floating-panel header strip height (title + move grip). */
11
+ export declare const LEGEND_HEADER_H = 18;
12
+ /** Entry row height shared by every swatch-list legend painter. */
13
+ export declare const LEGEND_LINE_HEIGHT = 18;
14
+ /**
15
+ * Floating-frame chrome, as consumed from the panel box by
16
+ * `paintFloatingLegendFrame`: `PAD_L` insets the content from the left
17
+ * edge, `W` is the total width the frame takes, and `H` the total
18
+ * height it takes BELOW the header (split evenly above/below). The
19
+ * auto-size math is the frame's inverse, so both must read the same
20
+ * constants — a drifted pair sizes a panel that clips its own last row.
21
+ */
22
+ export declare const LEGEND_FRAME_PAD_L = 8;
23
+ export declare const LEGEND_FRAME_W = 12;
24
+ export declare const LEGEND_FRAME_H = 8;
25
+ /** Width the header title is laid out against: `box.width - PAD`. */
26
+ export declare const LEGEND_TITLE_PAD = 16;
27
+ /**
28
+ * Entry-row chrome left of the label text: swatch (10) + gap (6).
29
+ * Gradient legends substitute their own (bar 16 + gap 5).
30
+ */
31
+ export declare const LEGEND_ENTRY_LEADING = 16;
32
+ /**
33
+ * Sidebar gutter width when the sidebar legend is active: the
34
+ * configured `legend_width_px`, or `legacy` (the chart family's
35
+ * historical gutter constant) when the config is 0 (auto).
36
+ */
37
+ export declare function legendSidebarWidth(cfg: PluginConfig, legacy: number): number;
38
+ /**
39
+ * `legend_mode` with `"auto"` resolved away — the only form the
40
+ * layout/paint/interaction paths consume. Painters record the resolved
41
+ * mode in `setPainted`, so the controller's drag semantics follow it
42
+ * with no separate resolution.
43
+ */
44
+ export type ResolvedLegendMode = "sidebar" | "none" | "floating";
45
+ /**
46
+ * `"auto"` resolves to `"floating"` when every legend entry fits the
47
+ * DEFAULT floating panel (160 px ≈ header + 7 rows) without scrolling —
48
+ * a compact overlay that frees the whole gutter — and to `"sidebar"`
49
+ * otherwise, where a long list gets dedicated, scrollable space instead
50
+ * of occluding the plot. Continuous gradient legends have no entry
51
+ * list; callers pass `entryCount = 0` and they resolve to floating.
52
+ */
53
+ export declare const AUTO_FLOATING_MAX_ROWS = 7;
54
+ export declare function resolveLegendMode(cfg: PluginConfig, entryCount: number): ResolvedLegendMode;
55
+ /**
56
+ * Right-margin width a plot layout should reserve for the legend.
57
+ * `legacy` is the family's historical `hasLegend` gutter (80 for
58
+ * single-plot layouts, 96 for facet grids). Modes `"none"` and
59
+ * `"floating"` collapse the gutter to the no-legend breathing margin —
60
+ * the plot widens and the floating panel overlays it. `entryCount`
61
+ * feeds the `"auto"` resolution ({@link resolveLegendMode}); pass the
62
+ * same count the legend painter will enumerate.
63
+ */
64
+ export declare function legendRightGutter(cfg: PluginConfig, hasLegend: boolean, legacy?: number, entryCount?: number): number;
65
+ /**
66
+ * Tree-chart variant of {@link legendRightGutter}: trees reserve `0`
67
+ * (fully flush cells) rather than a 16px breathing margin when no
68
+ * sidebar legend is present.
69
+ */
70
+ export declare function legendTreeGutter(cfg: PluginConfig, hasLegend: boolean, legacy: number, entryCount?: number): number;
71
+ /**
72
+ * Content measurements a floating panel needs to size itself in
73
+ * `legend_size_mode: "auto"`. Supplied by the call site (which owns the
74
+ * chrome canvas and the label set) and consumed ONLY by that mode, so a
75
+ * `"fixed"` panel — and every sidebar legend — pays nothing.
76
+ */
77
+ export interface LegendAutoFit {
78
+ /**
79
+ * Entry rows the painter will enumerate. `0` marks a continuous
80
+ * gradient legend, which has no row list and keeps the default
81
+ * panel height.
82
+ */
83
+ entryCount: number;
84
+ /**
85
+ * Panel width in CSS px that fits the widest entry label AND the
86
+ * header title, frame chrome included. Invoked at most once per
87
+ * `floatingBox` call; see `legendAutoFit`.
88
+ */
89
+ boxWidth?: () => number;
90
+ }
91
+ /**
92
+ * Cursor-zone classification for a point over the painted legend.
93
+ * Sidebar legends expose only `resize-w` (their left edge) plus
94
+ * `scrollbar` / `body`; floating panels add the E/S/SE handles and the
95
+ * `move` grip (header strip — the body also moves, gated by
96
+ * {@link MOVE_THRESHOLD} so entry clicks still land).
97
+ */
98
+ export type LegendHitZone = "none" | "body" | "move" | "scrollbar" | "resize-w" | "resize-e" | "resize-s" | "resize-se";
99
+ /**
100
+ * Frame snapshot the legend painters hand back after painting: the
101
+ * panel's outer box, the scrollable content rect inside it, and the
102
+ * full (unclipped) content height. Hit-testing and wheel/drag routing
103
+ * all consult the LAST PAINTED geometry — between paints the on-screen
104
+ * pixels are exactly this snapshot, so events resolve against what the
105
+ * user actually sees.
106
+ */
107
+ export interface PaintedLegend {
108
+ mode: "sidebar" | "floating";
109
+ box: PlotRect;
110
+ content: PlotRect;
111
+ contentHeight: number;
112
+ /**
113
+ * The resolved right-gutter width this frame (sidebar mode only).
114
+ * The width-drag applies its delta to this — NOT to `box.width`,
115
+ * which excludes the gutter's padding — so a drag that starts on
116
+ * an auto-width (config 0) legend continues from the family's
117
+ * legacy width without a jump.
118
+ */
119
+ sidebarGutter?: number;
120
+ }
121
+ /**
122
+ * Per-event services the worker renderer supplies. The controller is
123
+ * pure state — it never reaches into the chart or transport itself.
124
+ */
125
+ export interface LegendEventCtx {
126
+ /**
127
+ * The chart's live plugin config. Drag gestures mutate the legend
128
+ * fields on this object directly (the worker-local copy), so the
129
+ * next paint lays out from the in-progress values; the persisted
130
+ * delta is posted once at pointerup.
131
+ */
132
+ cfg: PluginConfig;
133
+ cssWidth: number;
134
+ cssHeight: number;
135
+ /**
136
+ * Visible toggleable entry rects (series legends). A floating-panel
137
+ * click that lands on one passes through to the chart's own legend
138
+ * click handler instead of being consumed.
139
+ */
140
+ legendRects: ReadonlyArray<{
141
+ rect: PlotRect;
142
+ }>;
143
+ /**
144
+ * Schedule a repaint. `relayout: true` when the gesture changed the
145
+ * plot geometry (sidebar width) and a full GL pass is required;
146
+ * `false` for chrome-only changes (scroll, floating move/resize).
147
+ */
148
+ repaint(relayout: boolean): void;
149
+ /** Post a persisted config delta (pointerup, changed fields only). */
150
+ postDelta(fields: Partial<PluginConfig>): void;
151
+ setCursor(cursor: string): void;
152
+ /** Dismiss any active plot hover (cursor entered the legend). */
153
+ dispatchLeave(): void;
154
+ }
155
+ /**
156
+ * Single owner of legend interaction state: last-painted geometry,
157
+ * scroll offset, hover cursor, and the drag state machine. One
158
+ * instance per chart (`AbstractChart._legend`); the worker renderer
159
+ * consults {@link handleEvent} FIRST for every forwarded interaction
160
+ * event, so legend gestures structurally preempt plot zoom / pan /
161
+ * tooltip routing rather than racing them.
162
+ *
163
+ * The scroll offset is transient (never persisted); geometry fields
164
+ * (`legend_size_mode`, `legend_width_px`, `legend_height_px`,
165
+ * `legend_x`, `legend_y`) round-trip through `plugin_config`.
166
+ */
167
+ export declare class LegendController {
168
+ private _painted;
169
+ private _scroll;
170
+ private _drag;
171
+ private _suppressClick;
172
+ private _hoverInside;
173
+ private _lastCursor;
174
+ /**
175
+ * Clamp and return the scroll offset for this frame. Painters call
176
+ * this before drawing the entry window so the offset is always
177
+ * valid against the CURRENT content/viewport pair (content can
178
+ * shrink between frames — data updates, panel resizes).
179
+ */
180
+ clampScroll(viewHeight: number, contentHeight: number): number;
181
+ /** Record this frame's painted geometry. */
182
+ setPainted(p: PaintedLegend): void;
183
+ /**
184
+ * Record that no legend was painted this frame (mode `"none"`, or
185
+ * no legend content). Kills every hit zone until the next paint.
186
+ */
187
+ clearPainted(): void;
188
+ /**
189
+ * Resolve the floating panel's outer box from config. Offsets are
190
+ * normalized to the free span and measured from the
191
+ * `legend_anchor` corner, so every `legend_x`/`legend_y` in [0, 1]
192
+ * yields a fully on-canvas box at any canvas size.
193
+ *
194
+ * `fit` drives `legend_size_mode: "auto"` (see {@link LegendAutoFit});
195
+ * omitting it in auto mode falls back to the fixed-mode defaults,
196
+ * so a call site that cannot measure still paints a sane panel. The
197
+ * canvas clamps apply to BOTH modes — an auto panel never grows
198
+ * past half the canvas width or its full height, it scrolls
199
+ * instead.
200
+ */
201
+ floatingBox(cfg: PluginConfig, cssWidth: number, cssHeight: number, fit?: LegendAutoFit): PlotRect;
202
+ /**
203
+ * Called by `setPluginConfig` BEFORE the incoming config replaces
204
+ * the current one. A restore whose legend fields EQUAL the current
205
+ * values is the persistence echo of a completed drag — a no-op
206
+ * here, so scroll and any in-flight gesture survive. Different
207
+ * values (the user typed in the settings form, or a restore raced
208
+ * in) win unconditionally: any in-flight drag is cancelled.
209
+ */
210
+ reconcileConfig(prev: PluginConfig, next: PluginConfig): void;
211
+ cancelGesture(): void;
212
+ /**
213
+ * Route one forwarded interaction event. Returns `true` when the
214
+ * event was consumed (the caller must not forward it to zoom /
215
+ * tooltip routing).
216
+ */
217
+ handleEvent(event: InteractionEvent, ctx: LegendEventCtx): boolean;
218
+ /** Classify a canvas-CSS-px point against the last painted legend. */
219
+ hitTest(mx: number, my: number): LegendHitZone;
220
+ private _scrollable;
221
+ private _inScrollbar;
222
+ private _onWheel;
223
+ private _onPointerDown;
224
+ private _onPointerMove;
225
+ private _onPointerUp;
226
+ private _onDblClick;
227
+ private _onClick;
228
+ private _applyDrag;
229
+ /**
230
+ * First write of a resize gesture on an auto-sized floating panel:
231
+ * switch it to `"fixed"`. Without this the gesture would be inert —
232
+ * the next paint recomputes the content size and discards it.
233
+ *
234
+ * BOTH dimensions are seeded from the box on screen, not just the
235
+ * dragged one: the saved `legend_width_px` / `legend_height_px` are
236
+ * whatever the panel had before it went auto, so adopting them
237
+ * wholesale would jump the edge the user is NOT dragging.
238
+ */
239
+ private _freezeAutoSize;
240
+ /** Bottom-edge resize: new height with the top edge held fixed. */
241
+ private _applySouthResize;
242
+ private _writeAnchoredPos;
243
+ private _setScroll;
244
+ private static _cursorFor;
245
+ private _setCursor;
246
+ }
@@ -1,9 +1,7 @@
1
1
  import type { Canvas2D } from "../charts/canvas-types";
2
2
  import type { PlotLayout } from "../layout/plot-layout";
3
3
  import type { Theme } from "../theme/theme";
4
- /**
5
- * Minimal positioning input — PlotLayout satisfies this.
6
- */
4
+ import { type TooltipContent, type TooltipStyle } from "./tooltip-grid";
7
5
  export interface CssBounds {
8
6
  cssWidth: number;
9
7
  cssHeight: number;
@@ -11,7 +9,7 @@ export interface CssBounds {
11
9
  export interface TooltipCallbacks {
12
10
  /**
13
11
  * RAF-throttled mouse position in CSS pixels, relative to the GL
14
- * canvas (host already subtracted `getBoundingClientRect`).
12
+ * canvas.
15
13
  */
16
14
  onHover(mx: number, my: number): void;
17
15
  /**
@@ -19,8 +17,7 @@ export interface TooltipCallbacks {
19
17
  */
20
18
  onLeave(): void;
21
19
  /**
22
- * Fires on click with mouse position. Return true to consume the click
23
- * (skipping the default pin/dismiss flow — used for legend clicks).
20
+ * Fires on click with mouse position.
24
21
  */
25
22
  onClickPre?(mx: number, my: number): boolean;
26
23
  /**
@@ -28,74 +25,33 @@ export interface TooltipCallbacks {
28
25
  */
29
26
  onPin?(mx: number, my: number): void;
30
27
  /**
31
- * Fires on dblclick (treemap drill-up gesture). Optional — charts
32
- * that don't bind a handler simply ignore the event.
28
+ * Fires on dblclick (treemap drill-up gesture).
33
29
  */
34
30
  onDblClick?(mx: number, my: number): void;
35
31
  /**
36
- * Fires when an active pin is dismissed by a click on the
37
- * already-pinned target (the "click again to unpin" gesture in
38
- * `dispatchClick`). Chart impls hook this to emit a
39
- * `perspective-global-filter` with `selected: false`. Does *not*
40
- * fire on the implicit dismiss inside `pin()` that replaces an
41
- * existing pin — that path is followed by a fresh `onPin` which
42
- * emits its own `selected: true`.
32
+ * Fires when an active pin is dismissed by a click.
43
33
  */
44
34
  onUnpin?(): void;
45
35
  }
46
36
  export interface RenderTooltipOptions {
47
- /**
48
- * Draw a dashed crosshair at `pos`. Used by scatter/line.
49
- */
50
37
  crosshair?: boolean;
51
- /**
52
- * Draw a ring of `radius` CSS pixels at `pos`. Used to highlight a
53
- * hovered point. Omit for bars (where the bar itself highlights).
54
- */
55
38
  highlightRadius?: number;
39
+ maxColumnPx?: number;
40
+ opacity?: number;
56
41
  }
57
42
  /**
58
- * Side-channel from the chart back to the host's DOM. The chart calls
59
- * into a sink rather than touching the DOM itself; the host
60
- * materializes the actual visual (`<div>` for pinned tooltip, cursor
61
- * mutation on the GL canvas).
62
- *
63
- * - `MessageHostSink` (in worker/) — forwards calls over a
64
- * `postMessage`-shaped channel back to
65
- * the host.
66
- * - `DomHostSink` (in host transport) — receives the matching
67
- * envelopes and applies them to the DOM.
68
- *
69
- * The controller's `_pinned` flag is the source of truth for whether
70
- * hover updates are gated; the sink only owns the visual artifact.
43
+ * Side-channel from the chart back to the host's DOM.
71
44
  */
72
45
  export interface HostSink {
73
- pin(lines: string[], pos: {
46
+ pin(grid: TooltipContent, pos: {
74
47
  px: number;
75
48
  py: number;
76
- }, bounds: CssBounds): void;
49
+ }, bounds: CssBounds, style?: TooltipStyle): void;
77
50
  dismiss(): void;
78
51
  setCursor(cursor: string): void;
79
- /**
80
- * Forward a `perspective-click` to the host. Optional — only the
81
- * worker-bound `MessageHostSink` implements it; `DomHostSink` (the
82
- * host-side consumer of pin/dismiss) never sees user-event calls,
83
- * so omits the implementation.
84
- */
85
52
  emitUserClick?(detail: UserClickPayload): void;
86
- /**
87
- * Forward a `perspective-global-filter` to the host with the
88
- * `selected: true` / `selected: false` semantics. The host owns the
89
- * `removeConfigs` history (mirrors datagrid's
90
- * `model._last_insert_configs`); the sink only ships the new state.
91
- */
92
53
  emitUserSelect?(payload: UserSelectPayload): void;
93
54
  }
94
- /**
95
- * Plain-object payload for `HostSink.emitUserClick`. Matches
96
- * `PerspectiveClickDetail` byte-for-byte; defined locally to avoid a
97
- * cycle through `event-detail.ts`.
98
- */
99
55
  export interface UserClickPayload {
100
56
  row: Record<string, unknown>;
101
57
  column_names: string[];
@@ -103,11 +59,6 @@ export interface UserClickPayload {
103
59
  filter?: unknown[];
104
60
  };
105
61
  }
106
- /**
107
- * Plain-object payload for `HostSink.emitUserSelect`. The host
108
- * transport reconstructs a `PerspectiveSelectDetail` class instance
109
- * from this plus its cached `_lastInsertConfig`.
110
- */
111
62
  export interface UserSelectPayload {
112
63
  selected: boolean;
113
64
  row: Record<string, unknown>;
@@ -118,13 +69,7 @@ export interface UserSelectPayload {
118
69
  }
119
70
  /**
120
71
  * Owns the hover/click/dblclick state machine and the pinned-tooltip
121
- * lifecycle. The renderer drives this purely through
122
- * `dispatchHover` / `dispatchLeave` / `dispatchClick` /
123
- * `dispatchDblClick` — the host's `RawEventForwarder` captures DOM
124
- * events on the GL canvas and posts them as `InteractionEvent`s.
125
- *
126
- * Pinning + cursor changes go through a {@link HostSink} so the actual
127
- * DOM mutations happen host-side regardless of where the chart runs.
72
+ * lifecycle.
128
73
  */
129
74
  export declare class TooltipController {
130
75
  private _callbacks;
@@ -134,60 +79,40 @@ export declare class TooltipController {
134
79
  private _pinned;
135
80
  get isPinned(): boolean;
136
81
  /**
137
- * Replace the active host sink. Dismisses any existing pin via the
138
- * prior sink so we never leak a pinned artifact across resets —
139
- * though in practice each chart instance uses one sink for its
140
- * lifetime.
82
+ * Replace the active host sink.
141
83
  */
142
84
  setHost(sink: HostSink): void;
143
85
  /**
144
- * Forward a cursor change to the host. No-op when no host sink is
145
- * installed (chart constructed without a transport).
86
+ * Forward a cursor change to the host.
146
87
  */
147
88
  setCursor(cursor: string): void;
148
89
  /**
149
- * Install the chart's tooltip callbacks. The renderer drives the
150
- * controller via `dispatchHover` / `dispatchLeave` /
151
- * `dispatchClick` / `dispatchDblClick`; this controller never
152
- * touches the DOM directly.
90
+ * Install the chart's tooltip callbacks.
153
91
  */
154
92
  attach(callbacks: TooltipCallbacks): void;
155
93
  detach(): void;
156
94
  /**
157
95
  * Schedule an `onHover` callback for the given canvas-relative
158
- * coords. Coalesces multiple calls within one animation frame so
159
- * pointer streams don't backlog the chart's hit-test path.
160
- *
161
- * Workers ship with `requestAnimationFrame` (DedicatedWorkerGlobalScope
162
- * exposes it for OffscreenCanvas painting), so the same coalescer
163
- * works in both modes. We fall back to setTimeout if RAF is missing
164
- * (e.g. node tests without a polyfill).
96
+ * coords.
165
97
  */
166
98
  dispatchHover(mx: number, my: number): void;
167
99
  dispatchLeave(): void;
168
100
  dispatchClick(mx: number, my: number): void;
169
101
  dispatchDblClick(mx: number, my: number): void;
170
102
  /**
171
- * Pin a tooltip (or replace an active one). Forwards through the
172
- * configured sink and flips the controller's pinned flag so hover
173
- * dispatch is suppressed until dismissal.
103
+ * Pin a tooltip (or replace an active one).
174
104
  */
175
- pin(lines: string[], pos: {
105
+ pin(grid: TooltipContent, pos: {
176
106
  px: number;
177
107
  py: number;
178
- }, bounds: CssBounds): void;
108
+ }, bounds: CssBounds, style?: TooltipStyle): void;
179
109
  dismiss(): void;
180
110
  }
181
111
  /**
182
112
  * Paint a canvas tooltip (crosshair, highlight ring, box + text) onto
183
- * `canvas`. The helper normalizes the 2D context to a DPR-scaled
184
- * identity transform on entry and restores prior state on exit, so it
185
- * composes cleanly with other chrome painters that may have already
186
- * called `initCanvas` on the same canvas — re-applying `scale(dpr,dpr)`
187
- * blind would double-scale in that case, misplacing the tooltip
188
- * proportionally to its distance from the origin.
113
+ * `canvas`.
189
114
  */
190
115
  export declare function renderCanvasTooltip(canvas: Canvas2D | null, pos: {
191
116
  px: number;
192
117
  py: number;
193
- }, lines: string[], layout: PlotLayout, theme: Theme, dpr: number, options?: RenderTooltipOptions): void;
118
+ }, grid: TooltipContent, layout: PlotLayout, theme: Theme, dpr: number, options?: RenderTooltipOptions): void;
@@ -0,0 +1,33 @@
1
+ import type { Context2D } from "../charts/canvas-types";
2
+ import type { Theme } from "../theme/theme";
3
+ export type TooltipRow = string[];
4
+ export type TooltipContent = TooltipRow[];
5
+ export interface TooltipStyle {
6
+ opacity: number;
7
+ maxColumnPx: number;
8
+ }
9
+ export declare const DEFAULT_TOOLTIP_STYLE: TooltipStyle;
10
+ export declare function tooltipStyleOf(cfg: {
11
+ tooltip_opacity: number;
12
+ tooltip_max_column_px: number;
13
+ }): TooltipStyle;
14
+ export declare const NUMERIC_RE: RegExp;
15
+ export declare const TOOLTIP_ROW_HEIGHT = 16;
16
+ export declare const TOOLTIP_PADDING = 8;
17
+ export declare const TOOLTIP_COL_GAP = 8;
18
+ export interface MeasuredTooltipGrid {
19
+ rows: string[][];
20
+ colWidths: number[];
21
+ colRightAlign: boolean[];
22
+ boxW: number;
23
+ boxH: number;
24
+ }
25
+ /**
26
+ * Measure a tooltip grid under the current `ctx.font`.
27
+ */
28
+ export declare function measureTooltipGrid(ctx: Context2D, grid: TooltipContent, maxColumnPx: number): MeasuredTooltipGrid;
29
+ /**
30
+ * Paint a measured tooltip grid at `(tx, ty)`.
31
+ */
32
+ export declare function paintTooltipGrid(ctx: Context2D, measured: MeasuredTooltipGrid, tx: number, ty: number, theme: Theme, opacity: number): void;
33
+ export declare function ellipsize(ctx: Context2D, text: string, maxW: number): string;
@@ -32,6 +32,12 @@ export interface FacetGridOptions {
32
32
  * Reserve a right gutter for a single shared legend.
33
33
  */
34
34
  hasLegend?: boolean;
35
+ /**
36
+ * Width of the legend gutter when `hasLegend` is set. Defaults to
37
+ * the historical `LEGEND_GUTTER` (96). Callers resolve this from
38
+ * `plugin_config.legend_width_px` via `legendSidebarWidth`.
39
+ */
40
+ legendWidth?: number;
35
41
  /** Axis-label allowance (consumed only when the corresponding axis
36
42
  * mode produces a gutter — outer band or per-cell). */
37
43
  hasXLabel?: boolean;
@@ -5,6 +5,7 @@
5
5
  * @param targetCount - Desired number of ticks (approximate)
6
6
  */
7
7
  export declare function computeNiceTicks(min: number, max: number, targetCount: number): number[];
8
+ export declare function stepTickFormatter(isDate: boolean | undefined, ticks: number[] | null | undefined): (v: number) => string;
8
9
  /**
9
10
  * Format a numeric tick value for display.
10
11
  * Uses K/M/B suffixes for large numbers, fixed decimals for small.
@@ -53,14 +53,83 @@ export declare class TemplatedTileSource implements TileSource {
53
53
  urlFor(z: number, x: number, y: number): string;
54
54
  }
55
55
  /**
56
- * Identifier of the default tile providers shipped with `viewer-charts`.
57
- * Surfaced as the `map_tile_provider` PluginConfig enum so users can
58
- * pick light vs. dark vs. labels-only without writing a custom source.
56
+ * Declarative description of one raster XYZ tile provider — the shape
57
+ * of an entry in [tile-sources.json] and of the argument to
58
+ * `registerTileSource`. Everything the renderer needs to construct a
59
+ * concrete {@link TileSource} lives here; the TypeScript is generic
60
+ * over these entries and hardcodes no provider URLs.
59
61
  */
60
- export type TileProviderId = "carto-positron" | "carto-dark-matter" | "carto-voyager";
62
+ export interface TileSourceSpec {
63
+ /** Registry key; the persisted `map_tile_provider` value. */
64
+ readonly id: string;
65
+ /** Human-readable enum-variant label on the settings panel. */
66
+ readonly label: string;
67
+ /**
68
+ * URL template with `{z}`/`{x}`/`{y}` placeholders and optional
69
+ * `{s}` subdomain rotation. A provider that needs an API key
70
+ * embeds it directly in the template it registers — keys never
71
+ * pass through `plugin_config`, so they never appear in `save()`
72
+ * output.
73
+ */
74
+ readonly template: string;
75
+ /** `{s}` rotation pool; required non-empty iff `template` has `{s}`. */
76
+ readonly subdomains: readonly string[];
77
+ /** See {@link TileSource.attribution}. */
78
+ readonly attribution: string;
79
+ /** See {@link TileSource.tileSize}. Default 256. */
80
+ readonly tile_size: number;
81
+ /** See {@link TileSource.maxZoom}. Default 19. */
82
+ readonly max_zoom: number;
83
+ }
84
+ /**
85
+ * Validate + normalize an untrusted spec (a JSON entry or a
86
+ * `registerTileSource` argument). Throws `TypeError` naming the
87
+ * offending field; returns a frozen spec with defaults applied.
88
+ */
89
+ export declare function parseTileSourceSpec(input: unknown): TileSourceSpec;
90
+ /**
91
+ * The set of known tile providers: the bundled entries from
92
+ * [tile-sources.json] plus any registered at runtime via
93
+ * `registerTileSource`. One instance exists per JS realm ({@link
94
+ * TILE_SOURCES}) — the plugin realm's is the source of truth for the
95
+ * settings-panel enum; the worker realm's resolves ids at render time.
96
+ * The realms need no eager mirroring: every `setPluginConfig` / `init`
97
+ * control message carries the resolved spec for the config's
98
+ * `map_tile_provider` alongside the config itself, so a worker can
99
+ * never hold a config whose spec it lacks.
100
+ */
101
+ export declare class TileSourceRegistry {
102
+ private _specs;
103
+ constructor(seed: readonly unknown[]);
104
+ /**
105
+ * Add (or replace, by id) a provider. Validates via
106
+ * {@link parseTileSourceSpec} and returns the normalized spec.
107
+ * Replacement is safe for live charts: the cache identity below is
108
+ * content-derived, so a changed template yields a new cache key
109
+ * and the tile layer drops its stale textures on next bind.
110
+ */
111
+ register(input: unknown): TileSourceSpec;
112
+ /** Every known spec, bundled first, in insertion order. */
113
+ list(): readonly TileSourceSpec[];
114
+ /** The spec registered under `id`, or `undefined` if unknown. */
115
+ specFor(id: string): TileSourceSpec | undefined;
116
+ /**
117
+ * Resolve a `map_tile_provider` id to a concrete `TileSource`.
118
+ * Unknown ids fall back to the first bundled entry so a
119
+ * misconfigured `plugin_config` never produces a blank map — and
120
+ * so a config restored *before* its custom source is registered
121
+ * degrades to the default basemap until the next config forward
122
+ * carries the registered spec.
123
+ */
124
+ sourceFor(id: string): TileSource;
125
+ }
126
+ /**
127
+ * Realm-wide provider registry, seeded from [tile-sources.json]. A
128
+ * malformed bundled entry fails loudly here, at first import.
129
+ */
130
+ export declare const TILE_SOURCES: TileSourceRegistry;
61
131
  /**
62
- * Resolve a `TileProviderId` (from PluginConfig) to a concrete
63
- * `TileSource`. Unknown ids fall back to Positron so a misconfigured
64
- * `_pluginConfig` never produces a blank map.
132
+ * Read-only listing of every known tile provider (bundled + runtime-
133
+ * registered), in settings-panel enum order. Public package export.
65
134
  */
66
- export declare function tileSourceFor(id: TileProviderId | string): TileSource;
135
+ export declare function tileSources(): readonly TileSourceSpec[];