@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.
Files changed (109) hide show
  1. package/dist/cdn/perspective-viewer-charts.js +1 -2
  2. package/dist/cdn/perspective-viewer-charts.js.map +4 -4
  3. package/dist/esm/axis/legend.d.ts +53 -5
  4. package/dist/esm/axis/map-ticks.d.ts +15 -0
  5. package/dist/esm/charts/candlestick/candlestick-interact.d.ts +2 -6
  6. package/dist/esm/charts/candlestick/candlestick-render.d.ts +3 -0
  7. package/dist/esm/charts/cartesian/cartesian.d.ts +7 -0
  8. package/dist/esm/charts/cartesian/glyph.d.ts +2 -1
  9. package/dist/esm/charts/cartesian/glyphs/density.d.ts +2 -1
  10. package/dist/esm/charts/cartesian/glyphs/lines.d.ts +2 -1
  11. package/dist/esm/charts/cartesian/glyphs/points.d.ts +2 -1
  12. package/dist/esm/charts/cartesian/tooltip-lines.d.ts +1 -1
  13. package/dist/esm/charts/chart-base.d.ts +24 -9
  14. package/dist/esm/charts/chart.d.ts +81 -6
  15. package/dist/esm/charts/common/category-axis-resolver.d.ts +1 -1
  16. package/dist/esm/charts/common/draw-tooltip-box.d.ts +2 -1
  17. package/dist/esm/charts/common/tree-chart.d.ts +1 -0
  18. package/dist/esm/charts/common/tree-interact.d.ts +1 -1
  19. package/dist/esm/charts/heatmap/heatmap.d.ts +1 -0
  20. package/dist/esm/charts/series/series-build.d.ts +7 -0
  21. package/dist/esm/charts/series/series-interact.d.ts +1 -1
  22. package/dist/esm/charts/series/series-type.d.ts +18 -0
  23. package/dist/esm/charts/series/series.d.ts +10 -1
  24. package/dist/esm/charts/sunburst/sunburst-render.d.ts +4 -0
  25. package/dist/esm/index.d.ts +2 -0
  26. package/dist/esm/interaction/axis-indicators.d.ts +11 -0
  27. package/dist/esm/interaction/host-sink-dom.d.ts +3 -2
  28. package/dist/esm/interaction/host-sink-message.d.ts +3 -2
  29. package/dist/esm/interaction/lazy-tooltip.d.ts +2 -2
  30. package/dist/esm/interaction/legend-controller.d.ts +246 -0
  31. package/dist/esm/interaction/tooltip-controller.d.ts +20 -95
  32. package/dist/esm/interaction/tooltip-grid.d.ts +33 -0
  33. package/dist/esm/layout/facet-grid.d.ts +6 -0
  34. package/dist/esm/layout/ticks.d.ts +1 -0
  35. package/dist/esm/map/tile-source.d.ts +77 -8
  36. package/dist/esm/perspective-viewer-charts.js +1 -2
  37. package/dist/esm/perspective-viewer-charts.js.map +4 -4
  38. package/dist/esm/plugin/charts.d.ts +2 -0
  39. package/dist/esm/plugin/format-defaults.d.ts +9 -0
  40. package/dist/esm/plugin/plugin.d.ts +28 -1
  41. package/dist/esm/theme/gradient.d.ts +32 -0
  42. package/dist/esm/theme/overrides.d.ts +19 -0
  43. package/dist/esm/transport/protocol.d.ts +58 -5
  44. package/dist/esm/transport/renderer-transport.d.ts +3 -0
  45. package/dist/esm/utils/css.d.ts +4 -0
  46. package/dist/esm/worker/renderer.worker.d.ts +46 -1
  47. package/package.json +1 -1
  48. package/src/css/perspective-viewer-charts.css +0 -1
  49. package/src/ts/axis/bar-axis.ts +2 -7
  50. package/src/ts/axis/legend.ts +351 -24
  51. package/src/ts/axis/map-ticks.ts +94 -0
  52. package/src/ts/axis/numeric-axis.ts +2 -9
  53. package/src/ts/charts/candlestick/candlestick-build.ts +1 -0
  54. package/src/ts/charts/candlestick/candlestick-interact.ts +46 -40
  55. package/src/ts/charts/candlestick/candlestick-render.ts +81 -10
  56. package/src/ts/charts/candlestick/candlestick.ts +4 -3
  57. package/src/ts/charts/cartesian/cartesian-interact.ts +9 -3
  58. package/src/ts/charts/cartesian/cartesian-render.ts +374 -75
  59. package/src/ts/charts/cartesian/cartesian.ts +13 -0
  60. package/src/ts/charts/cartesian/glyph.ts +6 -2
  61. package/src/ts/charts/cartesian/glyphs/density.ts +2 -2
  62. package/src/ts/charts/cartesian/glyphs/lines.ts +15 -11
  63. package/src/ts/charts/cartesian/glyphs/points.ts +9 -5
  64. package/src/ts/charts/cartesian/tooltip-lines.ts +8 -8
  65. package/src/ts/charts/chart-base.ts +76 -42
  66. package/src/ts/charts/chart.ts +111 -7
  67. package/src/ts/charts/common/category-axis-resolver.ts +44 -5
  68. package/src/ts/charts/common/draw-tooltip-box.ts +13 -30
  69. package/src/ts/charts/common/tree-chart.ts +4 -0
  70. package/src/ts/charts/common/tree-chrome.ts +102 -23
  71. package/src/ts/charts/common/tree-interact.ts +18 -17
  72. package/src/ts/charts/heatmap/heatmap-build.ts +7 -1
  73. package/src/ts/charts/heatmap/heatmap-interact.ts +75 -8
  74. package/src/ts/charts/heatmap/heatmap-render.ts +105 -24
  75. package/src/ts/charts/heatmap/heatmap.ts +8 -3
  76. package/src/ts/charts/map/map.ts +3 -5
  77. package/src/ts/charts/series/series-build.ts +15 -2
  78. package/src/ts/charts/series/series-interact.ts +14 -24
  79. package/src/ts/charts/series/series-render.ts +310 -132
  80. package/src/ts/charts/series/series-type.ts +65 -0
  81. package/src/ts/charts/series/series.ts +31 -8
  82. package/src/ts/charts/sunburst/sunburst-render.ts +16 -1
  83. package/src/ts/charts/treemap/treemap-render.ts +12 -1
  84. package/src/ts/data/view-reader.ts +1 -4
  85. package/src/ts/index.ts +2 -0
  86. package/src/ts/interaction/axis-indicators.ts +137 -0
  87. package/src/ts/interaction/host-sink-dom.ts +66 -2
  88. package/src/ts/interaction/host-sink-message.ts +8 -2
  89. package/src/ts/interaction/lazy-tooltip.ts +5 -5
  90. package/src/ts/interaction/legend-controller.ts +1084 -0
  91. package/src/ts/interaction/tooltip-controller.ts +43 -131
  92. package/src/ts/interaction/tooltip-grid.ts +187 -0
  93. package/src/ts/layout/facet-grid.ts +8 -1
  94. package/src/ts/layout/ticks.ts +48 -12
  95. package/src/ts/map/tile-loader.ts +16 -1
  96. package/src/ts/map/tile-source.ts +199 -53
  97. package/src/ts/map/tile-sources.json +18 -0
  98. package/src/ts/plugin/charts.ts +40 -6
  99. package/src/ts/plugin/format-defaults.ts +37 -0
  100. package/src/ts/plugin/plugin.ts +330 -76
  101. package/src/ts/shaders/line.vert.glsl +5 -2
  102. package/src/ts/shaders/scatter.vert.glsl +0 -7
  103. package/src/ts/theme/gradient.ts +265 -1
  104. package/src/ts/theme/overrides.ts +76 -0
  105. package/src/ts/transport/protocol.ts +60 -4
  106. package/src/ts/transport/renderer-transport.ts +107 -14
  107. package/src/ts/utils/css.ts +12 -0
  108. package/src/ts/worker/dispatch.ts +6 -2
  109. package/src/ts/worker/renderer.worker.ts +191 -7
