@perspective-dev/viewer-charts 5.3.0 → 5.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.
- package/dist/cdn/perspective-viewer-charts.js +1 -2
- package/dist/cdn/perspective-viewer-charts.js.map +4 -4
- package/dist/esm/axis/legend.d.ts +53 -5
- package/dist/esm/axis/map-ticks.d.ts +15 -0
- package/dist/esm/charts/candlestick/candlestick-interact.d.ts +2 -6
- package/dist/esm/charts/cartesian/cartesian.d.ts +6 -0
- package/dist/esm/charts/cartesian/glyph.d.ts +2 -1
- package/dist/esm/charts/cartesian/glyphs/density.d.ts +2 -1
- package/dist/esm/charts/cartesian/glyphs/lines.d.ts +2 -1
- package/dist/esm/charts/cartesian/glyphs/points.d.ts +2 -1
- package/dist/esm/charts/cartesian/tooltip-lines.d.ts +1 -1
- package/dist/esm/charts/chart-base.d.ts +2 -0
- package/dist/esm/charts/chart.d.ts +77 -6
- package/dist/esm/charts/common/draw-tooltip-box.d.ts +2 -1
- package/dist/esm/charts/common/tree-interact.d.ts +1 -1
- package/dist/esm/charts/series/series-build.d.ts +7 -0
- package/dist/esm/charts/series/series-interact.d.ts +1 -1
- package/dist/esm/charts/series/series.d.ts +6 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/interaction/axis-indicators.d.ts +11 -0
- package/dist/esm/interaction/host-sink-dom.d.ts +3 -2
- package/dist/esm/interaction/host-sink-message.d.ts +3 -2
- package/dist/esm/interaction/lazy-tooltip.d.ts +2 -2
- package/dist/esm/interaction/legend-controller.d.ts +246 -0
- package/dist/esm/interaction/tooltip-controller.d.ts +20 -95
- package/dist/esm/interaction/tooltip-grid.d.ts +33 -0
- package/dist/esm/layout/facet-grid.d.ts +6 -0
- package/dist/esm/layout/ticks.d.ts +1 -0
- package/dist/esm/map/tile-source.d.ts +77 -8
- package/dist/esm/perspective-viewer-charts.js +1 -2
- package/dist/esm/perspective-viewer-charts.js.map +4 -4
- package/dist/esm/plugin/charts.d.ts +2 -0
- package/dist/esm/plugin/format-defaults.d.ts +9 -0
- package/dist/esm/plugin/plugin.d.ts +20 -0
- package/dist/esm/theme/gradient.d.ts +1 -0
- package/dist/esm/transport/protocol.d.ts +45 -3
- package/dist/esm/transport/renderer-transport.d.ts +3 -0
- package/dist/esm/worker/renderer.worker.d.ts +19 -1
- package/package.json +6 -6
- package/src/css/perspective-viewer-charts.css +0 -1
- package/src/ts/axis/bar-axis.ts +2 -7
- package/src/ts/axis/legend.ts +351 -24
- package/src/ts/axis/map-ticks.ts +94 -0
- package/src/ts/axis/numeric-axis.ts +2 -9
- package/src/ts/charts/candlestick/candlestick-interact.ts +46 -40
- package/src/ts/charts/candlestick/candlestick-render.ts +73 -10
- package/src/ts/charts/cartesian/cartesian-interact.ts +9 -3
- package/src/ts/charts/cartesian/cartesian-render.ts +369 -75
- package/src/ts/charts/cartesian/cartesian.ts +9 -0
- package/src/ts/charts/cartesian/glyph.ts +6 -2
- package/src/ts/charts/cartesian/glyphs/density.ts +2 -2
- package/src/ts/charts/cartesian/glyphs/lines.ts +15 -11
- package/src/ts/charts/cartesian/glyphs/points.ts +9 -5
- package/src/ts/charts/cartesian/tooltip-lines.ts +8 -8
- package/src/ts/charts/chart-base.ts +34 -32
- package/src/ts/charts/chart.ts +106 -7
- package/src/ts/charts/common/draw-tooltip-box.ts +13 -30
- package/src/ts/charts/common/tree-chrome.ts +102 -23
- package/src/ts/charts/common/tree-interact.ts +18 -17
- package/src/ts/charts/heatmap/heatmap-interact.ts +65 -7
- package/src/ts/charts/heatmap/heatmap-render.ts +100 -24
- package/src/ts/charts/map/map.ts +3 -5
- package/src/ts/charts/series/series-build.ts +14 -2
- package/src/ts/charts/series/series-interact.ts +12 -11
- package/src/ts/charts/series/series-render.ts +305 -132
- package/src/ts/charts/series/series.ts +10 -0
- package/src/ts/charts/sunburst/sunburst-render.ts +7 -1
- package/src/ts/charts/treemap/treemap-render.ts +7 -1
- package/src/ts/data/view-reader.ts +1 -4
- package/src/ts/index.ts +2 -0
- package/src/ts/interaction/axis-indicators.ts +137 -0
- package/src/ts/interaction/host-sink-dom.ts +66 -2
- package/src/ts/interaction/host-sink-message.ts +8 -2
- package/src/ts/interaction/lazy-tooltip.ts +5 -5
- package/src/ts/interaction/legend-controller.ts +1084 -0
- package/src/ts/interaction/tooltip-controller.ts +43 -131
- package/src/ts/interaction/tooltip-grid.ts +187 -0
- package/src/ts/layout/facet-grid.ts +8 -1
- package/src/ts/layout/ticks.ts +12 -0
- package/src/ts/map/tile-loader.ts +16 -1
- package/src/ts/map/tile-source.ts +199 -53
- package/src/ts/map/tile-sources.json +18 -0
- package/src/ts/plugin/charts.ts +40 -6
- package/src/ts/plugin/format-defaults.ts +37 -0
- package/src/ts/plugin/plugin.ts +236 -67
- package/src/ts/shaders/line.vert.glsl +5 -2
- package/src/ts/shaders/scatter.vert.glsl +0 -7
- package/src/ts/theme/gradient.ts +24 -0
- package/src/ts/transport/protocol.ts +46 -2
- package/src/ts/transport/renderer-transport.ts +83 -4
- package/src/ts/worker/dispatch.ts +6 -2
- package/src/ts/worker/renderer.worker.ts +78 -2
|
@@ -14,6 +14,7 @@ import type { Client, View, ViewConfig } from "@perspective-dev/client";
|
|
|
14
14
|
import type { FacetConfig, PluginConfig } from "../charts/chart";
|
|
15
15
|
import type {
|
|
16
16
|
ControlMsg,
|
|
17
|
+
ErrorMsg,
|
|
17
18
|
InitMsg,
|
|
18
19
|
InteractionEvent,
|
|
19
20
|
LoadAndRenderMsg,
|
|
@@ -26,6 +27,7 @@ import {
|
|
|
26
27
|
} from "../event-detail";
|
|
27
28
|
import { snapshotThemeVars } from "../theme/theme-snapshot";
|
|
28
29
|
import { snapshotFontFaces } from "../utils/font-snapshot";
|
|
30
|
+
import { TILE_SOURCES } from "../map/tile-source";
|
|
29
31
|
import { DomHostSink } from "../interaction/host-sink-dom";
|
|
30
32
|
import { RUNTIME_MODE } from "../config";
|
|
31
33
|
|
|
@@ -72,6 +74,24 @@ async function getSharedWorker(): Promise<Worker> {
|
|
|
72
74
|
const env = e.data as WorkerEnvelope;
|
|
73
75
|
HOST_LISTENERS.get(env.sessionId)?.(env.msg);
|
|
74
76
|
});
|
|
77
|
+
|
|
78
|
+
let poisoned = false;
|
|
79
|
+
w.addEventListener("error", (event: ErrorEvent) => {
|
|
80
|
+
if (poisoned) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
poisoned = true;
|
|
85
|
+
SHARED_WORKER = null;
|
|
86
|
+
const detail = event.message || `failed to load ${url}`;
|
|
87
|
+
const message = `perspective-viewer-charts renderer worker error: ${detail}`;
|
|
88
|
+
console.error(message, event.error ?? "");
|
|
89
|
+
const errorMsg: ErrorMsg = { kind: "error", message };
|
|
90
|
+
for (const listener of HOST_LISTENERS.values()) {
|
|
91
|
+
listener(errorMsg);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
75
95
|
return w;
|
|
76
96
|
})();
|
|
77
97
|
|
|
@@ -156,6 +176,10 @@ export class RendererTransport {
|
|
|
156
176
|
private _pendingCounter = 0;
|
|
157
177
|
private _onZoomChanged: ((isDefault: boolean) => void) | null = null;
|
|
158
178
|
|
|
179
|
+
private _onPluginConfigDelta:
|
|
180
|
+
| ((fields: Record<string, string | number | boolean>) => void)
|
|
181
|
+
| null = null;
|
|
182
|
+
|
|
159
183
|
/**
|
|
160
184
|
* Cached zoom-default flag pushed by the renderer after each zoom
|
|
161
185
|
* mutation. Surfaced sync via `allZoomsDefault()`; updates between
|
|
@@ -235,6 +259,9 @@ export class RendererTransport {
|
|
|
235
259
|
maxCells: number;
|
|
236
260
|
precompileShaders?: boolean;
|
|
237
261
|
onZoomChanged?: (isDefault: boolean) => void;
|
|
262
|
+
onPluginConfigDelta?: (
|
|
263
|
+
fields: Record<string, string | number | boolean>,
|
|
264
|
+
) => void;
|
|
238
265
|
}) {
|
|
239
266
|
this._client = opts.client;
|
|
240
267
|
this._view = opts.view;
|
|
@@ -246,6 +273,7 @@ export class RendererTransport {
|
|
|
246
273
|
this._maxCells = opts.maxCells;
|
|
247
274
|
this._precompileShaders = opts.precompileShaders ?? false;
|
|
248
275
|
this._onZoomChanged = opts.onZoomChanged ?? null;
|
|
276
|
+
this._onPluginConfigDelta = opts.onPluginConfigDelta ?? null;
|
|
249
277
|
this._ready = new Promise((resolve, reject) => {
|
|
250
278
|
this._resolveReady = resolve;
|
|
251
279
|
this._rejectReady = reject;
|
|
@@ -351,6 +379,9 @@ export class RendererTransport {
|
|
|
351
379
|
pluginConfig: opts.pluginConfig,
|
|
352
380
|
columnsConfig: opts.columnsConfig,
|
|
353
381
|
defaultChartType: opts.defaultChartType,
|
|
382
|
+
tileSource: TILE_SOURCES.specFor(
|
|
383
|
+
opts.pluginConfig.map_tile_provider,
|
|
384
|
+
),
|
|
354
385
|
themeVars,
|
|
355
386
|
fontFaces,
|
|
356
387
|
cssWidth: rect.width,
|
|
@@ -488,7 +519,11 @@ export class RendererTransport {
|
|
|
488
519
|
}
|
|
489
520
|
|
|
490
521
|
setPluginConfig(cfg: PluginConfig): void {
|
|
491
|
-
this._post({
|
|
522
|
+
this._post({
|
|
523
|
+
kind: "setPluginConfig",
|
|
524
|
+
cfg,
|
|
525
|
+
tileSource: TILE_SOURCES.specFor(cfg.map_tile_provider),
|
|
526
|
+
});
|
|
492
527
|
}
|
|
493
528
|
|
|
494
529
|
setBufferMaxCapacity(n: number): void {
|
|
@@ -807,7 +842,12 @@ export class RendererTransport {
|
|
|
807
842
|
this._resolvePending(msg.requestId, "saveZoom", msg.state);
|
|
808
843
|
break;
|
|
809
844
|
case "pinTooltip":
|
|
810
|
-
this._ensureHostSink()?.pin(
|
|
845
|
+
this._ensureHostSink()?.pin(
|
|
846
|
+
msg.grid,
|
|
847
|
+
msg.pos,
|
|
848
|
+
msg.bounds,
|
|
849
|
+
msg.style,
|
|
850
|
+
);
|
|
811
851
|
break;
|
|
812
852
|
case "dismissTooltip":
|
|
813
853
|
this._hostSink?.dismiss();
|
|
@@ -815,6 +855,9 @@ export class RendererTransport {
|
|
|
815
855
|
case "setCursor":
|
|
816
856
|
this._ensureHostSink()?.setCursor(msg.cursor);
|
|
817
857
|
break;
|
|
858
|
+
case "pluginConfigDelta":
|
|
859
|
+
this._onPluginConfigDelta?.(msg.fields);
|
|
860
|
+
break;
|
|
818
861
|
case "userClick":
|
|
819
862
|
this._dispatchOnViewer(
|
|
820
863
|
new CustomEvent<PerspectiveClickDetail>(
|
|
@@ -878,13 +921,35 @@ export class RendererTransport {
|
|
|
878
921
|
this._rejectReady(new Error(msg.message));
|
|
879
922
|
break;
|
|
880
923
|
case "loadAndRenderAck":
|
|
881
|
-
|
|
924
|
+
if (msg.error !== undefined) {
|
|
925
|
+
this._rejectPending(
|
|
926
|
+
msg.msgId,
|
|
927
|
+
"loadAndRender",
|
|
928
|
+
new Error(msg.error),
|
|
929
|
+
);
|
|
930
|
+
} else {
|
|
931
|
+
this._resolvePending(msg.msgId, "loadAndRender", undefined);
|
|
932
|
+
}
|
|
933
|
+
|
|
882
934
|
break;
|
|
883
935
|
case "resizeAck":
|
|
884
936
|
this._resolvePending(msg.msgId, "resize", undefined);
|
|
885
937
|
break;
|
|
886
938
|
case "snapshotPngReply":
|
|
887
|
-
|
|
939
|
+
if (msg.error !== undefined) {
|
|
940
|
+
this._rejectPending(
|
|
941
|
+
msg.requestId,
|
|
942
|
+
"snapshotPng",
|
|
943
|
+
new Error(msg.error),
|
|
944
|
+
);
|
|
945
|
+
} else {
|
|
946
|
+
this._resolvePending(
|
|
947
|
+
msg.requestId,
|
|
948
|
+
"snapshotPng",
|
|
949
|
+
msg.blob,
|
|
950
|
+
);
|
|
951
|
+
}
|
|
952
|
+
|
|
888
953
|
break;
|
|
889
954
|
}
|
|
890
955
|
}
|
|
@@ -910,6 +975,20 @@ export class RendererTransport {
|
|
|
910
975
|
entry.resolve(value);
|
|
911
976
|
}
|
|
912
977
|
|
|
978
|
+
private _rejectPending(
|
|
979
|
+
id: number,
|
|
980
|
+
kind: PendingRenderType,
|
|
981
|
+
error: Error,
|
|
982
|
+
): void {
|
|
983
|
+
const entry = this._pending.get(id);
|
|
984
|
+
if (!entry || entry.kind !== kind) {
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
this._pending.delete(id);
|
|
989
|
+
entry.reject(error);
|
|
990
|
+
}
|
|
991
|
+
|
|
913
992
|
/**
|
|
914
993
|
* Blit-mode handler: draw a renderer-emitted frame into the
|
|
915
994
|
* visible 2D-context display canvas, then close the bitmap so its
|
|
@@ -28,7 +28,7 @@ export function dispatch(r: WorkerRenderer, msg: ControlMsg): void {
|
|
|
28
28
|
r.chartImpl.setColumnsConfig?.(msg.cfg);
|
|
29
29
|
break;
|
|
30
30
|
case "setPluginConfig":
|
|
31
|
-
r.
|
|
31
|
+
r.setPluginConfig(msg.cfg, msg.tileSource);
|
|
32
32
|
r.redraw();
|
|
33
33
|
break;
|
|
34
34
|
case "setBufferMaxCapacity":
|
|
@@ -93,7 +93,11 @@ export function dispatch(r: WorkerRenderer, msg: ControlMsg): void {
|
|
|
93
93
|
r.post({ kind: "snapshotPngReply", requestId, blob });
|
|
94
94
|
})
|
|
95
95
|
.catch((err) => {
|
|
96
|
-
r.post({
|
|
96
|
+
r.post({
|
|
97
|
+
kind: "snapshotPngReply",
|
|
98
|
+
requestId,
|
|
99
|
+
error: String(err),
|
|
100
|
+
});
|
|
97
101
|
});
|
|
98
102
|
break;
|
|
99
103
|
}
|
|
@@ -18,7 +18,7 @@ import type * as wasm_module_type from "@perspective-dev/viewer/dist/wasm/perspe
|
|
|
18
18
|
import { WebGLContextManager } from "../webgl/context-manager";
|
|
19
19
|
import { ContextPool } from "../webgl/context-pool";
|
|
20
20
|
import { RENDER_CONTEXT_POOL_SIZE } from "../config";
|
|
21
|
-
import { ChartImplementation } from "../charts/chart";
|
|
21
|
+
import { ChartImplementation, type PluginConfig } from "../charts/chart";
|
|
22
22
|
import { ZoomController } from "../interaction/zoom-controller";
|
|
23
23
|
import {
|
|
24
24
|
applyPan,
|
|
@@ -36,6 +36,7 @@ import type {
|
|
|
36
36
|
WorkerMsg,
|
|
37
37
|
} from "../transport/protocol";
|
|
38
38
|
import { viewToColumnDataMap } from "../data/view-reader";
|
|
39
|
+
import { TILE_SOURCES, type TileSourceSpec } from "../map/tile-source";
|
|
39
40
|
import { loadFontDeduped } from "./font-loader";
|
|
40
41
|
import { dispatch } from "./dispatch";
|
|
41
42
|
import { installSessionHost } from "./session-host";
|
|
@@ -166,6 +167,13 @@ export class WorkerRenderer {
|
|
|
166
167
|
|
|
167
168
|
this.chartImpl = new ImplClass();
|
|
168
169
|
|
|
170
|
+
// Registry write must precede the chart impl's first
|
|
171
|
+
// `setPluginConfig` — `pluginConfig.map_tile_provider` may
|
|
172
|
+
// name this runtime-registered source.
|
|
173
|
+
if (msg.tileSource) {
|
|
174
|
+
TILE_SOURCES.register(msg.tileSource);
|
|
175
|
+
}
|
|
176
|
+
|
|
169
177
|
// Three surfaces, by mode:
|
|
170
178
|
// - direct: the host's transferred `.webgl-canvas` (1:1 with a
|
|
171
179
|
// context, permanently).
|
|
@@ -253,6 +261,20 @@ export class WorkerRenderer {
|
|
|
253
261
|
this.chartImpl.setView?.(this.view);
|
|
254
262
|
}
|
|
255
263
|
|
|
264
|
+
/**
|
|
265
|
+
* Registry write precedes the config apply — `cfg` may name the
|
|
266
|
+
* spec riding alongside it (see `SetPluginConfigMsg.tileSource`),
|
|
267
|
+
* and a replaced template must be registered first so the map
|
|
268
|
+
* chart's rebind sees the new content-derived cache id.
|
|
269
|
+
*/
|
|
270
|
+
setPluginConfig(cfg: PluginConfig, tileSource?: TileSourceSpec): void {
|
|
271
|
+
if (tileSource) {
|
|
272
|
+
TILE_SOURCES.register(tileSource);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
this.chartImpl.setPluginConfig?.(cfg);
|
|
276
|
+
}
|
|
277
|
+
|
|
256
278
|
/**
|
|
257
279
|
* Full data-fetch + render pipeline. Owns every `Client`/`Table`/
|
|
258
280
|
* `View` await on the render path:
|
|
@@ -283,6 +305,7 @@ export class WorkerRenderer {
|
|
|
283
305
|
*/
|
|
284
306
|
async loadAndRender(msg: LoadAndRenderMsg): Promise<void> {
|
|
285
307
|
const myGen = ++this._renderGen;
|
|
308
|
+
let error: string | undefined;
|
|
286
309
|
try {
|
|
287
310
|
const [numRows, schema, exprSchema, tableSchema] =
|
|
288
311
|
await Promise.all([
|
|
@@ -352,9 +375,10 @@ export class WorkerRenderer {
|
|
|
352
375
|
} catch (err) {
|
|
353
376
|
if ((err + "").indexOf("View not found") === -1) {
|
|
354
377
|
console.error("loadAndRender failed", err);
|
|
378
|
+
error = String(err);
|
|
355
379
|
}
|
|
356
380
|
} finally {
|
|
357
|
-
this.post({ kind: "loadAndRenderAck", msgId: msg.msgId });
|
|
381
|
+
this.post({ kind: "loadAndRenderAck", msgId: msg.msgId, error });
|
|
358
382
|
}
|
|
359
383
|
}
|
|
360
384
|
|
|
@@ -523,7 +547,59 @@ export class WorkerRenderer {
|
|
|
523
547
|
return { controller: this.zoomController, layout };
|
|
524
548
|
}
|
|
525
549
|
|
|
550
|
+
/**
|
|
551
|
+
* Legend-first interaction routing. The chart's `LegendController`
|
|
552
|
+
* sees every forwarded event BEFORE the zoom / tooltip paths, so
|
|
553
|
+
* legend gestures (scroll, width drag, floating move / resize)
|
|
554
|
+
* structurally preempt plot pan / zoom / hover — a wheel over the
|
|
555
|
+
* legend can never zoom the plot under it, and a floating-panel
|
|
556
|
+
* drag can never start a pan.
|
|
557
|
+
*/
|
|
558
|
+
private _legendInteraction(event: InteractionEvent): boolean {
|
|
559
|
+
const chart = this.chartImpl as any;
|
|
560
|
+
const legend = chart?._legend;
|
|
561
|
+
const cfg = chart?._pluginConfig;
|
|
562
|
+
if (!legend || !cfg) {
|
|
563
|
+
return false;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
return legend.handleEvent(event, {
|
|
567
|
+
cfg,
|
|
568
|
+
cssWidth: this.cssWidth,
|
|
569
|
+
cssHeight: this.cssHeight,
|
|
570
|
+
legendRects: chart._legendRects ?? [],
|
|
571
|
+
repaint: (relayout: boolean) => {
|
|
572
|
+
if (!relayout && typeof chart.repaintChrome === "function") {
|
|
573
|
+
chart.repaintChrome();
|
|
574
|
+
} else {
|
|
575
|
+
this.chartImpl.requestRender(this.glManager);
|
|
576
|
+
}
|
|
577
|
+
},
|
|
578
|
+
postDelta: (fields: Record<string, string | number | boolean>) =>
|
|
579
|
+
this.post({ kind: "pluginConfigDelta", fields }),
|
|
580
|
+
setCursor: (cursor: string) => chart._hostSink?.setCursor?.(cursor),
|
|
581
|
+
dispatchLeave: () => this._tooltip()?.dispatchLeave(),
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
|
|
526
585
|
onInteraction(event: InteractionEvent): void {
|
|
586
|
+
try {
|
|
587
|
+
this._routeInteraction(event);
|
|
588
|
+
} catch (err) {
|
|
589
|
+
this._dragTarget = null;
|
|
590
|
+
(this.chartImpl as any)?._legend?.cancelGesture?.();
|
|
591
|
+
console.error("interaction dispatch failed", err);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
private _routeInteraction(event: InteractionEvent): void {
|
|
596
|
+
const plotDragging =
|
|
597
|
+
this._dragTarget !== null &&
|
|
598
|
+
(event.type === "pointermove" || event.type === "pointerup");
|
|
599
|
+
if (!plotDragging && this._legendInteraction(event)) {
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
|
|
527
603
|
switch (event.type) {
|
|
528
604
|
case "wheel": {
|
|
529
605
|
const target = this._resolveTarget(event.mx, event.my);
|