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,174 @@
|
|
|
1
|
+
import { describe, expect, test, vi } from "vite-plus/test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
rgba,
|
|
5
|
+
viewportFrame,
|
|
6
|
+
type DragPointerInfo,
|
|
7
|
+
type InteractionManager,
|
|
8
|
+
type InteractionNode,
|
|
9
|
+
type InteractionNodeSpec,
|
|
10
|
+
type PointerInfo,
|
|
11
|
+
} from "insomni";
|
|
12
|
+
import { createDataNavigator } from "./navigator.ts";
|
|
13
|
+
import { createDataViewport } from "./viewport.ts";
|
|
14
|
+
|
|
15
|
+
function fakeManager(): {
|
|
16
|
+
manager: InteractionManager;
|
|
17
|
+
spec(): InteractionNodeSpec | null;
|
|
18
|
+
destroyed: { value: boolean };
|
|
19
|
+
} {
|
|
20
|
+
let captured: InteractionNodeSpec | null = null;
|
|
21
|
+
const destroyed = { value: false };
|
|
22
|
+
const element = {
|
|
23
|
+
style: { cursor: "" },
|
|
24
|
+
clientWidth: 200,
|
|
25
|
+
width: 200,
|
|
26
|
+
} as unknown as HTMLElement;
|
|
27
|
+
const manager = {
|
|
28
|
+
element,
|
|
29
|
+
add(spec: InteractionNodeSpec): InteractionNode {
|
|
30
|
+
captured = spec;
|
|
31
|
+
return {
|
|
32
|
+
id: Symbol("nav"),
|
|
33
|
+
update: vi.fn(),
|
|
34
|
+
destroy: () => {
|
|
35
|
+
destroyed.value = true;
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
addPointCloud: vi.fn(),
|
|
40
|
+
onBackgroundTap: vi.fn(() => () => {}),
|
|
41
|
+
onChange: vi.fn(() => () => {}),
|
|
42
|
+
destroy: vi.fn(),
|
|
43
|
+
} as unknown as InteractionManager;
|
|
44
|
+
return { manager, spec: () => captured, destroyed };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const RED = rgba(1, 0, 0, 1);
|
|
48
|
+
|
|
49
|
+
function makePair() {
|
|
50
|
+
const source = createDataViewport({
|
|
51
|
+
frame: viewportFrame(400, 300),
|
|
52
|
+
x: { type: "linear", domain: [0, 100] },
|
|
53
|
+
y: { type: "linear", domain: [0, 100] },
|
|
54
|
+
});
|
|
55
|
+
const overview = createDataViewport({
|
|
56
|
+
frame: viewportFrame(400, 300),
|
|
57
|
+
x: { type: "linear", domain: [0, 100] },
|
|
58
|
+
y: { type: "linear", domain: [0, 100] },
|
|
59
|
+
});
|
|
60
|
+
return { source, overview };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
describe("createDataNavigator", () => {
|
|
64
|
+
test("render callback runs at construction and on overview change", () => {
|
|
65
|
+
const { source, overview } = makePair();
|
|
66
|
+
let calls = 0;
|
|
67
|
+
createDataNavigator({
|
|
68
|
+
source,
|
|
69
|
+
overview,
|
|
70
|
+
render: () => {
|
|
71
|
+
calls++;
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
expect(calls).toBe(1);
|
|
75
|
+
overview.panBy(10, 0);
|
|
76
|
+
expect(calls).toBe(2);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("indicator screen rect tracks source's visible domain via overview.dataToScreen", () => {
|
|
80
|
+
const { source, overview } = makePair();
|
|
81
|
+
const nav = createDataNavigator({
|
|
82
|
+
source,
|
|
83
|
+
overview,
|
|
84
|
+
render: (target) => {
|
|
85
|
+
target.pushRect({ x: 0, y: 0, width: 1, height: 1, fill: RED });
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const indicator = nav.layers[nav.layers.length - 1];
|
|
90
|
+
expect(indicator._pack.commands.length).toBe(1);
|
|
91
|
+
|
|
92
|
+
source.setVisibleDomain({ x: [25, 75] });
|
|
93
|
+
expect(indicator.shapeCount).toBe(1);
|
|
94
|
+
const left = overview.dataToScreen(25, 0).x;
|
|
95
|
+
const right = overview.dataToScreen(75, 0).x;
|
|
96
|
+
expect(right - left).toBeCloseTo(200, 5);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("drag in overview pans source's visible X domain", () => {
|
|
100
|
+
const { source, overview } = makePair();
|
|
101
|
+
source.setVisibleDomain({ x: [40, 60] });
|
|
102
|
+
const fake = fakeManager();
|
|
103
|
+
createDataNavigator({
|
|
104
|
+
source,
|
|
105
|
+
overview,
|
|
106
|
+
render: () => {},
|
|
107
|
+
interaction: { manager: fake.manager },
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const spec = fake.spec();
|
|
111
|
+
const press = (x: number, y: number): PointerInfo => ({
|
|
112
|
+
pointerId: 1,
|
|
113
|
+
type: "mouse",
|
|
114
|
+
x,
|
|
115
|
+
y,
|
|
116
|
+
localX: x,
|
|
117
|
+
localY: y,
|
|
118
|
+
buttons: 1,
|
|
119
|
+
mods: { shift: false, ctrl: false, meta: false, alt: false },
|
|
120
|
+
stopPropagation: () => {},
|
|
121
|
+
});
|
|
122
|
+
const drag = (x: number, y: number, dx: number, dy: number): DragPointerInfo => ({
|
|
123
|
+
...press(x, y),
|
|
124
|
+
dx,
|
|
125
|
+
dy,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
spec?.onDragStart?.(press(200, 150));
|
|
129
|
+
spec?.onDragMove?.(drag(280, 150, 80, 0));
|
|
130
|
+
const x = source.visibleXDomain as readonly [number, number];
|
|
131
|
+
// 80 px in a 400 px / 100-unit overview → +20 units.
|
|
132
|
+
expect(x[0]).toBeCloseTo(60, 4);
|
|
133
|
+
expect(x[1]).toBeCloseTo(80, 4);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("axes:'x' drag on left edge sets source's visible-x lower bound", () => {
|
|
137
|
+
const { source, overview } = makePair();
|
|
138
|
+
source.setVisibleDomain({ x: [40, 60] });
|
|
139
|
+
const fake = fakeManager();
|
|
140
|
+
createDataNavigator({
|
|
141
|
+
source,
|
|
142
|
+
overview,
|
|
143
|
+
axes: "x",
|
|
144
|
+
render: () => {},
|
|
145
|
+
interaction: { manager: fake.manager },
|
|
146
|
+
});
|
|
147
|
+
const spec = fake.spec();
|
|
148
|
+
const press = (x: number, y: number): PointerInfo => ({
|
|
149
|
+
pointerId: 1,
|
|
150
|
+
type: "mouse",
|
|
151
|
+
x,
|
|
152
|
+
y,
|
|
153
|
+
localX: x,
|
|
154
|
+
localY: y,
|
|
155
|
+
buttons: 1,
|
|
156
|
+
mods: { shift: false, ctrl: false, meta: false, alt: false },
|
|
157
|
+
stopPropagation: () => {},
|
|
158
|
+
});
|
|
159
|
+
const drag = (x: number, y: number, dx: number, dy: number): DragPointerInfo => ({
|
|
160
|
+
...press(x, y),
|
|
161
|
+
dx,
|
|
162
|
+
dy,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Left edge near screen x = 160.
|
|
166
|
+
spec?.onPress?.(press(161, 150));
|
|
167
|
+
spec?.onDragStart?.(press(161, 150));
|
|
168
|
+
spec?.onDragMove?.(drag(80, 150, -81, 0));
|
|
169
|
+
|
|
170
|
+
const x = source.visibleXDomain as readonly [number, number];
|
|
171
|
+
expect(x[0]).toBeCloseTo(20, 5);
|
|
172
|
+
expect(x[1]).toBeCloseTo(60, 5);
|
|
173
|
+
});
|
|
174
|
+
});
|
package/src/navigator.ts
ADDED
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createLayer,
|
|
3
|
+
rgba,
|
|
4
|
+
type Color,
|
|
5
|
+
type FrameRect,
|
|
6
|
+
type InteractionManager,
|
|
7
|
+
type InteractionNode,
|
|
8
|
+
type Layer,
|
|
9
|
+
} from "insomni";
|
|
10
|
+
import type { DataViewport, VisibleDomainInput } from "./viewport.ts";
|
|
11
|
+
|
|
12
|
+
const DEFAULT_INDICATOR_FILL = rgba(1, 1, 1, 0.08);
|
|
13
|
+
const DEFAULT_INDICATOR_STROKE = rgba(1, 1, 1, 0.85);
|
|
14
|
+
|
|
15
|
+
/** Which axes the navigator's indicator (and interaction) tracks. */
|
|
16
|
+
export type DataNavigatorAxes = "xy" | "x" | "y";
|
|
17
|
+
|
|
18
|
+
export interface DataNavigatorIndicatorOptions {
|
|
19
|
+
fill?: Color;
|
|
20
|
+
stroke?: Color;
|
|
21
|
+
strokeWidth?: number;
|
|
22
|
+
cornerRadius?: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface DataNavigatorInteractionOptions {
|
|
26
|
+
manager: InteractionManager;
|
|
27
|
+
/** Drag inside the overview to pan/reframe the source. Default: true. */
|
|
28
|
+
drag?: boolean;
|
|
29
|
+
/** Press outside the indicator to recenter. Default: `"center"`. */
|
|
30
|
+
click?: "center" | false;
|
|
31
|
+
/**
|
|
32
|
+
* Drag the indicator's outer edges to reframe the source instead of panning.
|
|
33
|
+
* 1D-only — only meaningful for `axes: "x"` or `"y"`. Default: `true` for 1D,
|
|
34
|
+
* `false` for `"xy"`.
|
|
35
|
+
*/
|
|
36
|
+
resizeEdges?: boolean;
|
|
37
|
+
/** Edge hit-zone width in CSS pixels. Default: 6. */
|
|
38
|
+
edgeHitPx?: number;
|
|
39
|
+
/** Override the interaction-node bounds. Default: `overview.absoluteFrame`. */
|
|
40
|
+
bounds?: () => FrameRect;
|
|
41
|
+
/** Interaction node z-index. Default: 50. */
|
|
42
|
+
zIndex?: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface DataNavigatorOptions<X = unknown, Y = unknown> {
|
|
46
|
+
/** Source data viewport the user navigates in the main view. */
|
|
47
|
+
source: DataViewport<X, Y>;
|
|
48
|
+
/** Overview data viewport that hosts the navigator. */
|
|
49
|
+
overview: DataViewport<X, Y>;
|
|
50
|
+
/** Required: callback that populates the navigator-owned content layer. */
|
|
51
|
+
render: (target: Layer) => void;
|
|
52
|
+
/** Which axes the indicator tracks. Default: `"xy"`. */
|
|
53
|
+
axes?: DataNavigatorAxes;
|
|
54
|
+
indicator?: DataNavigatorIndicatorOptions;
|
|
55
|
+
interaction?: DataNavigatorInteractionOptions;
|
|
56
|
+
/** Bake the render-callback target to an offscreen texture. */
|
|
57
|
+
cache?: {
|
|
58
|
+
renderer: { cacheLayer(layer: Layer): void };
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface DataNavigator {
|
|
63
|
+
readonly layers: readonly Layer[];
|
|
64
|
+
readonly interacting: boolean;
|
|
65
|
+
refresh(): void;
|
|
66
|
+
dispose(): void;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Build a Navigator for a data viewport: a secondary view of the same data
|
|
71
|
+
* with a rect that tracks the source's visible domain. Drag inside the
|
|
72
|
+
* overview pans/reframes the source.
|
|
73
|
+
*/
|
|
74
|
+
export function createDataNavigator<X = unknown, Y = unknown>(
|
|
75
|
+
opts: DataNavigatorOptions<X, Y>,
|
|
76
|
+
): DataNavigator {
|
|
77
|
+
const source = opts.source as DataViewport;
|
|
78
|
+
const overview = opts.overview as DataViewport;
|
|
79
|
+
const axes: DataNavigatorAxes = opts.axes ?? "xy";
|
|
80
|
+
const renderFn = opts.render;
|
|
81
|
+
const cache = opts.cache;
|
|
82
|
+
const interactionState = { active: false };
|
|
83
|
+
|
|
84
|
+
// v3 `LayerOptions` no longer accepts `viewport`. A `DataViewport` uses an
|
|
85
|
+
// identity camera (data-mode pan/zoom flows through the axis scales, not the
|
|
86
|
+
// camera), so binding the overview camera here was a no-op transform — the
|
|
87
|
+
// navigator content is already emitted in overview pixel space by `renderFn`.
|
|
88
|
+
//
|
|
89
|
+
// v3 cache: the old v1 `{ cache: "texture" }` option became the `cache` HINT
|
|
90
|
+
// ("auto"|"always"|"never"). The navigator's overview content is static
|
|
91
|
+
// between source-domain changes, so an opted-in cache maps to "always" (force
|
|
92
|
+
// a retained RTT bake). The old v1 `cacheSize` had no v3 equivalent — the core
|
|
93
|
+
// bakes at canvas resolution — so it was dropped.
|
|
94
|
+
const target = createLayer({
|
|
95
|
+
space: "ui",
|
|
96
|
+
...(cache ? { cache: "always" as const } : {}),
|
|
97
|
+
});
|
|
98
|
+
const indicator = createLayer({ space: "ui" });
|
|
99
|
+
const style = resolveIndicatorStyle(opts.indicator);
|
|
100
|
+
|
|
101
|
+
const refreshContent = () => {
|
|
102
|
+
target.clear();
|
|
103
|
+
renderFn(target);
|
|
104
|
+
if (cache) cache.renderer.cacheLayer(target);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const refreshIndicator = () => {
|
|
108
|
+
indicator.clear();
|
|
109
|
+
const rect = computeDataIndicatorRect(source, overview, axes);
|
|
110
|
+
if (!rect) return;
|
|
111
|
+
indicator.pushRect({
|
|
112
|
+
x: rect.x,
|
|
113
|
+
y: rect.y,
|
|
114
|
+
width: rect.width,
|
|
115
|
+
height: rect.height,
|
|
116
|
+
fill: style.fill,
|
|
117
|
+
stroke: style.stroke,
|
|
118
|
+
strokeWidth: style.strokeWidth,
|
|
119
|
+
cornerRadius: style.cornerRadius,
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const refresh = () => {
|
|
124
|
+
refreshContent();
|
|
125
|
+
refreshIndicator();
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
refresh();
|
|
129
|
+
|
|
130
|
+
// Indicator depends on both source's visible domain and overview's domain
|
|
131
|
+
// (dataToScreen). Content depends on overview's domain.
|
|
132
|
+
const unsubSource = source.onChange(refreshIndicator);
|
|
133
|
+
const unsubOverview = overview.onChange(refresh);
|
|
134
|
+
|
|
135
|
+
const node = opts.interaction
|
|
136
|
+
? bindDataInteraction(opts.interaction, source, overview, axes, interactionState)
|
|
137
|
+
: null;
|
|
138
|
+
|
|
139
|
+
let disposed = false;
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
layers: [target, indicator],
|
|
143
|
+
get interacting() {
|
|
144
|
+
return interactionState.active;
|
|
145
|
+
},
|
|
146
|
+
refresh,
|
|
147
|
+
dispose() {
|
|
148
|
+
if (disposed) return;
|
|
149
|
+
disposed = true;
|
|
150
|
+
unsubSource();
|
|
151
|
+
unsubOverview();
|
|
152
|
+
node?.destroy();
|
|
153
|
+
target.destroy();
|
|
154
|
+
indicator.destroy();
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function bindDataInteraction(
|
|
160
|
+
opts: DataNavigatorInteractionOptions,
|
|
161
|
+
source: DataViewport,
|
|
162
|
+
overview: DataViewport,
|
|
163
|
+
axes: DataNavigatorAxes,
|
|
164
|
+
interactionState: { active: boolean },
|
|
165
|
+
): InteractionNode {
|
|
166
|
+
const dragEnabled = opts.drag ?? true;
|
|
167
|
+
const clickMode = opts.click ?? "center";
|
|
168
|
+
const useX = axes === "xy" || axes === "x";
|
|
169
|
+
const useY = axes === "xy" || axes === "y";
|
|
170
|
+
const resizeEnabled = opts.resizeEdges ?? axes !== "xy";
|
|
171
|
+
const edgeHitPx = opts.edgeHitPx ?? 6;
|
|
172
|
+
|
|
173
|
+
type DragMode = "pan" | "resize-x0" | "resize-x1" | "resize-y0" | "resize-y1";
|
|
174
|
+
let mode: DragMode = "pan";
|
|
175
|
+
let startDataX = 0;
|
|
176
|
+
let startDataY = 0;
|
|
177
|
+
let startSrcX0 = 0;
|
|
178
|
+
let startSrcX1 = 0;
|
|
179
|
+
let startSrcY0 = 0;
|
|
180
|
+
let startSrcY1 = 0;
|
|
181
|
+
let dragHasX = false;
|
|
182
|
+
let dragHasY = false;
|
|
183
|
+
let dragging = false;
|
|
184
|
+
|
|
185
|
+
const detectEdge = (evt: { x: number; y: number }): DragMode | null => {
|
|
186
|
+
if (!resizeEnabled) return null;
|
|
187
|
+
const r = computeDataIndicatorRect(source, overview, axes);
|
|
188
|
+
if (!r) return null;
|
|
189
|
+
if (axes === "x") {
|
|
190
|
+
if (Math.abs(evt.x - r.x) <= edgeHitPx) return "resize-x0";
|
|
191
|
+
if (Math.abs(evt.x - (r.x + r.width)) <= edgeHitPx) return "resize-x1";
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
if (axes === "y") {
|
|
195
|
+
if (Math.abs(evt.y - r.y) <= edgeHitPx) return "resize-y0";
|
|
196
|
+
if (Math.abs(evt.y - (r.y + r.height)) <= edgeHitPx) return "resize-y1";
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
return null;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
return opts.manager.add({
|
|
203
|
+
zIndex: opts.zIndex ?? 50,
|
|
204
|
+
space: "ui",
|
|
205
|
+
bounds: opts.bounds ?? (() => overview.absoluteFrame),
|
|
206
|
+
cursor: dragEnabled ? "grab" : "pointer",
|
|
207
|
+
dragCursor: "grabbing",
|
|
208
|
+
|
|
209
|
+
onPress: (evt) => {
|
|
210
|
+
const edge = detectEdge(evt);
|
|
211
|
+
if (edge) {
|
|
212
|
+
mode = edge;
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const indRect = computeDataIndicatorRect(source, overview, axes);
|
|
216
|
+
if (indRect) {
|
|
217
|
+
const inside =
|
|
218
|
+
evt.x >= indRect.x &&
|
|
219
|
+
evt.x <= indRect.x + indRect.width &&
|
|
220
|
+
evt.y >= indRect.y &&
|
|
221
|
+
evt.y <= indRect.y + indRect.height;
|
|
222
|
+
if (inside) {
|
|
223
|
+
mode = "pan";
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
mode = "pan";
|
|
228
|
+
if (clickMode !== "center") return;
|
|
229
|
+
const data = overview.screenToData(evt.x, evt.y);
|
|
230
|
+
if (!data) return;
|
|
231
|
+
const update: { x?: VisibleDomainInput; y?: VisibleDomainInput } = {};
|
|
232
|
+
if (useX) {
|
|
233
|
+
const span = visibleNumericSpan(source, "x");
|
|
234
|
+
if (span !== null) {
|
|
235
|
+
const c = toNumber(data.x);
|
|
236
|
+
update.x = [c - span / 2, c + span / 2];
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (useY) {
|
|
240
|
+
const span = visibleNumericSpan(source, "y");
|
|
241
|
+
if (span !== null) {
|
|
242
|
+
const c = toNumber(data.y);
|
|
243
|
+
update.y = [c - span / 2, c + span / 2];
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
source.setVisibleDomain(update);
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
onDragStart: dragEnabled
|
|
250
|
+
? (evt) => {
|
|
251
|
+
const data = overview.screenToData(evt.x, evt.y);
|
|
252
|
+
if (!data) return;
|
|
253
|
+
startDataX = toNumber(data.x);
|
|
254
|
+
startDataY = toNumber(data.y);
|
|
255
|
+
dragHasX = false;
|
|
256
|
+
dragHasY = false;
|
|
257
|
+
if (useX) {
|
|
258
|
+
const dom = source.visibleXDomain;
|
|
259
|
+
if (Array.isArray(dom) && dom.length === 2) {
|
|
260
|
+
startSrcX0 = toNumber(dom[0]);
|
|
261
|
+
startSrcX1 = toNumber(dom[1]);
|
|
262
|
+
dragHasX = Number.isFinite(startSrcX0) && Number.isFinite(startSrcX1);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (useY) {
|
|
266
|
+
const dom = source.visibleYDomain;
|
|
267
|
+
if (Array.isArray(dom) && dom.length === 2) {
|
|
268
|
+
startSrcY0 = toNumber(dom[0]);
|
|
269
|
+
startSrcY1 = toNumber(dom[1]);
|
|
270
|
+
dragHasY = Number.isFinite(startSrcY0) && Number.isFinite(startSrcY1);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
dragging = dragHasX || dragHasY;
|
|
274
|
+
interactionState.active = dragging;
|
|
275
|
+
}
|
|
276
|
+
: undefined,
|
|
277
|
+
onDragMove: dragEnabled
|
|
278
|
+
? (evt) => {
|
|
279
|
+
if (!dragging) return;
|
|
280
|
+
const data = overview.screenToData(evt.x, evt.y);
|
|
281
|
+
if (!data) return;
|
|
282
|
+
if (mode === "resize-x0" && dragHasX) {
|
|
283
|
+
source.setVisibleDomain({ x: [toNumber(data.x), startSrcX1] });
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (mode === "resize-x1" && dragHasX) {
|
|
287
|
+
source.setVisibleDomain({ x: [startSrcX0, toNumber(data.x)] });
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (mode === "resize-y0" && dragHasY) {
|
|
291
|
+
source.setVisibleDomain({ y: [toNumber(data.y), startSrcY1] });
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (mode === "resize-y1" && dragHasY) {
|
|
295
|
+
source.setVisibleDomain({ y: [startSrcY0, toNumber(data.y)] });
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const update: { x?: VisibleDomainInput; y?: VisibleDomainInput } = {};
|
|
299
|
+
if (dragHasX) {
|
|
300
|
+
const dx = toNumber(data.x) - startDataX;
|
|
301
|
+
update.x = [startSrcX0 + dx, startSrcX1 + dx];
|
|
302
|
+
}
|
|
303
|
+
if (dragHasY) {
|
|
304
|
+
const dy = toNumber(data.y) - startDataY;
|
|
305
|
+
update.y = [startSrcY0 + dy, startSrcY1 + dy];
|
|
306
|
+
}
|
|
307
|
+
source.setVisibleDomain(update);
|
|
308
|
+
}
|
|
309
|
+
: undefined,
|
|
310
|
+
onDragEnd: dragEnabled
|
|
311
|
+
? () => {
|
|
312
|
+
dragging = false;
|
|
313
|
+
interactionState.active = false;
|
|
314
|
+
mode = "pan";
|
|
315
|
+
}
|
|
316
|
+
: undefined,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// ---------------------------------------------------------------------------
|
|
321
|
+
// Helpers
|
|
322
|
+
// ---------------------------------------------------------------------------
|
|
323
|
+
|
|
324
|
+
interface ResolvedStyle {
|
|
325
|
+
fill: Color;
|
|
326
|
+
stroke: Color;
|
|
327
|
+
strokeWidth: number;
|
|
328
|
+
cornerRadius: number;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function resolveIndicatorStyle(opts: DataNavigatorIndicatorOptions | undefined): ResolvedStyle {
|
|
332
|
+
return {
|
|
333
|
+
fill: opts?.fill ?? DEFAULT_INDICATOR_FILL,
|
|
334
|
+
stroke: opts?.stroke ?? DEFAULT_INDICATOR_STROKE,
|
|
335
|
+
strokeWidth: opts?.strokeWidth ?? 1,
|
|
336
|
+
cornerRadius: opts?.cornerRadius ?? 0,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function toNumber(v: unknown): number {
|
|
341
|
+
if (typeof v === "number") return v;
|
|
342
|
+
if (v instanceof Date) return v.getTime();
|
|
343
|
+
return Number(v);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function visibleNumericSpan(vp: DataViewport, axis: "x" | "y"): number | null {
|
|
347
|
+
const dom = axis === "x" ? vp.visibleXDomain : vp.visibleYDomain;
|
|
348
|
+
if (!Array.isArray(dom) || dom.length !== 2) return null;
|
|
349
|
+
const a = toNumber(dom[0]);
|
|
350
|
+
const b = toNumber(dom[1]);
|
|
351
|
+
if (!Number.isFinite(a) || !Number.isFinite(b)) return null;
|
|
352
|
+
return b - a;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function computeDataIndicatorRect(
|
|
356
|
+
source: DataViewport,
|
|
357
|
+
overview: DataViewport,
|
|
358
|
+
axes: DataNavigatorAxes,
|
|
359
|
+
): FrameRect | null {
|
|
360
|
+
const useX = axes === "xy" || axes === "x";
|
|
361
|
+
const useY = axes === "xy" || axes === "y";
|
|
362
|
+
const f = overview.absoluteFrame;
|
|
363
|
+
|
|
364
|
+
let x = f.x;
|
|
365
|
+
let width = f.width;
|
|
366
|
+
let y = f.y;
|
|
367
|
+
let height = f.height;
|
|
368
|
+
|
|
369
|
+
const srcX = source.visibleXDomain;
|
|
370
|
+
const srcY = source.visibleYDomain;
|
|
371
|
+
|
|
372
|
+
// Pick a Y anchor for X-axis pixel mapping (and vice versa). Any value works
|
|
373
|
+
// — dataToScreen treats axes independently for continuous/time inputs.
|
|
374
|
+
const yAnchor = Array.isArray(srcY) && srcY.length > 0 ? srcY[0] : null;
|
|
375
|
+
const xAnchor = Array.isArray(srcX) && srcX.length > 0 ? srcX[0] : null;
|
|
376
|
+
|
|
377
|
+
if (useX) {
|
|
378
|
+
if (!Array.isArray(srcX) || srcX.length !== 2 || yAnchor === null) return null;
|
|
379
|
+
const left = overview.dataToScreen(srcX[0] as never, yAnchor as never);
|
|
380
|
+
const right = overview.dataToScreen(srcX[1] as never, yAnchor as never);
|
|
381
|
+
x = Math.min(left.x, right.x);
|
|
382
|
+
width = Math.abs(right.x - left.x);
|
|
383
|
+
}
|
|
384
|
+
if (useY) {
|
|
385
|
+
if (!Array.isArray(srcY) || srcY.length !== 2 || xAnchor === null) return null;
|
|
386
|
+
const top = overview.dataToScreen(xAnchor as never, srcY[0] as never);
|
|
387
|
+
const bottom = overview.dataToScreen(xAnchor as never, srcY[1] as never);
|
|
388
|
+
y = Math.min(top.y, bottom.y);
|
|
389
|
+
height = Math.abs(bottom.y - top.y);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return { x, y, width, height };
|
|
393
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { DateDomain, NumericDomain } from "./scales.ts";
|
|
2
|
+
import type { DataViewport } from "./viewport.ts";
|
|
3
|
+
/** Axis the controller drives. */
|
|
4
|
+
export type RangePresetAxis = "x" | "y";
|
|
5
|
+
/**
|
|
6
|
+
* Resolved domain a preset applies to the viewport. Numeric pair for linear /
|
|
7
|
+
* log / score axes; Date pair for time axes.
|
|
8
|
+
*/
|
|
9
|
+
export type RangePresetDomain = NumericDomain | DateDomain;
|
|
10
|
+
/** Optional getter form for the data-extent fallback. */
|
|
11
|
+
export type RangePresetDataDomain = RangePresetDomain | (() => RangePresetDomain);
|
|
12
|
+
/**
|
|
13
|
+
* Context passed to a preset's `resolve` function. `dataDomain` is the full
|
|
14
|
+
* extent of the underlying data (either provided by the consumer at
|
|
15
|
+
* construction time or, if omitted, the viewport's current visible domain
|
|
16
|
+
* sampled at construction). `now` is the current time (epoch ms) — relevant
|
|
17
|
+
* for time presets; helpers like `linearPreset` ignore it.
|
|
18
|
+
*/
|
|
19
|
+
export interface RangePresetContext {
|
|
20
|
+
dataDomain: RangePresetDomain;
|
|
21
|
+
now: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A single preset entry. `resolve` returns the domain the viewport should be
|
|
25
|
+
* set to when the user activates this key. Return `null` to express "this
|
|
26
|
+
* preset has no valid domain given the current data" — the controller will
|
|
27
|
+
* leave the viewport untouched and not mark itself active.
|
|
28
|
+
*/
|
|
29
|
+
export interface RangePreset {
|
|
30
|
+
key: string;
|
|
31
|
+
label: string;
|
|
32
|
+
resolve: (ctx: RangePresetContext) => RangePresetDomain | null;
|
|
33
|
+
}
|
|
34
|
+
export interface RangePresetsOptions {
|
|
35
|
+
/**
|
|
36
|
+
* Any `DataViewport` regardless of axis-value type — accepts the
|
|
37
|
+
* `DataViewport<number, number>` returned by `MountedPlot.viewport` as
|
|
38
|
+
* well as the unparameterized form. The controller only reads visible
|
|
39
|
+
* domains and applies new ones through the viewport's continuous-axis
|
|
40
|
+
* API, so X/Y value types are not constrained at this layer.
|
|
41
|
+
*/
|
|
42
|
+
viewport: DataViewport<any, any>;
|
|
43
|
+
axis: RangePresetAxis;
|
|
44
|
+
presets: readonly RangePreset[];
|
|
45
|
+
/**
|
|
46
|
+
* Full data extent. Either a fixed `[min, max]` (numbers or Dates) or a
|
|
47
|
+
* getter that re-reads on each preset evaluation. If omitted, the
|
|
48
|
+
* controller snapshots the viewport's current visible domain at
|
|
49
|
+
* construction time and reuses it as the data domain.
|
|
50
|
+
*/
|
|
51
|
+
dataDomain?: RangePresetDataDomain;
|
|
52
|
+
/** Defaults to `Date.now`. Re-evaluated on every `setActive`. */
|
|
53
|
+
now?: () => number;
|
|
54
|
+
/**
|
|
55
|
+
* Relative tolerance for the diff-against-snapshot custom check. A change
|
|
56
|
+
* smaller than `epsilon * max(|a|, |b|, 1)` on either endpoint is ignored.
|
|
57
|
+
* Defaults to `1e-6` — tight enough to detect a one-pixel pan, loose
|
|
58
|
+
* enough to absorb float round-trips through the axis scale.
|
|
59
|
+
*/
|
|
60
|
+
epsilon?: number;
|
|
61
|
+
}
|
|
62
|
+
/** Subscriber receives the new active key, or `null` for "custom". */
|
|
63
|
+
export type RangePresetsSubscriber = (active: string | null) => void;
|
|
64
|
+
export interface RangePresetsController {
|
|
65
|
+
readonly presets: readonly RangePreset[];
|
|
66
|
+
/**
|
|
67
|
+
* Activate a preset by key. Re-resolves the preset, applies the resulting
|
|
68
|
+
* domain to the viewport, and remembers the resolved snapshot so future
|
|
69
|
+
* `onChange` events can detect manual changes. Passing `null` clears the
|
|
70
|
+
* active state without touching the viewport.
|
|
71
|
+
*/
|
|
72
|
+
setActive(key: string | null): void;
|
|
73
|
+
/** Currently active preset key, or `null` if user has panned/zoomed manually. */
|
|
74
|
+
getActive(): string | null;
|
|
75
|
+
/** Subscribe to active-state changes. Returns an unsubscribe fn. */
|
|
76
|
+
subscribe(fn: RangePresetsSubscriber): () => void;
|
|
77
|
+
dispose(): void;
|
|
78
|
+
}
|
|
79
|
+
export declare function createRangePresets(opts: RangePresetsOptions): RangePresetsController;
|
|
80
|
+
/**
|
|
81
|
+
* Recognized time-preset keys. Each maps to a window relative to `now`
|
|
82
|
+
* (except `MAX`, which uses the full data extent, and `YTD` which goes
|
|
83
|
+
* from January 1 of the current year to `now`).
|
|
84
|
+
*/
|
|
85
|
+
export type TimePresetKey = "24H" | "7D" | "1M" | "3M" | "6M" | "1Y" | "YTD" | "MAX";
|
|
86
|
+
/**
|
|
87
|
+
* Time-axis preset. Resolves to a `DateDomain` ending at `now` (or the data
|
|
88
|
+
* domain's end, for `MAX`). Built-in months use calendar-approximate
|
|
89
|
+
* constants (30/90/182/365 days) — close enough for chart UI.
|
|
90
|
+
*/
|
|
91
|
+
export declare function timePreset(key: TimePresetKey, label?: string): RangePreset;
|
|
92
|
+
/**
|
|
93
|
+
* Linear / numeric preset showing the last `span` units of `dataDomain` —
|
|
94
|
+
* i.e. `[max - span, max]`. Useful for "last 100m", "last 50 errors", etc.
|
|
95
|
+
* `MAX`-style behavior comes from passing `Infinity` (will clamp to the
|
|
96
|
+
* full data domain).
|
|
97
|
+
*/
|
|
98
|
+
export declare function linearPreset(opts: {
|
|
99
|
+
key: string;
|
|
100
|
+
label: string;
|
|
101
|
+
span: number;
|
|
102
|
+
}): RangePreset;
|
|
103
|
+
/**
|
|
104
|
+
* Log-axis preset showing the last `decades` decades of the data — i.e.
|
|
105
|
+
* `[max / 10^decades, max]`, clamped to the data domain's lower bound.
|
|
106
|
+
* The viewport itself stays linear-or-log depending on its scale config;
|
|
107
|
+
* this helper just picks the domain endpoints.
|
|
108
|
+
*/
|
|
109
|
+
export declare function logPreset(opts: {
|
|
110
|
+
key: string;
|
|
111
|
+
label: string;
|
|
112
|
+
decades: number;
|
|
113
|
+
}): RangePreset;
|