@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
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
rightAxisDataToPixel,
|
|
24
24
|
layoutForRecord,
|
|
25
25
|
} from "./series-render";
|
|
26
|
+
import { tooltipStyleOf } from "../../interaction/tooltip-grid";
|
|
26
27
|
|
|
27
28
|
const POINT_HIT_RADIUS_PX = 10;
|
|
28
29
|
|
|
@@ -598,26 +599,26 @@ export function handleBarLegendClick(
|
|
|
598
599
|
export function buildBarTooltipLines(
|
|
599
600
|
chart: SeriesChart,
|
|
600
601
|
b: SeriesChartRecord,
|
|
601
|
-
): string[] {
|
|
602
|
-
const
|
|
602
|
+
): string[][] {
|
|
603
|
+
const grid: string[][] = [];
|
|
603
604
|
const s = chart._series[b.seriesId];
|
|
604
605
|
const categoryPath = formatBarCategoryPath(chart, b.catIdx);
|
|
605
606
|
if (categoryPath) {
|
|
606
|
-
|
|
607
|
+
grid.push([categoryPath]);
|
|
607
608
|
}
|
|
608
609
|
|
|
609
610
|
const yFmt = chart.getColumnFormatter(s.aggName, "value");
|
|
610
|
-
|
|
611
|
+
grid.push([s.aggName, yFmt(b.value)]);
|
|
611
612
|
if (s.splitKey) {
|
|
612
|
-
|
|
613
|
+
grid.push(["Split", s.splitKey]);
|
|
613
614
|
}
|
|
614
615
|
|
|
615
616
|
if (b.y0 !== 0) {
|
|
616
|
-
|
|
617
|
-
|
|
617
|
+
grid.push(["Base", yFmt(b.y0)]);
|
|
618
|
+
grid.push(["Top", yFmt(b.y1)]);
|
|
618
619
|
}
|
|
619
620
|
|
|
620
|
-
return
|
|
621
|
+
return grid;
|
|
621
622
|
}
|
|
622
623
|
|
|
623
624
|
/**
|
|
@@ -705,12 +706,12 @@ function pinTooltip(chart: SeriesChart, b: SeriesChartRecord): void {
|
|
|
705
706
|
: layout.dataToPixel(b.xCenter, anchorV)
|
|
706
707
|
: rightAxisDataToPixel(chart, b.xCenter, anchorV, layout);
|
|
707
708
|
|
|
708
|
-
const
|
|
709
|
-
if (
|
|
709
|
+
const grid = buildBarTooltipLines(chart, b);
|
|
710
|
+
if (grid.length === 0) {
|
|
710
711
|
return;
|
|
711
712
|
}
|
|
712
713
|
|
|
713
|
-
chart._tooltip.pin(
|
|
714
|
+
chart._tooltip.pin(grid, pos, layout, tooltipStyleOf(chart._pluginConfig));
|
|
714
715
|
|
|
715
716
|
chart._hoveredBarIdx = -1;
|
|
716
717
|
chart._hoveredSample = null;
|
|
@@ -26,9 +26,14 @@ import {
|
|
|
26
26
|
withScissor,
|
|
27
27
|
} from "../../webgl/plot-frame";
|
|
28
28
|
import { renderCanvasTooltip } from "../../interaction/tooltip-controller";
|
|
29
|
+
import { tooltipStyleOf } from "../../interaction/tooltip-grid";
|
|
30
|
+
import {
|
|
31
|
+
renderAxisHoverIndicators,
|
|
32
|
+
type AxisIndicator,
|
|
33
|
+
} from "../../interaction/axis-indicators";
|
|
29
34
|
import { drawBars, BAR_TYPE_BAR_VAL as BAR_TYPE_BAR } from "./glyphs/draw-bars";
|
|
30
|
-
import { getHoveredBar } from "./series-interact";
|
|
31
|
-
import { computeNiceTicks } from "../../layout/ticks";
|
|
35
|
+
import { getHoveredBar, formatBarCategoryPath } from "./series-interact";
|
|
36
|
+
import { computeNiceTicks, stepTickFormatter } from "../../layout/ticks";
|
|
32
37
|
import {
|
|
33
38
|
renderOuterXAxis,
|
|
34
39
|
renderOuterYAxis,
|
|
@@ -57,6 +62,19 @@ import { drawFacetTitle } from "../../axis/facet-chrome";
|
|
|
57
62
|
import { getScaledContext, initCanvas } from "../../axis/canvas";
|
|
58
63
|
import { drawGridlinesX, drawGridlinesY } from "../../axis/axis-primitives";
|
|
59
64
|
import { buildBarTooltipLines } from "./series-interact";
|
|
65
|
+
import {
|
|
66
|
+
LEGEND_LINE_HEIGHT,
|
|
67
|
+
legendAutoFit,
|
|
68
|
+
paintFloatingLegendFrame,
|
|
69
|
+
paintLegendScrollbar,
|
|
70
|
+
truncateText,
|
|
71
|
+
type LegendPaintView,
|
|
72
|
+
} from "../../axis/legend";
|
|
73
|
+
import {
|
|
74
|
+
legendRightGutter,
|
|
75
|
+
resolveLegendMode,
|
|
76
|
+
legendSidebarWidth,
|
|
77
|
+
} from "../../interaction/legend-controller";
|
|
60
78
|
|
|
61
79
|
/**
|
|
62
80
|
* Reusable scratch for bar instance uploads.
|
|
@@ -529,7 +547,12 @@ export function renderBarFrame(
|
|
|
529
547
|
? 55
|
|
530
548
|
: measureCategoricalAxisWidth(provisionalDomain);
|
|
531
549
|
const estLeft = leftExtra + (hasCatLabel ? 16 : 0);
|
|
532
|
-
const estRight =
|
|
550
|
+
const estRight = legendRightGutter(
|
|
551
|
+
chart._pluginConfig,
|
|
552
|
+
hasLegend,
|
|
553
|
+
80,
|
|
554
|
+
chart._series.length,
|
|
555
|
+
);
|
|
533
556
|
const estPlotWidthH = Math.max(1, cssWidth - estLeft - estRight);
|
|
534
557
|
const bottomExtra = valueCatActive
|
|
535
558
|
? measureCategoricalAxisHeight(valueCatDomain, estPlotWidthH)
|
|
@@ -540,6 +563,7 @@ export function renderBarFrame(
|
|
|
540
563
|
hasLegend,
|
|
541
564
|
leftExtra,
|
|
542
565
|
bottomExtra,
|
|
566
|
+
rightExtra: estRight,
|
|
543
567
|
});
|
|
544
568
|
} else if (numericCat) {
|
|
545
569
|
// Y Bar with numeric category axis on X. Value axis (Y, left)
|
|
@@ -553,6 +577,12 @@ export function renderBarFrame(
|
|
|
553
577
|
hasLegend,
|
|
554
578
|
bottomExtra: 24,
|
|
555
579
|
leftExtra,
|
|
580
|
+
rightExtra: legendRightGutter(
|
|
581
|
+
chart._pluginConfig,
|
|
582
|
+
hasLegend,
|
|
583
|
+
80,
|
|
584
|
+
chart._series.length,
|
|
585
|
+
),
|
|
556
586
|
});
|
|
557
587
|
} else {
|
|
558
588
|
// Y Bar with categorical X. Value axis on the left may be
|
|
@@ -561,7 +591,12 @@ export function renderBarFrame(
|
|
|
561
591
|
? measureCategoricalAxisWidth(valueCatDomain)
|
|
562
592
|
: 55;
|
|
563
593
|
const estLeft = leftExtraBase + 16;
|
|
564
|
-
const estRight =
|
|
594
|
+
const estRight = legendRightGutter(
|
|
595
|
+
chart._pluginConfig,
|
|
596
|
+
hasLegend,
|
|
597
|
+
80,
|
|
598
|
+
chart._series.length,
|
|
599
|
+
);
|
|
565
600
|
const estPlotWidth = Math.max(1, cssWidth - estLeft - estRight);
|
|
566
601
|
const bottomExtra = measureCategoricalAxisHeight(
|
|
567
602
|
provisionalDomain,
|
|
@@ -573,6 +608,7 @@ export function renderBarFrame(
|
|
|
573
608
|
hasLegend,
|
|
574
609
|
bottomExtra,
|
|
575
610
|
leftExtra: valueCatActive ? leftExtraBase : undefined,
|
|
611
|
+
rightExtra: estRight,
|
|
576
612
|
});
|
|
577
613
|
}
|
|
578
614
|
|
|
@@ -898,7 +934,11 @@ function renderFacetedBarFrame(
|
|
|
898
934
|
cssHeight,
|
|
899
935
|
xAxis: horizontal ? valAxisMode : catAxisMode,
|
|
900
936
|
yAxis: horizontal ? catAxisMode : valAxisMode,
|
|
901
|
-
hasLegend
|
|
937
|
+
hasLegend:
|
|
938
|
+
hasLegend &&
|
|
939
|
+
resolveLegendMode(chart._pluginConfig, chart._aggregates.length) ===
|
|
940
|
+
"sidebar",
|
|
941
|
+
legendWidth: legendSidebarWidth(chart._pluginConfig, 96),
|
|
902
942
|
hasXLabel: horizontal ? true : hasCatLabel,
|
|
903
943
|
hasYLabel: horizontal ? hasCatLabel : true,
|
|
904
944
|
gap: chart._facetConfig.facet_padding,
|
|
@@ -1351,66 +1391,89 @@ function renderFacetedBarChromeOverlay(chart: SeriesChart): void {
|
|
|
1351
1391
|
}
|
|
1352
1392
|
|
|
1353
1393
|
/**
|
|
1354
|
-
*
|
|
1355
|
-
*
|
|
1356
|
-
* the split dimension; every split of an aggregate shares its color —
|
|
1357
|
-
* see `ensurePalette`). A legend toggle targets the aggregate's full
|
|
1358
|
-
* seriesId set, so hiding "Sales" hides it in every facet at once; an
|
|
1359
|
-
* entry reads as hidden only when ALL of its series are hidden.
|
|
1394
|
+
* One toggleable legend row, resolved lazily — only rows inside the
|
|
1395
|
+
* visible scroll window are ever materialized.
|
|
1360
1396
|
*/
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
const M = chart._aggregates.length;
|
|
1368
|
-
if (M <= 1) {
|
|
1369
|
-
return;
|
|
1370
|
-
}
|
|
1397
|
+
interface SeriesLegendEntry {
|
|
1398
|
+
label: string;
|
|
1399
|
+
color: [number, number, number];
|
|
1400
|
+
seriesIds: number[];
|
|
1401
|
+
hidden: boolean;
|
|
1402
|
+
}
|
|
1371
1403
|
|
|
1372
|
-
|
|
1404
|
+
/**
|
|
1405
|
+
* Shared swatch-list painter for both series legends (per-series and
|
|
1406
|
+
* per-aggregate). Paints only the rows inside the scroll window,
|
|
1407
|
+
* clipped to the content rect, rebuilds `chart._legendRects` with the
|
|
1408
|
+
* visible rows' canvas-space rects (so toggle clicks stay correct
|
|
1409
|
+
* while scrolled), and reports the painted geometry to the chart's
|
|
1410
|
+
* `LegendController`.
|
|
1411
|
+
*/
|
|
1412
|
+
function paintSeriesLegend(
|
|
1413
|
+
chart: SeriesChart,
|
|
1414
|
+
box: PlotRect,
|
|
1415
|
+
view: LegendPaintView,
|
|
1416
|
+
count: number,
|
|
1417
|
+
entryAt: (i: number) => SeriesLegendEntry,
|
|
1418
|
+
): void {
|
|
1419
|
+
const ctx = chart._chromeCanvas!.getContext("2d") as Context2D | null;
|
|
1373
1420
|
if (!ctx) {
|
|
1374
1421
|
return;
|
|
1375
1422
|
}
|
|
1376
1423
|
|
|
1377
1424
|
ctx.save();
|
|
1378
|
-
|
|
1379
1425
|
const theme = chart._resolveTheme();
|
|
1426
|
+
let content = box;
|
|
1427
|
+
if (view.mode === "floating") {
|
|
1428
|
+
content = paintFloatingLegendFrame(
|
|
1429
|
+
ctx,
|
|
1430
|
+
box,
|
|
1431
|
+
theme,
|
|
1432
|
+
view.title,
|
|
1433
|
+
view.opacity,
|
|
1434
|
+
);
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1380
1437
|
const swatchSize = 10;
|
|
1381
|
-
const lineHeight =
|
|
1382
|
-
const
|
|
1383
|
-
|
|
1438
|
+
const lineHeight = LEGEND_LINE_HEIGHT;
|
|
1439
|
+
const contentHeight = count * lineHeight;
|
|
1440
|
+
const scroll = chart._legend.clampScroll(content.height, contentHeight);
|
|
1441
|
+
const scrollable = contentHeight > content.height;
|
|
1442
|
+
const textMax = Math.max(
|
|
1443
|
+
0,
|
|
1444
|
+
content.width - swatchSize - 6 - (scrollable ? 10 : 0),
|
|
1445
|
+
);
|
|
1384
1446
|
|
|
1447
|
+
ctx.beginPath();
|
|
1448
|
+
ctx.rect(content.x, content.y, content.width, content.height);
|
|
1449
|
+
ctx.clip();
|
|
1385
1450
|
ctx.font = `11px ${theme.fontFamily}`;
|
|
1386
1451
|
ctx.textAlign = "left";
|
|
1387
1452
|
ctx.textBaseline = "middle";
|
|
1388
1453
|
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
const
|
|
1400
|
-
const
|
|
1401
|
-
const r = Math.round(color[0] * 255);
|
|
1402
|
-
const g = Math.round(color[1] * 255);
|
|
1403
|
-
const b = Math.round(color[2] * 255);
|
|
1454
|
+
const x = content.x;
|
|
1455
|
+
const start = Math.floor(scroll / lineHeight);
|
|
1456
|
+
let y = content.y + lineHeight / 2 + start * lineHeight - scroll;
|
|
1457
|
+
for (
|
|
1458
|
+
let i = start;
|
|
1459
|
+
i < count && y - lineHeight / 2 < content.y + content.height;
|
|
1460
|
+
i++
|
|
1461
|
+
) {
|
|
1462
|
+
const e = entryAt(i);
|
|
1463
|
+
const r = Math.round(e.color[0] * 255);
|
|
1464
|
+
const g = Math.round(e.color[1] * 255);
|
|
1465
|
+
const b = Math.round(e.color[2] * 255);
|
|
1404
1466
|
|
|
1405
|
-
ctx.globalAlpha = hidden ? 0.3 : 1.0;
|
|
1467
|
+
ctx.globalAlpha = e.hidden ? 0.3 : 1.0;
|
|
1406
1468
|
ctx.fillStyle = `rgb(${r},${g},${b})`;
|
|
1407
1469
|
ctx.fillRect(x, y - swatchSize / 2, swatchSize, swatchSize);
|
|
1408
1470
|
|
|
1471
|
+
const shown = truncateText(ctx, e.label, textMax);
|
|
1472
|
+
const textW = ctx.measureText(shown).width;
|
|
1409
1473
|
ctx.fillStyle = theme.legendText;
|
|
1410
|
-
ctx.fillText(
|
|
1474
|
+
ctx.fillText(shown, x + swatchSize + 6, y);
|
|
1411
1475
|
|
|
1412
|
-
|
|
1413
|
-
if (hidden) {
|
|
1476
|
+
if (e.hidden) {
|
|
1414
1477
|
ctx.strokeStyle = theme.legendText;
|
|
1415
1478
|
ctx.lineWidth = 1;
|
|
1416
1479
|
ctx.beginPath();
|
|
@@ -1422,11 +1485,14 @@ function renderFacetedBarLegend(chart: SeriesChart, grid: FacetGrid): void {
|
|
|
1422
1485
|
ctx.globalAlpha = 1.0;
|
|
1423
1486
|
|
|
1424
1487
|
chart._legendRects.push({
|
|
1425
|
-
seriesIds,
|
|
1488
|
+
seriesIds: e.seriesIds,
|
|
1426
1489
|
rect: {
|
|
1427
1490
|
x: x - 2,
|
|
1428
1491
|
y: y - lineHeight / 2,
|
|
1429
|
-
width:
|
|
1492
|
+
width: Math.min(
|
|
1493
|
+
swatchSize + 6 + textW + 4,
|
|
1494
|
+
Math.max(1, content.width),
|
|
1495
|
+
),
|
|
1430
1496
|
height: lineHeight,
|
|
1431
1497
|
},
|
|
1432
1498
|
});
|
|
@@ -1435,38 +1501,95 @@ function renderFacetedBarLegend(chart: SeriesChart, grid: FacetGrid): void {
|
|
|
1435
1501
|
}
|
|
1436
1502
|
|
|
1437
1503
|
ctx.restore();
|
|
1504
|
+
paintLegendScrollbar(ctx, content, scroll, contentHeight, theme);
|
|
1505
|
+
|
|
1506
|
+
chart._legend.setPainted({
|
|
1507
|
+
mode: view.mode,
|
|
1508
|
+
box,
|
|
1509
|
+
content,
|
|
1510
|
+
contentHeight,
|
|
1511
|
+
sidebarGutter: view.sidebarGutter,
|
|
1512
|
+
});
|
|
1438
1513
|
}
|
|
1439
1514
|
|
|
1440
1515
|
/**
|
|
1441
|
-
*
|
|
1442
|
-
*
|
|
1443
|
-
*
|
|
1444
|
-
*
|
|
1516
|
+
* Aggregate-level legend for the faceted frame, painted into the
|
|
1517
|
+
* grid's shared right gutter (or as a floating panel). One entry per
|
|
1518
|
+
* aggregate (facets absorb the split dimension; every split of an
|
|
1519
|
+
* aggregate shares its color — see `ensurePalette`). A legend toggle
|
|
1520
|
+
* targets the aggregate's full seriesId set, so hiding "Sales" hides
|
|
1521
|
+
* it in every facet at once; an entry reads as hidden only when ALL of
|
|
1522
|
+
* its series are hidden.
|
|
1445
1523
|
*/
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
chart: SeriesChart,
|
|
1450
|
-
ctx: Context2D,
|
|
1451
|
-
fontFamily: string,
|
|
1452
|
-
): void {
|
|
1453
|
-
if (chart._legendCacheValid) {
|
|
1524
|
+
function renderFacetedBarLegend(chart: SeriesChart, grid: FacetGrid): void {
|
|
1525
|
+
chart._legendRects = [];
|
|
1526
|
+
if (!chart._chromeCanvas || !chart._lastLayout) {
|
|
1454
1527
|
return;
|
|
1455
1528
|
}
|
|
1456
1529
|
|
|
1457
|
-
const
|
|
1458
|
-
|
|
1459
|
-
|
|
1530
|
+
const cfg = chart._pluginConfig;
|
|
1531
|
+
const M = chart._aggregates.length;
|
|
1532
|
+
const mode = resolveLegendMode(cfg, M);
|
|
1533
|
+
const floating = mode === "floating";
|
|
1534
|
+
if (M <= 1 || mode === "none" || (!floating && !grid.legendRect)) {
|
|
1535
|
+
chart._legend.clearPainted();
|
|
1536
|
+
return;
|
|
1460
1537
|
}
|
|
1461
1538
|
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1539
|
+
// Pre-bucket series by aggregate once (O(series)) so the per-row
|
|
1540
|
+
// resolver is O(1) — the windowed painter may touch only a few of
|
|
1541
|
+
// potentially many rows.
|
|
1542
|
+
const idsByAgg: number[][] = Array.from({ length: M }, () => []);
|
|
1543
|
+
const colorByAgg: [number, number, number][] = Array.from(
|
|
1544
|
+
{ length: M },
|
|
1545
|
+
() => [0.5, 0.5, 0.5],
|
|
1546
|
+
);
|
|
1547
|
+
for (const s of chart._series) {
|
|
1548
|
+
if (s.aggIdx >= 0 && s.aggIdx < M) {
|
|
1549
|
+
idsByAgg[s.aggIdx].push(s.seriesId);
|
|
1550
|
+
colorByAgg[s.aggIdx] = s.color;
|
|
1551
|
+
}
|
|
1466
1552
|
}
|
|
1467
1553
|
|
|
1468
|
-
|
|
1469
|
-
|
|
1554
|
+
const layout = chart._lastLayout;
|
|
1555
|
+
const box = floating
|
|
1556
|
+
? chart._legend.floatingBox(
|
|
1557
|
+
cfg,
|
|
1558
|
+
layout.cssWidth,
|
|
1559
|
+
layout.cssHeight,
|
|
1560
|
+
legendAutoFit(
|
|
1561
|
+
chart._chromeCanvas,
|
|
1562
|
+
chart._resolveTheme(),
|
|
1563
|
+
M,
|
|
1564
|
+
() => chart._aggregates.slice(0, M),
|
|
1565
|
+
{ title: "Legend" },
|
|
1566
|
+
),
|
|
1567
|
+
)
|
|
1568
|
+
: {
|
|
1569
|
+
x: grid.legendRect!.x + 12,
|
|
1570
|
+
y: grid.legendRect!.y + 10,
|
|
1571
|
+
width: Math.max(1, grid.legendRect!.width - 16),
|
|
1572
|
+
height: Math.max(1, grid.legendRect!.height - 20),
|
|
1573
|
+
};
|
|
1574
|
+
|
|
1575
|
+
paintSeriesLegend(
|
|
1576
|
+
chart,
|
|
1577
|
+
box,
|
|
1578
|
+
{
|
|
1579
|
+
mode: floating ? "floating" : "sidebar",
|
|
1580
|
+
legend: chart._legend,
|
|
1581
|
+
title: "Legend",
|
|
1582
|
+
sidebarGutter: floating ? undefined : grid.legendRect!.width,
|
|
1583
|
+
opacity: cfg.legend_opacity,
|
|
1584
|
+
},
|
|
1585
|
+
M,
|
|
1586
|
+
(k) => ({
|
|
1587
|
+
label: chart._aggregates[k],
|
|
1588
|
+
color: colorByAgg[k],
|
|
1589
|
+
seriesIds: idsByAgg[k],
|
|
1590
|
+
hidden: idsByAgg[k].every((sid) => chart._hiddenSeries.has(sid)),
|
|
1591
|
+
}),
|
|
1592
|
+
);
|
|
1470
1593
|
}
|
|
1471
1594
|
|
|
1472
1595
|
function renderBarLegend(chart: SeriesChart): void {
|
|
@@ -1475,73 +1598,65 @@ function renderBarLegend(chart: SeriesChart): void {
|
|
|
1475
1598
|
return;
|
|
1476
1599
|
}
|
|
1477
1600
|
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
if (!ctx) {
|
|
1601
|
+
const cfg = chart._pluginConfig;
|
|
1602
|
+
const series = chart._series;
|
|
1603
|
+
const mode = resolveLegendMode(cfg, series.length);
|
|
1604
|
+
if (series.length <= 1 || mode === "none") {
|
|
1605
|
+
chart._legend.clearPainted();
|
|
1484
1606
|
return;
|
|
1485
1607
|
}
|
|
1486
1608
|
|
|
1487
|
-
ctx.save();
|
|
1488
|
-
|
|
1489
|
-
const theme = chart._resolveTheme();
|
|
1490
|
-
const textColor = theme.legendText;
|
|
1491
|
-
const fontFamily = theme.fontFamily;
|
|
1492
|
-
|
|
1493
|
-
ensureLegendLayout(chart, ctx, fontFamily);
|
|
1494
|
-
|
|
1495
1609
|
const layout = chart._lastLayout;
|
|
1496
|
-
const
|
|
1497
|
-
const
|
|
1498
|
-
const
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1610
|
+
const floating = mode === "floating";
|
|
1611
|
+
const title = chart._splitBy.join(" / ") || "Legend";
|
|
1612
|
+
const box = floating
|
|
1613
|
+
? chart._legend.floatingBox(
|
|
1614
|
+
cfg,
|
|
1615
|
+
layout.cssWidth,
|
|
1616
|
+
layout.cssHeight,
|
|
1617
|
+
legendAutoFit(
|
|
1618
|
+
chart._chromeCanvas,
|
|
1619
|
+
chart._resolveTheme(),
|
|
1620
|
+
series.length,
|
|
1621
|
+
function* () {
|
|
1622
|
+
for (const s of series) {
|
|
1623
|
+
yield s.label;
|
|
1624
|
+
}
|
|
1625
|
+
},
|
|
1626
|
+
{ title },
|
|
1627
|
+
),
|
|
1628
|
+
)
|
|
1629
|
+
: {
|
|
1630
|
+
x: layout.plotRect.x + layout.plotRect.width + 12,
|
|
1631
|
+
y: layout.margins.top + 10,
|
|
1632
|
+
width: Math.max(
|
|
1633
|
+
1,
|
|
1634
|
+
layout.cssWidth -
|
|
1635
|
+
layout.plotRect.x -
|
|
1636
|
+
layout.plotRect.width -
|
|
1637
|
+
16,
|
|
1638
|
+
),
|
|
1639
|
+
height: Math.max(1, layout.plotRect.height - 10),
|
|
1640
|
+
};
|
|
1641
|
+
|
|
1642
|
+
paintSeriesLegend(
|
|
1643
|
+
chart,
|
|
1644
|
+
box,
|
|
1645
|
+
{
|
|
1646
|
+
mode: floating ? "floating" : "sidebar",
|
|
1647
|
+
legend: chart._legend,
|
|
1648
|
+
title,
|
|
1649
|
+
sidebarGutter: floating ? undefined : layout.margins.right,
|
|
1650
|
+
opacity: cfg.legend_opacity,
|
|
1651
|
+
},
|
|
1652
|
+
series.length,
|
|
1653
|
+
(i) => ({
|
|
1654
|
+
label: series[i].label,
|
|
1655
|
+
color: series[i].color,
|
|
1656
|
+
seriesIds: [series[i].seriesId],
|
|
1657
|
+
hidden: chart._hiddenSeries.has(series[i].seriesId),
|
|
1658
|
+
}),
|
|
1659
|
+
);
|
|
1545
1660
|
}
|
|
1546
1661
|
|
|
1547
1662
|
function renderBarTooltipCanvas(chart: SeriesChart): void {
|
|
@@ -1580,13 +1695,71 @@ function renderBarTooltipCanvas(chart: SeriesChart): void {
|
|
|
1580
1695
|
|
|
1581
1696
|
const lines = buildBarTooltipLines(chart, b);
|
|
1582
1697
|
const theme = chart._resolveTheme();
|
|
1698
|
+
const dpr = chart._glManager?.dpr ?? 1;
|
|
1699
|
+
|
|
1700
|
+
const datumPos =
|
|
1701
|
+
b.axis === 0
|
|
1702
|
+
? chart._isHorizontal
|
|
1703
|
+
? layout.dataToPixel(b.y1, b.xCenter)
|
|
1704
|
+
: layout.dataToPixel(b.xCenter, b.y1)
|
|
1705
|
+
: rightAxisDataToPixel(chart, b.xCenter, b.y1, layout);
|
|
1706
|
+
const indicators: AxisIndicator[] = [];
|
|
1707
|
+
|
|
1708
|
+
let categoryText: string;
|
|
1709
|
+
if (
|
|
1710
|
+
chart._categoryAxisMode === "numeric" &&
|
|
1711
|
+
chart._categoryPositions &&
|
|
1712
|
+
chart._categoryPositions[b.catIdx] != null
|
|
1713
|
+
) {
|
|
1714
|
+
const catFmt =
|
|
1715
|
+
chart.getColumnFormatter(chart._groupBy[0], "tick") ??
|
|
1716
|
+
stepTickFormatter(
|
|
1717
|
+
chart._numericCategoryDomain?.isDate,
|
|
1718
|
+
chart._lastCatTicks,
|
|
1719
|
+
);
|
|
1720
|
+
categoryText = catFmt(chart._categoryPositions[b.catIdx]);
|
|
1721
|
+
} else {
|
|
1722
|
+
categoryText = formatBarCategoryPath(chart, b.catIdx);
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
if (categoryText !== "") {
|
|
1726
|
+
indicators.push({
|
|
1727
|
+
side: chart._isHorizontal ? "left" : "bottom",
|
|
1728
|
+
px: datumPos.px,
|
|
1729
|
+
py: datumPos.py,
|
|
1730
|
+
text: categoryText,
|
|
1731
|
+
});
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
const valueDomain =
|
|
1735
|
+
b.axis === 1 ? chart._lastAltYDomain : chart._lastYDomain;
|
|
1736
|
+
const valueTicks = b.axis === 1 ? chart._lastAltYTicks : chart._lastYTicks;
|
|
1737
|
+
const valueFmt =
|
|
1738
|
+
chart.getColumnFormatter(chart._series[b.seriesId]?.aggName, "tick") ??
|
|
1739
|
+
stepTickFormatter(valueDomain?.isDate, valueTicks);
|
|
1740
|
+
indicators.push({
|
|
1741
|
+
side: chart._isHorizontal ? "bottom" : b.axis === 1 ? "right" : "left",
|
|
1742
|
+
px: datumPos.px,
|
|
1743
|
+
py: datumPos.py,
|
|
1744
|
+
text: valueFmt(b.y1),
|
|
1745
|
+
});
|
|
1746
|
+
renderAxisHoverIndicators(
|
|
1747
|
+
chart._chromeCanvas,
|
|
1748
|
+
layout,
|
|
1749
|
+
theme,
|
|
1750
|
+
dpr,
|
|
1751
|
+
indicators,
|
|
1752
|
+
chart._pluginConfig.tooltip_opacity,
|
|
1753
|
+
);
|
|
1754
|
+
|
|
1583
1755
|
renderCanvasTooltip(
|
|
1584
1756
|
chart._chromeCanvas,
|
|
1585
1757
|
pos,
|
|
1586
1758
|
lines,
|
|
1587
1759
|
layout,
|
|
1588
1760
|
theme,
|
|
1589
|
-
|
|
1761
|
+
dpr,
|
|
1762
|
+
tooltipStyleOf(chart._pluginConfig),
|
|
1590
1763
|
);
|
|
1591
1764
|
}
|
|
1592
1765
|
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
} from "./series-build";
|
|
31
31
|
import {
|
|
32
32
|
renderBarFrame,
|
|
33
|
+
renderBarChromeOverlay,
|
|
33
34
|
uploadBarInstances,
|
|
34
35
|
invalidateGlyphBuffers,
|
|
35
36
|
rebuildGlyphBuffers,
|
|
@@ -126,6 +127,15 @@ export class SeriesChart extends CategoricalYChart {
|
|
|
126
127
|
return { lockAxis: this._isHorizontal ? "x" : "y" };
|
|
127
128
|
}
|
|
128
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Chrome-only repaint for legend scroll / floating-legend drag —
|
|
132
|
+
* the same lightweight path hover updates use. No GL pass; the
|
|
133
|
+
* composite re-presents over the retained plot bitmap.
|
|
134
|
+
*/
|
|
135
|
+
repaintChrome(): void {
|
|
136
|
+
renderBarChromeOverlay(this);
|
|
137
|
+
}
|
|
138
|
+
|
|
129
139
|
_locations: CachedLocations | null = null;
|
|
130
140
|
|
|
131
141
|
// Series-specific categorical-axis bookkeeping. `_rowPaths`,
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
INNER_RING_PX,
|
|
29
29
|
} from "./sunburst-layout";
|
|
30
30
|
import { buildFacetGrid } from "../../layout/facet-grid";
|
|
31
|
+
import { legendTreeGutter } from "../../interaction/legend-controller";
|
|
31
32
|
import { withChromeCache } from "../common/chrome-cache";
|
|
32
33
|
import {
|
|
33
34
|
renderBreadcrumbs as renderTreeBreadcrumbs,
|
|
@@ -106,7 +107,12 @@ export function renderSunburstFrame(
|
|
|
106
107
|
chart._colorMin < chart._colorMax;
|
|
107
108
|
const breadcrumbH =
|
|
108
109
|
!hasSplits && chart._breadcrumbIds.length > 1 ? BREADCRUMB_H : 0;
|
|
109
|
-
const legendW =
|
|
110
|
+
const legendW = legendTreeGutter(
|
|
111
|
+
chart._pluginConfig,
|
|
112
|
+
hasLegend,
|
|
113
|
+
LEGEND_W,
|
|
114
|
+
chart._colorMode === "series" ? chart._uniqueColorLabels.size : 0,
|
|
115
|
+
);
|
|
110
116
|
|
|
111
117
|
if (hasSplits) {
|
|
112
118
|
layoutFacetedSunburst(chart, cssWidth, cssHeight, legendW);
|