@perspective-dev/viewer-charts 4.5.2 → 5.0.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 +3 -2
- package/dist/esm/charts/series/glyphs/draw-bars.d.ts +8 -1
- package/dist/esm/charts/series/glyphs/draw-lines.d.ts +3 -2
- package/dist/esm/charts/series/glyphs/draw-scatter.d.ts +5 -2
- package/dist/esm/charts/series/series-build.d.ts +11 -0
- package/dist/esm/charts/series/series-interact.d.ts +10 -0
- package/dist/esm/charts/series/series-render.d.ts +17 -1
- package/dist/esm/charts/series/series.d.ts +53 -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 +31 -2
- package/dist/esm/render/scheduler.d.ts +11 -8
- package/dist/esm/transport/protocol.d.ts +31 -2
- package/dist/esm/transport/renderer-transport.d.ts +74 -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 +10 -1
- package/src/ts/charts/series/glyphs/draw-bars.ts +30 -9
- package/src/ts/charts/series/glyphs/draw-lines.ts +10 -1
- package/src/ts/charts/series/glyphs/draw-scatter.ts +56 -3
- package/src/ts/charts/series/series-build.ts +21 -3
- package/src/ts/charts/series/series-interact.ts +86 -13
- package/src/ts/charts/series/series-render.ts +660 -17
- package/src/ts/charts/series/series.ts +96 -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 +86 -16
- package/src/ts/render/scheduler.ts +39 -13
- package/src/ts/transport/protocol.ts +33 -0
- package/src/ts/transport/renderer-transport.ts +185 -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 +32 -3
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import type { ColumnDataMap } from "../../data/view-reader";
|
|
14
14
|
import type { WebGLContextManager } from "../../webgl/context-manager";
|
|
15
15
|
import type { ZoomConfig } from "../../interaction/zoom-controller";
|
|
16
|
+
import type { FacetGrid } from "../../layout/facet-grid";
|
|
16
17
|
import { CategoricalYChart } from "../common/categorical-y-chart";
|
|
17
18
|
import { type PlotRect } from "../../layout/plot-layout";
|
|
18
19
|
import { type AxisDomain } from "../../axis/numeric-axis";
|
|
@@ -123,6 +124,35 @@ export class SeriesChart extends CategoricalYChart {
|
|
|
123
124
|
_splitPrefixes: string[] = [];
|
|
124
125
|
_series: SeriesInfo[] = [];
|
|
125
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Whether the CURRENT build's geometry was produced for facet-grid
|
|
129
|
+
* mode (`facet_mode: "grid"` with real splits). The render / hover
|
|
130
|
+
* paths branch on this — never on the live `_facetConfig` — so a
|
|
131
|
+
* `setPluginConfig` that flips `facet_mode` between a build and a
|
|
132
|
+
* redraw can't pair grid-mode rendering with overlay-built stack
|
|
133
|
+
* geometry (or vice versa). The host always follows a
|
|
134
|
+
* `plugin.restore` with an `update` → `loadAndRender`, so the flag
|
|
135
|
+
* catches up one build later.
|
|
136
|
+
*/
|
|
137
|
+
_facetActive = false;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Facet grid of the most recent faceted frame — one cell per
|
|
141
|
+
* split group. `null` in overlay / no-split frames. Read by the
|
|
142
|
+
* chrome overlay, hover hit-test, and tooltip pin to resolve the
|
|
143
|
+
* per-facet `PlotLayout`.
|
|
144
|
+
*/
|
|
145
|
+
_facetGrid: FacetGrid | null = null;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Per-split contiguous instance ranges in the uploaded bar
|
|
149
|
+
* buffers, indexed by `splitIdx`. Populated by
|
|
150
|
+
* `uploadBarInstances` when `_facetActive` (instances are emitted
|
|
151
|
+
* split-major); `null` in overlay mode. Each facet's draw binds
|
|
152
|
+
* the instance attributes at `start` and draws `count` instances.
|
|
153
|
+
*/
|
|
154
|
+
_facetBarRanges: { start: number; count: number }[] | null = null;
|
|
155
|
+
|
|
126
156
|
/**
|
|
127
157
|
* Columnar bar/area record storage. Indexed by bar slot in
|
|
128
158
|
* `[0, _bars.count)`. Replaces the legacy `SeriesChartRecord[]` to
|
|
@@ -205,8 +235,12 @@ export class SeriesChart extends CategoricalYChart {
|
|
|
205
235
|
|
|
206
236
|
/**
|
|
207
237
|
* `domain_mode: "expand"` accumulators. Hold the running union of
|
|
208
|
-
* every prior build's
|
|
209
|
-
*
|
|
238
|
+
* every prior build's VALUE-axis extents (primary + alt) for as
|
|
239
|
+
* long as the option is active. The category axis deliberately has
|
|
240
|
+
* NO accumulator: `domain_mode` scopes to the value axis only on
|
|
241
|
+
* band charts (Y for the Y-family, X for X Bar) — a numeric or
|
|
242
|
+
* datetime `group_by` axis always fits the current data, so a
|
|
243
|
+
* streaming chart's time axis releases departed categories.
|
|
210
244
|
* Cleared in `resetExpandedDomain` — wired from the worker's
|
|
211
245
|
* `resetAllZooms` and from view-config mutations on the base
|
|
212
246
|
* class. `null` whenever the option is `"fit"` or the accumulator
|
|
@@ -214,7 +248,6 @@ export class SeriesChart extends CategoricalYChart {
|
|
|
214
248
|
*/
|
|
215
249
|
_expandedLeftDomain: { min: number; max: number } | null = null;
|
|
216
250
|
_expandedRightDomain: { min: number; max: number } | null = null;
|
|
217
|
-
_expandedCategoryDomain: { min: number; max: number } | null = null;
|
|
218
251
|
|
|
219
252
|
/**
|
|
220
253
|
* Numeric category-axis state. Populated only when `group_by` has
|
|
@@ -296,7 +329,13 @@ export class SeriesChart extends CategoricalYChart {
|
|
|
296
329
|
*/
|
|
297
330
|
_visibleBarIndices: Int32Array = new Int32Array(0);
|
|
298
331
|
|
|
299
|
-
|
|
332
|
+
/**
|
|
333
|
+
* Hit-test rects for legend entries. Overlay mode: one entry per
|
|
334
|
+
* series (`seriesIds.length === 1`). Facet-grid mode: one entry
|
|
335
|
+
* per AGGREGATE, carrying every split's seriesId — a legend
|
|
336
|
+
* toggle hides/shows the aggregate across all facets at once.
|
|
337
|
+
*/
|
|
338
|
+
_legendRects: { seriesIds: number[]; rect: PlotRect }[] = [];
|
|
300
339
|
|
|
301
340
|
/**
|
|
302
341
|
* Cached legend layout — recomputed only on series-set / palette /
|
|
@@ -362,7 +401,14 @@ export class SeriesChart extends CategoricalYChart {
|
|
|
362
401
|
this._hoveredBarIdx = -1;
|
|
363
402
|
this._hoveredSample = null;
|
|
364
403
|
if (this._glManager) {
|
|
365
|
-
|
|
404
|
+
// Route through the scheduler (not a direct
|
|
405
|
+
// `renderBarFrame`) so the frame is serialized
|
|
406
|
+
// against sibling charts sharing a pooled GL
|
|
407
|
+
// context in blit mode — a direct render would
|
|
408
|
+
// paint into the shared canvas outside the
|
|
409
|
+
// present cycle and leak into another plugin's
|
|
410
|
+
// frame.
|
|
411
|
+
this.requestRender(this._glManager);
|
|
366
412
|
}
|
|
367
413
|
}
|
|
368
414
|
},
|
|
@@ -479,6 +525,9 @@ export class SeriesChart extends CategoricalYChart {
|
|
|
479
525
|
this._cornerBuffer = createQuadCornerBuffer(gl);
|
|
480
526
|
}
|
|
481
527
|
|
|
528
|
+
const facetSplits =
|
|
529
|
+
this._splitBy.length > 0 && this._facetConfig.facet_mode === "grid";
|
|
530
|
+
|
|
482
531
|
const result = buildSeriesPipeline({
|
|
483
532
|
columns,
|
|
484
533
|
numRows: endRow,
|
|
@@ -497,15 +546,21 @@ export class SeriesChart extends CategoricalYChart {
|
|
|
497
546
|
bandInnerFrac: this._pluginConfig.band_inner_frac,
|
|
498
547
|
barInnerPad: this._pluginConfig.bar_inner_pad,
|
|
499
548
|
includeZero: this._pluginConfig.include_zero,
|
|
549
|
+
facetSplits,
|
|
500
550
|
scratchBars: this._bars,
|
|
501
551
|
scratchPosStack: this._posStackScratch,
|
|
502
552
|
scratchNegStack: this._negStackScratch,
|
|
503
553
|
});
|
|
504
554
|
|
|
505
|
-
// `domain_mode: "expand"` post-build union
|
|
506
|
-
//
|
|
507
|
-
//
|
|
508
|
-
//
|
|
555
|
+
// `domain_mode: "expand"` post-build union — VALUE axes only
|
|
556
|
+
// (`leftDomain`/`rightDomain` are the logical value domains,
|
|
557
|
+
// orientation-agnostic, so this is Y for the Y-family and X for
|
|
558
|
+
// X Bar). The category axis is NEVER expanded: it identifies
|
|
559
|
+
// the data, and pinning a numeric/datetime `group_by` axis to
|
|
560
|
+
// departed categories froze streaming charts on their full
|
|
561
|
+
// history. Each call mutates the pipeline result in place so
|
|
562
|
+
// the downstream assignments below (`_leftDomain`,
|
|
563
|
+
// `_rightDomain`) automatically pick up the grown extent.
|
|
509
564
|
// `"fit"` (or a fresh reset) leaves the result untouched and
|
|
510
565
|
// clears the accumulators so the next toggle starts fresh.
|
|
511
566
|
if (this._pluginConfig.domain_mode === "expand") {
|
|
@@ -513,27 +568,25 @@ export class SeriesChart extends CategoricalYChart {
|
|
|
513
568
|
this._expandedLeftDomain,
|
|
514
569
|
result.leftDomain,
|
|
515
570
|
);
|
|
571
|
+
|
|
516
572
|
if (result.rightDomain) {
|
|
517
573
|
this._expandedRightDomain = expandDomainInPlace(
|
|
518
574
|
this._expandedRightDomain,
|
|
519
575
|
result.rightDomain,
|
|
520
576
|
);
|
|
521
577
|
}
|
|
522
|
-
|
|
523
|
-
if (result.numericCategoryDomain) {
|
|
524
|
-
this._expandedCategoryDomain = expandDomainInPlace(
|
|
525
|
-
this._expandedCategoryDomain,
|
|
526
|
-
result.numericCategoryDomain,
|
|
527
|
-
);
|
|
528
|
-
}
|
|
529
578
|
} else {
|
|
530
579
|
this._expandedLeftDomain = null;
|
|
531
580
|
this._expandedRightDomain = null;
|
|
532
|
-
this._expandedCategoryDomain = null;
|
|
533
581
|
}
|
|
534
582
|
|
|
535
583
|
this._aggregates = result.aggregates;
|
|
536
584
|
this._splitPrefixes = result.splitPrefixes;
|
|
585
|
+
|
|
586
|
+
// Stamp AFTER the build so render always branches on the mode
|
|
587
|
+
// the geometry was actually built for. A single "" prefix means
|
|
588
|
+
// no real splits — facet mode degenerates to the single plot.
|
|
589
|
+
this._facetActive = facetSplits && result.splitPrefixes.length > 1;
|
|
537
590
|
this._rowPaths = result.rowPaths;
|
|
538
591
|
this._numCategories = result.numCategories;
|
|
539
592
|
this._rowOffset = result.rowOffset;
|
|
@@ -638,7 +691,6 @@ export class SeriesChart extends CategoricalYChart {
|
|
|
638
691
|
override resetExpandedDomain(): void {
|
|
639
692
|
this._expandedLeftDomain = null;
|
|
640
693
|
this._expandedRightDomain = null;
|
|
641
|
-
this._expandedCategoryDomain = null;
|
|
642
694
|
}
|
|
643
695
|
|
|
644
696
|
protected destroyInternal(): void {
|
|
@@ -654,6 +706,9 @@ export class SeriesChart extends CategoricalYChart {
|
|
|
654
706
|
this._program = null;
|
|
655
707
|
this._locations = null;
|
|
656
708
|
this._cornerBuffer = null;
|
|
709
|
+
this._facetActive = false;
|
|
710
|
+
this._facetGrid = null;
|
|
711
|
+
this._facetBarRanges = null;
|
|
657
712
|
this._bars = emptyBarColumns();
|
|
658
713
|
this._series = [];
|
|
659
714
|
this._barSeries = [];
|
|
@@ -718,9 +773,16 @@ function uniqueAggLabels(series: SeriesInfo[], axis: 0 | 1): string {
|
|
|
718
773
|
/**
|
|
719
774
|
* Resolve the per-series palette and stamp it onto `_series[i].color`.
|
|
720
775
|
* Cached on `_paletteCache` keyed by reference identity of the theme
|
|
721
|
-
* inputs +
|
|
722
|
-
* via `invalidateTheme`) or a data load (which clears
|
|
723
|
-
* forces re-resolution.
|
|
776
|
+
* inputs + resolved color count — only `restyle()` (which clears
|
|
777
|
+
* `_paletteCache` via `invalidateTheme`) or a data load (which clears
|
|
778
|
+
* it explicitly) forces re-resolution.
|
|
779
|
+
*
|
|
780
|
+
* Facet-grid mode colors by AGGREGATE, not by (agg × split) series:
|
|
781
|
+
* the facet is the axis of splitting, so aggregate `k` renders the
|
|
782
|
+
* same color in every facet and the legend lists aggregates once.
|
|
783
|
+
* Overlay mode keeps the per-series palette (splits are distinguished
|
|
784
|
+
* by color there). `_facetActive` only changes at build time, which
|
|
785
|
+
* clears the cache, so the stamp mode can't go stale.
|
|
724
786
|
*
|
|
725
787
|
* Returns true when the cache changed (caller invalidates color upload).
|
|
726
788
|
*/
|
|
@@ -728,7 +790,9 @@ export function ensurePalette(chart: SeriesChart): boolean {
|
|
|
728
790
|
const theme = chart._resolveTheme();
|
|
729
791
|
const seriesPalette = theme.seriesPalette;
|
|
730
792
|
const gradientStops = theme.gradientStops;
|
|
731
|
-
const
|
|
793
|
+
const paletteCount = chart._facetActive
|
|
794
|
+
? Math.max(1, chart._aggregates.length)
|
|
795
|
+
: chart._series.length;
|
|
732
796
|
|
|
733
797
|
const key = chart._paletteCacheKey;
|
|
734
798
|
if (
|
|
@@ -736,7 +800,7 @@ export function ensurePalette(chart: SeriesChart): boolean {
|
|
|
736
800
|
key &&
|
|
737
801
|
key.seriesPalette === seriesPalette &&
|
|
738
802
|
key.gradientStops === gradientStops &&
|
|
739
|
-
key.seriesLength ===
|
|
803
|
+
key.seriesLength === paletteCount
|
|
740
804
|
) {
|
|
741
805
|
return false;
|
|
742
806
|
}
|
|
@@ -744,13 +808,19 @@ export function ensurePalette(chart: SeriesChart): boolean {
|
|
|
744
808
|
const palette = resolvePaletteCached(
|
|
745
809
|
seriesPalette,
|
|
746
810
|
gradientStops,
|
|
747
|
-
|
|
811
|
+
paletteCount,
|
|
748
812
|
);
|
|
749
813
|
chart._paletteCache = palette;
|
|
750
|
-
chart._paletteCacheKey = {
|
|
814
|
+
chart._paletteCacheKey = {
|
|
815
|
+
seriesPalette,
|
|
816
|
+
gradientStops,
|
|
817
|
+
seriesLength: paletteCount,
|
|
818
|
+
};
|
|
751
819
|
|
|
752
820
|
for (let i = 0; i < chart._series.length; i++) {
|
|
753
|
-
chart._series[i].color =
|
|
821
|
+
chart._series[i].color = chart._facetActive
|
|
822
|
+
? palette[chart._series[i].aggIdx]
|
|
823
|
+
: palette[i];
|
|
754
824
|
}
|
|
755
825
|
|
|
756
826
|
return true;
|
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
import type { SunburstChart } from "./sunburst";
|
|
14
14
|
import { NULL_NODE } from "../common/node-store";
|
|
15
15
|
import {
|
|
16
|
-
renderSunburstFrame,
|
|
17
16
|
renderSunburstChromeOverlay,
|
|
18
17
|
facetCenterForNode,
|
|
19
18
|
} from "./sunburst-render";
|
|
@@ -264,7 +263,7 @@ export function handleSunburstClick(
|
|
|
264
263
|
}
|
|
265
264
|
|
|
266
265
|
if (chart._glManager) {
|
|
267
|
-
|
|
266
|
+
chart.requestRender(chart._glManager);
|
|
268
267
|
}
|
|
269
268
|
}
|
|
270
269
|
|
|
@@ -289,7 +288,7 @@ export function handleSunburstClick(
|
|
|
289
288
|
function drillTo(chart: SunburstChart, nodeId: number): void {
|
|
290
289
|
treeDrillTo(chart, nodeId, () => {
|
|
291
290
|
if (chart._glManager) {
|
|
292
|
-
|
|
291
|
+
chart.requestRender(chart._glManager);
|
|
293
292
|
}
|
|
294
293
|
});
|
|
295
294
|
}
|
|
@@ -189,7 +189,9 @@ export function renderSunburstFrame(
|
|
|
189
189
|
drawArcs(chart, gl, glManager, 0, chart._instanceCount);
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
-
|
|
192
|
+
// Deferred past the GPU fence (see `_defer2D`) so the chrome canvas
|
|
193
|
+
// doesn't present ahead of the GL arcs on resize.
|
|
194
|
+
chart._defer2D(() => renderSunburstChromeOverlay(chart));
|
|
193
195
|
}
|
|
194
196
|
|
|
195
197
|
/**
|
|
@@ -13,10 +13,7 @@
|
|
|
13
13
|
import type { TreemapChart } from "./treemap";
|
|
14
14
|
import { NULL_NODE } from "../common/node-store";
|
|
15
15
|
import { PADDING_LABEL } from "./treemap-layout";
|
|
16
|
-
import {
|
|
17
|
-
renderTreemapFrame,
|
|
18
|
-
renderTreemapChromeOverlay,
|
|
19
|
-
} from "./treemap-render";
|
|
16
|
+
import { renderTreemapChromeOverlay } from "./treemap-render";
|
|
20
17
|
import {
|
|
21
18
|
buildTreeTooltipLines,
|
|
22
19
|
dismissTreePinnedTooltip,
|
|
@@ -254,7 +251,7 @@ export function handleTreemapDblClick(
|
|
|
254
251
|
function drillTo(chart: TreemapChart, nodeId: number): void {
|
|
255
252
|
treeDrillTo(chart, nodeId, () => {
|
|
256
253
|
if (chart._glManager) {
|
|
257
|
-
|
|
254
|
+
chart.requestRender(chart._glManager);
|
|
258
255
|
}
|
|
259
256
|
});
|
|
260
257
|
}
|
|
@@ -173,7 +173,9 @@ export function renderTreemapFrame(
|
|
|
173
173
|
|
|
174
174
|
gl.drawArrays(gl.TRIANGLES, 0, chart._vertexCount);
|
|
175
175
|
|
|
176
|
-
|
|
176
|
+
// Deferred past the GPU fence (see `_defer2D`) so the chrome canvas
|
|
177
|
+
// doesn't present ahead of the GL tiles on resize.
|
|
178
|
+
chart._defer2D(() => renderTreemapChromeOverlay(chart));
|
|
177
179
|
}
|
|
178
180
|
|
|
179
181
|
/**
|
|
@@ -109,7 +109,12 @@ export class TreemapChart extends TreeChartBase {
|
|
|
109
109
|
) {
|
|
110
110
|
this._hoveredNodeId = NULL_NODE;
|
|
111
111
|
if (this._glManager) {
|
|
112
|
-
|
|
112
|
+
// Schedule through the render scheduler rather
|
|
113
|
+
// than painting directly — a direct frame render
|
|
114
|
+
// bypasses the per-context serialization and, in
|
|
115
|
+
// pooled blit mode, corrupts a sibling chart's
|
|
116
|
+
// in-flight shared-canvas frame.
|
|
117
|
+
this.requestRender(this._glManager);
|
|
113
118
|
}
|
|
114
119
|
}
|
|
115
120
|
},
|
|
@@ -50,10 +50,19 @@ export interface TypedArrayWindowOptions {
|
|
|
50
50
|
* `Promise`, the underlying `with_typed_arrays` call awaits it before
|
|
51
51
|
* releasing the backing Arrow buffer. Callers must not retain any
|
|
52
52
|
* `ColumnData` reference past `render`'s resolution.
|
|
53
|
+
*
|
|
54
|
+
* `render` also receives the DELIVERED row count — the shortest
|
|
55
|
+
* column's length, which may be less than the requested `end_row`:
|
|
56
|
+
* the engine clamps the window to the view's size at fetch time, and
|
|
57
|
+
* on a fast-updating table with deletions that size can shrink after
|
|
58
|
+
* the caller's `num_rows()` round-trip. Build pipelines must iterate
|
|
59
|
+
* to THIS count, never the requested one — a typed array indexed past
|
|
60
|
+
* its length returns `undefined`, which no amount of validity-bitmap
|
|
61
|
+
* checking catches (the bitmap read is out-of-bounds too).
|
|
53
62
|
*/
|
|
54
63
|
export async function viewToColumnDataMap(
|
|
55
64
|
view: View,
|
|
56
|
-
render: (data: ColumnDataMap) => void | Promise<void>,
|
|
65
|
+
render: (data: ColumnDataMap, numRows: number) => void | Promise<void>,
|
|
57
66
|
options?: TypedArrayWindowOptions,
|
|
58
67
|
): Promise<void> {
|
|
59
68
|
const result: ColumnDataMap = new Map();
|
|
@@ -101,7 +110,18 @@ export async function viewToColumnDataMap(
|
|
|
101
110
|
}
|
|
102
111
|
}
|
|
103
112
|
|
|
104
|
-
|
|
113
|
+
let numRows = 0;
|
|
114
|
+
if (result.size > 0) {
|
|
115
|
+
numRows = Infinity;
|
|
116
|
+
for (const col of result.values()) {
|
|
117
|
+
const n = col.indices?.length ?? col.values?.length ?? 0;
|
|
118
|
+
if (n < numRows) {
|
|
119
|
+
numRows = n;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
await render(result, numRows);
|
|
105
125
|
},
|
|
106
126
|
);
|
|
107
127
|
}
|
package/src/ts/event-detail.ts
CHANGED
|
@@ -39,6 +39,14 @@ export interface PerspectiveClickDetail {
|
|
|
39
39
|
* `[<col>, "==", <value>]` clauses.
|
|
40
40
|
*/
|
|
41
41
|
config: Partial<ViewConfig>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The source panel's `slot` name in a multi-panel `<perspective-viewer>`,
|
|
45
|
+
* so the host can attribute the click to the originating panel. `undefined`
|
|
46
|
+
* for a lone, unslotted viewer. Mirrors `PerspectiveSelectDetail.panel` and
|
|
47
|
+
* the datagrid `PerspectiveClickDetail`.
|
|
48
|
+
*/
|
|
49
|
+
panel?: string;
|
|
42
50
|
}
|
|
43
51
|
|
|
44
52
|
export { PerspectiveSelectDetail } from "@perspective-dev/viewer/src/ts/extensions.js";
|
package/src/ts/index.ts
CHANGED
|
@@ -23,17 +23,17 @@ export function register(...plugin_names: string[]) {
|
|
|
23
23
|
: CHARTS.map((chart) => chart.name),
|
|
24
24
|
);
|
|
25
25
|
|
|
26
|
+
const already_registered: string[] = [];
|
|
27
|
+
|
|
26
28
|
CHARTS.forEach((chart) => {
|
|
27
29
|
if (plugins.has(chart.name)) {
|
|
28
30
|
const tagName = `perspective-viewer-charts-${chart.tag}`;
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
// boundary, and the renderer constructs the impl from
|
|
36
|
-
// its own `CHART_IMPLS` registry.
|
|
32
|
+
if (customElements.get(tagName)) {
|
|
33
|
+
already_registered.push(tagName);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
37
|
customElements.define(
|
|
38
38
|
tagName,
|
|
39
39
|
class extends HTMLPerspectiveViewerWebGLPluginElement {
|
|
@@ -48,6 +48,12 @@ export function register(...plugin_names: string[]) {
|
|
|
48
48
|
});
|
|
49
49
|
}
|
|
50
50
|
});
|
|
51
|
+
|
|
52
|
+
if (already_registered.length > 0) {
|
|
53
|
+
console.warn(
|
|
54
|
+
`viewer-charts plugins already registered (${already_registered.length}); skipping duplicate registration (Perspective was loaded more than once on this page).`,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
register();
|
|
@@ -55,6 +55,13 @@ export class RawEventForwarder {
|
|
|
55
55
|
};
|
|
56
56
|
|
|
57
57
|
this._onPointerDown = (e: PointerEvent) => {
|
|
58
|
+
// Only forward primary-button pointerdowns. Right/middle clicks must
|
|
59
|
+
// reach the panel's `contextmenu` handler — capturing the pointer
|
|
60
|
+
// here would otherwise swallow the right-click.
|
|
61
|
+
if (e.button !== 0) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
58
65
|
const rect = element.getBoundingClientRect();
|
|
59
66
|
const mx = e.clientX - rect.left;
|
|
60
67
|
const my = e.clientY - rect.top;
|
|
@@ -388,6 +388,14 @@ export class ZoomController {
|
|
|
388
388
|
};
|
|
389
389
|
|
|
390
390
|
this._onPointerDown = (e: PointerEvent) => {
|
|
391
|
+
// Only the primary (left) button starts a pan. Right/middle clicks
|
|
392
|
+
// must fall through so the panel's `contextmenu` handler can open
|
|
393
|
+
// the menu — capturing the pointer here would otherwise swallow the
|
|
394
|
+
// right-click.
|
|
395
|
+
if (e.button !== 0) {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
|
|
391
399
|
const rect = element.getBoundingClientRect();
|
|
392
400
|
const mouseX = e.clientX - rect.left;
|
|
393
401
|
const mouseY = e.clientY - rect.top;
|
package/src/ts/layout/ticks.ts
CHANGED
|
@@ -72,8 +72,17 @@ export function computeNiceTicks(
|
|
|
72
72
|
/**
|
|
73
73
|
* Format a numeric tick value for display.
|
|
74
74
|
* Uses K/M/B suffixes for large numbers, fixed decimals for small.
|
|
75
|
+
*
|
|
76
|
+
* Total over any input: label formatters run inside render passes, so a
|
|
77
|
+
* non-finite value (or `undefined` smuggled in by an upstream
|
|
78
|
+
* out-of-bounds read) must degrade to a placeholder, never throw the
|
|
79
|
+
* frame away.
|
|
75
80
|
*/
|
|
76
81
|
export function formatTickValue(val: number): string {
|
|
82
|
+
if (!Number.isFinite(val)) {
|
|
83
|
+
return "-";
|
|
84
|
+
}
|
|
85
|
+
|
|
77
86
|
const abs = Math.abs(val);
|
|
78
87
|
if (abs === 0) {
|
|
79
88
|
return "0";
|
package/src/ts/plugin/charts.ts
CHANGED
|
@@ -48,9 +48,12 @@ export interface ChartTypeConfig {
|
|
|
48
48
|
* Per-chart-type overrides for `DEFAULT_PLUGIN_CONFIG`. Used when a
|
|
49
49
|
* field's sensible default differs by chart family — currently
|
|
50
50
|
* `include_zero` (true for Y Bar / Y Area / X Bar, false for line
|
|
51
|
-
* / scatter / cartesian / financial)
|
|
52
|
-
*
|
|
53
|
-
*
|
|
51
|
+
* / scatter / cartesian / financial) and `facet_mode` ("overlay"
|
|
52
|
+
* for the band-pipeline families — series / financial — whose
|
|
53
|
+
* historical split_by rendering is the single stacked/colored
|
|
54
|
+
* plot; "grid" elsewhere). Applied at schema generation and at
|
|
55
|
+
* `restore({})` so the effective default matches the surfaced UI
|
|
56
|
+
* default.
|
|
54
57
|
*/
|
|
55
58
|
plugin_field_defaults?: Partial<PluginConfig>;
|
|
56
59
|
}
|
|
@@ -85,10 +88,19 @@ const SERIES_FIELDS: readonly PluginConfigField[] = [
|
|
|
85
88
|
"bar_inner_pad",
|
|
86
89
|
];
|
|
87
90
|
|
|
91
|
+
// The band pipeline's historical split_by rendering is a single plot
|
|
92
|
+
// (splits stacked / colored in place), so the series family defaults
|
|
93
|
+
// `facet_mode` to "overlay" — grid faceting is the opt-in. Cartesian
|
|
94
|
+
// charts keep the global "grid" default from `DEFAULT_PLUGIN_CONFIG`.
|
|
95
|
+
const SERIES_DEFAULTS: Partial<PluginConfig> = { facet_mode: "overlay" };
|
|
96
|
+
|
|
88
97
|
// Bar / area series glyphs grow from the zero baseline, so the value
|
|
89
98
|
// axis must enclose 0 to render correctly. Line / scatter glyphs have
|
|
90
99
|
// no such constraint — their default `include_zero` stays `false`.
|
|
91
|
-
const ZERO_ANCHORED_DEFAULTS: Partial<PluginConfig> = {
|
|
100
|
+
const ZERO_ANCHORED_DEFAULTS: Partial<PluginConfig> = {
|
|
101
|
+
...SERIES_DEFAULTS,
|
|
102
|
+
include_zero: true,
|
|
103
|
+
};
|
|
92
104
|
|
|
93
105
|
// Pure Cartesian (X/Y Scatter, X/Y Line) — no categorical axis, so no
|
|
94
106
|
// band geometry; gets the facet-routing variant of zoom_mode.
|
|
@@ -210,9 +222,11 @@ const CHARTS: ChartTypeConfig[] = [
|
|
|
210
222
|
}),
|
|
211
223
|
make("Y Line", "y-line", SERIES, SELECT, 1, Y_AXIS, SERIES_FIELDS, {
|
|
212
224
|
default_chart_type: "line",
|
|
225
|
+
plugin_field_defaults: SERIES_DEFAULTS,
|
|
213
226
|
}),
|
|
214
227
|
make("Y Scatter", "y-scatter", SERIES, SELECT, 1, Y_AXIS, SERIES_FIELDS, {
|
|
215
228
|
default_chart_type: "scatter",
|
|
229
|
+
plugin_field_defaults: SERIES_DEFAULTS,
|
|
216
230
|
}),
|
|
217
231
|
make("Y Area", "y-area", SERIES, SELECT, 1, Y_AXIS, SERIES_FIELDS, {
|
|
218
232
|
default_chart_type: "area",
|
|
@@ -250,9 +264,11 @@ const CHARTS: ChartTypeConfig[] = [
|
|
|
250
264
|
make("Heatmap", "heatmap", HIER, SELECT, 1, ["Color"], HEATMAP_FIELDS),
|
|
251
265
|
make("Candlestick", "candlestick", FIN, TOGGLE, 1, FIN_NAMES, FIN_FIELDS, {
|
|
252
266
|
default_chart_type: "candlestick",
|
|
267
|
+
plugin_field_defaults: SERIES_DEFAULTS,
|
|
253
268
|
}),
|
|
254
269
|
make("OHLC", "ohlc", FIN, TOGGLE, 1, FIN_NAMES, FIN_FIELDS, {
|
|
255
270
|
default_chart_type: "ohlc",
|
|
271
|
+
plugin_field_defaults: SERIES_DEFAULTS,
|
|
256
272
|
}),
|
|
257
273
|
make(
|
|
258
274
|
"Map Scatter",
|