@@ -12,6 +12,13 @@
12
12
 
13
13
  import type { Context2D } from "../canvas-types";
14
14
  import type { Theme } from "../../theme/theme";
15
+ import {
16
+ measureTooltipGrid,
17
+ paintTooltipGrid,
18
+ DEFAULT_TOOLTIP_STYLE,
19
+ type TooltipContent,
20
+ type TooltipStyle,
21
+ } from "../../interaction/tooltip-grid";
15
22
 
16
23
  /**
17
24
  * Draw a freestanding tooltip box anchored near (cx, cy), measuring
@@ -22,32 +29,21 @@ import type { Theme } from "../../theme/theme";
22
29
  export function drawTooltipBox(
23
30
  ctx: Context2D,
24
31
  theme: Theme,
25
- lines: string[],
32
+ grid: TooltipContent,
26
33
  cx: number,
27
34
  cy: number,
28
35
  cssWidth: number,
29
36
  cssHeight: number,
30
37
  fontFamily: string,
38
+ style: TooltipStyle = DEFAULT_TOOLTIP_STYLE,
31
39
  ): void {
32
- if (lines.length === 0) {
40
+ if (grid.length === 0) {
33
41
  return;
34
42
  }
35
43
 
36
- const { tooltipBg, tooltipText, tooltipBorder } = theme;
37
-
38
44
  ctx.font = `11px ${fontFamily}`;
39
- const lineHeight = 16;
40
- const padding = 8;
41
- let maxWidth = 0;
42
- for (const line of lines) {
43
- const w = ctx.measureText(line).width;
44
- if (w > maxWidth) {
45
- maxWidth = w;
46
- }
47
- }
48
-
49
- const boxW = maxWidth + padding * 2;
50
- const boxH = lines.length * lineHeight + padding * 2 - 4;
45
+ const measured = measureTooltipGrid(ctx, grid, style.maxColumnPx);
46
+ const { boxW, boxH } = measured;
51
47
 
52
48
  let tx = cx + 12;
53
49
  let ty = cy - boxH - 8;
@@ -67,18 +63,5 @@ export function drawTooltipBox(
67
63
  ty = cssHeight - boxH - 4;
68
64
  }
69
65
 
70
- ctx.fillStyle = tooltipBg;
71
- ctx.strokeStyle = tooltipBorder;
72
- ctx.lineWidth = 1;
73
- ctx.beginPath();
74
- ctx.roundRect(tx, ty, boxW, boxH, 4);
75
- ctx.fill();
76
- ctx.stroke();
77
-
78
- ctx.fillStyle = tooltipText;
79
- ctx.textAlign = "left";
80
- ctx.textBaseline = "top";
81
- for (let i = 0; i < lines.length; i++) {
82
- ctx.fillText(lines[i], tx + padding, ty + padding + i * lineHeight);
83
- }
66
+ paintTooltipGrid(ctx, measured, tx, ty, theme, style.opacity);
84
67
  }
@@ -40,6 +40,10 @@ export function firstNonMetadataColumn(columns: ColumnDataMap): string {
40
40
  * layout modules can read/write them without friction.
41
41
  */
