gloomberb 0.6.0 → 0.7.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/package.json +2 -1
- package/patches/@opentui%2Fcore@0.1.90.patch +48 -24
- package/src/app.tsx +9 -6
- package/src/cli/registry.test.ts +0 -1
- package/src/components/chart/chart-controller.ts +0 -1
- package/src/components/chart/chart-data.ts +1 -31
- package/src/components/chart/chart-renderer.ts +70 -0
- package/src/components/chart/chart-resolution.test.ts +0 -36
- package/src/components/chart/chart-resolution.ts +0 -27
- package/src/components/chart/chart.test.ts +17 -45
- package/src/components/chart/comparison-chart-data.test.ts +1 -5
- package/src/components/chart/comparison-chart-data.ts +1 -4
- package/src/components/chart/comparison-stock-chart.tsx +62 -77
- package/src/components/chart/native/kitty.test.ts +23 -0
- package/src/components/chart/native/surface-visibility.ts +48 -0
- package/src/components/chart/static-chart-surface.tsx +28 -4
- package/src/components/chart/stock-chart.test.ts +0 -16
- package/src/components/chart/stock-chart.tsx +52 -71
- package/src/components/chart/time-axis-label.tsx +43 -0
- package/src/components/command-bar/command-bar.test.tsx +71 -8
- package/src/components/command-bar/command-bar.tsx +388 -713
- package/src/components/command-bar/confirm-body.tsx +51 -0
- package/src/components/command-bar/list-model.ts +77 -0
- package/src/components/command-bar/view-model.ts +0 -19
- package/src/components/command-bar/workflow-ops.ts +4 -0
- package/src/components/data-table-stack-view.test.tsx +39 -4
- package/src/components/data-table-stack-view.tsx +11 -104
- package/src/components/data-table-view.tsx +43 -79
- package/src/components/feed-data-table-stack-view.tsx +10 -17
- package/src/components/feed-stack-controller.ts +39 -0
- package/src/components/index.ts +6 -2
- package/src/components/layout/floating-pane.tsx +30 -2
- package/src/components/layout/header.tsx +10 -7
- package/src/components/layout/pane-content.tsx +2 -0
- package/src/components/layout/pane-cycle-order.test.ts +30 -0
- package/src/components/layout/pane-cycle-order.ts +32 -0
- package/src/components/layout/pane-footer.tsx +26 -4
- package/src/components/layout/pane-header.tsx +5 -5
- package/src/components/layout/pane-keyboard-scroll.test.tsx +226 -0
- package/src/components/layout/shell.test.tsx +66 -22
- package/src/components/layout/shell.tsx +13 -6
- package/src/components/onboarding/onboarding-wizard.tsx +1 -2
- package/src/components/pane-settings-dialog.tsx +12 -4
- package/src/components/pane-template-wizard.test.tsx +3 -4
- package/src/components/speedometer-gauge.test.tsx +45 -0
- package/src/components/speedometer-gauge.tsx +558 -0
- package/src/components/table-view-shared.tsx +158 -0
- package/src/components/ticker-list-table-view.tsx +42 -72
- package/src/components/ui/confirm-dialog.tsx +66 -0
- package/src/components/ui/data-table.tsx +18 -10
- package/src/components/ui/index.ts +0 -1
- package/src/components/ui/page-stack-view.tsx +39 -10
- package/src/components/ui/remote-image.tsx +15 -40
- package/src/components/ui/table-layout.test.ts +16 -0
- package/src/components/ui/table-layout.ts +10 -3
- package/src/components/ui/ui.test.tsx +100 -20
- package/src/core/state/app-state.ts +6 -3
- package/src/market-data/coordinator.test.ts +1 -1
- package/src/market-data/coordinator.ts +0 -1
- package/src/news/aggregator.ts +0 -3
- package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +46 -16
- package/src/plugins/builtin/ai/providers.ts +0 -17
- package/src/plugins/builtin/ai/screener-pane.test.tsx +3 -4
- package/src/plugins/builtin/ai/screener-pane.tsx +0 -3
- package/src/plugins/builtin/ask-ai.test.tsx +89 -2
- package/src/plugins/builtin/broker-manager/index.test.tsx +2 -2
- package/src/plugins/builtin/broker-manager/index.tsx +19 -36
- package/src/plugins/builtin/chat-controller.test.ts +105 -0
- package/src/plugins/builtin/chat-controller.ts +75 -6
- package/src/plugins/builtin/chat.test.tsx +6 -6
- package/src/plugins/builtin/chat.tsx +29 -20
- package/src/plugins/builtin/cloud-tweets.tsx +0 -1
- package/src/plugins/builtin/comparison-chart.test.tsx +53 -2
- package/src/plugins/builtin/comparison-chart.tsx +11 -5
- package/src/plugins/builtin/correlation/index.tsx +1 -6
- package/src/plugins/builtin/debug.tsx +1 -3
- package/src/plugins/builtin/econ/index.tsx +1 -2
- package/src/plugins/builtin/fear-greed/fear-greed-data.test.ts +72 -0
- package/src/plugins/builtin/fear-greed/fear-greed-data.ts +366 -0
- package/src/plugins/builtin/fear-greed/index.tsx +478 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
- package/src/plugins/builtin/help.test.tsx +2 -2
- package/src/plugins/builtin/help.tsx +2 -2
- package/src/plugins/builtin/holders/index.tsx +10 -15
- package/src/plugins/builtin/insider/index.tsx +22 -89
- package/src/plugins/builtin/market-movers/index.tsx +1 -1
- package/src/plugins/builtin/news-wire/index.tsx +41 -2
- package/src/plugins/builtin/news-wire/news-table.tsx +11 -11
- package/src/plugins/builtin/news.tsx +4 -16
- package/src/plugins/builtin/notes.tsx +18 -55
- package/src/plugins/builtin/options.test.tsx +58 -2
- package/src/plugins/builtin/options.tsx +108 -49
- package/src/plugins/builtin/plugin-groups.ts +2 -0
- package/src/plugins/builtin/portfolio-list/cli/commands.ts +1 -1
- package/src/plugins/builtin/research/index.tsx +5 -21
- package/src/plugins/builtin/sec-filing-display.tsx +84 -0
- package/src/plugins/builtin/sec.tsx +13 -66
- package/src/plugins/builtin/sectors/index.tsx +1 -1
- package/src/plugins/builtin/ticker-detail/chart-tab.tsx +0 -1
- package/src/plugins/builtin/ticker-detail/index.tsx +0 -1
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +0 -1
- package/src/plugins/builtin/ticker-detail/pane.tsx +36 -28
- package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +69 -4
- package/src/plugins/builtin/ticker-detail.test.tsx +102 -0
- package/src/plugins/builtin/world-indices/index.tsx +1 -1
- package/src/plugins/builtin/yield-curve/index.tsx +1 -1
- package/src/plugins/ibkr/broker-adapter.ts +1 -3
- package/src/plugins/ibkr/gateway-service-native.ts +1 -5
- package/src/plugins/ibkr/gateway-service.ts +1 -1
- package/src/plugins/ibkr/index.test.tsx +0 -2
- package/src/plugins/ibkr/trade-tab.test.tsx +56 -5
- package/src/plugins/ibkr/trade-tab.tsx +13 -11
- package/src/plugins/pane-manager.ts +0 -13
- package/src/plugins/plugin-runtime.test.tsx +6 -6
- package/src/plugins/plugin-runtime.tsx +13 -8
- package/src/plugins/prediction-markets/cli.ts +0 -1
- package/src/plugins/prediction-markets/controller-data.ts +0 -2
- package/src/plugins/prediction-markets/pane.test.tsx +3 -11
- package/src/plugins/prediction-markets/test-helpers.tsx +1 -1
- package/src/plugins/registry.ts +10 -39
- package/src/react/input.ts +11 -2
- package/src/renderers/electrobun/view/data-table.tsx +28 -6
- package/src/renderers/electrobun/view/desktop-controls.tsx +34 -20
- package/src/renderers/electrobun/view/dialog-host.tsx +11 -1
- package/src/renderers/electrobun/view/input-host.tsx +123 -12
- package/src/renderers/electrobun/view/key-event.ts +1 -1
- package/src/renderers/electrobun/view/styles.css +331 -0
- package/src/renderers/electrobun/view/ui-host.tsx +22 -3
- package/src/renderers/opentui/chart-surface.tsx +179 -0
- package/src/renderers/opentui/host.tsx +6 -0
- package/src/renderers/opentui/image-loader.test.ts +25 -0
- package/src/renderers/opentui/image-loader.ts +105 -0
- package/src/renderers/opentui/image-surface.tsx +266 -0
- package/src/renderers/opentui/input-host.tsx +45 -6
- package/src/renderers/opentui/start.tsx +48 -30
- package/src/renderers/opentui/test-utils.tsx +17 -0
- package/src/renderers/opentui/ui-host.tsx +7 -3
- package/src/sources/provider-router.ts +0 -4
- package/src/state/app-context.test.ts +45 -0
- package/src/state/app-context.tsx +4 -0
- package/src/state/pane-scroll-registry.tsx +254 -0
- package/src/state/use-inline-tickers.test.tsx +73 -0
- package/src/state/use-inline-tickers.ts +7 -1
- package/src/test-support/plugin-runtime.ts +3 -2
- package/src/theme/colors.ts +12 -3
- package/src/theme/themes.test.ts +23 -1
- package/src/theme/themes.ts +115 -0
- package/src/types/broker.ts +1 -1
- package/src/types/data-provider.ts +0 -1
- package/src/types/plugin.ts +0 -2
- package/src/ui/host.tsx +4 -0
- package/src/ui/index.tsx +9 -1
- package/src/utils/back-navigation.ts +24 -1
- package/src/version.ts +1 -1
- package/src/plugins/builtin/news-wire/feed-pane.tsx +0 -21
- package/src/plugins/builtin/news-wire/top-pane.tsx +0 -21
|
@@ -54,12 +54,10 @@ import {
|
|
|
54
54
|
normalizeChartResolutionSupport,
|
|
55
55
|
sortChartResolutions,
|
|
56
56
|
type ChartResolutionSupport,
|
|
57
|
-
type ManualChartResolution,
|
|
58
57
|
} from "./chart-resolution";
|
|
59
58
|
import { RIGHT_EDGE_ANCHOR_RATIO, resolveAnchoredChartZoom } from "./chart-viewport";
|
|
60
59
|
import {
|
|
61
60
|
buildComparisonChartScene,
|
|
62
|
-
formatComparisonAxisValue,
|
|
63
61
|
formatComparisonCursorAxisValue,
|
|
64
62
|
renderComparisonChart,
|
|
65
63
|
} from "./comparison-chart-renderer";
|
|
@@ -82,7 +80,6 @@ import {
|
|
|
82
80
|
} from "./chart-types";
|
|
83
81
|
import {
|
|
84
82
|
computeBitmapSize,
|
|
85
|
-
intersectCellRects,
|
|
86
83
|
renderNativeComparisonChartBase,
|
|
87
84
|
renderNativeCrosshairOverlay,
|
|
88
85
|
type CellRect,
|
|
@@ -93,7 +90,13 @@ import { ensureKittySupport, getCachedKittySupport } from "./native/kitty-suppor
|
|
|
93
90
|
import { resolveChartRendererState } from "./native/renderer-selection";
|
|
94
91
|
import { getNativeSurfaceManager } from "./native/surface-manager";
|
|
95
92
|
import { syncCachedNativeSurface } from "./native/surface-sync";
|
|
93
|
+
import {
|
|
94
|
+
getRenderableCellRect,
|
|
95
|
+
resolveNativeSurfaceVisibleRect,
|
|
96
|
+
type NativeSurfaceRenderableNode,
|
|
97
|
+
} from "./native/surface-visibility";
|
|
96
98
|
import { formatAxisCell, formatDateShort, resolveChartAxisWidth, type StyledContent } from "./chart-renderer";
|
|
99
|
+
import { TimeAxisLabel } from "./time-axis-label";
|
|
97
100
|
|
|
98
101
|
const MODE_CHIPS: Record<ComparisonChartRenderMode, string> = {
|
|
99
102
|
area: "A",
|
|
@@ -108,6 +111,7 @@ interface ComparisonStockChartProps {
|
|
|
108
111
|
symbols: string[];
|
|
109
112
|
axisMode: ChartAxisMode;
|
|
110
113
|
onOpenSymbol: (symbol: string) => void;
|
|
114
|
+
onEditTickers?: () => void;
|
|
111
115
|
}
|
|
112
116
|
|
|
113
117
|
interface ComparisonChartSymbolSource {
|
|
@@ -169,14 +173,6 @@ interface DisplayCursorState {
|
|
|
169
173
|
pixelY: number | null;
|
|
170
174
|
}
|
|
171
175
|
|
|
172
|
-
interface RenderableNode {
|
|
173
|
-
x: number;
|
|
174
|
-
y: number;
|
|
175
|
-
width: number;
|
|
176
|
-
height: number;
|
|
177
|
-
parent: RenderableNode | null;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
176
|
const EMPTY_DISPLAY_CURSOR: DisplayCursorState = {
|
|
181
177
|
cellX: null,
|
|
182
178
|
cellY: null,
|
|
@@ -384,41 +380,6 @@ function resolveCursorMotionKind(
|
|
|
384
380
|
: "cell";
|
|
385
381
|
}
|
|
386
382
|
|
|
387
|
-
function extractCellRect(renderable: RenderableNode): CellRect {
|
|
388
|
-
return {
|
|
389
|
-
x: renderable.x,
|
|
390
|
-
y: renderable.y,
|
|
391
|
-
width: renderable.width,
|
|
392
|
-
height: renderable.height,
|
|
393
|
-
};
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
function resolveVisibleRect(
|
|
397
|
-
renderable: RenderableNode | null,
|
|
398
|
-
terminalWidth: number,
|
|
399
|
-
terminalHeight: number,
|
|
400
|
-
): CellRect | null {
|
|
401
|
-
if (!renderable) return null;
|
|
402
|
-
|
|
403
|
-
let visible: CellRect = {
|
|
404
|
-
x: 0,
|
|
405
|
-
y: 0,
|
|
406
|
-
width: terminalWidth,
|
|
407
|
-
height: terminalHeight,
|
|
408
|
-
};
|
|
409
|
-
let current: RenderableNode | null = renderable;
|
|
410
|
-
|
|
411
|
-
while (current) {
|
|
412
|
-
const currentRect = extractCellRect(current);
|
|
413
|
-
const nextVisible = intersectCellRects(visible, currentRect);
|
|
414
|
-
if (!nextVisible) return null;
|
|
415
|
-
visible = nextVisible;
|
|
416
|
-
current = current.parent;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
return visible;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
383
|
function buildBlankPlotLines(width: number, height: number): string[] {
|
|
423
384
|
return Array.from({ length: height }, () => " ".repeat(width));
|
|
424
385
|
}
|
|
@@ -553,6 +514,7 @@ function ComparisonStockChartView({
|
|
|
553
514
|
preferredRenderer,
|
|
554
515
|
symbolSources,
|
|
555
516
|
onOpenSymbol,
|
|
517
|
+
onEditTickers,
|
|
556
518
|
}: ComparisonStockChartViewProps) {
|
|
557
519
|
const renderer = useNativeRenderer();
|
|
558
520
|
const { canvasCharts, cellWidthPx = 8, cellHeightPx = 18, pixelRatio = 1 } = useUiCapabilities();
|
|
@@ -587,6 +549,7 @@ function ComparisonStockChartView({
|
|
|
587
549
|
const lastCanvasBaseBitmapRef = useRef<{ key: string; bitmap: NativeChartBitmap } | null>(null);
|
|
588
550
|
const displayCursorRef = useRef<DisplayCursorState>(EMPTY_DISPLAY_CURSOR);
|
|
589
551
|
const targetCursorRef = useRef<DisplayCursorState>(EMPTY_DISPLAY_CURSOR);
|
|
552
|
+
const mouseCrosshairDisabledRef = useRef(false);
|
|
590
553
|
const cursorMotionKindRef = useRef<ChartCursorMotionKind>("discrete");
|
|
591
554
|
const animationFrameRef = useRef<number | null>(null);
|
|
592
555
|
const pendingCanonicalResetRef = useRef(1);
|
|
@@ -947,11 +910,10 @@ function ComparisonStockChartView({
|
|
|
947
910
|
}
|
|
948
911
|
return {
|
|
949
912
|
...current,
|
|
950
|
-
panOffset: clamp(pendingExpansion.targetPanOffset, 0, getMaxComparisonPanOffset(series, current.
|
|
913
|
+
panOffset: clamp(pendingExpansion.targetPanOffset, 0, getMaxComparisonPanOffset(series, current.zoomLevel)),
|
|
951
914
|
};
|
|
952
915
|
});
|
|
953
916
|
}, [
|
|
954
|
-
chartWidth,
|
|
955
917
|
series,
|
|
956
918
|
seriesDates,
|
|
957
919
|
viewState.activePreset,
|
|
@@ -1021,8 +983,8 @@ function ComparisonStockChartView({
|
|
|
1021
983
|
}), [viewState.panOffset, viewState.renderMode, viewState.zoomLevel]);
|
|
1022
984
|
|
|
1023
985
|
const visibleWindow = useMemo(() => (
|
|
1024
|
-
getVisibleComparisonWindow(series, projectionViewState
|
|
1025
|
-
), [
|
|
986
|
+
getVisibleComparisonWindow(series, projectionViewState)
|
|
987
|
+
), [projectionViewState, series]);
|
|
1026
988
|
|
|
1027
989
|
const projection = useMemo(() => (
|
|
1028
990
|
projectComparisonChartData(series, chartWidth, projectionViewState, axisMode)
|
|
@@ -1076,6 +1038,14 @@ function ComparisonStockChartView({
|
|
|
1076
1038
|
selectedSymbol: viewState.selectedSymbol,
|
|
1077
1039
|
colors: chartColors,
|
|
1078
1040
|
}), [chartColors, chartHeight, chartWidth, projection, viewState.selectedSymbol]);
|
|
1041
|
+
const displayScene = useMemo(() => buildComparisonChartScene(projection, {
|
|
1042
|
+
width: chartWidth,
|
|
1043
|
+
height: chartHeight,
|
|
1044
|
+
cursorX: displayCursorX,
|
|
1045
|
+
cursorY: displayCursorY,
|
|
1046
|
+
selectedSymbol: viewState.selectedSymbol,
|
|
1047
|
+
colors: chartColors,
|
|
1048
|
+
}), [chartColors, chartHeight, chartWidth, displayCursorX, displayCursorY, projection, viewState.selectedSymbol]);
|
|
1079
1049
|
|
|
1080
1050
|
const staticResult = useMemo(() => renderComparisonChart(projection, {
|
|
1081
1051
|
width: chartWidth,
|
|
@@ -1127,6 +1097,12 @@ function ComparisonStockChartView({
|
|
|
1127
1097
|
};
|
|
1128
1098
|
|
|
1129
1099
|
return [
|
|
1100
|
+
...(onEditTickers ? [{
|
|
1101
|
+
id: "tickers",
|
|
1102
|
+
key: "t",
|
|
1103
|
+
label: "ickers",
|
|
1104
|
+
onPress: onEditTickers,
|
|
1105
|
+
}] : []),
|
|
1130
1106
|
{
|
|
1131
1107
|
id: "mode",
|
|
1132
1108
|
key: "m",
|
|
@@ -1150,26 +1126,13 @@ function ComparisonStockChartView({
|
|
|
1150
1126
|
{ id: "reset", key: "0", label: "reset", onPress: resetView },
|
|
1151
1127
|
...(width >= 72 ? [{ id: "range", key: "1-7", label: "range", onPress: cycleRange }] : []),
|
|
1152
1128
|
];
|
|
1153
|
-
}, [effectiveResolution, resolutionChips, series, supportMap, viewState.presetRange, visibleDateWindow, width]);
|
|
1129
|
+
}, [effectiveResolution, onEditTickers, resolutionChips, series, supportMap, viewState.presetRange, visibleDateWindow, width]);
|
|
1154
1130
|
|
|
1155
1131
|
usePaneFooter("comparison-chart", () => ({
|
|
1156
1132
|
order: 10,
|
|
1157
1133
|
hints: footerHints,
|
|
1158
1134
|
}), [footerHints]);
|
|
1159
1135
|
|
|
1160
|
-
const liveScene = useMemo(() => (
|
|
1161
|
-
effectiveRenderer === "kitty"
|
|
1162
|
-
? staticScene
|
|
1163
|
-
: buildComparisonChartScene(projection, {
|
|
1164
|
-
width: chartWidth,
|
|
1165
|
-
height: chartHeight,
|
|
1166
|
-
cursorX: displayCursorX,
|
|
1167
|
-
cursorY: displayCursorY,
|
|
1168
|
-
selectedSymbol: viewState.selectedSymbol,
|
|
1169
|
-
colors: chartColors,
|
|
1170
|
-
})
|
|
1171
|
-
), [chartColors, chartHeight, chartWidth, displayCursorX, displayCursorY, effectiveRenderer, projection, staticScene, viewState.selectedSymbol]);
|
|
1172
|
-
|
|
1173
1136
|
const nativeCrosshair = useMemo<NativeCrosshairOverlay | null>(() => {
|
|
1174
1137
|
if (displayCursor.cellX === null || displayCursor.cellY === null) return null;
|
|
1175
1138
|
return {
|
|
@@ -1201,8 +1164,9 @@ function ComparisonStockChartView({
|
|
|
1201
1164
|
|
|
1202
1165
|
const syncPlacement = () => {
|
|
1203
1166
|
if (effectiveRenderer !== "kitty" || !rendererState.nativeReady || !plotRef.current) return;
|
|
1204
|
-
const
|
|
1205
|
-
const
|
|
1167
|
+
const plot = plotRef.current as NativeSurfaceRenderableNode;
|
|
1168
|
+
const rect = getRenderableCellRect(plot);
|
|
1169
|
+
const visibleRect = resolveNativeSurfaceVisibleRect(plot, renderer.terminalWidth, renderer.terminalHeight);
|
|
1206
1170
|
const previous = lastNativeGeometryRef.current;
|
|
1207
1171
|
if (previous
|
|
1208
1172
|
&& previous.rect.x === rect.x
|
|
@@ -1257,8 +1221,9 @@ function ComparisonStockChartView({
|
|
|
1257
1221
|
return;
|
|
1258
1222
|
}
|
|
1259
1223
|
|
|
1260
|
-
const
|
|
1261
|
-
const
|
|
1224
|
+
const plot = plotRef.current as NativeSurfaceRenderableNode;
|
|
1225
|
+
const plotRect = getRenderableCellRect(plot);
|
|
1226
|
+
const visibleRect = resolveNativeSurfaceVisibleRect(plot, renderer.terminalWidth, renderer.terminalHeight);
|
|
1262
1227
|
const bitmapSize = computeBitmapSize(plotRect, renderer.resolution, renderer.terminalWidth, renderer.terminalHeight);
|
|
1263
1228
|
const bitmapKey = buildComparisonNativeBitmapKey(
|
|
1264
1229
|
symbols.length,
|
|
@@ -1314,8 +1279,9 @@ function ComparisonStockChartView({
|
|
|
1314
1279
|
return;
|
|
1315
1280
|
}
|
|
1316
1281
|
|
|
1317
|
-
const
|
|
1318
|
-
const
|
|
1282
|
+
const plot = plotRef.current as NativeSurfaceRenderableNode;
|
|
1283
|
+
const plotRect = getRenderableCellRect(plot);
|
|
1284
|
+
const visibleRect = resolveNativeSurfaceVisibleRect(plot, renderer.terminalWidth, renderer.terminalHeight);
|
|
1319
1285
|
const bitmapSize = computeBitmapSize(plotRect, renderer.resolution, renderer.terminalWidth, renderer.terminalHeight);
|
|
1320
1286
|
const renderablePixelSize = getRenderablePixelSize(plotRef.current, renderer);
|
|
1321
1287
|
const overlayPixelX = scaleLocalPixelCoordinate(
|
|
@@ -1421,7 +1387,7 @@ function ComparisonStockChartView({
|
|
|
1421
1387
|
return;
|
|
1422
1388
|
case "a": {
|
|
1423
1389
|
const panStep = getKeyboardPanCellCount(chartWidth);
|
|
1424
|
-
if (viewState.panOffset >= getMaxComparisonPanOffset(series, viewState.
|
|
1390
|
+
if (viewState.panOffset >= getMaxComparisonPanOffset(series, viewState.zoomLevel) && expandBufferRange({
|
|
1425
1391
|
kind: "pan-left",
|
|
1426
1392
|
targetPanOffset: viewState.panOffset + panStep,
|
|
1427
1393
|
})) {
|
|
@@ -1429,7 +1395,7 @@ function ComparisonStockChartView({
|
|
|
1429
1395
|
}
|
|
1430
1396
|
setViewState((current) => ({
|
|
1431
1397
|
...clearActivePreset(current),
|
|
1432
|
-
panOffset: clamp(current.panOffset + panStep, 0, getMaxComparisonPanOffset(series, current.
|
|
1398
|
+
panOffset: clamp(current.panOffset + panStep, 0, getMaxComparisonPanOffset(series, current.zoomLevel)),
|
|
1433
1399
|
}));
|
|
1434
1400
|
return;
|
|
1435
1401
|
}
|
|
@@ -1437,7 +1403,7 @@ function ComparisonStockChartView({
|
|
|
1437
1403
|
const panStep = getKeyboardPanCellCount(chartWidth);
|
|
1438
1404
|
setViewState((current) => ({
|
|
1439
1405
|
...clearActivePreset(current),
|
|
1440
|
-
panOffset: clamp(current.panOffset - panStep, 0, getMaxComparisonPanOffset(series, current.
|
|
1406
|
+
panOffset: clamp(current.panOffset - panStep, 0, getMaxComparisonPanOffset(series, current.zoomLevel)),
|
|
1441
1407
|
}));
|
|
1442
1408
|
return;
|
|
1443
1409
|
}
|
|
@@ -1453,7 +1419,11 @@ function ComparisonStockChartView({
|
|
|
1453
1419
|
setResolution(nextResolution);
|
|
1454
1420
|
return;
|
|
1455
1421
|
}
|
|
1422
|
+
case "t":
|
|
1423
|
+
onEditTickers?.();
|
|
1424
|
+
return;
|
|
1456
1425
|
case "h":
|
|
1426
|
+
mouseCrosshairDisabledRef.current = false;
|
|
1457
1427
|
cursorMotionKindRef.current = "discrete";
|
|
1458
1428
|
setViewState((current) => ({
|
|
1459
1429
|
...current,
|
|
@@ -1461,6 +1431,7 @@ function ComparisonStockChartView({
|
|
|
1461
1431
|
}));
|
|
1462
1432
|
return;
|
|
1463
1433
|
case "l":
|
|
1434
|
+
mouseCrosshairDisabledRef.current = false;
|
|
1464
1435
|
cursorMotionKindRef.current = "discrete";
|
|
1465
1436
|
setViewState((current) => ({
|
|
1466
1437
|
...current,
|
|
@@ -1468,6 +1439,7 @@ function ComparisonStockChartView({
|
|
|
1468
1439
|
}));
|
|
1469
1440
|
return;
|
|
1470
1441
|
case "escape":
|
|
1442
|
+
mouseCrosshairDisabledRef.current = true;
|
|
1471
1443
|
updateDisplayCursorTarget(EMPTY_DISPLAY_CURSOR, "discrete");
|
|
1472
1444
|
setViewState((current) => ({ ...current, cursorX: null, cursorY: null }));
|
|
1473
1445
|
return;
|
|
@@ -1524,6 +1496,7 @@ function ComparisonStockChartView({
|
|
|
1524
1496
|
});
|
|
1525
1497
|
|
|
1526
1498
|
const handlePlotMove = (event: ChartMouseEvent) => {
|
|
1499
|
+
if (mouseCrosshairDisabledRef.current) return;
|
|
1527
1500
|
const localPointer = getLocalPlotPointer(event, plotRef.current, renderer);
|
|
1528
1501
|
if (!localPointer) return;
|
|
1529
1502
|
const selectionCursor = resolveSelectionCursor(localPointer, projection.dates.length, chartWidth);
|
|
@@ -1542,6 +1515,7 @@ function ComparisonStockChartView({
|
|
|
1542
1515
|
};
|
|
1543
1516
|
|
|
1544
1517
|
const handlePlotDown = (event: ChartMouseEvent) => {
|
|
1518
|
+
mouseCrosshairDisabledRef.current = false;
|
|
1545
1519
|
const localPointer = getLocalPlotPointer(event, plotRef.current, renderer);
|
|
1546
1520
|
if (!localPointer) return;
|
|
1547
1521
|
const selectionCursor = resolveSelectionCursor(localPointer, projection.dates.length, chartWidth);
|
|
@@ -1564,6 +1538,7 @@ function ComparisonStockChartView({
|
|
|
1564
1538
|
};
|
|
1565
1539
|
|
|
1566
1540
|
const handlePlotDrag = (event: ChartMouseEvent) => {
|
|
1541
|
+
if (mouseCrosshairDisabledRef.current && !dragRef.current) return;
|
|
1567
1542
|
const localPointer = getLocalPlotPointer(event, plotRef.current, renderer);
|
|
1568
1543
|
if (localPointer) {
|
|
1569
1544
|
const selectionCursor = resolveSelectionCursor(localPointer, projection.dates.length, chartWidth);
|
|
@@ -1628,7 +1603,7 @@ function ComparisonStockChartView({
|
|
|
1628
1603
|
if (scrollPanCells === 0) return;
|
|
1629
1604
|
|
|
1630
1605
|
const targetPanOffset = viewState.panOffset + scrollPanCells;
|
|
1631
|
-
if (scrollPanCells > 0 && viewState.panOffset >= getMaxComparisonPanOffset(series, viewState.
|
|
1606
|
+
if (scrollPanCells > 0 && viewState.panOffset >= getMaxComparisonPanOffset(series, viewState.zoomLevel) && expandBufferRange({
|
|
1632
1607
|
kind: "pan-left",
|
|
1633
1608
|
targetPanOffset,
|
|
1634
1609
|
})) {
|
|
@@ -1639,7 +1614,7 @@ function ComparisonStockChartView({
|
|
|
1639
1614
|
panOffset: clamp(
|
|
1640
1615
|
current.panOffset + scrollPanCells,
|
|
1641
1616
|
0,
|
|
1642
|
-
getMaxComparisonPanOffset(series, current.
|
|
1617
|
+
getMaxComparisonPanOffset(series, current.zoomLevel),
|
|
1643
1618
|
),
|
|
1644
1619
|
}));
|
|
1645
1620
|
};
|
|
@@ -1649,7 +1624,8 @@ function ComparisonStockChartView({
|
|
|
1649
1624
|
}
|
|
1650
1625
|
|
|
1651
1626
|
const hasChartData = series.some((entry) => entry.points.length > 0);
|
|
1652
|
-
const
|
|
1627
|
+
const hasDisplayCursor = displayCursorX !== null && displayCursorY !== null;
|
|
1628
|
+
const cursorScene = hasDisplayCursor ? displayScene : staticScene;
|
|
1653
1629
|
const selectedSeries = hasChartData ? (cursorScene?.selectedSeries ?? staticResult.selectedSeries) : null;
|
|
1654
1630
|
const selectedPoint = hasChartData ? (cursorScene?.selectedPoint ?? staticResult.selectedPoint) : null;
|
|
1655
1631
|
const selectedRawValue = selectedPoint?.rawValue ?? selectedSeries?.latestRawValue ?? null;
|
|
@@ -1677,6 +1653,8 @@ function ComparisonStockChartView({
|
|
|
1677
1653
|
start: visibleWindow.dates[0] ?? null,
|
|
1678
1654
|
end: visibleWindow.dates[visibleWindow.dates.length - 1] ?? null,
|
|
1679
1655
|
});
|
|
1656
|
+
const cursorTimeAxisColumn = hasDisplayCursor ? displayScene?.cursorColumn ?? null : null;
|
|
1657
|
+
const cursorTimeAxisDate = hasDisplayCursor ? displayScene?.activeDate ?? null : null;
|
|
1680
1658
|
|
|
1681
1659
|
const canvasBitmapSize = useMemo(() => {
|
|
1682
1660
|
if (!canvasCharts) return null;
|
|
@@ -1965,7 +1943,14 @@ function ComparisonStockChartView({
|
|
|
1965
1943
|
</Box>
|
|
1966
1944
|
|
|
1967
1945
|
<Box height={timeAxisRows}>
|
|
1968
|
-
<
|
|
1946
|
+
<TimeAxisLabel
|
|
1947
|
+
timeLabels={timeAxisLabel}
|
|
1948
|
+
width={chartWidth}
|
|
1949
|
+
cursorColumn={cursorTimeAxisColumn}
|
|
1950
|
+
cursorDate={cursorTimeAxisDate}
|
|
1951
|
+
dates={projection.dates}
|
|
1952
|
+
cursorColor={chartColors.crosshairColor}
|
|
1953
|
+
/>
|
|
1969
1954
|
</Box>
|
|
1970
1955
|
|
|
1971
1956
|
{legendRows > 0 && (
|
|
@@ -17,6 +17,7 @@ import { chunkBase64Payload, encodeKittyTransmitRgba } from "./kitty-protocol";
|
|
|
17
17
|
import { resolveChartRendererState } from "./renderer-selection";
|
|
18
18
|
import { computeSurfaceVisibleFragments, NativeSurfaceManager } from "./surface-manager";
|
|
19
19
|
import { syncCachedNativeSurface } from "./surface-sync";
|
|
20
|
+
import { resolveNativeSurfaceVisibleRect, type NativeSurfaceRenderableNode } from "./surface-visibility";
|
|
20
21
|
|
|
21
22
|
describe("resolveChartRendererState", () => {
|
|
22
23
|
test("resolves auto and forced kitty correctly", () => {
|
|
@@ -98,6 +99,28 @@ describe("computeNativePlacement", () => {
|
|
|
98
99
|
});
|
|
99
100
|
});
|
|
100
101
|
|
|
102
|
+
describe("resolveNativeSurfaceVisibleRect", () => {
|
|
103
|
+
test("treats hidden ancestors as invisible for native graphics", () => {
|
|
104
|
+
const hiddenParent: NativeSurfaceRenderableNode = {
|
|
105
|
+
x: 0,
|
|
106
|
+
y: 0,
|
|
107
|
+
width: 80,
|
|
108
|
+
height: 20,
|
|
109
|
+
visible: false,
|
|
110
|
+
parent: null,
|
|
111
|
+
};
|
|
112
|
+
const child: NativeSurfaceRenderableNode = {
|
|
113
|
+
x: 2,
|
|
114
|
+
y: 3,
|
|
115
|
+
width: 20,
|
|
116
|
+
height: 8,
|
|
117
|
+
parent: hiddenParent,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
expect(resolveNativeSurfaceVisibleRect(child, 100, 30)).toBeNull();
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
101
124
|
describe("renderNativeChartBase", () => {
|
|
102
125
|
test("leaves untouched bitmap pixels transparent", () => {
|
|
103
126
|
const palette = resolveChartPalette({
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { intersectCellRects, type CellRect } from "./chart-rasterizer";
|
|
2
|
+
|
|
3
|
+
export interface NativeSurfaceRenderableNode {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
visible?: boolean;
|
|
9
|
+
parent: NativeSurfaceRenderableNode | null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function getRenderableCellRect(
|
|
13
|
+
renderable: Pick<NativeSurfaceRenderableNode, "x" | "y" | "width" | "height">,
|
|
14
|
+
): CellRect {
|
|
15
|
+
return {
|
|
16
|
+
x: renderable.x,
|
|
17
|
+
y: renderable.y,
|
|
18
|
+
width: renderable.width,
|
|
19
|
+
height: renderable.height,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function resolveNativeSurfaceVisibleRect(
|
|
24
|
+
renderable: NativeSurfaceRenderableNode | null,
|
|
25
|
+
terminalWidth: number,
|
|
26
|
+
terminalHeight: number,
|
|
27
|
+
): CellRect | null {
|
|
28
|
+
if (!renderable) return null;
|
|
29
|
+
|
|
30
|
+
let visible: CellRect = {
|
|
31
|
+
x: 0,
|
|
32
|
+
y: 0,
|
|
33
|
+
width: terminalWidth,
|
|
34
|
+
height: terminalHeight,
|
|
35
|
+
};
|
|
36
|
+
let current: NativeSurfaceRenderableNode | null = renderable;
|
|
37
|
+
|
|
38
|
+
while (current) {
|
|
39
|
+
if (current.visible === false) return null;
|
|
40
|
+
|
|
41
|
+
const nextVisible = intersectCellRects(visible, getRenderableCellRect(current));
|
|
42
|
+
if (!nextVisible) return null;
|
|
43
|
+
visible = nextVisible;
|
|
44
|
+
current = current.parent;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return visible;
|
|
48
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
|
-
import { Box, ChartSurface, Text, useUiCapabilities } from "../../ui";
|
|
2
|
+
import { Box, ChartSurface, Text, useNativeRenderer, useUiCapabilities } from "../../ui";
|
|
3
3
|
import type { ProjectedChartPoint } from "./chart-data";
|
|
4
4
|
import {
|
|
5
5
|
buildChartScene,
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
renderChart,
|
|
9
9
|
type RenderChartOptions,
|
|
10
10
|
} from "./chart-renderer";
|
|
11
|
-
import { renderNativeChartBase, type NativeChartBitmap } from "./native/chart-rasterizer";
|
|
11
|
+
import { computeBitmapSize, renderNativeChartBase, type NativeChartBitmap } from "./native/chart-rasterizer";
|
|
12
12
|
|
|
13
13
|
export interface StaticChartSurfaceProps extends Omit<
|
|
14
14
|
RenderChartOptions,
|
|
@@ -47,10 +47,12 @@ export function StaticChartSurface({
|
|
|
47
47
|
}: StaticChartSurfaceProps) {
|
|
48
48
|
const {
|
|
49
49
|
canvasCharts,
|
|
50
|
+
nativeCharts,
|
|
50
51
|
cellWidthPx = 8,
|
|
51
52
|
cellHeightPx = 18,
|
|
52
53
|
pixelRatio = 1,
|
|
53
54
|
} = useUiCapabilities();
|
|
55
|
+
const nativeRenderer = useNativeRenderer();
|
|
54
56
|
const timeAxisRows = showTimeAxis ? 1 : 0;
|
|
55
57
|
const labelRows = yAxisLabel ? 1 : 0;
|
|
56
58
|
const totalWidth = Math.max(1, Math.floor(width));
|
|
@@ -101,14 +103,36 @@ export function StaticChartSurface({
|
|
|
101
103
|
: 0;
|
|
102
104
|
const axisGap = axisWidth > 0 ? 1 : 0;
|
|
103
105
|
const plotWidth = Math.max(1, totalWidth - axisWidth - axisGap);
|
|
106
|
+
const rendererResolution = nativeRenderer.resolution;
|
|
107
|
+
const rendererTerminalWidth = nativeRenderer.terminalWidth;
|
|
108
|
+
const rendererTerminalHeight = nativeRenderer.terminalHeight;
|
|
104
109
|
const bitmapSize = useMemo(() => {
|
|
105
|
-
if (
|
|
110
|
+
if (nativeCharts && rendererResolution && rendererTerminalWidth > 0 && rendererTerminalHeight > 0) {
|
|
111
|
+
return computeBitmapSize(
|
|
112
|
+
{ x: 0, y: 0, width: plotWidth, height: plotHeight },
|
|
113
|
+
rendererResolution,
|
|
114
|
+
rendererTerminalWidth,
|
|
115
|
+
rendererTerminalHeight,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
if (!canvasCharts && !nativeCharts) return null;
|
|
106
119
|
const scale = Math.max(1, pixelRatio);
|
|
107
120
|
return {
|
|
108
121
|
pixelWidth: Math.max(1, Math.round(plotWidth * cellWidthPx * scale)),
|
|
109
122
|
pixelHeight: Math.max(1, Math.round(plotHeight * cellHeightPx * scale)),
|
|
110
123
|
};
|
|
111
|
-
}, [
|
|
124
|
+
}, [
|
|
125
|
+
canvasCharts,
|
|
126
|
+
cellHeightPx,
|
|
127
|
+
cellWidthPx,
|
|
128
|
+
nativeCharts,
|
|
129
|
+
pixelRatio,
|
|
130
|
+
plotHeight,
|
|
131
|
+
plotWidth,
|
|
132
|
+
rendererResolution,
|
|
133
|
+
rendererTerminalHeight,
|
|
134
|
+
rendererTerminalWidth,
|
|
135
|
+
]);
|
|
112
136
|
const renderOptions = useMemo<RenderChartOptions>(() => ({
|
|
113
137
|
width: plotWidth,
|
|
114
138
|
height: plotHeight,
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
resolveAdjacentSelectionCursorX,
|
|
10
10
|
resolveChartKeyboardKey,
|
|
11
11
|
resolveIndicatorBufferRange,
|
|
12
|
-
resolveVisibleChartDateWindow,
|
|
13
12
|
resolveSelectionDisplayCursorState,
|
|
14
13
|
} from "./stock-chart";
|
|
15
14
|
import { getPointTerminalColumn } from "./chart-renderer";
|
|
@@ -215,21 +214,6 @@ describe("stock chart auto helpers", () => {
|
|
|
215
214
|
expect(nextState.window).toEqual(renderedAutoView.window);
|
|
216
215
|
});
|
|
217
216
|
|
|
218
|
-
test("derives the visible label window from the actual visible chart points", () => {
|
|
219
|
-
const visibleWindow = resolveVisibleChartDateWindow([
|
|
220
|
-
{ date: new Date("2026-01-03T00:00:00Z") },
|
|
221
|
-
{ date: new Date("2026-01-06T00:00:00Z") },
|
|
222
|
-
], {
|
|
223
|
-
start: new Date("2026-01-01T00:00:00Z"),
|
|
224
|
-
end: new Date("2026-01-08T00:00:00Z"),
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
expect(visibleWindow).toEqual({
|
|
228
|
-
start: new Date("2026-01-03T00:00:00Z"),
|
|
229
|
-
end: new Date("2026-01-06T00:00:00Z"),
|
|
230
|
-
});
|
|
231
|
-
});
|
|
232
|
-
|
|
233
217
|
test("zooms in by at least one visible point on every auto step when more detail is available", () => {
|
|
234
218
|
const history = makeDailyHistory(1, 10);
|
|
235
219
|
|