gloomberb 0.7.2 → 0.7.3
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 +49 -15
- package/src/components/chart/chart-types.ts +2 -2
- package/src/components/chart/chart.test.ts +27 -8
- package/src/components/chart/comparison-stock-chart.tsx +84 -121
- package/src/components/chart/native/chart-rasterizer.ts +43 -17
- package/src/components/chart/price-axis-labels.tsx +25 -9
- package/src/components/chart/stock-chart-renderer-switch.test.tsx +1 -1
- package/src/components/chart/stock-chart.tsx +59 -41
- package/src/components/command-bar/command-bar.test.tsx +446 -120
- package/src/components/command-bar/command-bar.tsx +528 -383
- package/src/components/command-bar/command-registry.ts +16 -6
- package/src/components/command-bar/root-shortcuts.ts +4 -5
- package/src/components/command-bar/view-model.test.ts +1 -0
- package/src/components/command-bar/view-model.ts +2 -1
- 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 +11 -51
- 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/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.ts +8 -1
- 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/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 +258 -44
- package/src/plugins/builtin/chat.test.tsx +257 -6
- package/src/plugins/builtin/chat.tsx +225 -60
- 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/news-wire/index.tsx +1 -1
- 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 +11 -0
- package/src/plugins/builtin/portfolio-list/mutations.ts +17 -0
- package/src/plugins/builtin/portfolio-list/pane.tsx +117 -18
- 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 +282 -2
- package/src/plugins/builtin/quote-monitor.test.tsx +158 -25
- package/src/plugins/builtin/sectors/index.tsx +252 -41
- 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 +0 -6
- package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +442 -63
- package/src/plugins/builtin/ticker-detail/settings.ts +84 -3
- package/src/plugins/builtin/ticker-detail.test.tsx +2 -2
- 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/state/app-bootstrap.ts +3 -3
- package/src/state/app-context.test.ts +113 -0
- package/src/state/app-context.tsx +8 -4
- package/src/theme/colors.ts +13 -14
- package/src/types/config.ts +9 -4
- package/src/types/plugin.ts +8 -1
- package/src/ui/context-menu.tsx +4 -4
- package/src/utils/api-client.test.ts +128 -0
- package/src/utils/api-client.ts +112 -7
- package/src/utils/fuzzy-search.ts +12 -1
- package/src/utils/keyboard.ts +31 -0
- package/src/utils/ticker-search.test.ts +2 -2
- package/src/utils/ticker-search.ts +48 -1
- package/src/version.ts +1 -1
|
@@ -2,14 +2,14 @@ import type { PricePoint } from "../../types/financials";
|
|
|
2
2
|
|
|
3
3
|
export type TimeRange = "1D" | "1W" | "1M" | "3M" | "6M" | "1Y" | "5Y" | "ALL";
|
|
4
4
|
export type ChartResolution = "auto" | "1m" | "5m" | "15m" | "30m" | "45m" | "1h" | "1d" | "1wk" | "1mo";
|
|
5
|
-
export type ChartRenderMode = "area" | "line" | "candles" | "ohlc";
|
|
5
|
+
export type ChartRenderMode = "area" | "line" | "candles" | "ohlc" | "hlc";
|
|
6
6
|
export type ChartAxisMode = "price" | "percent";
|
|
7
7
|
export type ChartRendererPreference = "auto" | "kitty" | "braille";
|
|
8
8
|
export type ResolvedChartRenderer = "kitty" | "braille";
|
|
9
9
|
export type ComparisonChartRenderMode = "area" | "line";
|
|
10
10
|
|
|
11
11
|
export const TIME_RANGES: TimeRange[] = ["1D", "1W", "1M", "3M", "6M", "1Y", "5Y", "ALL"];
|
|
12
|
-
export const CHART_RENDER_MODES: ChartRenderMode[] = ["area", "line", "candles", "ohlc"];
|
|
12
|
+
export const CHART_RENDER_MODES: ChartRenderMode[] = ["area", "line", "candles", "ohlc", "hlc"];
|
|
13
13
|
export const CHART_RENDERER_PREFERENCES: ChartRendererPreference[] = ["auto", "kitty", "braille"];
|
|
14
14
|
export const COMPARISON_RENDER_MODES: ComparisonChartRenderMode[] = ["area", "line"];
|
|
15
15
|
|
|
@@ -183,6 +183,8 @@ describe("resolveRenderMode", () => {
|
|
|
183
183
|
expect(resolveRenderMode("candles", 28)).toMatchObject({ effectiveMode: "ohlc", fallbackMode: "ohlc" });
|
|
184
184
|
expect(resolveRenderMode("candles", 39)).toMatchObject({ effectiveMode: "ohlc", fallbackMode: "ohlc" });
|
|
185
185
|
expect(resolveRenderMode("candles", 40)).toMatchObject({ effectiveMode: "candles", fallbackMode: null });
|
|
186
|
+
expect(resolveRenderMode("hlc", 27)).toMatchObject({ effectiveMode: "line", fallbackMode: "line" });
|
|
187
|
+
expect(resolveRenderMode("hlc", 28)).toMatchObject({ effectiveMode: "hlc", fallbackMode: null });
|
|
186
188
|
});
|
|
187
189
|
|
|
188
190
|
test("forces compact charts back to area mode", () => {
|
|
@@ -192,7 +194,7 @@ describe("resolveRenderMode", () => {
|
|
|
192
194
|
expect(projection.fallbackMode).toBeNull();
|
|
193
195
|
});
|
|
194
196
|
|
|
195
|
-
test("uses fewer projected buckets for candles and ohlc to preserve spacing", () => {
|
|
197
|
+
test("uses fewer projected buckets for candles and ohlc-style bars to preserve spacing", () => {
|
|
196
198
|
const denseSeries = Array.from({ length: 100 }, (_, i) => ({
|
|
197
199
|
date: new Date(2024, 0, i + 1),
|
|
198
200
|
open: 100 + i,
|
|
@@ -204,6 +206,7 @@ describe("resolveRenderMode", () => {
|
|
|
204
206
|
|
|
205
207
|
expect(projectChartData(denseSeries, 80, "candles", false).points).toHaveLength(34);
|
|
206
208
|
expect(projectChartData(denseSeries, 80, "ohlc", false).points).toHaveLength(34);
|
|
209
|
+
expect(projectChartData(denseSeries, 80, "hlc", false).points).toHaveLength(34);
|
|
207
210
|
expect(projectChartData(denseSeries, 80, "line", false).points).toHaveLength(80);
|
|
208
211
|
});
|
|
209
212
|
|
|
@@ -468,7 +471,7 @@ describe("renderChart", () => {
|
|
|
468
471
|
cellHeightPx,
|
|
469
472
|
});
|
|
470
473
|
|
|
471
|
-
expect(overlay.labelText).toBe("$214.03
|
|
474
|
+
expect(overlay.labelText).toBe("$214.03");
|
|
472
475
|
expect(overlay.topPercent).toBeCloseTo((cursorPixelY / (height * cellHeightPx - 1)) * 100, 5);
|
|
473
476
|
});
|
|
474
477
|
|
|
@@ -528,6 +531,7 @@ describe("renderChart", () => {
|
|
|
528
531
|
{ mode: "line", width: 12, cursorX: 4 },
|
|
529
532
|
{ mode: "candles", width: 40, cursorX: 13 },
|
|
530
533
|
{ mode: "ohlc", width: 28, cursorX: 9 },
|
|
534
|
+
{ mode: "hlc", width: 28, cursorX: 9 },
|
|
531
535
|
];
|
|
532
536
|
|
|
533
537
|
for (const testCase of cases) {
|
|
@@ -553,6 +557,7 @@ describe("renderChart", () => {
|
|
|
553
557
|
{ mode: "area", width: 12, height: 6, cursorX: 5, cursorY: 4 },
|
|
554
558
|
{ mode: "candles", width: 40, height: 6, cursorX: 13, cursorY: 3 },
|
|
555
559
|
{ mode: "ohlc", width: 28, height: 6, cursorX: 9, cursorY: 1 },
|
|
560
|
+
{ mode: "hlc", width: 28, height: 6, cursorX: 9, cursorY: 1 },
|
|
556
561
|
];
|
|
557
562
|
|
|
558
563
|
for (const testCase of cases) {
|
|
@@ -638,12 +643,26 @@ describe("renderChart", () => {
|
|
|
638
643
|
width: 28,
|
|
639
644
|
height: 6,
|
|
640
645
|
lines: [
|
|
641
|
-
"⠁ ⠁ ⠁
|
|
642
|
-
"
|
|
643
|
-
"
|
|
644
|
-
"
|
|
645
|
-
"
|
|
646
|
-
"⡀ ⡀ ⡀ ⡀ ⡀ ⡀
|
|
646
|
+
"⠁ ⠁ ⠁ ⢸⣿ ⠁ ⠁ ⣤⡄ ⠁ ⠁ ⠁",
|
|
647
|
+
" ⢰⣶ ⢸⣿⠒⠂ ⠐⠒⣿⡇ ",
|
|
648
|
+
"⠁⢸⣿⠒⠂ ⠁ ⠒⢺⣿ ⠁ ⠁ ⣿⡇ ⠁ ⠁⣶⡆⠁",
|
|
649
|
+
"⠤⢼⣿⡀ ⡀ ⠸⠿ ⡀ ⡀ ⣿⡇ ⡀ ⡀⣿⡏⠉",
|
|
650
|
+
" ⠸⠿ ⣿⡧⠤ ⠠⠤⣿⡇ ",
|
|
651
|
+
"⡀ ⡀ ⡀ ⡀ ⡀ ⡀ ⣿⡇ ⡀ ⡀⠛⠃⡀",
|
|
652
|
+
],
|
|
653
|
+
timeLabels: "Jan 2 3 4 Jan 5",
|
|
654
|
+
},
|
|
655
|
+
{
|
|
656
|
+
mode: "hlc",
|
|
657
|
+
width: 28,
|
|
658
|
+
height: 6,
|
|
659
|
+
lines: [
|
|
660
|
+
"⠁ ⠁ ⠁ ⢸⣿ ⠁ ⠁ ⣤⡄ ⠁ ⠁ ⠁",
|
|
661
|
+
" ⢰⣶ ⢸⣿⠒⠂ ⣿⡇ ",
|
|
662
|
+
"⠁⢸⣿⠒⠂ ⠁ ⢸⣿ ⠁ ⠁ ⣿⡇ ⠁ ⠁⣶⡆⠁",
|
|
663
|
+
"⡀⢸⣿⡀ ⡀ ⠸⠿ ⡀ ⡀ ⣿⡇ ⡀ ⡀⣿⡏⠉",
|
|
664
|
+
" ⠸⠿ ⣿⡧⠤ ⣿⡇ ",
|
|
665
|
+
"⡀ ⡀ ⡀ ⡀ ⡀ ⡀ ⣿⡇ ⡀ ⡀⠛⠃⡀",
|
|
647
666
|
],
|
|
648
667
|
timeLabels: "Jan 2 3 4 Jan 5",
|
|
649
668
|
},
|
|
@@ -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,7 +95,7 @@ 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";
|
|
102
101
|
|
|
@@ -445,6 +444,23 @@ function clipText(text: string, width: number): string {
|
|
|
445
444
|
return `${text.slice(0, width - 3)}...`;
|
|
446
445
|
}
|
|
447
446
|
|
|
447
|
+
function formatLegendSummary(
|
|
448
|
+
symbol: string,
|
|
449
|
+
rawValue: number | null,
|
|
450
|
+
baseValue: number | null,
|
|
451
|
+
currency: string,
|
|
452
|
+
priceRange: number | undefined,
|
|
453
|
+
): string {
|
|
454
|
+
if (rawValue === null) return `${symbol} waiting`;
|
|
455
|
+
const price = formatMarketPriceWithCurrency(rawValue, currency, {
|
|
456
|
+
minimumFractionDigits: 2,
|
|
457
|
+
precisionOffset: 1,
|
|
458
|
+
priceRange,
|
|
459
|
+
});
|
|
460
|
+
if (baseValue === null || baseValue === 0) return `${symbol} ${price}`;
|
|
461
|
+
return `${symbol} ${price} ${formatPercentRaw(((rawValue - baseValue) / baseValue) * 100)}`;
|
|
462
|
+
}
|
|
463
|
+
|
|
448
464
|
function getInitialComparisonMode(mode: string | undefined): ComparisonChartRenderMode {
|
|
449
465
|
return mode === "line" ? "line" : "area";
|
|
450
466
|
}
|
|
@@ -555,6 +571,7 @@ function ComparisonStockChartView({
|
|
|
555
571
|
const mouseCrosshairDisabledRef = useRef(false);
|
|
556
572
|
const cursorMotionKindRef = useRef<ChartCursorMotionKind>("discrete");
|
|
557
573
|
const animationFrameRef = useRef<number | null>(null);
|
|
574
|
+
const lastAppliedStoredSelectionKeyRef = useRef<string | null>(null);
|
|
558
575
|
const pendingCanonicalResetRef = useRef(1);
|
|
559
576
|
const appliedCanonicalResetRef = useRef(0);
|
|
560
577
|
const pendingExpansionRef = useRef<PendingExpansionAction>(null);
|
|
@@ -564,7 +581,7 @@ function ComparisonStockChartView({
|
|
|
564
581
|
const axisRightPadding = 1;
|
|
565
582
|
const minimumAxisWidth = axisMode === "percent" ? 5 : 4;
|
|
566
583
|
const axisGap = axisSectionWidthBudget > 0 ? 1 : 0;
|
|
567
|
-
const headerRows =
|
|
584
|
+
const headerRows = 0;
|
|
568
585
|
const controlRows = 2;
|
|
569
586
|
const timeAxisRows = 1;
|
|
570
587
|
const helpRows = 0;
|
|
@@ -591,13 +608,35 @@ function ComparisonStockChartView({
|
|
|
591
608
|
));
|
|
592
609
|
};
|
|
593
610
|
|
|
594
|
-
const
|
|
595
|
-
|
|
596
|
-
source
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
611
|
+
const marketDataProvider = getSharedMarketData();
|
|
612
|
+
const capabilityDescriptor = useMemo(() => {
|
|
613
|
+
const sources = symbolSources.map((source) => ({
|
|
614
|
+
symbol: source.symbol,
|
|
615
|
+
exchange: source.exchange,
|
|
616
|
+
brokerId: source.brokerId,
|
|
617
|
+
brokerInstanceId: source.brokerInstanceId,
|
|
618
|
+
instrument: source.instrument,
|
|
619
|
+
}));
|
|
620
|
+
return {
|
|
621
|
+
key: sources.map((source) => [
|
|
622
|
+
source.symbol,
|
|
623
|
+
source.exchange,
|
|
624
|
+
source.brokerId ?? "",
|
|
625
|
+
source.brokerInstanceId ?? "",
|
|
626
|
+
source.instrument?.conId ?? "",
|
|
627
|
+
source.instrument?.localSymbol ?? "",
|
|
628
|
+
source.instrument?.symbol ?? "",
|
|
629
|
+
].join(":")).join("|"),
|
|
630
|
+
sources,
|
|
631
|
+
};
|
|
632
|
+
}, [symbolSources]);
|
|
633
|
+
const effectiveResolutionSupport = useMemo<ChartResolutionSupport[]>(() => (
|
|
634
|
+
resolutionSupport ?? DEFAULT_VISIBLE_MANUAL_CHART_RESOLUTIONS.map((resolution) => ({ resolution, maxRange: "ALL" as const }))
|
|
635
|
+
), [resolutionSupport]);
|
|
636
|
+
const selectionSupportMap = useMemo(
|
|
637
|
+
() => buildChartResolutionSupportMap(effectiveResolutionSupport),
|
|
638
|
+
[effectiveResolutionSupport],
|
|
639
|
+
);
|
|
601
640
|
|
|
602
641
|
useEffect(() => {
|
|
603
642
|
if (symbols.includes(viewState.selectedSymbol ?? "")) return;
|
|
@@ -608,21 +647,25 @@ function ComparisonStockChartView({
|
|
|
608
647
|
}, [symbols, viewState.selectedSymbol]);
|
|
609
648
|
|
|
610
649
|
useEffect(() => {
|
|
650
|
+
const storedSelectionKey = `${storedRangePreset}:${storedResolution}`;
|
|
651
|
+
if (lastAppliedStoredSelectionKeyRef.current === storedSelectionKey) return;
|
|
652
|
+
|
|
653
|
+
lastAppliedStoredSelectionKeyRef.current = storedSelectionKey;
|
|
611
654
|
pendingCanonicalResetRef.current += 1;
|
|
612
655
|
setViewState((current) => (
|
|
613
656
|
storedResolution === "auto"
|
|
614
|
-
? resolveStoredChartSelection(current, storedRangePreset, storedResolution,
|
|
657
|
+
? resolveStoredChartSelection(current, storedRangePreset, storedResolution, selectionSupportMap)
|
|
615
658
|
: resolvePresetSelection(
|
|
616
659
|
{
|
|
617
660
|
...current,
|
|
618
661
|
resolution: storedResolution,
|
|
619
662
|
},
|
|
620
663
|
storedRangePreset,
|
|
621
|
-
getSupportMaxRange(
|
|
664
|
+
getSupportMaxRange(selectionSupportMap, storedResolution),
|
|
622
665
|
)
|
|
623
666
|
));
|
|
624
667
|
updateDisplayCursorTarget(EMPTY_DISPLAY_CURSOR, "discrete");
|
|
625
|
-
}, [storedRangePreset, storedResolution
|
|
668
|
+
}, [selectionSupportMap, storedRangePreset, storedResolution]);
|
|
626
669
|
|
|
627
670
|
const commitDisplayCursor = (next: DisplayCursorState) => {
|
|
628
671
|
displayCursorRef.current = next;
|
|
@@ -705,15 +748,15 @@ function ComparisonStockChartView({
|
|
|
705
748
|
), [nativeSurfaceManager]);
|
|
706
749
|
|
|
707
750
|
useEffect(() => {
|
|
708
|
-
const provider =
|
|
709
|
-
if ((!provider?.getChartResolutionSupport && !provider?.getChartResolutionCapabilities) ||
|
|
751
|
+
const provider = marketDataProvider;
|
|
752
|
+
if ((!provider?.getChartResolutionSupport && !provider?.getChartResolutionCapabilities) || capabilityDescriptor.sources.length === 0) {
|
|
710
753
|
setResolutionSupport(null);
|
|
711
754
|
return;
|
|
712
755
|
}
|
|
713
756
|
|
|
714
757
|
let cancelled = false;
|
|
715
758
|
setResolutionSupport(null);
|
|
716
|
-
Promise.all(
|
|
759
|
+
Promise.all(capabilityDescriptor.sources.map(async (source) => {
|
|
717
760
|
try {
|
|
718
761
|
const support = provider.getChartResolutionSupport
|
|
719
762
|
? await provider.getChartResolutionSupport(
|
|
@@ -757,11 +800,7 @@ function ComparisonStockChartView({
|
|
|
757
800
|
return () => {
|
|
758
801
|
cancelled = true;
|
|
759
802
|
};
|
|
760
|
-
}, [
|
|
761
|
-
|
|
762
|
-
const effectiveResolutionSupport = useMemo<ChartResolutionSupport[]>(() => (
|
|
763
|
-
resolutionSupport ?? DEFAULT_VISIBLE_MANUAL_CHART_RESOLUTIONS.map((resolution) => ({ resolution, maxRange: "ALL" as const }))
|
|
764
|
-
), [resolutionSupport]);
|
|
803
|
+
}, [capabilityDescriptor.key, marketDataProvider]);
|
|
765
804
|
const availableManualResolutions = resolutionSupport?.map((entry) => entry.resolution) ?? DEFAULT_VISIBLE_MANUAL_CHART_RESOLUTIONS;
|
|
766
805
|
const effectiveResolution: ChartResolution = viewState.resolution !== "auto"
|
|
767
806
|
&& resolutionSupport !== null
|
|
@@ -873,7 +912,6 @@ function ComparisonStockChartView({
|
|
|
873
912
|
]);
|
|
874
913
|
const axisSectionWidth = axisWidth + axisRightPadding;
|
|
875
914
|
const chartWidth = Math.max(width - axisSectionWidth - axisGap, minChartWidth);
|
|
876
|
-
const maxCursorX = chartWidth - 1;
|
|
877
915
|
const seriesDates = useMemo(() => getUniqueSortedSeriesDates(series), [series]);
|
|
878
916
|
const visibleDateWindow = useMemo(() => buildVisibleDateWindow(seriesDates, viewState.panOffset, viewState.zoomLevel), [seriesDates, viewState.panOffset, viewState.zoomLevel]);
|
|
879
917
|
const activePreset = resolveVisibleActivePreset(seriesDates, {
|
|
@@ -1006,6 +1044,16 @@ function ComparisonStockChartView({
|
|
|
1006
1044
|
));
|
|
1007
1045
|
};
|
|
1008
1046
|
|
|
1047
|
+
const selectSymbolByOffset = (offset: number) => {
|
|
1048
|
+
setViewState((current) => {
|
|
1049
|
+
const currentIndex = Math.max(symbols.indexOf(current.selectedSymbol ?? symbols[0] ?? ""), 0);
|
|
1050
|
+
return {
|
|
1051
|
+
...current,
|
|
1052
|
+
selectedSymbol: symbols[clamp(currentIndex + offset, 0, symbols.length - 1)] ?? current.selectedSymbol,
|
|
1053
|
+
};
|
|
1054
|
+
});
|
|
1055
|
+
};
|
|
1056
|
+
|
|
1009
1057
|
useEffect(() => {
|
|
1010
1058
|
const remapCursor = (cursor: DisplayCursorState) => buildDisplayCursorState(
|
|
1011
1059
|
cursor.cellX,
|
|
@@ -1425,22 +1473,6 @@ function ComparisonStockChartView({
|
|
|
1425
1473
|
case "t":
|
|
1426
1474
|
onEditTickers?.();
|
|
1427
1475
|
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
1476
|
case "escape":
|
|
1445
1477
|
mouseCrosshairDisabledRef.current = true;
|
|
1446
1478
|
updateDisplayCursorTarget(EMPTY_DISPLAY_CURSOR, "discrete");
|
|
@@ -1451,42 +1483,20 @@ function ComparisonStockChartView({
|
|
|
1451
1483
|
if (viewState.selectedSymbol) onOpenSymbol(viewState.selectedSymbol);
|
|
1452
1484
|
return;
|
|
1453
1485
|
case "left":
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
return {
|
|
1457
|
-
...current,
|
|
1458
|
-
selectedSymbol: symbols[Math.max(currentIndex - 1, 0)] ?? current.selectedSymbol,
|
|
1459
|
-
};
|
|
1460
|
-
});
|
|
1486
|
+
case "h":
|
|
1487
|
+
selectSymbolByOffset(-1);
|
|
1461
1488
|
return;
|
|
1462
1489
|
case "right":
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
return {
|
|
1466
|
-
...current,
|
|
1467
|
-
selectedSymbol: symbols[Math.min(currentIndex + 1, symbols.length - 1)] ?? current.selectedSymbol,
|
|
1468
|
-
};
|
|
1469
|
-
});
|
|
1490
|
+
case "l":
|
|
1491
|
+
selectSymbolByOffset(1);
|
|
1470
1492
|
return;
|
|
1471
1493
|
case "up":
|
|
1472
1494
|
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
|
-
});
|
|
1495
|
+
selectSymbolByOffset(-legendColumns);
|
|
1480
1496
|
return;
|
|
1481
1497
|
case "down":
|
|
1482
1498
|
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
|
-
});
|
|
1499
|
+
selectSymbolByOffset(legendColumns);
|
|
1490
1500
|
return;
|
|
1491
1501
|
}
|
|
1492
1502
|
|
|
@@ -1629,18 +1639,9 @@ function ComparisonStockChartView({
|
|
|
1629
1639
|
const hasChartData = series.some((entry) => entry.points.length > 0);
|
|
1630
1640
|
const hasDisplayCursor = displayCursorX !== null && displayCursorY !== null;
|
|
1631
1641
|
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
|
|
1642
|
+
const legendActiveIndex = hasChartData
|
|
1643
|
+
? cursorScene?.activeIdx ?? staticScene?.activeIdx ?? null
|
|
1641
1644
|
: null;
|
|
1642
|
-
const selectedCurrency = selectedSeries?.currency ?? "USD";
|
|
1643
|
-
const displayDate = cursorScene?.activeDate ?? staticResult.activeDate;
|
|
1644
1645
|
const visiblePriceRange = result.priceRange ?? staticResult.priceRange ?? undefined;
|
|
1645
1646
|
const axisLabels = new Map(staticResult.axisLabels.map((entry) => [entry.row, entry.label]));
|
|
1646
1647
|
const cursorRow = useCanvasChart ? cursorScene?.cursorRow ?? null : result.cursorRow;
|
|
@@ -1652,10 +1653,6 @@ function ComparisonStockChartView({
|
|
|
1652
1653
|
visiblePriceRange,
|
|
1653
1654
|
)
|
|
1654
1655
|
: null;
|
|
1655
|
-
const visibleLabel = formatVisibleSpanLabel({
|
|
1656
|
-
start: visibleWindow.dates[0] ?? null,
|
|
1657
|
-
end: visibleWindow.dates[visibleWindow.dates.length - 1] ?? null,
|
|
1658
|
-
});
|
|
1659
1656
|
const cursorTimeAxisColumn = hasDisplayCursor ? displayScene?.cursorColumn ?? null : null;
|
|
1660
1657
|
const cursorTimeAxisDate = hasDisplayCursor ? displayScene?.activeDate ?? null : null;
|
|
1661
1658
|
|
|
@@ -1852,32 +1849,6 @@ function ComparisonStockChartView({
|
|
|
1852
1849
|
flexGrow={1}
|
|
1853
1850
|
onMouseScroll={hasChartData && !isBlockingBody && !bodyMessage ? handlePlotScroll : undefined}
|
|
1854
1851
|
>
|
|
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
1852
|
<Box flexDirection="row" height={1}>
|
|
1882
1853
|
<Box flexDirection="row" gap={1}>
|
|
1883
1854
|
{TIME_RANGES.map((range, index) => (
|
|
@@ -1964,19 +1935,11 @@ function ComparisonStockChartView({
|
|
|
1964
1935
|
{legendRow.map((symbol) => {
|
|
1965
1936
|
const item = projection.series.find((entry) => entry.symbol === symbol) ?? null;
|
|
1966
1937
|
const isSelected = viewState.selectedSymbol === symbol;
|
|
1967
|
-
const
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
? (latestChange / item.baseValue) * 100
|
|
1971
|
-
: null;
|
|
1938
|
+
const activeRaw = legendActiveIndex !== null
|
|
1939
|
+
? item?.points[legendActiveIndex]?.rawValue ?? null
|
|
1940
|
+
: item?.latestRawValue ?? null;
|
|
1972
1941
|
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`;
|
|
1942
|
+
const summary = formatLegendSummary(symbol, activeRaw, item?.baseValue ?? null, currency, visiblePriceRange);
|
|
1980
1943
|
|
|
1981
1944
|
return (
|
|
1982
1945
|
<Box
|
|
@@ -247,16 +247,39 @@ function projectX(index: number, count: number, left: number, right: number): nu
|
|
|
247
247
|
return lerp(left, right, index / (count - 1));
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
+
function isOhlcLikeMode(mode: ChartRenderMode): boolean {
|
|
251
|
+
return mode === "ohlc" || mode === "hlc";
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function isHighLowMode(mode: ChartRenderMode): boolean {
|
|
255
|
+
return mode === "candles" || isOhlcLikeMode(mode);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function getNativeBarMetrics(count: number, width: number, mode: Extract<ChartRenderMode, "candles" | "ohlc" | "hlc">) {
|
|
259
|
+
const spacing = width / Math.max(count, 1);
|
|
260
|
+
if (mode === "candles") {
|
|
261
|
+
return {
|
|
262
|
+
bodyWidth: clamp(spacing * 0.58, 2, Math.max(spacing - 1, 2)),
|
|
263
|
+
tickLength: 0,
|
|
264
|
+
stemThickness: 1.2,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
bodyWidth: 0,
|
|
270
|
+
tickLength: clamp(spacing * 0.5, 3, Math.max(spacing * 0.62, 3)),
|
|
271
|
+
stemThickness: clamp(spacing * 0.12, 2, 3.2),
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
250
275
|
function projectChartX(index: number, count: number, width: number, mode: ChartRenderMode): number {
|
|
251
|
-
if (mode
|
|
276
|
+
if (!isHighLowMode(mode)) {
|
|
252
277
|
return projectX(index, count, 0, Math.max(width - 1, 0));
|
|
253
278
|
}
|
|
254
279
|
|
|
255
|
-
const
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
const horizontalPad = mode === "ohlc"
|
|
259
|
-
? Math.ceil(Math.max(tickLength - 1, 0))
|
|
280
|
+
const { bodyWidth, tickLength, stemThickness } = getNativeBarMetrics(count, width, mode);
|
|
281
|
+
const horizontalPad = isOhlcLikeMode(mode)
|
|
282
|
+
? Math.ceil(Math.max(tickLength - 1, stemThickness / 2, 0))
|
|
260
283
|
: Math.ceil(bodyWidth / 2);
|
|
261
284
|
return projectX(index, count, horizontalPad, Math.max(width - 1 - horizontalPad, horizontalPad));
|
|
262
285
|
}
|
|
@@ -332,13 +355,11 @@ function drawCandles(
|
|
|
332
355
|
scene: ChartScene,
|
|
333
356
|
top: number,
|
|
334
357
|
bottom: number,
|
|
335
|
-
mode: Extract<ChartRenderMode, "candles" | "ohlc">,
|
|
358
|
+
mode: Extract<ChartRenderMode, "candles" | "ohlc" | "hlc">,
|
|
336
359
|
) {
|
|
337
|
-
const
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
const horizontalPad = mode === "ohlc"
|
|
341
|
-
? Math.ceil(Math.max(tickLength - 1, 0))
|
|
360
|
+
const { bodyWidth, tickLength, stemThickness } = getNativeBarMetrics(scene.points.length, width, mode);
|
|
361
|
+
const horizontalPad = isOhlcLikeMode(mode)
|
|
362
|
+
? Math.ceil(Math.max(tickLength - 1, stemThickness / 2, 0))
|
|
342
363
|
: Math.ceil(bodyWidth / 2);
|
|
343
364
|
const plotLeft = horizontalPad;
|
|
344
365
|
const plotRight = Math.max(width - 1 - horizontalPad, plotLeft);
|
|
@@ -355,10 +376,12 @@ function drawCandles(
|
|
|
355
376
|
const wickColor = parseHex(isUp ? scene.colors.wickUp : scene.colors.wickDown, 0.92);
|
|
356
377
|
const bodyColor = parseHex(isUp ? scene.colors.candleUp : scene.colors.candleDown, 1);
|
|
357
378
|
|
|
358
|
-
if (mode
|
|
359
|
-
drawLine(data, width, height, x, highY, x, lowY, wickColor,
|
|
360
|
-
|
|
361
|
-
|
|
379
|
+
if (isOhlcLikeMode(mode)) {
|
|
380
|
+
drawLine(data, width, height, x, highY, x, lowY, wickColor, stemThickness);
|
|
381
|
+
if (mode === "ohlc") {
|
|
382
|
+
drawLine(data, width, height, x - tickLength, openY, x, openY, bodyColor, Math.max(stemThickness, 1.4));
|
|
383
|
+
}
|
|
384
|
+
drawLine(data, width, height, x, closeY, x + tickLength, closeY, bodyColor, Math.max(stemThickness, 1.4));
|
|
362
385
|
continue;
|
|
363
386
|
}
|
|
364
387
|
|
|
@@ -395,7 +418,7 @@ function drawVolume(
|
|
|
395
418
|
if (heightRatio <= 0) continue;
|
|
396
419
|
const x = projectX(index, scene.points.length, 0, width - 1);
|
|
397
420
|
const barTop = lerp(bottom, top, heightRatio);
|
|
398
|
-
const isUp = scene.mode
|
|
421
|
+
const isUp = isHighLowMode(scene.mode)
|
|
399
422
|
? point.close >= point.open
|
|
400
423
|
: index === 0 || point.close >= scene.points[index - 1]!.close;
|
|
401
424
|
const color = parseHex(isUp ? scene.colors.volumeUp : scene.colors.volumeDown, 0.6);
|
|
@@ -486,6 +509,9 @@ export function renderNativeChartBase(scene: ChartScene, pixelWidth: number, pix
|
|
|
486
509
|
case "ohlc":
|
|
487
510
|
drawCandles(pixels, pixelWidth, pixelHeight, scene, layout.plotTop, layout.plotBottom, "ohlc");
|
|
488
511
|
break;
|
|
512
|
+
case "hlc":
|
|
513
|
+
drawCandles(pixels, pixelWidth, pixelHeight, scene, layout.plotTop, layout.plotBottom, "hlc");
|
|
514
|
+
break;
|
|
489
515
|
}
|
|
490
516
|
|
|
491
517
|
drawIndicatorOverlays(pixels, pixelWidth, pixelHeight, scene, layout.plotTop, layout.plotBottom);
|
|
@@ -25,7 +25,6 @@ function clamp(value: number, min: number, max: number): number {
|
|
|
25
25
|
|
|
26
26
|
export function buildCursorPriceAxisOverlay({
|
|
27
27
|
axisWidth,
|
|
28
|
-
axisSectionWidth,
|
|
29
28
|
height,
|
|
30
29
|
cursorPixelY,
|
|
31
30
|
cursorLabel,
|
|
@@ -40,7 +39,7 @@ export function buildCursorPriceAxisOverlay({
|
|
|
40
39
|
}): CursorPriceAxisOverlay {
|
|
41
40
|
const labelText = cursorLabel === null
|
|
42
41
|
? null
|
|
43
|
-
: formatAxisCell(cursorLabel, axisWidth).
|
|
42
|
+
: formatAxisCell(cursorLabel, axisWidth).trimStart();
|
|
44
43
|
const pixelHeight = Math.max(height * cellHeightPx, 1);
|
|
45
44
|
let topPercent: number | null = null;
|
|
46
45
|
|
|
@@ -73,6 +72,20 @@ export function PriceAxisLabels({
|
|
|
73
72
|
cellHeightPx,
|
|
74
73
|
}), [axisSectionWidth, axisWidth, cellHeightPx, cursorLabel, cursorPixelY, height]);
|
|
75
74
|
const usePixelOverlay = fractionalViewport && overlay.labelText !== null && overlay.topPercent !== null;
|
|
75
|
+
const axisPaddingWidth = Math.max(0, axisSectionWidth - axisWidth);
|
|
76
|
+
const axisLabelJustify = fractionalViewport ? "flex-start" : "flex-end";
|
|
77
|
+
const renderAxisLabel = (label: string | null, fg: string) => (
|
|
78
|
+
fractionalViewport ? (
|
|
79
|
+
<Box flexDirection="row" width={axisSectionWidth} height={1}>
|
|
80
|
+
<Box flexDirection="row" width={axisWidth} justifyContent={axisLabelJustify} overflow="hidden">
|
|
81
|
+
{label ? <Text fg={fg}>{formatAxisCell(label, axisWidth).trimStart()}</Text> : null}
|
|
82
|
+
</Box>
|
|
83
|
+
{axisPaddingWidth > 0 ? <Box width={axisPaddingWidth} /> : null}
|
|
84
|
+
</Box>
|
|
85
|
+
) : (
|
|
86
|
+
<Text fg={fg}>{formatAxisCell(label, axisWidth).padEnd(axisSectionWidth)}</Text>
|
|
87
|
+
)
|
|
88
|
+
);
|
|
76
89
|
|
|
77
90
|
return (
|
|
78
91
|
<Box
|
|
@@ -86,16 +99,16 @@ export function PriceAxisLabels({
|
|
|
86
99
|
const isCursorRow = !usePixelOverlay && cursorLabel !== null && cursorRow === row;
|
|
87
100
|
const label = isCursorRow ? cursorLabel : (axisLabels.get(row) ?? null);
|
|
88
101
|
return (
|
|
89
|
-
<
|
|
90
|
-
{
|
|
91
|
-
</
|
|
102
|
+
<Box key={row} height={1}>
|
|
103
|
+
{renderAxisLabel(label, isCursorRow ? cursorColor : colors.textDim)}
|
|
104
|
+
</Box>
|
|
92
105
|
);
|
|
93
106
|
})}
|
|
94
107
|
{usePixelOverlay ? (
|
|
95
|
-
<
|
|
108
|
+
<Box
|
|
96
109
|
width={axisSectionWidth}
|
|
97
|
-
fg={cursorColor}
|
|
98
110
|
bg={colors.bg}
|
|
111
|
+
flexDirection="row"
|
|
99
112
|
style={{
|
|
100
113
|
position: "absolute",
|
|
101
114
|
left: 0,
|
|
@@ -106,8 +119,11 @@ export function PriceAxisLabels({
|
|
|
106
119
|
zIndex: 1,
|
|
107
120
|
}}
|
|
108
121
|
>
|
|
109
|
-
{
|
|
110
|
-
|
|
122
|
+
<Box flexDirection="row" width={axisWidth} justifyContent={axisLabelJustify} overflow="hidden">
|
|
123
|
+
<Text fg={cursorColor}>{overlay.labelText}</Text>
|
|
124
|
+
</Box>
|
|
125
|
+
{axisPaddingWidth > 0 ? <Box width={axisPaddingWidth} /> : null}
|
|
126
|
+
</Box>
|
|
111
127
|
) : null}
|
|
112
128
|
</Box>
|
|
113
129
|
);
|
|
@@ -382,7 +382,7 @@ describe("StockChart renderer switching", () => {
|
|
|
382
382
|
const manager = getNativeSurfaceManager(testSetup.renderer as never) as unknown as {
|
|
383
383
|
surfaces: Map<string, { snapshot: { rect: { height: number } } }>;
|
|
384
384
|
};
|
|
385
|
-
expect(manager.surfaces.get("chart-surface:ticker-detail:test:full:base")?.snapshot.rect.height).toBe(
|
|
385
|
+
expect(manager.surfaces.get("chart-surface:ticker-detail:test:full:base")?.snapshot.rect.height).toBe(12);
|
|
386
386
|
});
|
|
387
387
|
|
|
388
388
|
test("moves candle OHLC summary into the pane footer", async () => {
|