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,63 @@
|
|
|
1
|
+
import { type Color, type Frame, type Layer } from "insomni";
|
|
2
|
+
import type { Aes } from "./aes.ts";
|
|
3
|
+
import type { Theme } from "./theme.ts";
|
|
4
|
+
export type FacetScales = "fixed" | "free";
|
|
5
|
+
export interface FacetStripStyle {
|
|
6
|
+
height?: number;
|
|
7
|
+
fontSize?: number;
|
|
8
|
+
color?: Color;
|
|
9
|
+
background?: Color;
|
|
10
|
+
}
|
|
11
|
+
export interface FacetSpec<T> {
|
|
12
|
+
/** Accessor / column name producing the facet key per datum. */
|
|
13
|
+
by: Aes<T, unknown>;
|
|
14
|
+
/** Number of columns. Default: ceil(sqrt(n)). */
|
|
15
|
+
ncol?: number;
|
|
16
|
+
/** Number of rows. Default: ceil(n / ncol). */
|
|
17
|
+
nrow?: number;
|
|
18
|
+
/**
|
|
19
|
+
* Scale handling across panels.
|
|
20
|
+
* - `"fixed"` (default) — every panel shares the same x/y/color/… scales.
|
|
21
|
+
* - `"free"` — each panel computes its own scales from its data.
|
|
22
|
+
*/
|
|
23
|
+
scales?: FacetScales;
|
|
24
|
+
/** Spacing between panels in pixels. Default `12`. */
|
|
25
|
+
gap?: number;
|
|
26
|
+
/** Strip header style. */
|
|
27
|
+
strip?: FacetStripStyle;
|
|
28
|
+
/** Optional formatter for the strip label. */
|
|
29
|
+
format?: (key: unknown) => string;
|
|
30
|
+
}
|
|
31
|
+
export interface FacetPanel<T> {
|
|
32
|
+
key: unknown;
|
|
33
|
+
rows: T[];
|
|
34
|
+
/** Index into the column-major grid. */
|
|
35
|
+
col: number;
|
|
36
|
+
row: number;
|
|
37
|
+
/** Sub-frame within the chart's plot frame. */
|
|
38
|
+
frame: Frame;
|
|
39
|
+
/** True for the leftmost column — render y-axis only here. */
|
|
40
|
+
isLeftCol: boolean;
|
|
41
|
+
/** True for the bottom row of *populated* panels — render x-axis only here. */
|
|
42
|
+
isBottomRow: boolean;
|
|
43
|
+
}
|
|
44
|
+
export interface FacetLayout<T> {
|
|
45
|
+
panels: FacetPanel<T>[];
|
|
46
|
+
ncol: number;
|
|
47
|
+
nrow: number;
|
|
48
|
+
stripHeight: number;
|
|
49
|
+
}
|
|
50
|
+
export declare function groupForFacet<T>(spec: FacetSpec<T>, data: readonly T[]): {
|
|
51
|
+
key: unknown;
|
|
52
|
+
rows: T[];
|
|
53
|
+
}[];
|
|
54
|
+
/**
|
|
55
|
+
* Slice the chart's plot frame into a grid of panel sub-frames. Each sub-frame
|
|
56
|
+
* contains a strip header at the top and a panel body underneath.
|
|
57
|
+
*/
|
|
58
|
+
export declare function computeFacetLayout<T>(spec: FacetSpec<T>, groups: readonly {
|
|
59
|
+
key: unknown;
|
|
60
|
+
rows: T[];
|
|
61
|
+
}[], plot: Frame): FacetLayout<T>;
|
|
62
|
+
/** Draw a strip header above a panel frame. */
|
|
63
|
+
export declare function renderFacetStrip(layer: Layer, panel: FacetPanel<unknown>, spec: FacetSpec<unknown>, theme: Theme): void;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, expect, test } from "vite-plus/test";
|
|
2
|
+
import { viewportFrame } from "insomni";
|
|
3
|
+
|
|
4
|
+
import { computeFacetLayout, groupForFacet, type FacetSpec } from "./facet.ts";
|
|
5
|
+
|
|
6
|
+
interface Row {
|
|
7
|
+
group: string;
|
|
8
|
+
v: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const DATA: Row[] = [
|
|
12
|
+
{ group: "a", v: 1 },
|
|
13
|
+
{ group: "b", v: 2 },
|
|
14
|
+
{ group: "a", v: 3 },
|
|
15
|
+
{ group: "c", v: 4 },
|
|
16
|
+
{ group: "b", v: 5 },
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
describe("facet", () => {
|
|
20
|
+
test("groupForFacet preserves first-occurrence order", () => {
|
|
21
|
+
const groups = groupForFacet({ by: "group" }, DATA);
|
|
22
|
+
expect(groups.map((g) => g.key)).toEqual(["a", "b", "c"]);
|
|
23
|
+
expect(groups[0]!.rows).toHaveLength(2);
|
|
24
|
+
expect(groups[1]!.rows).toHaveLength(2);
|
|
25
|
+
expect(groups[2]!.rows).toHaveLength(1);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("computeFacetLayout splits the plot frame into ncol×nrow panels", () => {
|
|
29
|
+
const groups = groupForFacet({ by: "group" }, DATA);
|
|
30
|
+
const plot = viewportFrame(400, 300);
|
|
31
|
+
const spec: FacetSpec<Row> = { by: "group", ncol: 2, gap: 10, scales: "fixed" };
|
|
32
|
+
const layout = computeFacetLayout(spec, groups, plot);
|
|
33
|
+
expect(layout.ncol).toBe(2);
|
|
34
|
+
expect(layout.nrow).toBe(2);
|
|
35
|
+
expect(layout.panels).toHaveLength(3);
|
|
36
|
+
|
|
37
|
+
// Expected panel sizes — the gap takes 10px per inner row/col, strip eats
|
|
38
|
+
// the top of every panel.
|
|
39
|
+
const panelW = (400 - 10) / 2;
|
|
40
|
+
expect(layout.panels[0]!.frame.width).toBeCloseTo(panelW, 5);
|
|
41
|
+
expect(layout.panels[0]!.frame.x).toBeCloseTo(0, 5);
|
|
42
|
+
expect(layout.panels[1]!.frame.x).toBeCloseTo(panelW + 10, 5);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("isBottomRow flags the last populated row in each column", () => {
|
|
46
|
+
// Five panels in a 2-col grid → row 2 has only column 0.
|
|
47
|
+
const data = ["a", "b", "c", "d", "e"].map((k) => ({ group: k, v: 0 }));
|
|
48
|
+
const groups = groupForFacet({ by: "group" }, data);
|
|
49
|
+
const plot = viewportFrame(400, 300);
|
|
50
|
+
const layout = computeFacetLayout({ by: "group", ncol: 2 }, groups, plot);
|
|
51
|
+
const flags = layout.panels.map((p) => p.isBottomRow);
|
|
52
|
+
// Column 0's bottom panel is index 4 (row 2). Column 1's bottom panel is
|
|
53
|
+
// index 3 (row 1).
|
|
54
|
+
expect(flags[0]).toBe(false);
|
|
55
|
+
expect(flags[1]).toBe(false);
|
|
56
|
+
expect(flags[2]).toBe(false);
|
|
57
|
+
expect(flags[3]).toBe(true);
|
|
58
|
+
expect(flags[4]).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Faceting — split data into a grid of panels
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// `chart.facet({ by })` produces one panel per unique value of `by`. Panels
|
|
5
|
+
// are arranged in `ncol × nrow` (defaulting to a near-square grid) and share
|
|
6
|
+
// scales by default. Each panel reuses the chart's geoms; only data and the
|
|
7
|
+
// containing sub-frame change between panels.
|
|
8
|
+
|
|
9
|
+
import { type Color, type Frame, type Layer, viewportFrame } from "insomni";
|
|
10
|
+
import type { Aes } from "./aes.ts";
|
|
11
|
+
import { resolveAes } from "./aes.ts";
|
|
12
|
+
import type { Theme } from "./theme.ts";
|
|
13
|
+
import {
|
|
14
|
+
DEFAULT_FACET_GAP,
|
|
15
|
+
DEFAULT_FACET_STRIP_FONT_SIZE,
|
|
16
|
+
DEFAULT_FACET_STRIP_HEIGHT,
|
|
17
|
+
} from "./constants.ts";
|
|
18
|
+
|
|
19
|
+
export type FacetScales = "fixed" | "free";
|
|
20
|
+
|
|
21
|
+
export interface FacetStripStyle {
|
|
22
|
+
height?: number;
|
|
23
|
+
fontSize?: number;
|
|
24
|
+
color?: Color;
|
|
25
|
+
background?: Color;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface FacetSpec<T> {
|
|
29
|
+
/** Accessor / column name producing the facet key per datum. */
|
|
30
|
+
by: Aes<T, unknown>;
|
|
31
|
+
/** Number of columns. Default: ceil(sqrt(n)). */
|
|
32
|
+
ncol?: number;
|
|
33
|
+
/** Number of rows. Default: ceil(n / ncol). */
|
|
34
|
+
nrow?: number;
|
|
35
|
+
/**
|
|
36
|
+
* Scale handling across panels.
|
|
37
|
+
* - `"fixed"` (default) — every panel shares the same x/y/color/… scales.
|
|
38
|
+
* - `"free"` — each panel computes its own scales from its data.
|
|
39
|
+
*/
|
|
40
|
+
scales?: FacetScales;
|
|
41
|
+
/** Spacing between panels in pixels. Default `12`. */
|
|
42
|
+
gap?: number;
|
|
43
|
+
/** Strip header style. */
|
|
44
|
+
strip?: FacetStripStyle;
|
|
45
|
+
/** Optional formatter for the strip label. */
|
|
46
|
+
format?: (key: unknown) => string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface FacetPanel<T> {
|
|
50
|
+
key: unknown;
|
|
51
|
+
rows: T[];
|
|
52
|
+
/** Index into the column-major grid. */
|
|
53
|
+
col: number;
|
|
54
|
+
row: number;
|
|
55
|
+
/** Sub-frame within the chart's plot frame. */
|
|
56
|
+
frame: Frame;
|
|
57
|
+
/** True for the leftmost column — render y-axis only here. */
|
|
58
|
+
isLeftCol: boolean;
|
|
59
|
+
/** True for the bottom row of *populated* panels — render x-axis only here. */
|
|
60
|
+
isBottomRow: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface FacetLayout<T> {
|
|
64
|
+
panels: FacetPanel<T>[];
|
|
65
|
+
ncol: number;
|
|
66
|
+
nrow: number;
|
|
67
|
+
stripHeight: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function groupForFacet<T>(
|
|
71
|
+
spec: FacetSpec<T>,
|
|
72
|
+
data: readonly T[],
|
|
73
|
+
): { key: unknown; rows: T[] }[] {
|
|
74
|
+
const aes = resolveAes<T, unknown>(spec.by);
|
|
75
|
+
const keyOrder: unknown[] = [];
|
|
76
|
+
const buckets = new Map<unknown, T[]>();
|
|
77
|
+
for (let i = 0; i < data.length; i++) {
|
|
78
|
+
const datum = data[i]!;
|
|
79
|
+
const k = aes.fn(datum, i);
|
|
80
|
+
let bucket = buckets.get(k);
|
|
81
|
+
if (!bucket) {
|
|
82
|
+
bucket = [];
|
|
83
|
+
buckets.set(k, bucket);
|
|
84
|
+
keyOrder.push(k);
|
|
85
|
+
}
|
|
86
|
+
bucket.push(datum);
|
|
87
|
+
}
|
|
88
|
+
return keyOrder.map((key) => ({ key, rows: buckets.get(key)! }));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Slice the chart's plot frame into a grid of panel sub-frames. Each sub-frame
|
|
93
|
+
* contains a strip header at the top and a panel body underneath.
|
|
94
|
+
*/
|
|
95
|
+
export function computeFacetLayout<T>(
|
|
96
|
+
spec: FacetSpec<T>,
|
|
97
|
+
groups: readonly { key: unknown; rows: T[] }[],
|
|
98
|
+
plot: Frame,
|
|
99
|
+
): FacetLayout<T> {
|
|
100
|
+
const n = groups.length;
|
|
101
|
+
const ncol = Math.max(1, spec.ncol ?? Math.ceil(Math.sqrt(n)));
|
|
102
|
+
const nrow = Math.max(1, spec.nrow ?? Math.ceil(n / ncol));
|
|
103
|
+
const gap = spec.gap ?? DEFAULT_FACET_GAP;
|
|
104
|
+
const stripHeight = spec.strip?.height ?? DEFAULT_FACET_STRIP_HEIGHT;
|
|
105
|
+
|
|
106
|
+
const panelWidth = (plot.width - gap * (ncol - 1)) / ncol;
|
|
107
|
+
const panelHeight = (plot.height - gap * (nrow - 1)) / nrow;
|
|
108
|
+
|
|
109
|
+
const panels: FacetPanel<T>[] = [];
|
|
110
|
+
// Determine the bottom-row index per column — for an n that doesn't fill
|
|
111
|
+
// the last row, panels in earlier columns still need x-axes drawn.
|
|
112
|
+
const colMaxRow = Array.from({ length: ncol }, () => -1);
|
|
113
|
+
for (let i = 0; i < n; i++) {
|
|
114
|
+
const col = i % ncol;
|
|
115
|
+
const row = Math.floor(i / ncol);
|
|
116
|
+
if (row > colMaxRow[col]!) colMaxRow[col] = row;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
for (let i = 0; i < n; i++) {
|
|
120
|
+
const col = i % ncol;
|
|
121
|
+
const row = Math.floor(i / ncol);
|
|
122
|
+
const x = plot.x + col * (panelWidth + gap);
|
|
123
|
+
const y = plot.y + row * (panelHeight + gap) + stripHeight;
|
|
124
|
+
const frame = viewportFrame(panelWidth, Math.max(0, panelHeight - stripHeight)).translated(
|
|
125
|
+
x,
|
|
126
|
+
y,
|
|
127
|
+
);
|
|
128
|
+
panels.push({
|
|
129
|
+
key: groups[i]!.key,
|
|
130
|
+
rows: groups[i]!.rows,
|
|
131
|
+
col,
|
|
132
|
+
row,
|
|
133
|
+
frame,
|
|
134
|
+
isLeftCol: col === 0,
|
|
135
|
+
isBottomRow: row === colMaxRow[col]!,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return { panels, ncol, nrow, stripHeight };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Draw a strip header above a panel frame. */
|
|
143
|
+
export function renderFacetStrip(
|
|
144
|
+
layer: Layer,
|
|
145
|
+
panel: FacetPanel<unknown>,
|
|
146
|
+
spec: FacetSpec<unknown>,
|
|
147
|
+
theme: Theme,
|
|
148
|
+
): void {
|
|
149
|
+
const stripHeight = spec.strip?.height ?? DEFAULT_FACET_STRIP_HEIGHT;
|
|
150
|
+
const fontSize = spec.strip?.fontSize ?? DEFAULT_FACET_STRIP_FONT_SIZE;
|
|
151
|
+
const color = spec.strip?.color ?? theme.text.color;
|
|
152
|
+
const bg = spec.strip?.background;
|
|
153
|
+
const text = spec.format ? spec.format(panel.key) : String(panel.key);
|
|
154
|
+
const x = panel.frame.x;
|
|
155
|
+
const y = panel.frame.y - stripHeight;
|
|
156
|
+
|
|
157
|
+
if (bg) {
|
|
158
|
+
layer.pushRect({
|
|
159
|
+
x,
|
|
160
|
+
y,
|
|
161
|
+
width: panel.frame.width,
|
|
162
|
+
height: stripHeight,
|
|
163
|
+
fill: bg,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
layer.pushText({
|
|
167
|
+
simple: true,
|
|
168
|
+
text,
|
|
169
|
+
x: x + panel.frame.width / 2,
|
|
170
|
+
y: y + (stripHeight - fontSize) / 2,
|
|
171
|
+
fontSize,
|
|
172
|
+
color,
|
|
173
|
+
align: "center",
|
|
174
|
+
});
|
|
175
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { Color } from "insomni";
|
|
2
|
+
import type { Aes } from "../aes.ts";
|
|
3
|
+
import { type ResolvedAes } from "../aes.ts";
|
|
4
|
+
import { type BandScale, type ContinuousScale, type GroupedBandScale } from "../../scales.ts";
|
|
5
|
+
import type { MarkBuilder } from "../../marks.ts";
|
|
6
|
+
import type { CompileContext } from "./types.ts";
|
|
7
|
+
/** Default inner-band padding for dodged categorical layouts (fraction of band). */
|
|
8
|
+
export declare const DEFAULT_GROUP_PADDING = 0.05;
|
|
9
|
+
export interface CategoricalChannels<T> {
|
|
10
|
+
x: Aes<T, string | number | Date>;
|
|
11
|
+
y: Aes<T, number>;
|
|
12
|
+
color?: Aes<T, unknown>;
|
|
13
|
+
}
|
|
14
|
+
export interface CategoricalLayoutOptions {
|
|
15
|
+
/** Force orientation; defaults to detecting which axis is `band`. */
|
|
16
|
+
orientation?: "x" | "y";
|
|
17
|
+
/** Inner-band padding for grouped (color-split) layout. Default `0.05`. */
|
|
18
|
+
groupPadding?: number;
|
|
19
|
+
/** Fallback fill (used when there's no color channel). */
|
|
20
|
+
fill?: Color;
|
|
21
|
+
}
|
|
22
|
+
export interface CategoricalBucket {
|
|
23
|
+
/** Stringified category key (band-axis value). */
|
|
24
|
+
category: string;
|
|
25
|
+
/** Stringified inner-group key — undefined when not dodging. */
|
|
26
|
+
groupKey: string | undefined;
|
|
27
|
+
/** Numeric samples in this bucket, in input order. */
|
|
28
|
+
values: number[];
|
|
29
|
+
}
|
|
30
|
+
export interface CategoricalLayout {
|
|
31
|
+
orientation: "x" | "y";
|
|
32
|
+
bandAxis: BandScale<string>;
|
|
33
|
+
valueAxis: ContinuousScale;
|
|
34
|
+
/** Present when a color channel splits the band into sub-bands. */
|
|
35
|
+
inner: GroupedBandScale<string, string> | undefined;
|
|
36
|
+
/** Color column != band column (and color column is set). */
|
|
37
|
+
dodging: boolean;
|
|
38
|
+
/** Step on the band axis: inner bandwidth if dodging, else outer bandwidth. */
|
|
39
|
+
cellSize: number;
|
|
40
|
+
/** Buckets in first-encounter order. */
|
|
41
|
+
buckets: CategoricalBucket[];
|
|
42
|
+
/** Resolved fill (theme alpha already applied) for a bucket. */
|
|
43
|
+
resolveFill(bucket: CategoricalBucket): Color;
|
|
44
|
+
/** Plot-frame origin; cached to avoid `ctx.plot.topLeft` re-reads. */
|
|
45
|
+
ox: number;
|
|
46
|
+
oy: number;
|
|
47
|
+
}
|
|
48
|
+
export declare function prepareCategoricalLayout<T>(ctx: CompileContext<T>, channels: CategoricalChannels<T>, options?: CategoricalLayoutOptions): CategoricalLayout;
|
|
49
|
+
/**
|
|
50
|
+
* Position of the band-axis center for a bucket. Returns `NaN` when the
|
|
51
|
+
* category isn't on the scale (caller should skip the bucket).
|
|
52
|
+
*/
|
|
53
|
+
export declare function bucketBandCenter(layout: CategoricalLayout, bucket: CategoricalBucket): number;
|
|
54
|
+
/**
|
|
55
|
+
* Stable per-bucket seed for jitter / random offsets. Same bucket always
|
|
56
|
+
* yields the same value so re-renders don't dance.
|
|
57
|
+
*/
|
|
58
|
+
export declare function bucketSeed(bucket: CategoricalBucket, baseSeed: number): number;
|
|
59
|
+
/**
|
|
60
|
+
* Aggregating geoms (boxplot / violin / histogram / ridgeline) emit one hit
|
|
61
|
+
* per bucket — not per source row — so their `compileHitTest` can't expose
|
|
62
|
+
* the user's column accessors meaningfully. Instead they `synthAes` channel
|
|
63
|
+
* accessors that ignore their datum and return a precomputed per-bucket
|
|
64
|
+
* value keyed by the hit's `dataIndex`. The `column` string flows through
|
|
65
|
+
* to the tooltip's row label.
|
|
66
|
+
*/
|
|
67
|
+
export declare function synthAes<T>(column: string, fn: (datum: T, index: number) => unknown): ResolvedAes<T, unknown>;
|
|
68
|
+
export interface CountsLabelOptions {
|
|
69
|
+
/** Pixel offset from the band-axis edge of the plot frame. */
|
|
70
|
+
offset: number;
|
|
71
|
+
fontSize: number;
|
|
72
|
+
color: Color;
|
|
73
|
+
/** "Sample size" prefix. Default `"n="`. */
|
|
74
|
+
prefix?: string;
|
|
75
|
+
/**
|
|
76
|
+
* Where to anchor the labels:
|
|
77
|
+
*
|
|
78
|
+
* - `"outside"` (default) — outside the plot frame on the band-axis
|
|
79
|
+
* perpendicular. Used by boxplot / violin so labels never collide with
|
|
80
|
+
* the marks.
|
|
81
|
+
* - `"inline"` — at the band-axis center, inside the plot frame at the
|
|
82
|
+
* row baseline (offset by `offset` along the value axis). Used by
|
|
83
|
+
* ridgeline-style row charts where each row owns its baseline.
|
|
84
|
+
*/
|
|
85
|
+
anchor?: "outside" | "inline";
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Build a `MarkBuilder` that draws `n=<count>` labels for each band-axis
|
|
89
|
+
* category, just outside the plot frame on the band-axis perpendicular.
|
|
90
|
+
*
|
|
91
|
+
* Counts are summed across dodge groups so a band with `A: 10` and
|
|
92
|
+
* `B: 15` displays `n=25`.
|
|
93
|
+
*/
|
|
94
|
+
export declare function countsLabelMark(layout: CategoricalLayout, options: CountsLabelOptions, plotWidth: number, plotHeight: number): MarkBuilder;
|
|
Binary file
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type BoxStats, type BoxStatsOptions, type KdeOptions, type KdeResult } from "../../stats/index.ts";
|
|
2
|
+
import type { CategoricalBucket } from "./_categorical.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Compute the value-axis extent across all buckets' KDEs. Used by `violin`
|
|
5
|
+
* and `ridgeline` to extend their value-axis domain so smoothed tails don't
|
|
6
|
+
* render past the plot frame. Returns `null` when no bucket yields a usable
|
|
7
|
+
* KDE (empty input, or all values non-finite).
|
|
8
|
+
*/
|
|
9
|
+
export declare function kdeValueExtent(buckets: Iterable<readonly number[]>, options: KdeOptions): readonly [number, number] | null;
|
|
10
|
+
/** How per-row width relates to density across groups. */
|
|
11
|
+
export type DensityScale = "width" | "area" | "count";
|
|
12
|
+
export interface GroupedKdeResult {
|
|
13
|
+
bucket: CategoricalBucket;
|
|
14
|
+
/** KDE evaluated on a fixed grid (`null` only when bucket has no finite values). */
|
|
15
|
+
kde: KdeResult;
|
|
16
|
+
/** Max raw density on the KDE grid, before any normalization. */
|
|
17
|
+
maxDensity: number;
|
|
18
|
+
/** Sample size that produced the KDE. */
|
|
19
|
+
n: number;
|
|
20
|
+
/** Box stats for the same sample — used by inner annotations. */
|
|
21
|
+
stats: BoxStats;
|
|
22
|
+
}
|
|
23
|
+
export interface ComputeGroupedKdeOptions {
|
|
24
|
+
kde: KdeOptions;
|
|
25
|
+
box: BoxStatsOptions;
|
|
26
|
+
/**
|
|
27
|
+
* Width-scaling mode. Affects only `globalMaxDensity`, which is then folded
|
|
28
|
+
* into `densityWidthFn`. The per-bucket `maxDensity` stays raw so callers
|
|
29
|
+
* that need it (e.g. for `width` mode) get an unscaled reference.
|
|
30
|
+
*/
|
|
31
|
+
scale: DensityScale;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Compute a KDE + box stats for each bucket. Buckets that yield no usable KDE
|
|
35
|
+
* (zero finite samples) are dropped from the output. Returns the global max
|
|
36
|
+
* density used for cross-group `area` / `count` normalization.
|
|
37
|
+
*/
|
|
38
|
+
export declare function computeGroupedKde(buckets: readonly CategoricalBucket[], options: ComputeGroupedKdeOptions): {
|
|
39
|
+
groups: GroupedKdeResult[];
|
|
40
|
+
globalMaxDensity: number;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Build the `density → pixel width` mapping for one group, given the global
|
|
44
|
+
* max density (for `area` / `count` modes) and the slot half-width in pixels.
|
|
45
|
+
*
|
|
46
|
+
* - `"width"` — each group fills its slot. Densities are not comparable.
|
|
47
|
+
* - `"area"` — each group is normalized by the global max density. Areas
|
|
48
|
+
* are comparable across groups.
|
|
49
|
+
* - `"count"` — like area, but multiplied by `n` so groups with more samples
|
|
50
|
+
* render larger.
|
|
51
|
+
*/
|
|
52
|
+
export declare function densityWidthFn(group: GroupedKdeResult, globalMaxDensity: number, scale: DensityScale, halfWidth: number): (density: number) => number;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Per-group distribution helpers
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Pure (no render deps) helpers shared between distribution geoms — violin and
|
|
5
|
+
// ridgeline. Centralizes:
|
|
6
|
+
// - per-bucket KDE evaluation
|
|
7
|
+
// - per-bucket box stats (used for inner annotations)
|
|
8
|
+
// - cross-group max-density bookkeeping for `area` / `count` width modes
|
|
9
|
+
// - the width-from-density function each geom plugs into its layout
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
boxStats,
|
|
13
|
+
kde,
|
|
14
|
+
type BoxStats,
|
|
15
|
+
type BoxStatsOptions,
|
|
16
|
+
type KdeOptions,
|
|
17
|
+
type KdeResult,
|
|
18
|
+
} from "../../stats/index.ts";
|
|
19
|
+
import type { CategoricalBucket } from "./_categorical.ts";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Compute the value-axis extent across all buckets' KDEs. Used by `violin`
|
|
23
|
+
* and `ridgeline` to extend their value-axis domain so smoothed tails don't
|
|
24
|
+
* render past the plot frame. Returns `null` when no bucket yields a usable
|
|
25
|
+
* KDE (empty input, or all values non-finite).
|
|
26
|
+
*/
|
|
27
|
+
export function kdeValueExtent(
|
|
28
|
+
buckets: Iterable<readonly number[]>,
|
|
29
|
+
options: KdeOptions,
|
|
30
|
+
): readonly [number, number] | null {
|
|
31
|
+
let lo = Number.POSITIVE_INFINITY;
|
|
32
|
+
let hi = Number.NEGATIVE_INFINITY;
|
|
33
|
+
for (const values of buckets) {
|
|
34
|
+
const k = kde(values, options);
|
|
35
|
+
if (!k || k.x.length === 0) continue;
|
|
36
|
+
const x0 = k.x[0]!;
|
|
37
|
+
const xn = k.x[k.x.length - 1]!;
|
|
38
|
+
if (x0 < lo) lo = x0;
|
|
39
|
+
if (xn > hi) hi = xn;
|
|
40
|
+
}
|
|
41
|
+
if (!Number.isFinite(lo) || !Number.isFinite(hi)) return null;
|
|
42
|
+
return [lo, hi];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** How per-row width relates to density across groups. */
|
|
46
|
+
export type DensityScale = "width" | "area" | "count";
|
|
47
|
+
|
|
48
|
+
export interface GroupedKdeResult {
|
|
49
|
+
bucket: CategoricalBucket;
|
|
50
|
+
/** KDE evaluated on a fixed grid (`null` only when bucket has no finite values). */
|
|
51
|
+
kde: KdeResult;
|
|
52
|
+
/** Max raw density on the KDE grid, before any normalization. */
|
|
53
|
+
maxDensity: number;
|
|
54
|
+
/** Sample size that produced the KDE. */
|
|
55
|
+
n: number;
|
|
56
|
+
/** Box stats for the same sample — used by inner annotations. */
|
|
57
|
+
stats: BoxStats;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface ComputeGroupedKdeOptions {
|
|
61
|
+
kde: KdeOptions;
|
|
62
|
+
box: BoxStatsOptions;
|
|
63
|
+
/**
|
|
64
|
+
* Width-scaling mode. Affects only `globalMaxDensity`, which is then folded
|
|
65
|
+
* into `densityWidthFn`. The per-bucket `maxDensity` stays raw so callers
|
|
66
|
+
* that need it (e.g. for `width` mode) get an unscaled reference.
|
|
67
|
+
*/
|
|
68
|
+
scale: DensityScale;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Compute a KDE + box stats for each bucket. Buckets that yield no usable KDE
|
|
73
|
+
* (zero finite samples) are dropped from the output. Returns the global max
|
|
74
|
+
* density used for cross-group `area` / `count` normalization.
|
|
75
|
+
*/
|
|
76
|
+
export function computeGroupedKde(
|
|
77
|
+
buckets: readonly CategoricalBucket[],
|
|
78
|
+
options: ComputeGroupedKdeOptions,
|
|
79
|
+
): { groups: GroupedKdeResult[]; globalMaxDensity: number } {
|
|
80
|
+
const groups: GroupedKdeResult[] = [];
|
|
81
|
+
let globalMaxDensity = 0;
|
|
82
|
+
for (const bucket of buckets) {
|
|
83
|
+
const k = kde(bucket.values, options.kde);
|
|
84
|
+
if (!k) continue;
|
|
85
|
+
const stats = boxStats(bucket.values, options.box);
|
|
86
|
+
if (!stats) continue;
|
|
87
|
+
let max = 0;
|
|
88
|
+
for (const v of k.y) if (v > max) max = v;
|
|
89
|
+
const scaledMax = options.scale === "count" ? max * stats.n : max;
|
|
90
|
+
if (scaledMax > globalMaxDensity) globalMaxDensity = scaledMax;
|
|
91
|
+
groups.push({ bucket, kde: k, maxDensity: max, n: stats.n, stats });
|
|
92
|
+
}
|
|
93
|
+
return { groups, globalMaxDensity };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Build the `density → pixel width` mapping for one group, given the global
|
|
98
|
+
* max density (for `area` / `count` modes) and the slot half-width in pixels.
|
|
99
|
+
*
|
|
100
|
+
* - `"width"` — each group fills its slot. Densities are not comparable.
|
|
101
|
+
* - `"area"` — each group is normalized by the global max density. Areas
|
|
102
|
+
* are comparable across groups.
|
|
103
|
+
* - `"count"` — like area, but multiplied by `n` so groups with more samples
|
|
104
|
+
* render larger.
|
|
105
|
+
*/
|
|
106
|
+
export function densityWidthFn(
|
|
107
|
+
group: GroupedKdeResult,
|
|
108
|
+
globalMaxDensity: number,
|
|
109
|
+
scale: DensityScale,
|
|
110
|
+
halfWidth: number,
|
|
111
|
+
): (density: number) => number {
|
|
112
|
+
if (scale === "width") {
|
|
113
|
+
if (group.maxDensity <= 0) return () => 0;
|
|
114
|
+
const k = halfWidth / group.maxDensity;
|
|
115
|
+
return (d) => d * k;
|
|
116
|
+
}
|
|
117
|
+
const ref = globalMaxDensity > 0 ? globalMaxDensity : 1;
|
|
118
|
+
if (scale === "count") {
|
|
119
|
+
const k = (halfWidth * group.n) / ref;
|
|
120
|
+
return (d) => d * k;
|
|
121
|
+
}
|
|
122
|
+
// area
|
|
123
|
+
const k = halfWidth / ref;
|
|
124
|
+
return (d) => d * k;
|
|
125
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { Color, Layer } from "insomni";
|
|
2
|
+
import type { MarkBuilder, MarkOrigin } from "../../marks.ts";
|
|
3
|
+
import { type Coord } from "../coord.ts";
|
|
4
|
+
import type { Theme } from "../theme.ts";
|
|
5
|
+
import type { CompileContext, GeomKind } from "./types.ts";
|
|
6
|
+
/**
|
|
7
|
+
* Resolve `ctx.coord` to a concrete {@link Coord}. The interface flags `coord`
|
|
8
|
+
* as optional in {@link CompileContext} during the Phase 1 migration to the
|
|
9
|
+
* `Coord` API — geoms call this once at the top of their compile path to get
|
|
10
|
+
* a non-null projection, falling back to {@link coordCartesian} when the
|
|
11
|
+
* pipeline didn't supply one (e.g. legacy direct-compile call sites). Under
|
|
12
|
+
* Cartesian both `project` and `segment` are the identity, so this is a
|
|
13
|
+
* zero-pixel-diff drop-in for every existing geom.
|
|
14
|
+
*/
|
|
15
|
+
export declare function resolveCoord<T>(ctx: CompileContext<T>): Coord;
|
|
16
|
+
/**
|
|
17
|
+
* Default alpha applied to non-selected marks while a selection is active.
|
|
18
|
+
* Lifted into a constant so geoms stay consistent without each importing the
|
|
19
|
+
* theme's selection config; per-mount overrides happen via the selection
|
|
20
|
+
* config (see chart.ts:SelectionConfig.dimAlpha).
|
|
21
|
+
*/
|
|
22
|
+
export declare const SELECTION_DIM_ALPHA = 0.3;
|
|
23
|
+
/**
|
|
24
|
+
* Returns the set of selected `dataIndex`es that belong to *this* geom (kind
|
|
25
|
+
* matches and the data array reference matches), or `null` when no selection
|
|
26
|
+
* row matches. Geoms use this to push stroke rings on the matching rows.
|
|
27
|
+
*/
|
|
28
|
+
export declare function selectedIndicesFor<T>(ctx: CompileContext<T>, kind: GeomKind): Set<number> | null;
|
|
29
|
+
/** True when any selection is active anywhere in the chart. */
|
|
30
|
+
export declare function selectionActive<T>(ctx: CompileContext<T>): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Multi-series equivalent of {@link selectedIndicesFor}: returns the list of
|
|
33
|
+
* `(dataIndex, seriesKey)` pairs selected for *this* geom. Used by stacked /
|
|
34
|
+
* dodged geoms where a single source row produces multiple visible segments
|
|
35
|
+
* and the ring must distinguish them by series key.
|
|
36
|
+
*/
|
|
37
|
+
export declare function selectedSegmentsFor<T>(ctx: CompileContext<T>, kind: GeomKind): Array<{
|
|
38
|
+
dataIndex: number;
|
|
39
|
+
seriesKey?: string;
|
|
40
|
+
}> | null;
|
|
41
|
+
/**
|
|
42
|
+
* Wrap a `MarkBuilder` so it composes into the `compile()` builder list with
|
|
43
|
+
* a fixed origin (typically the plot frame's top-left). Geoms collect these
|
|
44
|
+
* into the array returned from `compile`. Pass `length` to override the
|
|
45
|
+
* builder's reported length — used for label sub-marks where the visual
|
|
46
|
+
* count differs from the underlying mark's vertex count.
|
|
47
|
+
*/
|
|
48
|
+
export declare function wrapMark(mark: MarkBuilder, origin: MarkOrigin, length?: number): {
|
|
49
|
+
length: number;
|
|
50
|
+
addTo: (layer: Layer) => Layer;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Build a `MarkBuilder` from a draw callback. Used for one-off shapes that
|
|
54
|
+
* don't fit a mark factory — currently the hover-highlight halo each geom
|
|
55
|
+
* pushes for `ctx.hovered`. Coordinates passed to `draw` are layer-local
|
|
56
|
+
* (the caller has already applied any plot-frame offset).
|
|
57
|
+
*/
|
|
58
|
+
export declare function inlineMark(draw: (layer: Layer) => void, length?: number): MarkBuilder;
|
|
59
|
+
export declare function resolveFillAlpha(selected: boolean, theme: Theme): number;
|
|
60
|
+
export declare function defaultMarkFill(theme: Theme): Color;
|
|
61
|
+
export declare function selectChannels<X, Y>(orientation: "x" | "y", channels: {
|
|
62
|
+
x: X;
|
|
63
|
+
y: Y;
|
|
64
|
+
}): {
|
|
65
|
+
categoryChannel: X | Y;
|
|
66
|
+
valueChannel: X | Y;
|
|
67
|
+
};
|
|
68
|
+
/** Highlight ring used by point/line/area on `ctx.hovered`. */
|
|
69
|
+
export declare function haloRing(layer: Layer, cx: number, cy: number, radius: number, color: Color, width?: number): void;
|