@perspective-dev/viewer-charts 5.2.0 → 5.3.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 +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/candlestick/candlestick-render.d.ts +3 -0
- package/dist/esm/charts/cartesian/cartesian.d.ts +7 -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 +24 -9
- package/dist/esm/charts/chart.d.ts +81 -6
- package/dist/esm/charts/common/category-axis-resolver.d.ts +1 -1
- package/dist/esm/charts/common/draw-tooltip-box.d.ts +2 -1
- package/dist/esm/charts/common/tree-chart.d.ts +1 -0
- package/dist/esm/charts/common/tree-interact.d.ts +1 -1
- package/dist/esm/charts/heatmap/heatmap.d.ts +1 -0
- 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-type.d.ts +18 -0
- package/dist/esm/charts/series/series.d.ts +10 -1
- package/dist/esm/charts/sunburst/sunburst-render.d.ts +4 -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 +28 -1
- package/dist/esm/theme/gradient.d.ts +32 -0
- package/dist/esm/theme/overrides.d.ts +19 -0
- package/dist/esm/transport/protocol.d.ts +58 -5
- package/dist/esm/transport/renderer-transport.d.ts +3 -0
- package/dist/esm/utils/css.d.ts +4 -0
- package/dist/esm/worker/renderer.worker.d.ts +46 -1
- package/package.json +1 -1
- 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-build.ts +1 -0
- package/src/ts/charts/candlestick/candlestick-interact.ts +46 -40
- package/src/ts/charts/candlestick/candlestick-render.ts +81 -10
- package/src/ts/charts/candlestick/candlestick.ts +4 -3
- package/src/ts/charts/cartesian/cartesian-interact.ts +9 -3
- package/src/ts/charts/cartesian/cartesian-render.ts +374 -75
- package/src/ts/charts/cartesian/cartesian.ts +13 -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 +76 -42
- package/src/ts/charts/chart.ts +111 -7
- package/src/ts/charts/common/category-axis-resolver.ts +44 -5
- package/src/ts/charts/common/draw-tooltip-box.ts +13 -30
- package/src/ts/charts/common/tree-chart.ts +4 -0
- 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-build.ts +7 -1
- package/src/ts/charts/heatmap/heatmap-interact.ts +75 -8
- package/src/ts/charts/heatmap/heatmap-render.ts +105 -24
- package/src/ts/charts/heatmap/heatmap.ts +8 -3
- package/src/ts/charts/map/map.ts +3 -5
- package/src/ts/charts/series/series-build.ts +15 -2
- package/src/ts/charts/series/series-interact.ts +14 -24
- package/src/ts/charts/series/series-render.ts +310 -132
- package/src/ts/charts/series/series-type.ts +65 -0
- package/src/ts/charts/series/series.ts +31 -8
- package/src/ts/charts/sunburst/sunburst-render.ts +16 -1
- package/src/ts/charts/treemap/treemap-render.ts +12 -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 +48 -12
- 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 +330 -76
- package/src/ts/shaders/line.vert.glsl +5 -2
- package/src/ts/shaders/scatter.vert.glsl +0 -7
- package/src/ts/theme/gradient.ts +265 -1
- package/src/ts/theme/overrides.ts +76 -0
- package/src/ts/transport/protocol.ts +60 -4
- package/src/ts/transport/renderer-transport.ts +107 -14
- package/src/ts/utils/css.ts +12 -0
- package/src/ts/worker/dispatch.ts +6 -2
- package/src/ts/worker/renderer.worker.ts +191 -7
|
@@ -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
|
|
@@ -699,6 +792,11 @@ function renderFacetedFrame(
|
|
|
699
792
|
* Redraw the chrome canvas only. Used for lightweight hover updates.
|
|
700
793
|
*/
|
|
701
794
|
export function renderCartesianChromeOverlay(chart: CartesianChart): void {
|
|
795
|
+
paintCartesianChromeOverlay(chart);
|
|
796
|
+
chart.presentOverlay();
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
function paintCartesianChromeOverlay(chart: CartesianChart): void {
|
|
702
800
|
if (
|
|
703
801
|
!chart._chromeCanvas ||
|
|
704
802
|
!chart._lastLayout ||
|
|
@@ -728,6 +826,30 @@ function renderSinglePlotChromeOverlay(chart: CartesianChart): void {
|
|
|
728
826
|
const isMap = chart._renderMode === "map";
|
|
729
827
|
|
|
730
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
|
+
|
|
731
853
|
chart.renderMapChrome(chart._chromeCanvas!, layout, theme, dpr);
|
|
732
854
|
} else {
|
|
733
855
|
renderCartesianCellAxes(
|
|
@@ -743,37 +865,95 @@ function renderSinglePlotChromeOverlay(chart: CartesianChart): void {
|
|
|
743
865
|
);
|
|
744
866
|
}
|
|
745
867
|
|
|
746
|
-
|
|
868
|
+
const legendMode = resolveLegendMode(
|
|
869
|
+
chart._pluginConfig,
|
|
870
|
+
legendEntryCount(chart),
|
|
871
|
+
);
|
|
872
|
+
let legendPainted = false;
|
|
873
|
+
if (chart._lastHasColorCol && legendMode !== "none") {
|
|
747
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;
|
|
748
890
|
if (chart._colorIsString && chart._uniqueColorLabels.size > 0) {
|
|
749
891
|
const palette = resolvePalette(
|
|
750
892
|
theme.seriesPalette,
|
|
751
893
|
stops,
|
|
752
894
|
chart._uniqueColorLabels.size,
|
|
753
895
|
);
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
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;
|
|
761
917
|
} else if (chart._colorName) {
|
|
762
|
-
|
|
763
|
-
chart.
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
stops,
|
|
771
|
-
theme,
|
|
772
|
-
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",
|
|
773
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;
|
|
774
950
|
}
|
|
775
951
|
}
|
|
776
952
|
|
|
953
|
+
if (!legendPainted) {
|
|
954
|
+
chart._legend.clearPainted();
|
|
955
|
+
}
|
|
956
|
+
|
|
777
957
|
renderScatterLabels(chart, chart._chromeCanvas!, layout, 0, 1);
|
|
778
958
|
|
|
779
959
|
if (chart._hoveredIndex >= 0 && chart._xData && chart._yData) {
|
|
@@ -811,35 +991,44 @@ function renderFacetedChromeOverlay(chart: CartesianChart): void {
|
|
|
811
991
|
// (one pass per leftmost-column cell). Map mode replaces both
|
|
812
992
|
// with `renderMapChrome` (attribution + scale bar), painted once
|
|
813
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;
|
|
814
999
|
if (isMap) {
|
|
815
1000
|
chart.renderMapChrome(canvas, chart._lastLayout!, theme, dpr);
|
|
816
1001
|
}
|
|
817
1002
|
|
|
818
|
-
if (!isMap && sharedX && grid.outerXAxisRect) {
|
|
1003
|
+
if ((!isMap || sharedMapTicks) && sharedX && grid.outerXAxisRect) {
|
|
819
1004
|
renderOuterXAxis(
|
|
820
1005
|
canvas,
|
|
821
1006
|
grid.outerXAxisRect,
|
|
822
1007
|
xDomain,
|
|
823
|
-
sharedXTicks,
|
|
1008
|
+
sharedMapTicks ? sharedMapTicks.xTicks : sharedXTicks,
|
|
824
1009
|
bottomRowLayouts(grid),
|
|
825
1010
|
theme,
|
|
826
1011
|
!!chart._xLabel,
|
|
827
1012
|
dpr,
|
|
828
|
-
|
|
1013
|
+
sharedMapTicks
|
|
1014
|
+
? sharedMapTicks.formatX
|
|
1015
|
+
: chart.getColumnFormatter(chart._xName, "tick"),
|
|
829
1016
|
);
|
|
830
1017
|
}
|
|
831
1018
|
|
|
832
|
-
if (!isMap && sharedY && grid.outerYAxisRect) {
|
|
1019
|
+
if ((!isMap || sharedMapTicks) && sharedY && grid.outerYAxisRect) {
|
|
833
1020
|
renderOuterYAxis(
|
|
834
1021
|
canvas,
|
|
835
1022
|
grid.outerYAxisRect,
|
|
836
1023
|
yDomain,
|
|
837
|
-
sharedYTicks,
|
|
1024
|
+
sharedMapTicks ? sharedMapTicks.yTicks : sharedYTicks,
|
|
838
1025
|
leftColumnLayouts(grid),
|
|
839
1026
|
theme,
|
|
840
1027
|
!!chart._yLabel,
|
|
841
1028
|
dpr,
|
|
842
|
-
|
|
1029
|
+
sharedMapTicks
|
|
1030
|
+
? sharedMapTicks.formatY
|
|
1031
|
+
: chart.getColumnFormatter(chart._yName, "tick"),
|
|
843
1032
|
);
|
|
844
1033
|
}
|
|
845
1034
|
|
|
@@ -852,11 +1041,16 @@ function renderFacetedChromeOverlay(chart: CartesianChart): void {
|
|
|
852
1041
|
const d = zc ? zc.getVisibleDomain() : null;
|
|
853
1042
|
const localX = d ? { ...xDomain, min: d.xMin, max: d.xMax } : xDomain;
|
|
854
1043
|
const localY = d ? { ...yDomain, min: d.yMin, max: d.yMax } : yDomain;
|
|
855
|
-
const
|
|
856
|
-
?
|
|
857
|
-
:
|
|
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 };
|
|
858
1052
|
|
|
859
|
-
if (!isMap && !sharedX) {
|
|
1053
|
+
if ((!isMap || cellMapTicks) && !sharedX) {
|
|
860
1054
|
if (chart._xIsString && chart._xCategoryDomain) {
|
|
861
1055
|
const cellCtx = getScaledContext(canvas, dpr);
|
|
862
1056
|
if (cellCtx) {
|
|
@@ -876,12 +1070,14 @@ function renderFacetedChromeOverlay(chart: CartesianChart): void {
|
|
|
876
1070
|
theme,
|
|
877
1071
|
!!chart._xLabel,
|
|
878
1072
|
dpr,
|
|
879
|
-
|
|
1073
|
+
cellMapTicks
|
|
1074
|
+
? cellMapTicks.formatX
|
|
1075
|
+
: chart.getColumnFormatter(chart._xName, "tick"),
|
|
880
1076
|
);
|
|
881
1077
|
}
|
|
882
1078
|
}
|
|
883
1079
|
|
|
884
|
-
if (!isMap && !sharedY) {
|
|
1080
|
+
if ((!isMap || cellMapTicks) && !sharedY) {
|
|
885
1081
|
if (chart._yIsString && chart._yCategoryDomain) {
|
|
886
1082
|
const cellCtx = getScaledContext(canvas, dpr);
|
|
887
1083
|
if (cellCtx) {
|
|
@@ -901,7 +1097,9 @@ function renderFacetedChromeOverlay(chart: CartesianChart): void {
|
|
|
901
1097
|
theme,
|
|
902
1098
|
!!chart._yLabel,
|
|
903
1099
|
dpr,
|
|
904
|
-
|
|
1100
|
+
cellMapTicks
|
|
1101
|
+
? cellMapTicks.formatY
|
|
1102
|
+
: chart.getColumnFormatter(chart._yName, "tick"),
|
|
905
1103
|
);
|
|
906
1104
|
}
|
|
907
1105
|
}
|
|
@@ -914,10 +1112,29 @@ function renderFacetedChromeOverlay(chart: CartesianChart): void {
|
|
|
914
1112
|
}
|
|
915
1113
|
|
|
916
1114
|
// Shared legend: categorical (string color) or gradient
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
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) {
|
|
920
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
|
+
};
|
|
921
1138
|
if (chart._colorIsString && chart._uniqueColorLabels.size > 0) {
|
|
922
1139
|
const palette = resolvePalette(
|
|
923
1140
|
theme.seriesPalette,
|
|
@@ -926,23 +1143,27 @@ function renderFacetedChromeOverlay(chart: CartesianChart): void {
|
|
|
926
1143
|
);
|
|
927
1144
|
renderCategoricalLegendAt(
|
|
928
1145
|
canvas,
|
|
929
|
-
|
|
1146
|
+
legendAnchor,
|
|
930
1147
|
chart._uniqueColorLabels,
|
|
931
1148
|
palette,
|
|
932
1149
|
theme,
|
|
1150
|
+
view,
|
|
933
1151
|
);
|
|
1152
|
+
legendPainted = true;
|
|
934
1153
|
} else if (chart._colorName) {
|
|
935
1154
|
// Numeric gradient legend in the shared outer rect. The
|
|
936
1155
|
// label sits above the bar, so inset the rect's top by
|
|
937
1156
|
// the usual 20 px that `renderLegend` reserves.
|
|
938
1157
|
renderLegendAt(
|
|
939
1158
|
canvas,
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
1159
|
+
floating
|
|
1160
|
+
? legendAnchor
|
|
1161
|
+
: {
|
|
1162
|
+
x: legendAnchor.x,
|
|
1163
|
+
y: legendAnchor.y + 20,
|
|
1164
|
+
width: legendAnchor.width,
|
|
1165
|
+
height: legendAnchor.height - 20,
|
|
1166
|
+
},
|
|
946
1167
|
{
|
|
947
1168
|
min: chart._colorMin,
|
|
948
1169
|
max: chart._colorMax,
|
|
@@ -951,10 +1172,16 @@ function renderFacetedChromeOverlay(chart: CartesianChart): void {
|
|
|
951
1172
|
stops,
|
|
952
1173
|
theme,
|
|
953
1174
|
chart.getColumnFormatter(chart._colorName, "value"),
|
|
1175
|
+
view,
|
|
954
1176
|
);
|
|
1177
|
+
legendPainted = true;
|
|
955
1178
|
}
|
|
956
1179
|
}
|
|
957
1180
|
|
|
1181
|
+
if (!legendPainted) {
|
|
1182
|
+
chart._legend.clearPainted();
|
|
1183
|
+
}
|
|
1184
|
+
|
|
958
1185
|
// Coordinated hover / click indicators across facets. The tooltip
|
|
959
1186
|
// lines are whatever the last resolved lazy fetch produced (or
|
|
960
1187
|
// null while a fetch is still in flight); `renderCanvasTooltip`
|
|
@@ -970,7 +1197,9 @@ function renderFacetedChromeOverlay(chart: CartesianChart): void {
|
|
|
970
1197
|
const dataY = chart._yData[chart._hoveredIndex] + yOrigin;
|
|
971
1198
|
const sourceFacet = seriesFromIndex(chart, chart._hoveredIndex);
|
|
972
1199
|
const opts = chart.glyph.tooltipOptions();
|
|
973
|
-
const
|
|
1200
|
+
const axisInd = axisIndicatorsEnabled(chart, opts);
|
|
1201
|
+
const style = tooltipStyleOf(chart._pluginConfig);
|
|
1202
|
+
const tooltipGrid = chart._lazyTooltip.grid ?? [];
|
|
974
1203
|
|
|
975
1204
|
for (let i = 0; i < grid.cells.length; i++) {
|
|
976
1205
|
const cell = grid.cells[i];
|
|
@@ -989,10 +1218,28 @@ function renderFacetedChromeOverlay(chart: CartesianChart): void {
|
|
|
989
1218
|
continue;
|
|
990
1219
|
}
|
|
991
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
|
+
|
|
992
1238
|
const coordinated = chart._facetConfig.coordinated_tooltip;
|
|
993
|
-
const
|
|
994
|
-
renderCanvasTooltip(canvas, pos,
|
|
995
|
-
|
|
1239
|
+
const content = isSource || coordinated ? tooltipGrid : [];
|
|
1240
|
+
renderCanvasTooltip(canvas, pos, content, cell.layout, theme, dpr, {
|
|
1241
|
+
...style,
|
|
1242
|
+
crosshair: opts.crosshair && (!isSource || !axisInd),
|
|
996
1243
|
highlightRadius: isSource ? opts.highlightRadius : 0,
|
|
997
1244
|
});
|
|
998
1245
|
}
|
|
@@ -1117,24 +1364,76 @@ function renderTooltip(
|
|
|
1117
1364
|
|
|
1118
1365
|
const xOrigin = isNaN(chart._xOrigin) ? 0 : chart._xOrigin;
|
|
1119
1366
|
const yOrigin = isNaN(chart._yOrigin) ? 0 : chart._yOrigin;
|
|
1120
|
-
const
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
);
|
|
1367
|
+
const dataX = chart._xData[idx] + xOrigin;
|
|
1368
|
+
const dataY = chart._yData[idx] + yOrigin;
|
|
1369
|
+
const pos = layout.dataToPixel(dataX, dataY);
|
|
1124
1370
|
|
|
1125
1371
|
// Lines come from the async lazy tooltip fetch kicked off in
|
|
1126
1372
|
// `handleCartesianHover`. While a fetch is in flight this is
|
|
1127
1373
|
// `null`; the canvas tooltip helper still paints the crosshair /
|
|
1128
1374
|
// highlight ring but skips the text box.
|
|
1129
|
-
const
|
|
1375
|
+
const grid = chart._lazyTooltip.grid ?? [];
|
|
1130
1376
|
const theme = chart._resolveTheme();
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
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)
|
|
1139
1405
|
);
|
|
1140
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
|
+
}
|
|
@@ -61,6 +61,19 @@ export class CartesianChart extends AbstractChart {
|
|
|
61
61
|
this.glyph = glyph;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
protected override colorScaleColumn(): string | null {
|
|
65
|
+
return this._colorName || null;
|
|
66
|
+
}
|
|
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
|
+
|
|
64
77
|
/**
|
|
65
78
|
* Rendering pipeline selector. `"cartesian"` is the default —
|
|
66
79
|
* draws axes, gridlines, and ticks via the chrome canvas.
|