gloomberb 0.7.2 → 0.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -132
- package/package.json +1 -1
- package/src/app.tsx +73 -67
- package/src/components/chart/chart-data.ts +7 -2
- package/src/components/chart/chart-pane-settings.ts +7 -4
- package/src/components/chart/chart-renderer.ts +158 -47
- package/src/components/chart/chart-types.ts +20 -2
- package/src/components/chart/chart.test.ts +179 -13
- package/src/components/chart/comparison-chart-renderer.test.ts +14 -30
- package/src/components/chart/comparison-chart-renderer.ts +50 -11
- package/src/components/chart/comparison-stock-chart.tsx +129 -129
- package/src/components/chart/market-session.ts +187 -0
- package/src/components/chart/native/chart-rasterizer.ts +108 -19
- package/src/components/chart/price-axis-labels.tsx +25 -9
- package/src/components/chart/stock-chart-renderer-switch.test.tsx +97 -1
- package/src/components/chart/stock-chart.tsx +95 -49
- package/src/components/chart/time-axis-label.tsx +56 -2
- package/src/components/command-bar/command-bar.test.tsx +446 -120
- package/src/components/command-bar/command-bar.tsx +535 -384
- package/src/components/command-bar/command-registry.ts +16 -6
- package/src/components/command-bar/list-model.ts +8 -4
- package/src/components/command-bar/root-shortcuts.ts +4 -5
- package/src/components/command-bar/view-model.test.ts +21 -0
- package/src/components/command-bar/view-model.ts +17 -4
- package/src/components/command-bar/workflow-ops.ts +11 -32
- package/src/components/data-table-view.test.tsx +24 -1
- package/src/components/data-table-view.tsx +3 -2
- package/src/components/feed-data-table-stack-view.tsx +3 -2
- package/src/components/layout/detached-pane-shell.tsx +2 -2
- package/src/components/layout/floating-pane.tsx +9 -2
- package/src/components/layout/pane-footer.tsx +1 -1
- package/src/components/layout/pane-header.tsx +4 -2
- package/src/components/layout/pane-sizing.ts +12 -0
- package/src/components/layout/pane.tsx +24 -2
- package/src/components/layout/shell.tsx +24 -10
- package/src/components/layout/status-bar.test.tsx +4 -1
- package/src/components/layout/status-bar.tsx +4 -24
- package/src/components/markdown-text.tsx +53 -8
- package/src/components/onboarding/onboarding-wizard.tsx +7 -6
- package/src/components/pane-settings-dialog.tsx +9 -6
- package/src/components/pane-template-wizard.tsx +3 -2
- package/src/components/price-selector-dialog.tsx +3 -2
- package/src/components/price-sparkline-view.tsx +463 -0
- package/src/components/price-sparkline.ts +46 -0
- package/src/components/table-view-shared.tsx +21 -3
- package/src/components/ticker-list-table-view.tsx +3 -2
- package/src/components/ticker-list-table.tsx +15 -66
- package/src/components/toggle-list.tsx +3 -3
- package/src/components/ui/choice-dialog.tsx +3 -2
- package/src/components/ui/multi-select-dialog.tsx +3 -2
- package/src/components/ui/native-select.tsx +3 -3
- package/src/core/state/app-state.ts +210 -80
- package/src/core/state/session-persistence.ts +25 -0
- package/src/data/config-store-node.ts +113 -25
- package/src/data/config-store.test.ts +65 -3
- package/src/data/plugin-state-store.ts +39 -13
- package/src/data/sqlite-cache.test.ts +13 -0
- package/src/market-data/coordinator-subscriptions.test.ts +102 -18
- package/src/market-data/coordinator.test.ts +122 -2
- package/src/market-data/coordinator.ts +200 -56
- package/src/market-data/hooks.test.tsx +31 -0
- package/src/market-data/hooks.tsx +1 -0
- package/src/news/aggregator.test.ts +41 -0
- package/src/news/aggregator.ts +61 -9
- package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +25 -6
- package/src/plugins/builtin/ai/screener-pane.tsx +3 -0
- package/src/plugins/builtin/ai/settings.ts +3 -0
- package/src/plugins/builtin/broker-manager/index.tsx +4 -3
- package/src/plugins/builtin/chat-controller.test.ts +398 -17
- package/src/plugins/builtin/chat-controller.ts +263 -48
- package/src/plugins/builtin/chat.test.tsx +312 -6
- package/src/plugins/builtin/chat.tsx +245 -66
- package/src/plugins/builtin/comparison-chart.test.tsx +117 -3
- package/src/plugins/builtin/correlation/colors.test.ts +39 -0
- package/src/plugins/builtin/correlation/colors.ts +87 -0
- package/src/plugins/builtin/correlation/index.test.tsx +151 -0
- package/src/plugins/builtin/correlation/index.tsx +92 -21
- package/src/plugins/builtin/debug.tsx +4 -3
- package/src/plugins/builtin/econ/index.tsx +3 -2
- package/src/plugins/builtin/help.test.tsx +2 -2
- package/src/plugins/builtin/holders/index.tsx +25 -13
- package/src/plugins/builtin/layout-manager.tsx +1 -7
- package/src/plugins/builtin/market-movers/index.tsx +72 -40
- package/src/plugins/builtin/news-wire/breaking-pane.test.tsx +156 -0
- package/src/plugins/builtin/news-wire/breaking-pane.tsx +0 -90
- package/src/plugins/builtin/news-wire/index.tsx +1 -3
- package/src/plugins/builtin/news-wire/news-detail-view.tsx +3 -2
- package/src/plugins/builtin/news-wire/news-preset-pane.tsx +4 -1
- package/src/plugins/builtin/news-wire/persisted-articles.ts +7 -1
- package/src/plugins/builtin/options.tsx +9 -17
- package/src/plugins/builtin/portfolio-list/cli/commands.ts +6 -10
- package/src/plugins/builtin/portfolio-list/metrics.ts +13 -2
- package/src/plugins/builtin/portfolio-list/mutations.ts +17 -0
- package/src/plugins/builtin/portfolio-list/pane.tsx +139 -23
- package/src/plugins/builtin/portfolio-list/quick-add.tsx +422 -0
- package/src/plugins/builtin/portfolio-list/settings.ts +20 -12
- package/src/plugins/builtin/portfolio-list/table.tsx +3 -3
- package/src/plugins/builtin/portfolio-list.test.tsx +311 -9
- package/src/plugins/builtin/quote-monitor.test.tsx +158 -25
- package/src/plugins/builtin/sectors/index.tsx +275 -43
- package/src/plugins/builtin/sectors/sector-data.ts +41 -1
- package/src/plugins/builtin/ticker-detail/chart-tab.tsx +3 -2
- package/src/plugins/builtin/ticker-detail/index.tsx +30 -9
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +25 -15
- package/src/plugins/builtin/ticker-detail/pane.tsx +18 -7
- package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +445 -63
- package/src/plugins/builtin/ticker-detail/settings.ts +84 -3
- package/src/plugins/builtin/ticker-detail.test.tsx +2 -2
- package/src/plugins/builtin/world-indices/index.tsx +58 -19
- package/src/plugins/ibkr/trading-pane.tsx +3 -2
- package/src/plugins/plugin-runtime.tsx +2 -2
- package/src/plugins/prediction-markets/settings.ts +4 -0
- package/src/plugins/registry.test.ts +55 -0
- package/src/plugins/registry.ts +11 -4
- package/src/plugins/ticker-navigation.test.ts +35 -5
- package/src/plugins/ticker-navigation.ts +16 -22
- package/src/renderers/electrobun/bun/application-menu-click.test.ts +9 -0
- package/src/renderers/electrobun/bun/application-menu-click.ts +7 -4
- package/src/renderers/electrobun/bun/application-menu.ts +6 -1
- package/src/renderers/electrobun/bun/desktop-workspace.ts +47 -29
- package/src/renderers/electrobun/bun/index.ts +52 -13
- package/src/renderers/electrobun/view/app-services.ts +51 -9
- package/src/renderers/electrobun/view/backend-rpc.ts +5 -8
- package/src/renderers/electrobun/view/desktop-controls.tsx +4 -4
- package/src/renderers/electrobun/view/main.tsx +0 -16
- package/src/renderers/electrobun/view/styles.css +23 -21
- package/src/renderers/electrobun/view/ui-host.tsx +6 -4
- package/src/renderers/opentui/host.tsx +2 -1
- package/src/renderers/opentui/start.tsx +1 -1
- package/src/renderers/opentui/test-utils.tsx +6 -5
- package/src/sources/gloomberb-cloud.test.ts +55 -2
- package/src/sources/gloomberb-cloud.ts +180 -29
- package/src/sources/provider-router.test.ts +567 -10
- package/src/sources/provider-router.ts +323 -93
- package/src/sources/yahoo-finance.test.ts +37 -0
- package/src/sources/yahoo-finance.ts +48 -2
- package/src/state/app-bootstrap.test.ts +50 -23
- package/src/state/app-bootstrap.ts +29 -13
- package/src/state/app-context.test.ts +113 -0
- package/src/state/app-context.tsx +8 -4
- package/src/state/session-persistence.test.ts +5 -3
- package/src/state/use-inline-tickers.ts +3 -0
- package/src/state/use-quote-streaming.ts +11 -0
- package/src/theme/colors.ts +13 -14
- package/src/types/config.ts +9 -4
- package/src/types/data-provider.ts +5 -1
- package/src/types/financials.ts +1 -0
- package/src/types/plugin.ts +8 -1
- package/src/ui/context-menu.tsx +4 -4
- package/src/utils/api-client.test.ts +248 -0
- package/src/utils/api-client.ts +178 -26
- package/src/utils/exchanges.test.ts +16 -0
- package/src/utils/exchanges.ts +33 -0
- package/src/utils/fuzzy-search.ts +12 -1
- package/src/utils/keyboard.ts +31 -0
- package/src/utils/options.ts +1 -1
- package/src/utils/price-history.test.ts +28 -1
- package/src/utils/price-history.ts +16 -0
- package/src/utils/quote-freshness.test.ts +150 -0
- package/src/utils/quote-freshness.ts +42 -1
- package/src/utils/quote-resolution.ts +2 -0
- package/src/utils/quote-time.test.ts +34 -0
- package/src/utils/quote-time.ts +18 -5
- package/src/utils/ticker-search.test.ts +2 -2
- package/src/utils/ticker-search.ts +48 -1
- package/src/version.ts +1 -1
- package/src/plugins/builtin/news-wire/digest-store.ts +0 -44
|
@@ -7,12 +7,12 @@ import { useChartQueries } from "../../market-data/hooks";
|
|
|
7
7
|
import { buildChartKey } from "../../market-data/selectors";
|
|
8
8
|
import { useAppSelector, usePaneSettingValue } from "../../state/app-context";
|
|
9
9
|
import { usePaneFooter, type PaneHint } from "../layout/pane-footer";
|
|
10
|
-
import { blendHex, colors, getComparisonSeriesColor
|
|
10
|
+
import { blendHex, colors, getComparisonSeriesColor } from "../../theme/colors";
|
|
11
11
|
import { useThemeColors } from "../../theme/theme-context";
|
|
12
12
|
import type { BrokerContractRef } from "../../types/instrument";
|
|
13
13
|
import type { PricePoint } from "../../types/financials";
|
|
14
14
|
import { formatPercentRaw } from "../../utils/format";
|
|
15
|
-
import { formatMarketPriceWithCurrency
|
|
15
|
+
import { formatMarketPriceWithCurrency } from "../../utils/market-format";
|
|
16
16
|
import { getSharedMarketData } from "../../plugins/registry";
|
|
17
17
|
import {
|
|
18
18
|
applyComparisonZoomAroundAnchor,
|
|
@@ -30,7 +30,6 @@ import {
|
|
|
30
30
|
import {
|
|
31
31
|
buildVisibleDateWindow,
|
|
32
32
|
clearActivePreset,
|
|
33
|
-
formatVisibleSpanLabel,
|
|
34
33
|
needsCanonicalPresetViewportReset,
|
|
35
34
|
resolveChartBodyState,
|
|
36
35
|
resolveCanonicalPresetViewport,
|
|
@@ -96,9 +95,10 @@ import {
|
|
|
96
95
|
resolveNativeSurfaceVisibleRect,
|
|
97
96
|
type NativeSurfaceRenderableNode,
|
|
98
97
|
} from "./native/surface-visibility";
|
|
99
|
-
import {
|
|
98
|
+
import { resolveChartAxisWidth, type StyledContent } from "./chart-renderer";
|
|
100
99
|
import { TimeAxisLabel } from "./time-axis-label";
|
|
101
100
|
import { PriceAxisLabels } from "./price-axis-labels";
|
|
101
|
+
import { getChartMarketSessionKey, resolveChartMarketSession } from "./market-session";
|
|
102
102
|
|
|
103
103
|
const MODE_CHIPS: Record<ComparisonChartRenderMode, string> = {
|
|
104
104
|
area: "A",
|
|
@@ -393,6 +393,7 @@ function buildComparisonNativeBitmapKey(
|
|
|
393
393
|
pixelWidth: number,
|
|
394
394
|
pixelHeight: number,
|
|
395
395
|
paletteKey: string,
|
|
396
|
+
marketSessionKey: string,
|
|
396
397
|
): string {
|
|
397
398
|
const fingerprint = projection.series
|
|
398
399
|
.map((series) => [
|
|
@@ -413,6 +414,7 @@ function buildComparisonNativeBitmapKey(
|
|
|
413
414
|
pixelWidth,
|
|
414
415
|
pixelHeight,
|
|
415
416
|
paletteKey,
|
|
417
|
+
marketSessionKey,
|
|
416
418
|
fingerprint,
|
|
417
419
|
].join("::");
|
|
418
420
|
}
|
|
@@ -445,6 +447,23 @@ function clipText(text: string, width: number): string {
|
|
|
445
447
|
return `${text.slice(0, width - 3)}...`;
|
|
446
448
|
}
|
|
447
449
|
|
|
450
|
+
function formatLegendSummary(
|
|
451
|
+
symbol: string,
|
|
452
|
+
rawValue: number | null,
|
|
453
|
+
baseValue: number | null,
|
|
454
|
+
currency: string,
|
|
455
|
+
priceRange: number | undefined,
|
|
456
|
+
): string {
|
|
457
|
+
if (rawValue === null) return `${symbol} waiting`;
|
|
458
|
+
const price = formatMarketPriceWithCurrency(rawValue, currency, {
|
|
459
|
+
minimumFractionDigits: 2,
|
|
460
|
+
precisionOffset: 1,
|
|
461
|
+
priceRange,
|
|
462
|
+
});
|
|
463
|
+
if (baseValue === null || baseValue === 0) return `${symbol} ${price}`;
|
|
464
|
+
return `${symbol} ${price} ${formatPercentRaw(((rawValue - baseValue) / baseValue) * 100)}`;
|
|
465
|
+
}
|
|
466
|
+
|
|
448
467
|
function getInitialComparisonMode(mode: string | undefined): ComparisonChartRenderMode {
|
|
449
468
|
return mode === "line" ? "line" : "area";
|
|
450
469
|
}
|
|
@@ -555,6 +574,7 @@ function ComparisonStockChartView({
|
|
|
555
574
|
const mouseCrosshairDisabledRef = useRef(false);
|
|
556
575
|
const cursorMotionKindRef = useRef<ChartCursorMotionKind>("discrete");
|
|
557
576
|
const animationFrameRef = useRef<number | null>(null);
|
|
577
|
+
const lastAppliedStoredSelectionKeyRef = useRef<string | null>(null);
|
|
558
578
|
const pendingCanonicalResetRef = useRef(1);
|
|
559
579
|
const appliedCanonicalResetRef = useRef(0);
|
|
560
580
|
const pendingExpansionRef = useRef<PendingExpansionAction>(null);
|
|
@@ -564,7 +584,7 @@ function ComparisonStockChartView({
|
|
|
564
584
|
const axisRightPadding = 1;
|
|
565
585
|
const minimumAxisWidth = axisMode === "percent" ? 5 : 4;
|
|
566
586
|
const axisGap = axisSectionWidthBudget > 0 ? 1 : 0;
|
|
567
|
-
const headerRows =
|
|
587
|
+
const headerRows = 0;
|
|
568
588
|
const controlRows = 2;
|
|
569
589
|
const timeAxisRows = 1;
|
|
570
590
|
const helpRows = 0;
|
|
@@ -581,7 +601,16 @@ function ComparisonStockChartView({
|
|
|
581
601
|
bgColor: colors.bg,
|
|
582
602
|
gridColor: blendHex(colors.bg, colors.border, 0.55),
|
|
583
603
|
crosshairColor: colors.borderFocused,
|
|
604
|
+
preMarketBgColor: blendHex(colors.bg, "#1d4ed8", 0.18),
|
|
605
|
+
postMarketBgColor: blendHex(colors.bg, "#b45309", 0.18),
|
|
584
606
|
}), [colors.bg, colors.border, colors.borderFocused]);
|
|
607
|
+
const marketSession = useMemo(() => resolveChartMarketSession(symbolSources.map((source) => ({
|
|
608
|
+
exchange: source.exchange,
|
|
609
|
+
primaryExchange: source.instrument?.primaryExchange,
|
|
610
|
+
currency: source.currency,
|
|
611
|
+
assetCategory: source.instrument?.secType,
|
|
612
|
+
}))), [symbolSources]);
|
|
613
|
+
const marketSessionKey = useMemo(() => getChartMarketSessionKey(marketSession), [marketSession]);
|
|
585
614
|
|
|
586
615
|
const setSelectedSymbol = (symbol: string) => {
|
|
587
616
|
setViewState((current) => (
|
|
@@ -591,13 +620,35 @@ function ComparisonStockChartView({
|
|
|
591
620
|
));
|
|
592
621
|
};
|
|
593
622
|
|
|
594
|
-
const
|
|
595
|
-
|
|
596
|
-
source
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
623
|
+
const marketDataProvider = getSharedMarketData();
|
|
624
|
+
const capabilityDescriptor = useMemo(() => {
|
|
625
|
+
const sources = symbolSources.map((source) => ({
|
|
626
|
+
symbol: source.symbol,
|
|
627
|
+
exchange: source.exchange,
|
|
628
|
+
brokerId: source.brokerId,
|
|
629
|
+
brokerInstanceId: source.brokerInstanceId,
|
|
630
|
+
instrument: source.instrument,
|
|
631
|
+
}));
|
|
632
|
+
return {
|
|
633
|
+
key: sources.map((source) => [
|
|
634
|
+
source.symbol,
|
|
635
|
+
source.exchange,
|
|
636
|
+
source.brokerId ?? "",
|
|
637
|
+
source.brokerInstanceId ?? "",
|
|
638
|
+
source.instrument?.conId ?? "",
|
|
639
|
+
source.instrument?.localSymbol ?? "",
|
|
640
|
+
source.instrument?.symbol ?? "",
|
|
641
|
+
].join(":")).join("|"),
|
|
642
|
+
sources,
|
|
643
|
+
};
|
|
644
|
+
}, [symbolSources]);
|
|
645
|
+
const effectiveResolutionSupport = useMemo<ChartResolutionSupport[]>(() => (
|
|
646
|
+
resolutionSupport ?? DEFAULT_VISIBLE_MANUAL_CHART_RESOLUTIONS.map((resolution) => ({ resolution, maxRange: "ALL" as const }))
|
|
647
|
+
), [resolutionSupport]);
|
|
648
|
+
const selectionSupportMap = useMemo(
|
|
649
|
+
() => buildChartResolutionSupportMap(effectiveResolutionSupport),
|
|
650
|
+
[effectiveResolutionSupport],
|
|
651
|
+
);
|
|
601
652
|
|
|
602
653
|
useEffect(() => {
|
|
603
654
|
if (symbols.includes(viewState.selectedSymbol ?? "")) return;
|
|
@@ -608,21 +659,25 @@ function ComparisonStockChartView({
|
|
|
608
659
|
}, [symbols, viewState.selectedSymbol]);
|
|
609
660
|
|
|
610
661
|
useEffect(() => {
|
|
662
|
+
const storedSelectionKey = `${storedRangePreset}:${storedResolution}`;
|
|
663
|
+
if (lastAppliedStoredSelectionKeyRef.current === storedSelectionKey) return;
|
|
664
|
+
|
|
665
|
+
lastAppliedStoredSelectionKeyRef.current = storedSelectionKey;
|
|
611
666
|
pendingCanonicalResetRef.current += 1;
|
|
612
667
|
setViewState((current) => (
|
|
613
668
|
storedResolution === "auto"
|
|
614
|
-
? resolveStoredChartSelection(current, storedRangePreset, storedResolution,
|
|
669
|
+
? resolveStoredChartSelection(current, storedRangePreset, storedResolution, selectionSupportMap)
|
|
615
670
|
: resolvePresetSelection(
|
|
616
671
|
{
|
|
617
672
|
...current,
|
|
618
673
|
resolution: storedResolution,
|
|
619
674
|
},
|
|
620
675
|
storedRangePreset,
|
|
621
|
-
getSupportMaxRange(
|
|
676
|
+
getSupportMaxRange(selectionSupportMap, storedResolution),
|
|
622
677
|
)
|
|
623
678
|
));
|
|
624
679
|
updateDisplayCursorTarget(EMPTY_DISPLAY_CURSOR, "discrete");
|
|
625
|
-
}, [storedRangePreset, storedResolution
|
|
680
|
+
}, [selectionSupportMap, storedRangePreset, storedResolution]);
|
|
626
681
|
|
|
627
682
|
const commitDisplayCursor = (next: DisplayCursorState) => {
|
|
628
683
|
displayCursorRef.current = next;
|
|
@@ -705,15 +760,15 @@ function ComparisonStockChartView({
|
|
|
705
760
|
), [nativeSurfaceManager]);
|
|
706
761
|
|
|
707
762
|
useEffect(() => {
|
|
708
|
-
const provider =
|
|
709
|
-
if ((!provider?.getChartResolutionSupport && !provider?.getChartResolutionCapabilities) ||
|
|
763
|
+
const provider = marketDataProvider;
|
|
764
|
+
if ((!provider?.getChartResolutionSupport && !provider?.getChartResolutionCapabilities) || capabilityDescriptor.sources.length === 0) {
|
|
710
765
|
setResolutionSupport(null);
|
|
711
766
|
return;
|
|
712
767
|
}
|
|
713
768
|
|
|
714
769
|
let cancelled = false;
|
|
715
770
|
setResolutionSupport(null);
|
|
716
|
-
Promise.all(
|
|
771
|
+
Promise.all(capabilityDescriptor.sources.map(async (source) => {
|
|
717
772
|
try {
|
|
718
773
|
const support = provider.getChartResolutionSupport
|
|
719
774
|
? await provider.getChartResolutionSupport(
|
|
@@ -757,11 +812,7 @@ function ComparisonStockChartView({
|
|
|
757
812
|
return () => {
|
|
758
813
|
cancelled = true;
|
|
759
814
|
};
|
|
760
|
-
}, [
|
|
761
|
-
|
|
762
|
-
const effectiveResolutionSupport = useMemo<ChartResolutionSupport[]>(() => (
|
|
763
|
-
resolutionSupport ?? DEFAULT_VISIBLE_MANUAL_CHART_RESOLUTIONS.map((resolution) => ({ resolution, maxRange: "ALL" as const }))
|
|
764
|
-
), [resolutionSupport]);
|
|
815
|
+
}, [capabilityDescriptor.key, marketDataProvider]);
|
|
765
816
|
const availableManualResolutions = resolutionSupport?.map((entry) => entry.resolution) ?? DEFAULT_VISIBLE_MANUAL_CHART_RESOLUTIONS;
|
|
766
817
|
const effectiveResolution: ChartResolution = viewState.resolution !== "auto"
|
|
767
818
|
&& resolutionSupport !== null
|
|
@@ -873,7 +924,6 @@ function ComparisonStockChartView({
|
|
|
873
924
|
]);
|
|
874
925
|
const axisSectionWidth = axisWidth + axisRightPadding;
|
|
875
926
|
const chartWidth = Math.max(width - axisSectionWidth - axisGap, minChartWidth);
|
|
876
|
-
const maxCursorX = chartWidth - 1;
|
|
877
927
|
const seriesDates = useMemo(() => getUniqueSortedSeriesDates(series), [series]);
|
|
878
928
|
const visibleDateWindow = useMemo(() => buildVisibleDateWindow(seriesDates, viewState.panOffset, viewState.zoomLevel), [seriesDates, viewState.panOffset, viewState.zoomLevel]);
|
|
879
929
|
const activePreset = resolveVisibleActivePreset(seriesDates, {
|
|
@@ -1006,6 +1056,16 @@ function ComparisonStockChartView({
|
|
|
1006
1056
|
));
|
|
1007
1057
|
};
|
|
1008
1058
|
|
|
1059
|
+
const selectSymbolByOffset = (offset: number) => {
|
|
1060
|
+
setViewState((current) => {
|
|
1061
|
+
const currentIndex = Math.max(symbols.indexOf(current.selectedSymbol ?? symbols[0] ?? ""), 0);
|
|
1062
|
+
return {
|
|
1063
|
+
...current,
|
|
1064
|
+
selectedSymbol: symbols[clamp(currentIndex + offset, 0, symbols.length - 1)] ?? current.selectedSymbol,
|
|
1065
|
+
};
|
|
1066
|
+
});
|
|
1067
|
+
};
|
|
1068
|
+
|
|
1009
1069
|
useEffect(() => {
|
|
1010
1070
|
const remapCursor = (cursor: DisplayCursorState) => buildDisplayCursorState(
|
|
1011
1071
|
cursor.cellX,
|
|
@@ -1040,7 +1100,8 @@ function ComparisonStockChartView({
|
|
|
1040
1100
|
cursorY: null,
|
|
1041
1101
|
selectedSymbol: viewState.selectedSymbol,
|
|
1042
1102
|
colors: chartColors,
|
|
1043
|
-
|
|
1103
|
+
marketSession,
|
|
1104
|
+
}), [chartColors, chartHeight, chartWidth, marketSession, projection, viewState.selectedSymbol]);
|
|
1044
1105
|
const displayScene = useMemo(() => buildComparisonChartScene(projection, {
|
|
1045
1106
|
width: chartWidth,
|
|
1046
1107
|
height: chartHeight,
|
|
@@ -1048,7 +1109,8 @@ function ComparisonStockChartView({
|
|
|
1048
1109
|
cursorY: displayCursorY,
|
|
1049
1110
|
selectedSymbol: viewState.selectedSymbol,
|
|
1050
1111
|
colors: chartColors,
|
|
1051
|
-
|
|
1112
|
+
marketSession,
|
|
1113
|
+
}), [chartColors, chartHeight, chartWidth, displayCursorX, displayCursorY, marketSession, projection, viewState.selectedSymbol]);
|
|
1052
1114
|
|
|
1053
1115
|
const staticResult = useMemo(() => renderComparisonChart(projection, {
|
|
1054
1116
|
width: chartWidth,
|
|
@@ -1057,7 +1119,8 @@ function ComparisonStockChartView({
|
|
|
1057
1119
|
cursorY: null,
|
|
1058
1120
|
selectedSymbol: viewState.selectedSymbol,
|
|
1059
1121
|
colors: chartColors,
|
|
1060
|
-
|
|
1122
|
+
marketSession,
|
|
1123
|
+
}), [chartColors, chartHeight, chartWidth, marketSession, projection, viewState.selectedSymbol]);
|
|
1061
1124
|
|
|
1062
1125
|
const interactiveResult = useMemo(() => (
|
|
1063
1126
|
effectiveRenderer === "kitty" || useCanvasChart
|
|
@@ -1069,8 +1132,9 @@ function ComparisonStockChartView({
|
|
|
1069
1132
|
cursorY: displayCursorY,
|
|
1070
1133
|
selectedSymbol: viewState.selectedSymbol,
|
|
1071
1134
|
colors: chartColors,
|
|
1135
|
+
marketSession,
|
|
1072
1136
|
})
|
|
1073
|
-
), [chartColors, chartHeight, chartWidth, displayCursorX, displayCursorY, effectiveRenderer, projection, useCanvasChart, viewState.selectedSymbol]);
|
|
1137
|
+
), [chartColors, chartHeight, chartWidth, displayCursorX, displayCursorY, effectiveRenderer, marketSession, projection, useCanvasChart, viewState.selectedSymbol]);
|
|
1074
1138
|
|
|
1075
1139
|
const result = effectiveRenderer === "kitty" || useCanvasChart ? staticResult : interactiveResult!;
|
|
1076
1140
|
const timeAxisLabel = result.timeLabels || staticResult.timeLabels;
|
|
@@ -1234,7 +1298,14 @@ function ComparisonStockChartView({
|
|
|
1234
1298
|
viewState.selectedSymbol,
|
|
1235
1299
|
bitmapSize.pixelWidth,
|
|
1236
1300
|
bitmapSize.pixelHeight,
|
|
1237
|
-
[
|
|
1301
|
+
[
|
|
1302
|
+
chartColors.bgColor,
|
|
1303
|
+
chartColors.gridColor,
|
|
1304
|
+
chartColors.crosshairColor,
|
|
1305
|
+
chartColors.preMarketBgColor,
|
|
1306
|
+
chartColors.postMarketBgColor,
|
|
1307
|
+
].join(","),
|
|
1308
|
+
marketSessionKey,
|
|
1238
1309
|
);
|
|
1239
1310
|
const cachedBitmap = lastNativeBaseBitmapRef.current?.key === bitmapKey
|
|
1240
1311
|
? lastNativeBaseBitmapRef.current.bitmap
|
|
@@ -1264,7 +1335,10 @@ function ComparisonStockChartView({
|
|
|
1264
1335
|
chartColors.bgColor,
|
|
1265
1336
|
chartColors.crosshairColor,
|
|
1266
1337
|
chartColors.gridColor,
|
|
1338
|
+
chartColors.postMarketBgColor,
|
|
1339
|
+
chartColors.preMarketBgColor,
|
|
1267
1340
|
effectiveRenderer,
|
|
1341
|
+
marketSessionKey,
|
|
1268
1342
|
nativeSurfaceManager,
|
|
1269
1343
|
paneId,
|
|
1270
1344
|
projection,
|
|
@@ -1425,22 +1499,6 @@ function ComparisonStockChartView({
|
|
|
1425
1499
|
case "t":
|
|
1426
1500
|
onEditTickers?.();
|
|
1427
1501
|
return;
|
|
1428
|
-
case "h":
|
|
1429
|
-
mouseCrosshairDisabledRef.current = false;
|
|
1430
|
-
cursorMotionKindRef.current = "discrete";
|
|
1431
|
-
setViewState((current) => ({
|
|
1432
|
-
...current,
|
|
1433
|
-
cursorX: current.cursorX === null ? maxCursorX : Math.max(current.cursorX - 1, 0),
|
|
1434
|
-
}));
|
|
1435
|
-
return;
|
|
1436
|
-
case "l":
|
|
1437
|
-
mouseCrosshairDisabledRef.current = false;
|
|
1438
|
-
cursorMotionKindRef.current = "discrete";
|
|
1439
|
-
setViewState((current) => ({
|
|
1440
|
-
...current,
|
|
1441
|
-
cursorX: current.cursorX === null ? 0 : Math.min(current.cursorX + 1, maxCursorX),
|
|
1442
|
-
}));
|
|
1443
|
-
return;
|
|
1444
1502
|
case "escape":
|
|
1445
1503
|
mouseCrosshairDisabledRef.current = true;
|
|
1446
1504
|
updateDisplayCursorTarget(EMPTY_DISPLAY_CURSOR, "discrete");
|
|
@@ -1451,42 +1509,20 @@ function ComparisonStockChartView({
|
|
|
1451
1509
|
if (viewState.selectedSymbol) onOpenSymbol(viewState.selectedSymbol);
|
|
1452
1510
|
return;
|
|
1453
1511
|
case "left":
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
return {
|
|
1457
|
-
...current,
|
|
1458
|
-
selectedSymbol: symbols[Math.max(currentIndex - 1, 0)] ?? current.selectedSymbol,
|
|
1459
|
-
};
|
|
1460
|
-
});
|
|
1512
|
+
case "h":
|
|
1513
|
+
selectSymbolByOffset(-1);
|
|
1461
1514
|
return;
|
|
1462
1515
|
case "right":
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
return {
|
|
1466
|
-
...current,
|
|
1467
|
-
selectedSymbol: symbols[Math.min(currentIndex + 1, symbols.length - 1)] ?? current.selectedSymbol,
|
|
1468
|
-
};
|
|
1469
|
-
});
|
|
1516
|
+
case "l":
|
|
1517
|
+
selectSymbolByOffset(1);
|
|
1470
1518
|
return;
|
|
1471
1519
|
case "up":
|
|
1472
1520
|
case "k":
|
|
1473
|
-
|
|
1474
|
-
const currentIndex = Math.max(symbols.indexOf(current.selectedSymbol ?? symbols[0] ?? ""), 0);
|
|
1475
|
-
return {
|
|
1476
|
-
...current,
|
|
1477
|
-
selectedSymbol: symbols[Math.max(currentIndex - legendColumns, 0)] ?? current.selectedSymbol,
|
|
1478
|
-
};
|
|
1479
|
-
});
|
|
1521
|
+
selectSymbolByOffset(-legendColumns);
|
|
1480
1522
|
return;
|
|
1481
1523
|
case "down":
|
|
1482
1524
|
case "j":
|
|
1483
|
-
|
|
1484
|
-
const currentIndex = Math.max(symbols.indexOf(current.selectedSymbol ?? symbols[0] ?? ""), 0);
|
|
1485
|
-
return {
|
|
1486
|
-
...current,
|
|
1487
|
-
selectedSymbol: symbols[Math.min(currentIndex + legendColumns, symbols.length - 1)] ?? current.selectedSymbol,
|
|
1488
|
-
};
|
|
1489
|
-
});
|
|
1525
|
+
selectSymbolByOffset(legendColumns);
|
|
1490
1526
|
return;
|
|
1491
1527
|
}
|
|
1492
1528
|
|
|
@@ -1629,18 +1665,9 @@ function ComparisonStockChartView({
|
|
|
1629
1665
|
const hasChartData = series.some((entry) => entry.points.length > 0);
|
|
1630
1666
|
const hasDisplayCursor = displayCursorX !== null && displayCursorY !== null;
|
|
1631
1667
|
const cursorScene = hasDisplayCursor ? displayScene : staticScene;
|
|
1632
|
-
const
|
|
1633
|
-
|
|
1634
|
-
const selectedRawValue = selectedPoint?.rawValue ?? selectedSeries?.latestRawValue ?? null;
|
|
1635
|
-
const selectedBaseValue = selectedSeries?.baseValue ?? null;
|
|
1636
|
-
const selectedChange = selectedRawValue !== null && selectedBaseValue !== null
|
|
1637
|
-
? selectedRawValue - selectedBaseValue
|
|
1638
|
-
: null;
|
|
1639
|
-
const selectedChangePct = selectedChange !== null && selectedBaseValue
|
|
1640
|
-
? (selectedChange / selectedBaseValue) * 100
|
|
1668
|
+
const legendActiveIndex = hasChartData
|
|
1669
|
+
? cursorScene?.activeIdx ?? staticScene?.activeIdx ?? null
|
|
1641
1670
|
: null;
|
|
1642
|
-
const selectedCurrency = selectedSeries?.currency ?? "USD";
|
|
1643
|
-
const displayDate = cursorScene?.activeDate ?? staticResult.activeDate;
|
|
1644
1671
|
const visiblePriceRange = result.priceRange ?? staticResult.priceRange ?? undefined;
|
|
1645
1672
|
const axisLabels = new Map(staticResult.axisLabels.map((entry) => [entry.row, entry.label]));
|
|
1646
1673
|
const cursorRow = useCanvasChart ? cursorScene?.cursorRow ?? null : result.cursorRow;
|
|
@@ -1652,10 +1679,6 @@ function ComparisonStockChartView({
|
|
|
1652
1679
|
visiblePriceRange,
|
|
1653
1680
|
)
|
|
1654
1681
|
: null;
|
|
1655
|
-
const visibleLabel = formatVisibleSpanLabel({
|
|
1656
|
-
start: visibleWindow.dates[0] ?? null,
|
|
1657
|
-
end: visibleWindow.dates[visibleWindow.dates.length - 1] ?? null,
|
|
1658
|
-
});
|
|
1659
1682
|
const cursorTimeAxisColumn = hasDisplayCursor ? displayScene?.cursorColumn ?? null : null;
|
|
1660
1683
|
const cursorTimeAxisDate = hasDisplayCursor ? displayScene?.activeDate ?? null : null;
|
|
1661
1684
|
|
|
@@ -1688,9 +1711,10 @@ function ComparisonStockChartView({
|
|
|
1688
1711
|
cursorY: null,
|
|
1689
1712
|
selectedSymbol: viewState.selectedSymbol,
|
|
1690
1713
|
colors: chartColors,
|
|
1714
|
+
marketSession,
|
|
1691
1715
|
})
|
|
1692
1716
|
: null
|
|
1693
|
-
), [canvasBitmapSize?.pixelWidth, canvasProjection, chartColors, chartHeight, chartWidth, viewState.selectedSymbol]);
|
|
1717
|
+
), [canvasBitmapSize?.pixelWidth, canvasProjection, chartColors, chartHeight, chartWidth, marketSession, viewState.selectedSymbol]);
|
|
1694
1718
|
|
|
1695
1719
|
const canvasBaseBitmapKey = useMemo(() => {
|
|
1696
1720
|
if (!canvasBitmapSize || !canvasProjection || !hasChartData || isBlockingBody || bodyMessage) return null;
|
|
@@ -1700,7 +1724,14 @@ function ComparisonStockChartView({
|
|
|
1700
1724
|
viewState.selectedSymbol,
|
|
1701
1725
|
canvasBitmapSize.pixelWidth,
|
|
1702
1726
|
canvasBitmapSize.pixelHeight,
|
|
1703
|
-
[
|
|
1727
|
+
[
|
|
1728
|
+
chartColors.bgColor,
|
|
1729
|
+
chartColors.gridColor,
|
|
1730
|
+
chartColors.crosshairColor,
|
|
1731
|
+
chartColors.preMarketBgColor,
|
|
1732
|
+
chartColors.postMarketBgColor,
|
|
1733
|
+
].join(","),
|
|
1734
|
+
marketSessionKey,
|
|
1704
1735
|
);
|
|
1705
1736
|
}, [
|
|
1706
1737
|
bodyMessage,
|
|
@@ -1709,8 +1740,11 @@ function ComparisonStockChartView({
|
|
|
1709
1740
|
chartColors.bgColor,
|
|
1710
1741
|
chartColors.crosshairColor,
|
|
1711
1742
|
chartColors.gridColor,
|
|
1743
|
+
chartColors.postMarketBgColor,
|
|
1744
|
+
chartColors.preMarketBgColor,
|
|
1712
1745
|
hasChartData,
|
|
1713
1746
|
isBlockingBody,
|
|
1747
|
+
marketSessionKey,
|
|
1714
1748
|
symbols.length,
|
|
1715
1749
|
viewState.selectedSymbol,
|
|
1716
1750
|
]);
|
|
@@ -1852,32 +1886,6 @@ function ComparisonStockChartView({
|
|
|
1852
1886
|
flexGrow={1}
|
|
1853
1887
|
onMouseScroll={hasChartData && !isBlockingBody && !bodyMessage ? handlePlotScroll : undefined}
|
|
1854
1888
|
>
|
|
1855
|
-
<Box flexDirection="row" gap={2} height={1}>
|
|
1856
|
-
<Text attributes={TextAttributes.BOLD} fg={colors.textBright}>
|
|
1857
|
-
{viewState.selectedSymbol ?? "Compare"} - {getChartResolutionLabel(effectiveResolution)}
|
|
1858
|
-
</Text>
|
|
1859
|
-
<Text fg={colors.textDim}>{visibleLabel}</Text>
|
|
1860
|
-
<Text fg={selectedChange !== null ? priceColor(selectedChange) : colors.textDim}>
|
|
1861
|
-
{selectedRawValue !== null
|
|
1862
|
-
? formatMarketPriceWithCurrency(selectedRawValue, selectedCurrency, {
|
|
1863
|
-
minimumFractionDigits: 2,
|
|
1864
|
-
precisionOffset: 1,
|
|
1865
|
-
priceRange: visiblePriceRange,
|
|
1866
|
-
})
|
|
1867
|
-
: "—"}
|
|
1868
|
-
</Text>
|
|
1869
|
-
<Text fg={selectedChange !== null ? priceColor(selectedChange) : colors.textDim}>
|
|
1870
|
-
{selectedChange !== null && selectedChangePct !== null
|
|
1871
|
-
? `${formatSignedMarketPrice(selectedChange, {
|
|
1872
|
-
minimumFractionDigits: 2,
|
|
1873
|
-
precisionOffset: 1,
|
|
1874
|
-
priceRange: visiblePriceRange,
|
|
1875
|
-
})} (${formatPercentRaw(selectedChangePct)})`
|
|
1876
|
-
: "Waiting for data"}
|
|
1877
|
-
</Text>
|
|
1878
|
-
{displayDate && <Text fg={colors.textDim}>{formatDateShort(displayDate)}</Text>}
|
|
1879
|
-
</Box>
|
|
1880
|
-
|
|
1881
1889
|
<Box flexDirection="row" height={1}>
|
|
1882
1890
|
<Box flexDirection="row" gap={1}>
|
|
1883
1891
|
{TIME_RANGES.map((range, index) => (
|
|
@@ -1951,7 +1959,7 @@ function ComparisonStockChartView({
|
|
|
1951
1959
|
cursorColumn={cursorTimeAxisColumn}
|
|
1952
1960
|
cursorPixelX={hasDisplayCursor ? displayCursor.pixelX : null}
|
|
1953
1961
|
cursorDate={cursorTimeAxisDate}
|
|
1954
|
-
dates={
|
|
1962
|
+
dates={visibleWindow.dates}
|
|
1955
1963
|
cursorColor={chartColors.crosshairColor}
|
|
1956
1964
|
/>
|
|
1957
1965
|
</Box>
|
|
@@ -1964,19 +1972,11 @@ function ComparisonStockChartView({
|
|
|
1964
1972
|
{legendRow.map((symbol) => {
|
|
1965
1973
|
const item = projection.series.find((entry) => entry.symbol === symbol) ?? null;
|
|
1966
1974
|
const isSelected = viewState.selectedSymbol === symbol;
|
|
1967
|
-
const
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
? (latestChange / item.baseValue) * 100
|
|
1971
|
-
: null;
|
|
1975
|
+
const activeRaw = legendActiveIndex !== null
|
|
1976
|
+
? item?.points[legendActiveIndex]?.rawValue ?? null
|
|
1977
|
+
: item?.latestRawValue ?? null;
|
|
1972
1978
|
const currency = item?.currency ?? "USD";
|
|
1973
|
-
const summary =
|
|
1974
|
-
? `${symbol} ${formatMarketPriceWithCurrency(latestRaw, currency, {
|
|
1975
|
-
minimumFractionDigits: 2,
|
|
1976
|
-
precisionOffset: 1,
|
|
1977
|
-
priceRange: visiblePriceRange,
|
|
1978
|
-
})} ${formatPercentRaw(latestChangePct)}`
|
|
1979
|
-
: `${symbol} waiting`;
|
|
1979
|
+
const summary = formatLegendSummary(symbol, activeRaw, item?.baseValue ?? null, currency, visiblePriceRange);
|
|
1980
1980
|
|
|
1981
1981
|
return (
|
|
1982
1982
|
<Box
|