gloomberb 0.6.1 → 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 +1 -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 +48 -27
- 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 +38 -21
- 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 +5 -1
- package/src/components/layout/floating-pane.tsx +30 -2
- package/src/components/layout/header.tsx +10 -7
- 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 +11 -4
- package/src/components/layout/pane-header.tsx +5 -5
- 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/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-chart-tab-switch.test.tsx +3 -4
- 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 +3 -4
- 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 +1 -0
- 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 +74 -10
- package/src/renderers/electrobun/view/key-event.ts +1 -1
- package/src/renderers/electrobun/view/styles.css +331 -0
- package/src/renderers/opentui/chart-surface.tsx +179 -0
- package/src/renderers/opentui/start.tsx +48 -30
- package/src/renderers/opentui/test-utils.tsx +17 -0
- package/src/renderers/opentui/ui-host.tsx +5 -2
- package/src/sources/provider-router.ts +0 -4
- package/src/state/app-context.test.ts +45 -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 +1 -0
- 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";
|
|
@@ -98,6 +96,7 @@ import {
|
|
|
98
96
|
type NativeSurfaceRenderableNode,
|
|
99
97
|
} from "./native/surface-visibility";
|
|
100
98
|
import { formatAxisCell, formatDateShort, resolveChartAxisWidth, type StyledContent } from "./chart-renderer";
|
|
99
|
+
import { TimeAxisLabel } from "./time-axis-label";
|
|
101
100
|
|
|
102
101
|
const MODE_CHIPS: Record<ComparisonChartRenderMode, string> = {
|
|
103
102
|
area: "A",
|
|
@@ -112,6 +111,7 @@ interface ComparisonStockChartProps {
|
|
|
112
111
|
symbols: string[];
|
|
113
112
|
axisMode: ChartAxisMode;
|
|
114
113
|
onOpenSymbol: (symbol: string) => void;
|
|
114
|
+
onEditTickers?: () => void;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
interface ComparisonChartSymbolSource {
|
|
@@ -514,6 +514,7 @@ function ComparisonStockChartView({
|
|
|
514
514
|
preferredRenderer,
|
|
515
515
|
symbolSources,
|
|
516
516
|
onOpenSymbol,
|
|
517
|
+
onEditTickers,
|
|
517
518
|
}: ComparisonStockChartViewProps) {
|
|
518
519
|
const renderer = useNativeRenderer();
|
|
519
520
|
const { canvasCharts, cellWidthPx = 8, cellHeightPx = 18, pixelRatio = 1 } = useUiCapabilities();
|
|
@@ -548,6 +549,7 @@ function ComparisonStockChartView({
|
|
|
548
549
|
const lastCanvasBaseBitmapRef = useRef<{ key: string; bitmap: NativeChartBitmap } | null>(null);
|
|
549
550
|
const displayCursorRef = useRef<DisplayCursorState>(EMPTY_DISPLAY_CURSOR);
|
|
550
551
|
const targetCursorRef = useRef<DisplayCursorState>(EMPTY_DISPLAY_CURSOR);
|
|
552
|
+
const mouseCrosshairDisabledRef = useRef(false);
|
|
551
553
|
const cursorMotionKindRef = useRef<ChartCursorMotionKind>("discrete");
|
|
552
554
|
const animationFrameRef = useRef<number | null>(null);
|
|
553
555
|
const pendingCanonicalResetRef = useRef(1);
|
|
@@ -908,11 +910,10 @@ function ComparisonStockChartView({
|
|
|
908
910
|
}
|
|
909
911
|
return {
|
|
910
912
|
...current,
|
|
911
|
-
panOffset: clamp(pendingExpansion.targetPanOffset, 0, getMaxComparisonPanOffset(series, current.
|
|
913
|
+
panOffset: clamp(pendingExpansion.targetPanOffset, 0, getMaxComparisonPanOffset(series, current.zoomLevel)),
|
|
912
914
|
};
|
|
913
915
|
});
|
|
914
916
|
}, [
|
|
915
|
-
chartWidth,
|
|
916
917
|
series,
|
|
917
918
|
seriesDates,
|
|
918
919
|
viewState.activePreset,
|
|
@@ -982,8 +983,8 @@ function ComparisonStockChartView({
|
|
|
982
983
|
}), [viewState.panOffset, viewState.renderMode, viewState.zoomLevel]);
|
|
983
984
|
|
|
984
985
|
const visibleWindow = useMemo(() => (
|
|
985
|
-
getVisibleComparisonWindow(series, projectionViewState
|
|
986
|
-
), [
|
|
986
|
+
getVisibleComparisonWindow(series, projectionViewState)
|
|
987
|
+
), [projectionViewState, series]);
|
|
987
988
|
|
|
988
989
|
const projection = useMemo(() => (
|
|
989
990
|
projectComparisonChartData(series, chartWidth, projectionViewState, axisMode)
|
|
@@ -1037,6 +1038,14 @@ function ComparisonStockChartView({
|
|
|
1037
1038
|
selectedSymbol: viewState.selectedSymbol,
|
|
1038
1039
|
colors: chartColors,
|
|
1039
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]);
|
|
1040
1049
|
|
|
1041
1050
|
const staticResult = useMemo(() => renderComparisonChart(projection, {
|
|
1042
1051
|
width: chartWidth,
|
|
@@ -1088,6 +1097,12 @@ function ComparisonStockChartView({
|
|
|
1088
1097
|
};
|
|
1089
1098
|
|
|
1090
1099
|
return [
|
|
1100
|
+
...(onEditTickers ? [{
|
|
1101
|
+
id: "tickers",
|
|
1102
|
+
key: "t",
|
|
1103
|
+
label: "ickers",
|
|
1104
|
+
onPress: onEditTickers,
|
|
1105
|
+
}] : []),
|
|
1091
1106
|
{
|
|
1092
1107
|
id: "mode",
|
|
1093
1108
|
key: "m",
|
|
@@ -1111,26 +1126,13 @@ function ComparisonStockChartView({
|
|
|
1111
1126
|
{ id: "reset", key: "0", label: "reset", onPress: resetView },
|
|
1112
1127
|
...(width >= 72 ? [{ id: "range", key: "1-7", label: "range", onPress: cycleRange }] : []),
|
|
1113
1128
|
];
|
|
1114
|
-
}, [effectiveResolution, resolutionChips, series, supportMap, viewState.presetRange, visibleDateWindow, width]);
|
|
1129
|
+
}, [effectiveResolution, onEditTickers, resolutionChips, series, supportMap, viewState.presetRange, visibleDateWindow, width]);
|
|
1115
1130
|
|
|
1116
1131
|
usePaneFooter("comparison-chart", () => ({
|
|
1117
1132
|
order: 10,
|
|
1118
1133
|
hints: footerHints,
|
|
1119
1134
|
}), [footerHints]);
|
|
1120
1135
|
|
|
1121
|
-
const liveScene = useMemo(() => (
|
|
1122
|
-
effectiveRenderer === "kitty"
|
|
1123
|
-
? staticScene
|
|
1124
|
-
: buildComparisonChartScene(projection, {
|
|
1125
|
-
width: chartWidth,
|
|
1126
|
-
height: chartHeight,
|
|
1127
|
-
cursorX: displayCursorX,
|
|
1128
|
-
cursorY: displayCursorY,
|
|
1129
|
-
selectedSymbol: viewState.selectedSymbol,
|
|
1130
|
-
colors: chartColors,
|
|
1131
|
-
})
|
|
1132
|
-
), [chartColors, chartHeight, chartWidth, displayCursorX, displayCursorY, effectiveRenderer, projection, staticScene, viewState.selectedSymbol]);
|
|
1133
|
-
|
|
1134
1136
|
const nativeCrosshair = useMemo<NativeCrosshairOverlay | null>(() => {
|
|
1135
1137
|
if (displayCursor.cellX === null || displayCursor.cellY === null) return null;
|
|
1136
1138
|
return {
|
|
@@ -1385,7 +1387,7 @@ function ComparisonStockChartView({
|
|
|
1385
1387
|
return;
|
|
1386
1388
|
case "a": {
|
|
1387
1389
|
const panStep = getKeyboardPanCellCount(chartWidth);
|
|
1388
|
-
if (viewState.panOffset >= getMaxComparisonPanOffset(series, viewState.
|
|
1390
|
+
if (viewState.panOffset >= getMaxComparisonPanOffset(series, viewState.zoomLevel) && expandBufferRange({
|
|
1389
1391
|
kind: "pan-left",
|
|
1390
1392
|
targetPanOffset: viewState.panOffset + panStep,
|
|
1391
1393
|
})) {
|
|
@@ -1393,7 +1395,7 @@ function ComparisonStockChartView({
|
|
|
1393
1395
|
}
|
|
1394
1396
|
setViewState((current) => ({
|
|
1395
1397
|
...clearActivePreset(current),
|
|
1396
|
-
panOffset: clamp(current.panOffset + panStep, 0, getMaxComparisonPanOffset(series, current.
|
|
1398
|
+
panOffset: clamp(current.panOffset + panStep, 0, getMaxComparisonPanOffset(series, current.zoomLevel)),
|
|
1397
1399
|
}));
|
|
1398
1400
|
return;
|
|
1399
1401
|
}
|
|
@@ -1401,7 +1403,7 @@ function ComparisonStockChartView({
|
|
|
1401
1403
|
const panStep = getKeyboardPanCellCount(chartWidth);
|
|
1402
1404
|
setViewState((current) => ({
|
|
1403
1405
|
...clearActivePreset(current),
|
|
1404
|
-
panOffset: clamp(current.panOffset - panStep, 0, getMaxComparisonPanOffset(series, current.
|
|
1406
|
+
panOffset: clamp(current.panOffset - panStep, 0, getMaxComparisonPanOffset(series, current.zoomLevel)),
|
|
1405
1407
|
}));
|
|
1406
1408
|
return;
|
|
1407
1409
|
}
|
|
@@ -1417,7 +1419,11 @@ function ComparisonStockChartView({
|
|
|
1417
1419
|
setResolution(nextResolution);
|
|
1418
1420
|
return;
|
|
1419
1421
|
}
|
|
1422
|
+
case "t":
|
|
1423
|
+
onEditTickers?.();
|
|
1424
|
+
return;
|
|
1420
1425
|
case "h":
|
|
1426
|
+
mouseCrosshairDisabledRef.current = false;
|
|
1421
1427
|
cursorMotionKindRef.current = "discrete";
|
|
1422
1428
|
setViewState((current) => ({
|
|
1423
1429
|
...current,
|
|
@@ -1425,6 +1431,7 @@ function ComparisonStockChartView({
|
|
|
1425
1431
|
}));
|
|
1426
1432
|
return;
|
|
1427
1433
|
case "l":
|
|
1434
|
+
mouseCrosshairDisabledRef.current = false;
|
|
1428
1435
|
cursorMotionKindRef.current = "discrete";
|
|
1429
1436
|
setViewState((current) => ({
|
|
1430
1437
|
...current,
|
|
@@ -1432,6 +1439,7 @@ function ComparisonStockChartView({
|
|
|
1432
1439
|
}));
|
|
1433
1440
|
return;
|
|
1434
1441
|
case "escape":
|
|
1442
|
+
mouseCrosshairDisabledRef.current = true;
|
|
1435
1443
|
updateDisplayCursorTarget(EMPTY_DISPLAY_CURSOR, "discrete");
|
|
1436
1444
|
setViewState((current) => ({ ...current, cursorX: null, cursorY: null }));
|
|
1437
1445
|
return;
|
|
@@ -1488,6 +1496,7 @@ function ComparisonStockChartView({
|
|
|
1488
1496
|
});
|
|
1489
1497
|
|
|
1490
1498
|
const handlePlotMove = (event: ChartMouseEvent) => {
|
|
1499
|
+
if (mouseCrosshairDisabledRef.current) return;
|
|
1491
1500
|
const localPointer = getLocalPlotPointer(event, plotRef.current, renderer);
|
|
1492
1501
|
if (!localPointer) return;
|
|
1493
1502
|
const selectionCursor = resolveSelectionCursor(localPointer, projection.dates.length, chartWidth);
|
|
@@ -1506,6 +1515,7 @@ function ComparisonStockChartView({
|
|
|
1506
1515
|
};
|
|
1507
1516
|
|
|
1508
1517
|
const handlePlotDown = (event: ChartMouseEvent) => {
|
|
1518
|
+
mouseCrosshairDisabledRef.current = false;
|
|
1509
1519
|
const localPointer = getLocalPlotPointer(event, plotRef.current, renderer);
|
|
1510
1520
|
if (!localPointer) return;
|
|
1511
1521
|
const selectionCursor = resolveSelectionCursor(localPointer, projection.dates.length, chartWidth);
|
|
@@ -1528,6 +1538,7 @@ function ComparisonStockChartView({
|
|
|
1528
1538
|
};
|
|
1529
1539
|
|
|
1530
1540
|
const handlePlotDrag = (event: ChartMouseEvent) => {
|
|
1541
|
+
if (mouseCrosshairDisabledRef.current && !dragRef.current) return;
|
|
1531
1542
|
const localPointer = getLocalPlotPointer(event, plotRef.current, renderer);
|
|
1532
1543
|
if (localPointer) {
|
|
1533
1544
|
const selectionCursor = resolveSelectionCursor(localPointer, projection.dates.length, chartWidth);
|
|
@@ -1592,7 +1603,7 @@ function ComparisonStockChartView({
|
|
|
1592
1603
|
if (scrollPanCells === 0) return;
|
|
1593
1604
|
|
|
1594
1605
|
const targetPanOffset = viewState.panOffset + scrollPanCells;
|
|
1595
|
-
if (scrollPanCells > 0 && viewState.panOffset >= getMaxComparisonPanOffset(series, viewState.
|
|
1606
|
+
if (scrollPanCells > 0 && viewState.panOffset >= getMaxComparisonPanOffset(series, viewState.zoomLevel) && expandBufferRange({
|
|
1596
1607
|
kind: "pan-left",
|
|
1597
1608
|
targetPanOffset,
|
|
1598
1609
|
})) {
|
|
@@ -1603,7 +1614,7 @@ function ComparisonStockChartView({
|
|
|
1603
1614
|
panOffset: clamp(
|
|
1604
1615
|
current.panOffset + scrollPanCells,
|
|
1605
1616
|
0,
|
|
1606
|
-
getMaxComparisonPanOffset(series, current.
|
|
1617
|
+
getMaxComparisonPanOffset(series, current.zoomLevel),
|
|
1607
1618
|
),
|
|
1608
1619
|
}));
|
|
1609
1620
|
};
|
|
@@ -1613,7 +1624,8 @@ function ComparisonStockChartView({
|
|
|
1613
1624
|
}
|
|
1614
1625
|
|
|
1615
1626
|
const hasChartData = series.some((entry) => entry.points.length > 0);
|
|
1616
|
-
const
|
|
1627
|
+
const hasDisplayCursor = displayCursorX !== null && displayCursorY !== null;
|
|
1628
|
+
const cursorScene = hasDisplayCursor ? displayScene : staticScene;
|
|
1617
1629
|
const selectedSeries = hasChartData ? (cursorScene?.selectedSeries ?? staticResult.selectedSeries) : null;
|
|
1618
1630
|
const selectedPoint = hasChartData ? (cursorScene?.selectedPoint ?? staticResult.selectedPoint) : null;
|
|
1619
1631
|
const selectedRawValue = selectedPoint?.rawValue ?? selectedSeries?.latestRawValue ?? null;
|
|
@@ -1641,6 +1653,8 @@ function ComparisonStockChartView({
|
|
|
1641
1653
|
start: visibleWindow.dates[0] ?? null,
|
|
1642
1654
|
end: visibleWindow.dates[visibleWindow.dates.length - 1] ?? null,
|
|
1643
1655
|
});
|
|
1656
|
+
const cursorTimeAxisColumn = hasDisplayCursor ? displayScene?.cursorColumn ?? null : null;
|
|
1657
|
+
const cursorTimeAxisDate = hasDisplayCursor ? displayScene?.activeDate ?? null : null;
|
|
1644
1658
|
|
|
1645
1659
|
const canvasBitmapSize = useMemo(() => {
|
|
1646
1660
|
if (!canvasCharts) return null;
|
|
@@ -1929,7 +1943,14 @@ function ComparisonStockChartView({
|
|
|
1929
1943
|
</Box>
|
|
1930
1944
|
|
|
1931
1945
|
<Box height={timeAxisRows}>
|
|
1932
|
-
<
|
|
1946
|
+
<TimeAxisLabel
|
|
1947
|
+
timeLabels={timeAxisLabel}
|
|
1948
|
+
width={chartWidth}
|
|
1949
|
+
cursorColumn={cursorTimeAxisColumn}
|
|
1950
|
+
cursorDate={cursorTimeAxisDate}
|
|
1951
|
+
dates={projection.dates}
|
|
1952
|
+
cursorColor={chartColors.crosshairColor}
|
|
1953
|
+
/>
|
|
1933
1954
|
</Box>
|
|
1934
1955
|
|
|
1935
1956
|
{legendRows > 0 && (
|
|
@@ -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
|
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
getKeyboardPanCellCount,
|
|
28
28
|
resolveDragPanOffset,
|
|
29
29
|
} from "./chart-scroll";
|
|
30
|
-
import {
|
|
30
|
+
import { projectChartData, resolveBarSize, resolveStableOhlcProjectionOptions, type ProjectedChartPoint } from "./chart-data";
|
|
31
31
|
import {
|
|
32
32
|
buildPresetDateWindow,
|
|
33
33
|
buildVisibleDateWindow,
|
|
@@ -127,6 +127,7 @@ import {
|
|
|
127
127
|
} from "./native/surface-visibility";
|
|
128
128
|
import type { PricePoint, TickerFinancials } from "../../types/financials";
|
|
129
129
|
import type { TickerRecord } from "../../types/ticker";
|
|
130
|
+
import { TimeAxisLabel } from "./time-axis-label";
|
|
130
131
|
|
|
131
132
|
const MODE_CHIPS: Record<ChartRenderMode, string> = {
|
|
132
133
|
area: "A",
|
|
@@ -173,7 +174,6 @@ interface StockChartProps {
|
|
|
173
174
|
}
|
|
174
175
|
|
|
175
176
|
interface ResolvedStockChartProps extends StockChartProps {
|
|
176
|
-
symbol: string | null;
|
|
177
177
|
ticker: TickerRecord | null;
|
|
178
178
|
financials: TickerFinancials | null;
|
|
179
179
|
}
|
|
@@ -726,14 +726,6 @@ function isSeriesAcceptedForRequest(
|
|
|
726
726
|
return windowPoints.length >= minimumPointCount;
|
|
727
727
|
}
|
|
728
728
|
|
|
729
|
-
function canRenderAutoViewForWindow(
|
|
730
|
-
render: AutoRenderedView | null,
|
|
731
|
-
requestedWindow: DateWindowRange | null | undefined,
|
|
732
|
-
): render is AutoRenderedView {
|
|
733
|
-
if (!render || !requestedWindow?.start || !requestedWindow.end) return false;
|
|
734
|
-
return getWindowPoints(render.data, requestedWindow).length > 0;
|
|
735
|
-
}
|
|
736
|
-
|
|
737
729
|
export function resolveAutoDisplayState(options: {
|
|
738
730
|
shouldUseRenderedAutoView: boolean;
|
|
739
731
|
renderedAutoView: AutoRenderedView | null;
|
|
@@ -772,13 +764,6 @@ export function resolveAutoDisplayState(options: {
|
|
|
772
764
|
};
|
|
773
765
|
}
|
|
774
766
|
|
|
775
|
-
export function resolveVisibleChartDateWindow(
|
|
776
|
-
points: readonly Pick<PricePoint, "date">[],
|
|
777
|
-
preferredWindow: DateWindowRange | null | undefined,
|
|
778
|
-
): DateWindowRange | null {
|
|
779
|
-
return getDateWindowBounds(getPointDates(points)) ?? preferredWindow ?? null;
|
|
780
|
-
}
|
|
781
|
-
|
|
782
767
|
function buildDateWindowFromIndices(dates: readonly Date[], startIdx: number, endIdx: number): DateWindowRange | null {
|
|
783
768
|
if (dates.length === 0 || endIdx <= startIdx) return null;
|
|
784
769
|
return {
|
|
@@ -1430,8 +1415,8 @@ function buildIndicatorProjectionKey(options: {
|
|
|
1430
1415
|
}
|
|
1431
1416
|
|
|
1432
1417
|
export function StockChart(props: StockChartProps) {
|
|
1433
|
-
const {
|
|
1434
|
-
return <ResolvedStockChart {...props}
|
|
1418
|
+
const { ticker, financials } = usePaneTicker();
|
|
1419
|
+
return <ResolvedStockChart {...props} ticker={ticker} financials={financials} />;
|
|
1435
1420
|
}
|
|
1436
1421
|
|
|
1437
1422
|
export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
@@ -1448,7 +1433,6 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
1448
1433
|
showVolume: showVolumeOverride,
|
|
1449
1434
|
footerControls,
|
|
1450
1435
|
footerHints,
|
|
1451
|
-
symbol,
|
|
1452
1436
|
ticker,
|
|
1453
1437
|
financials,
|
|
1454
1438
|
}: ResolvedStockChartProps) {
|
|
@@ -1514,6 +1498,7 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
1514
1498
|
const lastCanvasBaseBitmapRef = useRef<{ key: string; bitmap: NativeChartBitmap } | null>(null);
|
|
1515
1499
|
const displayCursorRef = useRef<DisplayCursorState>(EMPTY_DISPLAY_CURSOR);
|
|
1516
1500
|
const targetCursorRef = useRef<DisplayCursorState>(EMPTY_DISPLAY_CURSOR);
|
|
1501
|
+
const mouseCrosshairDisabledRef = useRef(false);
|
|
1517
1502
|
const cursorMotionKindRef = useRef<ChartCursorMotionKind>("discrete");
|
|
1518
1503
|
const animationFrameRef = useRef<number | null>(null);
|
|
1519
1504
|
const pendingCanonicalResetRef = useRef(1);
|
|
@@ -2906,6 +2891,11 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
2906
2891
|
if (!interactive) return;
|
|
2907
2892
|
|
|
2908
2893
|
switch (key) {
|
|
2894
|
+
case "escape":
|
|
2895
|
+
mouseCrosshairDisabledRef.current = true;
|
|
2896
|
+
updateDisplayCursorTarget(EMPTY_DISPLAY_CURSOR, "discrete");
|
|
2897
|
+
commitSelectionCursor({ cursorX: null, cursorY: null });
|
|
2898
|
+
return;
|
|
2909
2899
|
case "left":
|
|
2910
2900
|
if (event.shift) {
|
|
2911
2901
|
if (effectiveResolution === "auto") {
|
|
@@ -2923,6 +2913,7 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
2923
2913
|
return { ...cleared, panOffset: current.panOffset + panStep };
|
|
2924
2914
|
});
|
|
2925
2915
|
} else {
|
|
2916
|
+
mouseCrosshairDisabledRef.current = false;
|
|
2926
2917
|
cursorMotionKindRef.current = "discrete";
|
|
2927
2918
|
const pointCount = projection.points.length;
|
|
2928
2919
|
const currentIndex = pointCount <= 0
|
|
@@ -2974,6 +2965,7 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
2974
2965
|
return cleared.panOffset === nextPanOffset ? cleared : { ...cleared, panOffset: nextPanOffset };
|
|
2975
2966
|
});
|
|
2976
2967
|
} else {
|
|
2968
|
+
mouseCrosshairDisabledRef.current = false;
|
|
2977
2969
|
cursorMotionKindRef.current = "discrete";
|
|
2978
2970
|
const pointCount = projection.points.length;
|
|
2979
2971
|
const currentIndex = pointCount <= 0
|
|
@@ -3058,6 +3050,19 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
3058
3050
|
colors: chartColors,
|
|
3059
3051
|
timeAxisDates,
|
|
3060
3052
|
}), [axisMode, chartColors, chartHeight, chartWidth, compact, projection.effectiveMode, projection.points, selectionCursorX, selectionCursorY, showVolume, timeAxisDates, volumeHeight]);
|
|
3053
|
+
const displayScene = useMemo(() => buildChartScene(projection.points, {
|
|
3054
|
+
width: chartWidth,
|
|
3055
|
+
height: chartHeight,
|
|
3056
|
+
showVolume: showVolume && !compact,
|
|
3057
|
+
volumeHeight,
|
|
3058
|
+
cursorX: displayCursorX,
|
|
3059
|
+
cursorY: displayCursorY,
|
|
3060
|
+
mode: projection.effectiveMode,
|
|
3061
|
+
axisMode,
|
|
3062
|
+
colors: chartColors,
|
|
3063
|
+
timeAxisDates,
|
|
3064
|
+
indicators,
|
|
3065
|
+
}), [axisMode, chartColors, chartHeight, chartWidth, compact, displayCursorX, displayCursorY, indicators, projection.effectiveMode, projection.points, showVolume, timeAxisDates, volumeHeight]);
|
|
3061
3066
|
const snappedSelectionCursorX = selectionScene
|
|
3062
3067
|
? getPointTerminalColumn(selectionScene.activeIdx, projection.points.length, chartWidth, projection.effectiveMode)
|
|
3063
3068
|
: null;
|
|
@@ -3419,6 +3424,8 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
3419
3424
|
: null;
|
|
3420
3425
|
const isUpdating = !compact && (renderBodyState.updating || boundsBodyState.updating);
|
|
3421
3426
|
const timeAxisLabel = selectionScene?.timeLabels ?? staticResult.timeLabels;
|
|
3427
|
+
const cursorTimeAxisColumn = hasDisplayCursor ? displayScene?.cursorColumn ?? null : null;
|
|
3428
|
+
const cursorTimeAxisDate = hasDisplayCursor ? displayScene?.dateAtCursor ?? null : null;
|
|
3422
3429
|
const timeAxisBox = (
|
|
3423
3430
|
<Box
|
|
3424
3431
|
height={1}
|
|
@@ -3426,7 +3433,14 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
3426
3433
|
overflow="hidden"
|
|
3427
3434
|
data-gloom-role="chart-time-axis"
|
|
3428
3435
|
>
|
|
3429
|
-
<
|
|
3436
|
+
<TimeAxisLabel
|
|
3437
|
+
timeLabels={timeAxisLabel}
|
|
3438
|
+
width={chartWidth}
|
|
3439
|
+
cursorColumn={cursorTimeAxisColumn}
|
|
3440
|
+
cursorDate={cursorTimeAxisDate}
|
|
3441
|
+
dates={timeAxisDates}
|
|
3442
|
+
cursorColor={chartColors.crosshairColor}
|
|
3443
|
+
/>
|
|
3430
3444
|
</Box>
|
|
3431
3445
|
);
|
|
3432
3446
|
const chartFooterHints = useMemo<PaneHint[]>(() => {
|
|
@@ -3573,6 +3587,7 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
3573
3587
|
|
|
3574
3588
|
const handlePlotMove = (event: ChartMouseEvent) => {
|
|
3575
3589
|
if (!interactive || compact) return;
|
|
3590
|
+
if (mouseCrosshairDisabledRef.current) return;
|
|
3576
3591
|
const localPointer = getLocalPlotPointer(event, plotRef.current, renderer);
|
|
3577
3592
|
if (!localPointer) return;
|
|
3578
3593
|
const selectionCursor = resolveSelectionCursor(localPointer, projection.points.length, chartWidth, projection.effectiveMode);
|
|
@@ -3594,6 +3609,7 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
3594
3609
|
if (compact) return;
|
|
3595
3610
|
if (!interactive) onActivate?.();
|
|
3596
3611
|
focusPaneForMouseInteraction(event);
|
|
3612
|
+
mouseCrosshairDisabledRef.current = false;
|
|
3597
3613
|
const localPointer = getLocalPlotPointer(event, plotRef.current, renderer);
|
|
3598
3614
|
if (!localPointer) return;
|
|
3599
3615
|
const selectionCursor = resolveSelectionCursor(localPointer, projection.points.length, chartWidth, projection.effectiveMode);
|
|
@@ -3618,6 +3634,7 @@ export const ResolvedStockChart = memo(function ResolvedStockChart({
|
|
|
3618
3634
|
|
|
3619
3635
|
const handlePlotDrag = (event: ChartMouseEvent) => {
|
|
3620
3636
|
if (compact || (!interactive && !dragRef.current)) return;
|
|
3637
|
+
if (mouseCrosshairDisabledRef.current && !dragRef.current) return;
|
|
3621
3638
|
const localPointer = getLocalPlotPointer(event, plotRef.current, renderer);
|
|
3622
3639
|
if (localPointer) {
|
|
3623
3640
|
const selectionCursor = resolveSelectionCursor(localPointer, projection.points.length, chartWidth, projection.effectiveMode);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { Span, Text } from "../../ui";
|
|
3
|
+
import { colors } from "../../theme/colors";
|
|
4
|
+
import { buildCursorTimeAxisSegments } from "./chart-renderer";
|
|
5
|
+
|
|
6
|
+
interface TimeAxisLabelProps {
|
|
7
|
+
timeLabels: string;
|
|
8
|
+
width: number;
|
|
9
|
+
cursorColumn: number | null;
|
|
10
|
+
cursorDate: Date | string | number | null;
|
|
11
|
+
dates: Array<Date | string | number>;
|
|
12
|
+
cursorColor: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function TimeAxisLabel({
|
|
16
|
+
timeLabels,
|
|
17
|
+
width,
|
|
18
|
+
cursorColumn,
|
|
19
|
+
cursorDate,
|
|
20
|
+
dates,
|
|
21
|
+
cursorColor,
|
|
22
|
+
}: TimeAxisLabelProps) {
|
|
23
|
+
const segments = useMemo(() => buildCursorTimeAxisSegments({
|
|
24
|
+
timeLabels,
|
|
25
|
+
width,
|
|
26
|
+
cursorColumn,
|
|
27
|
+
cursorDate,
|
|
28
|
+
dates,
|
|
29
|
+
}), [cursorColumn, cursorDate, dates, timeLabels, width]);
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<Text style={{ whiteSpace: "pre" }}>
|
|
33
|
+
{segments.map((segment, index) => (
|
|
34
|
+
<Span
|
|
35
|
+
key={index}
|
|
36
|
+
fg={segment.highlighted ? cursorColor : colors.textDim}
|
|
37
|
+
>
|
|
38
|
+
{segment.text}
|
|
39
|
+
</Span>
|
|
40
|
+
))}
|
|
41
|
+
</Text>
|
|
42
|
+
);
|
|
43
|
+
}
|