@perspective-dev/viewer-charts 4.5.0 → 4.5.2
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/LICENSE.md +193 -0
- package/dist/cdn/perspective-viewer-charts.js +2 -2
- package/dist/cdn/perspective-viewer-charts.js.map +3 -3
- package/dist/esm/axis/bar-axis.d.ts +9 -1
- package/dist/esm/axis/categorical-axis.d.ts +0 -2
- package/dist/esm/charts/cartesian/cartesian.d.ts +26 -0
- package/dist/esm/charts/common/category-axis-resolver.d.ts +43 -1
- package/dist/esm/charts/common/expand-domain.d.ts +20 -0
- package/dist/esm/charts/common/tree-chart.d.ts +7 -0
- package/dist/esm/charts/common/tree-chrome.d.ts +23 -1
- package/dist/esm/charts/common/tree-interact.d.ts +46 -0
- package/dist/esm/charts/series/glyphs/draw-lines.d.ts +11 -4
- package/dist/esm/charts/series/series-build.d.ts +38 -2
- package/dist/esm/charts/series/series-render.d.ts +1 -4
- package/dist/esm/charts/series/series-type.d.ts +19 -17
- package/dist/esm/charts/series/series.d.ts +16 -0
- package/dist/esm/charts/sunburst/sunburst-interact.d.ts +1 -1
- package/dist/esm/charts/treemap/treemap-interact.d.ts +1 -6
- package/dist/esm/config.d.ts +19 -0
- package/dist/esm/interaction/host-sink-message.d.ts +10 -28
- package/dist/esm/interaction/raw-event-forwarder.d.ts +6 -7
- package/dist/esm/interaction/zoom-controller.d.ts +31 -20
- package/dist/esm/interaction/zoom-router.d.ts +3 -26
- package/dist/esm/perspective-viewer-charts.js +2 -2
- package/dist/esm/perspective-viewer-charts.js.map +3 -3
- package/dist/esm/plugin/plugin.d.ts +15 -3
- package/dist/esm/render/scheduler.d.ts +23 -0
- package/dist/esm/theme/palette.d.ts +0 -5
- package/dist/esm/transport/protocol.d.ts +2 -7
- package/dist/esm/webgl/context-manager.d.ts +100 -117
- package/dist/esm/webgl/context-pool.d.ts +40 -0
- package/dist/esm/webgl/gl-context.d.ts +54 -0
- package/dist/esm/worker/renderer.worker.d.ts +2 -4
- package/package.json +45 -45
- package/src/ts/axis/bar-axis.ts +74 -45
- package/src/ts/axis/categorical-axis.ts +0 -2
- package/src/ts/charts/candlestick/candlestick-render.ts +10 -7
- package/src/ts/charts/candlestick/candlestick.ts +10 -29
- package/src/ts/charts/candlestick/glyphs/draw-candlesticks.ts +36 -2
- package/src/ts/charts/candlestick/glyphs/draw-ohlc.ts +36 -2
- package/src/ts/charts/cartesian/cartesian-build.ts +143 -9
- package/src/ts/charts/cartesian/cartesian-render.ts +205 -30
- package/src/ts/charts/cartesian/cartesian.ts +43 -4
- package/src/ts/charts/cartesian/glyphs/density.ts +36 -41
- package/src/ts/charts/cartesian/glyphs/lines.ts +13 -15
- package/src/ts/charts/cartesian/glyphs/points.ts +12 -17
- package/src/ts/charts/chart-base.ts +20 -6
- package/src/ts/charts/chart.ts +1 -1
- package/src/ts/charts/common/category-axis-resolver.ts +135 -1
- package/src/ts/charts/common/expand-domain.ts +40 -0
- package/src/ts/charts/common/tree-chart.ts +16 -0
- package/src/ts/charts/common/tree-chrome.ts +86 -1
- package/src/ts/charts/common/tree-interact.ts +209 -0
- package/src/ts/charts/heatmap/heatmap-render.ts +9 -11
- package/src/ts/charts/series/glyphs/draw-areas.ts +30 -1
- package/src/ts/charts/series/glyphs/draw-lines.ts +151 -76
- package/src/ts/charts/series/series-build.ts +394 -21
- package/src/ts/charts/series/series-render.ts +159 -38
- package/src/ts/charts/series/series-type.ts +37 -17
- package/src/ts/charts/series/series.ts +63 -68
- package/src/ts/charts/sunburst/sunburst-interact.ts +18 -162
- package/src/ts/charts/sunburst/sunburst-render.ts +24 -89
- package/src/ts/charts/sunburst/sunburst.ts +1 -15
- package/src/ts/charts/treemap/treemap-interact.ts +22 -189
- package/src/ts/charts/treemap/treemap-render.ts +19 -46
- package/src/ts/charts/treemap/treemap.ts +1 -16
- package/src/ts/config.ts +21 -1
- package/src/ts/interaction/host-sink-message.ts +33 -22
- package/src/ts/interaction/raw-event-forwarder.ts +10 -12
- package/src/ts/interaction/zoom-controller.ts +120 -83
- package/src/ts/interaction/zoom-router.ts +3 -126
- package/src/ts/map/tile-layer.ts +13 -13
- package/src/ts/plugin/plugin.ts +176 -196
- package/src/ts/render/scheduler.ts +125 -42
- package/src/ts/shaders/line-uniform.frag.glsl +2 -1
- package/src/ts/shaders/line-uniform.vert.glsl +19 -0
- package/src/ts/theme/palette.ts +1 -4
- package/src/ts/transport/protocol.ts +3 -8
- package/src/ts/webgl/context-manager.ts +195 -189
- package/src/ts/webgl/context-pool.ts +78 -0
- package/src/ts/webgl/gl-context.ts +191 -0
- package/src/ts/worker/dispatch.ts +0 -1
- package/src/ts/worker/renderer.worker.ts +72 -65
- package/src/ts/worker/session-host.ts +29 -0
|
@@ -24,134 +24,11 @@ export interface ZoomTarget {
|
|
|
24
24
|
controller: ZoomController;
|
|
25
25
|
layout: PlotLayout;
|
|
26
26
|
}
|
|
27
|
-
export type ZoomTargetResolver = (mx: number, my: number) => ZoomTarget | null;
|
|
28
27
|
|
|
29
28
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* controllers (one per facet) can coexist on a single canvas.
|
|
34
|
-
*/
|
|
35
|
-
export class ZoomRouter {
|
|
36
|
-
private _element: HTMLElement | null = null;
|
|
37
|
-
private _resolve: ZoomTargetResolver | null = null;
|
|
38
|
-
private _onUpdate: (() => void) | null = null;
|
|
39
|
-
|
|
40
|
-
private _pointerDown = false;
|
|
41
|
-
private _pointerTarget: ZoomTarget | null = null;
|
|
42
|
-
private _lastPointerX = 0;
|
|
43
|
-
private _lastPointerY = 0;
|
|
44
|
-
|
|
45
|
-
private _onWheel: ((e: WheelEvent) => void) | null = null;
|
|
46
|
-
private _onPointerDown: ((e: PointerEvent) => void) | null = null;
|
|
47
|
-
private _onPointerMove: ((e: PointerEvent) => void) | null = null;
|
|
48
|
-
private _onPointerUp: ((e: PointerEvent) => void) | null = null;
|
|
49
|
-
|
|
50
|
-
attach(
|
|
51
|
-
element: HTMLElement,
|
|
52
|
-
resolve: ZoomTargetResolver,
|
|
53
|
-
onUpdate: () => void,
|
|
54
|
-
): void {
|
|
55
|
-
this.detach();
|
|
56
|
-
this._element = element;
|
|
57
|
-
this._resolve = resolve;
|
|
58
|
-
this._onUpdate = onUpdate;
|
|
59
|
-
|
|
60
|
-
this._onWheel = (e: WheelEvent) => {
|
|
61
|
-
const rect = element.getBoundingClientRect();
|
|
62
|
-
const mouseX = e.clientX - rect.left;
|
|
63
|
-
const mouseY = e.clientY - rect.top;
|
|
64
|
-
const target = resolve(mouseX, mouseY);
|
|
65
|
-
if (!target) {
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
e.preventDefault();
|
|
70
|
-
applyWheel(target, mouseX, mouseY, e.deltaY);
|
|
71
|
-
onUpdate();
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
this._onPointerDown = (e: PointerEvent) => {
|
|
75
|
-
const rect = element.getBoundingClientRect();
|
|
76
|
-
const mouseX = e.clientX - rect.left;
|
|
77
|
-
const mouseY = e.clientY - rect.top;
|
|
78
|
-
const target = resolve(mouseX, mouseY);
|
|
79
|
-
if (!target) {
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
this._pointerDown = true;
|
|
84
|
-
this._pointerTarget = target;
|
|
85
|
-
this._lastPointerX = e.clientX;
|
|
86
|
-
this._lastPointerY = e.clientY;
|
|
87
|
-
element.setPointerCapture(e.pointerId);
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
this._onPointerMove = (e: PointerEvent) => {
|
|
91
|
-
if (!this._pointerDown || !this._pointerTarget) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const dx = e.clientX - this._lastPointerX;
|
|
96
|
-
const dy = e.clientY - this._lastPointerY;
|
|
97
|
-
this._lastPointerX = e.clientX;
|
|
98
|
-
this._lastPointerY = e.clientY;
|
|
99
|
-
applyPan(this._pointerTarget, dx, dy);
|
|
100
|
-
onUpdate();
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
this._onPointerUp = () => {
|
|
104
|
-
this._pointerDown = false;
|
|
105
|
-
this._pointerTarget = null;
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
element.addEventListener("wheel", this._onWheel, { passive: false });
|
|
109
|
-
element.addEventListener("pointerdown", this._onPointerDown);
|
|
110
|
-
element.addEventListener("pointermove", this._onPointerMove);
|
|
111
|
-
element.addEventListener("pointerup", this._onPointerUp);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
detach(): void {
|
|
115
|
-
if (this._element) {
|
|
116
|
-
if (this._onWheel) {
|
|
117
|
-
this._element.removeEventListener("wheel", this._onWheel);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (this._onPointerDown) {
|
|
121
|
-
this._element.removeEventListener(
|
|
122
|
-
"pointerdown",
|
|
123
|
-
this._onPointerDown,
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (this._onPointerMove) {
|
|
128
|
-
this._element.removeEventListener(
|
|
129
|
-
"pointermove",
|
|
130
|
-
this._onPointerMove,
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (this._onPointerUp) {
|
|
135
|
-
this._element.removeEventListener(
|
|
136
|
-
"pointerup",
|
|
137
|
-
this._onPointerUp,
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
this._element = null;
|
|
143
|
-
this._resolve = null;
|
|
144
|
-
this._onUpdate = null;
|
|
145
|
-
this._pointerDown = false;
|
|
146
|
-
this._pointerTarget = null;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Apply a wheel-zoom delta to the resolved target. Exported for
|
|
152
|
-
* re-use inside the worker, where the renderer dispatches interaction
|
|
153
|
-
* events forwarded from the host's `RawEventForwarder` instead of
|
|
154
|
-
* receiving DOM events directly.
|
|
29
|
+
* Apply a wheel-zoom delta to the resolved target. The worker
|
|
30
|
+
* dispatches interaction events forwarded from the host's
|
|
31
|
+
* `RawEventForwarder` and calls this directly.
|
|
155
32
|
*/
|
|
156
33
|
export function applyWheel(
|
|
157
34
|
target: ZoomTarget,
|
package/src/ts/map/tile-layer.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { TileLoader, tileKey } from "./tile-loader";
|
|
|
18
18
|
import type { TileSource } from "./tile-source";
|
|
19
19
|
import tileVert from "../shaders/tile.vert.glsl";
|
|
20
20
|
import tileFrag from "../shaders/tile.frag.glsl";
|
|
21
|
+
import { compileProgram } from "../webgl/program-cache";
|
|
21
22
|
|
|
22
23
|
type GL = WebGL2RenderingContext | WebGLRenderingContext;
|
|
23
24
|
|
|
@@ -344,24 +345,23 @@ export class TileLayer {
|
|
|
344
345
|
}
|
|
345
346
|
|
|
346
347
|
const gl = glManager.gl;
|
|
347
|
-
|
|
348
|
+
this._program = compileProgram<TileProgramCache>(
|
|
349
|
+
glManager,
|
|
348
350
|
"map-tile",
|
|
349
351
|
tileVert,
|
|
350
352
|
tileFrag,
|
|
353
|
+
[
|
|
354
|
+
"u_projection",
|
|
355
|
+
"u_extent_min",
|
|
356
|
+
"u_extent_max",
|
|
357
|
+
"u_uv_min",
|
|
358
|
+
"u_uv_max",
|
|
359
|
+
"u_tile",
|
|
360
|
+
"u_alpha",
|
|
361
|
+
],
|
|
362
|
+
["a_corner"],
|
|
351
363
|
);
|
|
352
364
|
|
|
353
|
-
this._program = {
|
|
354
|
-
program,
|
|
355
|
-
u_projection: gl.getUniformLocation(program, "u_projection"),
|
|
356
|
-
u_extent_min: gl.getUniformLocation(program, "u_extent_min"),
|
|
357
|
-
u_extent_max: gl.getUniformLocation(program, "u_extent_max"),
|
|
358
|
-
u_uv_min: gl.getUniformLocation(program, "u_uv_min"),
|
|
359
|
-
u_uv_max: gl.getUniformLocation(program, "u_uv_max"),
|
|
360
|
-
u_tile: gl.getUniformLocation(program, "u_tile"),
|
|
361
|
-
u_alpha: gl.getUniformLocation(program, "u_alpha"),
|
|
362
|
-
a_corner: gl.getAttribLocation(program, "a_corner"),
|
|
363
|
-
};
|
|
364
|
-
|
|
365
365
|
const buf = gl.createBuffer();
|
|
366
366
|
if (!buf) {
|
|
367
367
|
return;
|
package/src/ts/plugin/plugin.ts
CHANGED
|
@@ -38,173 +38,85 @@ import { RENDER_BLIT_MODE } from "../config";
|
|
|
38
38
|
const FACET_CONFIG_DEFAULTS: FacetConfig = { ...DEFAULT_FACET_CONFIG };
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
41
|
+
* Static UI-control spec per `plugin_config` field. Mirrors the shape
|
|
42
|
+
* `column_config_schema` already returns (datagrid). The runtime default
|
|
43
|
+
* is sourced separately from the chart-type-effective defaults at
|
|
44
|
+
* `fieldSpec` call time so per-chart overrides like
|
|
45
|
+
* `include_zero=true` for Y Bar / Y Area / X Bar surface in the UI.
|
|
45
46
|
*/
|
|
47
|
+
type FieldSpec =
|
|
48
|
+
| { kind: "Bool" }
|
|
49
|
+
| {
|
|
50
|
+
kind: "Enum";
|
|
51
|
+
variants: ReadonlyArray<{ value: string; label: string }>;
|
|
52
|
+
}
|
|
53
|
+
| { kind: "Number"; min: number; max: number; step?: number };
|
|
54
|
+
|
|
55
|
+
const FIELD_SCHEMAS: Record<PluginConfigField, FieldSpec> = {
|
|
56
|
+
auto_alt_y_axis: { kind: "Bool" },
|
|
57
|
+
include_zero: { kind: "Bool" },
|
|
58
|
+
domain_mode: {
|
|
59
|
+
kind: "Enum",
|
|
60
|
+
variants: [
|
|
61
|
+
{ value: "fit", label: "Fit" },
|
|
62
|
+
{ value: "expand", label: "Expand" },
|
|
63
|
+
],
|
|
64
|
+
},
|
|
65
|
+
facet_mode: {
|
|
66
|
+
kind: "Enum",
|
|
67
|
+
variants: [
|
|
68
|
+
{ value: "grid", label: "Grid" },
|
|
69
|
+
{ value: "overlay", label: "Overlay" },
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
facet_zoom_mode: {
|
|
73
|
+
kind: "Enum",
|
|
74
|
+
variants: [
|
|
75
|
+
{ value: "shared", label: "Shared" },
|
|
76
|
+
{ value: "independent", label: "Independent" },
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
series_zoom_mode: {
|
|
80
|
+
kind: "Enum",
|
|
81
|
+
variants: [
|
|
82
|
+
{ value: "dynamic", label: "Dynamic" },
|
|
83
|
+
{ value: "fixed", label: "Fixed" },
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
line_width_px: { kind: "Number", min: 0.5, step: 0.5, max: 16 },
|
|
87
|
+
point_size_px: { kind: "Number", min: 1, max: 32 },
|
|
88
|
+
band_inner_frac: { kind: "Number", min: 0.1, max: 1, step: 0.01 },
|
|
89
|
+
bar_inner_pad: { kind: "Number", min: 0, max: 0.9, step: 0.01 },
|
|
90
|
+
wick_width_px: { kind: "Number", min: 0.5, step: 0.5, max: 8 },
|
|
91
|
+
ohlc_line_width_px: { kind: "Number", min: 0.5, step: 0.5, max: 8 },
|
|
92
|
+
gradient_radius_px: { kind: "Number", min: 2, step: 1, max: 256 },
|
|
93
|
+
gradient_intensity: { kind: "Number", min: 0.05, step: 0.05, max: 4 },
|
|
94
|
+
gradient_heat_max: { kind: "Number", min: 0.1, step: 0.1, max: 64 },
|
|
95
|
+
gradient_color_mode: {
|
|
96
|
+
kind: "Enum",
|
|
97
|
+
variants: [
|
|
98
|
+
{ value: "mean", label: "Mean (density-weighted)" },
|
|
99
|
+
{ value: "density", label: "Density only" },
|
|
100
|
+
{ value: "extreme", label: "Extremes" },
|
|
101
|
+
{ value: "signed", label: "Signed sum" },
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
map_tile_provider: {
|
|
105
|
+
kind: "Enum",
|
|
106
|
+
variants: [
|
|
107
|
+
{ value: "carto-positron", label: "Light (Positron)" },
|
|
108
|
+
{ value: "carto-dark-matter", label: "Dark Matter" },
|
|
109
|
+
{ value: "carto-voyager", label: "Voyager" },
|
|
110
|
+
],
|
|
111
|
+
},
|
|
112
|
+
map_tile_alpha: { kind: "Number", min: 0, max: 1, step: 0.05 },
|
|
113
|
+
};
|
|
114
|
+
|
|
46
115
|
function fieldSpec(
|
|
47
116
|
key: PluginConfigField,
|
|
48
117
|
defaults: PluginConfig,
|
|
49
118
|
): Record<string, unknown> & { kind: string } {
|
|
50
|
-
|
|
51
|
-
case "auto_alt_y_axis":
|
|
52
|
-
return { kind: "Bool", key, default: defaults.auto_alt_y_axis };
|
|
53
|
-
case "include_zero":
|
|
54
|
-
return { kind: "Bool", key, default: defaults.include_zero };
|
|
55
|
-
case "domain_mode":
|
|
56
|
-
return {
|
|
57
|
-
kind: "Enum",
|
|
58
|
-
key,
|
|
59
|
-
default: defaults.domain_mode,
|
|
60
|
-
variants: [
|
|
61
|
-
{ value: "fit", label: "Fit" },
|
|
62
|
-
{ value: "expand", label: "Expand" },
|
|
63
|
-
],
|
|
64
|
-
};
|
|
65
|
-
case "facet_mode":
|
|
66
|
-
return {
|
|
67
|
-
kind: "Enum",
|
|
68
|
-
key,
|
|
69
|
-
default: DEFAULT_PLUGIN_CONFIG.facet_mode,
|
|
70
|
-
variants: [
|
|
71
|
-
{ value: "grid", label: "Grid" },
|
|
72
|
-
{ value: "overlay", label: "Overlay" },
|
|
73
|
-
],
|
|
74
|
-
};
|
|
75
|
-
case "facet_zoom_mode":
|
|
76
|
-
return {
|
|
77
|
-
kind: "Enum",
|
|
78
|
-
key,
|
|
79
|
-
default: DEFAULT_PLUGIN_CONFIG.facet_zoom_mode,
|
|
80
|
-
variants: [
|
|
81
|
-
{ value: "shared", label: "Shared" },
|
|
82
|
-
{ value: "independent", label: "Independent" },
|
|
83
|
-
],
|
|
84
|
-
};
|
|
85
|
-
case "series_zoom_mode":
|
|
86
|
-
return {
|
|
87
|
-
kind: "Enum",
|
|
88
|
-
key,
|
|
89
|
-
default: DEFAULT_PLUGIN_CONFIG.series_zoom_mode,
|
|
90
|
-
variants: [
|
|
91
|
-
{ value: "dynamic", label: "Dynamic" },
|
|
92
|
-
{ value: "fixed", label: "Fixed" },
|
|
93
|
-
],
|
|
94
|
-
};
|
|
95
|
-
case "line_width_px":
|
|
96
|
-
return {
|
|
97
|
-
kind: "Number",
|
|
98
|
-
key,
|
|
99
|
-
default: DEFAULT_PLUGIN_CONFIG.line_width_px,
|
|
100
|
-
min: 0.5,
|
|
101
|
-
step: 0.5,
|
|
102
|
-
max: 16,
|
|
103
|
-
};
|
|
104
|
-
case "point_size_px":
|
|
105
|
-
return {
|
|
106
|
-
kind: "Number",
|
|
107
|
-
key,
|
|
108
|
-
default: DEFAULT_PLUGIN_CONFIG.point_size_px,
|
|
109
|
-
min: 1,
|
|
110
|
-
max: 32,
|
|
111
|
-
};
|
|
112
|
-
case "band_inner_frac":
|
|
113
|
-
return {
|
|
114
|
-
kind: "Number",
|
|
115
|
-
key,
|
|
116
|
-
default: DEFAULT_PLUGIN_CONFIG.band_inner_frac,
|
|
117
|
-
min: 0.1,
|
|
118
|
-
max: 1,
|
|
119
|
-
step: 0.01,
|
|
120
|
-
};
|
|
121
|
-
case "bar_inner_pad":
|
|
122
|
-
return {
|
|
123
|
-
kind: "Number",
|
|
124
|
-
key,
|
|
125
|
-
default: DEFAULT_PLUGIN_CONFIG.bar_inner_pad,
|
|
126
|
-
min: 0,
|
|
127
|
-
max: 0.9,
|
|
128
|
-
step: 0.01,
|
|
129
|
-
};
|
|
130
|
-
case "wick_width_px":
|
|
131
|
-
return {
|
|
132
|
-
kind: "Number",
|
|
133
|
-
key,
|
|
134
|
-
default: DEFAULT_PLUGIN_CONFIG.wick_width_px,
|
|
135
|
-
min: 0.5,
|
|
136
|
-
step: 0.5,
|
|
137
|
-
max: 8,
|
|
138
|
-
};
|
|
139
|
-
case "ohlc_line_width_px":
|
|
140
|
-
return {
|
|
141
|
-
kind: "Number",
|
|
142
|
-
key,
|
|
143
|
-
default: DEFAULT_PLUGIN_CONFIG.ohlc_line_width_px,
|
|
144
|
-
min: 0.5,
|
|
145
|
-
step: 0.5,
|
|
146
|
-
max: 8,
|
|
147
|
-
};
|
|
148
|
-
case "gradient_radius_px":
|
|
149
|
-
return {
|
|
150
|
-
kind: "Number",
|
|
151
|
-
key,
|
|
152
|
-
default: DEFAULT_PLUGIN_CONFIG.gradient_radius_px,
|
|
153
|
-
min: 2,
|
|
154
|
-
step: 1,
|
|
155
|
-
max: 256,
|
|
156
|
-
};
|
|
157
|
-
case "gradient_intensity":
|
|
158
|
-
return {
|
|
159
|
-
kind: "Number",
|
|
160
|
-
key,
|
|
161
|
-
default: DEFAULT_PLUGIN_CONFIG.gradient_intensity,
|
|
162
|
-
min: 0.05,
|
|
163
|
-
step: 0.05,
|
|
164
|
-
max: 4,
|
|
165
|
-
};
|
|
166
|
-
case "gradient_heat_max":
|
|
167
|
-
return {
|
|
168
|
-
kind: "Number",
|
|
169
|
-
key,
|
|
170
|
-
default: DEFAULT_PLUGIN_CONFIG.gradient_heat_max,
|
|
171
|
-
min: 0.1,
|
|
172
|
-
step: 0.1,
|
|
173
|
-
max: 64,
|
|
174
|
-
};
|
|
175
|
-
case "gradient_color_mode":
|
|
176
|
-
return {
|
|
177
|
-
kind: "Enum",
|
|
178
|
-
key,
|
|
179
|
-
default: DEFAULT_PLUGIN_CONFIG.gradient_color_mode,
|
|
180
|
-
variants: [
|
|
181
|
-
{ value: "mean", label: "Mean (density-weighted)" },
|
|
182
|
-
{ value: "density", label: "Density only" },
|
|
183
|
-
{ value: "extreme", label: "Extremes" },
|
|
184
|
-
{ value: "signed", label: "Signed sum" },
|
|
185
|
-
],
|
|
186
|
-
};
|
|
187
|
-
case "map_tile_provider":
|
|
188
|
-
return {
|
|
189
|
-
kind: "Enum",
|
|
190
|
-
key,
|
|
191
|
-
default: DEFAULT_PLUGIN_CONFIG.map_tile_provider,
|
|
192
|
-
variants: [
|
|
193
|
-
{ value: "carto-positron", label: "Light (Positron)" },
|
|
194
|
-
{ value: "carto-dark-matter", label: "Dark Matter" },
|
|
195
|
-
{ value: "carto-voyager", label: "Voyager" },
|
|
196
|
-
],
|
|
197
|
-
};
|
|
198
|
-
case "map_tile_alpha":
|
|
199
|
-
return {
|
|
200
|
-
kind: "Number",
|
|
201
|
-
key,
|
|
202
|
-
default: DEFAULT_PLUGIN_CONFIG.map_tile_alpha,
|
|
203
|
-
min: 0,
|
|
204
|
-
max: 1,
|
|
205
|
-
step: 0.05,
|
|
206
|
-
};
|
|
207
|
-
}
|
|
119
|
+
return { ...FIELD_SCHEMAS[key], key, default: defaults[key] };
|
|
208
120
|
}
|
|
209
121
|
|
|
210
122
|
const GLOBAL_STYLES = (() => {
|
|
@@ -213,6 +125,22 @@ const GLOBAL_STYLES = (() => {
|
|
|
213
125
|
return [sheet];
|
|
214
126
|
})();
|
|
215
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Process-global GL presentation strategy, shared by *every* chart-type
|
|
130
|
+
* plugin in this renderer. Seeded from the build-time {@link RENDER_BLIT_MODE}
|
|
131
|
+
* and overridable at runtime via
|
|
132
|
+
* {@link HTMLPerspectiveViewerWebGLPluginElement.setBlitMode}.
|
|
133
|
+
*
|
|
134
|
+
* Module scope (not per-instance) on purpose: blit-vs-direct is a
|
|
135
|
+
* whole-renderer decision — in `"blit"` mode the worker shares a pool of
|
|
136
|
+
* GL contexts across all charts ([webgl/context-pool.ts]), so a page
|
|
137
|
+
* can't sensibly mix strategies per chart. Every per-chart-type subclass
|
|
138
|
+
* registered in [index.ts] reads this one value when it builds its
|
|
139
|
+
* renderer, so setting it once (before the first chart renders) applies
|
|
140
|
+
* to all of them.
|
|
141
|
+
*/
|
|
142
|
+
let BLIT_MODE: "direct" | "blit" = RENDER_BLIT_MODE;
|
|
143
|
+
|
|
216
144
|
export class HTMLPerspectiveViewerWebGLPluginElement
|
|
217
145
|
extends HTMLElement
|
|
218
146
|
implements IPerspectiveViewerPlugin
|
|
@@ -227,7 +155,6 @@ export class HTMLPerspectiveViewerWebGLPluginElement
|
|
|
227
155
|
private _rendererPromise: Promise<RendererTransport> | null = null;
|
|
228
156
|
private _rawEventForwarder: RawEventForwarder | null = null;
|
|
229
157
|
private _generation = 0;
|
|
230
|
-
private _renderBlitMode: "direct" | "blit" = RENDER_BLIT_MODE;
|
|
231
158
|
private _resetClickAbort: AbortController | null = null;
|
|
232
159
|
|
|
233
160
|
/**
|
|
@@ -345,13 +272,41 @@ export class HTMLPerspectiveViewerWebGLPluginElement
|
|
|
345
272
|
return this._rendererPromise;
|
|
346
273
|
}
|
|
347
274
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
275
|
+
// `_buildRenderer` is async — it awaits `getClient`, `getTable`,
|
|
276
|
+
// and a full worker handshake — so a `disconnectedCallback`
|
|
277
|
+
// (toggle / chart-type switch) frequently lands *before* this
|
|
278
|
+
// `.then` runs. At that point `this._renderer` is still null,
|
|
279
|
+
// so `delete()`'s `if (this._renderer)` teardown is skipped and
|
|
280
|
+
// the freshly-built transport (and its WebGL context) would be
|
|
281
|
+
// assigned to a detached element and leak — one context per
|
|
282
|
+
// raced toggle, until the browser evicts the oldest.
|
|
283
|
+
//
|
|
284
|
+
// `delete()` sets `_rendererPromise = null`, so promise identity
|
|
285
|
+
// is the dispose signal: if `this._rendererPromise` no longer
|
|
286
|
+
// points at *this* build when it resolves, the element was
|
|
287
|
+
// deleted (or a reconnect started a newer build) and we destroy
|
|
288
|
+
// the orphan instead of adopting it. Promise identity — not
|
|
289
|
+
// `_generation`, which every `draw`/`update` bumps — is the
|
|
290
|
+
// right token: a rapid draw→update must NOT tear down the
|
|
291
|
+
// single in-flight build it shares via this memoized promise.
|
|
292
|
+
const p: Promise<RendererTransport> = this._buildRenderer(view).then(
|
|
293
|
+
(r) => {
|
|
294
|
+
if (this._rendererPromise !== p) {
|
|
295
|
+
r.destroy();
|
|
296
|
+
throw new Error("renderer disposed during init");
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
this._renderer = r;
|
|
300
|
+
this._setupInteraction(r);
|
|
301
|
+
return r;
|
|
302
|
+
},
|
|
303
|
+
);
|
|
353
304
|
|
|
354
|
-
|
|
305
|
+
// Swallow the dispose rejection so it doesn't surface as an
|
|
306
|
+
// unhandled rejection; `_drawImpl` catches it and bails.
|
|
307
|
+
p.catch(() => {});
|
|
308
|
+
this._rendererPromise = p;
|
|
309
|
+
return p;
|
|
355
310
|
}
|
|
356
311
|
|
|
357
312
|
/**
|
|
@@ -438,15 +393,28 @@ export class HTMLPerspectiveViewerWebGLPluginElement
|
|
|
438
393
|
},
|
|
439
394
|
pluginConfig: this._pluginConfig,
|
|
440
395
|
defaultChartType: this._chartType.default_chart_type,
|
|
441
|
-
renderBlitMode:
|
|
396
|
+
renderBlitMode: BLIT_MODE,
|
|
442
397
|
});
|
|
443
398
|
|
|
444
399
|
return transport;
|
|
445
400
|
}
|
|
446
401
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
402
|
+
/**
|
|
403
|
+
* Select the GL presentation strategy for *all* chart-type plugins
|
|
404
|
+
* in this renderer. Static + process-global: the value is shared by
|
|
405
|
+
* every per-chart-type subclass, so it must be set once before the
|
|
406
|
+
* charts that should use it build their renderers (a renderer reads
|
|
407
|
+
* {@link BLIT_MODE} at construction in `_buildRenderer`; charts
|
|
408
|
+
* already built keep their mode until torn down and rebuilt).
|
|
409
|
+
*
|
|
410
|
+
* - `"direct"` — each chart owns a GL context 1:1 with its visible
|
|
411
|
+
* canvas (lowest latency; bounded by the browser's ~16-context
|
|
412
|
+
* cap).
|
|
413
|
+
* - `"blit"` — charts render off-screen and share a pool of GL
|
|
414
|
+
* contexts ([webgl/context-pool.ts]), so a page can exceed the cap.
|
|
415
|
+
*/
|
|
416
|
+
static setBlitMode(mode: "direct" | "blit") {
|
|
417
|
+
BLIT_MODE = mode;
|
|
450
418
|
}
|
|
451
419
|
|
|
452
420
|
get_static_config(): PluginStaticConfig {
|
|
@@ -519,6 +487,32 @@ export class HTMLPerspectiveViewerWebGLPluginElement
|
|
|
519
487
|
default: false,
|
|
520
488
|
});
|
|
521
489
|
}
|
|
490
|
+
|
|
491
|
+
// Line / area glyphs can bridge interior nulls by linear
|
|
492
|
+
// interpolation. Bar / scatter ignore the flag.
|
|
493
|
+
const supports_interpolate =
|
|
494
|
+
effective_chart_type === "line" ||
|
|
495
|
+
effective_chart_type === "area";
|
|
496
|
+
|
|
497
|
+
if (supports_interpolate) {
|
|
498
|
+
const variants =
|
|
499
|
+
effective_chart_type === "area"
|
|
500
|
+
? [
|
|
501
|
+
{ value: "skip", label: "Skip" },
|
|
502
|
+
{ value: "solid", label: "Solid" },
|
|
503
|
+
]
|
|
504
|
+
: [
|
|
505
|
+
{ value: "skip", label: "Skip" },
|
|
506
|
+
{ value: "solid", label: "Solid" },
|
|
507
|
+
{ value: "transparent", label: "Transparent" },
|
|
508
|
+
];
|
|
509
|
+
fields.push({
|
|
510
|
+
kind: "Enum",
|
|
511
|
+
key: "interpolate",
|
|
512
|
+
default: "solid",
|
|
513
|
+
variants,
|
|
514
|
+
});
|
|
515
|
+
}
|
|
522
516
|
}
|
|
523
517
|
|
|
524
518
|
// Per-column formatter widgets. Surfaced for every chart type so
|
|
@@ -561,7 +555,15 @@ export class HTMLPerspectiveViewerWebGLPluginElement
|
|
|
561
555
|
|
|
562
556
|
private async _drawImpl(view: View): Promise<void> {
|
|
563
557
|
const gen = ++this._generation;
|
|
564
|
-
|
|
558
|
+
let renderer: RendererTransport;
|
|
559
|
+
try {
|
|
560
|
+
renderer = await this._ensureRenderer(view);
|
|
561
|
+
} catch {
|
|
562
|
+
// Renderer was disposed mid-init (element disconnected
|
|
563
|
+
// during the async build) — nothing to draw.
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
|
|
565
567
|
if (this._generation !== gen) {
|
|
566
568
|
return;
|
|
567
569
|
}
|
|
@@ -599,28 +601,6 @@ export class HTMLPerspectiveViewerWebGLPluginElement
|
|
|
599
601
|
return 5;
|
|
600
602
|
}
|
|
601
603
|
|
|
602
|
-
save() {
|
|
603
|
-
const state: any = {};
|
|
604
|
-
const zoom = this._renderer?.saveZoom();
|
|
605
|
-
if (zoom) {
|
|
606
|
-
state.zoom = zoom;
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
// Only emit the keys this chart actually consumes.
|
|
610
|
-
const cfg: Partial<PluginConfig> = {};
|
|
611
|
-
for (const key of this._chartType.applicable_plugin_fields) {
|
|
612
|
-
// `key` is `PluginConfigField` = `keyof PluginConfig`, so this
|
|
613
|
-
// indexed assignment is type-safe without a cast.
|
|
614
|
-
(cfg[key] as PluginConfig[typeof key]) = this._pluginConfig[key];
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
if (Object.keys(cfg).length > 0) {
|
|
618
|
-
state.plugin_config = cfg;
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
return state;
|
|
622
|
-
}
|
|
623
|
-
|
|
624
604
|
async render(view: View): Promise<Blob> {
|
|
625
605
|
await this._ensureRenderer(view);
|
|
626
606
|
await this.draw(view);
|