@perspective-dev/viewer-charts 5.2.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 (109) 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/candlestick/candlestick-render.d.ts +3 -0
  7. package/dist/esm/charts/cartesian/cartesian.d.ts +7 -0
  8. package/dist/esm/charts/cartesian/glyph.d.ts +2 -1
  9. package/dist/esm/charts/cartesian/glyphs/density.d.ts +2 -1
  10. package/dist/esm/charts/cartesian/glyphs/lines.d.ts +2 -1
  11. package/dist/esm/charts/cartesian/glyphs/points.d.ts +2 -1
  12. package/dist/esm/charts/cartesian/tooltip-lines.d.ts +1 -1
  13. package/dist/esm/charts/chart-base.d.ts +24 -9
  14. package/dist/esm/charts/chart.d.ts +81 -6
  15. package/dist/esm/charts/common/category-axis-resolver.d.ts +1 -1
  16. package/dist/esm/charts/common/draw-tooltip-box.d.ts +2 -1
  17. package/dist/esm/charts/common/tree-chart.d.ts +1 -0
  18. package/dist/esm/charts/common/tree-interact.d.ts +1 -1
  19. package/dist/esm/charts/heatmap/heatmap.d.ts +1 -0
  20. package/dist/esm/charts/series/series-build.d.ts +7 -0
  21. package/dist/esm/charts/series/series-interact.d.ts +1 -1
  22. package/dist/esm/charts/series/series-type.d.ts +18 -0
  23. package/dist/esm/charts/series/series.d.ts +10 -1
  24. package/dist/esm/charts/sunburst/sunburst-render.d.ts +4 -0
  25. package/dist/esm/index.d.ts +2 -0
  26. package/dist/esm/interaction/axis-indicators.d.ts +11 -0
  27. package/dist/esm/interaction/host-sink-dom.d.ts +3 -2
  28. package/dist/esm/interaction/host-sink-message.d.ts +3 -2
  29. package/dist/esm/interaction/lazy-tooltip.d.ts +2 -2
  30. package/dist/esm/interaction/legend-controller.d.ts +246 -0
  31. package/dist/esm/interaction/tooltip-controller.d.ts +20 -95
  32. package/dist/esm/interaction/tooltip-grid.d.ts +33 -0
  33. package/dist/esm/layout/facet-grid.d.ts +6 -0
  34. package/dist/esm/layout/ticks.d.ts +1 -0
  35. package/dist/esm/map/tile-source.d.ts +77 -8
  36. package/dist/esm/perspective-viewer-charts.js +1 -2
  37. package/dist/esm/perspective-viewer-charts.js.map +4 -4
  38. package/dist/esm/plugin/charts.d.ts +2 -0
  39. package/dist/esm/plugin/format-defaults.d.ts +9 -0
  40. package/dist/esm/plugin/plugin.d.ts +28 -1
  41. package/dist/esm/theme/gradient.d.ts +32 -0
  42. package/dist/esm/theme/overrides.d.ts +19 -0
  43. package/dist/esm/transport/protocol.d.ts +58 -5
  44. package/dist/esm/transport/renderer-transport.d.ts +3 -0
  45. package/dist/esm/utils/css.d.ts +4 -0
  46. package/dist/esm/worker/renderer.worker.d.ts +46 -1
  47. package/package.json +1 -1
  48. package/src/css/perspective-viewer-charts.css +0 -1
  49. package/src/ts/axis/bar-axis.ts +2 -7
  50. package/src/ts/axis/legend.ts +351 -24
  51. package/src/ts/axis/map-ticks.ts +94 -0
  52. package/src/ts/axis/numeric-axis.ts +2 -9
  53. package/src/ts/charts/candlestick/candlestick-build.ts +1 -0
  54. package/src/ts/charts/candlestick/candlestick-interact.ts +46 -40
  55. package/src/ts/charts/candlestick/candlestick-render.ts +81 -10
  56. package/src/ts/charts/candlestick/candlestick.ts +4 -3
  57. package/src/ts/charts/cartesian/cartesian-interact.ts +9 -3
  58. package/src/ts/charts/cartesian/cartesian-render.ts +374 -75
  59. package/src/ts/charts/cartesian/cartesian.ts +13 -0
  60. package/src/ts/charts/cartesian/glyph.ts +6 -2
  61. package/src/ts/charts/cartesian/glyphs/density.ts +2 -2
  62. package/src/ts/charts/cartesian/glyphs/lines.ts +15 -11
  63. package/src/ts/charts/cartesian/glyphs/points.ts +9 -5
  64. package/src/ts/charts/cartesian/tooltip-lines.ts +8 -8
  65. package/src/ts/charts/chart-base.ts +76 -42
  66. package/src/ts/charts/chart.ts +111 -7
  67. package/src/ts/charts/common/category-axis-resolver.ts +44 -5
  68. package/src/ts/charts/common/draw-tooltip-box.ts +13 -30
  69. package/src/ts/charts/common/tree-chart.ts +4 -0
  70. package/src/ts/charts/common/tree-chrome.ts +102 -23
  71. package/src/ts/charts/common/tree-interact.ts +18 -17
  72. package/src/ts/charts/heatmap/heatmap-build.ts +7 -1
  73. package/src/ts/charts/heatmap/heatmap-interact.ts +75 -8
  74. package/src/ts/charts/heatmap/heatmap-render.ts +105 -24
  75. package/src/ts/charts/heatmap/heatmap.ts +8 -3
  76. package/src/ts/charts/map/map.ts +3 -5
  77. package/src/ts/charts/series/series-build.ts +15 -2
  78. package/src/ts/charts/series/series-interact.ts +14 -24
  79. package/src/ts/charts/series/series-render.ts +310 -132
  80. package/src/ts/charts/series/series-type.ts +65 -0
  81. package/src/ts/charts/series/series.ts +31 -8
  82. package/src/ts/charts/sunburst/sunburst-render.ts +16 -1
  83. package/src/ts/charts/treemap/treemap-render.ts +12 -1
  84. package/src/ts/data/view-reader.ts +1 -4
  85. package/src/ts/index.ts +2 -0
  86. package/src/ts/interaction/axis-indicators.ts +137 -0
  87. package/src/ts/interaction/host-sink-dom.ts +66 -2
  88. package/src/ts/interaction/host-sink-message.ts +8 -2
  89. package/src/ts/interaction/lazy-tooltip.ts +5 -5
  90. package/src/ts/interaction/legend-controller.ts +1084 -0
  91. package/src/ts/interaction/tooltip-controller.ts +43 -131
  92. package/src/ts/interaction/tooltip-grid.ts +187 -0
  93. package/src/ts/layout/facet-grid.ts +8 -1
  94. package/src/ts/layout/ticks.ts +48 -12
  95. package/src/ts/map/tile-loader.ts +16 -1
  96. package/src/ts/map/tile-source.ts +199 -53
  97. package/src/ts/map/tile-sources.json +18 -0
  98. package/src/ts/plugin/charts.ts +40 -6
  99. package/src/ts/plugin/format-defaults.ts +37 -0
  100. package/src/ts/plugin/plugin.ts +330 -76
  101. package/src/ts/shaders/line.vert.glsl +5 -2
  102. package/src/ts/shaders/scatter.vert.glsl +0 -7
  103. package/src/ts/theme/gradient.ts +265 -1
  104. package/src/ts/theme/overrides.ts +76 -0
  105. package/src/ts/transport/protocol.ts +60 -4
  106. package/src/ts/transport/renderer-transport.ts +107 -14
  107. package/src/ts/utils/css.ts +12 -0
  108. package/src/ts/worker/dispatch.ts +6 -2
  109. package/src/ts/worker/renderer.worker.ts +191 -7
