gloomberb 0.10.4 → 0.11.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/LICENSE +1 -1
- package/README.md +30 -3
- package/README.zh-CN.md +1 -1
- package/package.json +5 -5
- package/src/api-client/auth.ts +34 -0
- package/src/api-client/data.ts +25 -0
- package/src/api-client/index.test.ts +81 -0
- package/src/api-client/index.ts +37 -0
- package/src/api-client/request.test.ts +30 -0
- package/src/api-client/request.ts +44 -20
- package/src/api-client/socket-health.test.ts +79 -0
- package/src/api-client/socket.ts +108 -5
- package/src/api-client/types.ts +139 -0
- package/src/app/global-shortcuts.test.tsx +144 -9
- package/src/app/global-shortcuts.ts +33 -11
- package/src/app/runtime/broker-import.ts +39 -5
- package/src/app/runtime/startup.ts +7 -1
- package/src/app/runtime/update.ts +26 -1
- package/src/app.tsx +36 -19
- package/src/brokers/broker-ticker-sync.ts +3 -8
- package/src/brokers/sync-broker-instance.test.ts +59 -0
- package/src/brokers/sync-broker-instance.ts +37 -13
- package/src/capabilities/chart-series.ts +318 -0
- package/src/capabilities/factories.ts +46 -0
- package/src/capabilities/index.ts +1 -0
- package/src/capabilities/registry.test.ts +135 -0
- package/src/capabilities/registry.ts +31 -4
- package/src/capabilities/types.ts +52 -0
- package/src/cli/commands/overview.ts +2 -2
- package/src/cli/desktop-pane-shot.ts +2 -0
- package/src/cli/entry.ts +11 -1
- package/src/cli/native-smoke.ts +5 -0
- package/src/cli/pane-functions/catalog.ts +4 -0
- package/src/cli/pane-functions/data.ts +6 -1
- package/src/cli/pane-functions/discovery.ts +76 -10
- package/src/cli/pane-functions/report.test.ts +76 -0
- package/src/cli/pane-functions/report.ts +7 -1
- package/src/cli/pane-functions/resolver.ts +4 -1
- package/src/cli/pane-functions/screenshot.test.ts +56 -3
- package/src/cli/pane-functions/screenshot.ts +68 -5
- package/src/components/chart/composite/composite-chart.test.tsx +54 -0
- package/src/components/chart/composite/composite-chart.tsx +92 -15
- package/src/components/chart/composite/downsample.test.ts +136 -0
- package/src/components/chart/composite/downsample.ts +208 -0
- package/src/components/chart/composite/panel-series.test.ts +66 -0
- package/src/components/chart/composite/panel-series.ts +101 -0
- package/src/components/chart/multi-line/renderer.ts +1 -0
- package/src/components/chart/native/kitty/index.test.ts +25 -3
- package/src/components/chart/native/kitty/support.ts +37 -3
- package/src/components/chart/native/use-chart-text-fallback.ts +19 -0
- package/src/components/chart/static/chart/surface.tsx +43 -12
- package/src/components/chart/static/multi-line-chart-surface.tsx +41 -5
- package/src/components/command-bar/commands/direct/index.ts +18 -0
- package/src/components/command-bar/commands/registry.ts +14 -0
- package/src/components/command-bar/routes/root/article-results.test.ts +83 -0
- package/src/components/command-bar/routes/root/article-results.ts +55 -0
- package/src/components/command-bar/routes/root/command-items.ts +3 -0
- package/src/components/command-bar/routes/root/provider-search.ts +7 -3
- package/src/components/command-bar/routes/root/results.ts +38 -11
- package/src/components/command-bar/routes/root/runtime.ts +6 -2
- package/src/components/command-bar/routes/ticker-search/actions.ts +1 -1
- package/src/components/command-bar/routes/ticker-search/results.test.ts +34 -0
- package/src/components/command-bar/routes/ticker-search/results.ts +4 -6
- package/src/components/command-bar/routes/ticker-search/route.ts +2 -2
- package/src/components/command-bar/routing/list-state.ts +2 -1
- package/src/components/command-bar/surface/assist.test.tsx +13 -6
- package/src/components/command-bar/surface/index.test.tsx +65 -0
- package/src/components/command-bar/surface/index.tsx +35 -0
- package/src/components/command-bar/view-model.test.ts +43 -0
- package/src/components/command-bar/view-model.ts +41 -4
- package/src/components/command-bar/workflow/collection-actions.test.ts +94 -0
- package/src/components/command-bar/workflow/collection-actions.ts +14 -3
- package/src/components/command-bar/workflow/ops.test.ts +136 -0
- package/src/components/command-bar/workflow/ops.ts +71 -1
- package/src/components/index.ts +1 -1
- package/src/components/layout/header.test.tsx +35 -0
- package/src/components/layout/header.tsx +101 -9
- package/src/components/layout/pane/cycle-order.test.ts +0 -1
- package/src/components/layout/pane/footer/index.test.tsx +19 -19
- package/src/components/layout/pane/header.tsx +2 -13
- package/src/components/layout/shell/index.tsx +3 -0
- package/src/components/layout/status-bar.test.tsx +36 -1
- package/src/components/layout/status-bar.tsx +48 -2
- package/src/components/onboarding/account-step/chooser-panel.tsx +27 -50
- package/src/components/onboarding/account-step/form-panel.tsx +85 -29
- package/src/components/onboarding/account-step/index.tsx +10 -1
- package/src/components/onboarding/account-step/model.ts +3 -3
- package/src/components/onboarding/account-step/qr-panel.tsx +68 -0
- package/src/components/onboarding/account-step/signed-in-panel.tsx +4 -7
- package/src/components/onboarding/onboarding-frame.tsx +517 -0
- package/src/components/onboarding/onboarding-steps.tsx +0 -264
- package/src/components/onboarding/onboarding-wizard.test.tsx +626 -200
- package/src/components/onboarding/onboarding-wizard.tsx +848 -266
- package/src/components/onboarding/portfolio-step/broker-fields-panel.tsx +81 -51
- package/src/components/onboarding/portfolio-step/broker-setup-panel.tsx +14 -20
- package/src/components/onboarding/portfolio-step/broker-sync-panel.tsx +8 -11
- package/src/components/onboarding/portfolio-step/choose-panel.tsx +18 -22
- package/src/components/onboarding/portfolio-step/index.tsx +3 -0
- package/src/components/onboarding/portfolio-step/types.ts +3 -0
- package/src/components/onboarding/wizard-account.ts +38 -3
- package/src/components/onboarding/wizard-broker-sync.ts +45 -67
- package/src/components/onboarding/wizard-model.ts +18 -2
- package/src/components/ui/data-table/opentui/index.tsx +29 -9
- package/src/components/ui/data-table/types.ts +2 -0
- package/src/components/ui/external-link.tsx +4 -2
- package/src/components/ui/loading.tsx +1 -1
- package/src/components/ui/status.tsx +76 -4
- package/src/components/ui/table-layout.test.ts +24 -0
- package/src/components/ui/table-layout.ts +90 -23
- package/src/components/ui/tabs.tsx +86 -4
- package/src/core/app-services.ts +14 -3
- package/src/core/connection-health.test.ts +143 -0
- package/src/core/connection-health.ts +295 -0
- package/src/core/state/app/layout-reducer.ts +34 -0
- package/src/core/state/app/layout.ts +21 -0
- package/src/core/state/app/state.ts +10 -0
- package/src/core/state/app/types.ts +7 -1
- package/src/data/config/chart-settings.ts +11 -2
- package/src/data/config/store/index.test.ts +83 -9
- package/src/data/config/store/migrations.ts +16 -0
- package/src/data/config/store/normalize.ts +59 -1
- package/src/data/fred-series.ts +8 -2
- package/src/i18n/es.ts +126 -0
- package/src/i18n/ja.ts +126 -0
- package/src/i18n/ko.ts +126 -0
- package/src/i18n/zh-cn.ts +126 -0
- package/src/i18n/zh-tw.ts +126 -0
- package/src/market-data/coordinator/chart.ts +3 -2
- package/src/market-data/hooks.tsx +3 -2
- package/src/market-data/market/status.test.ts +12 -0
- package/src/market-data/market/status.ts +34 -17
- package/src/news/aggregator.test.ts +39 -0
- package/src/news/aggregator.ts +78 -22
- package/src/news/hooks.ts +4 -0
- package/src/notifications/app-notifier.test.ts +28 -6
- package/src/notifications/app-notifier.ts +24 -5
- package/src/plugins/broker-sync/broker-sync.test.ts +130 -0
- package/src/plugins/broker-sync/native-loader.ts +10 -0
- package/src/plugins/broker-sync/normalize.ts +271 -0
- package/src/plugins/broker-sync/public.ts +122 -0
- package/src/plugins/broker-sync/robinhood-native.ts +367 -0
- package/src/plugins/broker-sync/robinhood.ts +114 -0
- package/src/plugins/broker-sync/simplefin-native.ts +201 -0
- package/src/plugins/broker-sync/simplefin.ts +70 -0
- package/src/plugins/builtin/account-management/footer.ts +8 -19
- package/src/plugins/builtin/account-management/pane.tsx +13 -8
- package/src/plugins/builtin/ai/pi/host.ts +6 -5
- package/src/plugins/builtin/ai/providers.ts +1 -1
- package/src/plugins/builtin/ai/screener/footer.ts +0 -7
- package/src/plugins/builtin/ai/screener/pane.test.tsx +2 -1
- package/src/plugins/builtin/ai/screener/pane.tsx +3 -1
- package/src/plugins/builtin/ai/screener/results-view.tsx +8 -0
- package/src/plugins/builtin/ai/screener/runner.ts +7 -3
- package/src/plugins/builtin/ai/workspace/pane.tsx +13 -2
- package/src/plugins/builtin/alerts/alert-engine.ts +13 -0
- package/src/plugins/builtin/alerts/constants.ts +2 -1
- package/src/plugins/builtin/alerts/index.tsx +67 -36
- package/src/plugins/builtin/alerts/pane.tsx +17 -65
- package/src/plugins/builtin/analytics/index.tsx +24 -11
- package/src/plugins/builtin/analytics/pane-model.ts +48 -14
- package/src/plugins/builtin/analytics/portfolio-selection.ts +19 -0
- package/src/plugins/builtin/analytics/view.tsx +5 -3
- package/src/plugins/builtin/broker-manager/detail.tsx +7 -2
- package/src/plugins/builtin/broker-manager/footer.ts +4 -9
- package/src/plugins/builtin/broker-manager/index.tsx +1 -6
- package/src/plugins/builtin/buildout/detail/ui.tsx +15 -4
- package/src/plugins/builtin/buildout/pane/header.tsx +1 -0
- package/src/plugins/builtin/changelog/index.tsx +60 -45
- package/src/plugins/builtin/chart-composer/catalog-inventory.test.ts +97 -0
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +366 -0
- package/src/plugins/builtin/chart-composer/chart-spec.ts +21 -1
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +307 -0
- package/src/plugins/builtin/chart-composer/editor/controller.ts +1 -1
- package/src/plugins/builtin/chart-composer/index.tsx +45 -5
- package/src/plugins/builtin/chart-composer/pane.tsx +7 -88
- package/src/plugins/builtin/chart-composer/presets.test.ts +48 -0
- package/src/plugins/builtin/chart-composer/presets.ts +69 -3
- package/src/plugins/builtin/chart-composer/quick-add.tsx +8 -5
- package/src/plugins/builtin/chart-composer/semantic.ts +18 -14
- package/src/plugins/builtin/chart-composer/series-catalog.test.ts +39 -0
- package/src/plugins/builtin/chart-composer/series-catalog.ts +104 -3
- package/src/plugins/builtin/chart-composer/shortcuts.test.ts +10 -10
- package/src/plugins/builtin/chart-composer/shortcuts.ts +4 -12
- package/src/plugins/builtin/chart-composer/use-series-catalog.ts +230 -23
- package/src/plugins/builtin/chat/chat.test.tsx +22 -0
- package/src/plugins/builtin/chat/content/composer.tsx +6 -4
- package/src/plugins/builtin/chat/content/index.tsx +7 -0
- package/src/plugins/builtin/chat/content/snapshot.ts +9 -2
- package/src/plugins/builtin/chat/content/transcript.tsx +10 -0
- package/src/plugins/builtin/chat/controller/channel-actions.ts +11 -1
- package/src/plugins/builtin/chat/controller/channels.ts +13 -3
- package/src/plugins/builtin/chat/controller/index.ts +61 -7
- package/src/plugins/builtin/chat/controller/lifecycle.ts +3 -0
- package/src/plugins/builtin/chat/controller/message-loading.ts +5 -1
- package/src/plugins/builtin/chat/controller/messages.ts +3 -1
- package/src/plugins/builtin/chat/controller/notifications.ts +32 -11
- package/src/plugins/builtin/chat/controller/state.ts +7 -0
- package/src/plugins/builtin/chat/controller/view.ts +1 -0
- package/src/plugins/builtin/chat/controller.test.ts +117 -28
- package/src/plugins/builtin/cloud/auth-commands.ts +18 -0
- package/src/plugins/builtin/cloud/device-signin-dialog.tsx +243 -0
- package/src/plugins/builtin/cloud/device-signin.test.ts +217 -0
- package/src/plugins/builtin/cloud/device-signin.ts +240 -0
- package/src/plugins/builtin/cloud/plugin.tsx +7 -1
- package/src/plugins/builtin/cloud-tweets/table.tsx +57 -10
- package/src/plugins/builtin/composite-plugins.ts +39 -5
- package/src/plugins/builtin/congress-trades/detail.tsx +2 -4
- package/src/plugins/builtin/congress-trades/footer.ts +7 -16
- package/src/plugins/builtin/congress-trades/pane.tsx +12 -20
- package/src/plugins/builtin/connections/index.ts +53 -0
- package/src/plugins/builtin/connections/pane.test.tsx +79 -0
- package/src/plugins/builtin/connections/pane.tsx +258 -0
- package/src/plugins/builtin/correlation/index.test.tsx +2 -1
- package/src/plugins/builtin/correlation/index.tsx +58 -19
- package/src/plugins/builtin/correlation/matrix/model.ts +18 -7
- package/src/plugins/builtin/correlation/relationship/pane.tsx +43 -19
- package/src/plugins/builtin/correlation/settings.ts +1 -1
- package/src/plugins/builtin/credit-conditions/client.test.ts +80 -0
- package/src/plugins/builtin/credit-conditions/client.ts +105 -0
- package/src/plugins/builtin/credit-conditions/index.test.tsx +111 -0
- package/src/plugins/builtin/credit-conditions/index.tsx +227 -0
- package/src/plugins/builtin/credit-conditions/model.test.ts +43 -0
- package/src/plugins/builtin/credit-conditions/model.ts +64 -0
- package/src/plugins/builtin/debug/index.tsx +11 -6
- package/src/plugins/builtin/dividend-yield/client.test.ts +58 -0
- package/src/plugins/builtin/dividend-yield/client.ts +252 -0
- package/src/plugins/builtin/dividend-yield/index.tsx +61 -0
- package/src/plugins/builtin/dividend-yield/model.ts +83 -0
- package/src/plugins/builtin/dividend-yield/pane.tsx +305 -0
- package/src/plugins/builtin/dividend-yield/types.ts +22 -0
- package/src/plugins/builtin/earnings/data/cache.test.ts +10 -7
- package/src/plugins/builtin/earnings/data/cache.ts +23 -9
- package/src/plugins/builtin/earnings/index.tsx +57 -6
- package/src/plugins/builtin/econ/calendar-model.test.ts +37 -3
- package/src/plugins/builtin/econ/calendar-model.ts +32 -26
- package/src/plugins/builtin/econ/fred-cache.test.ts +8 -1
- package/src/plugins/builtin/econ/fred-series-map.ts +18 -0
- package/src/plugins/builtin/econ/index.test.tsx +105 -0
- package/src/plugins/builtin/econ/index.tsx +95 -52
- package/src/plugins/builtin/fear-greed/cache.test.ts +18 -3
- package/src/plugins/builtin/fear-greed/cache.ts +18 -6
- package/src/plugins/builtin/fear-greed/charts.tsx +9 -2
- package/src/plugins/builtin/fear-greed/format.ts +0 -9
- package/src/plugins/builtin/fear-greed/pane.tsx +21 -21
- package/src/plugins/builtin/futures/contracts.ts +100 -0
- package/src/plugins/builtin/futures/index.tsx +298 -0
- package/src/plugins/builtin/futures/model.test.ts +129 -0
- package/src/plugins/builtin/futures/model.ts +136 -0
- package/src/plugins/builtin/futures/pane.test.tsx +239 -0
- package/src/plugins/builtin/futures/table.test.ts +155 -0
- package/src/plugins/builtin/futures/table.tsx +241 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +160 -134
- package/src/plugins/builtin/fx-matrix/pairs.ts +12 -1
- package/src/plugins/builtin/fx-matrix/pane.test.tsx +109 -0
- package/src/plugins/builtin/help/components.tsx +1 -30
- package/src/plugins/builtin/help/index.tsx +5 -25
- package/src/plugins/builtin/holders/pane.tsx +17 -12
- package/src/plugins/builtin/holders/treemap.tsx +4 -2
- package/src/plugins/builtin/insider/index.tsx +20 -10
- package/src/plugins/builtin/ipo-calendar/cache.ts +101 -0
- package/src/plugins/builtin/ipo-calendar/client.test.ts +31 -0
- package/src/plugins/builtin/ipo-calendar/client.ts +296 -0
- package/src/plugins/builtin/ipo-calendar/index.tsx +65 -0
- package/src/plugins/builtin/ipo-calendar/model.ts +145 -0
- package/src/plugins/builtin/ipo-calendar/pane.tsx +328 -0
- package/src/plugins/builtin/ipo-calendar/types.ts +19 -0
- package/src/plugins/builtin/kelly-sizer/index.test.tsx +3 -3
- package/src/plugins/builtin/kelly-sizer/model.ts +8 -1
- package/src/plugins/builtin/kelly-sizer/pane.tsx +33 -15
- package/src/plugins/builtin/kelly-sizer/view.tsx +12 -5
- package/src/plugins/builtin/market-halts/client.ts +114 -0
- package/src/plugins/builtin/market-halts/index.tsx +29 -0
- package/src/plugins/builtin/market-halts/model.test.ts +187 -0
- package/src/plugins/builtin/market-halts/model.ts +291 -0
- package/src/plugins/builtin/market-halts/pane.tsx +256 -0
- package/src/plugins/builtin/market-heatmap/data.test.ts +1 -0
- package/src/plugins/builtin/market-heatmap/data.ts +12 -3
- package/src/plugins/builtin/market-heatmap/index.tsx +131 -46
- package/src/plugins/builtin/market-movers/index.tsx +145 -63
- package/src/plugins/builtin/market-movers/model.ts +19 -3
- package/src/plugins/builtin/market-movers/screener.test.ts +108 -0
- package/src/plugins/builtin/market-movers/screener.ts +168 -10
- package/src/plugins/builtin/news/index.tsx +78 -99
- package/src/plugins/builtin/news/wire/article-search.test.ts +72 -0
- package/src/plugins/builtin/news/wire/article-search.ts +140 -0
- package/src/plugins/builtin/news/wire/breaking/pane.tsx +18 -8
- package/src/plugins/builtin/news/wire/categories.ts +35 -0
- package/src/plugins/builtin/news/wire/default-feeds.ts +47 -1
- package/src/plugins/builtin/news/wire/feed-config.test.ts +10 -5
- package/src/plugins/builtin/news/wire/index.tsx +41 -4
- package/src/plugins/builtin/news/wire/industry-pane.tsx +38 -19
- package/src/plugins/builtin/news/wire/news/detail-view.tsx +10 -5
- package/src/plugins/builtin/news/wire/news/footer.ts +1 -0
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +15 -6
- package/src/plugins/builtin/news/wire/news/query-presets.ts +16 -2
- package/src/plugins/builtin/news/wire/news/table.test.tsx +54 -0
- package/src/plugins/builtin/news/wire/news/table.tsx +111 -17
- package/src/plugins/builtin/notes/files.test.ts +18 -0
- package/src/plugins/builtin/notes/files.ts +10 -4
- package/src/plugins/builtin/notes/index.tsx +4 -1
- package/src/plugins/builtin/notes/quick-notes-pane.test.tsx +182 -0
- package/src/plugins/builtin/notes/quick-notes-pane.tsx +74 -31
- package/src/plugins/builtin/notes/ticker-notes-tab.test.tsx +380 -0
- package/src/plugins/builtin/notes/ticker-notes-tab.tsx +97 -40
- package/src/plugins/builtin/options/calc-seed.test.ts +118 -0
- package/src/plugins/builtin/options/calc-seed.ts +52 -0
- package/src/plugins/builtin/options/footer.ts +4 -1
- package/src/plugins/builtin/options/index.tsx +22 -1
- package/src/plugins/builtin/options/live-quotes.ts +8 -0
- package/src/plugins/builtin/options/table.ts +3 -1
- package/src/plugins/builtin/options/view.test.tsx +5 -1
- package/src/plugins/builtin/options/view.tsx +112 -17
- package/src/plugins/builtin/options-calculator/index.tsx +35 -0
- package/src/plugins/builtin/options-calculator/model.test.ts +172 -0
- package/src/plugins/builtin/options-calculator/model.ts +273 -0
- package/src/plugins/builtin/options-calculator/pane.test.tsx +138 -0
- package/src/plugins/builtin/options-calculator/pane.tsx +218 -0
- package/src/plugins/builtin/plugin-module.test.ts +16 -2
- package/src/plugins/builtin/plugin-module.ts +5 -1
- package/src/plugins/builtin/polls/client.ts +57 -0
- package/src/plugins/builtin/polls/hooks.ts +20 -0
- package/src/plugins/builtin/polls/index.tsx +35 -0
- package/src/plugins/builtin/polls/normalize.test.ts +230 -0
- package/src/plugins/builtin/polls/normalize.ts +319 -0
- package/src/plugins/builtin/polls/pane.tsx +722 -0
- package/src/plugins/builtin/polls/types.ts +77 -0
- package/src/plugins/builtin/portfolio-list/header.tsx +15 -4
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +7 -3
- package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +1 -1
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -0
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +2 -1
- package/src/plugins/builtin/research/analyst-pane.tsx +40 -67
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +25 -19
- package/src/plugins/builtin/research/index.test.ts +6 -25
- package/src/plugins/builtin/research/index.tsx +11 -5
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +86 -26
- package/src/plugins/builtin/scanner/denied.tsx +31 -0
- package/src/plugins/builtin/scanner/feed.ts +94 -0
- package/src/plugins/builtin/scanner/flow-model.test.ts +85 -0
- package/src/plugins/builtin/scanner/flow-model.ts +133 -0
- package/src/plugins/builtin/scanner/flow-pane.tsx +197 -0
- package/src/plugins/builtin/scanner/hilo-bars.tsx +121 -0
- package/src/plugins/builtin/scanner/hilo-model.test.ts +44 -0
- package/src/plugins/builtin/scanner/hilo-model.ts +75 -0
- package/src/plugins/builtin/scanner/hilo-pane.tsx +165 -0
- package/src/plugins/builtin/scanner/index.ts +109 -0
- package/src/plugins/builtin/scanner/waiting.tsx +11 -0
- package/src/plugins/builtin/sec/index.tsx +11 -7
- package/src/plugins/builtin/sectors/index.tsx +105 -81
- package/src/plugins/builtin/sectors/move-bar.tsx +43 -0
- package/src/plugins/builtin/sectors/sector-data.ts +18 -0
- package/src/plugins/builtin/sectors/sector-model.ts +13 -15
- package/src/plugins/builtin/shared/auto-refresh.ts +51 -0
- package/src/plugins/builtin/shared/cloud-upgrade.ts +29 -5
- package/src/plugins/builtin/shared/pane-footer.ts +16 -3
- package/src/plugins/builtin/shared/screener-live-quotes.test.ts +91 -0
- package/src/plugins/builtin/shared/screener-live-quotes.ts +134 -0
- package/src/plugins/builtin/shared/table-pane.ts +1 -5
- package/src/plugins/builtin/shared/use-quote-board.test.tsx +211 -0
- package/src/plugins/builtin/shared/use-quote-board.ts +195 -0
- package/src/plugins/builtin/short-interest/client.ts +124 -0
- package/src/plugins/builtin/short-interest/index.tsx +62 -0
- package/src/plugins/builtin/short-interest/model.ts +113 -0
- package/src/plugins/builtin/short-interest/pane.tsx +252 -0
- package/src/plugins/builtin/short-interest/types.ts +9 -0
- package/src/plugins/builtin/substack/article-detail.tsx +15 -1
- package/src/plugins/builtin/substack/pane-footer.ts +3 -22
- package/src/plugins/builtin/substack/pane.tsx +1 -2
- package/src/plugins/builtin/thirteenf/api.ts +4 -1
- package/src/plugins/builtin/thirteenf/model.ts +2 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +6 -2
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +10 -4
- package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +67 -11
- package/src/plugins/builtin/ticker-detail/financials/tab.tsx +15 -8
- package/src/plugins/builtin/ticker-detail/index.tsx +5 -21
- package/src/plugins/builtin/ticker-detail/overview/components.tsx +72 -32
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +0 -10
- package/src/plugins/builtin/ticker-detail/pane.tsx +4 -1
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.test.tsx +70 -0
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +34 -26
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +96 -49
- package/src/plugins/builtin/ticker-detail/research-tabs.ts +32 -0
- package/src/plugins/builtin/ticker-research-backend-plugin.ts +2 -9
- package/src/plugins/builtin/ticker-research-plugin.ts +6 -0
- package/src/plugins/builtin/treasury-auctions/cache.test.ts +173 -0
- package/src/plugins/builtin/treasury-auctions/cache.ts +104 -0
- package/src/plugins/builtin/treasury-auctions/client.test.ts +181 -0
- package/src/plugins/builtin/treasury-auctions/client.ts +151 -0
- package/src/plugins/builtin/treasury-auctions/index.tsx +90 -0
- package/src/plugins/builtin/treasury-auctions/model.test.ts +147 -0
- package/src/plugins/builtin/treasury-auctions/model.ts +188 -0
- package/src/plugins/builtin/treasury-auctions/pane.test.tsx +170 -0
- package/src/plugins/builtin/treasury-auctions/pane.tsx +417 -0
- package/src/plugins/builtin/treasury-auctions/types.ts +59 -0
- package/src/plugins/builtin/tv/pane.tsx +67 -7
- package/src/plugins/builtin/tv/youtube-embed.test.ts +84 -0
- package/src/plugins/builtin/tv/youtube-embed.ts +173 -0
- package/src/plugins/builtin/tv/youtube-stream.ts +27 -2
- package/src/plugins/builtin/volatility/client.ts +87 -0
- package/src/plugins/builtin/volatility/index.test.tsx +107 -0
- package/src/plugins/builtin/volatility/index.tsx +193 -0
- package/src/plugins/builtin/volatility/model.test.ts +66 -0
- package/src/plugins/builtin/volatility/model.ts +113 -0
- package/src/plugins/builtin/world-indices/footer.ts +43 -23
- package/src/plugins/builtin/world-indices/index.tsx +44 -93
- package/src/plugins/builtin/world-indices/indices.ts +11 -2
- package/src/plugins/builtin/world-indices/model.ts +17 -24
- package/src/plugins/builtin/world-indices/pane.test.tsx +116 -0
- package/src/plugins/builtin/world-indices/table.tsx +82 -37
- package/src/plugins/builtin/yield-curve/index.tsx +16 -7
- package/src/plugins/builtin/yield-curve/treasury-data.test.ts +25 -0
- package/src/plugins/builtin/yield-curve/treasury-data.ts +16 -0
- package/src/plugins/catalog-backend.ts +8 -0
- package/src/plugins/catalog-ui.ts +8 -0
- package/src/plugins/event-bus.ts +1 -0
- package/src/plugins/ibkr/gateway/account-loaders.ts +1 -1
- package/src/plugins/ibkr/trade/tab/footer.ts +9 -23
- package/src/plugins/ibkr/trade/tab/index.tsx +1 -5
- package/src/plugins/ibkr/trading/pane-view.tsx +149 -87
- package/src/plugins/ibkr/trading/pane.tsx +2 -2
- package/src/plugins/ownership.ts +3 -0
- package/src/plugins/pane-manager/floating.ts +8 -2
- package/src/plugins/pane-manager.test.ts +13 -0
- package/src/plugins/prediction-markets/backend-plugin.ts +2 -0
- package/src/plugins/prediction-markets/capability.test.ts +281 -0
- package/src/plugins/prediction-markets/capability.ts +171 -0
- package/src/plugins/prediction-markets/chart.tsx +18 -26
- package/src/plugins/prediction-markets/columns.ts +3 -2
- package/src/plugins/prediction-markets/controller/catalog.ts +15 -12
- package/src/plugins/prediction-markets/controller/detail.ts +4 -0
- package/src/plugins/prediction-markets/controller/index.ts +0 -15
- package/src/plugins/prediction-markets/detail/book.tsx +3 -23
- package/src/plugins/prediction-markets/detail/overview.tsx +0 -18
- package/src/plugins/prediction-markets/detail/pane.tsx +36 -12
- package/src/plugins/prediction-markets/detail.test.tsx +1 -1
- package/src/plugins/prediction-markets/index.tsx +2 -0
- package/src/plugins/prediction-markets/metrics.ts +28 -18
- package/src/plugins/prediction-markets/navigation.ts +1 -0
- package/src/plugins/prediction-markets/pane.test.tsx +1 -1
- package/src/plugins/prediction-markets/pane.tsx +0 -1
- package/src/plugins/prediction-markets/services/fetch.ts +3 -2
- package/src/plugins/prediction-markets/services/kalshi/adapter.ts +82 -23
- package/src/plugins/prediction-markets/services/kalshi/normalize.ts +3 -1
- package/src/plugins/prediction-markets/services/kalshi/types.ts +14 -0
- package/src/plugins/prediction-markets/services/polymarket/adapter.ts +26 -14
- package/src/plugins/prediction-markets/services/polymarket/detail.ts +67 -18
- package/src/plugins/prediction-markets/test-helpers.tsx +0 -1
- package/src/plugins/prediction-markets/types.ts +3 -9
- package/src/plugins/registry/context.ts +6 -1
- package/src/plugins/registry/index.test.ts +2 -0
- package/src/plugins/registry/index.ts +53 -3
- package/src/plugins/runtime/context.tsx +4 -2
- package/src/plugins/runtime/index.tsx +9 -0
- package/src/remote/controller.test.ts +17 -4
- package/src/renderers/electrobun/bun/application-menu/index.ts +1 -1
- package/src/renderers/electrobun/bun/core-capabilities.ts +2 -2
- package/src/renderers/electrobun/bun/desktop/capability-bridge.test.ts +64 -0
- package/src/renderers/electrobun/bun/desktop/capability-bridge.ts +41 -7
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +5 -2
- package/src/renderers/electrobun/bun/desktop/http-fetch.test.ts +33 -0
- package/src/renderers/electrobun/bun/desktop/http-fetch.ts +53 -6
- package/src/renderers/electrobun/bun/desktop/initialization.test.ts +27 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +6 -1
- package/src/renderers/electrobun/bun/index.ts +1 -0
- package/src/renderers/electrobun/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/app-services.ts +25 -2
- package/src/renderers/electrobun/view/build-assets.ts +5 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +186 -65
- package/src/renderers/electrobun/view/cli-pane-shot-health.test.ts +25 -0
- package/src/renderers/electrobun/view/cli-pane-shot-health.ts +31 -0
- package/src/renderers/electrobun/view/data-table/index.tsx +10 -11
- package/src/renderers/electrobun/view/data-table/row.tsx +2 -0
- package/src/renderers/electrobun/view/desktop/controls.test.tsx +68 -0
- package/src/renderers/electrobun/view/desktop/controls.tsx +25 -1
- package/src/renderers/electrobun/view/host/box.test.tsx +80 -0
- package/src/renderers/electrobun/view/host/media-surface.tsx +70 -3
- package/src/renderers/electrobun/view/host/tabs.tsx +97 -5
- package/src/renderers/electrobun/view/host/text.tsx +5 -2
- package/src/renderers/electrobun/view/input-host.test.tsx +140 -0
- package/src/renderers/electrobun/view/input-host.tsx +4 -2
- package/src/renderers/electrobun/view/native-stubs/backend-rpc.ts +19 -1
- package/src/renderers/electrobun/view/native-stubs/broker-sync-native-loader.ts +7 -0
- package/src/renderers/electrobun/view/native-stubs/chart/kitty-support.ts +10 -0
- package/src/renderers/electrobun/view/remote/capability-invoker.test.ts +46 -0
- package/src/renderers/electrobun/view/remote/capability-invoker.ts +34 -13
- package/src/renderers/electrobun/view/remote/connection-health-backend.ts +19 -0
- package/src/renderers/electrobun/view/remote/connection-health.test.ts +44 -0
- package/src/renderers/electrobun/view/remote/connection-health.ts +49 -0
- package/src/renderers/electrobun/view/styles.css +5 -0
- package/src/renderers/opentui/dialog-host.tsx +279 -80
- package/src/renderers/opentui/host.tsx +15 -1
- package/src/renderers/opentui/terminal-media.test.ts +102 -0
- package/src/renderers/opentui/terminal-media.ts +142 -0
- package/src/renderers/opentui/test-utils.tsx +9 -6
- package/src/renderers/opentui/toast-host.tsx +136 -7
- package/src/renderers/opentui/ui-host.tsx +19 -3
- package/src/sources/provider-router/connection-health.test.ts +28 -0
- package/src/sources/provider-router/connection-health.ts +47 -0
- package/src/sources/provider-router/history.test.ts +53 -4
- package/src/sources/provider-router/history.ts +119 -14
- package/src/sources/provider-router/index.ts +12 -1
- package/src/sources/yahoo-finance/history.ts +2 -9
- package/src/sources/yahoo-finance/http.ts +1 -1
- package/src/sources/yahoo-finance/symbols.ts +1 -1
- package/src/sources/yahoo-finance/types.ts +19 -0
- package/src/state/app/context/index.test.tsx +101 -0
- package/src/state/app/context/index.tsx +10 -0
- package/src/state/config-save-scheduler.ts +1 -6
- package/src/state/persist-scheduler.test.ts +31 -0
- package/src/state/persist-scheduler.ts +21 -8
- package/src/sync/auth-transition.test.ts +36 -0
- package/src/sync/auth-transition.ts +22 -0
- package/src/sync/core-contributors.test.ts +58 -1
- package/src/sync/core-contributors.ts +39 -2
- package/src/sync/react.ts +9 -0
- package/src/test-support/plugin-runtime.ts +3 -0
- package/src/theme/font-scale.test.ts +14 -0
- package/src/theme/font-scale.ts +69 -0
- package/src/tickers/search/index.test.ts +284 -2
- package/src/tickers/search/index.ts +55 -18
- package/src/tickers/search/ranking.ts +143 -18
- package/src/tickers/search/types.ts +1 -0
- package/src/time-series/alignment.ts +15 -11
- package/src/time-series/field-catalog.ts +10 -0
- package/src/time-series/fundamentals.ts +19 -11
- package/src/time-series/hooks.ts +8 -2
- package/src/time-series/parsed-history-cache.ts +35 -0
- package/src/time-series/resolution.ts +10 -0
- package/src/time-series/resolve.test.ts +332 -3
- package/src/time-series/resolve.ts +225 -25
- package/src/time-series/spec-studies.test.ts +23 -0
- package/src/time-series/spec.ts +21 -2
- package/src/time-series/transforms.test.ts +76 -1
- package/src/time-series/transforms.ts +39 -8
- package/src/time-series/types.ts +9 -2
- package/src/time-series/use-chart-resolution.test.tsx +105 -1
- package/src/time-series/use-chart-resolution.ts +67 -13
- package/src/types/config.ts +30 -27
- package/src/types/media.ts +2 -0
- package/src/types/plugin.ts +8 -1
- package/src/ui/host.tsx +4 -0
- package/src/ui/pointer-drag.ts +12 -0
- package/src/ui/qr.test.ts +79 -0
- package/src/ui/qr.ts +82 -0
- package/src/updater/github-releases.test.ts +3 -3
- package/src/updater/github-releases.ts +1 -1
- package/src/updater.test.ts +117 -16
- package/src/updater.ts +32 -5
- package/src/utils/exchanges.ts +1 -0
- package/src/utils/external-url.test.ts +32 -0
- package/src/utils/external-url.ts +23 -0
- package/src/utils/sort-values.ts +31 -0
- package/src/utils/zoned-date-time.ts +67 -0
- package/src/components/onboarding/wizard-keyboard.ts +0 -306
- package/src/plugins/builtin/chart-composer/date-window-dialog.tsx +0 -120
|
@@ -12,11 +12,14 @@ import {
|
|
|
12
12
|
type ScrollBoxRenderable,
|
|
13
13
|
} from "../../../ui";
|
|
14
14
|
import { useShortcut } from "../../../react/input";
|
|
15
|
+
import { useOptionalPaneInstanceId, usePaneSettingValue } from "../../../state/app/context";
|
|
15
16
|
import { colors as themeColors, hoverBg } from "../../../theme/colors";
|
|
16
17
|
import { formatPercentRaw } from "../../../utils/format";
|
|
17
18
|
import { isPlainKey } from "../../../utils/keyboard";
|
|
18
19
|
import { truncateWithEllipsis } from "../../../utils/text-wrap";
|
|
19
20
|
import type { ResolvedSeries } from "../../../time-series/types";
|
|
21
|
+
import { downsampleCompositeChartScene } from "./downsample";
|
|
22
|
+
import { reuseResolvedSeriesList } from "./panel-series";
|
|
20
23
|
import {
|
|
21
24
|
consumeChartMouseEvent,
|
|
22
25
|
getGlobalMouseX,
|
|
@@ -24,6 +27,7 @@ import {
|
|
|
24
27
|
type ChartMouseEvent,
|
|
25
28
|
} from "../core/pointer";
|
|
26
29
|
import type { NativeChartBitmap } from "../native/chart-rasterizer";
|
|
30
|
+
import { useShowChartTextFallback } from "../native/use-chart-text-fallback";
|
|
27
31
|
import {
|
|
28
32
|
useStaticChartBitmapSize,
|
|
29
33
|
type StaticChartBitmapSize,
|
|
@@ -611,6 +615,7 @@ interface CompositePanelSurfaceProps {
|
|
|
611
615
|
onZoomViewport: (zoomFactor: number, anchorRatio: number) => void;
|
|
612
616
|
onSetViewport: (range: CompositeViewportRange) => void;
|
|
613
617
|
onToolSpanChange: (span: ChartToolSpan | null) => void;
|
|
618
|
+
showTextFallback: boolean;
|
|
614
619
|
}
|
|
615
620
|
|
|
616
621
|
function CompositePanelSurface({
|
|
@@ -637,6 +642,7 @@ function CompositePanelSurface({
|
|
|
637
642
|
onZoomViewport,
|
|
638
643
|
onSetViewport,
|
|
639
644
|
onToolSpanChange,
|
|
645
|
+
showTextFallback,
|
|
640
646
|
}: CompositePanelSurfaceProps) {
|
|
641
647
|
const isDesktopWeb = useUiHost().kind === "desktop-web";
|
|
642
648
|
const { cellHeightPx = 18, cellWidthPx = 8 } = useUiCapabilities();
|
|
@@ -787,10 +793,10 @@ function CompositePanelSurface({
|
|
|
787
793
|
toolReadout?.direction,
|
|
788
794
|
]);
|
|
789
795
|
const textLines = useMemo(
|
|
790
|
-
() => isDesktopWeb
|
|
796
|
+
() => isDesktopWeb || !showTextFallback
|
|
791
797
|
? []
|
|
792
798
|
: renderCompositePanelText(panel, plotWidth, scene.cursorXRatio, activeCursorYRatio),
|
|
793
|
-
[activeCursorYRatio, isDesktopWeb, panel, plotWidth, scene.cursorXRatio],
|
|
799
|
+
[activeCursorYRatio, isDesktopWeb, panel, plotWidth, scene.cursorXRatio, showTextFallback],
|
|
794
800
|
);
|
|
795
801
|
const leftAxisLabels = useMemo(
|
|
796
802
|
() => axisLabelRows(
|
|
@@ -1364,6 +1370,52 @@ function CompositeLegend({
|
|
|
1364
1370
|
);
|
|
1365
1371
|
}
|
|
1366
1372
|
|
|
1373
|
+
const CHART_DRAWINGS_SETTING_KEY = "chartDrawings";
|
|
1374
|
+
const NO_DRAWINGS: readonly ChartDrawing[] = [];
|
|
1375
|
+
/** Coalesces a drag into one write instead of one per pointer move. */
|
|
1376
|
+
const DRAWING_PERSIST_DELAY_MS = 400;
|
|
1377
|
+
|
|
1378
|
+
/**
|
|
1379
|
+
* Drawings are anchored to data, so they outlive the mounted chart: they ride
|
|
1380
|
+
* along with the pane settings that already carry the chart spec. Only mounted
|
|
1381
|
+
* inside a pane, so a standalone chart still renders without app state.
|
|
1382
|
+
*/
|
|
1383
|
+
function ChartDrawingStore({
|
|
1384
|
+
paneInstanceId,
|
|
1385
|
+
drawings,
|
|
1386
|
+
onRestore,
|
|
1387
|
+
}: {
|
|
1388
|
+
paneInstanceId: string;
|
|
1389
|
+
drawings: readonly ChartDrawing[];
|
|
1390
|
+
onRestore: (drawings: readonly ChartDrawing[]) => void;
|
|
1391
|
+
}) {
|
|
1392
|
+
const [stored, setStored] = usePaneSettingValue<readonly ChartDrawing[]>(
|
|
1393
|
+
CHART_DRAWINGS_SETTING_KEY,
|
|
1394
|
+
NO_DRAWINGS,
|
|
1395
|
+
paneInstanceId,
|
|
1396
|
+
);
|
|
1397
|
+
const restoredRef = useRef<readonly ChartDrawing[] | null>(null);
|
|
1398
|
+
if (restoredRef.current === null) {
|
|
1399
|
+
restoredRef.current = Array.isArray(stored) ? stored : NO_DRAWINGS;
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
useEffect(() => {
|
|
1403
|
+
const restored = restoredRef.current;
|
|
1404
|
+
if (restored && restored.length > 0) onRestore(restored);
|
|
1405
|
+
// Restoring once on mount: later writes must not scroll back in time.
|
|
1406
|
+
}, []);
|
|
1407
|
+
|
|
1408
|
+
useEffect(() => {
|
|
1409
|
+
const restored = restoredRef.current ?? NO_DRAWINGS;
|
|
1410
|
+
if (drawings === restored) return;
|
|
1411
|
+
if (drawings.length === 0 && restored.length === 0) return;
|
|
1412
|
+
const timer = setTimeout(() => setStored(drawings), DRAWING_PERSIST_DELAY_MS);
|
|
1413
|
+
return () => clearTimeout(timer);
|
|
1414
|
+
}, [drawings, setStored]);
|
|
1415
|
+
|
|
1416
|
+
return null;
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1367
1419
|
export function CompositeChart({
|
|
1368
1420
|
series,
|
|
1369
1421
|
legendSeries,
|
|
@@ -1392,15 +1444,15 @@ export function CompositeChart({
|
|
|
1392
1444
|
onToggleSeries,
|
|
1393
1445
|
isSeriesToggleable,
|
|
1394
1446
|
}: CompositeChartProps) {
|
|
1395
|
-
const { cellWidthPx = 8 } = useUiCapabilities();
|
|
1447
|
+
const { cellWidthPx = 8, pixelRatio = 1 } = useUiCapabilities();
|
|
1396
1448
|
const isDesktopWeb = useUiHost().kind === "desktop-web";
|
|
1449
|
+
const showTextFallback = useShowChartTextFallback();
|
|
1397
1450
|
const [internalCursorDate, setInternalCursorDate] = useState<Date | null>(null);
|
|
1398
1451
|
const [legendKeyboardIndex, setLegendKeyboardIndex] = useState<number | null>(null);
|
|
1399
1452
|
const [toolSpan, setToolSpan] = useState<ChartToolSpan | null>(null);
|
|
1400
1453
|
const [armedTool, setArmedTool] = useState<ChartToolKind | null>(null);
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
const [drawings, setDrawings] = useState<readonly ChartDrawing[]>([]);
|
|
1454
|
+
const paneInstanceId = useOptionalPaneInstanceId();
|
|
1455
|
+
const [drawings, setDrawings] = useState<readonly ChartDrawing[]>(NO_DRAWINGS);
|
|
1404
1456
|
const [selectedDrawingId, setSelectedDrawingId] = useState<string | null>(null);
|
|
1405
1457
|
const [drawColor, setDrawColor] = useState<string>(CHART_DRAWING_COLORS[0]);
|
|
1406
1458
|
const addDrawing = useCallback((drawing: ChartDrawing) => {
|
|
@@ -1433,7 +1485,10 @@ export function CompositeChart({
|
|
|
1433
1485
|
const resolvedCursorDate = cursorDate === undefined ? internalCursorDate : cursorDate;
|
|
1434
1486
|
const totalWidth = Math.max(1, Math.floor(width));
|
|
1435
1487
|
const totalHeight = Math.max(1, Math.floor(height));
|
|
1436
|
-
const
|
|
1488
|
+
const seriesIdentityRef = useRef<ResolvedSeries[]>([]);
|
|
1489
|
+
const stableSeries = reuseResolvedSeriesList(seriesIdentityRef.current, series);
|
|
1490
|
+
seriesIdentityRef.current = stableSeries;
|
|
1491
|
+
const visibleSeries = useMemo(() => stableSeries.filter((entry) => entry.points.length > 0), [stableSeries]);
|
|
1437
1492
|
const marketTimelineSeries = useMemo(() => {
|
|
1438
1493
|
const supplied = timelineSeries?.filter((entry) => entry.points.length > 0) ?? [];
|
|
1439
1494
|
return supplied.some((entry) => entry.timeBasis?.kind === "market")
|
|
@@ -1629,6 +1684,11 @@ export function CompositeChart({
|
|
|
1629
1684
|
: 0;
|
|
1630
1685
|
const timeAxisRows = showTimeAxis ? 1 : 0;
|
|
1631
1686
|
const panelCount = new Set(visibleSeries.map((entry) => entry.panelId)).size;
|
|
1687
|
+
const lastTickKey = visibleSeries.map((entry) => {
|
|
1688
|
+
const last = entry.points.at(-1);
|
|
1689
|
+
if (!last) return entry.id;
|
|
1690
|
+
return `${entry.id}:${last.date.getTime()}:${last.close ?? ""}:${last.value ?? ""}:${entry.latestChangePercent ?? ""}`;
|
|
1691
|
+
}).join("|");
|
|
1632
1692
|
const plotHeight = Math.max(panelCount, totalHeight - legendRows - timeAxisRows);
|
|
1633
1693
|
const resolvedColors = useMemo<CompositeChartColors>(() => ({
|
|
1634
1694
|
background: colors?.background ?? themeColors.bg,
|
|
@@ -1638,12 +1698,16 @@ export function CompositeChart({
|
|
|
1638
1698
|
textDim: colors?.textDim ?? themeColors.textDim,
|
|
1639
1699
|
negative: colors?.negative ?? themeColors.negative,
|
|
1640
1700
|
}), [colors]);
|
|
1641
|
-
const projectedScene = useMemo(() =>
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1701
|
+
const projectedScene = useMemo(() => {
|
|
1702
|
+
// lastTickKey busts this memo when a live tick mutates series identity in place.
|
|
1703
|
+
void lastTickKey;
|
|
1704
|
+
return buildCompositeChartScene(visibleSeries, panels, {
|
|
1705
|
+
width: 1,
|
|
1706
|
+
height: Math.max(panelCount, 1),
|
|
1707
|
+
viewport: effectiveViewport ?? undefined,
|
|
1708
|
+
timelineSeries: marketTimelineSeries,
|
|
1709
|
+
});
|
|
1710
|
+
}, [effectiveViewport, lastTickKey, marketTimelineSeries, panelCount, panels, visibleSeries]);
|
|
1647
1711
|
// Gutters follow the axes the scene actually built. Reading the series list
|
|
1648
1712
|
// instead drops a gutter the moment its series has no observation in view,
|
|
1649
1713
|
// which is exactly what a zoom does, taking the axis labels with it.
|
|
@@ -1673,6 +1737,10 @@ export function CompositeChart({
|
|
|
1673
1737
|
const horizontalReserved = leftAxisWidth + rightAxisWidth
|
|
1674
1738
|
+ axisGap * ((leftAxisWidth ? 1 : 0) + (rightAxisWidth ? 1 : 0));
|
|
1675
1739
|
const plotWidth = Math.max(1, totalWidth - horizontalReserved);
|
|
1740
|
+
const downsampleWidth = Math.max(
|
|
1741
|
+
1,
|
|
1742
|
+
Math.round(plotWidth * cellWidthPx * Math.max(1, pixelRatio)),
|
|
1743
|
+
);
|
|
1676
1744
|
const layoutPanels = useMemo<CompositePanelScene[] | null>(() => {
|
|
1677
1745
|
if (!projectedScene) return null;
|
|
1678
1746
|
const panelSpecById = new Map(panels.map((panel) => [panel.id, panel] as const));
|
|
@@ -1690,13 +1758,14 @@ export function CompositeChart({
|
|
|
1690
1758
|
}, [panels, plotHeight, projectedScene]);
|
|
1691
1759
|
const baseScene = useMemo<CompositeChartScene | null>(() => {
|
|
1692
1760
|
if (!projectedScene || !layoutPanels) return null;
|
|
1693
|
-
|
|
1761
|
+
const laidOut: CompositeChartScene = {
|
|
1694
1762
|
...projectedScene,
|
|
1695
1763
|
width: plotWidth,
|
|
1696
1764
|
height: layoutPanels.reduce((sum, panel) => sum + panel.height, 0),
|
|
1697
1765
|
panels: layoutPanels,
|
|
1698
1766
|
};
|
|
1699
|
-
|
|
1767
|
+
return downsampleCompositeChartScene(laidOut, downsampleWidth);
|
|
1768
|
+
}, [downsampleWidth, layoutPanels, plotWidth, projectedScene]);
|
|
1700
1769
|
const resolvedCursorTimestamp = resolvedCursorDate?.getTime() ?? null;
|
|
1701
1770
|
const normalizedCursorTimestamp = resolvedCursorTimestamp !== null && Number.isFinite(resolvedCursorTimestamp)
|
|
1702
1771
|
? resolvedCursorTimestamp
|
|
@@ -1988,6 +2057,13 @@ export function CompositeChart({
|
|
|
1988
2057
|
}}
|
|
1989
2058
|
/>
|
|
1990
2059
|
) : null}
|
|
2060
|
+
{paneInstanceId ? (
|
|
2061
|
+
<ChartDrawingStore
|
|
2062
|
+
paneInstanceId={paneInstanceId}
|
|
2063
|
+
drawings={drawings}
|
|
2064
|
+
onRestore={setDrawings}
|
|
2065
|
+
/>
|
|
2066
|
+
) : null}
|
|
1991
2067
|
{scene.panels.map((panel) => (
|
|
1992
2068
|
<CompositePanelSurface
|
|
1993
2069
|
key={panel.id}
|
|
@@ -2014,6 +2090,7 @@ export function CompositeChart({
|
|
|
2014
2090
|
onZoomViewport={zoomViewport}
|
|
2015
2091
|
onSetViewport={setViewportRange}
|
|
2016
2092
|
onToolSpanChange={setToolSpan}
|
|
2093
|
+
showTextFallback={showTextFallback}
|
|
2017
2094
|
/>
|
|
2018
2095
|
))}
|
|
2019
2096
|
{timeAxisLayout ? (
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { ResolvedSeries, TimeSeriesPoint } from "../../../time-series/types";
|
|
3
|
+
import {
|
|
4
|
+
downsampleCompositeChartScene,
|
|
5
|
+
downsampleLttb,
|
|
6
|
+
downsampleOhlcProjectedPoints,
|
|
7
|
+
} from "./downsample";
|
|
8
|
+
import { buildCompositeChartScene } from "./scene";
|
|
9
|
+
|
|
10
|
+
function point(date: string, value: number): TimeSeriesPoint {
|
|
11
|
+
const observedAt = new Date(`${date}T00:00:00.000Z`);
|
|
12
|
+
return { date: observedAt, observedAt, value };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function ohlcPoint(
|
|
16
|
+
date: Date,
|
|
17
|
+
open: number,
|
|
18
|
+
high: number,
|
|
19
|
+
low: number,
|
|
20
|
+
close: number,
|
|
21
|
+
): TimeSeriesPoint {
|
|
22
|
+
return {
|
|
23
|
+
date,
|
|
24
|
+
observedAt: date,
|
|
25
|
+
value: close,
|
|
26
|
+
open,
|
|
27
|
+
high,
|
|
28
|
+
low,
|
|
29
|
+
close,
|
|
30
|
+
volume: 1,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function series(
|
|
35
|
+
overrides: Partial<ResolvedSeries> & Pick<ResolvedSeries, "id" | "points">,
|
|
36
|
+
): ResolvedSeries {
|
|
37
|
+
return {
|
|
38
|
+
id: overrides.id,
|
|
39
|
+
label: overrides.label ?? overrides.id,
|
|
40
|
+
color: overrides.color ?? "#00ff66",
|
|
41
|
+
unit: overrides.unit ?? "USD",
|
|
42
|
+
unitGroup: overrides.unitGroup ?? "currency",
|
|
43
|
+
nativeFrequency: overrides.nativeFrequency ?? "daily",
|
|
44
|
+
dataShape: overrides.dataShape ?? "scalar",
|
|
45
|
+
style: overrides.style ?? "line",
|
|
46
|
+
transform: overrides.transform ?? "raw",
|
|
47
|
+
axis: overrides.axis ?? "left",
|
|
48
|
+
panelId: overrides.panelId ?? "main",
|
|
49
|
+
interpolation: overrides.interpolation ?? "none",
|
|
50
|
+
timestampMode: overrides.timestampMode,
|
|
51
|
+
timeBasis: overrides.timeBasis,
|
|
52
|
+
points: overrides.points,
|
|
53
|
+
warning: overrides.warning,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function denseLineSeries(count: number): ResolvedSeries {
|
|
58
|
+
const start = Date.parse("2024-01-01T00:00:00.000Z");
|
|
59
|
+
return series({
|
|
60
|
+
id: "price",
|
|
61
|
+
points: Array.from({ length: count }, (_, index) => {
|
|
62
|
+
const date = new Date(start + index * 86_400_000);
|
|
63
|
+
const value = 100 + Math.sin(index / 8) * 20 + (index % 37 === 0 ? 40 : 0);
|
|
64
|
+
return { date, observedAt: date, value };
|
|
65
|
+
}),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
describe("composite downsample", () => {
|
|
70
|
+
test("caps line series to the pixel width and keeps the shared date list", () => {
|
|
71
|
+
const scene = buildCompositeChartScene(
|
|
72
|
+
[denseLineSeries(400)],
|
|
73
|
+
[{ id: "main" }],
|
|
74
|
+
{ width: 80, height: 12 },
|
|
75
|
+
);
|
|
76
|
+
expect(scene).not.toBeNull();
|
|
77
|
+
expect(scene!.panels[0]!.series[0]!.points.length).toBe(400);
|
|
78
|
+
|
|
79
|
+
const downsampled = downsampleCompositeChartScene(scene!, 64);
|
|
80
|
+
expect(downsampled.dates).toBe(scene!.dates);
|
|
81
|
+
expect(downsampled.dateRatios).toBe(scene!.dateRatios);
|
|
82
|
+
expect(downsampled.dates).toHaveLength(400);
|
|
83
|
+
expect(downsampled.panels[0]!.series[0]!.points.length).toBeLessThanOrEqual(64);
|
|
84
|
+
expect(downsampled.panels[0]!.series[0]!.points.length).toBeGreaterThan(2);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("LTTB never exceeds the requested point budget", () => {
|
|
88
|
+
const scene = buildCompositeChartScene(
|
|
89
|
+
[denseLineSeries(250)],
|
|
90
|
+
[{ id: "main" }],
|
|
91
|
+
{ width: 120, height: 10 },
|
|
92
|
+
)!;
|
|
93
|
+
const points = scene.panels[0]!.series[0]!.points;
|
|
94
|
+
expect(downsampleLttb(points, 40)).toHaveLength(40);
|
|
95
|
+
expect(downsampleLttb(points, points.length)).toBe(points);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("OHLC buckets preserve the viewport high and low", () => {
|
|
99
|
+
const start = Date.parse("2024-01-01T00:00:00.000Z");
|
|
100
|
+
const candles = series({
|
|
101
|
+
id: "ohlc",
|
|
102
|
+
dataShape: "ohlcv",
|
|
103
|
+
style: "candles",
|
|
104
|
+
points: Array.from({ length: 180 }, (_, index) => {
|
|
105
|
+
const date = new Date(start + index * 86_400_000);
|
|
106
|
+
const close = 50 + index;
|
|
107
|
+
const high = index === 41 ? 900 : close + 1;
|
|
108
|
+
const low = index === 117 ? 2 : close - 1;
|
|
109
|
+
return ohlcPoint(date, close - 0.5, high, low, close);
|
|
110
|
+
}),
|
|
111
|
+
});
|
|
112
|
+
const scene = buildCompositeChartScene(
|
|
113
|
+
[candles],
|
|
114
|
+
[{ id: "main" }],
|
|
115
|
+
{ width: 80, height: 12 },
|
|
116
|
+
)!;
|
|
117
|
+
const source = scene.panels[0]!.series[0]!.points;
|
|
118
|
+
const buckets = downsampleOhlcProjectedPoints(source, 40);
|
|
119
|
+
expect(buckets.length).toBeLessThanOrEqual(40);
|
|
120
|
+
expect(Math.max(...buckets.map((entry) => entry.point.high ?? entry.value))).toBe(900);
|
|
121
|
+
expect(Math.min(...buckets.map((entry) => entry.point.low ?? entry.value))).toBe(2);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("does not downsample when the series already fits the budget", () => {
|
|
125
|
+
const scene = buildCompositeChartScene(
|
|
126
|
+
[series({
|
|
127
|
+
id: "short",
|
|
128
|
+
points: [point("2025-01-01", 10), point("2025-01-02", 12), point("2025-01-03", 11)],
|
|
129
|
+
})],
|
|
130
|
+
[{ id: "main" }],
|
|
131
|
+
{ width: 80, height: 8 },
|
|
132
|
+
)!;
|
|
133
|
+
const downsampled = downsampleCompositeChartScene(scene, 64);
|
|
134
|
+
expect(downsampled.panels[0]!.series[0]!.points).toBe(scene.panels[0]!.series[0]!.points);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { isOhlcSeriesStyle } from "../../../time-series/spec";
|
|
2
|
+
import type { TimeSeriesPoint } from "../../../time-series/types";
|
|
3
|
+
import type {
|
|
4
|
+
CompositeChartScene,
|
|
5
|
+
CompositePanelScene,
|
|
6
|
+
CompositeProjectedPoint,
|
|
7
|
+
CompositeProjectedSeries,
|
|
8
|
+
} from "./types";
|
|
9
|
+
|
|
10
|
+
function finiteNumber(value: unknown): value is number {
|
|
11
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function splitConnectedSegments(
|
|
15
|
+
points: readonly CompositeProjectedPoint[],
|
|
16
|
+
): CompositeProjectedPoint[][] {
|
|
17
|
+
const segments: CompositeProjectedPoint[][] = [];
|
|
18
|
+
let current: CompositeProjectedPoint[] = [];
|
|
19
|
+
for (const point of points) {
|
|
20
|
+
if (point.breakBefore && current.length > 0) {
|
|
21
|
+
segments.push(current);
|
|
22
|
+
current = [];
|
|
23
|
+
}
|
|
24
|
+
current.push(point);
|
|
25
|
+
}
|
|
26
|
+
if (current.length > 0) segments.push(current);
|
|
27
|
+
return segments;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Largest-Triangle-Three-Buckets. Keeps the first and last samples and one
|
|
32
|
+
* representative from each interior bucket so a line still fits the pixel
|
|
33
|
+
* budget without flattening extrema.
|
|
34
|
+
*/
|
|
35
|
+
export function downsampleLttb(
|
|
36
|
+
points: readonly CompositeProjectedPoint[],
|
|
37
|
+
threshold: number,
|
|
38
|
+
): CompositeProjectedPoint[] {
|
|
39
|
+
const count = points.length;
|
|
40
|
+
const budget = Math.max(Math.floor(threshold), 1);
|
|
41
|
+
if (count <= budget) return points as CompositeProjectedPoint[];
|
|
42
|
+
if (budget === 1) return [points[0]!];
|
|
43
|
+
if (budget === 2) return [points[0]!, points[count - 1]!];
|
|
44
|
+
|
|
45
|
+
const sampled: CompositeProjectedPoint[] = [points[0]!];
|
|
46
|
+
const bucketSize = (count - 2) / (budget - 2);
|
|
47
|
+
let previousIndex = 0;
|
|
48
|
+
|
|
49
|
+
for (let bucketIndex = 0; bucketIndex < budget - 2; bucketIndex += 1) {
|
|
50
|
+
const nextStart = Math.floor((bucketIndex + 1) * bucketSize) + 1;
|
|
51
|
+
const nextEnd = Math.min(Math.floor((bucketIndex + 2) * bucketSize) + 1, count);
|
|
52
|
+
let avgX = 0;
|
|
53
|
+
let avgY = 0;
|
|
54
|
+
const avgCount = Math.max(nextEnd - nextStart, 1);
|
|
55
|
+
for (let index = nextStart; index < nextEnd; index += 1) {
|
|
56
|
+
avgX += points[index]!.xRatio;
|
|
57
|
+
avgY += points[index]!.yRatio;
|
|
58
|
+
}
|
|
59
|
+
avgX /= avgCount;
|
|
60
|
+
avgY /= avgCount;
|
|
61
|
+
|
|
62
|
+
const currentStart = Math.floor(bucketIndex * bucketSize) + 1;
|
|
63
|
+
const currentEnd = Math.floor((bucketIndex + 1) * bucketSize) + 1;
|
|
64
|
+
const previous = points[previousIndex]!;
|
|
65
|
+
const prevX = previous.xRatio;
|
|
66
|
+
const prevY = previous.yRatio;
|
|
67
|
+
let maxArea = -1;
|
|
68
|
+
let maxIndex = currentStart;
|
|
69
|
+
for (let index = currentStart; index < currentEnd; index += 1) {
|
|
70
|
+
const point = points[index]!;
|
|
71
|
+
const area = Math.abs(
|
|
72
|
+
(prevX - avgX) * (point.yRatio - prevY) - (prevX - point.xRatio) * (avgY - prevY),
|
|
73
|
+
);
|
|
74
|
+
if (area > maxArea) {
|
|
75
|
+
maxArea = area;
|
|
76
|
+
maxIndex = index;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
sampled.push(points[maxIndex]!);
|
|
80
|
+
previousIndex = maxIndex;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
sampled.push(points[count - 1]!);
|
|
84
|
+
return sampled;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function downsampleLineProjectedPoints(
|
|
88
|
+
points: readonly CompositeProjectedPoint[],
|
|
89
|
+
pixelWidth: number,
|
|
90
|
+
): CompositeProjectedPoint[] {
|
|
91
|
+
if (points.length <= pixelWidth) return points as CompositeProjectedPoint[];
|
|
92
|
+
const segments = splitConnectedSegments(points);
|
|
93
|
+
if (segments.length === 1) return downsampleLttb(points, pixelWidth);
|
|
94
|
+
|
|
95
|
+
const sampled: CompositeProjectedPoint[] = [];
|
|
96
|
+
for (const segment of segments) {
|
|
97
|
+
const budget = Math.max(2, Math.floor(pixelWidth * (segment.length / points.length)));
|
|
98
|
+
const next = downsampleLttb(segment, Math.min(budget, segment.length));
|
|
99
|
+
if (sampled.length > 0 && next[0] && !next[0].breakBefore) {
|
|
100
|
+
sampled.push({ ...next[0], breakBefore: true }, ...next.slice(1));
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
sampled.push(...next);
|
|
104
|
+
}
|
|
105
|
+
return sampled.length <= pixelWidth ? sampled : downsampleLttb(sampled, pixelWidth);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function sourceValue(point: CompositeProjectedPoint, key: "open" | "high" | "low" | "close"): number {
|
|
109
|
+
const candidate = point.point[key];
|
|
110
|
+
return finiteNumber(candidate) ? candidate : point.value;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function aggregateOhlcProjectedBucket(
|
|
114
|
+
bucket: readonly CompositeProjectedPoint[],
|
|
115
|
+
): CompositeProjectedPoint {
|
|
116
|
+
const first = bucket[0]!;
|
|
117
|
+
const last = bucket[bucket.length - 1]!;
|
|
118
|
+
let high = sourceValue(first, "high");
|
|
119
|
+
let low = sourceValue(first, "low");
|
|
120
|
+
let volume = finiteNumber(first.point.volume) ? first.point.volume : 0;
|
|
121
|
+
let breakBefore = first.breakBefore;
|
|
122
|
+
|
|
123
|
+
for (let index = 1; index < bucket.length; index += 1) {
|
|
124
|
+
const point = bucket[index]!;
|
|
125
|
+
high = Math.max(high, sourceValue(point, "high"));
|
|
126
|
+
low = Math.min(low, sourceValue(point, "low"));
|
|
127
|
+
if (finiteNumber(point.point.volume)) volume += point.point.volume;
|
|
128
|
+
if (point.breakBefore) breakBefore = true;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const open = sourceValue(first, "open");
|
|
132
|
+
const close = sourceValue(last, "close");
|
|
133
|
+
const point: TimeSeriesPoint = {
|
|
134
|
+
...last.point,
|
|
135
|
+
value: close,
|
|
136
|
+
open,
|
|
137
|
+
high,
|
|
138
|
+
low,
|
|
139
|
+
close,
|
|
140
|
+
volume,
|
|
141
|
+
};
|
|
142
|
+
return {
|
|
143
|
+
...last,
|
|
144
|
+
point,
|
|
145
|
+
value: close,
|
|
146
|
+
breakBefore,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function downsampleOhlcProjectedPoints(
|
|
151
|
+
points: readonly CompositeProjectedPoint[],
|
|
152
|
+
targetWidth: number,
|
|
153
|
+
): CompositeProjectedPoint[] {
|
|
154
|
+
const budget = Math.max(Math.floor(targetWidth), 1);
|
|
155
|
+
if (points.length <= budget) return points as CompositeProjectedPoint[];
|
|
156
|
+
|
|
157
|
+
const buckets = new Map<number, CompositeProjectedPoint[]>();
|
|
158
|
+
for (const point of points) {
|
|
159
|
+
const index = Math.min(budget - 1, Math.max(0, Math.floor(point.xRatio * budget)));
|
|
160
|
+
const bucket = buckets.get(index);
|
|
161
|
+
if (bucket) bucket.push(point);
|
|
162
|
+
else buckets.set(index, [point]);
|
|
163
|
+
}
|
|
164
|
+
return [...buckets.entries()]
|
|
165
|
+
.sort((left, right) => left[0] - right[0])
|
|
166
|
+
.map(([, bucket]) => aggregateOhlcProjectedBucket(bucket));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function downsampleProjectedSeries(
|
|
170
|
+
series: CompositeProjectedSeries,
|
|
171
|
+
pixelWidth: number,
|
|
172
|
+
): CompositeProjectedSeries {
|
|
173
|
+
const width = Math.max(Math.floor(pixelWidth), 1);
|
|
174
|
+
if (series.points.length <= 1 || width <= 1) return series;
|
|
175
|
+
const points = isOhlcSeriesStyle(series.source.style)
|
|
176
|
+
? downsampleOhlcProjectedPoints(series.points, Math.max(Math.floor(width / 2), 1))
|
|
177
|
+
: downsampleLineProjectedPoints(series.points, width);
|
|
178
|
+
return points === series.points ? series : { ...series, points };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function downsamplePanel(panel: CompositePanelScene, pixelWidth: number): CompositePanelScene {
|
|
182
|
+
let changed = false;
|
|
183
|
+
const series = panel.series.map((entry) => {
|
|
184
|
+
const next = downsampleProjectedSeries(entry, pixelWidth);
|
|
185
|
+
if (next !== entry) changed = true;
|
|
186
|
+
return next;
|
|
187
|
+
});
|
|
188
|
+
return changed ? { ...panel, series } : panel;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Caps each panel's projected series to the plot's pixel budget. Cursor
|
|
193
|
+
* snapping still uses the full `dates` / `dateRatios` lists on the scene.
|
|
194
|
+
*/
|
|
195
|
+
export function downsampleCompositeChartScene(
|
|
196
|
+
scene: CompositeChartScene,
|
|
197
|
+
pixelWidth: number,
|
|
198
|
+
): CompositeChartScene {
|
|
199
|
+
const width = Math.max(Math.floor(pixelWidth), 1);
|
|
200
|
+
if (width <= 1) return scene;
|
|
201
|
+
let changed = false;
|
|
202
|
+
const panels = scene.panels.map((panel) => {
|
|
203
|
+
const next = downsamplePanel(panel, width);
|
|
204
|
+
if (next !== panel) changed = true;
|
|
205
|
+
return next;
|
|
206
|
+
});
|
|
207
|
+
return changed ? { ...scene, panels } : scene;
|
|
208
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { ResolvedSeries, TimeSeriesPoint } from "../../../time-series/types";
|
|
3
|
+
import { reuseResolvedSeriesIdentity, reuseResolvedSeriesList } from "./panel-series";
|
|
4
|
+
|
|
5
|
+
function point(): TimeSeriesPoint {
|
|
6
|
+
const date = new Date("2024-01-01T00:00:00.000Z");
|
|
7
|
+
return { date, observedAt: date, value: 1 };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function series(id: string, panelId: string): ResolvedSeries {
|
|
11
|
+
return {
|
|
12
|
+
id,
|
|
13
|
+
label: id,
|
|
14
|
+
color: "#0f0",
|
|
15
|
+
unit: "USD",
|
|
16
|
+
unitGroup: "currency",
|
|
17
|
+
nativeFrequency: "daily",
|
|
18
|
+
dataShape: "scalar",
|
|
19
|
+
style: "line",
|
|
20
|
+
transform: "raw",
|
|
21
|
+
axis: "left",
|
|
22
|
+
panelId,
|
|
23
|
+
interpolation: "none",
|
|
24
|
+
points: [point()],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe("reuseResolvedSeriesIdentity", () => {
|
|
29
|
+
test("reuses the previous object when a parent clones identical series data", () => {
|
|
30
|
+
const original = series("a", "main");
|
|
31
|
+
const clone: ResolvedSeries = { ...original, points: original.points.map((entry) => ({ ...entry })) };
|
|
32
|
+
expect(reuseResolvedSeriesIdentity(original, clone)).toBe(original);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("keeps series identity when only the last bar close and time change", () => {
|
|
36
|
+
const start = new Date("2024-01-01T00:00:00.000Z");
|
|
37
|
+
const first: TimeSeriesPoint = { date: start, observedAt: start, value: 10, close: 10 };
|
|
38
|
+
const second: TimeSeriesPoint = {
|
|
39
|
+
date: new Date("2024-01-02T00:00:00.000Z"),
|
|
40
|
+
observedAt: new Date("2024-01-02T00:00:00.000Z"),
|
|
41
|
+
value: 11,
|
|
42
|
+
close: 11,
|
|
43
|
+
};
|
|
44
|
+
const previous = series("a", "main");
|
|
45
|
+
previous.points = [first, second];
|
|
46
|
+
const nextLast: TimeSeriesPoint = {
|
|
47
|
+
date: new Date("2024-01-02T00:00:01.000Z"),
|
|
48
|
+
observedAt: new Date("2024-01-02T00:00:01.000Z"),
|
|
49
|
+
value: 11.5,
|
|
50
|
+
close: 11.5,
|
|
51
|
+
};
|
|
52
|
+
const next: ResolvedSeries = {
|
|
53
|
+
...previous,
|
|
54
|
+
points: [first, nextLast],
|
|
55
|
+
};
|
|
56
|
+
expect(reuseResolvedSeriesIdentity(previous, next)).toBe(previous);
|
|
57
|
+
expect(previous.points[1]).toBe(nextLast);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("reuses the list identity when every member is reused", () => {
|
|
61
|
+
const a = series("a", "main");
|
|
62
|
+
const first = [a];
|
|
63
|
+
const second = reuseResolvedSeriesList(first, [{ ...a, points: a.points.map((entry) => ({ ...entry })) }]);
|
|
64
|
+
expect(second).toBe(first);
|
|
65
|
+
});
|
|
66
|
+
});
|