insomni-plot 0.1.0-alpha.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/LICENSE.md +674 -0
- package/README.md +81 -0
- package/dist/core.d.mts +340 -0
- package/dist/core.mjs +1047 -0
- package/dist/index.d.mts +3426 -0
- package/dist/index.mjs +12762 -0
- package/dist/interactions-DEFL_F4E.mjs +5395 -0
- package/dist/range-presets-CzECsu3V.d.mts +1523 -0
- package/package.json +34 -0
- package/src/annotations.d.ts +121 -0
- package/src/annotations.ts +438 -0
- package/src/axis.d.ts +184 -0
- package/src/axis.test.ts +131 -0
- package/src/axis.ts +765 -0
- package/src/colorbar.d.ts +69 -0
- package/src/colorbar.ts +294 -0
- package/src/colors.d.ts +57 -0
- package/src/colors.test.ts +28 -0
- package/src/colors.ts +486 -0
- package/src/core.ts +299 -0
- package/src/format.d.ts +54 -0
- package/src/format.ts +138 -0
- package/src/grammar/accessibility.d.ts +147 -0
- package/src/grammar/accessibility.test.ts +199 -0
- package/src/grammar/accessibility.ts +443 -0
- package/src/grammar/aes.d.ts +35 -0
- package/src/grammar/aes.test.ts +75 -0
- package/src/grammar/aes.ts +120 -0
- package/src/grammar/annotations.d.ts +86 -0
- package/src/grammar/annotations.test.ts +68 -0
- package/src/grammar/annotations.ts +336 -0
- package/src/grammar/attach-brush.d.ts +44 -0
- package/src/grammar/attach-brush.test.ts +214 -0
- package/src/grammar/attach-brush.ts +111 -0
- package/src/grammar/attach-presets.d.ts +33 -0
- package/src/grammar/attach-presets.test.ts +106 -0
- package/src/grammar/attach-presets.ts +215 -0
- package/src/grammar/chart.d.ts +952 -0
- package/src/grammar/chart.test.ts +118 -0
- package/src/grammar/chart.ts +1172 -0
- package/src/grammar/color-utils.d.ts +29 -0
- package/src/grammar/color-utils.test.ts +53 -0
- package/src/grammar/color-utils.ts +66 -0
- package/src/grammar/constants.d.ts +45 -0
- package/src/grammar/constants.ts +61 -0
- package/src/grammar/coord.d.ts +183 -0
- package/src/grammar/coord.test.ts +355 -0
- package/src/grammar/coord.ts +619 -0
- package/src/grammar/data/pivot.d.ts +57 -0
- package/src/grammar/data/pivot.ts +107 -0
- package/src/grammar/emphasis-driver.d.ts +69 -0
- package/src/grammar/emphasis-driver.test.ts +199 -0
- package/src/grammar/emphasis-driver.ts +205 -0
- package/src/grammar/equality.d.ts +3 -0
- package/src/grammar/equality.ts +40 -0
- package/src/grammar/facet.d.ts +63 -0
- package/src/grammar/facet.test.ts +60 -0
- package/src/grammar/facet.ts +175 -0
- package/src/grammar/geoms/_categorical.d.ts +94 -0
- package/src/grammar/geoms/_categorical.ts +0 -0
- package/src/grammar/geoms/_distribution.d.ts +52 -0
- package/src/grammar/geoms/_distribution.ts +125 -0
- package/src/grammar/geoms/_mark.d.ts +69 -0
- package/src/grammar/geoms/_mark.ts +136 -0
- package/src/grammar/geoms/_shape.d.ts +41 -0
- package/src/grammar/geoms/_shape.ts +74 -0
- package/src/grammar/geoms/aggregate.d.ts +95 -0
- package/src/grammar/geoms/aggregate.test.ts +554 -0
- package/src/grammar/geoms/aggregate.ts +840 -0
- package/src/grammar/geoms/area.d.ts +32 -0
- package/src/grammar/geoms/area.test.ts +165 -0
- package/src/grammar/geoms/area.ts +578 -0
- package/src/grammar/geoms/band.d.ts +27 -0
- package/src/grammar/geoms/band.test.ts +57 -0
- package/src/grammar/geoms/band.ts +126 -0
- package/src/grammar/geoms/bar.d.ts +56 -0
- package/src/grammar/geoms/bar.test.ts +367 -0
- package/src/grammar/geoms/bar.ts +1054 -0
- package/src/grammar/geoms/boxplot.d.ts +129 -0
- package/src/grammar/geoms/boxplot.test.ts +299 -0
- package/src/grammar/geoms/boxplot.ts +834 -0
- package/src/grammar/geoms/connected-scatter.d.ts +27 -0
- package/src/grammar/geoms/connected-scatter.test.ts +157 -0
- package/src/grammar/geoms/connected-scatter.ts +63 -0
- package/src/grammar/geoms/emphasis.d.ts +76 -0
- package/src/grammar/geoms/emphasis.test.ts +135 -0
- package/src/grammar/geoms/emphasis.ts +162 -0
- package/src/grammar/geoms/histogram.d.ts +75 -0
- package/src/grammar/geoms/histogram.test.ts +262 -0
- package/src/grammar/geoms/histogram.ts +740 -0
- package/src/grammar/geoms/index.d.ts +20 -0
- package/src/grammar/geoms/index.ts +77 -0
- package/src/grammar/geoms/interval.d.ts +31 -0
- package/src/grammar/geoms/interval.test.ts +154 -0
- package/src/grammar/geoms/interval.ts +342 -0
- package/src/grammar/geoms/line.d.ts +38 -0
- package/src/grammar/geoms/line.test.ts +247 -0
- package/src/grammar/geoms/line.ts +659 -0
- package/src/grammar/geoms/point.d.ts +57 -0
- package/src/grammar/geoms/point.test.ts +163 -0
- package/src/grammar/geoms/point.ts +545 -0
- package/src/grammar/geoms/polar.test.ts +216 -0
- package/src/grammar/geoms/ribbon.d.ts +21 -0
- package/src/grammar/geoms/ribbon.test.ts +170 -0
- package/src/grammar/geoms/ribbon.ts +87 -0
- package/src/grammar/geoms/ridgeline.d.ts +89 -0
- package/src/grammar/geoms/ridgeline.test.ts +247 -0
- package/src/grammar/geoms/ridgeline.ts +1164 -0
- package/src/grammar/geoms/rolling.d.ts +43 -0
- package/src/grammar/geoms/rolling.test.ts +217 -0
- package/src/grammar/geoms/rolling.ts +387 -0
- package/src/grammar/geoms/rug.d.ts +28 -0
- package/src/grammar/geoms/rug.test.ts +126 -0
- package/src/grammar/geoms/rug.ts +214 -0
- package/src/grammar/geoms/rule.d.ts +23 -0
- package/src/grammar/geoms/rule.test.ts +69 -0
- package/src/grammar/geoms/rule.ts +212 -0
- package/src/grammar/geoms/smooth.d.ts +54 -0
- package/src/grammar/geoms/smooth.test.ts +78 -0
- package/src/grammar/geoms/smooth.ts +337 -0
- package/src/grammar/geoms/text.d.ts +29 -0
- package/src/grammar/geoms/text.test.ts +64 -0
- package/src/grammar/geoms/text.ts +234 -0
- package/src/grammar/geoms/tile.d.ts +61 -0
- package/src/grammar/geoms/tile.test.ts +157 -0
- package/src/grammar/geoms/tile.ts +621 -0
- package/src/grammar/geoms/types.d.ts +319 -0
- package/src/grammar/geoms/types.ts +362 -0
- package/src/grammar/geoms/violin.d.ts +85 -0
- package/src/grammar/geoms/violin.test.ts +187 -0
- package/src/grammar/geoms/violin.ts +672 -0
- package/src/grammar/index.d.ts +22 -0
- package/src/grammar/index.ts +269 -0
- package/src/grammar/interactions/_disposable.d.ts +5 -0
- package/src/grammar/interactions/_disposable.ts +23 -0
- package/src/grammar/interactions/_z.d.ts +4 -0
- package/src/grammar/interactions/_z.ts +16 -0
- package/src/grammar/interactions/brush-selection.test.ts +262 -0
- package/src/grammar/interactions/brush.d.ts +63 -0
- package/src/grammar/interactions/brush.test.ts +483 -0
- package/src/grammar/interactions/brush.ts +452 -0
- package/src/grammar/interactions/crosshair.d.ts +19 -0
- package/src/grammar/interactions/crosshair.test.ts +127 -0
- package/src/grammar/interactions/crosshair.ts +76 -0
- package/src/grammar/interactions/hit-layer.d.ts +64 -0
- package/src/grammar/interactions/hit-layer.ts +246 -0
- package/src/grammar/interactions/legend.d.ts +19 -0
- package/src/grammar/interactions/legend.ts +101 -0
- package/src/grammar/interactions/menu.d.ts +93 -0
- package/src/grammar/interactions/menu.test.ts +373 -0
- package/src/grammar/interactions/menu.ts +342 -0
- package/src/grammar/interactions/selection.d.ts +25 -0
- package/src/grammar/interactions/selection.test.ts +289 -0
- package/src/grammar/interactions/selection.ts +142 -0
- package/src/grammar/interactions/series-readout.d.ts +91 -0
- package/src/grammar/interactions/series-readout.test.ts +668 -0
- package/src/grammar/interactions/series-readout.ts +422 -0
- package/src/grammar/interactions/series-snap.d.ts +70 -0
- package/src/grammar/interactions/series-snap.test.ts +214 -0
- package/src/grammar/interactions/series-snap.ts +218 -0
- package/src/grammar/interactions/tooltip-axis.test.ts +176 -0
- package/src/grammar/interactions/tooltip-touch.browser.test.ts +49 -0
- package/src/grammar/interactions/tooltip-touch.test.ts +161 -0
- package/src/grammar/interactions/tooltip.d.ts +140 -0
- package/src/grammar/interactions/tooltip.test.ts +406 -0
- package/src/grammar/interactions/tooltip.ts +622 -0
- package/src/grammar/interactions/transitions.d.ts +34 -0
- package/src/grammar/interactions/transitions.test.ts +172 -0
- package/src/grammar/interactions/transitions.ts +160 -0
- package/src/grammar/layout.d.ts +68 -0
- package/src/grammar/layout.ts +186 -0
- package/src/grammar/legend-merge.test.ts +332 -0
- package/src/grammar/mount.d.ts +78 -0
- package/src/grammar/mount.test.ts +479 -0
- package/src/grammar/mount.ts +2112 -0
- package/src/grammar/palettes.d.ts +54 -0
- package/src/grammar/palettes.test.ts +80 -0
- package/src/grammar/palettes.ts +167 -0
- package/src/grammar/pan-zoom.test.ts +398 -0
- package/src/grammar/phylo.d.ts +65 -0
- package/src/grammar/phylo.test.ts +59 -0
- package/src/grammar/phylo.ts +112 -0
- package/src/grammar/pipeline.auto-ticks.test.ts +40 -0
- package/src/grammar/pipeline.d.ts +158 -0
- package/src/grammar/pipeline.test.ts +463 -0
- package/src/grammar/pipeline.ts +1233 -0
- package/src/grammar/profiling.d.ts +8 -0
- package/src/grammar/profiling.ts +24 -0
- package/src/grammar/scales.d.ts +188 -0
- package/src/grammar/scales.test.ts +181 -0
- package/src/grammar/scales.ts +800 -0
- package/src/grammar/svg.d.ts +3 -0
- package/src/grammar/svg.ts +39 -0
- package/src/grammar/theme.d.ts +261 -0
- package/src/grammar/theme.test.ts +105 -0
- package/src/grammar/theme.ts +490 -0
- package/src/heatmap/cpu.ts +109 -0
- package/src/heatmap/gpu.ts +565 -0
- package/src/heatmap/types.ts +177 -0
- package/src/heatmap.browser.test.ts +308 -0
- package/src/heatmap.test.ts +320 -0
- package/src/heatmap.ts +123 -0
- package/src/index.d.ts +1 -0
- package/src/index.ts +8 -0
- package/src/interactions.d.ts +48 -0
- package/src/interactions.test.ts +226 -0
- package/src/interactions.ts +394 -0
- package/src/layout/box.d.ts +48 -0
- package/src/layout/box.test.ts +107 -0
- package/src/layout/box.ts +143 -0
- package/src/legend.d.ts +115 -0
- package/src/legend.ts +422 -0
- package/src/marks/curve.d.ts +43 -0
- package/src/marks/curve.ts +244 -0
- package/src/marks/stack.d.ts +53 -0
- package/src/marks/stack.ts +184 -0
- package/src/marks.d.ts +273 -0
- package/src/marks.test.ts +541 -0
- package/src/marks.ts +1292 -0
- package/src/navigator.test.ts +174 -0
- package/src/navigator.ts +393 -0
- package/src/range-presets.d.ts +113 -0
- package/src/range-presets.test.ts +345 -0
- package/src/range-presets.ts +349 -0
- package/src/scales.d.ts +98 -0
- package/src/scales.test.ts +103 -0
- package/src/scales.ts +695 -0
- package/src/stats/index.d.ts +200 -0
- package/src/stats/index.test.ts +349 -0
- package/src/stats/index.ts +740 -0
- package/src/stats/regression.d.ts +38 -0
- package/src/stats/regression.test.ts +56 -0
- package/src/stats/regression.ts +396 -0
- package/src/stats/rolling-window.d.ts +55 -0
- package/src/stats/rolling-window.test.ts +237 -0
- package/src/stats/rolling-window.ts +256 -0
- package/src/test-setup.ts +19 -0
- package/src/viewport/axis-state.d.ts +72 -0
- package/src/viewport/axis-state.ts +476 -0
- package/src/viewport.d.ts +170 -0
- package/src/viewport.test.ts +363 -0
- package/src/viewport.ts +510 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { describe, expect, test } from "vite-plus/test";
|
|
2
|
+
|
|
3
|
+
import type { CompiledHitTest } from "../geoms/types.ts";
|
|
4
|
+
import { collectLayerGroups, computeSnap, type SnapGroup } from "./series-snap.ts";
|
|
5
|
+
|
|
6
|
+
interface Row {
|
|
7
|
+
t: number;
|
|
8
|
+
v: number;
|
|
9
|
+
series?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Fixture builders (mirror series-readout.test.ts style)
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
function singleSeriesHit(data: readonly Row[], xs: number[], label?: string): CompiledHitTest<Row> {
|
|
17
|
+
const positions = new Float32Array(xs.length * 2);
|
|
18
|
+
for (let i = 0; i < xs.length; i++) {
|
|
19
|
+
positions[i * 2] = xs[i]!;
|
|
20
|
+
positions[i * 2 + 1] = 50;
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
geomKind: "line",
|
|
24
|
+
label,
|
|
25
|
+
positions,
|
|
26
|
+
dataIndex: Int32Array.from(xs.map((_, i) => i)),
|
|
27
|
+
pickRadius: 5,
|
|
28
|
+
channels: {
|
|
29
|
+
x: { kind: "column", column: "t", fn: (d) => d.t },
|
|
30
|
+
y: { kind: "column", column: "v", fn: (d) => d.v },
|
|
31
|
+
},
|
|
32
|
+
data,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function ySpreadHit(data: readonly Row[], ys: number[]): CompiledHitTest<Row> {
|
|
37
|
+
const positions = new Float32Array(ys.length * 2);
|
|
38
|
+
for (let i = 0; i < ys.length; i++) {
|
|
39
|
+
positions[i * 2] = 100; // all share same x
|
|
40
|
+
positions[i * 2 + 1] = ys[i]!;
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
geomKind: "point",
|
|
44
|
+
positions,
|
|
45
|
+
dataIndex: Int32Array.from(ys.map((_, i) => i)),
|
|
46
|
+
pickRadius: 5,
|
|
47
|
+
channels: {
|
|
48
|
+
x: { kind: "column", column: "t", fn: (d) => d.t },
|
|
49
|
+
y: { kind: "column", column: "v", fn: (d) => d.v },
|
|
50
|
+
},
|
|
51
|
+
data,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function stackedSeriesHit(
|
|
56
|
+
data: readonly Row[],
|
|
57
|
+
xs: number[],
|
|
58
|
+
seriesKeys: string[],
|
|
59
|
+
): CompiledHitTest<Row> {
|
|
60
|
+
const positions = new Float32Array(xs.length * 2);
|
|
61
|
+
for (let i = 0; i < xs.length; i++) {
|
|
62
|
+
positions[i * 2] = xs[i]!;
|
|
63
|
+
positions[i * 2 + 1] = 50;
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
geomKind: "bar",
|
|
67
|
+
positions,
|
|
68
|
+
dataIndex: Int32Array.from(xs.map((_, i) => i)),
|
|
69
|
+
seriesKey: seriesKeys,
|
|
70
|
+
pickRadius: 5,
|
|
71
|
+
channels: {
|
|
72
|
+
x: { kind: "column", column: "t", fn: (d) => d.t },
|
|
73
|
+
y: { kind: "column", column: "v", fn: (d) => d.v },
|
|
74
|
+
},
|
|
75
|
+
data,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// Case 1: axis:"y" snaps on positions[i*2+1]
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
describe("collectLayerGroups axis:y", () => {
|
|
84
|
+
test("snaps on y position when axis is 'y'", () => {
|
|
85
|
+
// All share x=100, y differs: [10, 20, 30], v = [1, 2, 3]
|
|
86
|
+
const data: Row[] = [
|
|
87
|
+
{ t: 100, v: 1 },
|
|
88
|
+
{ t: 100, v: 2 },
|
|
89
|
+
{ t: 100, v: 3 },
|
|
90
|
+
];
|
|
91
|
+
const hit = ySpreadHit(data, [10, 20, 30]);
|
|
92
|
+
const out: SnapGroup[] = [];
|
|
93
|
+
collectLayerGroups(hit, 0, 22, "nearest-x", out, "y");
|
|
94
|
+
expect(out).toHaveLength(1);
|
|
95
|
+
// y=20 is closest to cursor=22 (dist 2 vs dist 8 for y=30, dist 12 for y=10)
|
|
96
|
+
expect(out[0]!.index).toBe(1);
|
|
97
|
+
expect(out[0]!.yValue).toBe(2);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("default axis (no 6th arg) snaps on x position", () => {
|
|
101
|
+
// Normal x-spread: positions at x=[10,20,30], y all=50
|
|
102
|
+
const data: Row[] = [
|
|
103
|
+
{ t: 10, v: 1 },
|
|
104
|
+
{ t: 20, v: 2 },
|
|
105
|
+
{ t: 30, v: 3 },
|
|
106
|
+
];
|
|
107
|
+
const hit = singleSeriesHit(data, [10, 20, 30], "weight");
|
|
108
|
+
const out: SnapGroup[] = [];
|
|
109
|
+
collectLayerGroups(hit, 0, 22, "nearest-x", out);
|
|
110
|
+
expect(out).toHaveLength(1);
|
|
111
|
+
// x=20 is closest to cursor=22
|
|
112
|
+
expect(out[0]!.index).toBe(1);
|
|
113
|
+
expect(out[0]!.yValue).toBe(2);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
// Case 2: stacked seriesKey splits into N groups
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
describe("collectLayerGroups stacked seriesKey", () => {
|
|
122
|
+
test("splits hits with seriesKey into separate groups", () => {
|
|
123
|
+
// 4 points: x=[10,10,20,20], all y=50
|
|
124
|
+
// seriesKeys: ["directRad","diffuseRad","directRad","diffuseRad"]
|
|
125
|
+
// v values: [100, 40, 110, 45]
|
|
126
|
+
const data: Row[] = [
|
|
127
|
+
{ t: 10, v: 100 },
|
|
128
|
+
{ t: 10, v: 40 },
|
|
129
|
+
{ t: 20, v: 110 },
|
|
130
|
+
{ t: 20, v: 45 },
|
|
131
|
+
];
|
|
132
|
+
const hit = stackedSeriesHit(
|
|
133
|
+
data,
|
|
134
|
+
[10, 10, 20, 20],
|
|
135
|
+
["directRad", "diffuseRad", "directRad", "diffuseRad"],
|
|
136
|
+
);
|
|
137
|
+
const out: SnapGroup[] = [];
|
|
138
|
+
collectLayerGroups(hit, 0, 12, "nearest-x", out);
|
|
139
|
+
|
|
140
|
+
expect(out).toHaveLength(2);
|
|
141
|
+
|
|
142
|
+
const byId = new Map(out.map((g) => [g.id, g]));
|
|
143
|
+
expect(byId.has("0::directRad")).toBe(true);
|
|
144
|
+
expect(byId.has("0::diffuseRad")).toBe(true);
|
|
145
|
+
|
|
146
|
+
// Nearest x to 12 is x=10 (dist 2) vs x=20 (dist 8) — so picks index 0/1
|
|
147
|
+
const direct = byId.get("0::directRad")!;
|
|
148
|
+
expect(direct.seriesKey).toBe("directRad");
|
|
149
|
+
expect(direct.yValue).toBe(100); // index 0, v=100 at x=10
|
|
150
|
+
|
|
151
|
+
const diffuse = byId.get("0::diffuseRad")!;
|
|
152
|
+
expect(diffuse.seriesKey).toBe("diffuseRad");
|
|
153
|
+
expect(diffuse.yValue).toBe(40); // index 1, v=40 at x=10
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
// Case 3: computeSnap nearest ranking + columnValue
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
|
|
161
|
+
describe("computeSnap", () => {
|
|
162
|
+
test("nearest-x ranking: activeId is the group closest to cursor, columnValue is its xValue", () => {
|
|
163
|
+
// Two single-series hits at x=[10,20,30]
|
|
164
|
+
const data1: Row[] = [
|
|
165
|
+
{ t: 10, v: 1 },
|
|
166
|
+
{ t: 20, v: 2 },
|
|
167
|
+
{ t: 30, v: 3 },
|
|
168
|
+
];
|
|
169
|
+
const data2: Row[] = [
|
|
170
|
+
{ t: 10, v: 10 },
|
|
171
|
+
{ t: 20, v: 20 },
|
|
172
|
+
{ t: 30, v: 30 },
|
|
173
|
+
];
|
|
174
|
+
const hit1 = singleSeriesHit(data1, [10, 20, 30], "series1");
|
|
175
|
+
const hit2 = singleSeriesHit(data2, [10, 20, 30], "series2");
|
|
176
|
+
|
|
177
|
+
const result = computeSnap({
|
|
178
|
+
hits: [hit1 as CompiledHitTest<unknown>, hit2 as CompiledHitTest<unknown>],
|
|
179
|
+
cursor: 19,
|
|
180
|
+
axis: "x",
|
|
181
|
+
snap: "nearest-x",
|
|
182
|
+
active: null,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
expect(result).not.toBeNull();
|
|
186
|
+
// cursor=19: x=20 is dist=1, x=10 is dist=9, x=30 is dist=11
|
|
187
|
+
// Both hits have nearest hit at x=20 (dist=1)
|
|
188
|
+
// First hit (layer 0) wins via iteration order for ties
|
|
189
|
+
expect(result!.activeId).toBe("0::__default__");
|
|
190
|
+
expect(result!.columnValue).toBe(20); // xValue of active group
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("axis:'y' columnValue comes from yValue of active group", () => {
|
|
194
|
+
// y-spread hit: x all=100, y=[10,20,30]
|
|
195
|
+
const data: Row[] = [
|
|
196
|
+
{ t: 100, v: 10 },
|
|
197
|
+
{ t: 100, v: 20 },
|
|
198
|
+
{ t: 100, v: 30 },
|
|
199
|
+
];
|
|
200
|
+
const hit = ySpreadHit(data, [10, 20, 30]);
|
|
201
|
+
|
|
202
|
+
const result = computeSnap({
|
|
203
|
+
hits: [hit as CompiledHitTest<unknown>],
|
|
204
|
+
cursor: 22,
|
|
205
|
+
axis: "y",
|
|
206
|
+
snap: "nearest-x",
|
|
207
|
+
active: null,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
expect(result).not.toBeNull();
|
|
211
|
+
// y=20 is closest to 22 → active group picks index 1
|
|
212
|
+
expect(result!.columnValue).toBe(20); // yValue (v=20 at t=100)
|
|
213
|
+
});
|
|
214
|
+
});
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Shared nearest-column snap + per-series grouping engine
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Extracted verbatim from `series-readout.ts` so BOTH the corner readout panel
|
|
5
|
+
// and the floating axis tooltip consume one source of truth. Generalized to
|
|
6
|
+
// snap on either screen axis: `axis:"x"` (nearest-X time-series) or `axis:"y"`
|
|
7
|
+
// (nearest-Y horizontal charts). X is the default so every existing readout
|
|
8
|
+
// consumer is byte-for-byte unaffected.
|
|
9
|
+
|
|
10
|
+
import type { Color } from "insomni";
|
|
11
|
+
import type { CompiledHitTest, ScaleBundle } from "../geoms/types.ts";
|
|
12
|
+
import type { HitLayerActive } from "./hit-layer.ts";
|
|
13
|
+
|
|
14
|
+
/** One per-series group snapped to the cursor's column. */
|
|
15
|
+
export interface SnapGroup {
|
|
16
|
+
/** Stable id `${layerIdx}::${seriesKey ?? "default"}`. */
|
|
17
|
+
id: string;
|
|
18
|
+
label: string;
|
|
19
|
+
/** Picked hit index inside the compiled hit. */
|
|
20
|
+
index: number;
|
|
21
|
+
/** |cursor − positions[index*2 (+1 for y)]|. */
|
|
22
|
+
dist: number;
|
|
23
|
+
xValue: unknown;
|
|
24
|
+
yValue: unknown;
|
|
25
|
+
/** Raw color-channel value, for scale lookup when no constant. */
|
|
26
|
+
colorRaw?: unknown;
|
|
27
|
+
/** Constant color when the layer's color channel is a constant aes. */
|
|
28
|
+
color?: Color;
|
|
29
|
+
/** seriesKey of the picked hit (stacked / dodged segments). */
|
|
30
|
+
seriesKey?: string;
|
|
31
|
+
/** Reference back to the compiled hit (used for swatch resolution). */
|
|
32
|
+
hit: CompiledHitTest<unknown>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface SnapResult {
|
|
36
|
+
/** Snapped column value: the X for `axis:"x"`, the Y for `axis:"y"`. */
|
|
37
|
+
columnValue: unknown;
|
|
38
|
+
/** Ordered groups, one per series across all layers. */
|
|
39
|
+
groups: SnapGroup[];
|
|
40
|
+
/** Resolved active group id (closest to cursor / dispatched hit). */
|
|
41
|
+
activeId: string | null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Resolve the group identity for a given hit index. Group key priority:
|
|
46
|
+
* 1. `seriesKey[i]` from the compiled hit (stacked / dodged geoms).
|
|
47
|
+
* 2. `colorAes(datum, i)` when color is an accessor / column.
|
|
48
|
+
* 3. Otherwise the whole layer is one group keyed by the layer label.
|
|
49
|
+
* Returns null when no x/y channels are bound. (axis-independent.)
|
|
50
|
+
*/
|
|
51
|
+
export function resolveGroupKey<T>(
|
|
52
|
+
hit: CompiledHitTest<T>,
|
|
53
|
+
hitIndex: number,
|
|
54
|
+
): { key: string; label: string; raw: unknown } | null {
|
|
55
|
+
const xAes = hit.channels.x;
|
|
56
|
+
const yAes = hit.channels.y;
|
|
57
|
+
if (!xAes || !yAes) return null;
|
|
58
|
+
const colorAes = hit.channels.color;
|
|
59
|
+
const labelFallback = hit.label ?? hit.geomKind;
|
|
60
|
+
const sk = hit.seriesKey?.[hitIndex];
|
|
61
|
+
if (sk !== undefined) return { key: sk, label: sk, raw: sk };
|
|
62
|
+
if (colorAes && colorAes.kind !== "constant") {
|
|
63
|
+
const dataIdx = hit.dataIndex[hitIndex]!;
|
|
64
|
+
const raw = colorAes.fn(hit.data[dataIdx]!, dataIdx);
|
|
65
|
+
const k = String(raw);
|
|
66
|
+
return { key: k, label: k, raw };
|
|
67
|
+
}
|
|
68
|
+
return { key: "__default__", label: labelFallback, raw: undefined };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Collect per-series groups for one layer, picking the nearest hit on `axis`
|
|
73
|
+
* within each group. `axis` is the LAST param and defaults to `"x"` so the
|
|
74
|
+
* existing 5-arg callers / `__test__` signature are unaffected.
|
|
75
|
+
*/
|
|
76
|
+
export function collectLayerGroups<T>(
|
|
77
|
+
hit: CompiledHitTest<T>,
|
|
78
|
+
layerIdx: number,
|
|
79
|
+
cursor: number,
|
|
80
|
+
snap: "nearest-x" | "hover",
|
|
81
|
+
out: SnapGroup[],
|
|
82
|
+
axis: "x" | "y" = "x",
|
|
83
|
+
): void {
|
|
84
|
+
const positions = hit.positions;
|
|
85
|
+
const n = (positions.length / 2) | 0;
|
|
86
|
+
if (n === 0) return;
|
|
87
|
+
const xAes = hit.channels.x;
|
|
88
|
+
const yAes = hit.channels.y;
|
|
89
|
+
if (!xAes || !yAes) return;
|
|
90
|
+
const colorAes = hit.channels.color;
|
|
91
|
+
const off = axis === "y" ? 1 : 0;
|
|
92
|
+
|
|
93
|
+
// Walk hits, group by key, picking nearest on the chosen axis within each.
|
|
94
|
+
const picked = new Map<string, { index: number; dist: number; label: string; raw: unknown }>();
|
|
95
|
+
for (let i = 0; i < n; i++) {
|
|
96
|
+
const pos = positions[i * 2 + off]!;
|
|
97
|
+
const dist = Math.abs(pos - cursor);
|
|
98
|
+
if (snap === "hover" && dist > hit.pickRadius) continue;
|
|
99
|
+
const g = resolveGroupKey(hit, i);
|
|
100
|
+
if (g === null) continue;
|
|
101
|
+
const cur = picked.get(g.key);
|
|
102
|
+
if (!cur || dist < cur.dist) {
|
|
103
|
+
picked.set(g.key, { index: i, dist, label: g.label, raw: g.raw });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
for (const [key, p] of picked) {
|
|
108
|
+
const dataIdx = hit.dataIndex[p.index]!;
|
|
109
|
+
const datum = hit.data[dataIdx];
|
|
110
|
+
if (datum === undefined) continue;
|
|
111
|
+
const xValue = xAes.fn(datum, dataIdx);
|
|
112
|
+
const yValue = yAes.fn(datum, dataIdx);
|
|
113
|
+
const colorConst =
|
|
114
|
+
colorAes && colorAes.kind === "constant"
|
|
115
|
+
? (colorAes.fn(datum, dataIdx) as Color | undefined)
|
|
116
|
+
: undefined;
|
|
117
|
+
out.push({
|
|
118
|
+
id: `${layerIdx}::${key}`,
|
|
119
|
+
label: p.label,
|
|
120
|
+
index: p.index,
|
|
121
|
+
dist: p.dist,
|
|
122
|
+
xValue,
|
|
123
|
+
yValue,
|
|
124
|
+
colorRaw: p.raw,
|
|
125
|
+
color: colorConst,
|
|
126
|
+
seriesKey: hit.seriesKey?.[p.index],
|
|
127
|
+
// Stored opaquely for potential swatch resolution; row grouping never
|
|
128
|
+
// re-reads it through `T`, so erase the row type at the boundary.
|
|
129
|
+
hit: hit as CompiledHitTest<unknown>,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Replace a group's pick with the exact dispatched hit. No-op if the group
|
|
136
|
+
* isn't in the collected set. (moved verbatim, axis-independent.)
|
|
137
|
+
*/
|
|
138
|
+
export function overrideGroupPick<T>(
|
|
139
|
+
groups: SnapGroup[],
|
|
140
|
+
groupId: string,
|
|
141
|
+
hit: CompiledHitTest<T>,
|
|
142
|
+
hitIndex: number,
|
|
143
|
+
): void {
|
|
144
|
+
const entry = groups.find((g) => g.id === groupId);
|
|
145
|
+
if (!entry) return;
|
|
146
|
+
const xAes = hit.channels.x;
|
|
147
|
+
const yAes = hit.channels.y;
|
|
148
|
+
if (!xAes || !yAes) return;
|
|
149
|
+
const dataIdx = hit.dataIndex[hitIndex]!;
|
|
150
|
+
const datum = hit.data[dataIdx];
|
|
151
|
+
if (datum === undefined) return;
|
|
152
|
+
entry.index = hitIndex;
|
|
153
|
+
entry.dist = 0;
|
|
154
|
+
entry.xValue = xAes.fn(datum, dataIdx);
|
|
155
|
+
entry.yValue = yAes.fn(datum, dataIdx);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Resolve a group's swatch color, falling back to the color scale. (moved.) */
|
|
159
|
+
export function resolveGroupColor(group: SnapGroup, scales: ScaleBundle | null): Color | undefined {
|
|
160
|
+
if (group.color) return group.color;
|
|
161
|
+
const colorScale = scales?.color;
|
|
162
|
+
if (!colorScale) return undefined;
|
|
163
|
+
if (group.colorRaw !== undefined && group.colorRaw !== null) {
|
|
164
|
+
return colorScale.fn(group.colorRaw);
|
|
165
|
+
}
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Encapsulates the body of series-readout's `recompute()` MINUS emit/DOM: snaps
|
|
171
|
+
* the cursor to the active hit's column (when one is dispatched), collects all
|
|
172
|
+
* groups across layers, resolves the active group, and returns the title column
|
|
173
|
+
* value. Pure — the caller maps `groups` → rows and resolves swatch colors via
|
|
174
|
+
* `resolveGroupColor`. Returns null when no layer produced a group.
|
|
175
|
+
*/
|
|
176
|
+
export function computeSnap(args: {
|
|
177
|
+
hits: readonly CompiledHitTest<unknown>[];
|
|
178
|
+
cursor: number;
|
|
179
|
+
axis: "x" | "y";
|
|
180
|
+
snap: "nearest-x" | "hover";
|
|
181
|
+
active: HitLayerActive | null;
|
|
182
|
+
}): SnapResult | null {
|
|
183
|
+
const { hits, cursor, axis, snap, active } = args;
|
|
184
|
+
const off = axis === "y" ? 1 : 0;
|
|
185
|
+
// When a hit is active, snap per-row picks to its column so every row reports
|
|
186
|
+
// values at the same column as the dispatched hit (not the raw cursor).
|
|
187
|
+
const snapCursor =
|
|
188
|
+
active !== null ? (active.compiled.positions[active.hitIndex * 2 + off] ?? cursor) : cursor;
|
|
189
|
+
const groups: SnapGroup[] = [];
|
|
190
|
+
for (let layerIdx = 0; layerIdx < hits.length; layerIdx++) {
|
|
191
|
+
collectLayerGroups(hits[layerIdx]!, layerIdx, snapCursor, snap, groups, axis);
|
|
192
|
+
}
|
|
193
|
+
if (groups.length === 0) return null;
|
|
194
|
+
// Resolve the active group: dispatched hit's group wins; else nearest-x rank.
|
|
195
|
+
let activeId: string | null = null;
|
|
196
|
+
if (active !== null) {
|
|
197
|
+
const g = resolveGroupKey(active.compiled, active.hitIndex);
|
|
198
|
+
if (g !== null) {
|
|
199
|
+
const candidateKey = `${active.layerIdx}::${g.key}`;
|
|
200
|
+
if (groups.some((entry) => entry.id === candidateKey)) {
|
|
201
|
+
activeId = candidateKey;
|
|
202
|
+
overrideGroupPick(groups, candidateKey, active.compiled, active.hitIndex);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (activeId === null && snap === "nearest-x") {
|
|
207
|
+
let bestDist = Infinity;
|
|
208
|
+
for (const g of groups) {
|
|
209
|
+
if (g.dist < bestDist) {
|
|
210
|
+
bestDist = g.dist;
|
|
211
|
+
activeId = g.id;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const titleSource = groups.find((g) => g.id === activeId) ?? groups[0]!;
|
|
216
|
+
const columnValue = axis === "y" ? titleSource.yValue : titleSource.xValue;
|
|
217
|
+
return { columnValue, groups, activeId };
|
|
218
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// Unit tests for Phase 2 axis-mode tooltip helpers.
|
|
2
|
+
// Pure / headless — no GPU, no canvas, no timers.
|
|
3
|
+
|
|
4
|
+
import { describe, expect, test } from "vite-plus/test";
|
|
5
|
+
|
|
6
|
+
import type { CompiledHitTest } from "../geoms/types.ts";
|
|
7
|
+
import { themeDefault } from "../theme.ts";
|
|
8
|
+
import type { SnapGroup, SnapResult } from "./series-snap.ts";
|
|
9
|
+
import { __test__ } from "./tooltip.ts";
|
|
10
|
+
|
|
11
|
+
const { buildAxisTooltipContent, hoveredHitFromGroup } = __test__;
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Helpers
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
interface Row {
|
|
18
|
+
name: string;
|
|
19
|
+
x: number;
|
|
20
|
+
y: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function makeHit(data: readonly Row[]): CompiledHitTest<Row> {
|
|
24
|
+
const positions = new Float32Array(data.length * 2);
|
|
25
|
+
for (let i = 0; i < data.length; i++) {
|
|
26
|
+
positions[i * 2] = data[i]!.x;
|
|
27
|
+
positions[i * 2 + 1] = data[i]!.y;
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
geomKind: "point",
|
|
31
|
+
positions,
|
|
32
|
+
dataIndex: Int32Array.from(data.map((_, i) => i)),
|
|
33
|
+
pickRadius: 5,
|
|
34
|
+
channels: {
|
|
35
|
+
x: { kind: "accessor", fn: (d: unknown) => (d as Row).x, column: "x" },
|
|
36
|
+
y: { kind: "accessor", fn: (d: unknown) => (d as Row).y, column: "y" },
|
|
37
|
+
},
|
|
38
|
+
data,
|
|
39
|
+
} as unknown as CompiledHitTest<Row>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function makeGroup(
|
|
43
|
+
id: string,
|
|
44
|
+
label: string,
|
|
45
|
+
index: number,
|
|
46
|
+
xValue: unknown,
|
|
47
|
+
yValue: unknown,
|
|
48
|
+
hit: CompiledHitTest<unknown>,
|
|
49
|
+
activeId: string | null,
|
|
50
|
+
): SnapGroup {
|
|
51
|
+
return {
|
|
52
|
+
id,
|
|
53
|
+
label,
|
|
54
|
+
index,
|
|
55
|
+
dist: 0,
|
|
56
|
+
xValue,
|
|
57
|
+
yValue,
|
|
58
|
+
hit,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Tests
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
describe("buildAxisTooltipContent", () => {
|
|
67
|
+
const data: Row[] = [
|
|
68
|
+
{ name: "a", x: 10, y: 2 },
|
|
69
|
+
{ name: "b", x: 20, y: 6 },
|
|
70
|
+
];
|
|
71
|
+
const hit = makeHit(data) as CompiledHitTest<unknown>;
|
|
72
|
+
|
|
73
|
+
const groups: SnapGroup[] = [
|
|
74
|
+
makeGroup("0::a", "a", 0, 10, 2, hit, "0::a"),
|
|
75
|
+
makeGroup("0::b", "b", 1, 20, 6, hit, "0::a"),
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
const result: SnapResult = {
|
|
79
|
+
columnValue: 10,
|
|
80
|
+
groups,
|
|
81
|
+
activeId: "0::a",
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
test("auto rows axis:'x' — row values come from yValue", () => {
|
|
85
|
+
const content = buildAxisTooltipContent({
|
|
86
|
+
result,
|
|
87
|
+
axis: "x",
|
|
88
|
+
scales: null,
|
|
89
|
+
theme: themeDefault,
|
|
90
|
+
titleFormat: (v) => String(v),
|
|
91
|
+
valueFormat: (v) => String(v),
|
|
92
|
+
});
|
|
93
|
+
expect(content.title).toBe(String(result.columnValue));
|
|
94
|
+
expect(content.rows).toHaveLength(2);
|
|
95
|
+
// axis:"x" → value is yValue
|
|
96
|
+
expect(content.rows[0]!.value).toBe("2");
|
|
97
|
+
expect(content.rows[1]!.value).toBe("6");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("axis:'y' — row values come from xValue", () => {
|
|
101
|
+
const content = buildAxisTooltipContent({
|
|
102
|
+
result,
|
|
103
|
+
axis: "y",
|
|
104
|
+
scales: null,
|
|
105
|
+
theme: themeDefault,
|
|
106
|
+
titleFormat: (v) => String(v),
|
|
107
|
+
valueFormat: (v) => String(v),
|
|
108
|
+
});
|
|
109
|
+
expect(content.rows).toHaveLength(2);
|
|
110
|
+
// axis:"y" → value is xValue
|
|
111
|
+
expect(content.rows[0]!.value).toBe("10");
|
|
112
|
+
expect(content.rows[1]!.value).toBe("20");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("axisContent override replaces auto rows", () => {
|
|
116
|
+
let capturedInfo: import("../chart.ts").AxisTooltipInfo | null = null;
|
|
117
|
+
const content = buildAxisTooltipContent({
|
|
118
|
+
result,
|
|
119
|
+
axis: "x",
|
|
120
|
+
scales: null,
|
|
121
|
+
theme: themeDefault,
|
|
122
|
+
titleFormat: (v) => String(v),
|
|
123
|
+
valueFormat: (v) => String(v),
|
|
124
|
+
axisContent: (info) => {
|
|
125
|
+
capturedInfo = info;
|
|
126
|
+
return { title: "custom", rows: [{ label: "z", value: "99" }] };
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
expect(content.title).toBe("custom");
|
|
130
|
+
expect(content.rows).toHaveLength(1);
|
|
131
|
+
expect(content.rows[0]!.value).toBe("99");
|
|
132
|
+
// The info passed to axisContent should have auto rows
|
|
133
|
+
expect(capturedInfo).not.toBeNull();
|
|
134
|
+
expect(capturedInfo!.rows).toHaveLength(2);
|
|
135
|
+
expect(capturedInfo!.data).toHaveLength(2);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe("hoveredHitFromGroup", () => {
|
|
140
|
+
test("returns correct HoveredHit fields from group", () => {
|
|
141
|
+
const data: Row[] = [{ name: "p", x: 5, y: 15 }];
|
|
142
|
+
const hit = makeHit(data) as CompiledHitTest<unknown>;
|
|
143
|
+
const g: SnapGroup = {
|
|
144
|
+
id: "0::default",
|
|
145
|
+
label: "p",
|
|
146
|
+
index: 0,
|
|
147
|
+
dist: 0,
|
|
148
|
+
xValue: 5,
|
|
149
|
+
yValue: 15,
|
|
150
|
+
hit,
|
|
151
|
+
};
|
|
152
|
+
const hovered = hoveredHitFromGroup(g);
|
|
153
|
+
expect(hovered.geomKind).toBe("point");
|
|
154
|
+
expect(hovered.dataIndex).toBe(0);
|
|
155
|
+
expect(hovered.data).toBe(data);
|
|
156
|
+
expect(hovered.x).toBe(5); // positions[0*2] = data[0].x = 5
|
|
157
|
+
expect(hovered.y).toBe(15); // positions[0*2+1] = data[0].y = 15
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("seriesKey is forwarded when present", () => {
|
|
161
|
+
const data: Row[] = [{ name: "q", x: 7, y: 3 }];
|
|
162
|
+
const hit = makeHit(data) as CompiledHitTest<unknown>;
|
|
163
|
+
const g: SnapGroup = {
|
|
164
|
+
id: "0::s1",
|
|
165
|
+
label: "s1",
|
|
166
|
+
index: 0,
|
|
167
|
+
dist: 0,
|
|
168
|
+
xValue: 7,
|
|
169
|
+
yValue: 3,
|
|
170
|
+
seriesKey: "s1",
|
|
171
|
+
hit,
|
|
172
|
+
};
|
|
173
|
+
const hovered = hoveredHitFromGroup(g);
|
|
174
|
+
expect(hovered.seriesKey).toBe("s1");
|
|
175
|
+
});
|
|
176
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// GPU-manual
|
|
2
|
+
//
|
|
3
|
+
// Conceptual browser test: mount a minimal chart, fire a touch tap (pointerdown
|
|
4
|
+
// + pointerup at a hit position), and assert the tooltip becomes visible.
|
|
5
|
+
//
|
|
6
|
+
// Run with: vp test -c vite.browser-test.config.ts packages/plot/src/grammar/interactions/tooltip-touch.browser.test.ts
|
|
7
|
+
//
|
|
8
|
+
// This test is intentionally kept minimal — it documents the intended
|
|
9
|
+
// acceptance check for P1-A and is expected to pass once the browser harness
|
|
10
|
+
// is available. It does not run in the standard unit-test config.
|
|
11
|
+
//
|
|
12
|
+
// The sketch below is structurally correct but is NOT currently executed in CI
|
|
13
|
+
// (the browser GPU config is manual-only per project convention).
|
|
14
|
+
|
|
15
|
+
import { describe, test } from "vite-plus/test";
|
|
16
|
+
|
|
17
|
+
describe("tooltip touch-tap (browser / GPU)", () => {
|
|
18
|
+
test.todo(
|
|
19
|
+
"mounting a chart and firing pointerdown+pointerup on a mark makes the tooltip visible",
|
|
20
|
+
);
|
|
21
|
+
// Outline of the full test when the browser harness is active:
|
|
22
|
+
//
|
|
23
|
+
// const canvas = document.createElement("canvas");
|
|
24
|
+
// canvas.width = 400; canvas.height = 300;
|
|
25
|
+
// document.body.appendChild(canvas);
|
|
26
|
+
//
|
|
27
|
+
// const chart = mount(canvas, {
|
|
28
|
+
// data: [{ x: 1, y: 2 }, { x: 2, y: 3 }],
|
|
29
|
+
// geoms: [point({ x: "x", y: "y" })],
|
|
30
|
+
// interactions: { tooltip: true },
|
|
31
|
+
// });
|
|
32
|
+
//
|
|
33
|
+
// // Wait for first frame.
|
|
34
|
+
// await chart.ready();
|
|
35
|
+
//
|
|
36
|
+
// // Simulate a touch tap at the first mark's canvas position.
|
|
37
|
+
// const { x, y } = chart.positionOf(0);
|
|
38
|
+
// canvas.dispatchEvent(new PointerEvent("pointerdown", { pointerId: 1, pointerType: "touch", clientX: x, clientY: y, bubbles: true }));
|
|
39
|
+
// canvas.dispatchEvent(new PointerEvent("pointerup", { pointerId: 1, pointerType: "touch", clientX: x, clientY: y, bubbles: true }));
|
|
40
|
+
//
|
|
41
|
+
// await chart.nextFrame();
|
|
42
|
+
//
|
|
43
|
+
// // Tooltip must now be visible with the correct data point.
|
|
44
|
+
// expect(chart.tooltipVisible()).toBe(true);
|
|
45
|
+
// expect(chart.hovered()?.dataIndex).toBe(0);
|
|
46
|
+
//
|
|
47
|
+
// chart.dispose();
|
|
48
|
+
// canvas.remove();
|
|
49
|
+
});
|