42
42
  export abstract class TreeChartBase extends AbstractChart {
43
+ protected override colorScaleColumn(): string | null {
44
+ return this._columnSlots[1] || null;
45
+ }
46
+
43
47
  // Shared column-slot resolution
44
48
  _sizeName = "";
45
49
  _colorName = "";
@@ -17,12 +17,21 @@ import type { GradientStop } from "../../theme/gradient";
17
17
  import type { Vec3 } from "../../theme/palette";
18
18
  import type { Theme } from "../../theme/theme";
19
19
  import {
20
+ gradientLegendAutoFit,
21
+ legendAutoFit,
20
22
  renderCategoricalLegend,
21
23
  renderCategoricalLegendAt,
22
24
  renderLegend,
25
+ renderLegendAt,
26
+ type LegendPaintView,
23
27
  } from "../../axis/legend";
28
+ import {
29
+ legendSidebarWidth,
30
+ resolveLegendMode,
31
+ } from "../../interaction/legend-controller";
24
32
  import type { TreeChartBase } from "./tree-chart";
25
33
  import { drawTooltipBox } from "./draw-tooltip-box";
34
+ import { tooltipStyleOf } from "../../interaction/tooltip-grid";
26
35
 
27
36
  /**
28
37
  * Click target for one breadcrumb segment. Tree-chart hit-testing
@@ -112,23 +121,24 @@ export function renderTreeTooltip(
112
121
  cssHeight: number,
113
122
  fontFamily: string,
114
123
  ): void {
115
- const lines =
124
+ const grid =
116
125
  chart._lazyTooltip.hoveredTarget === nodeId
117
- ? (chart._lazyTooltip.lines ?? [])
126
+ ? (chart._lazyTooltip.grid ?? [])
118
127
  : [];
119
- if (lines.length === 0) {
128
+ if (grid.length === 0) {
120
129
  return;
121
130
  }
122
131
 
123
132
  drawTooltipBox(
124
133
  ctx,
125
134
  chart._resolveTheme(),
126
- lines,
135
+ grid,
127
136
  cx,
128
137
  cy,
129
138
  cssWidth,
130
139
  cssHeight,
131
140
  fontFamily,
141
+ tooltipStyleOf(chart._pluginConfig),
132
142
  );
133
143
  }
134
144
 
@@ -159,50 +169,119 @@ export function renderTreeColorLegend(
159
169
  cssHeight: number,
160
170
  categoricalRect: PlotRect | null = null,
161
171
  ): void {
162
- if (chart._colorMode === "series" && chart._uniqueColorLabels.size > 1) {
163
- if (categoricalRect) {
172
+ const cfg = chart._pluginConfig;
173
+ const hasCategorical =
174
+ chart._colorMode === "series" && chart._uniqueColorLabels.size > 1;
175
+ const hasNumeric =
176
+ chart._colorMode === "numeric" && chart._colorMin < chart._colorMax;
177
+ const mode = resolveLegendMode(
178
+ cfg,
179
+ hasCategorical ? chart._uniqueColorLabels.size : 0,
180
+ );
181
+ if (mode === "none" || (!hasCategorical && !hasNumeric)) {
182
+ chart._legend.clearPainted();
183
+ return;
184
+ }
185
+
186
+ const floating = mode === "floating";
187
+ const view: LegendPaintView = {
188
+ mode: floating ? "floating" : "sidebar",
189
+ legend: chart._legend,
190
+ title: chart._colorName || "Legend",
191
+ opacity: cfg.legend_opacity,
192
+ };
193
+ const floatBox = floating
194
+ ? chart._legend.floatingBox(
195
+ cfg,
196
+ cssWidth,
197
+ cssHeight,
198
+ hasCategorical
199
+ ? legendAutoFit(
200
+ canvas,
201
+ theme,
202
+ chart._uniqueColorLabels.size,
203
+ () => chart._uniqueColorLabels.keys(),
204
+ { title: view.title },
205
+ )
206
+ : gradientLegendAutoFit(
207
+ canvas,
208
+ theme,
209
+ { min: chart._colorMin, max: chart._colorMax },
210
+ undefined,
211
+ view.title,
212
+ ),
213
+ )
214
+ : null;
215
+
216
+ if (hasCategorical) {
217
+ if (floatBox) {
218
+ renderCategoricalLegendAt(
219
+ canvas,
220
+ floatBox,
221
+ chart._uniqueColorLabels,
222
+ palette,
223
+ theme,
224
+ view,
225
+ );
226
+ } else if (categoricalRect) {
164
227
  renderCategoricalLegendAt(
165
228
  canvas,
166
229
  categoricalRect,
167
230
  chart._uniqueColorLabels,
168
231
  palette,
169
232
  theme,
233
+ { ...view, sidebarGutter: categoricalRect.width },
170
234
  );
171
235
  } else {
172
236
  renderCategoricalLegend(
173
237
  canvas,
174
- syntheticLegendLayout(cssWidth, cssHeight),
238
+ syntheticLegendLayout(cssWidth, cssHeight, cfg),
175
239
  chart._uniqueColorLabels,
176
240
  palette,
177
241
  theme,
242
+ view,
243
+ );
244
+ }
245
+ } else {
246
+ const colorDomain = {
247
+ min: chart._colorMin,
248
+ max: chart._colorMax,
249
+ label: chart._colorName,
250
+ };
251
+ const formatter = chart.getColumnFormatter(chart._colorName, "value");
252
+ if (floatBox) {
253
+ renderLegendAt(
254
+ canvas,
255
+ floatBox,
256
+ colorDomain,
257
+ stops,
258
+ theme,
259
+ formatter,
260
+ view,
261
+ );
262
+ } else {
263
+ renderLegend(
264
+ canvas,
265
+ syntheticLegendLayout(cssWidth, cssHeight, cfg),
266
+ colorDomain,
267
+ stops,
268
+ theme,
269
+ formatter,
270
+ view,
178
271
  );
179
272
  }
180
- } else if (
181
- chart._colorMode === "numeric" &&
182
- chart._colorMin < chart._colorMax
183
- ) {
184
- renderLegend(
185
- canvas,
186
- syntheticLegendLayout(cssWidth, cssHeight),
187
- {
188
- min: chart._colorMin,
189
- max: chart._colorMax,
190
- label: chart._colorName,
191
- },
192
- stops,
193
- theme,
194
- chart.getColumnFormatter(chart._colorName, "value"),
195
- );
196
273
  }
197
274
  }
198
275
 
199
276
  function syntheticLegendLayout(
200
277
  cssWidth: number,
201
278
  cssHeight: number,
279
+ cfg: TreeChartBase["_pluginConfig"],
202
280
  ): PlotLayout {
203
281
  return new PlotLayout(cssWidth, cssHeight, {
204
282
  hasXLabel: false,
205
283
  hasYLabel: false,
206
284
  hasLegend: true,
285
+ rightExtra: legendSidebarWidth(cfg, 80),
207
286
  });
208
287
  }
@@ -13,6 +13,7 @@
13
13
  import type { TreeChartBase } from "./tree-chart";
14
14
  import { NULL_NODE, ancestorNames } from "./node-store";
15
15
  import { rebuildBreadcrumbs } from "./tree-data";
16
+ import { tooltipStyleOf } from "../../interaction/tooltip-grid";
16
17
 
17
18
  /**
18
19
  * Common subset of `TreemapChart` / `SunburstChart` reached by the
@@ -73,9 +74,9 @@ export async function emitTreeNodeEvent(
73
74
  export async function buildTreeTooltipLines(
74
75
  chart: TreeInteractChart,
75
76
  nodeId: number,
76
- ): Promise<string[]> {
77
+ ): Promise<string[][]> {
77
78
  const store = chart._nodeStore;
78
- const lines: string[] = [];
79
+ const grid: string[][] = [];
79
80
 
80
81
  const pathNames: string[] = [];
81
82
  let p = nodeId;
@@ -86,19 +87,17 @@ export async function buildTreeTooltipLines(
86
87
 
87
88
  pathNames.reverse();
88
89
  if (pathNames.length > 0) {
89
- lines.push(pathNames.join(" › "));
90
+ grid.push([pathNames.join(" › ")]);
90
91
  } else {
91
- lines.push(store.name[nodeId]);
92
+ grid.push([store.name[nodeId]]);
92
93
  }
93
94
 
94
95
  const sizeFmt = chart.getColumnFormatter(chart._sizeName, "value");
95
- lines.push(`Value: ${sizeFmt(store.value[nodeId])}`);
96
+ grid.push(["Value", sizeFmt(store.value[nodeId])]);
96
97
 
97
98
  if (chart._colorName && !isNaN(store.colorValue[nodeId])) {
98
99
  const colorFmt = chart.getColumnFormatter(chart._colorName, "value");
99
- lines.push(
100
- `${chart._colorName}: ${colorFmt(store.colorValue[nodeId])}`,
101
- );
100
+ grid.push([chart._colorName, colorFmt(store.colorValue[nodeId])]);
102
101
  }
103
102
 
104
103
  const rowIdx = store.leafRowIdx[nodeId];
@@ -117,20 +116,21 @@ export async function buildTreeTooltipLines(
117
116
  }
118
117
 
119
118
  if (typeof value === "number") {
120
- lines.push(
121
- `${name}: ${chart.getColumnFormatter(name, "value")(value)}`,
122
- );
119
+ grid.push([
120
+ name,
121
+ chart.getColumnFormatter(name, "value")(value),
122
+ ]);
123
123
  } else {
124
- lines.push(`${name}: ${value}`);
124
+ grid.push([name, String(value)]);
125
125
  }
126
126
  }
127
127
  }
128
128
 
129
129
  if (store.firstChild[nodeId] !== NULL_NODE) {
130
- lines.push(`Children: ${store.childCount[nodeId]}`);
130
+ grid.push(["Children", String(store.childCount[nodeId])]);
131
131
  }
132
132
 
133
- return lines;
133
+ return grid;
134
134
  }
135
135
 
136
136
  /**
@@ -150,19 +150,20 @@ export function showTreePinnedTooltip(
150
150
  const cssWidth = chart._glManager?.cssWidth ?? 0;
151
151
  const cssHeight = chart._glManager?.cssHeight ?? 0;
152
152
 
153
- buildTreeTooltipLines(chart, nodeId).then((lines) => {
153
+ buildTreeTooltipLines(chart, nodeId).then((grid) => {
154
154
  if (chart._pinnedNodeId !== nodeId) {
155
155
  return;
156
156
  }
157
157
 
158
- if (lines.length === 0) {
158
+ if (grid.length === 0) {
159
159
  return;
160
160
  }
161
161
 
162
162
  chart._tooltip.pin(
163
- lines,
163
+ grid,
164
164
  { px: anchor.cx, py: anchor.cy },
165
165
  { cssWidth, cssHeight },
166
+ tooltipStyleOf(chart._pluginConfig),
166
167
  );
167
168
  });
168
169
 
@@ -128,7 +128,13 @@ export function buildHeatmapPipeline(
128
128
  rowPaths: xLevels,
129
129
  numCategories: numX,
130
130
  rowOffset,
131
- } = resolveCategoryAxis(columns, numRows, groupBy.length, levelTypes);
131
+ } = resolveCategoryAxis(
132
+ columns,
133
+ numRows,
134
+ groupBy.length,
135
+ levelTypes,
136
+ xAxisMode,
137
+ );
132
138
 
133
139
  // Numeric X domain: sourced from `__ROW_PATH_0__`'s raw values when
134
140
  // the single group_by is non-string.
@@ -13,6 +13,12 @@
13
13
  import type { HeatmapChart } from "./heatmap";
14
14
  import type { HeatmapCell } from "./heatmap-build";
15
15
  import { renderCanvasTooltip } from "../../interaction/tooltip-controller";
16
+ import {
17
+ renderAxisHoverIndicators,
18
+ type AxisIndicator,
19
+ } from "../../interaction/axis-indicators";
20
+ import { computeNiceTicks, stepTickFormatter } from "../../layout/ticks";
21
+ import { tooltipStyleOf } from "../../interaction/tooltip-grid";
16
22
  import type { CategoricalLevel } from "../../axis/categorical-axis";
17
23
 
18
24
  /**
@@ -215,6 +221,9 @@ export function renderHeatmapTooltip(chart: HeatmapChart): void {
215
221
  let layout: import("../../layout/plot-layout").PlotLayout | null;
216
222
  let xLevels: CategoricalLevel[];
217
223
  let yLevels: CategoricalLevel[];
224
+ let xPositions: Float64Array | null;
225
+ let yPositions: Float64Array | null;
226
+ let xNumericDomain: { isDate?: boolean } | null;
218
227
  let facetLabel: string | null = null;
219
228
 
220
229
  if (chart._hoveredFacetIdx >= 0) {
@@ -226,6 +235,9 @@ export function renderHeatmapTooltip(chart: HeatmapChart): void {
226
235
  layout = facet.layout;
227
236
  xLevels = facet.pipeline.xLevels;
228
237
  yLevels = facet.pipeline.yLevels;
238
+ xPositions = facet.pipeline.xPositions;
239
+ yPositions = facet.pipeline.yPositions;
240
+ xNumericDomain = facet.pipeline.xNumericDomain;
229
241
  facetLabel = facet.label;
230
242
  } else {
231
243
  if (!chart._lastLayout) {
@@ -235,6 +247,9 @@ export function renderHeatmapTooltip(chart: HeatmapChart): void {
235
247
  layout = chart._lastLayout;
236
248
  xLevels = chart._xLevels;
237
249
  yLevels = chart._yLevels;
250
+ xPositions = chart._xPositions;
251
+ yPositions = chart._yPositions;
252
+ xNumericDomain = chart._xNumericDomain;
238
253
  }
239
254
 
240
255
  const cell = chart._hoveredCell;
@@ -244,31 +259,83 @@ export function renderHeatmapTooltip(chart: HeatmapChart): void {
244
259
  // many pixels.
245
260
  const pos = { px: chart._hoveredMouseX, py: chart._hoveredMouseY };
246
261
 
247
- const lines: string[] = [];
262
+ const grid: string[][] = [];
248
263
  if (facetLabel) {
249
- lines.push(facetLabel);
264
+ grid.push([facetLabel]);
250
265
  }
251
266
 
252
- const xPath = formatHierarchicalPath(xLevels, cell.xIdx);
267
+ const xPath =
268
+ chart._xAxisMode.mode === "numeric" && xPositions
269
+ ? chart.getColumnFormatter(
270
+ chart._groupBy[0],
271
+ "value",
272
+ )(xPositions[cell.xIdx])
273
+ : formatHierarchicalPath(xLevels, cell.xIdx);
253
274
  const yPath = formatHierarchicalPath(yLevels, cell.yIdx);
254
275
  if (xPath) {
255
- lines.push(xPath);
276
+ grid.push([xPath]);
256
277
  }
257
278
 
258
279
  if (yPath) {
259
- lines.push(yPath);
280
+ grid.push([yPath]);
260
281
  }
261
282
 
262
283
  const valueFmt = chart.getColumnFormatter(chart._columnSlots[0], "value");
263
- lines.push(`Value: ${valueFmt(cell.value)}`);
284
+ grid.push(["Value", valueFmt(cell.value)]);
264
285
 
265
286
  const theme = chart._resolveTheme();
287
+ const dpr = chart._glManager?.dpr ?? 1;
288
+
289
+ const cellPos = layout.dataToPixel(
290
+ xPositions ? xPositions[cell.xIdx] : cell.xIdx,
291
+ yPositions ? yPositions[cell.yIdx] : cell.yIdx,
292
+ );
293
+ const badgeX =
294
+ chart._xAxisMode.mode === "numeric" && xPositions
295
+ ? (
296
+ chart.getColumnFormatter(chart._groupBy[0], "tick") ??
297
+ stepTickFormatter(
298
+ xNumericDomain?.isDate,
299
+ computeNiceTicks(layout.paddedXMin, layout.paddedXMax, 6),
300
+ )
301
+ )(xPositions[cell.xIdx])
302
+ : xPath;
303
+
304
+ const indicators: AxisIndicator[] = [];
305
+ if (badgeX) {
306
+ indicators.push({
307
+ side: "bottom",
308
+ px: cellPos.px,
309
+ py: cellPos.py,
310
+ text: badgeX,
311
+ });
312
+ }
313
+
314
+ if (yPath) {
315
+ indicators.push({
316
+ side: "left",
317
+ px: cellPos.px,
318
+ py: cellPos.py,
319
+ text: yPath,
320
+ });
321
+ }
322
+
323
+ renderAxisHoverIndicators(
324
+ chart._chromeCanvas,
325
+ layout,
326
+ theme,
327
+ dpr,
328
+ indicators,
329
+ chart._pluginConfig.tooltip_opacity,
330
+ );
331
+
266
332
  renderCanvasTooltip(
267
333
  chart._chromeCanvas,
268
334
  pos,
269
- lines,
335
+ grid,
270
336
  layout,
271
337
  theme,
272
- chart._glManager?.dpr ?? 1,
338
+ dpr,
339
+ tooltipStyleOf(chart._pluginConfig),
273
340
  );
274
341
  }