gloomberb 0.9.3 → 0.9.5
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 +1 -0
- package/package.json +3 -2
- package/patches/@opentui%2Fcore@0.3.2.patch +231 -29
- package/patches/react-reconciler@0.33.0.patch +10 -0
- package/src/app/runtime/desktop-menu.ts +5 -1
- package/src/app/runtime/ticker-refresh.ts +4 -1
- package/src/app.tsx +10 -1
- package/src/brokers/account-cache.ts +1 -1
- package/src/brokers/sync-broker-instance.test.ts +107 -0
- package/src/brokers/sync-broker-instance.ts +22 -10
- package/src/cli/desktop-pane-shot.ts +2 -1
- package/src/cli/entry.ts +8 -1
- package/src/cli/native-smoke.ts +5 -0
- package/src/cli/pane-functions/screenshot.ts +13 -1
- package/src/components/chart/bar-chart-renderer.test.ts +108 -3
- package/src/components/chart/bar-chart-renderer.ts +226 -27
- package/src/components/chart/core/pointer.ts +32 -6
- package/src/components/chart/native/bitmap-support.test.ts +27 -0
- package/src/components/chart/native/bitmap-support.ts +55 -0
- package/src/components/chart/native/kitty/index.test.ts +47 -0
- package/src/components/chart/native/surface/visibility.ts +8 -1
- package/src/components/chart/price-axis-labels.tsx +3 -1
- package/src/components/chart/static/bar-chart-surface.test.tsx +97 -0
- package/src/components/chart/static/bar-chart-surface.tsx +151 -28
- package/src/components/chart/static/chart/axis-overlays.tsx +243 -0
- package/src/components/chart/static/chart/bitmap.ts +16 -9
- package/src/components/chart/static/chart/surface.test.tsx +92 -0
- package/src/components/chart/static/chart/surface.tsx +167 -24
- package/src/components/chart/stock-chart/index.test.ts +25 -0
- package/src/components/command-bar/layout-items/current-layout.ts +5 -1
- package/src/components/command-bar/list/view.tsx +1 -1
- package/src/components/command-bar/theme-picker.tsx +1 -1
- package/src/components/data-table/view.tsx +0 -12
- package/src/components/layout/detached-pane-shell.tsx +6 -3
- package/src/components/layout/floating-pane.tsx +6 -5
- package/src/components/layout/pane/content.tsx +20 -8
- package/src/components/layout/pane/footer/registration.tsx +6 -2
- package/src/components/layout/pane/frame.tsx +4 -4
- package/src/components/layout/pane/header.tsx +45 -4
- package/src/components/layout/pane/index.tsx +6 -4
- package/src/components/layout/pane/sizing.test.ts +43 -0
- package/src/components/layout/pane/sizing.ts +13 -4
- package/src/components/layout/shell/action-menu-overlay.tsx +1 -1
- package/src/components/layout/shell/cursor-occlusion.ts +204 -0
- package/src/components/layout/shell/drag/runtime.ts +9 -2
- package/src/components/layout/shell/index.test.tsx +92 -2
- package/src/components/layout/shell/index.tsx +45 -3
- package/src/components/layout/shell/layout-state.test.ts +33 -0
- package/src/components/layout/shell/layout-state.ts +4 -9
- package/src/components/layout/shell/pane/layers.tsx +10 -4
- package/src/components/layout/shell/visible-layout.ts +17 -0
- package/src/components/layout/status-bar.test.tsx +6 -1
- package/src/components/layout/status-bar.tsx +10 -6
- package/src/components/onboarding/onboarding-steps.tsx +8 -28
- package/src/components/onboarding/onboarding-wizard.test.tsx +13 -12
- package/src/components/onboarding/onboarding-wizard.tsx +2 -4
- package/src/components/onboarding/portfolio-step/index.tsx +0 -12
- package/src/components/onboarding/portfolio-step/types.ts +1 -3
- package/src/components/onboarding/wizard-keyboard.ts +2 -5
- package/src/components/pane-settings-dialog/desktop.tsx +1 -1
- package/src/components/price-performance.tsx +1 -1
- package/src/components/price-sparkline/view.tsx +15 -14
- package/src/components/speedometer-gauge/terminal.tsx +18 -15
- package/src/components/table-view-shared.tsx +12 -14
- package/src/components/ticker/list-table-view.tsx +1 -7
- package/src/components/ticker/list-table.test.tsx +0 -4
- package/src/components/ui/data-table/opentui/index.tsx +29 -22
- package/src/components/ui/data-table/types.ts +10 -4
- package/src/components/ui/fields.tsx +10 -3
- package/src/components/ui/list-view.tsx +1 -1
- package/src/components/ui/page-stack-view.tsx +6 -2
- package/src/components/ui/table-layout.test.ts +25 -0
- package/src/components/ui/table-layout.ts +22 -6
- package/src/components/ui/tabs.tsx +27 -12
- package/src/components/ui/ui.test.tsx +87 -9
- package/src/core/state/app/layout-reducer.ts +2 -2
- package/src/plugins/builtin/account-management/form-components.tsx +2 -2
- package/src/plugins/builtin/alerts/pane.tsx +1 -1
- package/src/plugins/builtin/analytics/index.test.tsx +6 -2
- package/src/plugins/builtin/analytics/pane-model.ts +43 -8
- package/src/plugins/builtin/buildout/cells.tsx +1 -1
- package/src/plugins/builtin/buildout/pane/index.tsx +1 -1
- package/src/plugins/builtin/chat/message/desktop.tsx +1 -1
- package/src/plugins/builtin/chat/message/inline-tokens.tsx +1 -1
- package/src/plugins/builtin/chat/message/terminal.tsx +2 -2
- package/src/plugins/builtin/chat/sidebar.tsx +1 -1
- package/src/plugins/builtin/chat/status-widget.tsx +1 -1
- package/src/plugins/builtin/cloud/auth-actions.tsx +2 -2
- package/src/plugins/builtin/correlation/matrix/symbol-cell.tsx +1 -1
- package/src/plugins/builtin/debug/index.tsx +3 -4
- package/src/plugins/builtin/help/components.tsx +1 -1
- package/src/plugins/builtin/holders/treemap.tsx +1 -1
- package/src/plugins/builtin/kelly-sizer/constants.ts +3 -0
- package/src/plugins/builtin/kelly-sizer/fields.ts +214 -0
- package/src/plugins/builtin/kelly-sizer/index.test.tsx +197 -0
- package/src/plugins/builtin/kelly-sizer/index.tsx +47 -0
- package/src/plugins/builtin/kelly-sizer/model.test.ts +152 -0
- package/src/plugins/builtin/kelly-sizer/model.ts +580 -0
- package/src/plugins/builtin/kelly-sizer/pane.tsx +558 -0
- package/src/plugins/builtin/kelly-sizer/portfolio.ts +71 -0
- package/src/plugins/builtin/kelly-sizer/sections.tsx +142 -0
- package/src/plugins/builtin/kelly-sizer/state.ts +67 -0
- package/src/plugins/builtin/kelly-sizer/test-support.tsx +174 -0
- package/src/plugins/builtin/kelly-sizer/types.ts +156 -0
- package/src/plugins/builtin/kelly-sizer/view.test.tsx +77 -0
- package/src/plugins/builtin/kelly-sizer/view.tsx +375 -0
- package/src/plugins/builtin/layout-manager/index.tsx +5 -1
- package/src/plugins/builtin/options/table.ts +5 -5
- package/src/plugins/builtin/options/view.test.tsx +141 -4
- package/src/plugins/builtin/options/view.tsx +76 -15
- package/src/plugins/builtin/portfolio-list/account-metrics.test.ts +26 -1
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +14 -0
- package/src/plugins/builtin/portfolio-list/index.test.ts +23 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +2 -0
- package/src/plugins/builtin/portfolio-list/summary/index.test.ts +59 -1
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +36 -5
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +149 -48
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/model.ts +25 -2
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph.tsx +5 -4
- package/src/plugins/builtin/ticker-detail/data-panes.test.ts +16 -0
- package/src/plugins/builtin/ticker-detail/index.test.tsx +143 -0
- package/src/plugins/catalog-ui.ts +2 -0
- package/src/plugins/ibkr/broker-adapter.ts +19 -0
- package/src/plugins/ibkr/config.test.ts +31 -2
- package/src/plugins/ibkr/config.ts +9 -2
- package/src/plugins/ibkr/flex/index.test.ts +18 -9
- package/src/plugins/ibkr/flex/index.ts +30 -0
- package/src/plugins/ibkr/flex/statement-client.ts +68 -14
- package/src/plugins/ibkr/gateway/account-loaders.ts +1 -1
- package/src/plugins/ibkr/gateway/account-summary.ts +1 -0
- package/src/plugins/ibkr/gateway/service/index.test.ts +8 -0
- package/src/plugins/ibkr/trade/ticket-panel.tsx +1 -1
- package/src/plugins/pane-manager/gridlock.ts +13 -5
- package/src/plugins/pane-manager/layout-state.ts +25 -0
- package/src/plugins/pane-manager.test.ts +33 -0
- package/src/plugins/pane-manager.ts +6 -1
- package/src/plugins/prediction-markets/chart.tsx +10 -4
- package/src/plugins/prediction-markets/detail/book.tsx +141 -96
- package/src/plugins/prediction-markets/detail/overview.tsx +3 -0
- package/src/plugins/prediction-markets/detail/pane.tsx +71 -64
- package/src/plugins/prediction-markets/detail/shared.tsx +19 -0
- package/src/plugins/prediction-markets/detail/trades.tsx +64 -19
- package/src/plugins/prediction-markets/detail.test.tsx +50 -0
- package/src/plugins/prediction-markets/pane.test.tsx +43 -0
- package/src/plugins/prediction-markets/pane.tsx +50 -22
- package/src/plugins/prediction-markets/test-helpers.tsx +41 -23
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +13 -2
- package/src/renderers/electrobun/view/data-table/index.tsx +19 -12
- package/src/renderers/electrobun/view/data-table/row.tsx +32 -18
- package/src/renderers/electrobun/view/desktop/controls.tsx +11 -5
- package/src/renderers/electrobun/view/host/box.tsx +6 -1
- package/src/renderers/electrobun/view/host/input.tsx +115 -18
- package/src/renderers/electrobun/view/host/style.ts +1 -0
- package/src/renderers/electrobun/view/host/tabs.tsx +7 -2
- package/src/renderers/electrobun/view/styles.css +18 -0
- package/src/renderers/opentui/host.tsx +17 -1
- package/src/renderers/opentui/native-smoke.ts +1 -0
- package/src/renderers/opentui/ui-host.tsx +57 -25
- package/src/sources/provider-router/batches.ts +16 -2
- package/src/sources/provider-router/financial-routes.ts +5 -0
- package/src/sources/provider-router/financials.ts +11 -23
- package/src/sources/provider-router/index.test.ts +189 -0
- package/src/sources/provider-router/primary.ts +3 -2
- package/src/sources/sec-edgar.test.ts +83 -0
- package/src/sources/sec-edgar.ts +231 -0
- package/src/sources/yahoo-finance.test.ts +51 -0
- package/src/sources/yahoo-finance.ts +49 -1
- package/src/state/app/context/index.test.tsx +5 -8
- package/src/types/broker.ts +1 -0
- package/src/types/config.ts +10 -0
- package/src/types/trading.ts +2 -0
- package/src/ui/host.tsx +16 -0
- package/src/ui/index.tsx +2 -0
- package/src/utils/financial-statements.ts +23 -0
- package/src/version.ts +1 -1
- package/src/components/onboarding/portfolio-step/manual-name-panel.tsx +0 -62
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { Box, Text, useUiCapabilities, useUiHost } from "../../../../ui";
|
|
2
|
+
|
|
3
|
+
export interface StaticChartXMarker {
|
|
4
|
+
id: string;
|
|
5
|
+
xRatio: number;
|
|
6
|
+
label?: string;
|
|
7
|
+
color?: string;
|
|
8
|
+
lineChar?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function clampRatio(value: number): number {
|
|
12
|
+
if (!Number.isFinite(value)) return 0;
|
|
13
|
+
return Math.max(0, Math.min(1, value));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function markerColumn(xRatio: number, width: number): number {
|
|
17
|
+
return Math.round(clampRatio(xRatio) * Math.max(0, width - 1));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function alignAxisLabel(label: string, width: number, index: number, count: number): string {
|
|
21
|
+
const clipped = label.length > width ? label.slice(0, width) : label;
|
|
22
|
+
const padding = Math.max(0, width - clipped.length);
|
|
23
|
+
if (index === 0) return clipped.padEnd(width);
|
|
24
|
+
if (index === count - 1) return clipped.padStart(width);
|
|
25
|
+
const left = Math.floor(padding / 2);
|
|
26
|
+
return `${" ".repeat(left)}${clipped}${" ".repeat(padding - left)}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function StaticXAxisLabels({
|
|
30
|
+
labels,
|
|
31
|
+
width,
|
|
32
|
+
color,
|
|
33
|
+
cursorColumn,
|
|
34
|
+
cursorPixelX,
|
|
35
|
+
cursorLabel,
|
|
36
|
+
cursorColor,
|
|
37
|
+
cursorBackgroundColor,
|
|
38
|
+
}: {
|
|
39
|
+
labels: string[];
|
|
40
|
+
width: number;
|
|
41
|
+
color?: string;
|
|
42
|
+
cursorColumn?: number | null;
|
|
43
|
+
cursorPixelX?: number | null;
|
|
44
|
+
cursorLabel?: string | null;
|
|
45
|
+
cursorColor?: string;
|
|
46
|
+
cursorBackgroundColor?: string;
|
|
47
|
+
}) {
|
|
48
|
+
const { cellWidthPx = 8, fractionalViewport = false } = useUiCapabilities();
|
|
49
|
+
const visibleLabels = labels.filter(Boolean);
|
|
50
|
+
if (visibleLabels.length === 0 || width <= 0) return null;
|
|
51
|
+
const baseWidth = Math.floor(width / visibleLabels.length);
|
|
52
|
+
let remainder = width - baseWidth * visibleLabels.length;
|
|
53
|
+
const clippedCursorLabel = cursorLabel ? cursorLabel.slice(0, width) : null;
|
|
54
|
+
const cursorLabelWidth = clippedCursorLabel?.length ?? 0;
|
|
55
|
+
const usePixelOverlay = fractionalViewport
|
|
56
|
+
&& clippedCursorLabel !== null
|
|
57
|
+
&& cursorPixelX !== null
|
|
58
|
+
&& cursorPixelX !== undefined
|
|
59
|
+
&& Number.isFinite(cursorPixelX)
|
|
60
|
+
&& cursorLabelWidth > 0;
|
|
61
|
+
const pixelWidth = Math.max(width * cellWidthPx, 1);
|
|
62
|
+
const halfCursorLabelWidthPx = Math.min((cursorLabelWidth * cellWidthPx) / 2, (pixelWidth - 1) / 2);
|
|
63
|
+
const cursorLeftPercent = usePixelOverlay
|
|
64
|
+
? (
|
|
65
|
+
Math.max(
|
|
66
|
+
halfCursorLabelWidthPx,
|
|
67
|
+
Math.min(cursorPixelX!, Math.max(pixelWidth - halfCursorLabelWidthPx, halfCursorLabelWidthPx)),
|
|
68
|
+
) / Math.max(pixelWidth - 1, 1)
|
|
69
|
+
) * 100
|
|
70
|
+
: null;
|
|
71
|
+
const cursorLeft = cursorColumn !== null
|
|
72
|
+
&& cursorColumn !== undefined
|
|
73
|
+
&& Number.isFinite(cursorColumn)
|
|
74
|
+
&& cursorLabelWidth > 0
|
|
75
|
+
? Math.max(0, Math.min(Math.max(0, width - cursorLabelWidth), Math.round(cursorColumn) - Math.floor(cursorLabelWidth / 2)))
|
|
76
|
+
: null;
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<Box
|
|
80
|
+
width={width}
|
|
81
|
+
height={1}
|
|
82
|
+
flexDirection="row"
|
|
83
|
+
position="relative"
|
|
84
|
+
overflow="hidden"
|
|
85
|
+
>
|
|
86
|
+
<Box width={width} height={1} flexDirection="row">
|
|
87
|
+
{visibleLabels.map((label, index) => {
|
|
88
|
+
const cellWidth = baseWidth + (remainder > 0 ? 1 : 0);
|
|
89
|
+
remainder -= remainder > 0 ? 1 : 0;
|
|
90
|
+
return (
|
|
91
|
+
<Box key={`${label}:${index}`} width={cellWidth} overflow="hidden">
|
|
92
|
+
<Text fg={color}>{alignAxisLabel(label, cellWidth, index, visibleLabels.length)}</Text>
|
|
93
|
+
</Box>
|
|
94
|
+
);
|
|
95
|
+
})}
|
|
96
|
+
</Box>
|
|
97
|
+
{clippedCursorLabel && (cursorLeftPercent !== null || cursorLeft !== null) ? (
|
|
98
|
+
<Text
|
|
99
|
+
fg={cursorColor}
|
|
100
|
+
bg={cursorBackgroundColor}
|
|
101
|
+
selectable={false}
|
|
102
|
+
style={cursorLeftPercent !== null
|
|
103
|
+
? {
|
|
104
|
+
position: "absolute",
|
|
105
|
+
left: `${cursorLeftPercent}%`,
|
|
106
|
+
top: 0,
|
|
107
|
+
transform: "translateX(-50%)",
|
|
108
|
+
whiteSpace: "pre",
|
|
109
|
+
pointerEvents: "none",
|
|
110
|
+
zIndex: 3,
|
|
111
|
+
}
|
|
112
|
+
: {
|
|
113
|
+
position: "absolute",
|
|
114
|
+
left: cursorLeft ?? 0,
|
|
115
|
+
top: 0,
|
|
116
|
+
whiteSpace: "pre",
|
|
117
|
+
pointerEvents: "none",
|
|
118
|
+
zIndex: 3,
|
|
119
|
+
}}
|
|
120
|
+
>
|
|
121
|
+
{clippedCursorLabel}
|
|
122
|
+
</Text>
|
|
123
|
+
) : null}
|
|
124
|
+
</Box>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function StaticXMarkerOverlay({
|
|
129
|
+
markers,
|
|
130
|
+
width,
|
|
131
|
+
height,
|
|
132
|
+
fallbackColor,
|
|
133
|
+
}: {
|
|
134
|
+
markers: StaticChartXMarker[];
|
|
135
|
+
width: number;
|
|
136
|
+
height: number;
|
|
137
|
+
fallbackColor?: string;
|
|
138
|
+
}) {
|
|
139
|
+
const uiHost = useUiHost();
|
|
140
|
+
const visibleMarkers = markers.filter((marker) => Number.isFinite(marker.xRatio));
|
|
141
|
+
if (visibleMarkers.length === 0 || width <= 0 || height <= 0) return null;
|
|
142
|
+
|
|
143
|
+
if (uiHost.kind === "desktop-web") {
|
|
144
|
+
return (
|
|
145
|
+
<Box position="absolute" left={0} top={0} width={width} height={height}>
|
|
146
|
+
{visibleMarkers.map((marker) => (
|
|
147
|
+
<Box
|
|
148
|
+
key={marker.id}
|
|
149
|
+
position="absolute"
|
|
150
|
+
left={`${clampRatio(marker.xRatio) * 100}%`}
|
|
151
|
+
top={0}
|
|
152
|
+
bottom={0}
|
|
153
|
+
width={0}
|
|
154
|
+
zIndex={2}
|
|
155
|
+
style={{
|
|
156
|
+
width: 1,
|
|
157
|
+
transform: "translateX(-0.5px)",
|
|
158
|
+
backgroundColor: marker.color ?? fallbackColor ?? "currentColor",
|
|
159
|
+
opacity: 0.85,
|
|
160
|
+
pointerEvents: "none",
|
|
161
|
+
}}
|
|
162
|
+
/>
|
|
163
|
+
))}
|
|
164
|
+
</Box>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
<Box position="absolute" left={0} top={0} width={width} height={height}>
|
|
170
|
+
{visibleMarkers.map((marker) => {
|
|
171
|
+
const left = markerColumn(marker.xRatio, width);
|
|
172
|
+
return (
|
|
173
|
+
<Box
|
|
174
|
+
key={marker.id}
|
|
175
|
+
position="absolute"
|
|
176
|
+
left={left}
|
|
177
|
+
top={0}
|
|
178
|
+
width={1}
|
|
179
|
+
height={height}
|
|
180
|
+
flexDirection="column"
|
|
181
|
+
zIndex={2}
|
|
182
|
+
>
|
|
183
|
+
{Array.from({ length: height }, (_, row) => (
|
|
184
|
+
<Text key={row} fg={marker.color}>
|
|
185
|
+
{marker.lineChar ?? "│"}
|
|
186
|
+
</Text>
|
|
187
|
+
))}
|
|
188
|
+
</Box>
|
|
189
|
+
);
|
|
190
|
+
})}
|
|
191
|
+
</Box>
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function StaticXMarkerLabels({
|
|
196
|
+
markers,
|
|
197
|
+
width,
|
|
198
|
+
fallbackColor,
|
|
199
|
+
}: {
|
|
200
|
+
markers: StaticChartXMarker[];
|
|
201
|
+
width: number;
|
|
202
|
+
fallbackColor?: string;
|
|
203
|
+
}) {
|
|
204
|
+
const visibleMarkers = markers
|
|
205
|
+
.filter((marker) => marker.label && Number.isFinite(marker.xRatio))
|
|
206
|
+
.sort((left, right) => markerColumn(left.xRatio, width) - markerColumn(right.xRatio, width));
|
|
207
|
+
if (visibleMarkers.length === 0 || width <= 0) return null;
|
|
208
|
+
|
|
209
|
+
let nextAvailableLeft = 0;
|
|
210
|
+
const placements = visibleMarkers.map((marker) => {
|
|
211
|
+
const label = marker.label ?? "";
|
|
212
|
+
const labelWidth = Math.min(label.length, width);
|
|
213
|
+
const centeredLeft = markerColumn(marker.xRatio, width) - Math.floor(labelWidth / 2);
|
|
214
|
+
let left = Math.max(0, Math.min(Math.max(0, width - labelWidth), centeredLeft));
|
|
215
|
+
left = Math.max(left, nextAvailableLeft);
|
|
216
|
+
if (left + labelWidth > width) {
|
|
217
|
+
left = Math.max(nextAvailableLeft, width - labelWidth);
|
|
218
|
+
}
|
|
219
|
+
nextAvailableLeft = Math.min(width, left + labelWidth + 1);
|
|
220
|
+
return { marker, label, labelWidth, left };
|
|
221
|
+
}).filter((placement) => placement.labelWidth > 0 && placement.left < width);
|
|
222
|
+
|
|
223
|
+
return (
|
|
224
|
+
<Box position="relative" width={width} height={1}>
|
|
225
|
+
{placements.map(({ marker, label, labelWidth, left }) => {
|
|
226
|
+
return (
|
|
227
|
+
<Box
|
|
228
|
+
key={marker.id}
|
|
229
|
+
position="absolute"
|
|
230
|
+
left={left}
|
|
231
|
+
top={0}
|
|
232
|
+
width={labelWidth}
|
|
233
|
+
height={1}
|
|
234
|
+
overflow="hidden"
|
|
235
|
+
zIndex={2}
|
|
236
|
+
>
|
|
237
|
+
<Text fg={marker.color ?? fallbackColor}>{label.slice(0, labelWidth)}</Text>
|
|
238
|
+
</Box>
|
|
239
|
+
);
|
|
240
|
+
})}
|
|
241
|
+
</Box>
|
|
242
|
+
);
|
|
243
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
2
|
import { useNativeRenderer, useUiCapabilities } from "../../../../ui";
|
|
3
|
-
import {
|
|
3
|
+
import { resolveNativeBitmapSize, shouldRenderNativeBitmap } from "../../native/bitmap-support";
|
|
4
4
|
|
|
5
5
|
export interface StaticChartBitmapSize {
|
|
6
6
|
pixelWidth: number;
|
|
@@ -16,20 +16,25 @@ export function useStaticChartBitmapSize(width: number, height: number): StaticC
|
|
|
16
16
|
pixelRatio = 1,
|
|
17
17
|
} = useUiCapabilities();
|
|
18
18
|
const nativeRenderer = useNativeRenderer();
|
|
19
|
+
const rendererCapabilities = nativeRenderer.capabilities;
|
|
19
20
|
const rendererResolution = nativeRenderer.resolution;
|
|
20
21
|
const rendererTerminalWidth = nativeRenderer.terminalWidth;
|
|
21
22
|
const rendererTerminalHeight = nativeRenderer.terminalHeight;
|
|
22
23
|
|
|
23
24
|
return useMemo(() => {
|
|
24
|
-
if (nativeCharts
|
|
25
|
-
return
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
if (shouldRenderNativeBitmap(nativeRenderer, nativeCharts === true)) {
|
|
26
|
+
return resolveNativeBitmapSize({
|
|
27
|
+
width,
|
|
28
|
+
height,
|
|
29
|
+
resolution: rendererResolution,
|
|
30
|
+
terminalWidth: rendererTerminalWidth,
|
|
31
|
+
terminalHeight: rendererTerminalHeight,
|
|
32
|
+
cellWidthPx,
|
|
33
|
+
cellHeightPx,
|
|
34
|
+
pixelRatio,
|
|
35
|
+
});
|
|
31
36
|
}
|
|
32
|
-
if (!canvasCharts
|
|
37
|
+
if (!canvasCharts) return null;
|
|
33
38
|
const scale = Math.max(1, pixelRatio);
|
|
34
39
|
return {
|
|
35
40
|
pixelWidth: Math.max(1, Math.round(width * cellWidthPx * scale)),
|
|
@@ -41,7 +46,9 @@ export function useStaticChartBitmapSize(width: number, height: number): StaticC
|
|
|
41
46
|
cellWidthPx,
|
|
42
47
|
height,
|
|
43
48
|
nativeCharts,
|
|
49
|
+
nativeRenderer,
|
|
44
50
|
pixelRatio,
|
|
51
|
+
rendererCapabilities,
|
|
45
52
|
rendererResolution,
|
|
46
53
|
rendererTerminalHeight,
|
|
47
54
|
rendererTerminalWidth,
|
|
@@ -48,4 +48,96 @@ describe("StaticChartSurface", () => {
|
|
|
48
48
|
expect(frame).toContain("4.90%");
|
|
49
49
|
expect(frame).toContain("3.50%");
|
|
50
50
|
});
|
|
51
|
+
|
|
52
|
+
test("renders custom x-axis scale labels", async () => {
|
|
53
|
+
testSetup = await testRender(
|
|
54
|
+
<StaticChartSurface
|
|
55
|
+
points={points}
|
|
56
|
+
width={48}
|
|
57
|
+
height={10}
|
|
58
|
+
mode="line"
|
|
59
|
+
colors={resolveChartPalette(colors, "positive")}
|
|
60
|
+
xAxisLabels={["0%", "50%", "100%"]}
|
|
61
|
+
xAxisColor={colors.textDim}
|
|
62
|
+
/>,
|
|
63
|
+
{ width: 50, height: 12 },
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
await act(async () => {
|
|
67
|
+
await testSetup!.renderOnce();
|
|
68
|
+
await testSetup!.renderOnce();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const frame = testSetup.captureCharFrame();
|
|
72
|
+
expect(frame).toContain("0%");
|
|
73
|
+
expect(frame).toContain("50%");
|
|
74
|
+
expect(frame).toContain("100%");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("renders x-axis decision markers", async () => {
|
|
78
|
+
testSetup = await testRender(
|
|
79
|
+
<StaticChartSurface
|
|
80
|
+
points={points}
|
|
81
|
+
width={56}
|
|
82
|
+
height={10}
|
|
83
|
+
mode="line"
|
|
84
|
+
colors={resolveChartPalette(colors, "positive")}
|
|
85
|
+
xAxisLabels={["0%", "50%", "100%"]}
|
|
86
|
+
xMarkers={[
|
|
87
|
+
{ id: "current", xRatio: 0.1, label: "current", lineChar: "┊" },
|
|
88
|
+
{ id: "target", xRatio: 0.5, label: "target", lineChar: "┃" },
|
|
89
|
+
{ id: "full", xRatio: 0.8, label: "full", lineChar: "│" },
|
|
90
|
+
]}
|
|
91
|
+
xAxisColor={colors.textDim}
|
|
92
|
+
/>,
|
|
93
|
+
{ width: 58, height: 12 },
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
await act(async () => {
|
|
97
|
+
await testSetup!.renderOnce();
|
|
98
|
+
await testSetup!.renderOnce();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const frame = testSetup.captureCharFrame();
|
|
102
|
+
expect(frame).toContain("current");
|
|
103
|
+
expect(frame).toContain("target");
|
|
104
|
+
expect(frame).toContain("full");
|
|
105
|
+
expect(frame).toContain("┃");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("renders a cursor crosshair after mouse movement", async () => {
|
|
109
|
+
testSetup = await testRender(
|
|
110
|
+
<StaticChartSurface
|
|
111
|
+
points={points}
|
|
112
|
+
width={48}
|
|
113
|
+
height={10}
|
|
114
|
+
mode="line"
|
|
115
|
+
colors={resolveChartPalette(colors, "positive")}
|
|
116
|
+
xAxisLabels={["0%", "50%", "100%"]}
|
|
117
|
+
formatXAxisCursorValue={(ratio) => `X${Math.round(ratio * 100)}`}
|
|
118
|
+
formatYAxisValue={(value) => `Y${value.toFixed(2)}`}
|
|
119
|
+
xMarkers={[
|
|
120
|
+
{ id: "current", xRatio: 0.1, label: "current", lineChar: "┊" },
|
|
121
|
+
{ id: "target", xRatio: 0.5, label: "target", lineChar: "┃" },
|
|
122
|
+
]}
|
|
123
|
+
/>,
|
|
124
|
+
{ width: 50, height: 12 },
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
await act(async () => {
|
|
128
|
+
await testSetup!.renderOnce();
|
|
129
|
+
await testSetup!.renderOnce();
|
|
130
|
+
});
|
|
131
|
+
const initialFrame = testSetup.captureCharFrame();
|
|
132
|
+
|
|
133
|
+
await act(async () => {
|
|
134
|
+
await testSetup!.mockMouse.moveTo(20, 4);
|
|
135
|
+
await testSetup!.renderOnce();
|
|
136
|
+
await testSetup!.renderOnce();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
expect(testSetup.captureCharFrame()).not.toBe(initialFrame);
|
|
140
|
+
expect(testSetup.captureCharFrame()).toContain("X49");
|
|
141
|
+
expect(testSetup.captureCharFrame()).toContain("Y3.97");
|
|
142
|
+
});
|
|
51
143
|
});
|
|
@@ -1,16 +1,26 @@
|
|
|
1
|
-
import { useMemo } from "react";
|
|
2
|
-
import { Box, ChartSurface, Text } from "../../../../ui";
|
|
1
|
+
import { useCallback, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { Box, ChartSurface, Text, type BoxRenderable, type ChartSurfaceProps, useUiCapabilities } from "../../../../ui";
|
|
3
|
+
import { colors as themeColors } from "../../../../theme/colors";
|
|
3
4
|
import { useThemeColors } from "../../../../theme/theme-context";
|
|
5
|
+
import { PriceAxisLabels } from "../../price-axis-labels";
|
|
4
6
|
import type { ProjectedChartPoint } from "../../core/data";
|
|
5
7
|
import {
|
|
6
8
|
buildChartScene,
|
|
7
9
|
computeGridLines,
|
|
8
|
-
formatAxisCell,
|
|
9
10
|
renderChart,
|
|
10
11
|
type RenderChartOptions,
|
|
11
12
|
} from "../../core/renderer";
|
|
12
13
|
import { renderNativeChartBase, type NativeChartBitmap } from "../../native/chart-rasterizer";
|
|
13
14
|
import { useStaticChartBitmapSize } from "./bitmap";
|
|
15
|
+
import {
|
|
16
|
+
StaticXAxisLabels,
|
|
17
|
+
StaticXMarkerLabels,
|
|
18
|
+
StaticXMarkerOverlay,
|
|
19
|
+
clampRatio,
|
|
20
|
+
type StaticChartXMarker,
|
|
21
|
+
} from "./axis-overlays";
|
|
22
|
+
|
|
23
|
+
export type { StaticChartXMarker } from "./axis-overlays";
|
|
14
24
|
|
|
15
25
|
export interface StaticChartSurfaceProps extends Omit<
|
|
16
26
|
RenderChartOptions,
|
|
@@ -23,11 +33,52 @@ export interface StaticChartSurfaceProps extends Omit<
|
|
|
23
33
|
volumeHeight?: number;
|
|
24
34
|
showTimeAxis?: boolean;
|
|
25
35
|
timeAxisColor?: string;
|
|
36
|
+
xAxisLabels?: string[];
|
|
37
|
+
xAxisColor?: string;
|
|
38
|
+
formatXAxisCursorValue?: (xRatio: number) => string;
|
|
39
|
+
xMarkers?: StaticChartXMarker[];
|
|
26
40
|
yAxisLabel?: string;
|
|
27
41
|
yAxisColor?: string;
|
|
28
42
|
formatYAxisValue?: (value: number) => string;
|
|
29
43
|
}
|
|
30
44
|
|
|
45
|
+
interface ChartMouseEventLike {
|
|
46
|
+
x: number;
|
|
47
|
+
y: number;
|
|
48
|
+
preciseX?: number;
|
|
49
|
+
preciseY?: number;
|
|
50
|
+
preventDefault?: () => void;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function localPlotPointer(
|
|
54
|
+
event: ChartMouseEventLike,
|
|
55
|
+
renderable: BoxRenderable | null,
|
|
56
|
+
): { x: number; y: number } | null {
|
|
57
|
+
if (!renderable) return null;
|
|
58
|
+
const originX = typeof renderable.absoluteX === "number"
|
|
59
|
+
? renderable.absoluteX
|
|
60
|
+
: typeof renderable.x === "number"
|
|
61
|
+
? renderable.x
|
|
62
|
+
: 0;
|
|
63
|
+
const originY = typeof renderable.absoluteY === "number"
|
|
64
|
+
? renderable.absoluteY
|
|
65
|
+
: typeof renderable.y === "number"
|
|
66
|
+
? renderable.y
|
|
67
|
+
: 0;
|
|
68
|
+
const rawX = typeof event.preciseX === "number" ? event.preciseX : event.x;
|
|
69
|
+
const rawY = typeof event.preciseY === "number" ? event.preciseY : event.y;
|
|
70
|
+
const x = rawX - originX;
|
|
71
|
+
const y = rawY - originY;
|
|
72
|
+
const width = typeof renderable.width === "number" ? renderable.width : 0;
|
|
73
|
+
const height = typeof renderable.height === "number" ? renderable.height : 0;
|
|
74
|
+
if (!Number.isFinite(x) || !Number.isFinite(y) || width <= 0 || height <= 0) return null;
|
|
75
|
+
if (x < 0 || y < 0 || x >= width || y >= height) return null;
|
|
76
|
+
return {
|
|
77
|
+
x: Math.max(0, Math.min(x, Math.max(width - 1, 0))),
|
|
78
|
+
y: Math.max(0, Math.min(y, Math.max(height - 1, 0))),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
31
82
|
export function StaticChartSurface({
|
|
32
83
|
points,
|
|
33
84
|
width,
|
|
@@ -43,16 +94,24 @@ export function StaticChartSurface({
|
|
|
43
94
|
volumeHeight = 0,
|
|
44
95
|
showTimeAxis = false,
|
|
45
96
|
timeAxisColor,
|
|
97
|
+
xAxisLabels,
|
|
98
|
+
xAxisColor,
|
|
99
|
+
formatXAxisCursorValue,
|
|
100
|
+
xMarkers,
|
|
46
101
|
yAxisLabel,
|
|
47
102
|
yAxisColor,
|
|
48
103
|
formatYAxisValue,
|
|
49
104
|
}: StaticChartSurfaceProps) {
|
|
50
105
|
useThemeColors();
|
|
51
|
-
const
|
|
106
|
+
const { cellWidthPx = 8, cellHeightPx = 18 } = useUiCapabilities();
|
|
107
|
+
const plotRef = useRef<BoxRenderable | null>(null);
|
|
108
|
+
const [cursor, setCursor] = useState<{ x: number; y: number } | null>(null);
|
|
109
|
+
const xAxisRows = showTimeAxis || (xAxisLabels?.length ?? 0) > 0 ? 1 : 0;
|
|
110
|
+
const xMarkerRows = xMarkers?.some((marker) => marker.label) ? 1 : 0;
|
|
52
111
|
const labelRows = yAxisLabel ? 1 : 0;
|
|
53
112
|
const totalWidth = Math.max(1, Math.floor(width));
|
|
54
113
|
const totalHeight = Math.max(1, Math.floor(height));
|
|
55
|
-
const plotHeight = Math.max(1, totalHeight -
|
|
114
|
+
const plotHeight = Math.max(1, totalHeight - xAxisRows - xMarkerRows - labelRows);
|
|
56
115
|
const axisSourceOptions = useMemo<RenderChartOptions>(() => ({
|
|
57
116
|
width: totalWidth,
|
|
58
117
|
height: plotHeight,
|
|
@@ -104,8 +163,8 @@ export function StaticChartSurface({
|
|
|
104
163
|
height: plotHeight,
|
|
105
164
|
showVolume,
|
|
106
165
|
volumeHeight,
|
|
107
|
-
cursorX: null,
|
|
108
|
-
cursorY: null,
|
|
166
|
+
cursorX: cursor?.x ?? null,
|
|
167
|
+
cursorY: cursor?.y ?? null,
|
|
109
168
|
mode,
|
|
110
169
|
axisMode,
|
|
111
170
|
currency,
|
|
@@ -122,6 +181,7 @@ export function StaticChartSurface({
|
|
|
122
181
|
mode,
|
|
123
182
|
plotHeight,
|
|
124
183
|
plotWidth,
|
|
184
|
+
cursor,
|
|
125
185
|
showVolume,
|
|
126
186
|
timeAxisDates,
|
|
127
187
|
volumeHeight,
|
|
@@ -133,6 +193,14 @@ export function StaticChartSurface({
|
|
|
133
193
|
const effectiveAxisLabelsByRow = useMemo(() => {
|
|
134
194
|
return new Map(axisLabels.map((entry) => [entry.row, entry.label] as const));
|
|
135
195
|
}, [axisLabels]);
|
|
196
|
+
const cursorAxisLabel = useMemo(() => {
|
|
197
|
+
if (!formatYAxisValue || textResult.crosshairPrice === null) return null;
|
|
198
|
+
return formatYAxisValue(textResult.crosshairPrice);
|
|
199
|
+
}, [formatYAxisValue, textResult.crosshairPrice]);
|
|
200
|
+
const cursorXAxisLabel = useMemo(() => {
|
|
201
|
+
if (!formatXAxisCursorValue || !cursor) return null;
|
|
202
|
+
return formatXAxisCursorValue(clampRatio(cursor.x / Math.max(plotWidth - 1, 1)));
|
|
203
|
+
}, [cursor, formatXAxisCursorValue, plotWidth]);
|
|
136
204
|
const effectiveAxisWidth = axisWidth;
|
|
137
205
|
const effectiveAxisGap = effectiveAxisWidth > 0 ? 1 : 0;
|
|
138
206
|
const bitmap = useMemo<NativeChartBitmap | null>(() => {
|
|
@@ -141,40 +209,115 @@ export function StaticChartSurface({
|
|
|
141
209
|
if (!scene) return null;
|
|
142
210
|
return renderNativeChartBase(scene, bitmapSize.pixelWidth, bitmapSize.pixelHeight);
|
|
143
211
|
}, [bitmapSize, points, renderOptions]);
|
|
212
|
+
const canvasCrosshair = useMemo<ChartSurfaceProps["crosshair"]>(() => {
|
|
213
|
+
if (!bitmap || !cursor) return null;
|
|
214
|
+
return {
|
|
215
|
+
pixelX: Math.round(clampRatio(cursor.x / Math.max(plotWidth - 1, 1)) * Math.max(bitmap.width - 1, 0)),
|
|
216
|
+
pixelY: Math.round(clampRatio(cursor.y / Math.max(plotHeight - 1, 1)) * Math.max(bitmap.height - 1, 0)),
|
|
217
|
+
color: colors.crosshairColor,
|
|
218
|
+
};
|
|
219
|
+
}, [bitmap, colors.crosshairColor, cursor, plotHeight, plotWidth]);
|
|
220
|
+
const handleCursorEvent = useCallback((event: ChartMouseEventLike) => {
|
|
221
|
+
const pointer = localPlotPointer(event, plotRef.current);
|
|
222
|
+
if (!pointer) return;
|
|
223
|
+
event.preventDefault?.();
|
|
224
|
+
setCursor((current) => (
|
|
225
|
+
current && current.x === pointer.x && current.y === pointer.y ? current : pointer
|
|
226
|
+
));
|
|
227
|
+
}, []);
|
|
228
|
+
const clearCursor = useCallback(() => {
|
|
229
|
+
setCursor(null);
|
|
230
|
+
}, []);
|
|
144
231
|
|
|
145
232
|
return (
|
|
146
|
-
<Box flexDirection="column" width={totalWidth} height={plotHeight +
|
|
233
|
+
<Box flexDirection="column" width={totalWidth} height={plotHeight + xAxisRows + xMarkerRows + labelRows}>
|
|
147
234
|
{yAxisLabel ? (
|
|
148
235
|
<Box height={1}>
|
|
149
236
|
<Text fg={yAxisColor}>{yAxisLabel}</Text>
|
|
150
237
|
</Box>
|
|
151
238
|
) : null}
|
|
152
239
|
<Box flexDirection="row" height={plotHeight}>
|
|
153
|
-
<
|
|
240
|
+
<Box
|
|
241
|
+
ref={plotRef}
|
|
242
|
+
position="relative"
|
|
154
243
|
width={plotWidth}
|
|
155
244
|
height={plotHeight}
|
|
156
|
-
|
|
157
|
-
|
|
245
|
+
onMouseMove={handleCursorEvent}
|
|
246
|
+
onMouseDown={handleCursorEvent}
|
|
247
|
+
onMouseOut={clearCursor}
|
|
248
|
+
data-gloom-role="static-chart-plot"
|
|
158
249
|
>
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
250
|
+
<ChartSurface
|
|
251
|
+
width={plotWidth}
|
|
252
|
+
height={plotHeight}
|
|
253
|
+
flexDirection="column"
|
|
254
|
+
bitmaps={bitmap ? [bitmap] : null}
|
|
255
|
+
crosshair={canvasCrosshair}
|
|
256
|
+
>
|
|
257
|
+
{textResult.lines.map((line, index) => (
|
|
258
|
+
<Text key={index} content={line} />
|
|
259
|
+
))}
|
|
260
|
+
</ChartSurface>
|
|
261
|
+
{xMarkers ? (
|
|
262
|
+
<StaticXMarkerOverlay
|
|
263
|
+
markers={xMarkers}
|
|
264
|
+
width={plotWidth}
|
|
265
|
+
height={plotHeight}
|
|
266
|
+
fallbackColor={xAxisColor ?? timeAxisColor}
|
|
267
|
+
/>
|
|
268
|
+
) : null}
|
|
269
|
+
</Box>
|
|
163
270
|
{effectiveAxisWidth > 0 ? (
|
|
164
271
|
<>
|
|
165
272
|
<Box width={effectiveAxisGap} />
|
|
166
|
-
<
|
|
167
|
-
{
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
273
|
+
<PriceAxisLabels
|
|
274
|
+
axisLabels={effectiveAxisLabelsByRow}
|
|
275
|
+
axisWidth={effectiveAxisWidth}
|
|
276
|
+
axisSectionWidth={effectiveAxisWidth}
|
|
277
|
+
height={plotHeight}
|
|
278
|
+
cursorRow={textResult.cursorRow}
|
|
279
|
+
cursorPixelY={cursor ? cursor.y * cellHeightPx : null}
|
|
280
|
+
cursorLabel={cursorAxisLabel}
|
|
281
|
+
cursorColor={colors.crosshairColor}
|
|
282
|
+
axisColor={yAxisColor}
|
|
283
|
+
/>
|
|
173
284
|
</>
|
|
174
285
|
) : null}
|
|
175
286
|
</Box>
|
|
176
|
-
{showTimeAxis ? (
|
|
177
|
-
<
|
|
287
|
+
{showTimeAxis || (xAxisLabels?.length ?? 0) > 0 ? (
|
|
288
|
+
<Box height={1} flexDirection="row">
|
|
289
|
+
{xAxisLabels ? (
|
|
290
|
+
<StaticXAxisLabels
|
|
291
|
+
labels={xAxisLabels}
|
|
292
|
+
width={plotWidth}
|
|
293
|
+
color={xAxisColor ?? timeAxisColor}
|
|
294
|
+
cursorColumn={textResult.cursorColumn}
|
|
295
|
+
cursorPixelX={cursor ? cursor.x * cellWidthPx : null}
|
|
296
|
+
cursorLabel={cursorXAxisLabel}
|
|
297
|
+
cursorColor={colors.crosshairColor}
|
|
298
|
+
cursorBackgroundColor={colors.bgColor ?? themeColors.bg}
|
|
299
|
+
/>
|
|
300
|
+
) : (
|
|
301
|
+
<Text fg={timeAxisColor}>{textResult.timeLabels}</Text>
|
|
302
|
+
)}
|
|
303
|
+
{effectiveAxisWidth > 0 ? (
|
|
304
|
+
<>
|
|
305
|
+
<Box width={effectiveAxisGap} />
|
|
306
|
+
<Box width={effectiveAxisWidth} />
|
|
307
|
+
</>
|
|
308
|
+
) : null}
|
|
309
|
+
</Box>
|
|
310
|
+
) : null}
|
|
311
|
+
{xMarkers && xMarkerRows > 0 ? (
|
|
312
|
+
<Box height={1} flexDirection="row">
|
|
313
|
+
<StaticXMarkerLabels markers={xMarkers} width={plotWidth} fallbackColor={xAxisColor ?? timeAxisColor} />
|
|
314
|
+
{effectiveAxisWidth > 0 ? (
|
|
315
|
+
<>
|
|
316
|
+
<Box width={effectiveAxisGap} />
|
|
317
|
+
<Box width={effectiveAxisWidth} />
|
|
318
|
+
</>
|
|
319
|
+
) : null}
|
|
320
|
+
</Box>
|
|
178
321
|
) : null}
|
|
179
322
|
</Box>
|
|
180
323
|
);
|
|
@@ -76,6 +76,31 @@ describe("stock chart pointer helpers", () => {
|
|
|
76
76
|
});
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
+
test("uses absolute renderable coordinates for desktop mouse hit testing", () => {
|
|
80
|
+
const pointer = getLocalPlotPointer({
|
|
81
|
+
x: 49,
|
|
82
|
+
y: 10,
|
|
83
|
+
preciseX: 49.5,
|
|
84
|
+
preciseY: 10.25,
|
|
85
|
+
modifiers: { shift: false, alt: false, ctrl: false },
|
|
86
|
+
}, {
|
|
87
|
+
x: 0,
|
|
88
|
+
y: 0,
|
|
89
|
+
absoluteX: 10,
|
|
90
|
+
absoluteY: 5,
|
|
91
|
+
width: 40,
|
|
92
|
+
height: 12,
|
|
93
|
+
}, renderer);
|
|
94
|
+
|
|
95
|
+
expect(pointer).toMatchObject({
|
|
96
|
+
hasPixelPrecision: false,
|
|
97
|
+
pixelX: null,
|
|
98
|
+
pixelY: null,
|
|
99
|
+
});
|
|
100
|
+
expect(pointer!.cellX).toBeCloseTo(39.5, 2);
|
|
101
|
+
expect(pointer!.cellY).toBeCloseTo(5.25, 2);
|
|
102
|
+
});
|
|
103
|
+
|
|
79
104
|
test("can derive local pixels from cell coordinates for non-pixel fallbacks", () => {
|
|
80
105
|
const pixels = projectCellCursorToLocalPixels(14, 5, renderable, renderer);
|
|
81
106
|
|