@perspective-dev/viewer-charts 4.5.2 → 5.1.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 +2 -2
- package/dist/cdn/perspective-viewer-charts.js.map +3 -3
- package/dist/esm/charts/candlestick/candlestick.d.ts +8 -10
- package/dist/esm/charts/chart-base.d.ts +58 -4
- package/dist/esm/charts/chart.d.ts +45 -9
- package/dist/esm/charts/series/glyphs/draw-areas.d.ts +8 -2
- package/dist/esm/charts/series/glyphs/draw-bars.d.ts +8 -1
- package/dist/esm/charts/series/glyphs/draw-lines.d.ts +8 -2
- package/dist/esm/charts/series/glyphs/draw-scatter.d.ts +8 -2
- package/dist/esm/charts/series/series-build.d.ts +11 -0
- package/dist/esm/charts/series/series-interact.d.ts +13 -2
- package/dist/esm/charts/series/series-render.d.ts +17 -1
- package/dist/esm/charts/series/series.d.ts +104 -10
- package/dist/esm/data/view-reader.d.ts +10 -1
- package/dist/esm/event-detail.d.ts +7 -0
- package/dist/esm/layout/ticks.d.ts +5 -0
- package/dist/esm/perspective-viewer-charts.js +2 -2
- package/dist/esm/perspective-viewer-charts.js.map +3 -3
- package/dist/esm/plugin/charts.d.ts +6 -3
- package/dist/esm/plugin/plugin.d.ts +40 -2
- package/dist/esm/render/scheduler.d.ts +11 -8
- package/dist/esm/transport/protocol.d.ts +39 -2
- package/dist/esm/transport/renderer-transport.d.ts +75 -3
- package/dist/esm/webgl/instanced-attrs.d.ts +7 -1
- package/dist/esm/worker/renderer.worker.d.ts +12 -0
- package/package.json +1 -1
- package/src/ts/charts/candlestick/candlestick-render.ts +4 -1
- package/src/ts/charts/candlestick/candlestick.ts +12 -17
- package/src/ts/charts/cartesian/cartesian-render.ts +80 -63
- package/src/ts/charts/chart-base.ts +85 -5
- package/src/ts/charts/chart.ts +47 -9
- package/src/ts/charts/common/category-axis-resolver.ts +5 -1
- package/src/ts/charts/common/tree-data.ts +61 -16
- package/src/ts/charts/heatmap/heatmap-render.ts +6 -2
- package/src/ts/charts/series/glyphs/draw-areas.ts +21 -1
- package/src/ts/charts/series/glyphs/draw-bars.ts +30 -9
- package/src/ts/charts/series/glyphs/draw-lines.ts +21 -1
- package/src/ts/charts/series/glyphs/draw-scatter.ts +68 -3
- package/src/ts/charts/series/series-build.ts +21 -3
- package/src/ts/charts/series/series-interact.ts +210 -78
- package/src/ts/charts/series/series-render.ts +779 -49
- package/src/ts/charts/series/series.ts +209 -26
- package/src/ts/charts/sunburst/sunburst-interact.ts +2 -3
- package/src/ts/charts/sunburst/sunburst-render.ts +3 -1
- package/src/ts/charts/treemap/treemap-interact.ts +2 -5
- package/src/ts/charts/treemap/treemap-render.ts +3 -1
- package/src/ts/charts/treemap/treemap.ts +6 -1
- package/src/ts/data/view-reader.ts +22 -2
- package/src/ts/event-detail.ts +8 -0
- package/src/ts/index.ts +13 -7
- package/src/ts/interaction/raw-event-forwarder.ts +7 -0
- package/src/ts/interaction/zoom-controller.ts +8 -0
- package/src/ts/layout/ticks.ts +9 -0
- package/src/ts/plugin/charts.ts +20 -4
- package/src/ts/plugin/plugin.ts +99 -17
- package/src/ts/render/scheduler.ts +39 -13
- package/src/ts/transport/protocol.ts +42 -0
- package/src/ts/transport/renderer-transport.ts +198 -9
- package/src/ts/webgl/instanced-attrs.ts +15 -1
- package/src/ts/worker/dispatch.ts +4 -1
- package/src/ts/worker/renderer.worker.ts +35 -3
|
@@ -142,6 +142,23 @@ export abstract class AbstractChart implements ChartImplementation {
|
|
|
142
142
|
_gridlineCanvas: HTMLCanvasElement | OffscreenCanvas | null = null;
|
|
143
143
|
_chromeCanvas: HTMLCanvasElement | OffscreenCanvas | null = null;
|
|
144
144
|
|
|
145
|
+
/**
|
|
146
|
+
* 2D-canvas draw closures collected during `_fullRender` (the GL
|
|
147
|
+
* pass) and flushed by {@link _flush2D} *after* the scheduler's
|
|
148
|
+
* `awaitGpuFence`. Deferring the gridline/chrome draws past the GPU
|
|
149
|
+
* fence stops the 2D placeholder canvases from pushing a resized
|
|
150
|
+
* frame to the compositor a frame ahead of the GL present — the
|
|
151
|
+
* visible GL/2D misalignment on resize.
|
|
152
|
+
*
|
|
153
|
+
* Renderers wrap their 2D drawing in {@link _defer2D}; the closures
|
|
154
|
+
* capture the exact per-frame locals (layout, ticks, theme) so no
|
|
155
|
+
* state needs to be recomputed or replayed from cache at flush time.
|
|
156
|
+
* Reset at the start of every GL pass (see {@link requestRender} /
|
|
157
|
+
* {@link renderFrameSync}) so a `_fullRender` that throws mid-way
|
|
158
|
+
* can't leak stale closures into the next frame.
|
|
159
|
+
*/
|
|
160
|
+
_deferred2D: Array<() => void> = [];
|
|
161
|
+
|
|
145
162
|
/**
|
|
146
163
|
* Host-supplied CSS-variable map. The host snapshots its DOM via
|
|
147
164
|
* `snapshotThemeVars(el)` and ships it over the control channel;
|
|
@@ -630,6 +647,20 @@ export abstract class AbstractChart implements ChartImplementation {
|
|
|
630
647
|
}
|
|
631
648
|
}
|
|
632
649
|
|
|
650
|
+
/**
|
|
651
|
+
* Silently dismiss any pinned tooltip (the chart's selection visual)
|
|
652
|
+
* WITHOUT emitting `perspective-global-filter` — unlike the `setView`
|
|
653
|
+
* implicit dismiss above, the HOST initiated this (its global filter
|
|
654
|
+
* bar removed the clause this selection contributed), so an unselect
|
|
655
|
+
* emit would double-mutate the host's filter set.
|
|
656
|
+
* `TooltipController.dismiss()` posts the host-side pin teardown but
|
|
657
|
+
* never fires `onUnpin` (that emit belongs to `dispatchClick`'s
|
|
658
|
+
* click-to-unpin gesture), so this is emit-free by construction.
|
|
659
|
+
*/
|
|
660
|
+
deselect(): void {
|
|
661
|
+
this._tooltip.dismiss();
|
|
662
|
+
}
|
|
663
|
+
|
|
633
664
|
/**
|
|
634
665
|
* Build the chart-specific {@link TooltipCallbacks} object — the
|
|
635
666
|
* `onHover` / `onLeave` / `onClickPre` / `onPin` / `onDblClick`
|
|
@@ -810,17 +841,66 @@ export abstract class AbstractChart implements ChartImplementation {
|
|
|
810
841
|
* scheduler so concurrent calls collapse to one `_fullRender` per
|
|
811
842
|
* RAF and the host blitter receives one bitmap per frame. The
|
|
812
843
|
* returned promise resolves after this chart's `awaitGpuFence` +
|
|
813
|
-
* `endFrame` chain — independent of other charts in the
|
|
814
|
-
* RAF, which run their fence waits in parallel.
|
|
844
|
+
* `render2D` + `endFrame` chain — independent of other charts in the
|
|
845
|
+
* same RAF, which run their fence waits in parallel.
|
|
846
|
+
*
|
|
847
|
+
* `_fullRender` submits the GL commands and stashes the frame's 2D
|
|
848
|
+
* draws (gridlines + chrome) via {@link _defer2D}; the scheduler
|
|
849
|
+
* flushes them with {@link _flush2D} *after* the GPU fence, so the
|
|
850
|
+
* 2D canvases don't present ahead of the GL frame. The GL pass
|
|
851
|
+
* resets `_deferred2D` first so a mid-pass throw can't leak stale
|
|
852
|
+
* closures into the next frame.
|
|
815
853
|
*
|
|
816
854
|
* Every render-triggering caller — upload chunks, zoom / pan,
|
|
817
855
|
* resize, theme invalidation, host-driven redraws — calls this.
|
|
818
|
-
* The only sanctioned bypass is `snapshotPng`, which
|
|
819
|
-
*
|
|
856
|
+
* The only sanctioned bypass is `snapshotPng`, which uses
|
|
857
|
+
* {@link renderFrameSync} to keep the GL backbuffer intact for
|
|
820
858
|
* `gl.readPixels`.
|
|
821
859
|
*/
|
|
822
860
|
requestRender(glManager: WebGLContextManager): Promise<void> {
|
|
823
|
-
return scheduleRender(
|
|
861
|
+
return scheduleRender(
|
|
862
|
+
glManager,
|
|
863
|
+
() => {
|
|
864
|
+
this._deferred2D = [];
|
|
865
|
+
this._fullRender(glManager);
|
|
866
|
+
},
|
|
867
|
+
() => this._flush2D(),
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* Queue a 2D-canvas draw (gridline or chrome) to run after the
|
|
873
|
+
* GPU fence. Called from the chart renderers in place of drawing to
|
|
874
|
+
* the gridline/chrome canvases inline during the GL pass. See
|
|
875
|
+
* {@link _deferred2D}.
|
|
876
|
+
*/
|
|
877
|
+
_defer2D(fn: () => void): void {
|
|
878
|
+
this._deferred2D.push(fn);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* Run and clear the queued 2D-canvas draws. Invoked by the scheduler
|
|
883
|
+
* after `awaitGpuFence` (Phase 2), and by {@link renderFrameSync}.
|
|
884
|
+
*/
|
|
885
|
+
_flush2D(): void {
|
|
886
|
+
const draws = this._deferred2D;
|
|
887
|
+
this._deferred2D = [];
|
|
888
|
+
for (const draw of draws) {
|
|
889
|
+
draw();
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/**
|
|
894
|
+
* Synchronous full frame (GL + 2D) for the `snapshotPng` bypass,
|
|
895
|
+
* which sits outside the scheduler and needs the gridline/chrome
|
|
896
|
+
* canvases painted before it composites them. Resets the deferred
|
|
897
|
+
* queue, runs the GL pass, then flushes the 2D draws immediately
|
|
898
|
+
* (no fence split — a snapshot isn't presented to the compositor).
|
|
899
|
+
*/
|
|
900
|
+
renderFrameSync(glManager: WebGLContextManager): void {
|
|
901
|
+
this._deferred2D = [];
|
|
902
|
+
this._fullRender(glManager);
|
|
903
|
+
this._flush2D();
|
|
824
904
|
}
|
|
825
905
|
|
|
826
906
|
// Lifecycle
|
package/src/ts/charts/chart.ts
CHANGED
|
@@ -44,13 +44,23 @@ export interface ChartImplementation {
|
|
|
44
44
|
requestRender(glManager: WebGLContextManager): Promise<void>;
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
|
-
* The chart-specific frame builder.
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
47
|
+
* The chart-specific GL frame builder. Submits the GL draw commands
|
|
48
|
+
* and stashes the frame's 2D-canvas draws (gridlines + chrome) for
|
|
49
|
+
* the scheduler to flush after `awaitGpuFence`. The scheduler wraps
|
|
50
|
+
* this with fence + 2D flush + `endFrame`; callers must not invoke
|
|
51
|
+
* it directly — `snapshotPng` uses {@link renderFrameSync} instead.
|
|
51
52
|
*/
|
|
52
53
|
_fullRender(glManager: WebGLContextManager): void;
|
|
53
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Synchronous full frame (GL + 2D) for the `snapshotPng` bypass,
|
|
57
|
+
* which sits outside the scheduler and needs the gridline/chrome
|
|
58
|
+
* canvases painted before compositing. Runs the GL pass then flushes
|
|
59
|
+
* the deferred 2D draws immediately, skipping the fence split and
|
|
60
|
+
* `endFrame` so the GL backbuffer stays intact for `gl.readPixels`.
|
|
61
|
+
*/
|
|
62
|
+
renderFrameSync(glManager: WebGLContextManager): void;
|
|
63
|
+
|
|
54
64
|
/**
|
|
55
65
|
* Hand the current View to the chart so it can make on-demand
|
|
56
66
|
* per-row queries (for lazy tooltip column lookups). Called on
|
|
@@ -174,6 +184,15 @@ export interface ChartImplementation {
|
|
|
174
184
|
*/
|
|
175
185
|
resetExpandedDomain?(): void;
|
|
176
186
|
|
|
187
|
+
/**
|
|
188
|
+
* Silently clear any active selection state (pinned tooltip) WITHOUT
|
|
189
|
+
* emitting selection events. Driven from the host's `deselect`
|
|
190
|
+
* message — its global filter bar removed a clause this chart's
|
|
191
|
+
* selection contributed, so an unselect emit would double-mutate the
|
|
192
|
+
* host's filter set.
|
|
193
|
+
*/
|
|
194
|
+
deselect?(): void;
|
|
195
|
+
|
|
177
196
|
destroy(): void;
|
|
178
197
|
}
|
|
179
198
|
|
|
@@ -246,8 +265,19 @@ export interface PluginConfig {
|
|
|
246
265
|
* Faceting strategy when `split_by` is non-empty.
|
|
247
266
|
*
|
|
248
267
|
* - `"grid"` — one small-multiple sub-plot per split group.
|
|
249
|
-
* - `"overlay"` — single plot
|
|
250
|
-
* color
|
|
268
|
+
* - `"overlay"` — a single plot: cartesian charts differentiate
|
|
269
|
+
* split groups by color; the categorical band pipeline (series
|
|
270
|
+
* charts) stacks splits within each aggregate's band slot.
|
|
271
|
+
* Synced into `_facetConfig.facet_mode`.
|
|
272
|
+
*
|
|
273
|
+
* The default differs by family via
|
|
274
|
+
* `ChartTypeConfig.plugin_field_defaults`: cartesian / density /
|
|
275
|
+
* map chart types default to `"grid"`, the series / financial
|
|
276
|
+
* band-pipeline types to `"overlay"` (their historical split
|
|
277
|
+
* rendering). Series charts REBUILD on a mode change — grid mode
|
|
278
|
+
* keys the stack ladder per split (`facetSplits` in
|
|
279
|
+
* `buildSeriesPipeline`) so each facet grows from its own
|
|
280
|
+
* baseline.
|
|
251
281
|
*/
|
|
252
282
|
facet_mode: "grid" | "overlay";
|
|
253
283
|
|
|
@@ -283,15 +313,23 @@ export interface PluginConfig {
|
|
|
283
313
|
/**
|
|
284
314
|
* Domain accumulation policy across successive `View` updates.
|
|
285
315
|
*
|
|
286
|
-
* - `"fit"` — every update recomputes the
|
|
287
|
-
* cartesian charts, the X/Y range and color/size scales) from
|
|
316
|
+
* - `"fit"` — every update recomputes the affected domains from
|
|
288
317
|
* the current data extent. Can grow or shrink frame-to-frame.
|
|
289
|
-
* - `"expand"` — the
|
|
318
|
+
* - `"expand"` — the affected domains monotonically *grow*: each
|
|
290
319
|
* update unions the new data extent with the previously rendered
|
|
291
320
|
* extent, so once a value is in scope it stays in scope. Reset
|
|
292
321
|
* by the "Reset Zoom" button, view-config changes (group_by /
|
|
293
322
|
* split_by / column-slot / column-type), or toggling back to
|
|
294
323
|
* `"fit"`.
|
|
324
|
+
*
|
|
325
|
+
* AXIS SCOPE differs by family: cartesian charts (X/Y Scatter,
|
|
326
|
+
* X/Y Line, Density, Maps) apply it to BOTH axes plus the
|
|
327
|
+
* color/size scales (categorical string axes opt out — slot
|
|
328
|
+
* indices are frame-local); the categorical band pipeline (series
|
|
329
|
+
* / financial) applies it to the VALUE axis only — Y for the
|
|
330
|
+
* Y-family, X for X Bar — while the category axis always fits, so
|
|
331
|
+
* a streaming numeric/datetime `group_by` axis releases departed
|
|
332
|
+
* categories instead of pinning to its history.
|
|
295
333
|
*/
|
|
296
334
|
domain_mode: "fit" | "expand";
|
|
297
335
|
|
|
@@ -157,7 +157,11 @@ function formatLevelValue(
|
|
|
157
157
|
valid: boolean,
|
|
158
158
|
levelType: string,
|
|
159
159
|
): string {
|
|
160
|
-
|
|
160
|
+
// `value` can be `undefined` even when `valid` claims otherwise — an
|
|
161
|
+
// out-of-bounds read when a row window outran the delivered Arrow
|
|
162
|
+
// (belt to `loadAndRender`'s delivered-rows clamp) — treat it as
|
|
163
|
+
// null rather than crash the label formatters.
|
|
164
|
+
if (!valid || value == null || Number.isNaN(value)) {
|
|
161
165
|
return "";
|
|
162
166
|
}
|
|
163
167
|
|
|
@@ -156,6 +156,33 @@ function insertRow(
|
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Arrow validity-bitmap test. Columns without a bitmap are all-valid.
|
|
161
|
+
*/
|
|
162
|
+
function isValidCell(col: ColumnData, rowIdx: number): boolean {
|
|
163
|
+
const v = col.valid;
|
|
164
|
+
return !v || !!((v[rowIdx >> 3] >> (rowIdx & 7)) & 1);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Read a size cell, honoring the validity bitmap: the bytes under an
|
|
169
|
+
* invalid slot are UNDEFINED (DuckDB's Arrow leaves NaN where the
|
|
170
|
+
* engine zero-fills), and a NaN here would poison every ancestor sum
|
|
171
|
+
* in `finalizeTree`. Invalid → 0 (no contribution); no size column at
|
|
172
|
+
* all → 1 (count semantics).
|
|
173
|
+
*/
|
|
174
|
+
function readSize(col: ColumnData | null | undefined, rowIdx: number): number {
|
|
175
|
+
if (!col?.values) {
|
|
176
|
+
return 1;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!isValidCell(col, rowIdx)) {
|
|
180
|
+
return 0;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return col.values[rowIdx] as number;
|
|
184
|
+
}
|
|
185
|
+
|
|
159
186
|
function readColor(
|
|
160
187
|
chart: TreeChartBase,
|
|
161
188
|
colorCol: ColumnData | null | undefined,
|
|
@@ -163,7 +190,7 @@ function readColor(
|
|
|
163
190
|
): { colorValue: number; colorLabel: string } {
|
|
164
191
|
let colorValue = NaN;
|
|
165
192
|
let colorLabel = "";
|
|
166
|
-
if (!colorCol) {
|
|
193
|
+
if (!colorCol || !isValidCell(colorCol, rowIdx)) {
|
|
167
194
|
return { colorValue, colorLabel };
|
|
168
195
|
}
|
|
169
196
|
|
|
@@ -261,7 +288,11 @@ export function processTreeChunk(
|
|
|
261
288
|
chart: TreeChartBase,
|
|
262
289
|
columns: ColumnDataMap,
|
|
263
290
|
): void {
|
|
264
|
-
const rpCols: {
|
|
291
|
+
const rpCols: {
|
|
292
|
+
indices: Int32Array;
|
|
293
|
+
dictionary: string[];
|
|
294
|
+
valid?: Uint8Array;
|
|
295
|
+
}[] = [];
|
|
265
296
|
for (let n = 0; ; n++) {
|
|
266
297
|
const rp = columns.get(`__ROW_PATH_${n}__`);
|
|
267
298
|
if (!rp) {
|
|
@@ -269,7 +300,11 @@ export function processTreeChunk(
|
|
|
269
300
|
}
|
|
270
301
|
|
|
271
302
|
if (rp.type === "string" && rp.indices && rp.dictionary) {
|
|
272
|
-
rpCols.push({
|
|
303
|
+
rpCols.push({
|
|
304
|
+
indices: rp.indices,
|
|
305
|
+
dictionary: rp.dictionary,
|
|
306
|
+
valid: rp.valid,
|
|
307
|
+
});
|
|
273
308
|
} else if (rp.values) {
|
|
274
309
|
// Non-string group_by (integer / float / date / datetime /
|
|
275
310
|
// boolean) — synthesize a string-indexed dictionary so the
|
|
@@ -397,9 +432,7 @@ export function processTreeChunk(
|
|
|
397
432
|
// Pass the signed value through; `insertRow` stores
|
|
398
433
|
// `|size|` and `sizeSign` separately so the
|
|
399
434
|
// render pass can dim negative leaves.
|
|
400
|
-
const sizeValue = src.sizeCol
|
|
401
|
-
? (src.sizeCol.values[i] as number)
|
|
402
|
-
: 1;
|
|
435
|
+
const sizeValue = readSize(src.sizeCol, i);
|
|
403
436
|
const { colorValue, colorLabel } = readColor(
|
|
404
437
|
chart,
|
|
405
438
|
src.colorCol,
|
|
@@ -416,9 +449,7 @@ export function processTreeChunk(
|
|
|
416
449
|
);
|
|
417
450
|
}
|
|
418
451
|
} else {
|
|
419
|
-
const sizeValue = sizeCol
|
|
420
|
-
? (sizeCol.values[i] as number)
|
|
421
|
-
: 1;
|
|
452
|
+
const sizeValue = readSize(sizeCol, i);
|
|
422
453
|
const { colorValue, colorLabel } = readColor(
|
|
423
454
|
chart,
|
|
424
455
|
colorCol,
|
|
@@ -449,6 +480,19 @@ export function processTreeChunk(
|
|
|
449
480
|
let pathLen = 0;
|
|
450
481
|
for (let d = 0; d < rpCols.length; d++) {
|
|
451
482
|
const rp = rpCols[d];
|
|
483
|
+
|
|
484
|
+
// In `"rollup"` mode the view emits total / subtotal rows
|
|
485
|
+
// whose deeper row-path levels are INVALID — index slot 0,
|
|
486
|
+
// which decodes to a REAL dictionary value (the engine's
|
|
487
|
+
// string row-path dictionaries carry no `""` sentinel), so
|
|
488
|
+
// the validity bitmap is the only truthful signal. Truncate
|
|
489
|
+
// the path at the first invalid level; the `!label` check
|
|
490
|
+
// below still covers synthesized non-string levels, whose
|
|
491
|
+
// dictionary index 0 IS the `""` sentinel.
|
|
492
|
+
if (rp.valid && !((rp.valid[i >> 3] >> (i & 7)) & 1)) {
|
|
493
|
+
break;
|
|
494
|
+
}
|
|
495
|
+
|
|
452
496
|
const label = rp.dictionary[rp.indices[i]];
|
|
453
497
|
if (!label && label !== "0") {
|
|
454
498
|
break;
|
|
@@ -465,9 +509,7 @@ export function processTreeChunk(
|
|
|
465
509
|
for (const src of splitSources!) {
|
|
466
510
|
pathScratch[0] = src.prefix;
|
|
467
511
|
const rowPath = pathScratch.slice(0, pathLen + 1);
|
|
468
|
-
const sizeValue = src.sizeCol
|
|
469
|
-
? (src.sizeCol.values[i] as number)
|
|
470
|
-
: 1;
|
|
512
|
+
const sizeValue = readSize(src.sizeCol, i);
|
|
471
513
|
const { colorValue, colorLabel } = readColor(
|
|
472
514
|
chart,
|
|
473
515
|
src.colorCol,
|
|
@@ -485,9 +527,7 @@ export function processTreeChunk(
|
|
|
485
527
|
}
|
|
486
528
|
} else {
|
|
487
529
|
const rowPath = pathScratch.slice(0, pathLen);
|
|
488
|
-
const sizeValue = sizeCol
|
|
489
|
-
? (sizeCol.values[i] as number)
|
|
490
|
-
: 1;
|
|
530
|
+
const sizeValue = readSize(sizeCol, i);
|
|
491
531
|
const { colorValue, colorLabel } = readColor(chart, colorCol, i);
|
|
492
532
|
insertRow(
|
|
493
533
|
chart,
|
|
@@ -556,7 +596,12 @@ export function finalizeTree(chart: TreeChartBase): void {
|
|
|
556
596
|
}
|
|
557
597
|
} else {
|
|
558
598
|
if (firstChild[id] === NULL_NODE) {
|
|
559
|
-
|
|
599
|
+
// NOT `Math.max(0, x)`: that maps NaN → NaN, and one NaN
|
|
600
|
+
// leaf would poison every ancestor sum (dropping whole
|
|
601
|
+
// subtrees in `squarify`'s `value > 0` filter). The
|
|
602
|
+
// comparison form maps NaN → 0.
|
|
603
|
+
const s = size[id];
|
|
604
|
+
value[id] = s > 0 ? s : 0;
|
|
560
605
|
} else {
|
|
561
606
|
let sum = 0;
|
|
562
607
|
for (
|
|
@@ -225,7 +225,9 @@ export function renderHeatmapFrame(
|
|
|
225
225
|
drawCellsInstanced(chart, gl, glManager, 0, chart._uploadedCells);
|
|
226
226
|
});
|
|
227
227
|
|
|
228
|
-
|
|
228
|
+
// Deferred past the GPU fence (see `_defer2D`) so the chrome canvas
|
|
229
|
+
// doesn't present ahead of the GL cells on resize.
|
|
230
|
+
chart._defer2D(() => renderHeatmapChromeOverlay(chart));
|
|
229
231
|
}
|
|
230
232
|
|
|
231
233
|
function ensureProgram(
|
|
@@ -650,7 +652,9 @@ function renderFacetedHeatmap(
|
|
|
650
652
|
});
|
|
651
653
|
}
|
|
652
654
|
|
|
653
|
-
|
|
655
|
+
// Deferred past the GPU fence (see `_defer2D`) so the chrome canvas
|
|
656
|
+
// doesn't present ahead of the GL cells on resize.
|
|
657
|
+
chart._defer2D(() => renderHeatmapChromeOverlay(chart));
|
|
654
658
|
}
|
|
655
659
|
|
|
656
660
|
/**
|
|
@@ -187,7 +187,10 @@ export class AreaGlyph {
|
|
|
187
187
|
|
|
188
188
|
/**
|
|
189
189
|
* Bind persistent strip buffers and dispatch one TRIANGLE_STRIP per
|
|
190
|
-
* series-run. Skips hidden series.
|
|
190
|
+
* series-run. Skips hidden series. `splitFilter` (faceted frames)
|
|
191
|
+
* draws only the series whose `splitIdx` matches; `aggRange` (mixed
|
|
192
|
+
* glyph-run frames) only those whose `aggIdx` lies in the inclusive
|
|
193
|
+
* run span.
|
|
191
194
|
*/
|
|
192
195
|
draw(
|
|
193
196
|
chart: SeriesChart,
|
|
@@ -196,6 +199,8 @@ export class AreaGlyph {
|
|
|
196
199
|
projLeft: Float32Array,
|
|
197
200
|
projRight: Float32Array,
|
|
198
201
|
opacity: number,
|
|
202
|
+
splitFilter?: number,
|
|
203
|
+
aggRange?: { start: number; end: number },
|
|
199
204
|
): void {
|
|
200
205
|
const buf = this._buffers;
|
|
201
206
|
const cache = this._program;
|
|
@@ -212,6 +217,21 @@ export class AreaGlyph {
|
|
|
212
217
|
continue;
|
|
213
218
|
}
|
|
214
219
|
|
|
220
|
+
if (
|
|
221
|
+
splitFilter !== undefined &&
|
|
222
|
+
chart._series[s.seriesId].splitIdx !== splitFilter
|
|
223
|
+
) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const aggIdx = chart._series[s.seriesId].aggIdx;
|
|
228
|
+
if (
|
|
229
|
+
aggRange !== undefined &&
|
|
230
|
+
(aggIdx < aggRange.start || aggIdx > aggRange.end)
|
|
231
|
+
) {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
|
|
215
235
|
gl.uniformMatrix4fv(
|
|
216
236
|
cache.u_projection,
|
|
217
237
|
false,
|
|
@@ -30,13 +30,20 @@ type GL = WebGL2RenderingContext | WebGLRenderingContext;
|
|
|
30
30
|
/**
|
|
31
31
|
* Draw the bar-typed subset of `chart._bars` as instanced quads. Assumes
|
|
32
32
|
* the caller has already `useProgram`'d the bar shader and set uniforms.
|
|
33
|
+
*
|
|
34
|
+
* `range` draws a contiguous instance sub-range instead of the full
|
|
35
|
+
* uploaded set — the faceted renderer uploads instances split-major
|
|
36
|
+
* (see `uploadBarInstances`) and dispatches one range per facet.
|
|
33
37
|
*/
|
|
34
38
|
export function drawBars(
|
|
35
39
|
chart: SeriesChart,
|
|
36
40
|
gl: GL,
|
|
37
41
|
glManager: WebGLContextManager,
|
|
42
|
+
range?: { start: number; count: number },
|
|
38
43
|
): void {
|
|
39
|
-
|
|
44
|
+
const first = range?.start ?? 0;
|
|
45
|
+
const count = range?.count ?? chart._uploadedBars;
|
|
46
|
+
if (chart._uploadedBars === 0 || count === 0) {
|
|
40
47
|
return;
|
|
41
48
|
}
|
|
42
49
|
|
|
@@ -62,6 +69,7 @@ export function drawBars(
|
|
|
62
69
|
loc.a_x_center,
|
|
63
70
|
"bar_x",
|
|
64
71
|
1,
|
|
72
|
+
first,
|
|
65
73
|
) &&
|
|
66
74
|
bindInstancedFloatAttr(
|
|
67
75
|
glManager,
|
|
@@ -69,15 +77,31 @@ export function drawBars(
|
|
|
69
77
|
loc.a_half_width,
|
|
70
78
|
"bar_hw",
|
|
71
79
|
1,
|
|
80
|
+
first,
|
|
81
|
+
) &&
|
|
82
|
+
bindInstancedFloatAttr(
|
|
83
|
+
glManager,
|
|
84
|
+
instancing,
|
|
85
|
+
loc.a_y0,
|
|
86
|
+
"bar_y0",
|
|
87
|
+
1,
|
|
88
|
+
first,
|
|
89
|
+
) &&
|
|
90
|
+
bindInstancedFloatAttr(
|
|
91
|
+
glManager,
|
|
92
|
+
instancing,
|
|
93
|
+
loc.a_y1,
|
|
94
|
+
"bar_y1",
|
|
95
|
+
1,
|
|
96
|
+
first,
|
|
72
97
|
) &&
|
|
73
|
-
bindInstancedFloatAttr(glManager, instancing, loc.a_y0, "bar_y0", 1) &&
|
|
74
|
-
bindInstancedFloatAttr(glManager, instancing, loc.a_y1, "bar_y1", 1) &&
|
|
75
98
|
bindInstancedFloatAttr(
|
|
76
99
|
glManager,
|
|
77
100
|
instancing,
|
|
78
101
|
loc.a_color,
|
|
79
102
|
"bar_color",
|
|
80
103
|
3,
|
|
104
|
+
first,
|
|
81
105
|
) &&
|
|
82
106
|
bindInstancedFloatAttr(
|
|
83
107
|
glManager,
|
|
@@ -85,6 +109,7 @@ export function drawBars(
|
|
|
85
109
|
loc.a_series_id,
|
|
86
110
|
"bar_sid",
|
|
87
111
|
1,
|
|
112
|
+
first,
|
|
88
113
|
) &&
|
|
89
114
|
bindInstancedFloatAttr(
|
|
90
115
|
glManager,
|
|
@@ -92,15 +117,11 @@ export function drawBars(
|
|
|
92
117
|
loc.a_axis,
|
|
93
118
|
"bar_axis",
|
|
94
119
|
1,
|
|
120
|
+
first,
|
|
95
121
|
);
|
|
96
122
|
|
|
97
123
|
if (ok) {
|
|
98
|
-
instancing.drawArraysInstanced(
|
|
99
|
-
gl.TRIANGLE_STRIP,
|
|
100
|
-
0,
|
|
101
|
-
4,
|
|
102
|
-
chart._uploadedBars,
|
|
103
|
-
);
|
|
124
|
+
instancing.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, count);
|
|
104
125
|
}
|
|
105
126
|
|
|
106
127
|
setDivisor(loc.a_x_center, 0);
|
|
@@ -271,7 +271,10 @@ export class LineGlyph {
|
|
|
271
271
|
* Bind the persistent vertex buffers and dispatch one instanced draw
|
|
272
272
|
* per series. Skips hidden series via `_hiddenSeries`. Gap /
|
|
273
273
|
* transparency rendering is governed by `u_interp_alpha`, set per
|
|
274
|
-
* series.
|
|
274
|
+
* series. `splitFilter` (faceted frames) draws only the series
|
|
275
|
+
* whose `splitIdx` matches — one call per facet. `aggRange` (mixed
|
|
276
|
+
* glyph-run frames) draws only the series whose `aggIdx` lies in
|
|
277
|
+
* the inclusive run span.
|
|
275
278
|
*/
|
|
276
279
|
draw(
|
|
277
280
|
chart: SeriesChart,
|
|
@@ -279,6 +282,8 @@ export class LineGlyph {
|
|
|
279
282
|
glManager: WebGLContextManager,
|
|
280
283
|
projLeft: Float32Array,
|
|
281
284
|
projRight: Float32Array,
|
|
285
|
+
splitFilter?: number,
|
|
286
|
+
aggRange?: { start: number; end: number },
|
|
282
287
|
): void {
|
|
283
288
|
const buf = this._buffers;
|
|
284
289
|
const cache = this._program;
|
|
@@ -310,6 +315,21 @@ export class LineGlyph {
|
|
|
310
315
|
continue;
|
|
311
316
|
}
|
|
312
317
|
|
|
318
|
+
if (
|
|
319
|
+
splitFilter !== undefined &&
|
|
320
|
+
chart._series[s.seriesId].splitIdx !== splitFilter
|
|
321
|
+
) {
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const aggIdx = chart._series[s.seriesId].aggIdx;
|
|
326
|
+
if (
|
|
327
|
+
aggRange !== undefined &&
|
|
328
|
+
(aggIdx < aggRange.start || aggIdx > aggRange.end)
|
|
329
|
+
) {
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
|
|
313
333
|
gl.uniformMatrix4fv(
|
|
314
334
|
cache.u_projection,
|
|
315
335
|
false,
|