gloomberb 0.10.3 → 0.10.4
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/README.md +3 -3
- package/README.zh-CN.md +2 -2
- package/package.json +2 -1
- package/src/api-client/auth.ts +8 -0
- package/src/api-client/index.ts +70 -0
- package/src/api-client/types.ts +49 -0
- package/src/app/runtime/desktop-deeplink.test.ts +8 -0
- package/src/app/runtime/desktop-deeplink.ts +14 -1
- package/src/components/chart/composite/composite-chart.test.tsx +340 -17
- package/src/components/chart/composite/composite-chart.tsx +859 -68
- package/src/components/chart/composite/interactions.test.ts +78 -22
- package/src/components/chart/composite/interactions.ts +45 -17
- package/src/components/chart/composite/rasterizer.ts +8 -2
- package/src/components/chart/composite/renderers.test.ts +61 -1
- package/src/components/chart/composite/scene.ts +14 -4
- package/src/components/chart/composite/tools.test.ts +371 -0
- package/src/components/chart/composite/tools.ts +508 -0
- package/src/components/chart/composite/types.ts +4 -0
- package/src/components/chart/core/pointer.ts +2 -0
- package/src/components/chart/native/raster/primitives.ts +18 -0
- package/src/components/chart/price-axis-labels.tsx +42 -2
- package/src/components/chart/static/chart/axis-overlays.test.tsx +2 -0
- package/src/components/chart/static/chart/axis-overlays.tsx +46 -0
- package/src/components/command-bar/action-runtime.ts +3 -0
- package/src/components/command-bar/assist/inventory.test.ts +93 -0
- package/src/components/command-bar/assist/inventory.ts +92 -0
- package/src/components/command-bar/assist/model.test.ts +104 -0
- package/src/components/command-bar/assist/model.ts +169 -0
- package/src/components/command-bar/assist/runtime.ts +185 -0
- package/src/components/command-bar/commands/direct/index.ts +1 -1
- package/src/components/command-bar/keyboard-shortcuts.ts +5 -0
- package/src/components/command-bar/list/model.ts +15 -2
- package/src/components/command-bar/list/navigation.ts +6 -0
- package/src/components/command-bar/list/view.tsx +16 -3
- package/src/components/command-bar/pane-templates/workflow.ts +1 -0
- package/src/components/command-bar/panel/body.tsx +2 -0
- package/src/components/command-bar/panel/index.tsx +3 -0
- package/src/components/command-bar/panel/runtime.ts +6 -0
- package/src/components/command-bar/panel/types.ts +1 -0
- package/src/components/command-bar/routes/root/results.ts +28 -2
- package/src/components/command-bar/routes/root/runtime.ts +66 -36
- package/src/components/command-bar/routes/root/shortcuts.ts +8 -4
- package/src/components/command-bar/routing/navigation-state.ts +17 -0
- package/src/components/command-bar/selection-runtime.ts +26 -0
- package/src/components/command-bar/surface/assist.test.tsx +276 -0
- package/src/components/command-bar/surface/index.tsx +93 -2
- package/src/components/command-bar/surface/portfolio.test.tsx +2 -1
- package/src/components/command-bar/view-model.ts +7 -1
- package/src/components/command-bar/workflow/broker.test.ts +22 -0
- package/src/components/command-bar/workflow/broker.ts +1 -0
- package/src/components/layout/detached-pane-shell.tsx +40 -2
- package/src/components/layout/floating-pane.tsx +4 -1
- package/src/components/layout/pane/header.tsx +64 -6
- package/src/components/layout/pane/index.tsx +4 -1
- package/src/components/layout/shell/index.tsx +23 -0
- package/src/components/layout/shell/pane/layers.tsx +5 -0
- package/src/components/onboarding/account-step/chooser-panel.tsx +74 -0
- package/src/components/onboarding/account-step/form-panel.tsx +157 -0
- package/src/components/onboarding/account-step/index.tsx +58 -0
- package/src/components/onboarding/account-step/model.test.ts +67 -0
- package/src/components/onboarding/account-step/model.ts +126 -0
- package/src/components/onboarding/account-step/signed-in-panel.tsx +31 -0
- package/src/components/onboarding/onboarding-steps.tsx +91 -55
- package/src/components/onboarding/onboarding-wizard.test.tsx +73 -6
- package/src/components/onboarding/onboarding-wizard.tsx +93 -9
- package/src/components/onboarding/portfolio-step/broker-fields-panel.tsx +3 -3
- package/src/components/onboarding/wizard-account.ts +235 -0
- package/src/components/onboarding/wizard-keyboard.ts +67 -1
- package/src/components/onboarding/wizard-model.ts +2 -2
- package/src/i18n/es.ts +1093 -0
- package/src/i18n/index.test.ts +5 -2
- package/src/i18n/index.ts +3 -0
- package/src/i18n/ja.ts +64 -3
- package/src/i18n/ko.ts +64 -3
- package/src/i18n/languages.test.ts +4 -1
- package/src/i18n/languages.ts +12 -1
- package/src/i18n/zh-cn.ts +64 -3
- package/src/i18n/zh-tw.ts +64 -3
- package/src/market-data/coordinator/quotes.ts +63 -1
- package/src/market-data/coordinator/subscriptions.test.ts +33 -0
- package/src/market-data/quotes/cadence.ts +2 -0
- package/src/plugins/builtin/account-management/footer.ts +9 -3
- package/src/plugins/builtin/account-management/model.ts +53 -1
- package/src/plugins/builtin/account-management/pane.tsx +107 -24
- package/src/plugins/builtin/ai/index.tsx +7 -2
- package/src/plugins/builtin/ai/screener/market-runtime.ts +4 -2
- package/src/plugins/builtin/ai/screener/pane.tsx +3 -0
- package/src/plugins/builtin/chart-composer/editor.tsx +0 -1
- package/src/plugins/builtin/chart-composer/index.tsx +10 -2
- package/src/plugins/builtin/chart-composer/pane.tsx +111 -44
- package/src/plugins/builtin/chart-composer/quick-add.tsx +27 -3
- package/src/plugins/builtin/chart-composer/semantic.ts +21 -0
- package/src/plugins/builtin/chat/controller/persistence.ts +5 -0
- package/src/plugins/builtin/cloud/plugin.tsx +9 -1
- package/src/plugins/builtin/cloud/upgrade-command.ts +28 -0
- package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +54 -0
- package/src/plugins/builtin/help/index.tsx +38 -0
- package/src/plugins/builtin/news/wire/news/footer.ts +18 -38
- package/src/plugins/builtin/options/footer.test.ts +20 -124
- package/src/plugins/builtin/options/footer.ts +32 -90
- package/src/plugins/builtin/options/index.tsx +6 -0
- package/src/plugins/builtin/options/view.tsx +3 -0
- package/src/plugins/builtin/portfolio-list/index.tsx +12 -4
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +22 -1
- package/src/plugins/builtin/portfolio-list/pane/streaming.ts +17 -13
- package/src/plugins/builtin/portfolio-list/use-throttled-ticker-order.test.tsx +76 -0
- package/src/plugins/builtin/portfolio-list/use-throttled-ticker-order.ts +78 -0
- package/src/plugins/builtin/shared/cloud-upgrade.ts +131 -0
- package/src/plugins/builtin/shared/live-streaming.ts +60 -0
- package/src/plugins/builtin/shared/plan-access.test.ts +69 -0
- package/src/plugins/builtin/shared/plan-access.ts +109 -0
- package/src/plugins/builtin/ticker-detail/chart-tab-switch.test.tsx +1 -1
- package/src/plugins/builtin/ticker-detail/index.tsx +11 -2
- package/src/plugins/builtin/ticker-detail/pane.tsx +21 -3
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +4 -2
- package/src/plugins/registry/index.test.ts +51 -0
- package/src/plugins/registry/index.ts +12 -0
- package/src/plugins/registry/pane-settings.ts +29 -0
- package/src/renderers/electrobun/view/host/box.test.tsx +97 -0
- package/src/renderers/electrobun/view/host/box.tsx +3 -0
- package/src/renderers/electrobun/view/host/chart-surface.tsx +85 -1
- package/src/renderers/electrobun/view/host/focus-scope.test.ts +37 -0
- package/src/renderers/electrobun/view/host/focus-scope.ts +56 -0
- package/src/renderers/electrobun/view/host/input.tsx +16 -1
- package/src/renderers/electrobun/view/host/mouse.ts +1 -0
- package/src/renderers/electrobun/view/host/style.ts +1 -1
- package/src/renderers/electrobun/view/host/tabs.tsx +4 -2
- package/src/renderers/electrobun/view/main.tsx +2 -0
- package/src/renderers/opentui/chart-surface.tsx +2 -0
- package/src/renderers/opentui/host.tsx +3 -0
- package/src/sources/gloomberb-cloud/index.test.ts +8 -0
- package/src/sources/gloomberb-cloud/index.ts +4 -4
- package/src/sources/provider-router/history.test.ts +3 -3
- package/src/sources/provider-router/history.ts +15 -4
- package/src/sources/yahoo-finance/history.test.ts +47 -0
- package/src/sources/yahoo-finance/history.ts +5 -1
- package/src/state/hooks/quote-streaming.test.tsx +124 -1
- package/src/state/hooks/quote-streaming.ts +90 -7
- package/src/theme/colors.ts +20 -1
- package/src/theme/themes.test.ts +3 -0
- package/src/{sources/gloomberb-cloud → time-series}/history-quality.ts +27 -3
- package/src/time-series/resolution.test.ts +51 -0
- package/src/time-series/resolution.ts +41 -0
- package/src/time-series/resolve.test.ts +207 -1
- package/src/time-series/resolve.ts +196 -29
- package/src/time-series/spec-studies.test.ts +4 -0
- package/src/time-series/spec.ts +15 -1
- package/src/time-series/types.ts +7 -0
- package/src/time-series/use-chart-resolution.test.tsx +37 -0
- package/src/time-series/use-chart-resolution.ts +17 -1
- package/src/types/plugin.ts +9 -0
- package/src/ui/host.tsx +17 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
buildChartToolVectors,
|
|
4
|
+
CHART_DRAWING_COLORS,
|
|
5
|
+
countMeasureBars,
|
|
6
|
+
drawChartToolOverlay,
|
|
7
|
+
hitTestDrawings,
|
|
8
|
+
nextDrawingColor,
|
|
9
|
+
shiftDrawing,
|
|
10
|
+
formatMeasureSpan,
|
|
11
|
+
resolveChartToolKind,
|
|
12
|
+
resolveDrawingFromDrag,
|
|
13
|
+
resolveZoomBoxRange,
|
|
14
|
+
summarizeMeasure,
|
|
15
|
+
} from "./tools";
|
|
16
|
+
import type {
|
|
17
|
+
CompositeAxisDomain,
|
|
18
|
+
CompositeChartScene,
|
|
19
|
+
CompositePanelScene,
|
|
20
|
+
} from "./types";
|
|
21
|
+
|
|
22
|
+
const domain: CompositeAxisDomain = {
|
|
23
|
+
side: "right",
|
|
24
|
+
min: 0,
|
|
25
|
+
max: 200,
|
|
26
|
+
scale: "linear",
|
|
27
|
+
unit: "USD",
|
|
28
|
+
unitGroup: "currency",
|
|
29
|
+
seriesIds: ["a"],
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const DAY = 24 * 60 * 60 * 1000;
|
|
33
|
+
const START = Date.UTC(2026, 0, 1);
|
|
34
|
+
|
|
35
|
+
function calendarScene(spanMs: number): CompositeChartScene {
|
|
36
|
+
return {
|
|
37
|
+
width: 100,
|
|
38
|
+
height: 20,
|
|
39
|
+
startTime: START,
|
|
40
|
+
endTime: START + spanMs,
|
|
41
|
+
timeScale: { kind: "calendar", startTime: START, endTime: START + spanMs },
|
|
42
|
+
dates: [],
|
|
43
|
+
dateRatios: [],
|
|
44
|
+
panels: [],
|
|
45
|
+
cursorDate: null,
|
|
46
|
+
cursorXRatio: null,
|
|
47
|
+
cursorValues: [],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
describe("resolveChartToolKind", () => {
|
|
52
|
+
it("leaves plain and ctrl drags to pan and wheel zoom", () => {
|
|
53
|
+
expect(resolveChartToolKind({ shift: false, alt: false, ctrl: false })).toBeNull();
|
|
54
|
+
expect(resolveChartToolKind({ shift: true, alt: false, ctrl: true })).toBeNull();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("maps shift to zoom and alt to measure", () => {
|
|
58
|
+
expect(resolveChartToolKind({ shift: true, alt: false, ctrl: false })).toBe("zoom");
|
|
59
|
+
expect(resolveChartToolKind({ shift: false, alt: true, ctrl: false })).toBe("measure");
|
|
60
|
+
expect(resolveChartToolKind({ shift: true, alt: true, ctrl: false })).toBe("zoom");
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("summarizeMeasure", () => {
|
|
65
|
+
it("reports signed change, percent, bars, and span", () => {
|
|
66
|
+
expect(summarizeMeasure({
|
|
67
|
+
startValue: 100,
|
|
68
|
+
endValue: 110,
|
|
69
|
+
startTime: START,
|
|
70
|
+
endTime: START + 3 * DAY,
|
|
71
|
+
bars: 3,
|
|
72
|
+
domain,
|
|
73
|
+
})).toBe("Δ +$10.0 (+10.00%) · 3 bars · 3d");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("keeps the percentage signed when measuring downward", () => {
|
|
77
|
+
expect(summarizeMeasure({
|
|
78
|
+
startValue: 110,
|
|
79
|
+
endValue: 99,
|
|
80
|
+
startTime: START,
|
|
81
|
+
endTime: START + DAY,
|
|
82
|
+
bars: 1,
|
|
83
|
+
domain,
|
|
84
|
+
})).toBe("Δ -$11.0 (-10.00%) · 1 bar · 1d");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("drops the percentage when the baseline is zero or flips sign", () => {
|
|
88
|
+
const fromZero = summarizeMeasure({
|
|
89
|
+
startValue: 0,
|
|
90
|
+
endValue: 5,
|
|
91
|
+
startTime: START,
|
|
92
|
+
endTime: START + DAY,
|
|
93
|
+
bars: 0,
|
|
94
|
+
domain,
|
|
95
|
+
});
|
|
96
|
+
expect(fromZero).toBe("Δ +$5.00 · 1d");
|
|
97
|
+
const acrossZero = summarizeMeasure({
|
|
98
|
+
startValue: -4,
|
|
99
|
+
endValue: 6,
|
|
100
|
+
startTime: START,
|
|
101
|
+
endTime: START + DAY,
|
|
102
|
+
bars: 0,
|
|
103
|
+
domain,
|
|
104
|
+
});
|
|
105
|
+
expect(acrossZero).not.toContain("%");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("reports a percentage relative to the magnitude of a negative baseline", () => {
|
|
109
|
+
const summary = summarizeMeasure({
|
|
110
|
+
startValue: -100,
|
|
111
|
+
endValue: -80,
|
|
112
|
+
startTime: START,
|
|
113
|
+
endTime: START + DAY,
|
|
114
|
+
bars: 0,
|
|
115
|
+
domain,
|
|
116
|
+
});
|
|
117
|
+
expect(summary).toContain("(+20.00%)");
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe("formatMeasureSpan", () => {
|
|
122
|
+
it("scales the unit to the span", () => {
|
|
123
|
+
expect(formatMeasureSpan(45_000)).toBe("45s");
|
|
124
|
+
expect(formatMeasureSpan(90 * 60_000)).toBe("1h 30m");
|
|
125
|
+
expect(formatMeasureSpan(2 * DAY + 3 * 60 * 60_000)).toBe("2d 3h");
|
|
126
|
+
expect(formatMeasureSpan(30 * DAY)).toBe("30d");
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe("countMeasureBars", () => {
|
|
131
|
+
it("counts observations inside the drag regardless of direction", () => {
|
|
132
|
+
const dates = [0, 1, 2, 3, 4].map((day) => new Date(START + day * DAY));
|
|
133
|
+
expect(countMeasureBars(dates, START, START + 2 * DAY)).toBe(3);
|
|
134
|
+
expect(countMeasureBars(dates, START + 2 * DAY, START)).toBe(3);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe("resolveZoomBoxRange", () => {
|
|
139
|
+
const scene = calendarScene(10 * DAY);
|
|
140
|
+
|
|
141
|
+
it("ignores a modifier-held click", () => {
|
|
142
|
+
expect(resolveZoomBoxRange(scene, {
|
|
143
|
+
kind: "zoom",
|
|
144
|
+
startXRatio: 0.5,
|
|
145
|
+
startYRatio: 0.5,
|
|
146
|
+
endXRatio: 0.501,
|
|
147
|
+
endYRatio: 0.5,
|
|
148
|
+
path: [],
|
|
149
|
+
}, DAY)).toBeNull();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("orders a right-to-left drag", () => {
|
|
153
|
+
const range = resolveZoomBoxRange(scene, {
|
|
154
|
+
kind: "zoom",
|
|
155
|
+
startXRatio: 0.8,
|
|
156
|
+
startYRatio: 0,
|
|
157
|
+
endXRatio: 0.2,
|
|
158
|
+
endYRatio: 1,
|
|
159
|
+
path: [],
|
|
160
|
+
}, DAY);
|
|
161
|
+
expect(range?.start.getTime()).toBe(START + 2 * DAY);
|
|
162
|
+
expect(range?.end.getTime()).toBe(START + 8 * DAY);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("widens a selection below the smallest resolvable span", () => {
|
|
166
|
+
const range = resolveZoomBoxRange(scene, {
|
|
167
|
+
kind: "zoom",
|
|
168
|
+
startXRatio: 0.5,
|
|
169
|
+
startYRatio: 0,
|
|
170
|
+
endXRatio: 0.52,
|
|
171
|
+
endYRatio: 1,
|
|
172
|
+
path: [],
|
|
173
|
+
}, DAY);
|
|
174
|
+
expect(range).not.toBeNull();
|
|
175
|
+
expect(range!.end.getTime() - range!.start.getTime()).toBe(DAY);
|
|
176
|
+
expect((range!.start.getTime() + range!.end.getTime()) / 2).toBe(START + 5.1 * DAY);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe("chart drawings", () => {
|
|
181
|
+
const panel: CompositePanelScene = {
|
|
182
|
+
id: "main",
|
|
183
|
+
height: 10,
|
|
184
|
+
scale: "linear",
|
|
185
|
+
axes: { left: { ...domain, side: "left" } },
|
|
186
|
+
series: [{
|
|
187
|
+
source: { id: "a", axis: "left" } as never,
|
|
188
|
+
points: [],
|
|
189
|
+
}],
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
it("anchors a drawing to the data under the drag, not to the pixels", () => {
|
|
193
|
+
const scene = calendarScene(10 * DAY);
|
|
194
|
+
const drawing = resolveDrawingFromDrag(scene, panel, {
|
|
195
|
+
kind: "line",
|
|
196
|
+
startXRatio: 0.2,
|
|
197
|
+
startYRatio: 0.25,
|
|
198
|
+
endXRatio: 0.8,
|
|
199
|
+
endYRatio: 0.75,
|
|
200
|
+
path: [],
|
|
201
|
+
}, "#ffcc00", "d1");
|
|
202
|
+
|
|
203
|
+
expect(drawing?.points[0]?.time).toBe(START + 2 * DAY);
|
|
204
|
+
expect(drawing?.points[1]?.time).toBe(START + 8 * DAY);
|
|
205
|
+
// yRatio runs top-down, so 0.25 is three quarters up a 0..200 axis.
|
|
206
|
+
expect(drawing?.points[0]?.value).toBeCloseTo(150, 6);
|
|
207
|
+
expect(drawing?.points[1]?.value).toBeCloseTo(50, 6);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("redraws a stored line through the current viewport", () => {
|
|
211
|
+
const scene = calendarScene(10 * DAY);
|
|
212
|
+
const drawing = resolveDrawingFromDrag(scene, panel, {
|
|
213
|
+
kind: "line",
|
|
214
|
+
startXRatio: 0,
|
|
215
|
+
startYRatio: 0.5,
|
|
216
|
+
endXRatio: 1,
|
|
217
|
+
endYRatio: 0.5,
|
|
218
|
+
path: [],
|
|
219
|
+
}, "#ffcc00", "d1")!;
|
|
220
|
+
const base = {
|
|
221
|
+
width: 21,
|
|
222
|
+
height: 11,
|
|
223
|
+
pixels: new Uint8Array(21 * 11 * 4),
|
|
224
|
+
};
|
|
225
|
+
const painted = drawChartToolOverlay(base, null, {
|
|
226
|
+
positive: "#00ff00",
|
|
227
|
+
negative: "#ff0000",
|
|
228
|
+
zoom: "#8888ff",
|
|
229
|
+
draw: "#ffcc00",
|
|
230
|
+
}, "up", { scene, panel, items: [drawing] });
|
|
231
|
+
const alphaAt = (x: number, y: number) => painted.pixels[(y * 21 + x) * 4 + 3]!;
|
|
232
|
+
|
|
233
|
+
// A flat line at the middle value paints the middle row and nothing else.
|
|
234
|
+
expect(alphaAt(10, 5)).toBeGreaterThan(0);
|
|
235
|
+
expect(alphaAt(10, 0)).toBe(0);
|
|
236
|
+
|
|
237
|
+
// Halving the visible span pushes the same anchors off both edges.
|
|
238
|
+
const zoomed: CompositeChartScene = {
|
|
239
|
+
...scene,
|
|
240
|
+
startTime: START + 2 * DAY,
|
|
241
|
+
endTime: START + 7 * DAY,
|
|
242
|
+
timeScale: { kind: "calendar", startTime: START + 2 * DAY, endTime: START + 7 * DAY },
|
|
243
|
+
};
|
|
244
|
+
const rezoomed = drawChartToolOverlay(base, null, {
|
|
245
|
+
positive: "#00ff00",
|
|
246
|
+
negative: "#ff0000",
|
|
247
|
+
zoom: "#8888ff",
|
|
248
|
+
draw: "#ffcc00",
|
|
249
|
+
}, "up", { scene: zoomed, panel, items: [drawing] });
|
|
250
|
+
expect(rezoomed.pixels[(5 * 21 + 10) * 4 + 3]!).toBeGreaterThan(0);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
describe("editing drawings", () => {
|
|
255
|
+
const panel: CompositePanelScene = {
|
|
256
|
+
id: "main",
|
|
257
|
+
height: 10,
|
|
258
|
+
scale: "linear",
|
|
259
|
+
axes: { left: { ...domain, side: "left" } },
|
|
260
|
+
series: [{ source: { id: "a", axis: "left" } as never, points: [] }],
|
|
261
|
+
};
|
|
262
|
+
const scene = calendarScene(10 * DAY);
|
|
263
|
+
const line = {
|
|
264
|
+
id: "line-1",
|
|
265
|
+
panelId: "main",
|
|
266
|
+
color: CHART_DRAWING_COLORS[0],
|
|
267
|
+
points: [
|
|
268
|
+
{ time: START + 2 * DAY, value: 150 },
|
|
269
|
+
{ time: START + 8 * DAY, value: 50 },
|
|
270
|
+
],
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
it("grabs an endpoint before the body, and misses outside the slack", () => {
|
|
274
|
+
expect(hitTestDrawings([line], scene, panel, { xRatio: 0.2, yRatio: 0.25 }))
|
|
275
|
+
.toMatchObject({ pointIndex: 0 });
|
|
276
|
+
expect(hitTestDrawings([line], scene, panel, { xRatio: 0.5, yRatio: 0.5 }))
|
|
277
|
+
.toMatchObject({ pointIndex: null });
|
|
278
|
+
expect(hitTestDrawings([line], scene, panel, { xRatio: 0.5, yRatio: 0.9 })).toBeNull();
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it("measures grab slack in pixels, not in ratios", () => {
|
|
282
|
+
// On a 4:1 plot the same ratio is four times fewer pixels sideways, so the
|
|
283
|
+
// sideways slack has to shrink with it or the grab is uneven.
|
|
284
|
+
const near = { xRatio: 0.2 + 0.005, yRatio: 0.25 };
|
|
285
|
+
const far = { xRatio: 0.2 + 0.02, yRatio: 0.25 };
|
|
286
|
+
expect(hitTestDrawings([line], scene, panel, near, 4)).toMatchObject({ pointIndex: 0 });
|
|
287
|
+
expect(hitTestDrawings([line], scene, panel, far, 4)?.pointIndex ?? "none").not.toBe(0);
|
|
288
|
+
// Vertical slack is unchanged by the aspect.
|
|
289
|
+
expect(hitTestDrawings([line], scene, panel, { xRatio: 0.2, yRatio: 0.27 }, 4))
|
|
290
|
+
.toMatchObject({ pointIndex: 0 });
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("leaves drawings that belong to another panel alone", () => {
|
|
294
|
+
const other = { ...line, panelId: "volume" };
|
|
295
|
+
expect(hitTestDrawings([other], scene, panel, { xRatio: 0.2, yRatio: 0.25 })).toBeNull();
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it("moves a whole shape, or just the grabbed end", () => {
|
|
299
|
+
const moved = shiftDrawing(line, scene, panel, 0.1, 0, null);
|
|
300
|
+
expect(moved.points[0]!.time).toBe(START + 3 * DAY);
|
|
301
|
+
expect(moved.points[1]!.time).toBe(START + 9 * DAY);
|
|
302
|
+
|
|
303
|
+
const reshaped = shiftDrawing(line, scene, panel, 0, 0.25, 1);
|
|
304
|
+
expect(reshaped.points[0]!.value).toBeCloseTo(150, 6);
|
|
305
|
+
expect(reshaped.points[1]!.value).toBeCloseTo(0, 6);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it("cycles colours and wraps", () => {
|
|
309
|
+
expect(nextDrawingColor(CHART_DRAWING_COLORS[0])).toBe(CHART_DRAWING_COLORS[1]);
|
|
310
|
+
expect(nextDrawingColor(CHART_DRAWING_COLORS.at(-1)!)).toBe(CHART_DRAWING_COLORS[0]);
|
|
311
|
+
expect(nextDrawingColor("#000000")).toBe(CHART_DRAWING_COLORS[0]);
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
describe("vector overlays", () => {
|
|
316
|
+
const panel: CompositePanelScene = {
|
|
317
|
+
id: "main",
|
|
318
|
+
height: 10,
|
|
319
|
+
scale: "linear",
|
|
320
|
+
axes: { left: { ...domain, side: "left" } },
|
|
321
|
+
series: [{ source: { id: "a", axis: "left" } as never, points: [] }],
|
|
322
|
+
};
|
|
323
|
+
const scene = calendarScene(10 * DAY);
|
|
324
|
+
const colors = { positive: "#0f0", negative: "#f00", zoom: "#00f", draw: "#ff0" };
|
|
325
|
+
|
|
326
|
+
it("spans the full height for a zoom selection and boxes a measurement", () => {
|
|
327
|
+
const zoom = buildChartToolVectors({
|
|
328
|
+
scene,
|
|
329
|
+
panel,
|
|
330
|
+
drawings: [],
|
|
331
|
+
selectedId: null,
|
|
332
|
+
colors,
|
|
333
|
+
direction: "up",
|
|
334
|
+
drag: { kind: "zoom", startXRatio: 0.2, startYRatio: 0.4, endXRatio: 0.6, endYRatio: 0.9, path: [] },
|
|
335
|
+
});
|
|
336
|
+
expect(zoom[0]).toMatchObject({ box: true, points: [{ x: 0.2, y: 0 }, { x: 0.6, y: 1 }] });
|
|
337
|
+
|
|
338
|
+
const measure = buildChartToolVectors({
|
|
339
|
+
scene,
|
|
340
|
+
panel,
|
|
341
|
+
drawings: [],
|
|
342
|
+
selectedId: null,
|
|
343
|
+
colors,
|
|
344
|
+
direction: "down",
|
|
345
|
+
drag: { kind: "measure", startXRatio: 0.2, startYRatio: 0.4, endXRatio: 0.6, endYRatio: 0.9, path: [] },
|
|
346
|
+
});
|
|
347
|
+
expect(measure[0]).toMatchObject({ box: true, color: "#f00" });
|
|
348
|
+
expect(measure[1]!.points).toEqual([{ x: 0.2, y: 0.4 }, { x: 0.6, y: 0.9 }]);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it("gives handles to the selected line only", () => {
|
|
352
|
+
const line = {
|
|
353
|
+
id: "line-1",
|
|
354
|
+
panelId: "main",
|
|
355
|
+
color: "#ff0",
|
|
356
|
+
points: [
|
|
357
|
+
{ time: START + 2 * DAY, value: 150 },
|
|
358
|
+
{ time: START + 8 * DAY, value: 50 },
|
|
359
|
+
],
|
|
360
|
+
};
|
|
361
|
+
const [unselected] = buildChartToolVectors({
|
|
362
|
+
scene, panel, drawings: [line], selectedId: null, colors, direction: "up", drag: null,
|
|
363
|
+
});
|
|
364
|
+
const [selected] = buildChartToolVectors({
|
|
365
|
+
scene, panel, drawings: [line], selectedId: "line-1", colors, direction: "up", drag: null,
|
|
366
|
+
});
|
|
367
|
+
expect(unselected?.handles).toBe(false);
|
|
368
|
+
expect(selected?.handles).toBe(true);
|
|
369
|
+
expect(selected?.points).toEqual([{ x: 0.2, y: 0.25 }, { x: 0.8, y: 0.75 }]);
|
|
370
|
+
});
|
|
371
|
+
});
|