@perspective-dev/viewer-charts 5.3.0 → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdn/perspective-viewer-charts.js +1 -2
- package/dist/cdn/perspective-viewer-charts.js.map +4 -4
- package/dist/esm/axis/legend.d.ts +53 -5
- package/dist/esm/axis/map-ticks.d.ts +15 -0
- package/dist/esm/charts/candlestick/candlestick-interact.d.ts +2 -6
- package/dist/esm/charts/cartesian/cartesian.d.ts +6 -0
- package/dist/esm/charts/cartesian/glyph.d.ts +2 -1
- package/dist/esm/charts/cartesian/glyphs/density.d.ts +2 -1
- package/dist/esm/charts/cartesian/glyphs/lines.d.ts +2 -1
- package/dist/esm/charts/cartesian/glyphs/points.d.ts +2 -1
- package/dist/esm/charts/cartesian/tooltip-lines.d.ts +1 -1
- package/dist/esm/charts/chart-base.d.ts +2 -0
- package/dist/esm/charts/chart.d.ts +77 -6
- package/dist/esm/charts/common/draw-tooltip-box.d.ts +2 -1
- package/dist/esm/charts/common/tree-interact.d.ts +1 -1
- package/dist/esm/charts/series/series-build.d.ts +7 -0
- package/dist/esm/charts/series/series-interact.d.ts +1 -1
- package/dist/esm/charts/series/series.d.ts +6 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/interaction/axis-indicators.d.ts +11 -0
- package/dist/esm/interaction/host-sink-dom.d.ts +3 -2
- package/dist/esm/interaction/host-sink-message.d.ts +3 -2
- package/dist/esm/interaction/lazy-tooltip.d.ts +2 -2
- package/dist/esm/interaction/legend-controller.d.ts +246 -0
- package/dist/esm/interaction/tooltip-controller.d.ts +20 -95
- package/dist/esm/interaction/tooltip-grid.d.ts +33 -0
- package/dist/esm/layout/facet-grid.d.ts +6 -0
- package/dist/esm/layout/ticks.d.ts +1 -0
- package/dist/esm/map/tile-source.d.ts +77 -8
- package/dist/esm/perspective-viewer-charts.js +1 -2
- package/dist/esm/perspective-viewer-charts.js.map +4 -4
- package/dist/esm/plugin/charts.d.ts +2 -0
- package/dist/esm/plugin/format-defaults.d.ts +9 -0
- package/dist/esm/plugin/plugin.d.ts +20 -0
- package/dist/esm/theme/gradient.d.ts +1 -0
- package/dist/esm/transport/protocol.d.ts +45 -3
- package/dist/esm/transport/renderer-transport.d.ts +3 -0
- package/dist/esm/worker/renderer.worker.d.ts +19 -1
- package/package.json +6 -6
- package/src/css/perspective-viewer-charts.css +0 -1
- package/src/ts/axis/bar-axis.ts +2 -7
- package/src/ts/axis/legend.ts +351 -24
- package/src/ts/axis/map-ticks.ts +94 -0
- package/src/ts/axis/numeric-axis.ts +2 -9
- package/src/ts/charts/candlestick/candlestick-interact.ts +46 -40
- package/src/ts/charts/candlestick/candlestick-render.ts +73 -10
- package/src/ts/charts/cartesian/cartesian-interact.ts +9 -3
- package/src/ts/charts/cartesian/cartesian-render.ts +369 -75
- package/src/ts/charts/cartesian/cartesian.ts +9 -0
- package/src/ts/charts/cartesian/glyph.ts +6 -2
- package/src/ts/charts/cartesian/glyphs/density.ts +2 -2
- package/src/ts/charts/cartesian/glyphs/lines.ts +15 -11
- package/src/ts/charts/cartesian/glyphs/points.ts +9 -5
- package/src/ts/charts/cartesian/tooltip-lines.ts +8 -8
- package/src/ts/charts/chart-base.ts +34 -32
- package/src/ts/charts/chart.ts +106 -7
- package/src/ts/charts/common/draw-tooltip-box.ts +13 -30
- package/src/ts/charts/common/tree-chrome.ts +102 -23
- package/src/ts/charts/common/tree-interact.ts +18 -17
- package/src/ts/charts/heatmap/heatmap-interact.ts +65 -7
- package/src/ts/charts/heatmap/heatmap-render.ts +100 -24
- package/src/ts/charts/map/map.ts +3 -5
- package/src/ts/charts/series/series-build.ts +14 -2
- package/src/ts/charts/series/series-interact.ts +12 -11
- package/src/ts/charts/series/series-render.ts +305 -132
- package/src/ts/charts/series/series.ts +10 -0
- package/src/ts/charts/sunburst/sunburst-render.ts +7 -1
- package/src/ts/charts/treemap/treemap-render.ts +7 -1
- package/src/ts/data/view-reader.ts +1 -4
- package/src/ts/index.ts +2 -0
- package/src/ts/interaction/axis-indicators.ts +137 -0
- package/src/ts/interaction/host-sink-dom.ts +66 -2
- package/src/ts/interaction/host-sink-message.ts +8 -2
- package/src/ts/interaction/lazy-tooltip.ts +5 -5
- package/src/ts/interaction/legend-controller.ts +1084 -0
- package/src/ts/interaction/tooltip-controller.ts +43 -131
- package/src/ts/interaction/tooltip-grid.ts +187 -0
- package/src/ts/layout/facet-grid.ts +8 -1
- package/src/ts/layout/ticks.ts +12 -0
- package/src/ts/map/tile-loader.ts +16 -1
- package/src/ts/map/tile-source.ts +199 -53
- package/src/ts/map/tile-sources.json +18 -0
- package/src/ts/plugin/charts.ts +40 -6
- package/src/ts/plugin/format-defaults.ts +37 -0
- package/src/ts/plugin/plugin.ts +236 -67
- package/src/ts/shaders/line.vert.glsl +5 -2
- package/src/ts/shaders/scatter.vert.glsl +0 -7
- package/src/ts/theme/gradient.ts +24 -0
- package/src/ts/transport/protocol.ts +46 -2
- package/src/ts/transport/renderer-transport.ts +83 -4
- package/src/ts/worker/dispatch.ts +6 -2
- package/src/ts/worker/renderer.worker.ts +78 -2
|
@@ -31,6 +31,11 @@ import {
|
|
|
31
31
|
} from "../../webgl/plot-frame";
|
|
32
32
|
import { ensureGradientTexture } from "../../webgl/gradient-texture";
|
|
33
33
|
import { renderCanvasTooltip } from "../../interaction/tooltip-controller";
|
|
34
|
+
import { tooltipStyleOf } from "../../interaction/tooltip-grid";
|
|
35
|
+
import {
|
|
36
|
+
renderAxisHoverIndicators,
|
|
37
|
+
type AxisIndicator,
|
|
38
|
+
} from "../../interaction/axis-indicators";
|
|
34
39
|
import {
|
|
35
40
|
computeTicks,
|
|
36
41
|
renderGridlines,
|
|
@@ -41,6 +46,9 @@ import {
|
|
|
41
46
|
type AxisDomain,
|
|
42
47
|
} from "../../axis/numeric-axis";
|
|
43
48
|
import { initCanvas, getScaledContext } from "../../axis/canvas";
|
|
49
|
+
import { computeMapDegreeTicks } from "../../axis/map-ticks";
|
|
50
|
+
import { mercatorToLonLat } from "../../map/mercator";
|
|
51
|
+
import { stepTickFormatter } from "../../layout/ticks";
|
|
44
52
|
import {
|
|
45
53
|
type CategoricalDomain,
|
|
46
54
|
type CategoricalLevel,
|
|
@@ -54,7 +62,16 @@ import {
|
|
|
54
62
|
renderLegendAt,
|
|
55
63
|
renderCategoricalLegend,
|
|
56
64
|
renderCategoricalLegendAt,
|
|
65
|
+
legendAutoFit,
|
|
66
|
+
gradientLegendAutoFit,
|
|
67
|
+
type LegendPaintView,
|
|
57
68
|
} from "../../axis/legend";
|
|
69
|
+
import {
|
|
70
|
+
legendRightGutter,
|
|
71
|
+
legendSidebarWidth,
|
|
72
|
+
resolveLegendMode,
|
|
73
|
+
type LegendAutoFit,
|
|
74
|
+
} from "../../interaction/legend-controller";
|
|
58
75
|
|
|
59
76
|
/**
|
|
60
77
|
* NaN guard: `_xOrigin`/`_yOrigin` start as NaN before the first valid sample.
|
|
@@ -63,6 +80,43 @@ function rebaseOrigin(o: number): number {
|
|
|
63
80
|
return isNaN(o) ? 0 : o;
|
|
64
81
|
}
|
|
65
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Legend entry count for `legend_mode: "auto"` resolution: the
|
|
85
|
+
* categorical swatch count when a string color column is wired, `0`
|
|
86
|
+
* for continuous gradient legends (no entry list — always compact).
|
|
87
|
+
*/
|
|
88
|
+
function legendEntryCount(chart: CartesianChart): number {
|
|
89
|
+
return chart._colorIsString ? chart._uniqueColorLabels.size : 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Content measurements for a `legend_size_mode: "auto"` floating panel.
|
|
94
|
+
* Categorical legends hug their label list; gradient legends have no
|
|
95
|
+
* rows and hug their tick labels instead.
|
|
96
|
+
*/
|
|
97
|
+
function legendFit(chart: CartesianChart, theme: Theme): LegendAutoFit {
|
|
98
|
+
const title = chart._colorName ?? undefined;
|
|
99
|
+
if (chart._colorIsString) {
|
|
100
|
+
return legendAutoFit(
|
|
101
|
+
chart._chromeCanvas,
|
|
102
|
+
theme,
|
|
103
|
+
chart._uniqueColorLabels.size,
|
|
104
|
+
() => chart._uniqueColorLabels.keys(),
|
|
105
|
+
{ title },
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return gradientLegendAutoFit(
|
|
110
|
+
chart._chromeCanvas,
|
|
111
|
+
theme,
|
|
112
|
+
{ min: chart._colorMin, max: chart._colorMax },
|
|
113
|
+
chart._colorName
|
|
114
|
+
? chart.getColumnFormatter(chart._colorName, "value")
|
|
115
|
+
: undefined,
|
|
116
|
+
title,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
66
120
|
/**
|
|
67
121
|
* Full-frame render: gridlines → glyph draw inside the plot-frame
|
|
68
122
|
* scissor → chrome overlay (axes + legend + tooltip).
|
|
@@ -368,7 +422,12 @@ function renderSinglePlotFrame(
|
|
|
368
422
|
|
|
369
423
|
// One-pass plot-width / plot-height estimate to size the
|
|
370
424
|
// categorical gutter overrides; same approach as series-render.
|
|
371
|
-
const estRight =
|
|
425
|
+
const estRight = legendRightGutter(
|
|
426
|
+
chart._pluginConfig,
|
|
427
|
+
hasColorCol,
|
|
428
|
+
80,
|
|
429
|
+
legendEntryCount(chart),
|
|
430
|
+
);
|
|
372
431
|
const estLeftPlain = 55 + (chart._yLabel ? 16 : 0);
|
|
373
432
|
const estPlotWidth = Math.max(1, cssWidth - estLeftPlain - estRight);
|
|
374
433
|
const leftExtra = chart._yCategoryDomain
|
|
@@ -378,13 +437,36 @@ function renderSinglePlotFrame(
|
|
|
378
437
|
? measureCategoricalAxisHeight(chart._xCategoryDomain, estPlotWidth)
|
|
379
438
|
: undefined;
|
|
380
439
|
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
440
|
+
const isMap = chart._renderMode === "map";
|
|
441
|
+
const bareMap = isMap && !chart._pluginConfig.numeric_axes;
|
|
442
|
+
const layout = new PlotLayout(
|
|
443
|
+
cssWidth,
|
|
444
|
+
cssHeight,
|
|
445
|
+
bareMap
|
|
446
|
+
? {
|
|
447
|
+
hasXLabel: false,
|
|
448
|
+
hasYLabel: false,
|
|
449
|
+
hasLegend: hasColorCol,
|
|
450
|
+
leftExtra: 0,
|
|
451
|
+
bottomExtra: 0,
|
|
452
|
+
rightExtra:
|
|
453
|
+
hasColorCol &&
|
|
454
|
+
resolveLegendMode(
|
|
455
|
+
chart._pluginConfig,
|
|
456
|
+
legendEntryCount(chart),
|
|
457
|
+
) === "sidebar"
|
|
458
|
+
? legendSidebarWidth(chart._pluginConfig, 80)
|
|
459
|
+
: 0,
|
|
460
|
+
}
|
|
461
|
+
: {
|
|
462
|
+
hasXLabel: !!chart._xLabel,
|
|
463
|
+
hasYLabel: !!chart._yLabel,
|
|
464
|
+
hasLegend: hasColorCol,
|
|
465
|
+
leftExtra,
|
|
466
|
+
bottomExtra,
|
|
467
|
+
rightExtra: estRight,
|
|
468
|
+
},
|
|
469
|
+
);
|
|
388
470
|
chart._lastLayout = layout;
|
|
389
471
|
if (chart._zoomController) {
|
|
390
472
|
chart._zoomController.updateLayout(layout);
|
|
@@ -408,8 +490,6 @@ function renderSinglePlotFrame(
|
|
|
408
490
|
const xTicks = chart._xIsString ? [] : numericTicks.xTicks;
|
|
409
491
|
const yTicks = chart._yIsString ? [] : numericTicks.yTicks;
|
|
410
492
|
|
|
411
|
-
const isMap = chart._renderMode === "map";
|
|
412
|
-
|
|
413
493
|
// Defer the gridline draw past the GPU fence (see `_defer2D`) so the
|
|
414
494
|
// gridline canvas doesn't present ahead of the GL glyphs on resize.
|
|
415
495
|
// The closure captures this frame's `layout` / ticks / `theme`.
|
|
@@ -516,14 +596,28 @@ function renderFacetedFrame(
|
|
|
516
596
|
// charts always have both axes, so the false branch maps to
|
|
517
597
|
// per-cell mode (never to "none", which is reserved for tree
|
|
518
598
|
// charts).
|
|
599
|
+
const isMap = chart._renderMode === "map";
|
|
600
|
+
const bareMap = isMap && !chart._pluginConfig.numeric_axes;
|
|
519
601
|
const grid: FacetGrid = buildFacetGrid(labels, {
|
|
520
602
|
cssWidth,
|
|
521
603
|
cssHeight,
|
|
522
|
-
xAxis:
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
604
|
+
xAxis: bareMap
|
|
605
|
+
? "none"
|
|
606
|
+
: chart._lastEffectiveSharedX
|
|
607
|
+
? "outer"
|
|
608
|
+
: "cell",
|
|
609
|
+
yAxis: bareMap
|
|
610
|
+
? "none"
|
|
611
|
+
: chart._lastEffectiveSharedY
|
|
612
|
+
? "outer"
|
|
613
|
+
: "cell",
|
|
614
|
+
hasLegend:
|
|
615
|
+
hasLegend &&
|
|
616
|
+
resolveLegendMode(chart._pluginConfig, legendEntryCount(chart)) ===
|
|
617
|
+
"sidebar",
|
|
618
|
+
legendWidth: legendSidebarWidth(chart._pluginConfig, 96),
|
|
619
|
+
hasXLabel: !bareMap && !!chart._xLabel,
|
|
620
|
+
hasYLabel: !bareMap && !!chart._yLabel,
|
|
527
621
|
gap: chart._facetConfig.facet_padding,
|
|
528
622
|
});
|
|
529
623
|
chart._facetGrid = grid;
|
|
@@ -618,7 +712,6 @@ function renderFacetedFrame(
|
|
|
618
712
|
// own domain). Map mode skips gridlines entirely; the
|
|
619
713
|
// basemap layer is rendered into the GL canvas inside the
|
|
620
714
|
// facet's scissor below.
|
|
621
|
-
const isMap = chart._renderMode === "map";
|
|
622
715
|
if (gridlineCanvas && !isMap) {
|
|
623
716
|
// Deferred to the post-fence 2D flush. The closure captures
|
|
624
717
|
// this facet's `cell` (whose `cell.layout` already carries
|
|
@@ -733,6 +826,30 @@ function renderSinglePlotChromeOverlay(chart: CartesianChart): void {
|
|
|
733
826
|
const isMap = chart._renderMode === "map";
|
|
734
827
|
|
|
735
828
|
if (isMap) {
|
|
829
|
+
if (chart._pluginConfig.numeric_axes) {
|
|
830
|
+
const mt = computeMapDegreeTicks(layout);
|
|
831
|
+
renderCellXAxis(
|
|
832
|
+
chart._chromeCanvas!,
|
|
833
|
+
chart._lastXDomain!,
|
|
834
|
+
layout,
|
|
835
|
+
mt.xTicks,
|
|
836
|
+
theme,
|
|
837
|
+
!!chart._xLabel,
|
|
838
|
+
dpr,
|
|
839
|
+
mt.formatX,
|
|
840
|
+
);
|
|
841
|
+
renderCellYAxis(
|
|
842
|
+
chart._chromeCanvas!,
|
|
843
|
+
chart._lastYDomain!,
|
|
844
|
+
layout,
|
|
845
|
+
mt.yTicks,
|
|
846
|
+
theme,
|
|
847
|
+
!!chart._yLabel,
|
|
848
|
+
dpr,
|
|
849
|
+
mt.formatY,
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
|
|
736
853
|
chart.renderMapChrome(chart._chromeCanvas!, layout, theme, dpr);
|
|
737
854
|
} else {
|
|
738
855
|
renderCartesianCellAxes(
|
|
@@ -748,37 +865,95 @@ function renderSinglePlotChromeOverlay(chart: CartesianChart): void {
|
|
|
748
865
|
);
|
|
749
866
|
}
|
|
750
867
|
|
|
751
|
-
|
|
868
|
+
const legendMode = resolveLegendMode(
|
|
869
|
+
chart._pluginConfig,
|
|
870
|
+
legendEntryCount(chart),
|
|
871
|
+
);
|
|
872
|
+
let legendPainted = false;
|
|
873
|
+
if (chart._lastHasColorCol && legendMode !== "none") {
|
|
752
874
|
const stops = chart._lastGradientStops ?? theme.gradientStops;
|
|
875
|
+
const floating = legendMode === "floating";
|
|
876
|
+
const view: LegendPaintView = {
|
|
877
|
+
mode: floating ? "floating" : "sidebar",
|
|
878
|
+
legend: chart._legend,
|
|
879
|
+
title: chart._colorName ?? undefined,
|
|
880
|
+
opacity: chart._pluginConfig.legend_opacity,
|
|
881
|
+
};
|
|
882
|
+
const floatBox = floating
|
|
883
|
+
? chart._legend.floatingBox(
|
|
884
|
+
chart._pluginConfig,
|
|
885
|
+
layout.cssWidth,
|
|
886
|
+
layout.cssHeight,
|
|
887
|
+
legendFit(chart, theme),
|
|
888
|
+
)
|
|
889
|
+
: null;
|
|
753
890
|
if (chart._colorIsString && chart._uniqueColorLabels.size > 0) {
|
|
754
891
|
const palette = resolvePalette(
|
|
755
892
|
theme.seriesPalette,
|
|
756
893
|
stops,
|
|
757
894
|
chart._uniqueColorLabels.size,
|
|
758
895
|
);
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
896
|
+
if (floatBox) {
|
|
897
|
+
renderCategoricalLegendAt(
|
|
898
|
+
chart._chromeCanvas!,
|
|
899
|
+
floatBox,
|
|
900
|
+
chart._uniqueColorLabels,
|
|
901
|
+
palette,
|
|
902
|
+
theme,
|
|
903
|
+
view,
|
|
904
|
+
);
|
|
905
|
+
} else {
|
|
906
|
+
renderCategoricalLegend(
|
|
907
|
+
chart._chromeCanvas!,
|
|
908
|
+
layout,
|
|
909
|
+
chart._uniqueColorLabels,
|
|
910
|
+
palette,
|
|
911
|
+
theme,
|
|
912
|
+
view,
|
|
913
|
+
);
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
legendPainted = true;
|
|
766
917
|
} else if (chart._colorName) {
|
|
767
|
-
|
|
768
|
-
chart.
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
stops,
|
|
776
|
-
theme,
|
|
777
|
-
chart.getColumnFormatter(chart._colorName, "value"),
|
|
918
|
+
const colorDomain = {
|
|
919
|
+
min: chart._colorMin,
|
|
920
|
+
max: chart._colorMax,
|
|
921
|
+
label: chart._colorName,
|
|
922
|
+
};
|
|
923
|
+
const formatter = chart.getColumnFormatter(
|
|
924
|
+
chart._colorName,
|
|
925
|
+
"value",
|
|
778
926
|
);
|
|
927
|
+
if (floatBox) {
|
|
928
|
+
renderLegendAt(
|
|
929
|
+
chart._chromeCanvas!,
|
|
930
|
+
floatBox,
|
|
931
|
+
colorDomain,
|
|
932
|
+
stops,
|
|
933
|
+
theme,
|
|
934
|
+
formatter,
|
|
935
|
+
view,
|
|
936
|
+
);
|
|
937
|
+
} else {
|
|
938
|
+
renderLegend(
|
|
939
|
+
chart._chromeCanvas!,
|
|
940
|
+
layout,
|
|
941
|
+
colorDomain,
|
|
942
|
+
stops,
|
|
943
|
+
theme,
|
|
944
|
+
formatter,
|
|
945
|
+
view,
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
legendPainted = true;
|
|
779
950
|
}
|
|
780
951
|
}
|
|
781
952
|
|
|
953
|
+
if (!legendPainted) {
|
|
954
|
+
chart._legend.clearPainted();
|
|
955
|
+
}
|
|
956
|
+
|
|
782
957
|
renderScatterLabels(chart, chart._chromeCanvas!, layout, 0, 1);
|
|
783
958
|
|
|
784
959
|
if (chart._hoveredIndex >= 0 && chart._xData && chart._yData) {
|
|
@@ -816,35 +991,44 @@ function renderFacetedChromeOverlay(chart: CartesianChart): void {
|
|
|
816
991
|
// (one pass per leftmost-column cell). Map mode replaces both
|
|
817
992
|
// with `renderMapChrome` (attribution + scale bar), painted once
|
|
818
993
|
// over the whole facet grid.
|
|
994
|
+
const mapAxes = isMap && chart._pluginConfig.numeric_axes;
|
|
995
|
+
const sharedMapTicks =
|
|
996
|
+
mapAxes && grid.cells.length > 0
|
|
997
|
+
? computeMapDegreeTicks(grid.cells[0].layout)
|
|
998
|
+
: null;
|
|
819
999
|
if (isMap) {
|
|
820
1000
|
chart.renderMapChrome(canvas, chart._lastLayout!, theme, dpr);
|
|
821
1001
|
}
|
|
822
1002
|
|
|
823
|
-
if (!isMap && sharedX && grid.outerXAxisRect) {
|
|
1003
|
+
if ((!isMap || sharedMapTicks) && sharedX && grid.outerXAxisRect) {
|
|
824
1004
|
renderOuterXAxis(
|
|
825
1005
|
canvas,
|
|
826
1006
|
grid.outerXAxisRect,
|
|
827
1007
|
xDomain,
|
|
828
|
-
sharedXTicks,
|
|
1008
|
+
sharedMapTicks ? sharedMapTicks.xTicks : sharedXTicks,
|
|
829
1009
|
bottomRowLayouts(grid),
|
|
830
1010
|
theme,
|
|
831
1011
|
!!chart._xLabel,
|
|
832
1012
|
dpr,
|
|
833
|
-
|
|
1013
|
+
sharedMapTicks
|
|
1014
|
+
? sharedMapTicks.formatX
|
|
1015
|
+
: chart.getColumnFormatter(chart._xName, "tick"),
|
|
834
1016
|
);
|
|
835
1017
|
}
|
|
836
1018
|
|
|
837
|
-
if (!isMap && sharedY && grid.outerYAxisRect) {
|
|
1019
|
+
if ((!isMap || sharedMapTicks) && sharedY && grid.outerYAxisRect) {
|
|
838
1020
|
renderOuterYAxis(
|
|
839
1021
|
canvas,
|
|
840
1022
|
grid.outerYAxisRect,
|
|
841
1023
|
yDomain,
|
|
842
|
-
sharedYTicks,
|
|
1024
|
+
sharedMapTicks ? sharedMapTicks.yTicks : sharedYTicks,
|
|
843
1025
|
leftColumnLayouts(grid),
|
|
844
1026
|
theme,
|
|
845
1027
|
!!chart._yLabel,
|
|
846
1028
|
dpr,
|
|
847
|
-
|
|
1029
|
+
sharedMapTicks
|
|
1030
|
+
? sharedMapTicks.formatY
|
|
1031
|
+
: chart.getColumnFormatter(chart._yName, "tick"),
|
|
848
1032
|
);
|
|
849
1033
|
}
|
|
850
1034
|
|
|
@@ -857,11 +1041,16 @@ function renderFacetedChromeOverlay(chart: CartesianChart): void {
|
|
|
857
1041
|
const d = zc ? zc.getVisibleDomain() : null;
|
|
858
1042
|
const localX = d ? { ...xDomain, min: d.xMin, max: d.xMax } : xDomain;
|
|
859
1043
|
const localY = d ? { ...yDomain, min: d.yMin, max: d.yMax } : yDomain;
|
|
860
|
-
const
|
|
861
|
-
?
|
|
862
|
-
:
|
|
1044
|
+
const cellMapTicks = mapAxes
|
|
1045
|
+
? computeMapDegreeTicks(cell.layout)
|
|
1046
|
+
: null;
|
|
1047
|
+
const ticks = cellMapTicks
|
|
1048
|
+
? cellMapTicks
|
|
1049
|
+
: independent
|
|
1050
|
+
? computeTicks(localX, localY, cell.layout)
|
|
1051
|
+
: { xTicks: sharedXTicks, yTicks: sharedYTicks };
|
|
863
1052
|
|
|
864
|
-
if (!isMap && !sharedX) {
|
|
1053
|
+
if ((!isMap || cellMapTicks) && !sharedX) {
|
|
865
1054
|
if (chart._xIsString && chart._xCategoryDomain) {
|
|
866
1055
|
const cellCtx = getScaledContext(canvas, dpr);
|
|
867
1056
|
if (cellCtx) {
|
|
@@ -881,12 +1070,14 @@ function renderFacetedChromeOverlay(chart: CartesianChart): void {
|
|
|
881
1070
|
theme,
|
|
882
1071
|
!!chart._xLabel,
|
|
883
1072
|
dpr,
|
|
884
|
-
|
|
1073
|
+
cellMapTicks
|
|
1074
|
+
? cellMapTicks.formatX
|
|
1075
|
+
: chart.getColumnFormatter(chart._xName, "tick"),
|
|
885
1076
|
);
|
|
886
1077
|
}
|
|
887
1078
|
}
|
|
888
1079
|
|
|
889
|
-
if (!isMap && !sharedY) {
|
|
1080
|
+
if ((!isMap || cellMapTicks) && !sharedY) {
|
|
890
1081
|
if (chart._yIsString && chart._yCategoryDomain) {
|
|
891
1082
|
const cellCtx = getScaledContext(canvas, dpr);
|
|
892
1083
|
if (cellCtx) {
|
|
@@ -906,7 +1097,9 @@ function renderFacetedChromeOverlay(chart: CartesianChart): void {
|
|
|
906
1097
|
theme,
|
|
907
1098
|
!!chart._yLabel,
|
|
908
1099
|
dpr,
|
|
909
|
-
|
|
1100
|
+
cellMapTicks
|
|
1101
|
+
? cellMapTicks.formatY
|
|
1102
|
+
: chart.getColumnFormatter(chart._yName, "tick"),
|
|
910
1103
|
);
|
|
911
1104
|
}
|
|
912
1105
|
}
|
|
@@ -919,10 +1112,29 @@ function renderFacetedChromeOverlay(chart: CartesianChart): void {
|
|
|
919
1112
|
}
|
|
920
1113
|
|
|
921
1114
|
// Shared legend: categorical (string color) or gradient
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
1115
|
+
const legendMode = resolveLegendMode(
|
|
1116
|
+
chart._pluginConfig,
|
|
1117
|
+
legendEntryCount(chart),
|
|
1118
|
+
);
|
|
1119
|
+
const floating = legendMode === "floating";
|
|
1120
|
+
const legendAnchor = floating
|
|
1121
|
+
? chart._legend.floatingBox(
|
|
1122
|
+
chart._pluginConfig,
|
|
1123
|
+
chart._lastLayout!.cssWidth,
|
|
1124
|
+
chart._lastLayout!.cssHeight,
|
|
1125
|
+
legendFit(chart, theme),
|
|
1126
|
+
)
|
|
1127
|
+
: grid.legendRect;
|
|
1128
|
+
let legendPainted = false;
|
|
1129
|
+
if (chart._lastHasColorCol && legendMode !== "none" && legendAnchor) {
|
|
925
1130
|
const stops = chart._lastGradientStops ?? theme.gradientStops;
|
|
1131
|
+
const view: LegendPaintView = {
|
|
1132
|
+
mode: floating ? "floating" : "sidebar",
|
|
1133
|
+
legend: chart._legend,
|
|
1134
|
+
title: chart._colorName ?? undefined,
|
|
1135
|
+
sidebarGutter: floating ? undefined : grid.legendRect?.width,
|
|
1136
|
+
opacity: chart._pluginConfig.legend_opacity,
|
|
1137
|
+
};
|
|
926
1138
|
if (chart._colorIsString && chart._uniqueColorLabels.size > 0) {
|
|
927
1139
|
const palette = resolvePalette(
|
|
928
1140
|
theme.seriesPalette,
|
|
@@ -931,23 +1143,27 @@ function renderFacetedChromeOverlay(chart: CartesianChart): void {
|
|
|
931
1143
|
);
|
|
932
1144
|
renderCategoricalLegendAt(
|
|
933
1145
|
canvas,
|
|
934
|
-
|
|
1146
|
+
legendAnchor,
|
|
935
1147
|
chart._uniqueColorLabels,
|
|
936
1148
|
palette,
|
|
937
1149
|
theme,
|
|
1150
|
+
view,
|
|
938
1151
|
);
|
|
1152
|
+
legendPainted = true;
|
|
939
1153
|
} else if (chart._colorName) {
|
|
940
1154
|
// Numeric gradient legend in the shared outer rect. The
|
|
941
1155
|
// label sits above the bar, so inset the rect's top by
|
|
942
1156
|
// the usual 20 px that `renderLegend` reserves.
|
|
943
1157
|
renderLegendAt(
|
|
944
1158
|
canvas,
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
1159
|
+
floating
|
|
1160
|
+
? legendAnchor
|
|
1161
|
+
: {
|
|
1162
|
+
x: legendAnchor.x,
|
|
1163
|
+
y: legendAnchor.y + 20,
|
|
1164
|
+
width: legendAnchor.width,
|
|
1165
|
+
height: legendAnchor.height - 20,
|
|
1166
|
+
},
|
|
951
1167
|
{
|
|
952
1168
|
min: chart._colorMin,
|
|
953
1169
|
max: chart._colorMax,
|
|
@@ -956,10 +1172,16 @@ function renderFacetedChromeOverlay(chart: CartesianChart): void {
|
|
|
956
1172
|
stops,
|
|
957
1173
|
theme,
|
|
958
1174
|
chart.getColumnFormatter(chart._colorName, "value"),
|
|
1175
|
+
view,
|
|
959
1176
|
);
|
|
1177
|
+
legendPainted = true;
|
|
960
1178
|
}
|
|
961
1179
|
}
|
|
962
1180
|
|
|
1181
|
+
if (!legendPainted) {
|
|
1182
|
+
chart._legend.clearPainted();
|
|
1183
|
+
}
|
|
1184
|
+
|
|
963
1185
|
// Coordinated hover / click indicators across facets. The tooltip
|
|
964
1186
|
// lines are whatever the last resolved lazy fetch produced (or
|
|
965
1187
|
// null while a fetch is still in flight); `renderCanvasTooltip`
|
|
@@ -975,7 +1197,9 @@ function renderFacetedChromeOverlay(chart: CartesianChart): void {
|
|
|
975
1197
|
const dataY = chart._yData[chart._hoveredIndex] + yOrigin;
|
|
976
1198
|
const sourceFacet = seriesFromIndex(chart, chart._hoveredIndex);
|
|
977
1199
|
const opts = chart.glyph.tooltipOptions();
|
|
978
|
-
const
|
|
1200
|
+
const axisInd = axisIndicatorsEnabled(chart, opts);
|
|
1201
|
+
const style = tooltipStyleOf(chart._pluginConfig);
|
|
1202
|
+
const tooltipGrid = chart._lazyTooltip.grid ?? [];
|
|
979
1203
|
|
|
980
1204
|
for (let i = 0; i < grid.cells.length; i++) {
|
|
981
1205
|
const cell = grid.cells[i];
|
|
@@ -994,10 +1218,28 @@ function renderFacetedChromeOverlay(chart: CartesianChart): void {
|
|
|
994
1218
|
continue;
|
|
995
1219
|
}
|
|
996
1220
|
|
|
1221
|
+
if (isSource && axisInd) {
|
|
1222
|
+
renderAxisHoverIndicators(
|
|
1223
|
+
canvas,
|
|
1224
|
+
cell.layout,
|
|
1225
|
+
theme,
|
|
1226
|
+
dpr,
|
|
1227
|
+
cartesianAxisIndicators(
|
|
1228
|
+
chart,
|
|
1229
|
+
cell.layout,
|
|
1230
|
+
pos,
|
|
1231
|
+
dataX,
|
|
1232
|
+
dataY,
|
|
1233
|
+
),
|
|
1234
|
+
style.opacity,
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
1237
|
+
|
|
997
1238
|
const coordinated = chart._facetConfig.coordinated_tooltip;
|
|
998
|
-
const
|
|
999
|
-
renderCanvasTooltip(canvas, pos,
|
|
1000
|
-
|
|
1239
|
+
const content = isSource || coordinated ? tooltipGrid : [];
|
|
1240
|
+
renderCanvasTooltip(canvas, pos, content, cell.layout, theme, dpr, {
|
|
1241
|
+
...style,
|
|
1242
|
+
crosshair: opts.crosshair && (!isSource || !axisInd),
|
|
1001
1243
|
highlightRadius: isSource ? opts.highlightRadius : 0,
|
|
1002
1244
|
});
|
|
1003
1245
|
}
|
|
@@ -1122,24 +1364,76 @@ function renderTooltip(
|
|
|
1122
1364
|
|
|
1123
1365
|
const xOrigin = isNaN(chart._xOrigin) ? 0 : chart._xOrigin;
|
|
1124
1366
|
const yOrigin = isNaN(chart._yOrigin) ? 0 : chart._yOrigin;
|
|
1125
|
-
const
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
);
|
|
1367
|
+
const dataX = chart._xData[idx] + xOrigin;
|
|
1368
|
+
const dataY = chart._yData[idx] + yOrigin;
|
|
1369
|
+
const pos = layout.dataToPixel(dataX, dataY);
|
|
1129
1370
|
|
|
1130
1371
|
// Lines come from the async lazy tooltip fetch kicked off in
|
|
1131
1372
|
// `handleCartesianHover`. While a fetch is in flight this is
|
|
1132
1373
|
// `null`; the canvas tooltip helper still paints the crosshair /
|
|
1133
1374
|
// highlight ring but skips the text box.
|
|
1134
|
-
const
|
|
1375
|
+
const grid = chart._lazyTooltip.grid ?? [];
|
|
1135
1376
|
const theme = chart._resolveTheme();
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1377
|
+
const dpr = chart._glManager?.dpr ?? 1;
|
|
1378
|
+
const opts = chart.glyph.tooltipOptions();
|
|
1379
|
+
const axisInd = axisIndicatorsEnabled(chart, opts);
|
|
1380
|
+
if (axisInd) {
|
|
1381
|
+
renderAxisHoverIndicators(
|
|
1382
|
+
canvas,
|
|
1383
|
+
layout,
|
|
1384
|
+
theme,
|
|
1385
|
+
dpr,
|
|
1386
|
+
cartesianAxisIndicators(chart, layout, pos, dataX, dataY),
|
|
1387
|
+
chart._pluginConfig.tooltip_opacity,
|
|
1388
|
+
);
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
renderCanvasTooltip(canvas, pos, grid, layout, theme, dpr, {
|
|
1392
|
+
...tooltipStyleOf(chart._pluginConfig),
|
|
1393
|
+
crosshair: opts.crosshair && !axisInd,
|
|
1394
|
+
highlightRadius: opts.highlightRadius,
|
|
1395
|
+
});
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
function axisIndicatorsEnabled(
|
|
1399
|
+
chart: CartesianChart,
|
|
1400
|
+
opts: { axisIndicators: boolean },
|
|
1401
|
+
): boolean {
|
|
1402
|
+
return (
|
|
1403
|
+
opts.axisIndicators &&
|
|
1404
|
+
(chart._renderMode !== "map" || !!chart._pluginConfig.numeric_axes)
|
|
1144
1405
|
);
|
|
1145
1406
|
}
|
|
1407
|
+
|
|
1408
|
+
function cartesianAxisIndicators(
|
|
1409
|
+
chart: CartesianChart,
|
|
1410
|
+
layout: PlotLayout,
|
|
1411
|
+
pos: { px: number; py: number },
|
|
1412
|
+
dataX: number,
|
|
1413
|
+
dataY: number,
|
|
1414
|
+
): AxisIndicator[] {
|
|
1415
|
+
let xText: string;
|
|
1416
|
+
let yText: string;
|
|
1417
|
+
const xOverride = chart.getColumnFormatter(chart._xName, "tick");
|
|
1418
|
+
const yOverride = chart.getColumnFormatter(chart._yName, "tick");
|
|
1419
|
+
if (chart._renderMode === "map") {
|
|
1420
|
+
const mt = computeMapDegreeTicks(layout);
|
|
1421
|
+
const [lon, lat] = mercatorToLonLat(dataX, dataY);
|
|
1422
|
+
xText = xOverride ? xOverride(lon) : mt.formatX(dataX);
|
|
1423
|
+
yText = yOverride ? yOverride(lat) : mt.formatY(dataY);
|
|
1424
|
+
} else {
|
|
1425
|
+
const xFmt =
|
|
1426
|
+
xOverride ??
|
|
1427
|
+
stepTickFormatter(chart._lastXDomain?.isDate, chart._lastXTicks);
|
|
1428
|
+
const yFmt =
|
|
1429
|
+
yOverride ??
|
|
1430
|
+
stepTickFormatter(chart._lastYDomain?.isDate, chart._lastYTicks);
|
|
1431
|
+
xText = xFmt(dataX);
|
|
1432
|
+
yText = yFmt(dataY);
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
return [
|
|
1436
|
+
{ side: "bottom", px: pos.px, py: pos.py, text: xText },
|
|
1437
|
+
{ side: "left", px: pos.px, py: pos.py, text: yText },
|
|
1438
|
+
];
|
|
1439
|
+
}
|
|
@@ -65,6 +65,15 @@ export class CartesianChart extends AbstractChart {
|
|
|
65
65
|
return this._colorName || null;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Chrome-only repaint for legend scroll / floating-legend drag —
|
|
70
|
+
* the same lightweight path hover updates use. No GL pass; the
|
|
71
|
+
* composite re-presents over the retained plot bitmap.
|
|
72
|
+
*/
|
|
73
|
+
repaintChrome(): void {
|
|
74
|
+
renderCartesianChromeOverlay(this);
|
|
75
|
+
}
|
|
76
|
+
|
|
68
77
|
/**
|
|
69
78
|
* Rendering pipeline selector. `"cartesian"` is the default —
|
|
70
79
|
* draws axes, gridlines, and ticks via the chrome canvas.
|
|
@@ -67,12 +67,16 @@ export interface Glyph {
|
|
|
67
67
|
buildTooltipLines(
|
|
68
68
|
chart: CartesianChart,
|
|
69
69
|
flatIdx: number,
|
|
70
|
-
): Promise<string[]>;
|
|
70
|
+
): Promise<string[][]>;
|
|
71
71
|
|
|
72
72
|
/**
|
|
73
73
|
* Hover-overlay options (crosshair, highlight radius).
|
|
74
74
|
*/
|
|
75
|
-
tooltipOptions(): {
|
|
75
|
+
tooltipOptions(): {
|
|
76
|
+
crosshair: boolean;
|
|
77
|
+
highlightRadius: number;
|
|
78
|
+
axisIndicators: boolean;
|
|
79
|
+
};
|
|
76
80
|
|
|
77
81
|
/**
|
|
78
82
|
* Release GL resources created by `ensureProgram`.
|