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,85 @@
|
|
|
1
|
+
import type { Color } from "insomni";
|
|
2
|
+
import { type KdeBandwidth, type KdeKernel, type QuantileMethod, type WhiskerRule } from "../../stats/index.ts";
|
|
3
|
+
import type { Aes } from "../aes.ts";
|
|
4
|
+
import type { Geom } from "./types.ts";
|
|
5
|
+
import type { PointsMode } from "./boxplot.ts";
|
|
6
|
+
import { type DensityScale } from "./_distribution.ts";
|
|
7
|
+
export interface ViolinChannels<T> {
|
|
8
|
+
x: Aes<T, string | number | Date>;
|
|
9
|
+
/**
|
|
10
|
+
* Numeric value axis when vertical. For horizontal layouts
|
|
11
|
+
* (`orientation: "x"`) this carries the band category, so a string is
|
|
12
|
+
* accepted; the value axis then lives on `x`.
|
|
13
|
+
*/
|
|
14
|
+
y: Aes<T, string | number | Date>;
|
|
15
|
+
color?: Aes<T, unknown>;
|
|
16
|
+
}
|
|
17
|
+
export type ViolinScale = DensityScale;
|
|
18
|
+
export type ViolinInner = "box" | "quartile" | "stick" | "none";
|
|
19
|
+
export interface ViolinOptions {
|
|
20
|
+
orientation?: "x" | "y";
|
|
21
|
+
/** Violin width as a fraction of the bandwidth (or grouped inner bandwidth). Default `0.9`. */
|
|
22
|
+
width?: number;
|
|
23
|
+
/** KDE bandwidth — number, `"silverman"` (default), or `"scott"`. */
|
|
24
|
+
bandwidth?: KdeBandwidth;
|
|
25
|
+
/** KDE evaluation grid size. Default `64`. */
|
|
26
|
+
gridSize?: number;
|
|
27
|
+
/** KDE kernel. Default `"gaussian"`. */
|
|
28
|
+
kernel?: KdeKernel;
|
|
29
|
+
/** Clip KDE to data range (true, default) or pad outward by ~3 bandwidths. */
|
|
30
|
+
trim?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Width-normalization mode.
|
|
33
|
+
*
|
|
34
|
+
* - `"width"` (default) — each violin uses its full allotted width; shapes
|
|
35
|
+
* are easy to compare but **areas are misleading**.
|
|
36
|
+
* - `"area"` — density scaled relative to the global max so areas are
|
|
37
|
+
* comparable across groups (groups with low n look skinny).
|
|
38
|
+
* - `"count"` — area scales with sample size (bigger n → wider violin).
|
|
39
|
+
*/
|
|
40
|
+
scale?: ViolinScale;
|
|
41
|
+
/**
|
|
42
|
+
* Inner annotation drawn on top of the violin.
|
|
43
|
+
*
|
|
44
|
+
* - `"none"` (default) — bare KDE polygon, like ggplot's `geom_violin`.
|
|
45
|
+
* - `"box"` — slim box plot with median, IQR, whiskers.
|
|
46
|
+
* - `"quartile"` — three horizontal/vertical lines at Q1, median, Q3.
|
|
47
|
+
* - `"stick"` — single line at the median only.
|
|
48
|
+
*/
|
|
49
|
+
inner?: ViolinInner;
|
|
50
|
+
/** When to overlay raw jittered points. See `BoxplotOptions["points"]`. */
|
|
51
|
+
points?: PointsMode;
|
|
52
|
+
pointsThreshold?: number;
|
|
53
|
+
pointJitter?: number;
|
|
54
|
+
pointRadius?: number;
|
|
55
|
+
jitterSeed?: number;
|
|
56
|
+
/** Box stats: whisker rule when `inner: "box"`. Default `1.5`. */
|
|
57
|
+
whisker?: WhiskerRule;
|
|
58
|
+
/** Box stats: quantile method. Default `"type-7"`. */
|
|
59
|
+
quantile?: QuantileMethod;
|
|
60
|
+
fill?: Color;
|
|
61
|
+
stroke?: Color;
|
|
62
|
+
strokeWidth?: number;
|
|
63
|
+
/** Color of inner annotation marks. Defaults to `stroke` (or theme text). */
|
|
64
|
+
innerStroke?: Color;
|
|
65
|
+
/** Stroke width for the embedded mini-box body. Default `1`. */
|
|
66
|
+
innerStrokeWidth?: number;
|
|
67
|
+
/** Inner-band padding for grouped (color-split) layout. Default `0.05`. */
|
|
68
|
+
groupPadding?: number;
|
|
69
|
+
/**
|
|
70
|
+
* Render an `n=<count>` label per group, aligned with the band axis just
|
|
71
|
+
* outside the plot frame. Useful when the categorical labels alone hide
|
|
72
|
+
* sample-size differences.
|
|
73
|
+
*/
|
|
74
|
+
showCounts?: boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Pixel offset of the count labels from the plot frame edge along the
|
|
77
|
+
* band-axis perpendicular. Increase to clear longer tick labels. Default
|
|
78
|
+
* `28` (vertical) / `32` (horizontal).
|
|
79
|
+
*/
|
|
80
|
+
countsOffset?: number;
|
|
81
|
+
countsFontSize?: number;
|
|
82
|
+
countsColor?: Color;
|
|
83
|
+
label?: string;
|
|
84
|
+
}
|
|
85
|
+
export declare function violin<T>(channels: ViolinChannels<T>, options?: ViolinOptions): Geom<T>;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { createFrame } from "insomni";
|
|
3
|
+
import { describe, expect, test } from "vite-plus/test";
|
|
4
|
+
import { plot } from "../chart.ts";
|
|
5
|
+
import { resolveAes } from "../aes.ts";
|
|
6
|
+
import { buildPositionScale, type ScaleBundle } from "../scales.ts";
|
|
7
|
+
import { themeDefault } from "../theme.ts";
|
|
8
|
+
import { violin } from "./violin.ts";
|
|
9
|
+
import { emphasisKeyFor, geomEmphasisBase } from "./emphasis.ts";
|
|
10
|
+
import type { CompileContext } from "./types.ts";
|
|
11
|
+
|
|
12
|
+
// Fake Layer recording the emphasisKey of every primitive a violin composites
|
|
13
|
+
// (KDE polygon + outline polyline + inner box rect/polylines + point overlay).
|
|
14
|
+
function makeKeyCapture() {
|
|
15
|
+
const keys: (number | undefined)[] = [];
|
|
16
|
+
const record = (s: { emphasisKey?: number }) => keys.push(s.emphasisKey);
|
|
17
|
+
const layer = {
|
|
18
|
+
pushRect: record,
|
|
19
|
+
pushCircle: record,
|
|
20
|
+
pushEllipse: record,
|
|
21
|
+
pushSegment: record,
|
|
22
|
+
pushLine: record,
|
|
23
|
+
pushPolyline: record,
|
|
24
|
+
pushPolygon: record,
|
|
25
|
+
pushText: () => layer,
|
|
26
|
+
pushString: () => layer,
|
|
27
|
+
};
|
|
28
|
+
return { layer, keys };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface Obs {
|
|
32
|
+
group: string;
|
|
33
|
+
value: number;
|
|
34
|
+
color?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ============ Fixtures ============
|
|
38
|
+
const fixture = {
|
|
39
|
+
threeGroups: (): Obs[] => {
|
|
40
|
+
const out: Obs[] = [];
|
|
41
|
+
const seeds = { A: 0.1, B: 0.5, C: 0.9 };
|
|
42
|
+
let r = 0;
|
|
43
|
+
const rand = () => {
|
|
44
|
+
r = (r * 9301 + 49297) % 233280;
|
|
45
|
+
return r / 233280;
|
|
46
|
+
};
|
|
47
|
+
for (const [name, mu] of Object.entries(seeds)) {
|
|
48
|
+
for (let i = 0; i < 50; i++) {
|
|
49
|
+
out.push({ group: name, value: mu + (rand() - 0.5) * 0.3 });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
},
|
|
54
|
+
bimodal: (): Obs[] => {
|
|
55
|
+
const out: Obs[] = [];
|
|
56
|
+
for (let i = 0; i < 30; i++) out.push({ group: "A", value: 0.2 + i * 0.005 });
|
|
57
|
+
for (let i = 0; i < 30; i++) out.push({ group: "A", value: 0.8 + i * 0.005 });
|
|
58
|
+
return out;
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// ============ Tests ============
|
|
63
|
+
describe("violin()", () => {
|
|
64
|
+
test("renders a polygon per group to SVG", () => {
|
|
65
|
+
const svg = plot({ data: fixture.threeGroups(), width: 480, height: 320 })
|
|
66
|
+
.layer(violin({ x: "group", y: "value" }))
|
|
67
|
+
.toSVG();
|
|
68
|
+
expect(svg).toBeInstanceOf(SVGSVGElement);
|
|
69
|
+
// SVG renderer tessellates filled polygons into <path> elements.
|
|
70
|
+
const polygons = svg.querySelectorAll("polygon");
|
|
71
|
+
expect(polygons.length).toBeGreaterThan(0);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("respects gridSize via point-count of polygon (2 * gridSize vertices)", () => {
|
|
75
|
+
// We don't have a direct vertex-count probe through SVG, but we can verify
|
|
76
|
+
// it doesn't crash on small grids and renders non-empty output.
|
|
77
|
+
const svg = plot({ data: fixture.bimodal(), width: 320, height: 240 })
|
|
78
|
+
.layer(violin({ x: "group", y: "value" }, { gridSize: 16 }))
|
|
79
|
+
.toSVG();
|
|
80
|
+
expect(svg.querySelectorAll("polygon").length).toBeGreaterThan(0);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("'inner: none' suppresses the embedded box-and-stick", () => {
|
|
84
|
+
const svgWith = plot({ data: fixture.threeGroups(), width: 320, height: 240 })
|
|
85
|
+
.layer(violin({ x: "group", y: "value" }, { inner: "box", points: "none" }))
|
|
86
|
+
.toSVG();
|
|
87
|
+
const svgNone = plot({ data: fixture.threeGroups(), width: 320, height: 240 })
|
|
88
|
+
.layer(violin({ x: "group", y: "value" }, { inner: "none", points: "none" }))
|
|
89
|
+
.toSVG();
|
|
90
|
+
// Inner "box" mode draws additional <rect> + <line> elements.
|
|
91
|
+
const withRects = svgWith.querySelectorAll("rect").length;
|
|
92
|
+
const noneRects = svgNone.querySelectorAll("rect").length;
|
|
93
|
+
expect(withRects).toBeGreaterThan(noneRects);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("does not throw on degenerate samples (n=1, all-equal)", () => {
|
|
97
|
+
expect(() =>
|
|
98
|
+
plot({ data: [{ group: "A", value: 5 }] as Obs[] })
|
|
99
|
+
.layer(violin({ x: "group", y: "value" }))
|
|
100
|
+
.toSVG({ width: 200, height: 200 }),
|
|
101
|
+
).not.toThrow();
|
|
102
|
+
expect(() =>
|
|
103
|
+
plot({ data: [3, 3, 3, 3].map((v) => ({ group: "A", value: v })) as Obs[] })
|
|
104
|
+
.layer(violin({ x: "group", y: "value" }))
|
|
105
|
+
.toSVG({ width: 200, height: 200 }),
|
|
106
|
+
).not.toThrow();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("compileHitTest emits one hit per non-empty bucket at (bandCenter, median)", () => {
|
|
110
|
+
const data = fixture.threeGroups();
|
|
111
|
+
const xAes = resolveAes<Obs, unknown>("group");
|
|
112
|
+
const yAes = resolveAes<Obs, unknown>("value");
|
|
113
|
+
const xScale = buildPositionScale(xAes, data, [0, 300], { type: "band", padding: 0 });
|
|
114
|
+
const yScale = buildPositionScale(yAes, data, [200, 0]);
|
|
115
|
+
const scales: ScaleBundle = { x: xScale, y: yScale };
|
|
116
|
+
const ctx: CompileContext<Obs> = {
|
|
117
|
+
data,
|
|
118
|
+
scales,
|
|
119
|
+
plot: createFrame({ x: 50, y: 30, width: 300, height: 200 }),
|
|
120
|
+
theme: themeDefault,
|
|
121
|
+
atlas: undefined,
|
|
122
|
+
};
|
|
123
|
+
const geom = violin<Obs>({ x: "group", y: "value" });
|
|
124
|
+
const hits = geom.compileHitTest!(ctx)!;
|
|
125
|
+
expect(hits.geomKind).toBe("violin");
|
|
126
|
+
expect(hits.dataIndex.length).toBe(3);
|
|
127
|
+
expect(new Set(hits.seriesKey)).toEqual(new Set(["A", "B", "C"]));
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("scale: 'count' / 'area' / 'width' all render", () => {
|
|
131
|
+
for (const mode of ["width", "area", "count"] as const) {
|
|
132
|
+
const svg = plot({ data: fixture.threeGroups(), width: 400, height: 300 })
|
|
133
|
+
.layer(violin({ x: "group", y: "value" }, { scale: mode }))
|
|
134
|
+
.toSVG();
|
|
135
|
+
expect(svg).toBeInstanceOf(SVGSVGElement);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe("violin() — GPU hover emphasis (P5-T3 Gap 1)", () => {
|
|
141
|
+
function makeCtx(data: Obs[], emphasis = true): CompileContext<Obs> {
|
|
142
|
+
const xAes = resolveAes<Obs, unknown>("group");
|
|
143
|
+
const yAes = resolveAes<Obs, unknown>("value");
|
|
144
|
+
const xScale = buildPositionScale(xAes, data, [0, 300], { type: "band", padding: 0 });
|
|
145
|
+
const yScale = buildPositionScale(yAes, data, [200, 0]);
|
|
146
|
+
const scales: ScaleBundle = { x: xScale, y: yScale };
|
|
147
|
+
return {
|
|
148
|
+
data,
|
|
149
|
+
scales,
|
|
150
|
+
plot: createFrame({ x: 50, y: 30, width: 300, height: 200 }),
|
|
151
|
+
theme: themeDefault,
|
|
152
|
+
atlas: undefined,
|
|
153
|
+
emphasisBase: emphasis ? geomEmphasisBase(0) : undefined,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
test("tagging present + every primitive of a single violin shares ONE key", () => {
|
|
158
|
+
const data = fixture.bimodal(); // one bucket "A"
|
|
159
|
+
const geom = violin<Obs>({ x: "group", y: "value" }, { inner: "box", points: "always" });
|
|
160
|
+
const { layer, keys } = makeKeyCapture();
|
|
161
|
+
for (const b of geom.compile(makeCtx(data))) b.addTo(layer as never);
|
|
162
|
+
const nonzero = keys.filter((k): k is number => k !== undefined && k >= 1);
|
|
163
|
+
expect(nonzero.length).toBeGreaterThan(0);
|
|
164
|
+
const expected = emphasisKeyFor(geomEmphasisBase(0), 0);
|
|
165
|
+
expect(new Set(nonzero)).toEqual(new Set([expected]));
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("emphasisResolution maps a hit's dataIndex (bucketIndex) to the tagged key", () => {
|
|
169
|
+
const data = fixture.threeGroups();
|
|
170
|
+
const geom = violin<Obs>({ x: "group", y: "value" });
|
|
171
|
+
const res = geom.emphasisResolution!(makeCtx(data))!;
|
|
172
|
+
expect(res.geomKind).toBe("violin");
|
|
173
|
+
expect(res.resolve({ geomKind: "violin", dataIndex: 1, data, x: 0, y: 0 })).toBe(
|
|
174
|
+
emphasisKeyFor(geomEmphasisBase(0), 1),
|
|
175
|
+
);
|
|
176
|
+
expect(res.resolve({ geomKind: "boxplot", dataIndex: 0, data, x: 0, y: 0 })).toBeNull();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("no emphasisBase (SSR/SVG) → violins untagged, no resolver key", () => {
|
|
180
|
+
const data = fixture.threeGroups();
|
|
181
|
+
const geom = violin<Obs>({ x: "group", y: "value" });
|
|
182
|
+
const { layer, keys } = makeKeyCapture();
|
|
183
|
+
for (const b of geom.compile(makeCtx(data, false))) b.addTo(layer as never);
|
|
184
|
+
expect(keys.every((k) => k === undefined)).toBe(true);
|
|
185
|
+
expect(geom.emphasisResolution!(makeCtx(data, false))).toBeNull();
|
|
186
|
+
});
|
|
187
|
+
});
|