gloomberb 0.11.2 → 0.12.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/README.md +7 -4
- package/README.zh-CN.md +4 -2
- package/package.json +28 -1
- package/src/api-client/auth.ts +10 -0
- package/src/api-client/data.ts +108 -1
- package/src/api-client/index.test.ts +127 -0
- package/src/api-client/index.ts +134 -1
- package/src/api-client/normalizers.ts +39 -0
- package/src/api-client/paths.ts +85 -1
- package/src/api-client/types.ts +220 -0
- package/src/app/pane-runtime/index.tsx +15 -12
- package/src/app/pane-runtime/layout-placement.test.ts +27 -0
- package/src/app/pane-runtime/layout-placement.ts +13 -0
- package/src/app/pane-runtime/plugin-bindings.ts +62 -0
- package/src/app.tsx +60 -18
- package/src/architecture/browser-entry-graph.test.ts +96 -0
- package/src/brokers/profile-form.test.ts +25 -27
- package/src/brokers/test-broker.ts +68 -0
- package/src/capabilities/chart-series.ts +3 -2
- package/src/cli/commands/market.ts +2 -2
- package/src/cli/commands/plugins.ts +39 -11
- package/src/cli/desktop-pane-shot.ts +285 -16
- package/src/cli/index.ts +1 -1
- package/src/cli/pane-functions/capabilities.ts +61 -31
- package/src/cli/pane-functions/catalog.ts +16 -4
- package/src/cli/pane-functions/discovery.ts +1 -0
- package/src/cli/pane-functions/headless.test.ts +247 -0
- package/src/cli/pane-functions/headless.ts +423 -0
- package/src/cli/pane-functions/index.test.ts +9 -0
- package/src/cli/pane-functions/index.ts +6 -2
- package/src/cli/pane-functions/options.ts +27 -2
- package/src/cli/pane-functions/report.ts +4 -0
- package/src/cli/pane-functions/resolver.ts +26 -8
- package/src/cli/pane-functions/screenshot.test.ts +76 -0
- package/src/cli/pane-functions/screenshot.ts +236 -20
- package/src/cli/registry.ts +0 -22
- package/src/cli/restore-plugins.ts +34 -0
- package/src/cli/result.ts +7 -10
- package/src/components/chart/{static/chart → composite}/axis-overlays.test.tsx +3 -3
- package/src/components/chart/{static/chart → composite}/axis-overlays.tsx +6 -11
- package/src/components/chart/{static/chart → composite}/bitmap.ts +17 -3
- package/src/components/chart/composite/composite-chart.test.tsx +68 -20
- package/src/components/chart/composite/composite-chart.tsx +301 -204
- package/src/components/chart/composite/format.ts +8 -2
- package/src/components/chart/composite/interactions.test.ts +68 -102
- package/src/components/chart/composite/interactions.ts +214 -264
- package/src/components/chart/composite/price-axis-labels.test.ts +21 -0
- package/src/components/chart/{price-axis-labels.tsx → composite/price-axis-labels.tsx} +9 -4
- package/src/components/chart/composite/scene.ts +57 -3
- package/src/components/chart/composite/text-renderer.ts +3 -2
- package/src/components/chart/composite/time-scale.ts +138 -57
- package/src/components/chart/composite/types.ts +25 -1
- package/src/components/chart/core/data.ts +9 -13
- package/src/components/chart/core/date-window.ts +7 -1
- package/src/components/chart/core/pointer.ts +3 -0
- package/src/components/chart/core/types.ts +0 -45
- package/src/components/chart/native/chart-rasterizer.ts +0 -1
- package/src/components/chart/native/kitty/index.test.ts +0 -188
- package/src/components/chart/native/raster/crosshair.ts +6 -5
- package/src/components/chart/native/raster/primitives.ts +0 -23
- package/src/components/chart/native/renderer-selection.ts +81 -31
- package/src/components/chart/static/{chart/surface.test.tsx → chart-surface.test.tsx} +28 -80
- package/src/components/chart/static/chart-surface.tsx +150 -0
- package/src/components/chart/static/index.ts +8 -0
- package/src/components/chart/static/multi-line-chart-surface.tsx +51 -166
- package/src/components/chart/static/scatter-chart-surface.test.ts +30 -0
- package/src/components/chart/static/scatter-chart-surface.tsx +135 -34
- package/src/components/chart/static/series.ts +49 -0
- package/src/components/command-bar/action-runtime.ts +1 -3
- package/src/components/command-bar/assist/model.test.ts +17 -2
- package/src/components/command-bar/assist/model.ts +40 -9
- package/src/components/command-bar/commands/direct/index.ts +1 -1
- package/src/components/command-bar/commands/direct/runtime.ts +1 -1
- package/src/components/command-bar/commands/registry.ts +0 -11
- package/src/components/command-bar/helpers.test.ts +2 -2
- package/src/components/command-bar/helpers.ts +2 -6
- package/src/components/command-bar/keyboard-shortcuts.ts +0 -11
- package/src/components/command-bar/list/badge.test.ts +54 -0
- package/src/components/command-bar/list/badge.ts +62 -0
- package/src/components/command-bar/list/model.test.ts +103 -0
- package/src/components/command-bar/list/model.ts +61 -4
- package/src/components/command-bar/list/view.test.tsx +113 -0
- package/src/components/command-bar/list/view.tsx +147 -131
- package/src/components/command-bar/pane-templates/workflow.ts +1 -1
- package/src/components/command-bar/panel/body.tsx +51 -65
- package/src/components/command-bar/panel/index.tsx +48 -13
- package/src/components/command-bar/panel/layout.test.ts +139 -0
- package/src/components/command-bar/panel/layout.ts +65 -28
- package/src/components/command-bar/panel/native-surface.ts +24 -0
- package/src/components/command-bar/panel/prompt-binding.ts +54 -0
- package/src/components/command-bar/panel/runtime.ts +6 -3
- package/src/components/command-bar/panel/state.ts +41 -7
- package/src/components/command-bar/panel/types.ts +7 -1
- package/src/components/command-bar/routes/root/provider-search.ts +34 -15
- package/src/components/command-bar/routes/root/results.test.ts +161 -0
- package/src/components/command-bar/routes/root/results.ts +30 -28
- package/src/components/command-bar/routes/root/runtime.ts +37 -20
- package/src/components/command-bar/routes/root/search-providers.test.tsx +224 -0
- package/src/components/command-bar/routes/root/search-providers.ts +205 -0
- package/src/components/command-bar/routes/root/selection.ts +1 -12
- package/src/components/command-bar/routes/root/shortcuts.test.ts +8 -8
- package/src/components/command-bar/routes/ticker-search/actions.ts +10 -9
- package/src/components/command-bar/routes/ticker-search/results.test.ts +54 -1
- package/src/components/command-bar/routes/ticker-search/results.ts +60 -19
- package/src/components/command-bar/routes/ticker-search/route.ts +1 -0
- package/src/components/command-bar/routing/actions.ts +1 -81
- package/src/components/command-bar/routing/launch-request.ts +1 -1
- package/src/components/command-bar/routing/list-state.ts +6 -21
- package/src/components/command-bar/selection-runtime.ts +1 -1
- package/src/components/command-bar/surface/assist.test.tsx +14 -10
- package/src/components/command-bar/surface/environment.ts +8 -1
- package/src/components/command-bar/surface/index.test.tsx +84 -68
- package/src/components/command-bar/surface/index.tsx +33 -36
- package/src/components/command-bar/surface/test-harness.tsx +15 -3
- package/src/components/command-bar/theme-picker.tsx +43 -15
- package/src/components/command-bar/view-model.test.ts +53 -8
- package/src/components/command-bar/view-model.ts +43 -14
- package/src/components/command-bar/workflow/tickers.ts +1 -0
- package/src/components/command-bar/workflow/types.ts +1 -1
- package/src/components/data-table/export.test.ts +21 -0
- package/src/components/data-table/export.ts +21 -0
- package/src/components/data-table/view.test.tsx +45 -6
- package/src/components/data-table/view.tsx +55 -9
- package/src/components/index.ts +10 -2
- package/src/components/input-search-bar.test.tsx +36 -2
- package/src/components/input-search-bar.tsx +18 -1
- package/src/components/layout/header.test.tsx +57 -10
- package/src/components/layout/header.tsx +292 -149
- package/src/components/layout/market-summary.test.ts +81 -0
- package/src/components/layout/market-summary.ts +109 -0
- package/src/components/layout/shell/chrome.ts +96 -1
- package/src/components/layout/shell/index.tsx +11 -3
- package/src/components/layout/shell/menu.ts +9 -0
- package/src/components/layout/shell/pane/actions.ts +29 -0
- package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
- package/src/components/layout/shell/shortcuts.ts +3 -0
- package/src/components/layout/status-bar.test.tsx +9 -8
- package/src/components/layout/status-bar.tsx +85 -54
- package/src/components/onboarding/onboarding-frame.tsx +2 -26
- package/src/components/price-sparkline/index.ts +61 -34
- package/src/components/price-sparkline/view.tsx +1 -1
- package/src/components/sign-in-gate.test.tsx +128 -0
- package/src/components/sign-in-gate.tsx +149 -0
- package/src/components/speedometer-gauge/desktop.tsx +2 -12
- package/src/components/ticker/badge/list.tsx +17 -3
- package/src/components/ticker/list-table-view.tsx +6 -3
- package/src/components/ui/data-table/index.tsx +22 -4
- package/src/components/ui/data-table/opentui/index.tsx +165 -120
- package/src/components/ui/frame.tsx +28 -0
- package/src/components/ui/index.ts +3 -0
- package/src/components/ui/native-select.tsx +20 -10
- package/src/components/ui/select-button.tsx +138 -0
- package/src/components/ui/status.tsx +2 -2
- package/src/components/ui/table-layout.ts +1 -1
- package/src/components/use-double-click-activation.ts +7 -3
- package/src/core/app-services.ts +3 -3
- package/src/data/config/chart-settings.ts +4 -4
- package/src/data/config/layout.ts +12 -6
- package/src/data/config/store/index.test.ts +97 -0
- package/src/data/config/store/migrations.ts +41 -0
- package/src/data/config/store/normalize.ts +5 -3
- package/src/i18n/es.ts +6 -2
- package/src/i18n/ja.ts +6 -2
- package/src/i18n/ko.ts +6 -2
- package/src/i18n/zh-cn.ts +6 -2
- package/src/i18n/zh-tw.ts +6 -2
- package/src/layout-marketplace/gallery-terminal.tsx +126 -96
- package/src/layout-marketplace/gallery.test.tsx +65 -1
- package/src/layout-marketplace/pane-imagery.ts +1 -0
- package/src/layout-marketplace/payload.test.ts +17 -0
- package/src/layout-marketplace/payload.ts +40 -5
- package/src/market-data/coordinator/index.ts +3 -2
- package/src/market-data/coordinator/quotes.ts +173 -66
- package/src/market-data/coordinator/subscriptions.test.ts +215 -37
- package/src/pane-settings.ts +1 -1
- package/src/plugins/builtin/analytics/pane-model.ts +1 -1
- package/src/plugins/builtin/analytics/view.tsx +1 -1
- package/src/plugins/builtin/broker-manager/index.test.tsx +3 -2
- package/src/plugins/builtin/broker-manager/model.test.ts +2 -2
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +16 -2
- package/src/plugins/builtin/chart-composer/cli-options.ts +12 -9
- package/src/plugins/builtin/chart-composer/pane.tsx +22 -8
- package/src/plugins/builtin/chart-composer/settings.ts +2 -13
- package/src/plugins/builtin/chat/content/composer.tsx +8 -4
- package/src/plugins/builtin/cloud/auth-dialog.tsx +14 -228
- package/src/plugins/builtin/cloud/auth-form.tsx +277 -0
- package/src/plugins/builtin/cloud/plugin.tsx +1 -0
- package/src/plugins/builtin/composite-plugins.ts +16 -1
- package/src/plugins/builtin/correlation/relationship/model.ts +1 -1
- package/src/plugins/builtin/correlation/relationship/pane.tsx +2 -6
- package/src/plugins/builtin/correlation/relationship/view-model.ts +2 -2
- package/src/plugins/builtin/credit-conditions/index.test.tsx +12 -6
- package/src/plugins/builtin/credit-conditions/index.tsx +2 -21
- package/src/plugins/builtin/dividend-yield/index.tsx +1 -0
- package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
- package/src/plugins/builtin/earnings/index.tsx +1 -0
- package/src/plugins/builtin/earnings-calls/data.ts +223 -0
- package/src/plugins/builtin/earnings-calls/format.ts +49 -0
- package/src/plugins/builtin/earnings-calls/index.tsx +69 -0
- package/src/plugins/builtin/earnings-calls/pane.tsx +756 -0
- package/src/plugins/builtin/earnings-calls/prose.test.ts +36 -0
- package/src/plugins/builtin/earnings-calls/prose.ts +59 -0
- package/src/plugins/builtin/earnings-calls/transcript-view.tsx +334 -0
- package/src/plugins/builtin/econ/detail-view.tsx +1 -2
- package/src/plugins/builtin/econ/fred-series-map.ts +16 -1
- package/src/plugins/builtin/econ/index.tsx +3 -1
- package/src/plugins/builtin/econ-statistics/cache.ts +4 -0
- package/src/plugins/builtin/econ-statistics/client.ts +92 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +66 -0
- package/src/plugins/builtin/econ-statistics/detail.tsx +154 -0
- package/src/plugins/builtin/econ-statistics/headless.test.ts +54 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +171 -0
- package/src/plugins/builtin/econ-statistics/index.tsx +54 -0
- package/src/plugins/builtin/econ-statistics/pane.tsx +356 -0
- package/src/plugins/builtin/econ-statistics/stats.ts +522 -0
- package/src/plugins/builtin/econ-statistics/transform.test.ts +86 -0
- package/src/plugins/builtin/econ-statistics/transform.ts +85 -0
- package/src/plugins/builtin/econ-statistics/trend.ts +57 -0
- package/src/plugins/builtin/econ-statistics/view.ts +90 -0
- package/src/plugins/builtin/fear-greed/charts.tsx +7 -18
- package/src/plugins/builtin/fear-greed/data.ts +6 -1
- package/src/plugins/builtin/futures/index.tsx +1 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +1 -0
- package/src/plugins/builtin/help/index.tsx +4 -0
- package/src/plugins/builtin/holders/index.tsx +1 -0
- package/src/plugins/builtin/insider/index.tsx +1 -0
- package/src/plugins/builtin/ipo-calendar/index.tsx +1 -0
- package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
- package/src/plugins/builtin/kelly-sizer/sections.tsx +2 -3
- package/src/plugins/builtin/market-halts/index.tsx +1 -0
- package/src/plugins/builtin/market-movers/index.tsx +1 -0
- package/src/plugins/builtin/market-valuation/align.ts +122 -0
- package/src/plugins/builtin/market-valuation/cache.ts +32 -0
- package/src/plugins/builtin/market-valuation/chart-projection.ts +217 -0
- package/src/plugins/builtin/market-valuation/chart-series.ts +89 -0
- package/src/plugins/builtin/market-valuation/client.test.ts +141 -0
- package/src/plugins/builtin/market-valuation/client.ts +116 -0
- package/src/plugins/builtin/market-valuation/defs.ts +251 -0
- package/src/plugins/builtin/market-valuation/detail.tsx +159 -0
- package/src/plugins/builtin/market-valuation/headless.test.ts +58 -0
- package/src/plugins/builtin/market-valuation/headless.ts +170 -0
- package/src/plugins/builtin/market-valuation/index.tsx +64 -0
- package/src/plugins/builtin/market-valuation/indicators.ts +422 -0
- package/src/plugins/builtin/market-valuation/model.test.ts +350 -0
- package/src/plugins/builtin/market-valuation/model.ts +8 -0
- package/src/plugins/builtin/market-valuation/pane.test.tsx +247 -0
- package/src/plugins/builtin/market-valuation/pane.tsx +344 -0
- package/src/plugins/builtin/market-valuation/series.ts +12 -0
- package/src/plugins/builtin/market-valuation/settings.ts +38 -0
- package/src/plugins/builtin/market-valuation/sources.ts +130 -0
- package/src/plugins/builtin/market-valuation/trend.ts +89 -0
- package/src/plugins/builtin/market-valuation/view.ts +137 -0
- package/src/plugins/builtin/market-valuation/zone-scale/desktop.tsx +123 -0
- package/src/plugins/builtin/market-valuation/zone-scale/index.tsx +12 -0
- package/src/plugins/builtin/market-valuation/zone-scale/model.ts +60 -0
- package/src/plugins/builtin/market-valuation/zone-scale/terminal.tsx +94 -0
- package/src/plugins/builtin/news/wire/breaking/filters.test.ts +99 -0
- package/src/plugins/builtin/news/wire/breaking/filters.ts +110 -0
- package/src/plugins/builtin/news/wire/breaking/notifications.test.ts +157 -53
- package/src/plugins/builtin/news/wire/breaking/notifications.ts +91 -25
- package/src/plugins/builtin/news/wire/index.tsx +31 -44
- package/src/plugins/builtin/options/analytics.test.ts +83 -0
- package/src/plugins/builtin/options/analytics.ts +110 -0
- package/src/plugins/builtin/options/index.tsx +18 -2
- package/src/plugins/builtin/options/table.ts +121 -51
- package/src/plugins/builtin/options/types.ts +22 -15
- package/src/plugins/builtin/options/view.test.tsx +53 -1
- package/src/plugins/builtin/options/view.tsx +92 -41
- package/src/plugins/builtin/plugin-marketplace/feed.ts +80 -0
- package/src/plugins/builtin/plugin-marketplace/index.tsx +31 -0
- package/src/plugins/builtin/plugin-marketplace/model.test.ts +200 -0
- package/src/plugins/builtin/plugin-marketplace/model.ts +204 -0
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +385 -0
- package/src/plugins/builtin/plugin-marketplace/store.ts +46 -0
- package/src/plugins/builtin/plugin-module.test.ts +2 -4
- package/src/plugins/builtin/plugin-module.ts +9 -8
- package/src/plugins/builtin/portfolio-list/index.tsx +1 -0
- package/src/plugins/builtin/research/index.tsx +4 -0
- package/src/plugins/builtin/research-search/command-bar-search.test.ts +81 -0
- package/src/plugins/builtin/research-search/command-bar-search.ts +175 -0
- package/src/plugins/builtin/research-search/data.ts +109 -0
- package/src/plugins/builtin/research-search/document-view.test.tsx +75 -0
- package/src/plugins/builtin/research-search/document-view.tsx +254 -0
- package/src/plugins/builtin/research-search/filter-bar.tsx +62 -0
- package/src/plugins/builtin/research-search/focus-handoff.ts +47 -0
- package/src/plugins/builtin/research-search/index.tsx +85 -0
- package/src/plugins/builtin/research-search/model.test.ts +40 -0
- package/src/plugins/builtin/research-search/model.ts +291 -0
- package/src/plugins/builtin/research-search/pane.test.tsx +241 -0
- package/src/plugins/builtin/research-search/pane.tsx +678 -0
- package/src/plugins/builtin/research-search/saved-view.tsx +171 -0
- package/src/plugins/builtin/research-search/snippet-text.tsx +35 -0
- package/src/plugins/builtin/research-search/snippet.test.ts +78 -0
- package/src/plugins/builtin/research-search/snippet.ts +134 -0
- package/src/plugins/builtin/scanner/flow-model.test.ts +11 -1
- package/src/plugins/builtin/scanner/flow-model.ts +29 -1
- package/src/plugins/builtin/scanner/flow-pane.tsx +78 -22
- package/src/plugins/builtin/scanner/index.ts +2 -0
- package/src/plugins/builtin/sec/index.tsx +1 -0
- package/src/plugins/builtin/sectors/index.tsx +1 -0
- package/src/plugins/builtin/shared/series-cache.ts +93 -0
- package/src/plugins/builtin/short-interest/index.tsx +1 -0
- package/src/plugins/builtin/short-interest/pane.tsx +1 -1
- package/src/plugins/builtin/thirteenf/index.tsx +1 -0
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +3 -5
- package/src/plugins/builtin/ticker-detail/index.tsx +2 -14
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +1 -1
- package/src/plugins/builtin/treasury-auctions/index.tsx +1 -0
- package/src/plugins/builtin/volatility/index.tsx +1 -1
- package/src/plugins/builtin/world-indices/index.tsx +1 -0
- package/src/plugins/builtin/world-venue-map/index.ts +27 -0
- package/src/plugins/builtin/world-venue-map/map.desktop.test.tsx +106 -0
- package/src/plugins/builtin/world-venue-map/map.tsx +481 -0
- package/src/plugins/builtin/world-venue-map/model.test.ts +85 -0
- package/src/plugins/builtin/world-venue-map/model.ts +268 -0
- package/src/plugins/builtin/world-venue-map/pane.tsx +300 -0
- package/src/plugins/builtin/world-venue-map/world-outlines.ts +2 -0
- package/src/plugins/builtin/yield-curve/index.tsx +1 -1
- package/src/plugins/bundle.test.ts +109 -0
- package/src/plugins/bundle.ts +139 -0
- package/src/plugins/catalog-backend.ts +6 -44
- package/src/plugins/catalog-browser.test.ts +1 -0
- package/src/plugins/catalog-browser.ts +12 -0
- package/src/plugins/catalog-manifest.test.ts +43 -0
- package/src/plugins/catalog-manifest.ts +92 -0
- package/src/plugins/catalog-ui.ts +20 -4
- package/src/plugins/catalog.ts +9 -2
- package/src/plugins/current-target.ts +28 -0
- package/src/plugins/host-contract.ts +37 -0
- package/src/plugins/host-link.ts +158 -0
- package/src/plugins/host-modules.ts +61 -0
- package/src/plugins/loader.ts +41 -22
- package/src/plugins/ownership.ts +1 -0
- package/src/plugins/prediction-markets/index.tsx +1 -0
- package/src/plugins/registry/context.ts +3 -0
- package/src/plugins/registry/contributions.ts +27 -0
- package/src/plugins/registry/index.test.ts +25 -0
- package/src/plugins/registry/index.ts +8 -0
- package/src/plugins/registry/pane-settings.test.ts +53 -0
- package/src/plugins/registry/pane-settings.ts +29 -3
- package/src/plugins/seed.test.ts +83 -0
- package/src/plugins/seed.ts +80 -0
- package/src/public/broker.ts +31 -0
- package/src/public/market-data.ts +9 -0
- package/src/public/pane-hooks.ts +57 -0
- package/src/public/public-api.test.ts +285 -0
- package/src/public/react.ts +62 -0
- package/src/public/test-support.ts +23 -0
- package/src/public/time-series.ts +8 -0
- package/src/public/utils.ts +77 -0
- package/src/remote/layout-helpers.ts +6 -8
- package/src/remote/resources.ts +2 -0
- package/src/remote/schema.ts +1 -0
- package/src/remote/semantic-tree.test.tsx +105 -0
- package/src/remote/semantic-tree.tsx +53 -35
- package/src/renderers/browser/main.tsx +6 -0
- package/src/renderers/browser/ui-host.tsx +11 -0
- package/src/renderers/cloudflare/worker.test.ts +19 -0
- package/src/renderers/cloudflare/worker.ts +1 -1
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +9 -2
- package/src/renderers/electrobun/bun/external-plugins.ts +140 -0
- package/src/renderers/electrobun/bun/index.ts +6 -2
- package/src/renderers/electrobun/shared/protocol.ts +25 -0
- package/src/renderers/electrobun/view/build-assets.ts +0 -1
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +51 -1
- package/src/renderers/electrobun/view/external-plugins.test.ts +45 -0
- package/src/renderers/electrobun/view/external-plugins.ts +63 -0
- package/src/renderers/electrobun/view/host/ascii-text.test.ts +48 -0
- package/src/renderers/electrobun/view/host/ascii-text.ts +11 -1
- package/src/renderers/electrobun/view/host/chart-surface.tsx +38 -32
- package/src/renderers/electrobun/view/host/mouse.ts +16 -2
- package/src/renderers/electrobun/view/main.tsx +27 -2
- package/src/renderers/electrobun/view/styles.css +5 -0
- package/src/renderers/electrobun/view/toast-host.tsx +18 -0
- package/src/renderers/electrobun/view/ui-host.tsx +3 -0
- package/src/renderers/opentui/chart-surface.tsx +5 -1
- package/src/renderers/opentui/host.tsx +20 -2
- package/src/renderers/opentui/interaction-performance.test.ts +79 -0
- package/src/renderers/opentui/interaction-performance.ts +188 -0
- package/src/renderers/opentui/start.tsx +26 -1
- package/src/renderers/opentui/test-utils.tsx +13 -0
- package/src/renderers/opentui/toast-host.tsx +26 -2
- package/src/renderers/opentui/ui-host.tsx +1 -0
- package/src/sources/provider-router/search.test.ts +191 -0
- package/src/sources/provider-router/search.ts +92 -39
- package/src/state/app/context/index.tsx +2 -0
- package/src/state/app/context/selector.test.tsx +33 -1
- package/src/state/hooks/quote-streaming.test.tsx +93 -0
- package/src/state/hooks/quote-streaming.ts +106 -30
- package/src/state/pane-table-export-registry.ts +50 -0
- package/src/theme/color-utils.ts +1 -1
- package/src/theme/colors.ts +50 -0
- package/src/theme/themes.test.ts +15 -2
- package/src/theme/themes.ts +19 -1
- package/src/tickers/search/index.test.ts +26 -0
- package/src/tickers/search/index.ts +42 -17
- package/src/tickers/search/ranking.ts +14 -0
- package/src/time-series/chart-data.test.ts +216 -0
- package/src/time-series/range.ts +12 -0
- package/src/time-series/resolution.test.ts +25 -26
- package/src/time-series/resolution.ts +44 -25
- package/src/time-series/resolve.ts +7 -0
- package/src/time-series/spec.ts +5 -5
- package/src/time-series/types.ts +3 -1
- package/src/time-series/use-chart-resolution.ts +5 -0
- package/src/types/config.ts +123 -3
- package/src/types/data-provider.ts +11 -0
- package/src/types/headless.ts +161 -0
- package/src/types/plugin.ts +104 -20
- package/src/ui/host.tsx +19 -1
- package/src/ui/index.tsx +1 -0
- package/src/ui/toast.tsx +8 -4
- package/src/utils/csv.test.ts +13 -0
- package/src/utils/csv.ts +37 -0
- package/src/utils/format.ts +35 -0
- package/src/utils/fuzzy-search.test.ts +94 -0
- package/src/utils/fuzzy-search.ts +81 -22
- package/src/utils/save-text-file.test.ts +24 -0
- package/src/utils/save-text-file.ts +36 -0
- package/src/utils/shortcut-labels.ts +14 -0
- package/src/components/chart/core/axis-format.ts +0 -157
- package/src/components/chart/core/index.test.ts +0 -1075
- package/src/components/chart/core/projection.ts +0 -357
- package/src/components/chart/core/render-utils.ts +0 -4
- package/src/components/chart/core/renderer.ts +0 -244
- package/src/components/chart/core/scene.ts +0 -180
- package/src/components/chart/core/time-axis.ts +0 -330
- package/src/components/chart/cursor-motion.ts +0 -47
- package/src/components/chart/indicators/types.ts +0 -26
- package/src/components/chart/market-session.ts +0 -176
- package/src/components/chart/multi-line/renderer.test.ts +0 -83
- package/src/components/chart/multi-line/renderer.ts +0 -161
- package/src/components/chart/multi-line/scene.ts +0 -188
- package/src/components/chart/native/raster/layout.ts +0 -129
- package/src/components/chart/native/raster/price-chart.ts +0 -244
- package/src/components/chart/rendering/chart-draw.ts +0 -270
- package/src/components/chart/rendering/geometry.ts +0 -95
- package/src/components/chart/rendering/pixel-buffer.ts +0 -267
- package/src/components/chart/scatter-chart-renderer.test.ts +0 -31
- package/src/components/chart/scatter-chart-renderer.ts +0 -223
- package/src/components/chart/static/chart/surface.tsx +0 -358
- package/src/components/command-bar/routes/root/article-results.test.ts +0 -83
- package/src/components/command-bar/routes/root/article-results.ts +0 -55
- package/src/plugins/builtin/news/wire/article-search.test.ts +0 -72
- package/src/plugins/builtin/news/wire/article-search.ts +0 -140
- package/src/plugins/builtin/substack/api/auth.ts +0 -185
- package/src/plugins/builtin/substack/api/cache.ts +0 -82
- package/src/plugins/builtin/substack/api/loaders.ts +0 -170
- package/src/plugins/builtin/substack/api/store.ts +0 -186
- package/src/plugins/builtin/substack/api/types.ts +0 -42
- package/src/plugins/builtin/substack/api.test.ts +0 -298
- package/src/plugins/builtin/substack/article-detail.tsx +0 -397
- package/src/plugins/builtin/substack/article-stack.tsx +0 -133
- package/src/plugins/builtin/substack/content.ts +0 -478
- package/src/plugins/builtin/substack/feed-tabs.tsx +0 -44
- package/src/plugins/builtin/substack/index.tsx +0 -49
- package/src/plugins/builtin/substack/login-view.tsx +0 -126
- package/src/plugins/builtin/substack/model.test.ts +0 -155
- package/src/plugins/builtin/substack/normalize.ts +0 -343
- package/src/plugins/builtin/substack/pane-footer.ts +0 -52
- package/src/plugins/builtin/substack/pane-state.ts +0 -170
- package/src/plugins/builtin/substack/pane.tsx +0 -547
- package/src/plugins/builtin/substack/read-state.ts +0 -30
- package/src/plugins/builtin/substack/table.ts +0 -111
- package/src/plugins/builtin/substack/types.ts +0 -71
- package/src/plugins/builtin/substack/utils.ts +0 -131
- package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +0 -229
- package/src/plugins/ibkr/account-cache.test.ts +0 -102
- package/src/plugins/ibkr/account-cache.ts +0 -24
- package/src/plugins/ibkr/broker-adapter.ts +0 -286
- package/src/plugins/ibkr/config.test.ts +0 -79
- package/src/plugins/ibkr/config.ts +0 -250
- package/src/plugins/ibkr/dialogs.tsx +0 -40
- package/src/plugins/ibkr/flex/index.test.ts +0 -264
- package/src/plugins/ibkr/flex/index.ts +0 -367
- package/src/plugins/ibkr/flex/statement-client.ts +0 -235
- package/src/plugins/ibkr/fundamental-parser.ts +0 -149
- package/src/plugins/ibkr/gateway/account-loaders.ts +0 -266
- package/src/plugins/ibkr/gateway/account-summary.ts +0 -164
- package/src/plugins/ibkr/gateway/client-lock.ts +0 -154
- package/src/plugins/ibkr/gateway/connection-events.ts +0 -114
- package/src/plugins/ibkr/gateway/connection.ts +0 -152
- package/src/plugins/ibkr/gateway/contracts.ts +0 -132
- package/src/plugins/ibkr/gateway/fundamentals.ts +0 -83
- package/src/plugins/ibkr/gateway/helpers.ts +0 -33
- package/src/plugins/ibkr/gateway/history-requests.ts +0 -158
- package/src/plugins/ibkr/gateway/history.ts +0 -120
- package/src/plugins/ibkr/gateway/instrument-lookup.ts +0 -117
- package/src/plugins/ibkr/gateway/market-data.ts +0 -345
- package/src/plugins/ibkr/gateway/order-execution.ts +0 -218
- package/src/plugins/ibkr/gateway/orders.ts +0 -63
- package/src/plugins/ibkr/gateway/price-normalization.test.ts +0 -44
- package/src/plugins/ibkr/gateway/price-normalization.ts +0 -50
- package/src/plugins/ibkr/gateway/quote-stream-controller.ts +0 -203
- package/src/plugins/ibkr/gateway/quote-stream.ts +0 -171
- package/src/plugins/ibkr/gateway/selection.ts +0 -51
- package/src/plugins/ibkr/gateway/service/index.test.ts +0 -675
- package/src/plugins/ibkr/gateway/service/index.ts +0 -429
- package/src/plugins/ibkr/gateway/service/lifecycle.ts +0 -191
- package/src/plugins/ibkr/gateway/service/manager.ts +0 -52
- package/src/plugins/ibkr/gateway/service/market-data.ts +0 -48
- package/src/plugins/ibkr/gateway/service/native-loader.ts +0 -12
- package/src/plugins/ibkr/gateway/service/native.ts +0 -543
- package/src/plugins/ibkr/gateway/timeouts.ts +0 -13
- package/src/plugins/ibkr/gateway/types.ts +0 -27
- package/src/plugins/ibkr/index.test.tsx +0 -162
- package/src/plugins/ibkr/index.tsx +0 -159
- package/src/plugins/ibkr/instance-selection.test.ts +0 -120
- package/src/plugins/ibkr/instance-selection.ts +0 -36
- package/src/plugins/ibkr/portfolio-performance.test.ts +0 -32
- package/src/plugins/ibkr/portfolio-performance.ts +0 -129
- package/src/plugins/ibkr/trade/badge.tsx +0 -28
- package/src/plugins/ibkr/trade/dialogs.tsx +0 -44
- package/src/plugins/ibkr/trade/preview-panel.tsx +0 -130
- package/src/plugins/ibkr/trade/tab/actions.tsx +0 -443
- package/src/plugins/ibkr/trade/tab/field-editors.tsx +0 -79
- package/src/plugins/ibkr/trade/tab/footer.ts +0 -53
- package/src/plugins/ibkr/trade/tab/header.tsx +0 -167
- package/src/plugins/ibkr/trade/tab/index.test.tsx +0 -178
- package/src/plugins/ibkr/trade/tab/index.tsx +0 -298
- package/src/plugins/ibkr/trade/tab/model.ts +0 -159
- package/src/plugins/ibkr/trade/tab/order-request.ts +0 -64
- package/src/plugins/ibkr/trade/tab/shortcuts.ts +0 -89
- package/src/plugins/ibkr/trade/tab/state-sync.ts +0 -86
- package/src/plugins/ibkr/trade/ticket-panel.tsx +0 -301
- package/src/plugins/ibkr/trade/utils.test.ts +0 -175
- package/src/plugins/ibkr/trade/utils.ts +0 -144
- package/src/plugins/ibkr/trading/pane-view.tsx +0 -237
- package/src/plugins/ibkr/trading/pane.tsx +0 -332
- package/src/plugins/ibkr/trading/state.test.ts +0 -109
- package/src/plugins/ibkr/trading/state.ts +0 -335
|
@@ -32,9 +32,14 @@ import { useShowChartTextFallback } from "../native/use-chart-text-fallback";
|
|
|
32
32
|
import {
|
|
33
33
|
useStaticChartBitmapSize,
|
|
34
34
|
type StaticChartBitmapSize,
|
|
35
|
-
} from "
|
|
36
|
-
import {
|
|
37
|
-
|
|
35
|
+
} from "./bitmap";
|
|
36
|
+
import {
|
|
37
|
+
StaticXAxisLabels,
|
|
38
|
+
StaticXMarkerLabels,
|
|
39
|
+
StaticXMarkerOverlay,
|
|
40
|
+
type StaticChartXMarker,
|
|
41
|
+
} from "./axis-overlays";
|
|
42
|
+
import { PriceAxisLabels } from "./price-axis-labels";
|
|
38
43
|
import {
|
|
39
44
|
compositeAxisTicks,
|
|
40
45
|
formatCompositeAxisValue,
|
|
@@ -43,19 +48,24 @@ import {
|
|
|
43
48
|
formatCompositePointDetails,
|
|
44
49
|
formatCompositeSeriesValue,
|
|
45
50
|
formatCompositeTimeAxisDate,
|
|
51
|
+
type CompositeAxisValueFormatter,
|
|
46
52
|
} from "./format";
|
|
47
53
|
import {
|
|
48
54
|
COMPOSITE_KEYBOARD_PAN_RATIO,
|
|
49
55
|
COMPOSITE_ZOOM_STEP_FACTOR,
|
|
56
|
+
buildCompositeNavigationFrame,
|
|
50
57
|
clampCompositeViewport,
|
|
58
|
+
compositeNavigationDataViewport,
|
|
59
|
+
compositeViewportPositions,
|
|
60
|
+
fitCompositeViewport,
|
|
51
61
|
panCompositeViewport,
|
|
52
62
|
resolveCompositeChartInteraction,
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
resolveCompositeWheelPanRatio,
|
|
63
|
+
resolveCompositeWheelPan,
|
|
64
|
+
resolveCompositeWheelZoom,
|
|
56
65
|
sameCompositeViewport,
|
|
57
66
|
shouldResetCompositeViewport,
|
|
58
67
|
zoomCompositeViewport,
|
|
68
|
+
type CompositeNavigationFrame,
|
|
59
69
|
type CompositeViewportRange,
|
|
60
70
|
} from "./interactions";
|
|
61
71
|
import { buildCompositeColumnLayout, type CompositeColumnLayout } from "./column-layout";
|
|
@@ -112,6 +122,8 @@ import type {
|
|
|
112
122
|
// live-data paints or depending on a foreground animation frame.
|
|
113
123
|
const DESKTOP_BITMAP_RESIZE_DEBOUNCE_MS = 32;
|
|
114
124
|
const LEGEND_WHEEL_DELTA_PER_CELL = 8;
|
|
125
|
+
/** How far past the first loaded observation a backfilling chart may pan, as a fraction of the view. */
|
|
126
|
+
const HISTORICAL_PADDING_RATIO = 0.5;
|
|
115
127
|
|
|
116
128
|
function isVerticalWheelDirection(
|
|
117
129
|
direction: "up" | "down" | "left" | "right",
|
|
@@ -119,6 +131,44 @@ function isVerticalWheelDirection(
|
|
|
119
131
|
return direction === "up" || direction === "down";
|
|
120
132
|
}
|
|
121
133
|
|
|
134
|
+
interface PanGesture {
|
|
135
|
+
kind: "pan";
|
|
136
|
+
startGlobalX: number;
|
|
137
|
+
frame: CompositeNavigationFrame;
|
|
138
|
+
startViewport: CompositeViewportRange;
|
|
139
|
+
positionsPerCell: number;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** A drag pans in the frame it started in; see `CompositeNavigationFrame`. */
|
|
143
|
+
function startPanGesture(
|
|
144
|
+
frame: CompositeNavigationFrame,
|
|
145
|
+
viewport: CompositeViewportRange,
|
|
146
|
+
plotWidth: number,
|
|
147
|
+
globalX: number,
|
|
148
|
+
): PanGesture {
|
|
149
|
+
const positions = compositeViewportPositions(frame, viewport);
|
|
150
|
+
const span = positions ? Math.max(positions.end - positions.start, Number.EPSILON) : 1;
|
|
151
|
+
return {
|
|
152
|
+
kind: "pan",
|
|
153
|
+
startGlobalX: globalX,
|
|
154
|
+
frame,
|
|
155
|
+
startViewport: viewport,
|
|
156
|
+
positionsPerCell: span / Math.max(plotWidth, 1),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
interface PendingWheel {
|
|
161
|
+
/** Fraction of the visible span, positive toward older observations. */
|
|
162
|
+
panRatio: number;
|
|
163
|
+
zoomLog: number;
|
|
164
|
+
anchorRatio: number;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const webFrame = globalThis as typeof globalThis & {
|
|
168
|
+
requestAnimationFrame?: (callback: () => void) => number;
|
|
169
|
+
cancelAnimationFrame?: (handle: number) => void;
|
|
170
|
+
};
|
|
171
|
+
|
|
122
172
|
function renderPanelBitmap(
|
|
123
173
|
panel: CompositePanelScene,
|
|
124
174
|
bitmapSize: StaticChartBitmapSize,
|
|
@@ -255,7 +305,7 @@ function resolvePanelCrosshair(
|
|
|
255
305
|
cursorYRatio: number | null,
|
|
256
306
|
color: string,
|
|
257
307
|
): ChartSurfaceProps["crosshair"] {
|
|
258
|
-
if (!bitmap || cursorXRatio === null
|
|
308
|
+
if (!bitmap || cursorXRatio === null) return null;
|
|
259
309
|
const markers = panel.series.flatMap((series) => {
|
|
260
310
|
// Column cohorts are drawn at their group center, not each observation's
|
|
261
311
|
// own timestamp, so match the position the bar actually occupies.
|
|
@@ -274,7 +324,7 @@ function resolvePanelCrosshair(
|
|
|
274
324
|
});
|
|
275
325
|
return {
|
|
276
326
|
pixelX: cursorXRatio * Math.max(bitmap.width - 1, 0),
|
|
277
|
-
pixelY: cursorYRatio * Math.max(bitmap.height - 1, 0),
|
|
327
|
+
pixelY: cursorYRatio === null ? null : cursorYRatio * Math.max(bitmap.height - 1, 0),
|
|
278
328
|
color,
|
|
279
329
|
markers,
|
|
280
330
|
};
|
|
@@ -291,11 +341,13 @@ function cursorAxisLabel(
|
|
|
291
341
|
panel: CompositePanelScene,
|
|
292
342
|
side: "left" | "right",
|
|
293
343
|
cursorYRatio: number | null,
|
|
344
|
+
format?: CompositeAxisValueFormatter,
|
|
294
345
|
): string | null {
|
|
295
346
|
const domain = panel.axes[side];
|
|
296
347
|
if (!domain || cursorYRatio === null) return null;
|
|
297
348
|
const value = unprojectCompositeValue(cursorYRatio, domain);
|
|
298
|
-
|
|
349
|
+
if (value === null) return null;
|
|
350
|
+
return format ? format(value, domain) : formatCompositeCursorValue(value, domain);
|
|
299
351
|
}
|
|
300
352
|
|
|
301
353
|
const MINIMUM_AXIS_LABEL_WIDTH = 3;
|
|
@@ -304,10 +356,13 @@ const MINIMUM_AXIS_LABEL_WIDTH = 3;
|
|
|
304
356
|
* Cells the gutter needs for its own labels. Ticks cover the axis itself, and a
|
|
305
357
|
* mid-domain sample covers the wider cursor readout that lands between them.
|
|
306
358
|
*/
|
|
307
|
-
function compositeAxisLabelWidth(
|
|
359
|
+
function compositeAxisLabelWidth(
|
|
360
|
+
domain: CompositeAxisDomain | undefined,
|
|
361
|
+
format: CompositeAxisValueFormatter = formatCompositeAxisValue,
|
|
362
|
+
): number {
|
|
308
363
|
if (!domain) return 0;
|
|
309
|
-
const labels = compositeAxisTicks(domain).map((tick) => tick.label);
|
|
310
|
-
labels.push(
|
|
364
|
+
const labels = compositeAxisTicks(domain, 3, format).map((tick) => tick.label);
|
|
365
|
+
labels.push(format((domain.min + domain.max) / 2, domain));
|
|
311
366
|
return labels.reduce((widest, label) => Math.max(widest, [...label].length), 0);
|
|
312
367
|
}
|
|
313
368
|
|
|
@@ -604,8 +659,12 @@ interface CompositePanelSurfaceProps {
|
|
|
604
659
|
axisGap: number;
|
|
605
660
|
colors: CompositeChartColors;
|
|
606
661
|
interactive: boolean;
|
|
662
|
+
/** False keeps the hover cursor but removes pan, zoom, and the tools. */
|
|
663
|
+
navigable: boolean;
|
|
664
|
+
formatAxisValue?: CompositeAxisValueFormatter;
|
|
665
|
+
remoteKind?: string;
|
|
607
666
|
viewport: CompositeViewportRange;
|
|
608
|
-
|
|
667
|
+
frame: CompositeNavigationFrame;
|
|
609
668
|
armedTool: ChartToolKind | null;
|
|
610
669
|
drawings: readonly ChartDrawing[];
|
|
611
670
|
selectedDrawingId: string | null;
|
|
@@ -615,7 +674,11 @@ interface CompositePanelSurfaceProps {
|
|
|
615
674
|
onSelectDrawing: (id: string | null) => void;
|
|
616
675
|
onActivate?: () => void;
|
|
617
676
|
onCursorDateChange: (date: Date | null) => void;
|
|
618
|
-
|
|
677
|
+
/** Positive positions move toward older observations. A gesture supplies the frame and origin it started from. */
|
|
678
|
+
onPanViewport: (
|
|
679
|
+
deltaPositions: number,
|
|
680
|
+
gesture?: { frame: CompositeNavigationFrame; from: CompositeViewportRange },
|
|
681
|
+
) => void;
|
|
619
682
|
onZoomViewport: (zoomFactor: number, anchorRatio: number) => void;
|
|
620
683
|
onSetViewport: (range: CompositeViewportRange) => void;
|
|
621
684
|
onToolSpanChange: (span: ChartToolSpan | null) => void;
|
|
@@ -631,8 +694,11 @@ function CompositePanelSurface({
|
|
|
631
694
|
axisGap,
|
|
632
695
|
colors,
|
|
633
696
|
interactive,
|
|
697
|
+
navigable,
|
|
698
|
+
formatAxisValue,
|
|
699
|
+
remoteKind,
|
|
634
700
|
viewport,
|
|
635
|
-
|
|
701
|
+
frame,
|
|
636
702
|
armedTool,
|
|
637
703
|
drawings,
|
|
638
704
|
selectedDrawingId,
|
|
@@ -661,7 +727,7 @@ function CompositePanelSurface({
|
|
|
661
727
|
? null
|
|
662
728
|
: cursorYRatio ?? seriesCursorYRatio;
|
|
663
729
|
const dragRef = useRef<
|
|
664
|
-
|
|
|
730
|
+
| PanGesture
|
|
665
731
|
| {
|
|
666
732
|
kind: "edit";
|
|
667
733
|
drawingId: string;
|
|
@@ -677,9 +743,12 @@ function CompositePanelSurface({
|
|
|
677
743
|
const bitmapSize = useStaticChartBitmapSize(plotWidth, panel.height);
|
|
678
744
|
const bitmap = useCompositePanelBitmap({ panel, bitmapSize, colors, isDesktopWeb });
|
|
679
745
|
const columnLayout = useMemo(() => buildCompositeColumnLayout(panel), [panel]);
|
|
746
|
+
// The level line follows the pointer only. A keyboard or shared cursor knows
|
|
747
|
+
// its column, and the series markers and axis readout already say the value.
|
|
748
|
+
const pointerCursorYRatio = scene.cursorXRatio === null ? null : cursorYRatio;
|
|
680
749
|
const crosshair = useMemo(
|
|
681
|
-
() => resolvePanelCrosshair(panel, columnLayout, bitmap, scene.cursorXRatio,
|
|
682
|
-
[
|
|
750
|
+
() => resolvePanelCrosshair(panel, columnLayout, bitmap, scene.cursorXRatio, pointerCursorYRatio, colors.crosshair),
|
|
751
|
+
[bitmap, colors.crosshair, columnLayout, panel, pointerCursorYRatio, scene.cursorXRatio],
|
|
683
752
|
);
|
|
684
753
|
const measureDomain = useMemo(() => resolveMeasureAxisDomain(panel), [panel]);
|
|
685
754
|
const toolReadout = useMemo(() => {
|
|
@@ -799,20 +868,20 @@ function CompositePanelSurface({
|
|
|
799
868
|
const textLines = useMemo(
|
|
800
869
|
() => isDesktopWeb || !showTextFallback
|
|
801
870
|
? []
|
|
802
|
-
: renderCompositePanelText(panel, plotWidth, scene.cursorXRatio,
|
|
803
|
-
[
|
|
871
|
+
: renderCompositePanelText(panel, plotWidth, scene.cursorXRatio, pointerCursorYRatio),
|
|
872
|
+
[isDesktopWeb, panel, plotWidth, pointerCursorYRatio, scene.cursorXRatio, showTextFallback],
|
|
804
873
|
);
|
|
805
874
|
const leftAxisLabels = useMemo(
|
|
806
875
|
() => axisLabelRows(
|
|
807
|
-
renderCompositeAxisText(panel.axes.left, panel.height, leftAxisWidth, "left"),
|
|
876
|
+
renderCompositeAxisText(panel.axes.left, panel.height, leftAxisWidth, "left", formatAxisValue),
|
|
808
877
|
),
|
|
809
|
-
[leftAxisWidth, panel],
|
|
878
|
+
[formatAxisValue, leftAxisWidth, panel],
|
|
810
879
|
);
|
|
811
880
|
const rightAxisLabels = useMemo(
|
|
812
881
|
() => axisLabelRows(
|
|
813
|
-
renderCompositeAxisText(panel.axes.right, panel.height, rightAxisWidth, "right"),
|
|
882
|
+
renderCompositeAxisText(panel.axes.right, panel.height, rightAxisWidth, "right", formatAxisValue),
|
|
814
883
|
),
|
|
815
|
-
[panel, rightAxisWidth],
|
|
884
|
+
[formatAxisValue, panel, rightAxisWidth],
|
|
816
885
|
);
|
|
817
886
|
const cursorRow = activeCursorYRatio === null
|
|
818
887
|
? null
|
|
@@ -820,8 +889,8 @@ function CompositePanelSurface({
|
|
|
820
889
|
const cursorPixelY = activeCursorYRatio === null
|
|
821
890
|
? null
|
|
822
891
|
: activeCursorYRatio * Math.max(panel.height * cellHeightPx - 1, 0);
|
|
823
|
-
const leftCursorLabel = cursorAxisLabel(panel, "left", activeCursorYRatio);
|
|
824
|
-
const rightCursorLabel = cursorAxisLabel(panel, "right", activeCursorYRatio);
|
|
892
|
+
const leftCursorLabel = cursorAxisLabel(panel, "left", activeCursorYRatio, formatAxisValue);
|
|
893
|
+
const rightCursorLabel = cursorAxisLabel(panel, "right", activeCursorYRatio, formatAxisValue);
|
|
825
894
|
const measureReadout = useMemo(() => {
|
|
826
895
|
if (!toolDrag || !toolReadout?.summary) return null;
|
|
827
896
|
const text = toolReadout.summary;
|
|
@@ -944,24 +1013,26 @@ function CompositePanelSurface({
|
|
|
944
1013
|
return;
|
|
945
1014
|
}
|
|
946
1015
|
if (!updateCursor(event)) return;
|
|
947
|
-
dragRef.current =
|
|
948
|
-
kind: "pan",
|
|
949
|
-
startGlobalX: getGlobalMouseX(event, renderer),
|
|
950
|
-
startViewport: viewport,
|
|
951
|
-
};
|
|
1016
|
+
dragRef.current = startPanGesture(frame, viewport, plotWidth, getGlobalMouseX(event, renderer));
|
|
952
1017
|
}, [
|
|
953
1018
|
armedTool,
|
|
954
1019
|
drawings,
|
|
1020
|
+
frame,
|
|
955
1021
|
onActivate,
|
|
956
1022
|
onSelectDrawing,
|
|
957
1023
|
panel,
|
|
958
1024
|
plotAspect,
|
|
1025
|
+
plotWidth,
|
|
959
1026
|
pointerRatios,
|
|
960
1027
|
renderer,
|
|
961
1028
|
scene,
|
|
962
1029
|
updateCursor,
|
|
963
1030
|
viewport,
|
|
964
1031
|
]);
|
|
1032
|
+
const pressCursor = useCallback((event: ChartMouseEvent) => {
|
|
1033
|
+
onActivate?.();
|
|
1034
|
+
updateCursor(event);
|
|
1035
|
+
}, [onActivate, updateCursor]);
|
|
965
1036
|
const dragViewport = useCallback((event: ChartMouseEvent) => {
|
|
966
1037
|
const drag = dragRef.current;
|
|
967
1038
|
if (!drag) return;
|
|
@@ -992,9 +1063,18 @@ function CompositePanelSurface({
|
|
|
992
1063
|
setToolDrag({ ...drag, path: [...drag.path] });
|
|
993
1064
|
return;
|
|
994
1065
|
}
|
|
995
|
-
const
|
|
996
|
-
|
|
1066
|
+
const globalX = getGlobalMouseX(event, renderer);
|
|
1067
|
+
if (drag.frame !== frame) {
|
|
1068
|
+
// Data refreshed mid-drag. The new frame maps positions differently, so
|
|
1069
|
+
// continue from where the plot is now instead of replaying the drag.
|
|
1070
|
+
Object.assign(drag, startPanGesture(frame, viewport, plotWidth, globalX));
|
|
1071
|
+
}
|
|
1072
|
+
onPanViewport(
|
|
1073
|
+
(globalX - drag.startGlobalX) * drag.positionsPerCell,
|
|
1074
|
+
{ frame: drag.frame, from: drag.startViewport },
|
|
1075
|
+
);
|
|
997
1076
|
}, [
|
|
1077
|
+
frame,
|
|
998
1078
|
onEditDrawing,
|
|
999
1079
|
onPanViewport,
|
|
1000
1080
|
panel,
|
|
@@ -1003,6 +1083,7 @@ function CompositePanelSurface({
|
|
|
1003
1083
|
renderer,
|
|
1004
1084
|
scene,
|
|
1005
1085
|
updateCursor,
|
|
1086
|
+
viewport,
|
|
1006
1087
|
]);
|
|
1007
1088
|
const finishToolDrag = useCallback(() => {
|
|
1008
1089
|
const drag = dragRef.current;
|
|
@@ -1016,9 +1097,9 @@ function CompositePanelSurface({
|
|
|
1016
1097
|
return;
|
|
1017
1098
|
}
|
|
1018
1099
|
if (drag.kind !== "zoom") return;
|
|
1019
|
-
const range = resolveZoomBoxRange(scene, drag,
|
|
1100
|
+
const range = resolveZoomBoxRange(scene, drag, frame.minimumSpanMs);
|
|
1020
1101
|
if (range) onSetViewport(range);
|
|
1021
|
-
}, [drawColor,
|
|
1102
|
+
}, [drawColor, frame.minimumSpanMs, onDraw, onSetViewport, panel, scene]);
|
|
1022
1103
|
const resetDrag = useCallback(() => {
|
|
1023
1104
|
if (dragRef.current?.kind === "pan") {
|
|
1024
1105
|
dragRef.current = null;
|
|
@@ -1032,27 +1113,58 @@ function CompositePanelSurface({
|
|
|
1032
1113
|
finishToolDrag();
|
|
1033
1114
|
updateCursor(event);
|
|
1034
1115
|
}, [finishToolDrag, updateCursor]);
|
|
1116
|
+
// Trackpads fire wheel events faster than the plot can paint, so one frame
|
|
1117
|
+
// absorbs every event that arrived since the last one.
|
|
1118
|
+
const pendingWheelRef = useRef<PendingWheel | null>(null);
|
|
1119
|
+
const wheelFrameRef = useRef<number | null>(null);
|
|
1120
|
+
const latestWheelStateRef = useRef({ frame, viewport, onPanViewport, onZoomViewport });
|
|
1121
|
+
latestWheelStateRef.current = { frame, viewport, onPanViewport, onZoomViewport };
|
|
1122
|
+
const flushWheel = useCallback(() => {
|
|
1123
|
+
wheelFrameRef.current = null;
|
|
1124
|
+
const pending = pendingWheelRef.current;
|
|
1125
|
+
pendingWheelRef.current = null;
|
|
1126
|
+
if (!pending) return;
|
|
1127
|
+
const latest = latestWheelStateRef.current;
|
|
1128
|
+
if (pending.panRatio !== 0) {
|
|
1129
|
+
const positions = compositeViewportPositions(latest.frame, latest.viewport);
|
|
1130
|
+
const span = positions ? Math.max(positions.end - positions.start, Number.EPSILON) : 0;
|
|
1131
|
+
latest.onPanViewport(pending.panRatio * span);
|
|
1132
|
+
}
|
|
1133
|
+
if (pending.zoomLog !== 0) {
|
|
1134
|
+
latest.onZoomViewport(Math.exp(pending.zoomLog), pending.anchorRatio);
|
|
1135
|
+
}
|
|
1136
|
+
}, []);
|
|
1137
|
+
useEffect(() => () => {
|
|
1138
|
+
pendingWheelRef.current = null;
|
|
1139
|
+
if (wheelFrameRef.current !== null) {
|
|
1140
|
+
webFrame.cancelAnimationFrame?.(wheelFrameRef.current);
|
|
1141
|
+
wheelFrameRef.current = null;
|
|
1142
|
+
}
|
|
1143
|
+
}, []);
|
|
1035
1144
|
const panFromWheel = useCallback((event: ChartMouseEvent) => {
|
|
1036
|
-
const
|
|
1037
|
-
if (!
|
|
1145
|
+
const scroll = event.scroll;
|
|
1146
|
+
if (!scroll) return;
|
|
1038
1147
|
onActivate?.();
|
|
1039
1148
|
consumeChartMouseEvent(event);
|
|
1040
1149
|
const pointerTarget = plotRef.current as unknown as Parameters<typeof getLocalPlotPointer>[1];
|
|
1041
1150
|
const pointer = getLocalPlotPointer(event, pointerTarget, renderer);
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1151
|
+
updateCursor(event);
|
|
1152
|
+
const pending = pendingWheelRef.current ?? { panRatio: 0, zoomLog: 0, anchorRatio: 0.5 };
|
|
1153
|
+
if (event.modifiers.ctrl && pointer && isVerticalWheelDirection(scroll.direction)) {
|
|
1154
|
+
pending.zoomLog += Math.log(resolveCompositeWheelZoom(scroll));
|
|
1155
|
+
pending.anchorRatio = pointer.cellX / Math.max(plotWidth - 1, 1);
|
|
1156
|
+
} else {
|
|
1157
|
+
pending.panRatio += resolveCompositeWheelPan(scroll, plotWidth * cellWidthPx);
|
|
1158
|
+
}
|
|
1159
|
+
pendingWheelRef.current = pending;
|
|
1160
|
+
if (!isDesktopWeb || typeof webFrame.requestAnimationFrame !== "function") {
|
|
1161
|
+
flushWheel();
|
|
1051
1162
|
return;
|
|
1052
1163
|
}
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1164
|
+
if (wheelFrameRef.current === null) {
|
|
1165
|
+
wheelFrameRef.current = webFrame.requestAnimationFrame(flushWheel);
|
|
1166
|
+
}
|
|
1167
|
+
}, [cellWidthPx, flushWheel, isDesktopWeb, onActivate, plotWidth, renderer, updateCursor]);
|
|
1056
1168
|
|
|
1057
1169
|
return (
|
|
1058
1170
|
<Box
|
|
@@ -1111,15 +1223,16 @@ function CompositePanelSurface({
|
|
|
1111
1223
|
crosshair={crosshair}
|
|
1112
1224
|
vectors={vectors}
|
|
1113
1225
|
onMouseMove={interactive ? handleMouseMove : undefined}
|
|
1114
|
-
onMouseDown={interactive ? startDrag : undefined}
|
|
1115
|
-
onMouseDrag={interactive ? dragViewport : undefined}
|
|
1116
|
-
onMouseUp={interactive ? resetDrag : undefined}
|
|
1117
|
-
onMouseDragEnd={interactive ? resetDrag : undefined}
|
|
1118
|
-
onMouseScroll={interactive ? panFromWheel : undefined}
|
|
1226
|
+
onMouseDown={interactive ? navigable ? startDrag : pressCursor : undefined}
|
|
1227
|
+
onMouseDrag={interactive && navigable ? dragViewport : undefined}
|
|
1228
|
+
onMouseUp={interactive && navigable ? resetDrag : undefined}
|
|
1229
|
+
onMouseDragEnd={interactive && navigable ? resetDrag : undefined}
|
|
1230
|
+
onMouseScroll={interactive && navigable ? panFromWheel : undefined}
|
|
1119
1231
|
onMouseOut={interactive ? clearCursor : undefined}
|
|
1120
|
-
cursor={interactive ? toolDrag ? "crosshair" : "grab" : undefined}
|
|
1232
|
+
cursor={interactive ? toolDrag || !navigable ? "crosshair" : "grab" : undefined}
|
|
1121
1233
|
data-gloom-interactive={interactive ? "true" : undefined}
|
|
1122
1234
|
data-gloom-role={COMPOSITE_PANEL_ROLE}
|
|
1235
|
+
data-gloom-remote-kind={remoteKind}
|
|
1123
1236
|
data-gloom-label={panel.label ?? panel.id}
|
|
1124
1237
|
>
|
|
1125
1238
|
{textLines.map((line, index) => <Text key={index} fg={colors.text}>{line}</Text>)}
|
|
@@ -1375,6 +1488,7 @@ function CompositeLegend({
|
|
|
1375
1488
|
}
|
|
1376
1489
|
|
|
1377
1490
|
const NO_DRAWINGS: readonly ChartDrawing[] = [];
|
|
1491
|
+
const NO_X_MARKERS: readonly StaticChartXMarker[] = [];
|
|
1378
1492
|
/** Coalesces a drag into one write instead of one per pointer move. */
|
|
1379
1493
|
const DRAWING_PERSIST_DELAY_MS = 400;
|
|
1380
1494
|
|
|
@@ -1432,6 +1546,10 @@ export function CompositeChart({
|
|
|
1432
1546
|
viewportResetKey,
|
|
1433
1547
|
colors,
|
|
1434
1548
|
interactive = true,
|
|
1549
|
+
navigable = true,
|
|
1550
|
+
formatAxisValue,
|
|
1551
|
+
xAxis,
|
|
1552
|
+
remoteKind,
|
|
1435
1553
|
allowHistoricalBackfill = false,
|
|
1436
1554
|
axisWidth = 9,
|
|
1437
1555
|
showLegend = true,
|
|
@@ -1519,7 +1637,9 @@ export function CompositeChart({
|
|
|
1519
1637
|
}, [visibleLegendSeries.length]);
|
|
1520
1638
|
const previousAuthoredViewportRef = useRef<CompositeViewportRange | null>(viewport ?? null);
|
|
1521
1639
|
const previousViewportResetKeyRef = useRef(viewportResetKey);
|
|
1522
|
-
|
|
1640
|
+
// The user owns this once they navigate. Data refreshes never rewrite it;
|
|
1641
|
+
// only the authored viewport changing or an explicit reset clears it.
|
|
1642
|
+
const [userViewport, setUserViewport] = useState<CompositeViewportRange | null>(null);
|
|
1523
1643
|
const hasViewportResetKey = viewportResetKey !== undefined
|
|
1524
1644
|
|| previousViewportResetKeyRef.current !== undefined;
|
|
1525
1645
|
const authoredViewportChanged = hasViewportResetKey
|
|
@@ -1528,52 +1648,29 @@ export function CompositeChart({
|
|
|
1528
1648
|
previousAuthoredViewportRef.current,
|
|
1529
1649
|
viewport ?? null,
|
|
1530
1650
|
);
|
|
1531
|
-
const
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
visibleSeries,
|
|
1537
|
-
navigationAnchorViewport,
|
|
1538
|
-
{ historicalPaddingRatio: allowHistoricalBackfill ? 1 : 0 },
|
|
1539
|
-
),
|
|
1540
|
-
[allowHistoricalBackfill, navigationAnchorViewport, visibleSeries],
|
|
1651
|
+
const navigationFrame = useMemo(
|
|
1652
|
+
() => buildCompositeNavigationFrame(visibleSeries, marketTimelineSeries, {
|
|
1653
|
+
historicalPaddingRatio: allowHistoricalBackfill ? HISTORICAL_PADDING_RATIO : 0,
|
|
1654
|
+
}),
|
|
1655
|
+
[allowHistoricalBackfill, marketTimelineSeries, visibleSeries],
|
|
1541
1656
|
);
|
|
1542
|
-
const
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
const
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
const clampedInteractionViewport = interactionViewport && navigationBounds
|
|
1553
|
-
? clampCompositeViewport(interactionViewport, navigationBounds)
|
|
1554
|
-
: interactionViewport;
|
|
1555
|
-
const interactionViewportNeedsSync = !!interactionViewport
|
|
1556
|
-
&& !!clampedInteractionViewport
|
|
1557
|
-
&& !sameCompositeViewport(interactionViewport, clampedInteractionViewport);
|
|
1558
|
-
const currentInteractionViewport = authoredViewportChanged
|
|
1559
|
-
? null
|
|
1560
|
-
: clampedInteractionViewport;
|
|
1561
|
-
const effectiveViewport = navigationBounds
|
|
1562
|
-
? currentInteractionViewport
|
|
1563
|
-
? clampCompositeViewport(currentInteractionViewport, navigationBounds)
|
|
1564
|
-
: initialViewport
|
|
1565
|
-
: null;
|
|
1566
|
-
const interactionViewportStart = currentInteractionViewport?.start.getTime() ?? null;
|
|
1567
|
-
const interactionViewportEnd = currentInteractionViewport?.end.getTime() ?? null;
|
|
1657
|
+
const authoredViewport = useMemo(() => {
|
|
1658
|
+
if (!navigationFrame) return viewport ?? null;
|
|
1659
|
+
return viewport
|
|
1660
|
+
? clampCompositeViewport(navigationFrame, viewport)
|
|
1661
|
+
: compositeNavigationDataViewport(navigationFrame);
|
|
1662
|
+
}, [navigationFrame, viewport]);
|
|
1663
|
+
const activeUserViewport = authoredViewportChanged ? null : userViewport;
|
|
1664
|
+
const effectiveViewport = activeUserViewport ?? authoredViewport;
|
|
1665
|
+
const userViewportStart = activeUserViewport?.start.getTime() ?? null;
|
|
1666
|
+
const userViewportEnd = activeUserViewport?.end.getTime() ?? null;
|
|
1568
1667
|
const lastReportedViewportRef = useRef<string | null>(null);
|
|
1569
|
-
const viewportInteractionRef = useRef<"pan" | "reset" | "
|
|
1668
|
+
const viewportInteractionRef = useRef<"pan" | "reset" | "zoom">("reset");
|
|
1570
1669
|
useEffect(() => {
|
|
1571
1670
|
if (!onViewportChange) return;
|
|
1572
|
-
|
|
1573
|
-
const interactionKey = interactionViewportStart === null || interactionViewportEnd === null
|
|
1671
|
+
const key = userViewportStart === null || userViewportEnd === null
|
|
1574
1672
|
? "none"
|
|
1575
|
-
: `${
|
|
1576
|
-
const key = `${viewportSeriesKey}|${interactionKey}`;
|
|
1673
|
+
: `${userViewportStart}:${userViewportEnd}`;
|
|
1577
1674
|
// The callback drives adaptive data loading. Seed it from the authored
|
|
1578
1675
|
// viewport without echoing that controlled value back into the loader.
|
|
1579
1676
|
if (lastReportedViewportRef.current === null) {
|
|
@@ -1583,99 +1680,62 @@ export function CompositeChart({
|
|
|
1583
1680
|
if (lastReportedViewportRef.current === key) return;
|
|
1584
1681
|
lastReportedViewportRef.current = key;
|
|
1585
1682
|
onViewportChange(
|
|
1586
|
-
|
|
1683
|
+
userViewportStart === null || userViewportEnd === null
|
|
1587
1684
|
? null
|
|
1588
|
-
: {
|
|
1589
|
-
start: new Date(interactionViewportStart),
|
|
1590
|
-
end: new Date(interactionViewportEnd),
|
|
1591
|
-
},
|
|
1685
|
+
: { start: new Date(userViewportStart), end: new Date(userViewportEnd) },
|
|
1592
1686
|
viewportInteractionRef.current,
|
|
1593
1687
|
);
|
|
1594
|
-
}, [
|
|
1595
|
-
interactionViewportEnd,
|
|
1596
|
-
interactionViewportNeedsSync,
|
|
1597
|
-
interactionViewportStart,
|
|
1598
|
-
onViewportChange,
|
|
1599
|
-
viewportSeriesKey,
|
|
1600
|
-
]);
|
|
1601
|
-
const minimumViewportSpanMs = useMemo(
|
|
1602
|
-
() => navigationBounds ? resolveCompositeMinimumSpanMs(visibleSeries, navigationBounds) : 1,
|
|
1603
|
-
[navigationBounds, visibleSeries],
|
|
1604
|
-
);
|
|
1688
|
+
}, [onViewportChange, userViewportEnd, userViewportStart]);
|
|
1605
1689
|
|
|
1606
1690
|
useEffect(() => {
|
|
1607
1691
|
previousAuthoredViewportRef.current = viewport ?? null;
|
|
1608
1692
|
previousViewportResetKeyRef.current = viewportResetKey;
|
|
1609
|
-
if (
|
|
1610
|
-
authoredViewportChanged
|
|
1611
|
-
|| (interactionViewport && !navigationBounds)
|
|
1612
|
-
) {
|
|
1693
|
+
if (authoredViewportChanged && userViewport) {
|
|
1613
1694
|
viewportInteractionRef.current = "reset";
|
|
1614
|
-
|
|
1615
|
-
return;
|
|
1616
|
-
}
|
|
1617
|
-
if (interactionViewportNeedsSync && clampedInteractionViewport) {
|
|
1618
|
-
viewportInteractionRef.current = "sync";
|
|
1619
|
-
setInteractionViewport(clampedInteractionViewport);
|
|
1695
|
+
setUserViewport(null);
|
|
1620
1696
|
}
|
|
1621
|
-
}, [
|
|
1622
|
-
authoredViewportChanged,
|
|
1623
|
-
clampedInteractionViewport,
|
|
1624
|
-
interactionViewport,
|
|
1625
|
-
interactionViewportNeedsSync,
|
|
1626
|
-
navigationBounds,
|
|
1627
|
-
viewport,
|
|
1628
|
-
viewportResetKey,
|
|
1629
|
-
]);
|
|
1697
|
+
}, [authoredViewportChanged, userViewport, viewport, viewportResetKey]);
|
|
1630
1698
|
|
|
1631
|
-
const
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1699
|
+
const navigate = useCallback((
|
|
1700
|
+
kind: "pan" | "zoom",
|
|
1701
|
+
compute: (base: CompositeViewportRange, frame: CompositeNavigationFrame) => CompositeViewportRange,
|
|
1702
|
+
gesture?: { frame: CompositeNavigationFrame; from: CompositeViewportRange },
|
|
1703
|
+
) => {
|
|
1704
|
+
const frame = gesture?.frame ?? navigationFrame;
|
|
1705
|
+
if (!frame) return;
|
|
1706
|
+
viewportInteractionRef.current = kind;
|
|
1707
|
+
setUserViewport((current) => {
|
|
1708
|
+
const base = gesture?.from ?? current ?? authoredViewport;
|
|
1709
|
+
if (!base) return current;
|
|
1710
|
+
const next = compute(base, frame);
|
|
1711
|
+
// Once navigated, the window is the user's until they reset it, even if
|
|
1712
|
+
// a gesture happens to land back on the authored range: the owner may
|
|
1713
|
+
// echo a navigated range back as the authored one, and dropping to null
|
|
1714
|
+
// there would reload the original range under the pointer.
|
|
1715
|
+
return sameCompositeViewport(next, current ?? base) ? current : next;
|
|
1646
1716
|
});
|
|
1647
|
-
}, [
|
|
1717
|
+
}, [authoredViewport, navigationFrame]);
|
|
1648
1718
|
const panViewport = useCallback((
|
|
1649
|
-
|
|
1650
|
-
|
|
1719
|
+
deltaPositions: number,
|
|
1720
|
+
gesture?: { frame: CompositeNavigationFrame; from: CompositeViewportRange },
|
|
1651
1721
|
) => {
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
if (sameCompositeViewport(next, base)) {
|
|
1664
|
-
if (!fromViewport) return current;
|
|
1665
|
-
return sameCompositeViewport(base, initialViewport) ? null : base;
|
|
1666
|
-
}
|
|
1667
|
-
return sameCompositeViewport(next, initialViewport) ? null : next;
|
|
1668
|
-
});
|
|
1669
|
-
}, [allowHistoricalBackfill, initialViewport, marketTimelineSeries, navigationBounds]);
|
|
1722
|
+
navigate("pan", (base, frame) => panCompositeViewport(frame, base, deltaPositions), gesture);
|
|
1723
|
+
}, [navigate]);
|
|
1724
|
+
const panViewportByRatio = useCallback((shiftRatio: number) => {
|
|
1725
|
+
if (!navigationFrame || !effectiveViewport) return;
|
|
1726
|
+
const positions = compositeViewportPositions(navigationFrame, effectiveViewport);
|
|
1727
|
+
if (!positions) return;
|
|
1728
|
+
panViewport(shiftRatio * Math.max(positions.end - positions.start, Number.EPSILON));
|
|
1729
|
+
}, [effectiveViewport, navigationFrame, panViewport]);
|
|
1730
|
+
const zoomViewport = useCallback((zoomFactor: number, anchorRatio = 1) => {
|
|
1731
|
+
navigate("zoom", (base, frame) => zoomCompositeViewport(frame, base, zoomFactor, anchorRatio));
|
|
1732
|
+
}, [navigate]);
|
|
1670
1733
|
const setViewportRange = useCallback((range: CompositeViewportRange) => {
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
const next = clampCompositeViewport(range, navigationBounds);
|
|
1674
|
-
setInteractionViewport(sameCompositeViewport(next, initialViewport) ? null : next);
|
|
1675
|
-
}, [initialViewport, navigationBounds]);
|
|
1734
|
+
navigate("zoom", (_base, frame) => fitCompositeViewport(frame, range));
|
|
1735
|
+
}, [navigate]);
|
|
1676
1736
|
const resetViewport = useCallback(() => {
|
|
1677
1737
|
viewportInteractionRef.current = "reset";
|
|
1678
|
-
|
|
1738
|
+
setUserViewport(null);
|
|
1679
1739
|
}, []);
|
|
1680
1740
|
// Sticky while armed: the toolbar chip shows which tool owns the drag, and a
|
|
1681
1741
|
// one-shot tool would blink off before the user could see it.
|
|
@@ -1687,13 +1747,15 @@ export function CompositeChart({
|
|
|
1687
1747
|
? 1
|
|
1688
1748
|
: 0;
|
|
1689
1749
|
const timeAxisRows = showTimeAxis ? 1 : 0;
|
|
1750
|
+
const xMarkers = xAxis?.markers ?? NO_X_MARKERS;
|
|
1751
|
+
const xMarkerRows = xMarkers.some((marker) => marker.label) ? 1 : 0;
|
|
1690
1752
|
const panelCount = new Set(visibleSeries.map((entry) => entry.panelId)).size;
|
|
1691
1753
|
const lastTickKey = visibleSeries.map((entry) => {
|
|
1692
1754
|
const last = entry.points.at(-1);
|
|
1693
1755
|
if (!last) return entry.id;
|
|
1694
1756
|
return `${entry.id}:${last.date.getTime()}:${last.close ?? ""}:${last.value ?? ""}:${entry.latestChangePercent ?? ""}`;
|
|
1695
1757
|
}).join("|");
|
|
1696
|
-
const plotHeight = Math.max(panelCount, totalHeight - legendRows - timeAxisRows);
|
|
1758
|
+
const plotHeight = Math.max(panelCount, totalHeight - legendRows - timeAxisRows - xMarkerRows);
|
|
1697
1759
|
const resolvedColors = useMemo<CompositeChartColors>(() => ({
|
|
1698
1760
|
background: colors?.background ?? activeThemeColors.bg,
|
|
1699
1761
|
grid: colors?.grid ?? activeThemeColors.border,
|
|
@@ -1730,11 +1792,11 @@ export function CompositeChart({
|
|
|
1730
1792
|
Math.max(
|
|
1731
1793
|
MINIMUM_AXIS_LABEL_WIDTH,
|
|
1732
1794
|
...(projectedScene?.panels ?? []).flatMap((panel) => [
|
|
1733
|
-
compositeAxisLabelWidth(panel.axes.left),
|
|
1734
|
-
compositeAxisLabelWidth(panel.axes.right),
|
|
1795
|
+
compositeAxisLabelWidth(panel.axes.left, formatAxisValue),
|
|
1796
|
+
compositeAxisLabelWidth(panel.axes.right, formatAxisValue),
|
|
1735
1797
|
]),
|
|
1736
1798
|
),
|
|
1737
|
-
), [maximumAxisWidth, projectedScene]);
|
|
1799
|
+
), [formatAxisValue, maximumAxisWidth, projectedScene]);
|
|
1738
1800
|
const leftAxisWidth = hasLeftAxis ? resolvedAxisWidth : 0;
|
|
1739
1801
|
const rightAxisWidth = hasRightAxis ? resolvedAxisWidth : 0;
|
|
1740
1802
|
const axisGap = resolvedAxisWidth > 0 ? 1 : 0;
|
|
@@ -1783,26 +1845,27 @@ export function CompositeChart({
|
|
|
1783
1845
|
: null
|
|
1784
1846
|
), [baseScene, normalizedCursorTimestamp]);
|
|
1785
1847
|
const handleEmptyMouseScroll = useCallback((event: ChartMouseEvent) => {
|
|
1786
|
-
const
|
|
1787
|
-
if (!interactive || !
|
|
1848
|
+
const scroll = event.scroll;
|
|
1849
|
+
if (!interactive || !navigationFrame || !scroll) return;
|
|
1788
1850
|
onActivate?.();
|
|
1789
1851
|
consumeChartMouseEvent(event);
|
|
1790
|
-
if (event.modifiers.ctrl && isVerticalWheelDirection(direction)) {
|
|
1791
|
-
const
|
|
1792
|
-
if (
|
|
1852
|
+
if (event.modifiers.ctrl && isVerticalWheelDirection(scroll.direction)) {
|
|
1853
|
+
const factor = resolveCompositeWheelZoom(scroll);
|
|
1854
|
+
if (factor < 1) {
|
|
1793
1855
|
resetViewport();
|
|
1794
1856
|
return;
|
|
1795
1857
|
}
|
|
1796
|
-
|
|
1797
|
-
zoomViewport(1 + magnitude * 0.04, 0.5);
|
|
1858
|
+
zoomViewport(factor, 0.5);
|
|
1798
1859
|
return;
|
|
1799
1860
|
}
|
|
1800
|
-
|
|
1861
|
+
panViewportByRatio(resolveCompositeWheelPan(scroll, plotWidth * cellWidthPx));
|
|
1801
1862
|
}, [
|
|
1863
|
+
cellWidthPx,
|
|
1802
1864
|
interactive,
|
|
1803
|
-
|
|
1865
|
+
navigationFrame,
|
|
1804
1866
|
onActivate,
|
|
1805
|
-
|
|
1867
|
+
panViewportByRatio,
|
|
1868
|
+
plotWidth,
|
|
1806
1869
|
resetViewport,
|
|
1807
1870
|
zoomViewport,
|
|
1808
1871
|
]);
|
|
@@ -1825,7 +1888,7 @@ export function CompositeChart({
|
|
|
1825
1888
|
}, [cursorDate, onCursorDateChange]);
|
|
1826
1889
|
|
|
1827
1890
|
useShortcut((event) => {
|
|
1828
|
-
if (!focused || !interactive) return;
|
|
1891
|
+
if (!focused || !interactive || !navigable) return;
|
|
1829
1892
|
if (isPlainKey(event, "[", "]") && visibleLegendSeries.length > 0) {
|
|
1830
1893
|
event.preventDefault();
|
|
1831
1894
|
event.stopPropagation();
|
|
@@ -1874,7 +1937,7 @@ export function CompositeChart({
|
|
|
1874
1937
|
|| ((interaction === "zoom-in"
|
|
1875
1938
|
|| interaction === "zoom-out"
|
|
1876
1939
|
|| interaction === "pan-left"
|
|
1877
|
-
|| interaction === "pan-right") && !
|
|
1940
|
+
|| interaction === "pan-right") && !navigationFrame)
|
|
1878
1941
|
) {
|
|
1879
1942
|
return;
|
|
1880
1943
|
}
|
|
@@ -1910,10 +1973,10 @@ export function CompositeChart({
|
|
|
1910
1973
|
return;
|
|
1911
1974
|
}
|
|
1912
1975
|
case "pan-left":
|
|
1913
|
-
|
|
1976
|
+
panViewportByRatio(COMPOSITE_KEYBOARD_PAN_RATIO);
|
|
1914
1977
|
return;
|
|
1915
1978
|
case "pan-right":
|
|
1916
|
-
|
|
1979
|
+
panViewportByRatio(-COMPOSITE_KEYBOARD_PAN_RATIO);
|
|
1917
1980
|
return;
|
|
1918
1981
|
case "reset":
|
|
1919
1982
|
resetViewport();
|
|
@@ -1924,7 +1987,7 @@ export function CompositeChart({
|
|
|
1924
1987
|
case "zoom-out":
|
|
1925
1988
|
zoomViewport(1 / COMPOSITE_ZOOM_STEP_FACTOR);
|
|
1926
1989
|
}
|
|
1927
|
-
}, { enabled: focused && interactive });
|
|
1990
|
+
}, { enabled: focused && interactive && navigable });
|
|
1928
1991
|
|
|
1929
1992
|
const leftPadding = leftAxisWidth + (leftAxisWidth ? axisGap : 0);
|
|
1930
1993
|
const rightPadding = rightAxisWidth + (rightAxisWidth ? axisGap : 0);
|
|
@@ -1936,7 +1999,7 @@ export function CompositeChart({
|
|
|
1936
1999
|
: null;
|
|
1937
2000
|
|
|
1938
2001
|
if (!scene) {
|
|
1939
|
-
const emptyPlotHeight = Math.max(0, totalHeight - legendRows - timeAxisRows);
|
|
2002
|
+
const emptyPlotHeight = Math.max(0, totalHeight - legendRows - timeAxisRows - xMarkerRows);
|
|
1940
2003
|
return (
|
|
1941
2004
|
<Box
|
|
1942
2005
|
flexDirection="column"
|
|
@@ -1969,9 +2032,9 @@ export function CompositeChart({
|
|
|
1969
2032
|
height={emptyPlotHeight}
|
|
1970
2033
|
alignItems="center"
|
|
1971
2034
|
justifyContent="center"
|
|
1972
|
-
onMouseScroll={interactive &&
|
|
1973
|
-
cursor={interactive &&
|
|
1974
|
-
data-gloom-interactive={interactive &&
|
|
2035
|
+
onMouseScroll={interactive && navigable && navigationFrame ? handleEmptyMouseScroll : undefined}
|
|
2036
|
+
cursor={interactive && navigable && navigationFrame ? "grab" : undefined}
|
|
2037
|
+
data-gloom-interactive={interactive && navigable && navigationFrame ? "true" : undefined}
|
|
1975
2038
|
data-gloom-role="composite-chart-empty"
|
|
1976
2039
|
data-gloom-label={emptyMessage}
|
|
1977
2040
|
>
|
|
@@ -2002,9 +2065,11 @@ export function CompositeChart({
|
|
|
2002
2065
|
const timeAxisCursorPixelX = timeAxisCursorColumn === null
|
|
2003
2066
|
? null
|
|
2004
2067
|
: timeAxisCursorColumn * cellWidthPx;
|
|
2005
|
-
const timeAxisCursorLabel = scene.
|
|
2006
|
-
?
|
|
2007
|
-
:
|
|
2068
|
+
const timeAxisCursorLabel = xAxis?.formatCursor && scene.cursorXRatio !== null
|
|
2069
|
+
? xAxis.formatCursor(scene.cursorXRatio)
|
|
2070
|
+
: scene.cursorDate
|
|
2071
|
+
? formatCompositeTimeAxisDate(scene.cursorDate, scene.startTime, scene.endTime)
|
|
2072
|
+
: null;
|
|
2008
2073
|
// The crosshair labels the moving end, so the axis only adds the anchor.
|
|
2009
2074
|
const timeAxisMarkers = toolSpan
|
|
2010
2075
|
? [{
|
|
@@ -2043,7 +2108,7 @@ export function CompositeChart({
|
|
|
2043
2108
|
keyboardIndex={legendKeyboardIndex}
|
|
2044
2109
|
/>
|
|
2045
2110
|
) : null}
|
|
2046
|
-
{interactive && plotWidth > CHART_TOOLBAR_WIDTH + 4 ? (
|
|
2111
|
+
{interactive && navigable && plotWidth > CHART_TOOLBAR_WIDTH + 4 ? (
|
|
2047
2112
|
<ChartToolbar
|
|
2048
2113
|
armedTool={armedTool}
|
|
2049
2114
|
isDesktopWeb={isDesktopWeb}
|
|
@@ -2079,8 +2144,11 @@ export function CompositeChart({
|
|
|
2079
2144
|
axisGap={axisGap}
|
|
2080
2145
|
colors={resolvedColors}
|
|
2081
2146
|
interactive={interactive}
|
|
2147
|
+
navigable={navigable}
|
|
2148
|
+
formatAxisValue={formatAxisValue}
|
|
2149
|
+
remoteKind={remoteKind}
|
|
2082
2150
|
viewport={effectiveViewport!}
|
|
2083
|
-
|
|
2151
|
+
frame={navigationFrame!}
|
|
2084
2152
|
armedTool={armedTool}
|
|
2085
2153
|
drawings={drawings}
|
|
2086
2154
|
selectedDrawingId={selectedDrawingId}
|
|
@@ -2097,12 +2165,30 @@ export function CompositeChart({
|
|
|
2097
2165
|
showTextFallback={showTextFallback}
|
|
2098
2166
|
/>
|
|
2099
2167
|
))}
|
|
2168
|
+
{xMarkers.length > 0 ? (
|
|
2169
|
+
<Box
|
|
2170
|
+
position="absolute"
|
|
2171
|
+
left={leftPadding}
|
|
2172
|
+
top={legendRows}
|
|
2173
|
+
width={plotWidth}
|
|
2174
|
+
height={plotHeight}
|
|
2175
|
+
zIndex={12}
|
|
2176
|
+
style={isDesktopWeb ? { pointerEvents: "none" } : undefined}
|
|
2177
|
+
>
|
|
2178
|
+
<StaticXMarkerOverlay
|
|
2179
|
+
markers={xMarkers}
|
|
2180
|
+
width={plotWidth}
|
|
2181
|
+
height={plotHeight}
|
|
2182
|
+
fallbackColor={resolvedColors.textDim}
|
|
2183
|
+
/>
|
|
2184
|
+
</Box>
|
|
2185
|
+
) : null}
|
|
2100
2186
|
{timeAxisLayout ? (
|
|
2101
2187
|
<Box flexDirection="row" width={totalWidth} height={1}>
|
|
2102
2188
|
{leftPadding > 0 ? <Box width={leftPadding} /> : null}
|
|
2103
2189
|
<StaticXAxisLabels
|
|
2104
|
-
labels={[timeAxisLayout.text]}
|
|
2105
|
-
positionedLabels={timeAxisLayout.ticks}
|
|
2190
|
+
labels={xAxis?.labels ? [...xAxis.labels] : [timeAxisLayout.text]}
|
|
2191
|
+
positionedLabels={xAxis?.labels ? undefined : timeAxisLayout.ticks}
|
|
2106
2192
|
width={plotWidth}
|
|
2107
2193
|
color={resolvedColors.textDim}
|
|
2108
2194
|
cursorColumn={timeAxisCursorColumn}
|
|
@@ -2115,6 +2201,17 @@ export function CompositeChart({
|
|
|
2115
2201
|
{rightPadding > 0 ? <Box width={rightPadding} /> : null}
|
|
2116
2202
|
</Box>
|
|
2117
2203
|
) : null}
|
|
2204
|
+
{xMarkerRows > 0 ? (
|
|
2205
|
+
<Box flexDirection="row" width={totalWidth} height={1}>
|
|
2206
|
+
{leftPadding > 0 ? <Box width={leftPadding} /> : null}
|
|
2207
|
+
<StaticXMarkerLabels
|
|
2208
|
+
markers={xMarkers}
|
|
2209
|
+
width={plotWidth}
|
|
2210
|
+
fallbackColor={resolvedColors.textDim}
|
|
2211
|
+
/>
|
|
2212
|
+
{rightPadding > 0 ? <Box width={rightPadding} /> : null}
|
|
2213
|
+
</Box>
|
|
2214
|
+
) : null}
|
|
2118
2215
|
</Box>
|
|
2119
2216
|
);
|
|
2120
2217
|
}
|