@@ -10,7 +10,7 @@
10
10
  // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
- import { parseCSSColorToVec3 } from "../utils/css";
13
+ import { parseCSSColorToVec3, vec3ToHexColor } from "../utils/css";
14
14
 
15
15
  /**
16
16
  * A single stop on a parsed CSS gradient. `offset` ∈ [0, 1].
@@ -178,6 +178,246 @@ export function parseCssGradient(
178
178
  return result;
179
179
  }
180
180
 
181
+ /**
182
+ * A color literal — `#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`, `rgb()` or
183
+ * `rgba()` — as a `[0, 1]` RGB triple, or `null` for anything else.
184
+ */
185
+ export function parseCssColorStrict(
186
+ src: string,
187
+ ): [number, number, number] | null {
188
+ const s = src.trim();
189
+ if (s.startsWith("#")) {
190
+ const hex = s.slice(1);
191
+ if (!/^[0-9a-f]+$/i.test(hex)) {
192
+ return null;
193
+ }
194
+
195
+ if (hex.length === 3 || hex.length === 4) {
196
+ return [
197
+ parseInt(hex[0] + hex[0], 16) / 255,
198
+ parseInt(hex[1] + hex[1], 16) / 255,
199
+ parseInt(hex[2] + hex[2], 16) / 255,
200
+ ];
201
+ }
202
+
203
+ if (hex.length === 6 || hex.length === 8) {
204
+ return [
205
+ parseInt(hex.slice(0, 2), 16) / 255,
206
+ parseInt(hex.slice(2, 4), 16) / 255,
207
+ parseInt(hex.slice(4, 6), 16) / 255,
208
+ ];
209
+ }
210
+
211
+ return null;
212
+ }
213
+
214
+ const m = s.match(/^rgba?\(([^)]*)\)$/i);
215
+ if (!m) {
216
+ return null;
217
+ }
218
+
219
+ const tokens = m[1]
220
+ .split("/")[0]
221
+ .split(/[\s,]+/)
222
+ .filter((x) => x.length > 0);
223
+ if (tokens.length < 3 || tokens.length > 4) {
224
+ return null;
225
+ }
226
+
227
+ const channel = (token: string): number | null => {
228
+ const pct = token.endsWith("%");
229
+ const n = parseFloat(pct ? token.slice(0, -1) : token);
230
+ if (!isFinite(n)) {
231
+ return null;
232
+ }
233
+
234
+ const v = pct ? (n / 100) * 255 : n;
235
+ return Math.max(0, Math.min(255, Math.round(v))) / 255;
236
+ };
237
+
238
+ const r = channel(tokens[0]);
239
+ const g = channel(tokens[1]);
240
+ const b = channel(tokens[2]);
241
+ return r === null || g === null || b === null ? null : [r, g, b];
242
+ }
243
+
244
+ /**
245
+ * The shared `linear-gradient(...)` tokenizer of the viewer's CSS-valued
246
+ * config grammar.
247
+ */
248
+ export function tokenizeLinearGradient(
249
+ src: string,
250
+ ): Array<[[number, number, number], number | null]> | null {
251
+ const m = src.trim().match(/^linear-gradient\s*\((.*)\)$/is);
252
+ if (!m) {
253
+ return null;
254
+ }
255
+
256
+ const body = m[1];
257
+ const parts: string[] = [];
258
+ let depth = 0;
259
+ let start = 0;
260
+ for (let i = 0; i < body.length; i++) {
261
+ const ch = body[i];
262
+ if (ch === "(") {
263
+ depth++;
264
+ } else if (ch === ")") {
265
+ depth--;
266
+ } else if (ch === "," && depth === 0) {
267
+ parts.push(body.substring(start, i));
268
+ start = i + 1;
269
+ }
270
+ }
271
+
272
+ parts.push(body.substring(start));
273
+ const out: Array<[[number, number, number], number | null]> = [];
274
+ for (let i = 0; i < parts.length; i++) {
275
+ const piece = parts[i].trim();
276
+ if (!piece) {
277
+ return null;
278
+ }
279
+
280
+ const lower = piece.toLowerCase();
281
+ if (
282
+ i === 0 &&
283
+ (lower.startsWith("to ") ||
284
+ /^[-\d.]+(deg|rad|grad|turn)$/.test(lower))
285
+ ) {
286
+ continue;
287
+ }
288
+
289
+ const posMatch = piece.match(/\s([^\s)]+)$/);
290
+ let color = piece;
291
+ let offset: number | null = null;
292
+ if (posMatch) {
293
+ const tail = posMatch[1];
294
+ if (tail.endsWith("%")) {
295
+ const n = parseFloat(tail.slice(0, -1));
296
+ if (!isFinite(n)) {
297
+ return null;
298
+ }
299
+
300
+ color = piece.substring(0, posMatch.index).trim();
301
+ offset = n / 100;
302
+ } else if (/^[-\d.]/.test(tail)) {
303
+ return null;
304
+ }
305
+ }
306
+
307
+ const rgb = parseCssColorStrict(color);
308
+ if (!rgb) {
309
+ return null;
310
+ }
311
+
312
+ out.push([rgb, offset]);
313
+ }
314
+
315
+ return out;
316
+ }
317
+
318
+ /**
319
+ * Strict gradient reader: a `linear-gradient(...)` with ≥ 2 stops,
320
+ * positions optional, offsets clamped to `[0, 1]` and sorted, or `null`
321
+ * on malformed input.
322
+ */
323
+ export function parseCssGradientStrict(src: string): GradientStop[] | null {
324
+ const entries = tokenizeLinearGradient(src);
325
+ if (!entries || entries.length < 2) {
326
+ return null;
327
+ }
328
+
329
+ const offsets: Array<number | null> = entries.map(([, p]) => p);
330
+ const last = offsets.length - 1;
331
+ if (offsets[0] === null) {
332
+ offsets[0] = 0;
333
+ }
334
+
335
+ if (offsets[last] === null) {
336
+ offsets[last] = 1;
337
+ }
338
+
339
+ for (let i = 1; i < last; i++) {
340
+ if (offsets[i] !== null) {
341
+ continue;
342
+ }
343
+
344
+ let j = i + 1;
345
+ while (offsets[j] === null) {
346
+ j++;
347
+ }
348
+
349
+ const before = offsets[i - 1]!;
350
+ const after = offsets[j]!;
351
+ const span = j - (i - 1);
352
+ for (let k = i; k < j; k++) {
353
+ offsets[k] = before + ((k - (i - 1)) / span) * (after - before);
354
+ }
355
+
356
+ i = j - 1;
357
+ }
358
+
359
+ const stops: GradientStop[] = entries.map(([rgb], i) => ({
360
+ offset: Math.max(0, Math.min(1, offsets[i]!)),
361
+ color: [rgb[0], rgb[1], rgb[2], 1],
362
+ }));
363
+
364
+ stops.sort((a, b) => a.offset - b.offset);
365
+ return stops;
366
+ }
367
+
368
+ /**
369
+ * Strict palette reader: a `linear-gradient(...)` of ≥ 1 colors with no
370
+ * positions, or `null` on malformed input.
371
+ */
372
+ export function parseCssColorList(
373
+ src: string,
374
+ ): Array<[number, number, number]> | null {
375
+ const entries = tokenizeLinearGradient(src);
376
+ if (!entries || entries.length === 0) {
377
+ return null;
378
+ }
379
+
380
+ const out: Array<[number, number, number]> = [];
381
+ for (const [rgb, offset] of entries) {
382
+ if (offset !== null) {
383
+ return null;
384
+ }
385
+
386
+ out.push(rgb);
387
+ }
388
+
389
+ return out;
390
+ }
391
+
392
+ function formatPercent(offset: number): string {
393
+ const rounded = Math.round(Math.max(0, Math.min(1, offset)) * 1000) / 10;
394
+ return `${rounded}%`;
395
+ }
396
+
397
+ /**
398
+ * The viewer's canonical gradient string for `stops`: `linear-gradient(to
399
+ * right, #rrggbb P%, …)`, sorted, positions at 0.1% resolution.
400
+ */
401
+ export function stopsToCss(stops: GradientStop[]): string {
402
+ const body = [...stops]
403
+ .sort((a, b) => a.offset - b.offset)
404
+ .map(
405
+ (stop) =>
406
+ `${vec3ToHexColor([stop.color[0], stop.color[1], stop.color[2]])} ${formatPercent(stop.offset)}`,
407
+ )
408
+ .join(", ");
409
+
410
+ return `linear-gradient(to right, ${body})`;
411
+ }
412
+
413
+ /**
414
+ * The viewer's canonical palette string for `colors`: the same frame
415
+ * with bare `#rrggbb` entries and no positions.
416
+ */
417
+ export function colorsToCss(colors: Array<[number, number, number]>): string {
418
+ return `linear-gradient(to right, ${colors.map(vec3ToHexColor).join(", ")})`;
419
+ }
420
+
181
421
  /**
182
422
  * Piecewise-linear color sample at `t ∈ [0, 1]`. Returns RGBA in [0, 1].
183
423
  * Clamps `t` to the gradient's first/last stop outside `[0, 1]`.
@@ -257,6 +497,30 @@ export function colorValueToT(
257
497
  return t < 0 ? 0 : t > 1 ? 1 : t;
258
498
  }
259
499
 
500
+ export function colorRangePivot(
501
+ colorMin: number,
502
+ colorMax: number,
503
+ ): [number, number] {
504
+ if (!isFinite(colorMin) || !isFinite(colorMax) || colorMin >= colorMax) {
505
+ return [0, 0];
506
+ }
507
+
508
+ let denom: number;
509
+ if (colorMin >= 0) {
510
+ denom = colorMax;
511
+ } else if (colorMax <= 0) {
512
+ denom = -colorMin;
513
+ } else {
514
+ denom = Math.max(-colorMin, colorMax);
515
+ }
516
+
517
+ if (denom <= 0) {
518
+ return [0, 0];
519
+ }
520
+
521
+ return [-denom, denom];
522
+ }
523
+
260
524
  /**
261
525
  * Convert a discrete series palette (from `--psp-charts--series-N--color`)
262
526
  * into a `GradientStop[]` with stops at `i / (N - 1)`. The resulting
@@ -0,0 +1,76 @@
1
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2
+ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3
+ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4
+ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5
+ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6
+ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7
+ // ┃ Copyright (c) 2017, the Perspective Authors. ┃
8
+ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9
+ // ┃ This file is part of the Perspective library, distributed under the terms ┃
10
+ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
+
13
+ import {
14
+ parseCssColorList,
15
+ parseCssGradientStrict,
16
+ type GradientStop,
17
+ } from "./gradient";
18
+ import type { Theme } from "./theme";
19
+ import type { Vec3 } from "./palette";
20
+
21
+ /**
22
+ * Per-column color-scale overrides from the viewer's `columns_config`,
23
+ * patched into the resolved `Theme`'s `seriesPalette` / `gradientStops`
24
+ * fields.
25
+ */
26
+ export function applyColumnColorOverrides(
27
+ theme: Theme,
28
+ columnsConfig: Record<string, any> | undefined,
29
+ colorColumn: string | null,
30
+ ): Theme {
31
+ if (!colorColumn) {
32
+ return theme;
33
+ }
34
+
35
+ const cfg = columnsConfig?.[colorColumn];
36
+ if (!cfg) {
37
+ return theme;
38
+ }
39
+
40
+ let out = theme;
41
+ const gradient = parseGradientOverride(cfg.gradient);
42
+ if (gradient) {
43
+ out = { ...out, gradientStops: gradient };
44
+ }
45
+
46
+ const palette = parsePaletteOverride(cfg.palette);
47
+ if (palette) {
48
+ out = { ...out, seriesPalette: palette };
49
+ }
50
+
51
+ return out;
52
+ }
53
+
54
+ /**
55
+ * Parse a stored `palette` value into the `seriesPalette` shape, or
56
+ * `null` on anything malformed.
57
+ */
58
+ export function parsePaletteOverride(raw: unknown): Vec3[] | null {
59
+ if (typeof raw !== "string") {
60
+ return null;
61
+ }
62
+
63
+ return parseCssColorList(raw);
64
+ }
65
+
66
+ /**
67
+ * Parse a stored `gradient` value into sorted `GradientStop[]`, or
68
+ * `null` on malformed input or fewer than 2 stops.
69
+ */
70
+ export function parseGradientOverride(raw: unknown): GradientStop[] | null {
71
+ if (typeof raw !== "string") {
72
+ return null;
73
+ }
74
+
75
+ return parseCssGradientStrict(raw);
76
+ }
@@ -13,6 +13,7 @@
13
13
  import type { FacetConfig, PluginConfig } from "../charts/chart";
