@perspective-dev/viewer-charts 4.3.0 → 4.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/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 +0 -1
- package/dist/esm/theme/palette.d.ts +0 -5
- package/dist/esm/transport/protocol.d.ts +2 -7
- package/dist/esm/worker/renderer.worker.d.ts +2 -4
- package/package.json +1 -1
- 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/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 +100 -184
- 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/worker/dispatch.ts +0 -1
- package/src/ts/worker/renderer.worker.ts +10 -46
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
renderBarAxesChrome,
|
|
30
30
|
renderBarGridlines,
|
|
31
31
|
type BarCategoryAxis,
|
|
32
|
+
type BarValueAxis,
|
|
32
33
|
} from "../../axis/bar-axis";
|
|
33
34
|
import {
|
|
34
35
|
measureCategoricalAxisHeight,
|
|
@@ -38,10 +39,7 @@ import {
|
|
|
38
39
|
import { buildBarTooltipLines } from "./series-interact";
|
|
39
40
|
|
|
40
41
|
/**
|
|
41
|
-
* Reusable scratch for bar instance uploads.
|
|
42
|
-
* use; grown on demand. Avoids `new Float32Array(n)` × 7 buffers per
|
|
43
|
-
* legend-toggle / data-load; size is bounded by the bar-typed subset
|
|
44
|
-
* of `_bars.count`.
|
|
42
|
+
* Reusable scratch for bar instance uploads.
|
|
45
43
|
*/
|
|
46
44
|
interface BarInstanceScratch {
|
|
47
45
|
xCenters: Float32Array;
|
|
@@ -78,10 +76,7 @@ function ensureBarInstanceScratch(n: number): BarInstanceScratch {
|
|
|
78
76
|
}
|
|
79
77
|
|
|
80
78
|
/**
|
|
81
|
-
* Upload bar instance buffers from the columnar `_bars` storage.
|
|
82
|
-
* to bar-typed records only (areas draw as triangle strips). Skips
|
|
83
|
-
* hidden series. Re-called from data-load and legend-toggle paths; the
|
|
84
|
-
* scratch buffers and `_visibleBarIndices` are reused across calls.
|
|
79
|
+
* Upload bar instance buffers from the columnar `_bars` storage.
|
|
85
80
|
*/
|
|
86
81
|
export function uploadBarInstances(
|
|
87
82
|
chart: SeriesChart,
|
|
@@ -103,12 +98,6 @@ export function uploadBarInstances(
|
|
|
103
98
|
const indices = chart._visibleBarIndices;
|
|
104
99
|
|
|
105
100
|
// Rebase each xCenter by `_categoryOrigin` before f32 narrowing.
|
|
106
|
-
// For datetime numeric category axes the absolute xCenter is
|
|
107
|
-
// ~1.7e12 and f32 narrowing collapses adjacent bars onto the
|
|
108
|
-
// same value; subtracting the origin brings every value into
|
|
109
|
-
// the seconds range where f32 has full precision. The matching
|
|
110
|
-
// projection matrix is built with the same origin so the shader
|
|
111
|
-
// math is consistent.
|
|
112
101
|
const xOrigin = chart._categoryOrigin;
|
|
113
102
|
const series = chart._series;
|
|
114
103
|
const hidden = chart._hiddenSeries;
|
|
@@ -350,14 +339,15 @@ export function renderBarFrame(
|
|
|
350
339
|
}
|
|
351
340
|
|
|
352
341
|
// Auto-fit the value axis to the visible categorical window. Gated
|
|
353
|
-
// on `_autoFitValue` + non-default
|
|
354
|
-
//
|
|
355
|
-
//
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
!chart._zoomController
|
|
360
|
-
|
|
342
|
+
// on `_autoFitValue` + the categorical axis being non-default: the
|
|
343
|
+
// refit only narrows when the categorical axis is itself zoomed
|
|
344
|
+
// (otherwise the visible window equals the data extent and the
|
|
345
|
+
// refit collapses back to `_leftDomain`/`_rightDomain`). Vertical
|
|
346
|
+
// charts put the category on X; horizontal charts put it on Y.
|
|
347
|
+
const catNonDefault = horizontal
|
|
348
|
+
? !chart._zoomController?.isYDefault()
|
|
349
|
+
: !chart._zoomController?.isXDefault();
|
|
350
|
+
if (chart._autoFitValue && chart._zoomController && catNonDefault) {
|
|
361
351
|
const fit = computeVisibleValueExtent(chart, visCatMin, visCatMax);
|
|
362
352
|
if (fit.hasLeft) {
|
|
363
353
|
visValMin = fit.leftMin;
|
|
@@ -405,32 +395,58 @@ export function renderBarFrame(
|
|
|
405
395
|
levelLabels: chart._groupBy.slice(),
|
|
406
396
|
};
|
|
407
397
|
|
|
398
|
+
// Categorical value-axis sizing. Y Bar puts the value axis on the
|
|
399
|
+
// left (so the category labels need extra `leftExtra` width); X Bar
|
|
400
|
+
// puts it on the bottom (extra `bottomExtra` height for the leaf
|
|
401
|
+
// labels). We additionally override the category-axis gutter on
|
|
402
|
+
// the opposite side via the existing `provisionalDomain` path.
|
|
403
|
+
const valueCatDomain = chart._leftValueCategoryDomain;
|
|
404
|
+
const valueCatActive =
|
|
405
|
+
chart._leftValueAxisMode === "category" &&
|
|
406
|
+
valueCatDomain !== null &&
|
|
407
|
+
valueCatDomain.numRows > 0;
|
|
408
|
+
|
|
408
409
|
let layout: PlotLayout;
|
|
409
410
|
if (horizontal) {
|
|
410
|
-
//
|
|
411
|
-
//
|
|
412
|
-
//
|
|
411
|
+
// X Bar: category axis on the left (Y side), value axis on the
|
|
412
|
+
// bottom (X side). Categorical value axis grows the bottom
|
|
413
|
+
// gutter; numeric value axis uses the fixed 24px row.
|
|
413
414
|
const leftExtra = numericCat
|
|
414
415
|
? 55
|
|
415
416
|
: measureCategoricalAxisWidth(provisionalDomain);
|
|
416
|
-
|
|
417
|
+
const estLeft = leftExtra + (hasCatLabel ? 16 : 0);
|
|
418
|
+
const estRight = hasLegend ? 80 : 16;
|
|
419
|
+
const estPlotWidthH = Math.max(1, cssWidth - estLeft - estRight);
|
|
420
|
+
const bottomExtra = valueCatActive
|
|
421
|
+
? measureCategoricalAxisHeight(valueCatDomain, estPlotWidthH)
|
|
422
|
+
: undefined;
|
|
417
423
|
layout = new PlotLayout(cssWidth, cssHeight, {
|
|
418
424
|
hasXLabel: true,
|
|
419
425
|
hasYLabel: hasCatLabel,
|
|
420
426
|
hasLegend,
|
|
421
427
|
leftExtra,
|
|
428
|
+
bottomExtra,
|
|
422
429
|
});
|
|
423
430
|
} else if (numericCat) {
|
|
424
|
-
//
|
|
425
|
-
//
|
|
431
|
+
// Y Bar with numeric category axis on X. Value axis (Y, left)
|
|
432
|
+
// may still be categorical when all aggregates are string.
|
|
433
|
+
const leftExtra = valueCatActive
|
|
434
|
+
? measureCategoricalAxisWidth(valueCatDomain)
|
|
435
|
+
: undefined;
|
|
426
436
|
layout = new PlotLayout(cssWidth, cssHeight, {
|
|
427
437
|
hasXLabel: hasCatLabel,
|
|
428
438
|
hasYLabel: true,
|
|
429
439
|
hasLegend,
|
|
430
440
|
bottomExtra: 24,
|
|
441
|
+
leftExtra,
|
|
431
442
|
});
|
|
432
443
|
} else {
|
|
433
|
-
|
|
444
|
+
// Y Bar with categorical X. Value axis on the left may be
|
|
445
|
+
// categorical too — independently sized.
|
|
446
|
+
const leftExtraBase = valueCatActive
|
|
447
|
+
? measureCategoricalAxisWidth(valueCatDomain)
|
|
448
|
+
: 55;
|
|
449
|
+
const estLeft = leftExtraBase + 16;
|
|
434
450
|
const estRight = hasLegend ? 80 : 16;
|
|
435
451
|
const estPlotWidth = Math.max(1, cssWidth - estLeft - estRight);
|
|
436
452
|
const bottomExtra = measureCategoricalAxisHeight(
|
|
@@ -442,6 +458,7 @@ export function renderBarFrame(
|
|
|
442
458
|
hasYLabel: true,
|
|
443
459
|
hasLegend,
|
|
444
460
|
bottomExtra,
|
|
461
|
+
leftExtra: valueCatActive ? leftExtraBase : undefined,
|
|
445
462
|
});
|
|
446
463
|
}
|
|
447
464
|
|
|
@@ -635,16 +652,44 @@ export function renderBarChromeOverlay(chart: SeriesChart): void {
|
|
|
635
652
|
const primarySeries = chart._series.find((s) => s.axis === 0);
|
|
636
653
|
const altSeries = chart._series.find((s) => s.axis === 1);
|
|
637
654
|
const xColumn = chart._groupBy[0];
|
|
655
|
+
|
|
656
|
+
// Discriminate each value-axis side independently: a side becomes
|
|
657
|
+
// categorical when every aggregate on it is post-aggregation
|
|
658
|
+
// `string`-typed (the build pipeline already applied this
|
|
659
|
+
// all-or-nothing rule and stamped `_*ValueAxisMode`).
|
|
660
|
+
const valueAxis: BarValueAxis =
|
|
661
|
+
chart._leftValueAxisMode === "category" &&
|
|
662
|
+
chart._leftValueCategoryDomain
|
|
663
|
+
? { mode: "category", domain: chart._leftValueCategoryDomain }
|
|
664
|
+
: {
|
|
665
|
+
mode: "numeric",
|
|
666
|
+
domain: chart._lastYDomain,
|
|
667
|
+
ticks: chart._lastYTicks,
|
|
668
|
+
};
|
|
669
|
+
let altAxis: BarValueAxis | undefined;
|
|
670
|
+
if (chart._lastAltYDomain && chart._lastAltYTicks) {
|
|
671
|
+
altAxis =
|
|
672
|
+
chart._rightValueAxisMode === "category" &&
|
|
673
|
+
chart._rightValueCategoryDomain
|
|
674
|
+
? {
|
|
675
|
+
mode: "category",
|
|
676
|
+
domain: chart._rightValueCategoryDomain,
|
|
677
|
+
}
|
|
678
|
+
: {
|
|
679
|
+
mode: "numeric",
|
|
680
|
+
domain: chart._lastAltYDomain,
|
|
681
|
+
ticks: chart._lastAltYTicks,
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
|
|
638
685
|
renderBarAxesChrome(
|
|
639
686
|
chart._chromeCanvas,
|
|
640
687
|
catAxis,
|
|
641
|
-
|
|
642
|
-
chart._lastYTicks,
|
|
688
|
+
valueAxis,
|
|
643
689
|
chart._lastLayout,
|
|
644
690
|
theme,
|
|
645
691
|
chart._glManager?.dpr ?? 1,
|
|
646
|
-
|
|
647
|
-
chart._lastAltYTicks ?? undefined,
|
|
692
|
+
altAxis,
|
|
648
693
|
chart._isHorizontal,
|
|
649
694
|
{
|
|
650
695
|
value: chart.getColumnFormatter(
|
|
@@ -880,11 +925,34 @@ function computeVisibleValueExtent(
|
|
|
880
925
|
let hasRight = false;
|
|
881
926
|
|
|
882
927
|
if (buckets.n > 0) {
|
|
883
|
-
//
|
|
884
|
-
//
|
|
885
|
-
//
|
|
886
|
-
|
|
887
|
-
|
|
928
|
+
// Resolve the visible catIdx range. Category mode: `visCat*` are
|
|
929
|
+
// already in catIdx space, so floor/ceil into `[0, n-1]`.
|
|
930
|
+
// Numeric mode (`date | datetime | integer | float` group_by):
|
|
931
|
+
// `visCat*` are absolute data values from the zoom controller's
|
|
932
|
+
// visible domain — for a datetime axis they're ~1.7e12-magnitude
|
|
933
|
+
// timestamps. A blind `Math.floor(visCatMin)` of that gives `lo
|
|
934
|
+
// ≫ n`, the loop body never executes, and the value-axis refit
|
|
935
|
+
// silently no-ops (chart looks the same horizontally-zoomed as
|
|
936
|
+
// unzoomed). Map the data range back to catIdx via the sorted
|
|
937
|
+
// `_categoryPositions`. See [series-interact.ts:239-250] for the
|
|
938
|
+
// parallel hit-test branch.
|
|
939
|
+
const positions = chart._categoryPositions;
|
|
940
|
+
let lo: number;
|
|
941
|
+
let hi: number;
|
|
942
|
+
if (positions) {
|
|
943
|
+
const r = mapDomainToCatRange(
|
|
944
|
+
positions,
|
|
945
|
+
buckets.n,
|
|
946
|
+
visCatMin,
|
|
947
|
+
visCatMax,
|
|
948
|
+
);
|
|
949
|
+
lo = r.lo;
|
|
950
|
+
hi = r.hi;
|
|
951
|
+
} else {
|
|
952
|
+
lo = Math.max(0, Math.floor(visCatMin));
|
|
953
|
+
hi = Math.min(buckets.n - 1, Math.ceil(visCatMax));
|
|
954
|
+
}
|
|
955
|
+
|
|
888
956
|
const lMin = buckets.leftMin;
|
|
889
957
|
const lMax = buckets.leftMax;
|
|
890
958
|
const rMin = buckets.rightMin;
|
|
@@ -937,6 +1005,59 @@ function computeVisibleValueExtent(
|
|
|
937
1005
|
return next;
|
|
938
1006
|
}
|
|
939
1007
|
|
|
1008
|
+
/**
|
|
1009
|
+
* Map a numeric visible domain `[visMin, visMax]` to the inclusive catIdx
|
|
1010
|
+
* range `[lo, hi]` that intersects it, using a sorted `categoryPositions`
|
|
1011
|
+
* vector (ASC, per the pivot order). Returns an empty range (`lo > hi`)
|
|
1012
|
+
* when the domain misses every category.
|
|
1013
|
+
*
|
|
1014
|
+
* Edges are expanded by one catIdx on each side so a category whose
|
|
1015
|
+
* center sits just outside the visible window — but whose band-half
|
|
1016
|
+
* still overlaps it — still contributes to the auto-fit extent.
|
|
1017
|
+
*/
|
|
1018
|
+
function mapDomainToCatRange(
|
|
1019
|
+
positions: Float64Array,
|
|
1020
|
+
n: number,
|
|
1021
|
+
visMin: number,
|
|
1022
|
+
visMax: number,
|
|
1023
|
+
): { lo: number; hi: number } {
|
|
1024
|
+
if (n === 0 || visMin > visMax) {
|
|
1025
|
+
return { lo: 0, hi: -1 };
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// Lower bound: smallest idx where positions[idx] >= visMin.
|
|
1029
|
+
let l = 0;
|
|
1030
|
+
let r = n;
|
|
1031
|
+
while (l < r) {
|
|
1032
|
+
const m = (l + r) >>> 1;
|
|
1033
|
+
if (positions[m] < visMin) {
|
|
1034
|
+
l = m + 1;
|
|
1035
|
+
} else {
|
|
1036
|
+
r = m;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
const lo = Math.max(0, l - 1);
|
|
1041
|
+
|
|
1042
|
+
// Upper bound: smallest idx where positions[idx] > visMax (`l` after
|
|
1043
|
+
// loop). `l` itself is one past the last in-range catIdx, so the
|
|
1044
|
+
// inclusive `hi` for an exactly-overlapping band is `l - 1`; the
|
|
1045
|
+
// `+1`-then-clamp expands by one to capture partial-overlap bands.
|
|
1046
|
+
l = 0;
|
|
1047
|
+
r = n;
|
|
1048
|
+
while (l < r) {
|
|
1049
|
+
const m = (l + r) >>> 1;
|
|
1050
|
+
if (positions[m] <= visMax) {
|
|
1051
|
+
l = m + 1;
|
|
1052
|
+
} else {
|
|
1053
|
+
r = m;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
const hi = Math.min(n - 1, l);
|
|
1058
|
+
return { lo, hi };
|
|
1059
|
+
}
|
|
1060
|
+
|
|
940
1061
|
function newSeriesAutoFitCache(): SeriesAutoFitCache {
|
|
941
1062
|
return {
|
|
942
1063
|
catMin: 0,
|
|
@@ -13,10 +13,12 @@
|
|
|
13
13
|
export type ChartType = "bar" | "line" | "scatter" | "area";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
* Per-column
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
* Per-column interpolation mode for line / area glyphs.
|
|
17
|
+
*/
|
|
18
|
+
export type InterpolateMode = "skip" | "solid" | "transparent";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Per-column entry inside the viewer's `columns_config` map.
|
|
20
22
|
*/
|
|
21
23
|
export interface ColumnChartConfig {
|
|
22
24
|
/**
|
|
@@ -25,18 +27,24 @@ export interface ColumnChartConfig {
|
|
|
25
27
|
chart_type?: string;
|
|
26
28
|
|
|
27
29
|
/**
|
|
28
|
-
* Explicit stack override.
|
|
29
|
-
* line / scatter do not.
|
|
30
|
+
* Explicit stack override.
|
|
30
31
|
*/
|
|
31
32
|
stack?: boolean;
|
|
32
33
|
|
|
33
34
|
/**
|
|
34
35
|
* Force this aggregate onto the secondary (right) Y axis,
|
|
35
36
|
* independent of `autoAltYAxis` and the dual-axis ratio
|
|
36
|
-
* heuristic.
|
|
37
|
-
* `autoAltYAxis` alone.
|
|
37
|
+
* heuristic.
|
|
38
38
|
*/
|
|
39
39
|
alt_axis?: boolean;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Interpolation mode for line / area glyphs. See
|
|
43
|
+
* {@link InterpolateMode}. Legacy values `true` / `false` are also
|
|
44
|
+
* accepted by {@link resolveInterpolate} (mapped to `"solid"` /
|
|
45
|
+
* `"skip"`). Default `"skip"`. No effect on bar / scatter.
|
|
46
|
+
*/
|
|
47
|
+
interpolate?: InterpolateMode;
|
|
40
48
|
}
|
|
41
49
|
|
|
42
50
|
/**
|
|
@@ -44,10 +52,6 @@ export interface ColumnChartConfig {
|
|
|
44
52
|
* *base* (e.g. `"Sales"`); composite arrow columns like `"North|Sales"`
|
|
45
53
|
* should strip the prefix before calling — the bar pipeline already
|
|
46
54
|
* tracks aggregates as base names, so call sites pass the base directly.
|
|
47
|
-
*
|
|
48
|
-
* `fallback` is the plugin's default glyph (e.g. `"line"` for Y Line),
|
|
49
|
-
* supplied by the plugin element via `setDefaultChartType`. Falls back to
|
|
50
|
-
* `"bar"` when the plugin never set one.
|
|
51
55
|
*/
|
|
52
56
|
export function resolveChartType(
|
|
53
57
|
aggName: string,
|
|
@@ -69,8 +73,6 @@ export function resolveChartType(
|
|
|
69
73
|
|
|
70
74
|
/**
|
|
71
75
|
* Resolve whether a series stacks with its aggregate siblings.
|
|
72
|
-
* Default: `true` for bar/area, `false` for line/scatter. Overridable
|
|
73
|
-
* per column via `columns_config[aggName].stack`.
|
|
74
76
|
*/
|
|
75
77
|
export function resolveStack(
|
|
76
78
|
aggName: string,
|
|
@@ -87,9 +89,7 @@ export function resolveStack(
|
|
|
87
89
|
|
|
88
90
|
/**
|
|
89
91
|
* Resolve whether a column is pinned to the secondary Y axis via
|
|
90
|
-
* `columns_config[aggName].alt_axis`.
|
|
91
|
-
* when `true`, the per-column override forces axis 1 regardless of
|
|
92
|
-
* the auto-split heuristic.
|
|
92
|
+
* `columns_config[aggName].alt_axis`.
|
|
93
93
|
*/
|
|
94
94
|
export function resolveAltAxis(
|
|
95
95
|
aggName: string,
|
|
@@ -97,3 +97,23 @@ export function resolveAltAxis(
|
|
|
97
97
|
): boolean {
|
|
98
98
|
return cfg?.[aggName]?.alt_axis === true;
|
|
99
99
|
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Resolve the interpolation mode for this aggregate.
|
|
103
|
+
*/
|
|
104
|
+
export function resolveInterpolate(
|
|
105
|
+
aggName: string,
|
|
106
|
+
chartType: ChartType,
|
|
107
|
+
cfg: Record<string, ColumnChartConfig> | undefined,
|
|
108
|
+
): InterpolateMode {
|
|
109
|
+
if (chartType !== "line" && chartType !== "area") {
|
|
110
|
+
return "skip";
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const mode = cfg?.[aggName]?.interpolate;
|
|
114
|
+
if (mode === undefined || chartType === "area") {
|
|
115
|
+
return "solid";
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return mode;
|
|
119
|
+
}
|
|
@@ -16,6 +16,7 @@ import type { ZoomConfig } from "../../interaction/zoom-controller";
|
|
|
16
16
|
import { CategoricalYChart } from "../common/categorical-y-chart";
|
|
17
17
|
import { type PlotRect } from "../../layout/plot-layout";
|
|
18
18
|
import { type AxisDomain } from "../../axis/numeric-axis";
|
|
19
|
+
import type { CategoricalDomain } from "../../axis/categorical-axis";
|
|
19
20
|
import {
|
|
20
21
|
buildSeriesPipeline,
|
|
21
22
|
readBarRecord,
|
|
@@ -41,6 +42,9 @@ import { resolvePalette } from "../../theme/palette";
|
|
|
41
42
|
import { LineGlyph } from "./glyphs/draw-lines";
|
|
42
43
|
import { ScatterGlyph } from "./glyphs/draw-scatter";
|
|
43
44
|
import { AreaGlyph } from "./glyphs/draw-areas";
|
|
45
|
+
import { createQuadCornerBuffer } from "../../webgl/instanced-attrs";
|
|
46
|
+
import { compileProgram } from "../../webgl/program-cache";
|
|
47
|
+
import { expandDomainInPlace } from "../common/expand-domain";
|
|
44
48
|
import barVert from "../../shaders/bar.vert.glsl";
|
|
45
49
|
import barFrag from "../../shaders/bar.frag.glsl";
|
|
46
50
|
|
|
@@ -145,6 +149,22 @@ export class SeriesChart extends CategoricalYChart {
|
|
|
145
149
|
_primaryValueLabel = "";
|
|
146
150
|
_altValueLabel = "";
|
|
147
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Per-side value-axis mode. `"category"` fires when every
|
|
154
|
+
* aggregate on that side is post-aggregation `string`-typed
|
|
155
|
+
* (all-or-nothing rule, evaluated independently for primary and
|
|
156
|
+
* alt). When set, `_bars[].y0`/`y1` carry dictionary slot indices
|
|
157
|
+
* instead of numeric values, and the chrome overlay paints a
|
|
158
|
+
* categorical axis on that side.
|
|
159
|
+
*
|
|
160
|
+
* Read by `series-render.ts` to construct the `BarCategoryAxis`
|
|
161
|
+
* descriptor for the value-axis sides.
|
|
162
|
+
*/
|
|
163
|
+
_leftValueAxisMode: "numeric" | "category" = "numeric";
|
|
164
|
+
_rightValueAxisMode: "numeric" | "category" | null = null;
|
|
165
|
+
_leftValueCategoryDomain: CategoricalDomain | null = null;
|
|
166
|
+
_rightValueCategoryDomain: CategoricalDomain | null = null;
|
|
167
|
+
|
|
148
168
|
/**
|
|
149
169
|
* (seriesId * 1e9 + catIdx) → bar-record index in `_bars`. Built once
|
|
150
170
|
* per pipeline run for area-strip lookups; rebuilt on hidden-toggle
|
|
@@ -430,34 +450,33 @@ export class SeriesChart extends CategoricalYChart {
|
|
|
430
450
|
}
|
|
431
451
|
|
|
432
452
|
if (!this._program) {
|
|
433
|
-
|
|
453
|
+
const compiled = compileProgram<
|
|
454
|
+
{ program: WebGLProgram } & CachedLocations
|
|
455
|
+
>(
|
|
456
|
+
glManager,
|
|
434
457
|
"bar",
|
|
435
458
|
barVert,
|
|
436
459
|
barFrag,
|
|
460
|
+
[
|
|
461
|
+
"u_proj_left",
|
|
462
|
+
"u_proj_right",
|
|
463
|
+
"u_hover_series",
|
|
464
|
+
"u_horizontal",
|
|
465
|
+
],
|
|
466
|
+
[
|
|
467
|
+
"a_corner",
|
|
468
|
+
"a_x_center",
|
|
469
|
+
"a_half_width",
|
|
470
|
+
"a_y0",
|
|
471
|
+
"a_y1",
|
|
472
|
+
"a_color",
|
|
473
|
+
"a_series_id",
|
|
474
|
+
"a_axis",
|
|
475
|
+
],
|
|
437
476
|
);
|
|
438
|
-
|
|
439
|
-
this._locations =
|
|
440
|
-
|
|
441
|
-
u_proj_right: gl.getUniformLocation(p, "u_proj_right"),
|
|
442
|
-
u_hover_series: gl.getUniformLocation(p, "u_hover_series"),
|
|
443
|
-
u_horizontal: gl.getUniformLocation(p, "u_horizontal"),
|
|
444
|
-
a_corner: gl.getAttribLocation(p, "a_corner"),
|
|
445
|
-
a_x_center: gl.getAttribLocation(p, "a_x_center"),
|
|
446
|
-
a_half_width: gl.getAttribLocation(p, "a_half_width"),
|
|
447
|
-
a_y0: gl.getAttribLocation(p, "a_y0"),
|
|
448
|
-
a_y1: gl.getAttribLocation(p, "a_y1"),
|
|
449
|
-
a_color: gl.getAttribLocation(p, "a_color"),
|
|
450
|
-
a_series_id: gl.getAttribLocation(p, "a_series_id"),
|
|
451
|
-
a_axis: gl.getAttribLocation(p, "a_axis"),
|
|
452
|
-
};
|
|
453
|
-
|
|
454
|
-
this._cornerBuffer = gl.createBuffer()!;
|
|
455
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, this._cornerBuffer);
|
|
456
|
-
gl.bufferData(
|
|
457
|
-
gl.ARRAY_BUFFER,
|
|
458
|
-
new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]),
|
|
459
|
-
gl.STATIC_DRAW,
|
|
460
|
-
);
|
|
477
|
+
this._program = compiled.program;
|
|
478
|
+
this._locations = compiled;
|
|
479
|
+
this._cornerBuffer = createQuadCornerBuffer(gl);
|
|
461
480
|
}
|
|
462
481
|
|
|
463
482
|
const result = buildSeriesPipeline({
|
|
@@ -483,57 +502,29 @@ export class SeriesChart extends CategoricalYChart {
|
|
|
483
502
|
scratchNegStack: this._negStackScratch,
|
|
484
503
|
});
|
|
485
504
|
|
|
486
|
-
// `domain_mode: "expand"` post-build union.
|
|
487
|
-
// result
|
|
488
|
-
// (`_leftDomain`, `_rightDomain`, `_numericCategoryDomain`,
|
|
489
|
-
// `_categoryOrigin`) automatically
|
|
505
|
+
// `domain_mode: "expand"` post-build union. Each call mutates
|
|
506
|
+
// the pipeline result in place so the downstream assignments
|
|
507
|
+
// below (`_leftDomain`, `_rightDomain`, `_numericCategoryDomain`,
|
|
508
|
+
// `_categoryOrigin`) automatically pick up the grown extent.
|
|
490
509
|
// `"fit"` (or a fresh reset) leaves the result untouched and
|
|
491
510
|
// clears the accumulators so the next toggle starts fresh.
|
|
492
511
|
if (this._pluginConfig.domain_mode === "expand") {
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
);
|
|
498
|
-
result.leftDomain.max = Math.max(
|
|
499
|
-
this._expandedLeftDomain.max,
|
|
500
|
-
result.leftDomain.max,
|
|
501
|
-
);
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
this._expandedLeftDomain = { ...result.leftDomain };
|
|
505
|
-
|
|
512
|
+
this._expandedLeftDomain = expandDomainInPlace(
|
|
513
|
+
this._expandedLeftDomain,
|
|
514
|
+
result.leftDomain,
|
|
515
|
+
);
|
|
506
516
|
if (result.rightDomain) {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
);
|
|
512
|
-
result.rightDomain.max = Math.max(
|
|
513
|
-
this._expandedRightDomain.max,
|
|
514
|
-
result.rightDomain.max,
|
|
515
|
-
);
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
this._expandedRightDomain = { ...result.rightDomain };
|
|
517
|
+
this._expandedRightDomain = expandDomainInPlace(
|
|
518
|
+
this._expandedRightDomain,
|
|
519
|
+
result.rightDomain,
|
|
520
|
+
);
|
|
519
521
|
}
|
|
520
522
|
|
|
521
523
|
if (result.numericCategoryDomain) {
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
);
|
|
527
|
-
result.numericCategoryDomain.max = Math.max(
|
|
528
|
-
this._expandedCategoryDomain.max,
|
|
529
|
-
result.numericCategoryDomain.max,
|
|
530
|
-
);
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
this._expandedCategoryDomain = {
|
|
534
|
-
min: result.numericCategoryDomain.min,
|
|
535
|
-
max: result.numericCategoryDomain.max,
|
|
536
|
-
};
|
|
524
|
+
this._expandedCategoryDomain = expandDomainInPlace(
|
|
525
|
+
this._expandedCategoryDomain,
|
|
526
|
+
result.numericCategoryDomain,
|
|
527
|
+
);
|
|
537
528
|
}
|
|
538
529
|
} else {
|
|
539
530
|
this._expandedLeftDomain = null;
|
|
@@ -617,6 +608,10 @@ export class SeriesChart extends CategoricalYChart {
|
|
|
617
608
|
this._leftDomain = result.leftDomain;
|
|
618
609
|
this._rightDomain = result.rightDomain;
|
|
619
610
|
this._hasRightAxis = result.hasRightAxis;
|
|
611
|
+
this._leftValueAxisMode = result.leftValueAxisMode;
|
|
612
|
+
this._rightValueAxisMode = result.rightValueAxisMode;
|
|
613
|
+
this._leftValueCategoryDomain = result.leftValueCategoryDomain;
|
|
614
|
+
this._rightValueCategoryDomain = result.rightValueCategoryDomain;
|
|
620
615
|
|
|
621
616
|
// Resolve the palette eagerly. Both `uploadBarInstances` (color
|
|
622
617
|
// attribute) and `rebuildGlyphBuffers` (per-series RGB capture)
|