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
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "insomni-plot",
|
|
3
|
+
"version": "0.1.0-alpha.0",
|
|
4
|
+
"description": "Plotting and charting for insomni.",
|
|
5
|
+
"license": "GPL-3.0",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist",
|
|
8
|
+
"src"
|
|
9
|
+
],
|
|
10
|
+
"type": "module",
|
|
11
|
+
"sideEffects": false,
|
|
12
|
+
"types": "./src/index.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": "./dist/index.mjs",
|
|
15
|
+
"./core": "./dist/core.mjs",
|
|
16
|
+
"./package.json": "./package.json"
|
|
17
|
+
},
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"insomni": "0.2.0-alpha.0"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"typegpu": "^0.10.2"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "vp pack",
|
|
29
|
+
"dev": "vp pack --watch",
|
|
30
|
+
"test": "vp test",
|
|
31
|
+
"test:browser": "vp test -c vite.browser-test.config.ts",
|
|
32
|
+
"check": "vp check"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { type Color, type GlyphAtlas, type Group } from "insomni";
|
|
2
|
+
import { type Accessor, type MarkBuilder, type ValueOrAccessor } from "./marks.ts";
|
|
3
|
+
export type RuleAnchor = "start" | "middle" | "end";
|
|
4
|
+
interface RuleStyle {
|
|
5
|
+
stroke?: Color;
|
|
6
|
+
strokeWidth?: number;
|
|
7
|
+
dashPattern?: readonly number[];
|
|
8
|
+
dashOffset?: number;
|
|
9
|
+
}
|
|
10
|
+
interface RuleLabel {
|
|
11
|
+
label?: string;
|
|
12
|
+
/** Where the label sits along the rule. Default `"end"`. */
|
|
13
|
+
labelAnchor?: RuleAnchor;
|
|
14
|
+
/** Perpendicular offset from the rule (positive = away from the data). Default `6`. */
|
|
15
|
+
labelOffset?: number;
|
|
16
|
+
/** Tangential offset from the chosen anchor (positive = inwards). Default `6`. */
|
|
17
|
+
labelInset?: number;
|
|
18
|
+
labelColor?: Color;
|
|
19
|
+
fontSize?: number;
|
|
20
|
+
fontStyle?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface HorizontalRuleOptions extends RuleStyle, RuleLabel {
|
|
23
|
+
/** Value (in layer-space pixels, post-scale) of the horizontal rule. */
|
|
24
|
+
y: number;
|
|
25
|
+
x?: never;
|
|
26
|
+
/** Range of x covered. Required — typically `[0, plot.width]`. */
|
|
27
|
+
extent: readonly [number, number];
|
|
28
|
+
group?: Group;
|
|
29
|
+
}
|
|
30
|
+
export interface VerticalRuleOptions extends RuleStyle, RuleLabel {
|
|
31
|
+
/** Value (in layer-space pixels, post-scale) of the vertical rule. */
|
|
32
|
+
x: number;
|
|
33
|
+
y?: never;
|
|
34
|
+
/** Range of y covered. Required — typically `[0, plot.height]`. */
|
|
35
|
+
extent: readonly [number, number];
|
|
36
|
+
group?: Group;
|
|
37
|
+
}
|
|
38
|
+
export type RuleMarkOptions = HorizontalRuleOptions | VerticalRuleOptions;
|
|
39
|
+
export declare function ruleMark(options: RuleMarkOptions): MarkBuilder;
|
|
40
|
+
interface BandStyle {
|
|
41
|
+
fill?: Color;
|
|
42
|
+
stroke?: Color;
|
|
43
|
+
strokeWidth?: number;
|
|
44
|
+
/** Draw the two perpendicular edge lines (uses `stroke`). Default `true` when `stroke` set. */
|
|
45
|
+
edges?: boolean;
|
|
46
|
+
}
|
|
47
|
+
interface BandLabel {
|
|
48
|
+
label?: string;
|
|
49
|
+
/** Where the label sits along the band. Default `"middle"`. */
|
|
50
|
+
labelAnchor?: RuleAnchor;
|
|
51
|
+
/** Perpendicular offset into the perpendicular extent (positive = inwards). Default `4`. */
|
|
52
|
+
labelOffset?: number;
|
|
53
|
+
labelColor?: Color;
|
|
54
|
+
fontSize?: number;
|
|
55
|
+
fontStyle?: string;
|
|
56
|
+
}
|
|
57
|
+
export interface VerticalBandOptions extends BandStyle, BandLabel {
|
|
58
|
+
/** Range along the x axis (vertical band — fills between x0 and x1). */
|
|
59
|
+
x: readonly [number, number];
|
|
60
|
+
y?: never;
|
|
61
|
+
/** Required perpendicular extent (typically `[0, plot.height]`). */
|
|
62
|
+
extent: readonly [number, number];
|
|
63
|
+
group?: Group;
|
|
64
|
+
}
|
|
65
|
+
export interface HorizontalBandOptions extends BandStyle, BandLabel {
|
|
66
|
+
/** Range along the y axis (horizontal band). */
|
|
67
|
+
y: readonly [number, number];
|
|
68
|
+
x?: never;
|
|
69
|
+
/** Required perpendicular extent (typically `[0, plot.width]`). */
|
|
70
|
+
extent: readonly [number, number];
|
|
71
|
+
group?: Group;
|
|
72
|
+
}
|
|
73
|
+
export type BandMarkOptions = VerticalBandOptions | HorizontalBandOptions;
|
|
74
|
+
export declare function bandMark(options: BandMarkOptions): MarkBuilder;
|
|
75
|
+
export type ValueLabelAlign = "left" | "center" | "right";
|
|
76
|
+
/**
|
|
77
|
+
* Optional rounded-rectangle background drawn behind label text. When set
|
|
78
|
+
* (and an `atlas` is available for measurement), `valueLabelMark` measures
|
|
79
|
+
* each label and emits a `pushRect` underneath sized to the text bounds plus
|
|
80
|
+
* `padding`. The rect's anchor matches the label's `align` so the text stays
|
|
81
|
+
* visually centered on `(x, y) + offset`.
|
|
82
|
+
*/
|
|
83
|
+
export interface LabelBoxStyle {
|
|
84
|
+
fill?: Color;
|
|
85
|
+
stroke?: Color;
|
|
86
|
+
strokeWidth?: number;
|
|
87
|
+
/** Pixels added on each side. Number = uniform; object = per-edge. Default `4`. */
|
|
88
|
+
padding?: number | {
|
|
89
|
+
x?: number;
|
|
90
|
+
y?: number;
|
|
91
|
+
};
|
|
92
|
+
cornerRadius?: number;
|
|
93
|
+
}
|
|
94
|
+
export interface ValueLabelMarkOptions<T> {
|
|
95
|
+
x: Accessor<T, number>;
|
|
96
|
+
y: Accessor<T, number>;
|
|
97
|
+
text: Accessor<T, string>;
|
|
98
|
+
/** Horizontal anchor of the text relative to (`x`,`y`). Default `"center"`. */
|
|
99
|
+
align?: ValueLabelAlign;
|
|
100
|
+
/** Pixel offset from (`x`,`y`). Default `{ x: 0, y: -4 }` (above). */
|
|
101
|
+
offset?: {
|
|
102
|
+
x?: number;
|
|
103
|
+
y?: number;
|
|
104
|
+
};
|
|
105
|
+
color?: ValueOrAccessor<T, Color>;
|
|
106
|
+
fontSize?: number;
|
|
107
|
+
fontStyle?: string;
|
|
108
|
+
/** Skip labels for which this returns false. */
|
|
109
|
+
filter?: Accessor<T, boolean>;
|
|
110
|
+
/** Optional rounded-rect background. */
|
|
111
|
+
box?: LabelBoxStyle;
|
|
112
|
+
/**
|
|
113
|
+
* Glyph atlas used to measure each label's true rendered width and height
|
|
114
|
+
* for `box` sizing. When omitted the box falls back to a coarse character-
|
|
115
|
+
* width estimate that visibly mismatches the glyphs at small font sizes.
|
|
116
|
+
*/
|
|
117
|
+
atlas?: GlyphAtlas;
|
|
118
|
+
group?: Group;
|
|
119
|
+
}
|
|
120
|
+
export declare function valueLabelMark<T>(data: readonly T[], options: ValueLabelMarkOptions<T>): MarkBuilder;
|
|
121
|
+
export {};
|
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
import { BLACK, type Color, type GlyphAtlas, type Group, type Layer } from "insomni";
|
|
2
|
+
import {
|
|
3
|
+
resolveValueOrAccessor as resolve,
|
|
4
|
+
type Accessor,
|
|
5
|
+
type MarkBuilder,
|
|
6
|
+
type MarkOrigin,
|
|
7
|
+
type ValueOrAccessor,
|
|
8
|
+
} from "./marks.ts";
|
|
9
|
+
import { TEXT_WIDTH_FALLBACK_RATIO } from "./format.ts";
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// ruleMark — horizontal or vertical reference line, optionally labelled
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
export type RuleAnchor = "start" | "middle" | "end";
|
|
16
|
+
|
|
17
|
+
interface RuleStyle {
|
|
18
|
+
stroke?: Color;
|
|
19
|
+
strokeWidth?: number;
|
|
20
|
+
dashPattern?: readonly number[];
|
|
21
|
+
dashOffset?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface RuleLabel {
|
|
25
|
+
label?: string;
|
|
26
|
+
/** Where the label sits along the rule. Default `"end"`. */
|
|
27
|
+
labelAnchor?: RuleAnchor;
|
|
28
|
+
/** Perpendicular offset from the rule (positive = away from the data). Default `6`. */
|
|
29
|
+
labelOffset?: number;
|
|
30
|
+
/** Tangential offset from the chosen anchor (positive = inwards). Default `6`. */
|
|
31
|
+
labelInset?: number;
|
|
32
|
+
labelColor?: Color;
|
|
33
|
+
fontSize?: number;
|
|
34
|
+
fontStyle?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface HorizontalRuleOptions extends RuleStyle, RuleLabel {
|
|
38
|
+
/** Value (in layer-space pixels, post-scale) of the horizontal rule. */
|
|
39
|
+
y: number;
|
|
40
|
+
x?: never;
|
|
41
|
+
/** Range of x covered. Required — typically `[0, plot.width]`. */
|
|
42
|
+
extent: readonly [number, number];
|
|
43
|
+
group?: Group;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface VerticalRuleOptions extends RuleStyle, RuleLabel {
|
|
47
|
+
/** Value (in layer-space pixels, post-scale) of the vertical rule. */
|
|
48
|
+
x: number;
|
|
49
|
+
y?: never;
|
|
50
|
+
/** Range of y covered. Required — typically `[0, plot.height]`. */
|
|
51
|
+
extent: readonly [number, number];
|
|
52
|
+
group?: Group;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type RuleMarkOptions = HorizontalRuleOptions | VerticalRuleOptions;
|
|
56
|
+
|
|
57
|
+
function isHorizontalRule(opts: RuleMarkOptions): opts is HorizontalRuleOptions {
|
|
58
|
+
return typeof (opts as HorizontalRuleOptions).y === "number";
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function ruleMark(options: RuleMarkOptions): MarkBuilder {
|
|
62
|
+
return {
|
|
63
|
+
length: 1,
|
|
64
|
+
addTo(layer: Layer, origin: MarkOrigin = {}) {
|
|
65
|
+
const ox = origin.x ?? 0;
|
|
66
|
+
const oy = origin.y ?? 0;
|
|
67
|
+
const stroke = options.stroke ?? BLACK;
|
|
68
|
+
const width = options.strokeWidth ?? 1;
|
|
69
|
+
const [e0, e1] = options.extent;
|
|
70
|
+
|
|
71
|
+
const horizontal = isHorizontalRule(options);
|
|
72
|
+
const x1 = horizontal ? ox + e0 : ox + options.x;
|
|
73
|
+
const y1 = horizontal ? oy + options.y : oy + e0;
|
|
74
|
+
const x2 = horizontal ? ox + e1 : ox + options.x;
|
|
75
|
+
const y2 = horizontal ? oy + options.y : oy + e1;
|
|
76
|
+
|
|
77
|
+
if (options.dashPattern && options.dashPattern.length > 0) {
|
|
78
|
+
layer.pushPolyline({
|
|
79
|
+
points: [
|
|
80
|
+
{ x: x1, y: y1 },
|
|
81
|
+
{ x: x2, y: y2 },
|
|
82
|
+
],
|
|
83
|
+
color: stroke,
|
|
84
|
+
width,
|
|
85
|
+
dashPattern: options.dashPattern,
|
|
86
|
+
dashOffset: options.dashOffset,
|
|
87
|
+
group: options.group,
|
|
88
|
+
});
|
|
89
|
+
} else {
|
|
90
|
+
layer.pushLine({ x1, y1, x2, y2, color: stroke, width, group: options.group });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (options.label) {
|
|
94
|
+
const fontSize = options.fontSize ?? 11;
|
|
95
|
+
const labelOffset = options.labelOffset ?? 6;
|
|
96
|
+
const labelInset = options.labelInset ?? 6;
|
|
97
|
+
const anchor: RuleAnchor = options.labelAnchor ?? "end";
|
|
98
|
+
const labelColor = options.labelColor ?? stroke;
|
|
99
|
+
const e0o = horizontal ? ox + e0 : oy + e0;
|
|
100
|
+
const e1o = horizontal ? ox + e1 : oy + e1;
|
|
101
|
+
const along =
|
|
102
|
+
anchor === "start"
|
|
103
|
+
? Math.min(e0o, e1o) + labelInset
|
|
104
|
+
: anchor === "middle"
|
|
105
|
+
? (e0o + e1o) / 2
|
|
106
|
+
: Math.max(e0o, e1o) - labelInset;
|
|
107
|
+
const align = anchor === "start" ? "left" : anchor === "middle" ? "center" : "right";
|
|
108
|
+
|
|
109
|
+
if (layer.atlas != null) {
|
|
110
|
+
if (horizontal) {
|
|
111
|
+
layer.pushText({
|
|
112
|
+
simple: true,
|
|
113
|
+
text: options.label,
|
|
114
|
+
x: along,
|
|
115
|
+
y: oy + options.y - labelOffset - fontSize,
|
|
116
|
+
fontSize,
|
|
117
|
+
color: labelColor,
|
|
118
|
+
align,
|
|
119
|
+
group: options.group,
|
|
120
|
+
});
|
|
121
|
+
} else {
|
|
122
|
+
layer.pushText({
|
|
123
|
+
simple: true,
|
|
124
|
+
text: options.label,
|
|
125
|
+
x: ox + options.x + labelOffset,
|
|
126
|
+
y: along,
|
|
127
|
+
fontSize,
|
|
128
|
+
color: labelColor,
|
|
129
|
+
align: "left",
|
|
130
|
+
group: options.group,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return layer;
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
// bandMark — highlighted x or y range, with optional label
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
interface BandStyle {
|
|
146
|
+
fill?: Color;
|
|
147
|
+
stroke?: Color;
|
|
148
|
+
strokeWidth?: number;
|
|
149
|
+
/** Draw the two perpendicular edge lines (uses `stroke`). Default `true` when `stroke` set. */
|
|
150
|
+
edges?: boolean;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
interface BandLabel {
|
|
154
|
+
label?: string;
|
|
155
|
+
/** Where the label sits along the band. Default `"middle"`. */
|
|
156
|
+
labelAnchor?: RuleAnchor;
|
|
157
|
+
/** Perpendicular offset into the perpendicular extent (positive = inwards). Default `4`. */
|
|
158
|
+
labelOffset?: number;
|
|
159
|
+
labelColor?: Color;
|
|
160
|
+
fontSize?: number;
|
|
161
|
+
fontStyle?: string;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export interface VerticalBandOptions extends BandStyle, BandLabel {
|
|
165
|
+
/** Range along the x axis (vertical band — fills between x0 and x1). */
|
|
166
|
+
x: readonly [number, number];
|
|
167
|
+
y?: never;
|
|
168
|
+
/** Required perpendicular extent (typically `[0, plot.height]`). */
|
|
169
|
+
extent: readonly [number, number];
|
|
170
|
+
group?: Group;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export interface HorizontalBandOptions extends BandStyle, BandLabel {
|
|
174
|
+
/** Range along the y axis (horizontal band). */
|
|
175
|
+
y: readonly [number, number];
|
|
176
|
+
x?: never;
|
|
177
|
+
/** Required perpendicular extent (typically `[0, plot.width]`). */
|
|
178
|
+
extent: readonly [number, number];
|
|
179
|
+
group?: Group;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export type BandMarkOptions = VerticalBandOptions | HorizontalBandOptions;
|
|
183
|
+
|
|
184
|
+
function isVerticalBand(opts: BandMarkOptions): opts is VerticalBandOptions {
|
|
185
|
+
return Array.isArray((opts as VerticalBandOptions).x);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function bandMark(options: BandMarkOptions): MarkBuilder {
|
|
189
|
+
return {
|
|
190
|
+
length: 1,
|
|
191
|
+
addTo(layer: Layer, origin: MarkOrigin = {}) {
|
|
192
|
+
const ox = origin.x ?? 0;
|
|
193
|
+
const oy = origin.y ?? 0;
|
|
194
|
+
const [eA, eB] = options.extent;
|
|
195
|
+
const vertical = isVerticalBand(options);
|
|
196
|
+
const [pA, pB] = vertical ? options.x : options.y;
|
|
197
|
+
const xMin = vertical ? ox + Math.min(pA, pB) : ox + Math.min(eA, eB);
|
|
198
|
+
const yMin = vertical ? oy + Math.min(eA, eB) : oy + Math.min(pA, pB);
|
|
199
|
+
const xW = vertical ? Math.abs(pB - pA) : Math.abs(eB - eA);
|
|
200
|
+
const yH = vertical ? Math.abs(eB - eA) : Math.abs(pB - pA);
|
|
201
|
+
|
|
202
|
+
if (options.fill) {
|
|
203
|
+
layer.pushRect({
|
|
204
|
+
x: xMin,
|
|
205
|
+
y: yMin,
|
|
206
|
+
width: xW,
|
|
207
|
+
height: yH,
|
|
208
|
+
fill: options.fill,
|
|
209
|
+
group: options.group,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (options.stroke && (options.edges ?? true)) {
|
|
214
|
+
const sw = options.strokeWidth ?? 1;
|
|
215
|
+
if (vertical) {
|
|
216
|
+
layer.pushLine({
|
|
217
|
+
x1: ox + pA,
|
|
218
|
+
y1: yMin,
|
|
219
|
+
x2: ox + pA,
|
|
220
|
+
y2: yMin + yH,
|
|
221
|
+
color: options.stroke,
|
|
222
|
+
width: sw,
|
|
223
|
+
group: options.group,
|
|
224
|
+
});
|
|
225
|
+
layer.pushLine({
|
|
226
|
+
x1: ox + pB,
|
|
227
|
+
y1: yMin,
|
|
228
|
+
x2: ox + pB,
|
|
229
|
+
y2: yMin + yH,
|
|
230
|
+
color: options.stroke,
|
|
231
|
+
width: sw,
|
|
232
|
+
group: options.group,
|
|
233
|
+
});
|
|
234
|
+
} else {
|
|
235
|
+
layer.pushLine({
|
|
236
|
+
x1: xMin,
|
|
237
|
+
y1: oy + pA,
|
|
238
|
+
x2: xMin + xW,
|
|
239
|
+
y2: oy + pA,
|
|
240
|
+
color: options.stroke,
|
|
241
|
+
width: sw,
|
|
242
|
+
group: options.group,
|
|
243
|
+
});
|
|
244
|
+
layer.pushLine({
|
|
245
|
+
x1: xMin,
|
|
246
|
+
y1: oy + pB,
|
|
247
|
+
x2: xMin + xW,
|
|
248
|
+
y2: oy + pB,
|
|
249
|
+
color: options.stroke,
|
|
250
|
+
width: sw,
|
|
251
|
+
group: options.group,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (options.label) {
|
|
257
|
+
const fontSize = options.fontSize ?? 10;
|
|
258
|
+
const labelOffset = options.labelOffset ?? 4;
|
|
259
|
+
const anchor: RuleAnchor = options.labelAnchor ?? "middle";
|
|
260
|
+
const labelColor = options.labelColor ?? options.stroke ?? BLACK;
|
|
261
|
+
const along0 = vertical ? ox + pA : oy + pA;
|
|
262
|
+
const along1 = vertical ? ox + pB : oy + pB;
|
|
263
|
+
const center =
|
|
264
|
+
anchor === "start"
|
|
265
|
+
? Math.min(along0, along1)
|
|
266
|
+
: anchor === "middle"
|
|
267
|
+
? (along0 + along1) / 2
|
|
268
|
+
: Math.max(along0, along1);
|
|
269
|
+
|
|
270
|
+
if (layer.atlas != null) {
|
|
271
|
+
if (vertical) {
|
|
272
|
+
layer.pushText({
|
|
273
|
+
simple: true,
|
|
274
|
+
text: options.label,
|
|
275
|
+
x: center,
|
|
276
|
+
y: yMin + labelOffset,
|
|
277
|
+
fontSize,
|
|
278
|
+
color: labelColor,
|
|
279
|
+
align: "center",
|
|
280
|
+
group: options.group,
|
|
281
|
+
});
|
|
282
|
+
} else {
|
|
283
|
+
layer.pushText({
|
|
284
|
+
simple: true,
|
|
285
|
+
text: options.label,
|
|
286
|
+
x: xMin + labelOffset,
|
|
287
|
+
y: center - fontSize / 2,
|
|
288
|
+
fontSize,
|
|
289
|
+
color: labelColor,
|
|
290
|
+
align: "left",
|
|
291
|
+
group: options.group,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return layer;
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ---------------------------------------------------------------------------
|
|
303
|
+
// valueLabelMark — text labels per datum (above/right of bars, beside points)
|
|
304
|
+
// ---------------------------------------------------------------------------
|
|
305
|
+
|
|
306
|
+
export type ValueLabelAlign = "left" | "center" | "right";
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Optional rounded-rectangle background drawn behind label text. When set
|
|
310
|
+
* (and an `atlas` is available for measurement), `valueLabelMark` measures
|
|
311
|
+
* each label and emits a `pushRect` underneath sized to the text bounds plus
|
|
312
|
+
* `padding`. The rect's anchor matches the label's `align` so the text stays
|
|
313
|
+
* visually centered on `(x, y) + offset`.
|
|
314
|
+
*/
|
|
315
|
+
export interface LabelBoxStyle {
|
|
316
|
+
fill?: Color;
|
|
317
|
+
stroke?: Color;
|
|
318
|
+
strokeWidth?: number;
|
|
319
|
+
/** Pixels added on each side. Number = uniform; object = per-edge. Default `4`. */
|
|
320
|
+
padding?: number | { x?: number; y?: number };
|
|
321
|
+
cornerRadius?: number;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export interface ValueLabelMarkOptions<T> {
|
|
325
|
+
x: Accessor<T, number>;
|
|
326
|
+
y: Accessor<T, number>;
|
|
327
|
+
text: Accessor<T, string>;
|
|
328
|
+
/** Horizontal anchor of the text relative to (`x`,`y`). Default `"center"`. */
|
|
329
|
+
align?: ValueLabelAlign;
|
|
330
|
+
/** Pixel offset from (`x`,`y`). Default `{ x: 0, y: -4 }` (above). */
|
|
331
|
+
offset?: { x?: number; y?: number };
|
|
332
|
+
color?: ValueOrAccessor<T, Color>;
|
|
333
|
+
fontSize?: number;
|
|
334
|
+
fontStyle?: string;
|
|
335
|
+
/** Skip labels for which this returns false. */
|
|
336
|
+
filter?: Accessor<T, boolean>;
|
|
337
|
+
/** Optional rounded-rect background. */
|
|
338
|
+
box?: LabelBoxStyle;
|
|
339
|
+
/**
|
|
340
|
+
* Glyph atlas used to measure each label's true rendered width and height
|
|
341
|
+
* for `box` sizing. When omitted the box falls back to a coarse character-
|
|
342
|
+
* width estimate that visibly mismatches the glyphs at small font sizes.
|
|
343
|
+
*/
|
|
344
|
+
atlas?: GlyphAtlas;
|
|
345
|
+
group?: Group;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export function valueLabelMark<T>(
|
|
349
|
+
data: readonly T[],
|
|
350
|
+
options: ValueLabelMarkOptions<T>,
|
|
351
|
+
): MarkBuilder {
|
|
352
|
+
return {
|
|
353
|
+
length: data.length,
|
|
354
|
+
addTo(layer: Layer, origin: MarkOrigin = {}) {
|
|
355
|
+
const ox = origin.x ?? 0;
|
|
356
|
+
const oy = origin.y ?? 0;
|
|
357
|
+
const align = options.align ?? "center";
|
|
358
|
+
const offsetX = options.offset?.x ?? 0;
|
|
359
|
+
const offsetY = options.offset?.y ?? -4;
|
|
360
|
+
const fontSize = options.fontSize ?? 11;
|
|
361
|
+
const box = options.box;
|
|
362
|
+
const padX = box ? boxPad(box.padding, "x") : 0;
|
|
363
|
+
const padY = box ? boxPad(box.padding, "y") : 0;
|
|
364
|
+
|
|
365
|
+
// valueLabelMark is text-only (optional box background + label). Both
|
|
366
|
+
// pushString and pushText require a glyph atlas; skip the entire mark
|
|
367
|
+
// when none is present (e.g. SVG export without an external atlas).
|
|
368
|
+
if (layer.atlas == null) return layer;
|
|
369
|
+
|
|
370
|
+
for (let i = 0; i < data.length; i++) {
|
|
371
|
+
const datum = data[i]!;
|
|
372
|
+
if (options.filter && !options.filter(datum, i)) continue;
|
|
373
|
+
const x = ox + options.x(datum, i) + offsetX;
|
|
374
|
+
const y = oy + options.y(datum, i) + offsetY;
|
|
375
|
+
const text = options.text(datum, i);
|
|
376
|
+
const color = options.color === undefined ? BLACK : resolve(options.color, datum, i);
|
|
377
|
+
|
|
378
|
+
if (box) {
|
|
379
|
+
// Prefer real glyph metrics when an atlas is available — the
|
|
380
|
+
// ratio-based fallback ignores per-glyph advance and visibly
|
|
381
|
+
// under/over-shoots for short labels.
|
|
382
|
+
let tw: number;
|
|
383
|
+
let th: number;
|
|
384
|
+
if (options.atlas) {
|
|
385
|
+
const m = options.atlas.measureText(text, { fontSize, simple: true });
|
|
386
|
+
tw = m.width;
|
|
387
|
+
th = m.height;
|
|
388
|
+
} else {
|
|
389
|
+
tw = text.length * fontSize * TEXT_WIDTH_FALLBACK_RATIO;
|
|
390
|
+
th = fontSize;
|
|
391
|
+
}
|
|
392
|
+
const rx =
|
|
393
|
+
align === "center" ? x - tw / 2 - padX : align === "right" ? x - tw - padX : x - padX;
|
|
394
|
+
const ry = y - padY;
|
|
395
|
+
layer.pushRect({
|
|
396
|
+
x: rx,
|
|
397
|
+
y: ry,
|
|
398
|
+
width: tw + padX * 2,
|
|
399
|
+
height: th + padY * 2,
|
|
400
|
+
fill: box.fill,
|
|
401
|
+
stroke: box.stroke,
|
|
402
|
+
strokeWidth: box.strokeWidth,
|
|
403
|
+
cornerRadius: box.cornerRadius,
|
|
404
|
+
group: options.group,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Fast path: when the layer has a glyph atlas (v3: layer.atlas) and the
|
|
409
|
+
// caller hasn't requested any feature outside the fast path's support
|
|
410
|
+
// (no outline, shadow, gradient, wrap), route through the GPU expansion
|
|
411
|
+
// pipeline. valueLabelMark itself never sets those, so this is always
|
|
412
|
+
// safe here. v3 replaced the v1 `stringPack` sentinel with `atlas`:
|
|
413
|
+
// pushString calls _requireAtlas and throws without one, so checking
|
|
414
|
+
// atlas != null is the correct gate. When no atlas is present (e.g.
|
|
415
|
+
// SVG export without an external atlas), skip text emission entirely —
|
|
416
|
+
// pushText also throws without an atlas.
|
|
417
|
+
if (layer.atlas != null) {
|
|
418
|
+
layer.pushString({
|
|
419
|
+
text,
|
|
420
|
+
x,
|
|
421
|
+
y,
|
|
422
|
+
fontSize,
|
|
423
|
+
color,
|
|
424
|
+
align,
|
|
425
|
+
group: options.group,
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return layer;
|
|
430
|
+
},
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function boxPad(padding: LabelBoxStyle["padding"], axis: "x" | "y"): number {
|
|
435
|
+
if (padding === undefined) return 4;
|
|
436
|
+
if (typeof padding === "number") return padding;
|
|
437
|
+
return padding[axis] ?? 4;
|
|
438
|
+
}
|