14
14
  import type { PerspectiveClickDetail } from "../event-detail";
15
15
  import type { ThemeSnapshot } from "../theme/theme";
16
+ import type { TileSourceSpec } from "../map/tile-source";
16
17
  import type { ViewConfig } from "@perspective-dev/client";
17
18
 
18
19
  export type { ThemeSnapshot };
@@ -53,6 +54,7 @@ export type WorkerMsg =
53
54
  | SetCursorMsg
54
55
  | UserClickMsg
55
56
  | UserSelectMsg
57
+ | PluginConfigDeltaMsg
56
58
  | LoadAndRenderAckMsg
57
59
  | ResizeAckMsg
58
60
  | FrameBitmapMsg
@@ -100,8 +102,20 @@ export interface InitMsg {
100
102
  * `dpr` and there is no host-side GL drawing buffer.
101
103
  */
102
104
  glCanvas?: OffscreenCanvas;
103
- gridlinesCanvas: OffscreenCanvas;
104
- chromeCanvas: OffscreenCanvas;
105
+
106
+ /**
107
+ * The gridlines (bottom) and chrome/axes (top) 2D layers.
108
+ * Transferred via `transferControlToOffscreen` on the host, present
109
+ * iff `renderMode === "direct"` — their pixels reach the screen
110
+ * through the compositor. In blit mode the renderer allocates
111
+ * worker-local surfaces instead and composites both layers into the
112
+ * shipped `FrameBitmapMsg`, so every layer of the frame rides the
113
+ * host's staged-present hold (a transferred canvas presents on the
114
+ * compositor's own schedule, unsynchronized with the host's layout
115
+ * commits — the one-frame axes warp on anticipated resizes).
116
+ */
117
+ gridlinesCanvas?: OffscreenCanvas;
118
+ chromeCanvas?: OffscreenCanvas;
105
119
 
106
120
  /**
107
121
  * `MessagePort` to the host's `ProxySession`. Worker mode only —
@@ -173,6 +187,17 @@ export interface InitMsg {
173
187
  columnsConfig?: Record<string, any>;
174
188
  defaultChartType?: string;
175
189
 
190
+ /**
191
+ * Resolved spec for `pluginConfig.map_tile_provider`, when the
192
+ * plugin realm's registry knows the id — see
193
+ * {@link SetPluginConfigMsg.tileSource} for the invariant. Applied
194
+ * to the worker realm's registry before the chart impl is
195
+ * constructed. Bundled [map/tile-sources.json] entries ship inside
196
+ * the worker bundle, so this matters only for runtime-registered
197
+ * providers.
198
+ */
199
+ tileSource?: TileSourceSpec;
200
+
176
201
  /**
177
202
  * Pre-resolved CSS-variable theme snapshot from the host.
178
203
  */
@@ -259,6 +284,18 @@ export interface SetColumnsConfigMsg {
259
284
  export interface SetPluginConfigMsg {
260
285
  kind: "setPluginConfig";
261
286
  cfg: PluginConfig;
287
+
288
+ /**
289
+ * Resolved spec for `cfg.map_tile_provider`, when the plugin
290
+ * realm's registry knows the id. Riding the config keeps the two
291
+ * realms' registries convergent with NO eager mirroring: the
292
+ * worker registers this spec before applying `cfg`, so it can
293
+ * never hold a config whose provider it cannot resolve —
294
+ * regardless of when `registerTileSource` ran relative to
295
+ * renderer construction. Absent for unknown ids (the worker falls
296
+ * back to the default basemap).
297
+ */
298
+ tileSource?: TileSourceSpec;
262
299
  }
263
300
 
264
301
  export interface SetBufferMaxCapacityMsg {
@@ -305,6 +342,7 @@ export interface LoadAndRenderMsg {
305
342
  export interface LoadAndRenderAckMsg {
306
343
  kind: "loadAndRenderAck";
307
344
  msgId: number;
345
+ error?: string;
308
346
  }
309
347
 
310
348
  export interface RedrawMsg {
@@ -405,7 +443,8 @@ export interface SnapshotPngReqMsg {
405
443
  export interface SnapshotPngReplyMsg {
406
444
  kind: "snapshotPngReply";
407
445
  requestId: number;
408
- blob: Blob;
446
+ blob?: Blob;
447
+ error?: string;
409
448
  }
410
449
 
411
450
  export interface DestroyMsg {
@@ -469,7 +508,9 @@ export interface ErrorMsg {
469
508
  */
470
509
  export interface PinTooltipMsg {
471
510
  kind: "pinTooltip";
472
- lines: string[];
511
+
512
+ grid: string[][];
513
+ style: { opacity: number; maxColumnPx: number };
473
514
  pos: { px: number; py: number };
474
515
  bounds: { cssWidth: number; cssHeight: number };
475
516
  }
@@ -489,6 +530,21 @@ export interface SetCursorMsg {
489
530
  cursor: string;
490
531
  }
491
532
 
533
+ /**
534
+ * Renderer → host: a completed legend gesture (sidebar width drag,
535
+ * floating move / resize) produced new values for the legend's
536
+ * `plugin_config` fields. Posted ONCE per gesture, at pointerup — never
537
+ * per pointermove — with only the fields the gesture changed. The host
538
+ * plugin persists them through the viewer's public `restore` surface
539
+ * (a user-gesture echo), which merges the host bucket, refreshes the
540
+ * settings form, and echoes one `setPluginConfig` back with the same
541
+ * values (a no-op by the worker's legend-field equality guard).
542
+ */
543
+ export interface PluginConfigDeltaMsg {
544
+ kind: "pluginConfigDelta";
545
+ fields: Record<string, string | number | boolean>;
546
+ }
547
+
492
548
  /**
493
549
  * Renderer → host: a user click landed on a chart glyph. Host
494
550
  * re-dispatches as `CustomEvent<PerspectiveClickDetail>` on the