@363045841yyt/klinechart-core 0.7.3 → 0.7.5
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 +201 -201
- package/README.zh-CN.md +201 -201
- package/dist/controllers/index.d.ts +1 -0
- package/dist/controllers/index.d.ts.map +1 -1
- package/dist/controllers/index.js +1 -0
- package/dist/controllers/index.js.map +1 -1
- package/dist/engine/chart.d.ts +11 -19
- package/dist/engine/chart.d.ts.map +1 -1
- package/dist/engine/chart.js +92 -109
- package/dist/engine/chart.js.map +1 -1
- package/dist/engine/renderers/Indicator/indicatorData.d.ts +1 -0
- package/dist/engine/renderers/Indicator/indicatorData.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/indicatorData.js +1 -1
- package/dist/engine/renderers/Indicator/indicatorData.js.map +1 -1
- package/dist/engine/renderers/webgl/candleSurface.js +47 -47
- package/dist/engine/subPaneManager.d.ts +4 -0
- package/dist/engine/subPaneManager.d.ts.map +1 -1
- package/dist/engine/subPaneManager.js +13 -0
- package/dist/engine/subPaneManager.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -2
- package/dist/version.js.map +1 -1
- package/package.json +129 -122
- package/src/__tests__/signal.test.ts +124 -124
- package/src/config/chartSettings.ts +66 -66
- package/src/controllers/__tests__/drawing.test.ts +214 -214
- package/src/controllers/__tests__/indicatorSelector.test.ts +481 -481
- package/src/controllers/__tests__/toolbar.test.ts +225 -225
- package/src/controllers/createChartController.ts +665 -665
- package/src/controllers/createDrawingController.ts +96 -96
- package/src/controllers/createIndicatorSelectorController.ts +307 -307
- package/src/controllers/createToolbarController.ts +146 -146
- package/src/controllers/index.ts +20 -19
- package/src/controllers/types.ts +284 -284
- package/src/engine/__tests__/chart.dpr.test.ts +401 -401
- package/src/engine/__tests__/paneRenderer.resize.test.ts +92 -92
- package/src/engine/chart-store.ts +121 -121
- package/src/engine/chart.d.ts +617 -617
- package/src/engine/chart.ts +2803 -2815
- package/src/engine/controller/__tests__/interaction.dpr.test.ts +259 -259
- package/src/engine/controller/interaction.ts +722 -722
- package/src/engine/controller/markerInteraction.ts +130 -130
- package/src/engine/controller/pinchTracker.ts +82 -82
- package/src/engine/controller/tooltipPosition.ts +48 -48
- package/src/engine/draw/__tests__/pixelAlign.spec.ts +176 -176
- package/src/engine/draw/pixelAlign.ts +259 -259
- package/src/engine/drawing/index.ts +655 -655
- package/src/engine/drawing/interaction.ts +842 -842
- package/src/engine/drawing/plugin.ts +343 -343
- package/src/engine/indicators/__tests__/__fixtures__/golden/atr.json +38 -38
- package/src/engine/indicators/__tests__/__fixtures__/golden/dema.json +14 -14
- package/src/engine/indicators/__tests__/__fixtures__/golden/hma.json +14 -14
- package/src/engine/indicators/__tests__/__fixtures__/golden/index.ts +55 -55
- package/src/engine/indicators/__tests__/__fixtures__/golden/kama.json +14 -14
- package/src/engine/indicators/__tests__/__fixtures__/golden/tema.json +14 -14
- package/src/engine/indicators/__tests__/__fixtures__/golden/wma.json +40 -40
- package/src/engine/indicators/__tests__/__fixtures__/synthetic.ts +65 -65
- package/src/engine/indicators/__tests__/_propertyAssertions.ts +76 -76
- package/src/engine/indicators/__tests__/atr.test.ts +153 -153
- package/src/engine/indicators/__tests__/calculators.test.ts +614 -614
- package/src/engine/indicators/__tests__/cmf-mfi.test.ts +100 -100
- package/src/engine/indicators/__tests__/dema.test.ts +73 -73
- package/src/engine/indicators/__tests__/donchian.test.ts +70 -70
- package/src/engine/indicators/__tests__/hma.test.ts +73 -73
- package/src/engine/indicators/__tests__/ichimoku.test.ts +105 -105
- package/src/engine/indicators/__tests__/kama.test.ts +80 -80
- package/src/engine/indicators/__tests__/keltner.test.ts +65 -65
- package/src/engine/indicators/__tests__/pivot-fib.test.ts +110 -110
- package/src/engine/indicators/__tests__/roc.test.ts +68 -68
- package/src/engine/indicators/__tests__/sar.test.ts +86 -86
- package/src/engine/indicators/__tests__/scheduler.test.ts +831 -831
- package/src/engine/indicators/__tests__/soa.test.ts +533 -533
- package/src/engine/indicators/__tests__/structure.test.ts +110 -110
- package/src/engine/indicators/__tests__/supertrend.test.ts +65 -65
- package/src/engine/indicators/__tests__/tema.test.ts +68 -68
- package/src/engine/indicators/__tests__/trix.test.ts +70 -70
- package/src/engine/indicators/__tests__/volatility.test.ts +117 -117
- package/src/engine/indicators/__tests__/volume.test.ts +115 -115
- package/src/engine/indicators/__tests__/volumeProfile.test.ts +74 -74
- package/src/engine/indicators/__tests__/vwap.test.ts +69 -69
- package/src/engine/indicators/__tests__/wma.test.ts +112 -112
- package/src/engine/indicators/__tests__/zones.test.ts +95 -95
- package/src/engine/indicators/atrState.ts +27 -27
- package/src/engine/indicators/bollState.ts +51 -51
- package/src/engine/indicators/calculators.ts +2593 -2593
- package/src/engine/indicators/cciState.ts +25 -25
- package/src/engine/indicators/chaikinVolState.ts +32 -32
- package/src/engine/indicators/cmfState.ts +27 -27
- package/src/engine/indicators/demaState.ts +27 -27
- package/src/engine/indicators/donchianState.ts +43 -43
- package/src/engine/indicators/eneState.ts +43 -43
- package/src/engine/indicators/expmaState.ts +43 -43
- package/src/engine/indicators/fastkState.ts +25 -25
- package/src/engine/indicators/fibState.ts +41 -41
- package/src/engine/indicators/hmaState.ts +27 -27
- package/src/engine/indicators/hvState.ts +28 -28
- package/src/engine/indicators/ichimokuState.ts +70 -70
- package/src/engine/indicators/indicator.worker.ts +169 -169
- package/src/engine/indicators/indicatorDefinitionRegistry.ts +62 -62
- package/src/engine/indicators/indicatorMetadata.ts +110 -110
- package/src/engine/indicators/indicatorRegistry.ts +106 -106
- package/src/engine/indicators/indicatorRuntime.ts +1548 -1548
- package/src/engine/indicators/kamaState.ts +34 -34
- package/src/engine/indicators/keltnerState.ts +49 -49
- package/src/engine/indicators/kstState.ts +42 -42
- package/src/engine/indicators/maState.ts +36 -36
- package/src/engine/indicators/macdState.ts +76 -76
- package/src/engine/indicators/mfiState.ts +27 -27
- package/src/engine/indicators/momState.ts +25 -25
- package/src/engine/indicators/obvState.ts +25 -25
- package/src/engine/indicators/parkinsonState.ts +28 -28
- package/src/engine/indicators/pivotState.ts +51 -51
- package/src/engine/indicators/pvtState.ts +25 -25
- package/src/engine/indicators/rocState.ts +27 -27
- package/src/engine/indicators/rsiState.ts +65 -65
- package/src/engine/indicators/sarState.ts +41 -41
- package/src/engine/indicators/scheduler.ts +1205 -1205
- package/src/engine/indicators/soa.ts +352 -352
- package/src/engine/indicators/stateComposer.ts +1262 -1262
- package/src/engine/indicators/stochState.ts +26 -26
- package/src/engine/indicators/structureState.ts +69 -69
- package/src/engine/indicators/supertrendState.ts +37 -37
- package/src/engine/indicators/temaState.ts +27 -27
- package/src/engine/indicators/trixState.ts +35 -35
- package/src/engine/indicators/vmaState.ts +27 -27
- package/src/engine/indicators/volumeProfileState.ts +63 -63
- package/src/engine/indicators/vwapState.ts +29 -29
- package/src/engine/indicators/wmaState.ts +27 -27
- package/src/engine/indicators/wmsrState.ts +25 -25
- package/src/engine/indicators/workerProtocol.ts +613 -613
- package/src/engine/indicators/zonesState.ts +47 -47
- package/src/engine/layout/pane.ts +161 -161
- package/src/engine/marker/registry.ts +265 -265
- package/src/engine/paneRenderer.ts +169 -169
- package/src/engine/renderers/Indicator/atr.ts +237 -237
- package/src/engine/renderers/Indicator/boll.ts +317 -317
- package/src/engine/renderers/Indicator/cci.ts +275 -275
- package/src/engine/renderers/Indicator/chaikinVol.ts +138 -138
- package/src/engine/renderers/Indicator/cmf.ts +137 -137
- package/src/engine/renderers/Indicator/dema.ts +136 -136
- package/src/engine/renderers/Indicator/donchian.ts +137 -137
- package/src/engine/renderers/Indicator/ene.ts +271 -271
- package/src/engine/renderers/Indicator/expma.ts +197 -197
- package/src/engine/renderers/Indicator/fastk.ts +316 -316
- package/src/engine/renderers/Indicator/fib.ts +141 -141
- package/src/engine/renderers/Indicator/hma.ts +136 -136
- package/src/engine/renderers/Indicator/hv.ts +124 -124
- package/src/engine/renderers/Indicator/ichimoku.ts +181 -181
- package/src/engine/renderers/Indicator/index.ts +241 -241
- package/src/engine/renderers/Indicator/indicatorData.ts +650 -650
- package/src/engine/renderers/Indicator/kama.ts +136 -136
- package/src/engine/renderers/Indicator/keltner.ts +137 -137
- package/src/engine/renderers/Indicator/kst.ts +302 -302
- package/src/engine/renderers/Indicator/ma.ts +200 -200
- package/src/engine/renderers/Indicator/macd.ts +477 -477
- package/src/engine/renderers/Indicator/macdLegend.ts +141 -141
- package/src/engine/renderers/Indicator/mainIndicatorLegend.ts +272 -272
- package/src/engine/renderers/Indicator/mfi.ts +142 -142
- package/src/engine/renderers/Indicator/mom.ts +311 -311
- package/src/engine/renderers/Indicator/obv.ts +123 -123
- package/src/engine/renderers/Indicator/parkinson.ts +124 -124
- package/src/engine/renderers/Indicator/pivot.ts +131 -131
- package/src/engine/renderers/Indicator/pvt.ts +123 -123
- package/src/engine/renderers/Indicator/roc.ts +143 -143
- package/src/engine/renderers/Indicator/rsi.ts +390 -390
- package/src/engine/renderers/Indicator/sar.ts +113 -113
- package/src/engine/renderers/Indicator/scale/atr_scale.ts +19 -19
- package/src/engine/renderers/Indicator/scale/cci_scale.ts +19 -19
- package/src/engine/renderers/Indicator/scale/fastk_scale.ts +19 -19
- package/src/engine/renderers/Indicator/scale/indicator_scale.ts +204 -204
- package/src/engine/renderers/Indicator/scale/kst_scale.ts +19 -19
- package/src/engine/renderers/Indicator/scale/macd_scale.ts +22 -22
- package/src/engine/renderers/Indicator/scale/mom_scale.ts +19 -19
- package/src/engine/renderers/Indicator/scale/rsi_scale.ts +19 -19
- package/src/engine/renderers/Indicator/scale/stoch_scale.ts +19 -19
- package/src/engine/renderers/Indicator/scale/volume_scale.ts +26 -26
- package/src/engine/renderers/Indicator/scale/wmsr_scale.ts +19 -19
- package/src/engine/renderers/Indicator/stoch.ts +359 -359
- package/src/engine/renderers/Indicator/structure.ts +126 -126
- package/src/engine/renderers/Indicator/subPaneConfig.ts +265 -265
- package/src/engine/renderers/Indicator/supertrend.ts +115 -115
- package/src/engine/renderers/Indicator/tema.ts +136 -136
- package/src/engine/renderers/Indicator/trix.ts +158 -158
- package/src/engine/renderers/Indicator/vma.ts +124 -124
- package/src/engine/renderers/Indicator/volumeProfile.ts +125 -125
- package/src/engine/renderers/Indicator/vwap.ts +123 -123
- package/src/engine/renderers/Indicator/wma.ts +136 -136
- package/src/engine/renderers/Indicator/wmsr.ts +328 -328
- package/src/engine/renderers/Indicator/zones.ts +104 -104
- package/src/engine/renderers/__tests__/boll.renderer.test.ts +314 -314
- package/src/engine/renderers/__tests__/ene.renderer.test.ts +305 -305
- package/src/engine/renderers/__tests__/expma.renderer.test.ts +279 -279
- package/src/engine/renderers/__tests__/ma.renderer.test.ts +426 -426
- package/src/engine/renderers/__tests__/mainIndicatorLegend.renderer.test.ts +502 -502
- package/src/engine/renderers/__tests__/yAxis.renderer.test.ts +173 -173
- package/src/engine/renderers/candle.ts +459 -459
- package/src/engine/renderers/crosshair.ts +69 -69
- package/src/engine/renderers/customMarkers.ts +162 -162
- package/src/engine/renderers/extremaMarkers.ts +246 -246
- package/src/engine/renderers/gridLines.ts +90 -90
- package/src/engine/renderers/lastPrice.ts +97 -97
- package/src/engine/renderers/paneTitle.ts +136 -136
- package/src/engine/renderers/subVolume.ts +236 -236
- package/src/engine/renderers/timeAxis.ts +121 -121
- package/src/engine/renderers/webgl/candleSurface.ts +955 -955
- package/src/engine/renderers/webgl/sharedWebGLSurface.ts +146 -146
- package/src/engine/renderers/yAxis.ts +105 -105
- package/src/engine/scale/__tests__/logFormula.spec.ts +148 -148
- package/src/engine/scale/logFormula.ts +130 -130
- package/src/engine/scale/price.ts +39 -39
- package/src/engine/scale/priceScale.ts +264 -264
- package/src/engine/subPaneManager.ts +442 -427
- package/src/engine/theme/colors.ts +642 -642
- package/src/engine/theme/fonts.ts +20 -20
- package/src/engine/utils/klineConfig.ts +49 -49
- package/src/engine/utils/tickCount.ts +11 -11
- package/src/engine/utils/tickPosition.ts +214 -214
- package/src/engine/utils/zoom.ts +83 -83
- package/src/engine/viewport/viewport.ts +67 -67
- package/src/index.ts +3 -3
- package/src/plugin/ConfigManager.ts +93 -93
- package/src/plugin/EventBus.ts +77 -77
- package/src/plugin/HookSystem.ts +106 -106
- package/src/plugin/PluginHost.ts +243 -243
- package/src/plugin/PluginRegistry.ts +92 -92
- package/src/plugin/StateStore.ts +73 -73
- package/src/plugin/index.ts +19 -19
- package/src/plugin/rendererPluginManager.ts +368 -368
- package/src/plugin/stateKeys.ts +8 -8
- package/src/plugin/types.ts +526 -526
- package/src/reactivity/index.ts +2 -2
- package/src/reactivity/signal.ts +119 -119
- package/src/semantic/controller.ts +251 -251
- package/src/semantic/drawShape.ts +260 -260
- package/src/semantic/index.ts +28 -28
- package/src/semantic/schema.json +256 -256
- package/src/semantic/types.ts +251 -251
- package/src/semantic/validator.ts +349 -349
- package/src/types/kLine.ts +13 -13
- package/src/types/price.ts +56 -56
- package/src/types/volumePrice.ts +33 -33
- package/src/utils/dateFormat.ts +208 -208
- package/src/utils/kLineDraw/axis.ts +562 -562
- package/src/utils/priceToY.ts +34 -34
- package/src/utils/volumePrice.ts +202 -202
- package/src/version.ts +1 -1
|
@@ -1,665 +1,665 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* createChartController — production ChartControllerFactory.
|
|
3
|
-
*
|
|
4
|
-
* Wraps the legacy chart engine (`src/core/chart.ts`) behind the
|
|
5
|
-
* framework-agnostic `ChartController` signal surface. Adapters
|
|
6
|
-
* (React / Vue / Angular) consume this.
|
|
7
|
-
*
|
|
8
|
-
* Boundaries owned here:
|
|
9
|
-
* - Construct the inner DOM scaffold the legacy `Chart` expects.
|
|
10
|
-
* - Bridge Chart's facade signals into controller-owned signals.
|
|
11
|
-
* - Delegate zoom / interaction / indicator / drawing methods to Chart.
|
|
12
|
-
* - Tear down DOM + listeners on dispose().
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { createSignal, type Signal } from '../reactivity'
|
|
16
|
-
import type {
|
|
17
|
-
ChartController,
|
|
18
|
-
ChartMountOptions,
|
|
19
|
-
ChartViewport,
|
|
20
|
-
DrawingToolType,
|
|
21
|
-
DrawingObject,
|
|
22
|
-
SubPaneInfo,
|
|
23
|
-
IndicatorInstance,
|
|
24
|
-
InteractionSnapshot,
|
|
25
|
-
DrawingControllerCallbacks,
|
|
26
|
-
IndicatorDefinition,
|
|
27
|
-
KLineData,
|
|
28
|
-
} from './types'
|
|
29
|
-
import type { CustomMarkerEntity } from '../engine/marker/registry'
|
|
30
|
-
import {
|
|
31
|
-
Chart,
|
|
32
|
-
type ChartOptions,
|
|
33
|
-
type Viewport as LegacyViewport,
|
|
34
|
-
type ViewportState as LegacyViewportState,
|
|
35
|
-
type IndicatorInstance as LegacyIndicatorInstance,
|
|
36
|
-
type SubPaneInfo as LegacySubPaneInfo,
|
|
37
|
-
type DrawingObject as LegacyDrawingObject,
|
|
38
|
-
type DrawingToolType as LegacyDrawingToolType,
|
|
39
|
-
type InteractionSnapshot as LegacyInteractionSnapshot,
|
|
40
|
-
} from '../engine/chart'
|
|
41
|
-
import { zoomLevelToKWidth, kGapFromKWidth } from '../engine/utils/zoom'
|
|
42
|
-
|
|
43
|
-
// Plugin-backed drawings expose `kind` instead of legacy `type`.
|
|
44
|
-
type PluginBackedDrawingObject = {
|
|
45
|
-
id: string
|
|
46
|
-
kind: string
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// ---------------------------------------------------------------------------
|
|
50
|
-
// Defaults
|
|
51
|
-
// ---------------------------------------------------------------------------
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const DEFAULT_OPTS = {
|
|
55
|
-
yPaddingPx: 20,
|
|
56
|
-
minKWidth: 1,
|
|
57
|
-
maxKWidth: 50,
|
|
58
|
-
rightAxisWidth: 0,
|
|
59
|
-
bottomAxisHeight: 24,
|
|
60
|
-
priceLabelWidth: 60,
|
|
61
|
-
zoomLevels: 20,
|
|
62
|
-
initialZoomLevel: 3,
|
|
63
|
-
} as const
|
|
64
|
-
|
|
65
|
-
const INITIAL_INTERACTION: InteractionSnapshot = {
|
|
66
|
-
crosshairPos: null,
|
|
67
|
-
crosshairIndex: null,
|
|
68
|
-
crosshairPrice: null,
|
|
69
|
-
hoveredIndex: null,
|
|
70
|
-
activePaneId: null,
|
|
71
|
-
tooltipPos: { x: 0, y: 0 },
|
|
72
|
-
tooltipAnchorPlacement: 'right-bottom',
|
|
73
|
-
hoveredMarkerData: null,
|
|
74
|
-
hoveredCustomMarker: null,
|
|
75
|
-
isDragging: false,
|
|
76
|
-
isResizingPaneBoundary: false,
|
|
77
|
-
isHoveringPaneBoundary: false,
|
|
78
|
-
hoveredPaneBoundaryId: null,
|
|
79
|
-
isHoveringRightAxis: false,
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// ---------------------------------------------------------------------------
|
|
83
|
-
// Indicator catalog (mirrors renderer ids registered in the engine)
|
|
84
|
-
// ---------------------------------------------------------------------------
|
|
85
|
-
|
|
86
|
-
const DEFAULT_INDICATOR_CATALOG: ReadonlyArray<IndicatorDefinition> = [
|
|
87
|
-
{ id: 'MA', label: 'MA', name: '移动平均线', role: 'main', params: [] },
|
|
88
|
-
{ id: 'BOLL', label: 'BOLL', name: '布林带', role: 'main', params: [] },
|
|
89
|
-
{ id: 'EXPMA', label: 'EXPMA', name: '指数平均线', role: 'main', params: [] },
|
|
90
|
-
{ id: 'ENE', label: 'ENE', name: '轨道线', role: 'main', params: [] },
|
|
91
|
-
{ id: 'SAR', label: 'SAR', name: '抛物线', role: 'main', params: [] },
|
|
92
|
-
{ id: 'SUPERTREND', label: 'SuperTrend', name: '超级趋势', role: 'main', params: [] },
|
|
93
|
-
{ id: 'STRUCTURE', label: 'Structure', name: 'SMC 结构', role: 'main', params: [] },
|
|
94
|
-
{ id: 'ZONES', label: 'Zones', name: 'SMC 区域', role: 'main', params: [] },
|
|
95
|
-
{ id: 'VOLUME', label: 'VOL', name: '成交量', role: 'sub', params: [] },
|
|
96
|
-
{ id: 'MACD', label: 'MACD', name: 'MACD', role: 'sub', params: [] },
|
|
97
|
-
{ id: 'RSI', label: 'RSI', name: '相对强弱', role: 'sub', params: [] },
|
|
98
|
-
{ id: 'CCI', label: 'CCI', name: '顺势指标', role: 'sub', params: [] },
|
|
99
|
-
{ id: 'STOCH', label: 'KDJ/STOCH', name: '随机指标', role: 'sub', params: [] },
|
|
100
|
-
{ id: 'MOM', label: 'MOM', name: '动量', role: 'sub', params: [] },
|
|
101
|
-
{ id: 'WMSR', label: 'WMSR', name: '威廉指标', role: 'sub', params: [] },
|
|
102
|
-
{ id: 'KST', label: 'KST', name: 'KST 振荡器', role: 'sub', params: [] },
|
|
103
|
-
{ id: 'FASTK', label: 'FASTK', name: '快速 K', role: 'sub', params: [] },
|
|
104
|
-
{ id: 'OBV', label: 'OBV', name: '能量潮', role: 'sub', params: [] },
|
|
105
|
-
{ id: 'VWAP', label: 'VWAP', name: '成交量加权均价', role: 'sub', params: [] },
|
|
106
|
-
{ id: 'VOLUME_PROFILE', label: 'VP', name: '成交量分布', role: 'sub', params: [] },
|
|
107
|
-
]
|
|
108
|
-
|
|
109
|
-
// ---------------------------------------------------------------------------
|
|
110
|
-
// DOM scaffolding
|
|
111
|
-
// ---------------------------------------------------------------------------
|
|
112
|
-
|
|
113
|
-
interface MountedDom {
|
|
114
|
-
container: HTMLDivElement
|
|
115
|
-
canvasLayer: HTMLDivElement
|
|
116
|
-
rightAxisLayer: HTMLDivElement
|
|
117
|
-
xAxisCanvas: HTMLCanvasElement
|
|
118
|
-
cleanup: () => void
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function mapViewportState(vp: LegacyViewportState): ChartViewport {
|
|
122
|
-
return {
|
|
123
|
-
zoomLevel: vp.zoomLevel,
|
|
124
|
-
plotWidth: vp.plotWidth,
|
|
125
|
-
plotHeight: vp.plotHeight,
|
|
126
|
-
dpr: vp.dpr,
|
|
127
|
-
visibleFrom: vp.visibleFrom,
|
|
128
|
-
visibleTo: vp.visibleTo,
|
|
129
|
-
desiredScrollLeft: vp.desiredScrollLeft,
|
|
130
|
-
kWidth: vp.kWidth,
|
|
131
|
-
kGap: vp.kGap,
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function mapIndicatorInstance(indicator: LegacyIndicatorInstance): IndicatorInstance {
|
|
136
|
-
return {
|
|
137
|
-
id: indicator.id,
|
|
138
|
-
definitionId: indicator.definitionId,
|
|
139
|
-
label: indicator.label,
|
|
140
|
-
name: indicator.name,
|
|
141
|
-
role: indicator.role,
|
|
142
|
-
paneId: indicator.paneId,
|
|
143
|
-
params: { ...indicator.params },
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function mapSubPaneInfo(subPane: LegacySubPaneInfo): SubPaneInfo {
|
|
148
|
-
return {
|
|
149
|
-
paneId: subPane.paneId,
|
|
150
|
-
indicatorId: subPane.indicatorId,
|
|
151
|
-
params: { ...subPane.params },
|
|
152
|
-
ratio: subPane.ratio,
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function mapDrawingTool(tool: LegacyDrawingToolType | null): DrawingToolType | null {
|
|
157
|
-
return tool
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function mapPluginDrawingKind(kind: PluginBackedDrawingObject['kind']): DrawingToolType {
|
|
161
|
-
switch (kind) {
|
|
162
|
-
case 'trend-line':
|
|
163
|
-
case 'ray':
|
|
164
|
-
case 'extended-line':
|
|
165
|
-
return 'trendline'
|
|
166
|
-
case 'horizontal-line':
|
|
167
|
-
case 'horizontal-ray':
|
|
168
|
-
case 'flat-line':
|
|
169
|
-
return 'horizontal'
|
|
170
|
-
default:
|
|
171
|
-
return 'trendline'
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function mapDrawingObject(drawing: LegacyDrawingObject | PluginBackedDrawingObject): DrawingObject {
|
|
176
|
-
return {
|
|
177
|
-
id: drawing.id,
|
|
178
|
-
type: 'type' in drawing
|
|
179
|
-
? (mapDrawingTool(drawing.type) ?? drawing.type)
|
|
180
|
-
: mapPluginDrawingKind(drawing.kind),
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function mapPaneRatios(ratios: Readonly<Record<string, number>>): Readonly<Record<string, number>> {
|
|
185
|
-
return { ...ratios }
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function mapInteractionRecord(
|
|
189
|
-
value: Record<string, any> | null | undefined,
|
|
190
|
-
): Record<string, unknown> | null {
|
|
191
|
-
if (!value) {
|
|
192
|
-
return null
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return { ...value }
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function mapInteractionSnapshot(snapshot: LegacyInteractionSnapshot): InteractionSnapshot {
|
|
199
|
-
return {
|
|
200
|
-
crosshairPos: snapshot.crosshairPos ? { ...snapshot.crosshairPos } : null,
|
|
201
|
-
crosshairIndex: snapshot.crosshairIndex,
|
|
202
|
-
crosshairPrice: snapshot.crosshairPrice,
|
|
203
|
-
hoveredIndex: snapshot.hoveredIndex,
|
|
204
|
-
activePaneId: snapshot.activePaneId,
|
|
205
|
-
tooltipPos: { ...snapshot.tooltipPos },
|
|
206
|
-
tooltipAnchorPlacement: snapshot.tooltipAnchorPlacement,
|
|
207
|
-
hoveredMarkerData: mapInteractionRecord(snapshot.hoveredMarkerData),
|
|
208
|
-
hoveredCustomMarker: mapInteractionRecord(snapshot.hoveredCustomMarker),
|
|
209
|
-
isDragging: snapshot.isDragging,
|
|
210
|
-
isResizingPaneBoundary: snapshot.isResizingPaneBoundary,
|
|
211
|
-
isHoveringPaneBoundary: snapshot.isHoveringPaneBoundary,
|
|
212
|
-
hoveredPaneBoundaryId: snapshot.hoveredPaneBoundaryId,
|
|
213
|
-
isHoveringRightAxis: snapshot.isHoveringRightAxis,
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function buildDom(container: HTMLElement): MountedDom {
|
|
218
|
-
const ownerDoc = container.ownerDocument
|
|
219
|
-
if (!ownerDoc) {
|
|
220
|
-
throw new Error(
|
|
221
|
-
'[createChartController] container has no ownerDocument; cannot build DOM scaffold',
|
|
222
|
-
)
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
let chartContainer: HTMLDivElement
|
|
226
|
-
let containerCreatedByUs = false
|
|
227
|
-
if (container instanceof HTMLDivElement) {
|
|
228
|
-
chartContainer = container
|
|
229
|
-
} else {
|
|
230
|
-
chartContainer = ownerDoc.createElement('div')
|
|
231
|
-
chartContainer.style.width = '100%'
|
|
232
|
-
chartContainer.style.height = '100%'
|
|
233
|
-
chartContainer.style.position = 'relative'
|
|
234
|
-
chartContainer.style.overflow = 'auto'
|
|
235
|
-
container.appendChild(chartContainer)
|
|
236
|
-
containerCreatedByUs = true
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const scrollContent = ownerDoc.createElement('div')
|
|
240
|
-
scrollContent.className = 'klc-scroll-content'
|
|
241
|
-
scrollContent.style.position = 'relative'
|
|
242
|
-
|
|
243
|
-
const canvasLayer = ownerDoc.createElement('div')
|
|
244
|
-
canvasLayer.className = 'klc-canvas-layer'
|
|
245
|
-
canvasLayer.style.position = 'sticky'
|
|
246
|
-
canvasLayer.style.top = '0'
|
|
247
|
-
canvasLayer.style.left = '0'
|
|
248
|
-
|
|
249
|
-
const xAxisCanvas = ownerDoc.createElement('canvas')
|
|
250
|
-
xAxisCanvas.className = 'klc-x-axis-canvas'
|
|
251
|
-
|
|
252
|
-
canvasLayer.appendChild(xAxisCanvas)
|
|
253
|
-
scrollContent.appendChild(canvasLayer)
|
|
254
|
-
chartContainer.appendChild(scrollContent)
|
|
255
|
-
|
|
256
|
-
const rightAxisLayer = ownerDoc.createElement('div')
|
|
257
|
-
rightAxisLayer.className = 'klc-right-axis-host'
|
|
258
|
-
rightAxisLayer.style.position = 'absolute'
|
|
259
|
-
rightAxisLayer.style.top = '0'
|
|
260
|
-
rightAxisLayer.style.right = '0'
|
|
261
|
-
chartContainer.appendChild(rightAxisLayer)
|
|
262
|
-
|
|
263
|
-
const cleanup = (): void => {
|
|
264
|
-
try {
|
|
265
|
-
scrollContent.remove()
|
|
266
|
-
rightAxisLayer.remove()
|
|
267
|
-
if (containerCreatedByUs) {
|
|
268
|
-
chartContainer.remove()
|
|
269
|
-
}
|
|
270
|
-
} catch {
|
|
271
|
-
/* DOM may already be gone — best effort */
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
return { container: chartContainer, canvasLayer, rightAxisLayer, xAxisCanvas, cleanup }
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// ---------------------------------------------------------------------------
|
|
279
|
-
// Factory
|
|
280
|
-
// ---------------------------------------------------------------------------
|
|
281
|
-
|
|
282
|
-
export function createChartController(opts: ChartMountOptions): ChartController {
|
|
283
|
-
if (!opts) {
|
|
284
|
-
throw new Error('[createChartController] opts is required')
|
|
285
|
-
}
|
|
286
|
-
if (!opts.container) {
|
|
287
|
-
throw new Error('[createChartController] opts.container must be a non-null HTMLElement')
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const mounted = buildDom(opts.container)
|
|
291
|
-
const initialZoomLevel = opts.initialZoomLevel ?? DEFAULT_OPTS.initialZoomLevel
|
|
292
|
-
const zoomLevelCount = opts.zoomLevels ?? DEFAULT_OPTS.zoomLevels
|
|
293
|
-
|
|
294
|
-
const chartOptions: ChartOptions = {
|
|
295
|
-
yPaddingPx: DEFAULT_OPTS.yPaddingPx,
|
|
296
|
-
rightAxisWidth: DEFAULT_OPTS.rightAxisWidth,
|
|
297
|
-
bottomAxisHeight: DEFAULT_OPTS.bottomAxisHeight,
|
|
298
|
-
minKWidth: DEFAULT_OPTS.minKWidth,
|
|
299
|
-
maxKWidth: DEFAULT_OPTS.maxKWidth,
|
|
300
|
-
priceLabelWidth: DEFAULT_OPTS.priceLabelWidth,
|
|
301
|
-
panes: [{ id: 'main', ratio: 1 }],
|
|
302
|
-
paneGap: 0,
|
|
303
|
-
zoomLevels: zoomLevelCount,
|
|
304
|
-
initialZoomLevel,
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
const chart = new Chart(
|
|
308
|
-
{
|
|
309
|
-
container: mounted.container,
|
|
310
|
-
canvasLayer: mounted.canvasLayer,
|
|
311
|
-
rightAxisLayer: mounted.rightAxisLayer,
|
|
312
|
-
xAxisCanvas: mounted.xAxisCanvas,
|
|
313
|
-
},
|
|
314
|
-
chartOptions,
|
|
315
|
-
)
|
|
316
|
-
|
|
317
|
-
const currentDpr = typeof window !== 'undefined' && window.devicePixelRatio > 0
|
|
318
|
-
? window.devicePixelRatio
|
|
319
|
-
: 1
|
|
320
|
-
const currentKWidth = zoomLevelToKWidth(initialZoomLevel, {
|
|
321
|
-
minKWidth: DEFAULT_OPTS.minKWidth,
|
|
322
|
-
maxKWidth: DEFAULT_OPTS.maxKWidth,
|
|
323
|
-
zoomLevelCount,
|
|
324
|
-
dpr: currentDpr,
|
|
325
|
-
})
|
|
326
|
-
const currentKGap = kGapFromKWidth(currentKWidth, currentDpr)
|
|
327
|
-
|
|
328
|
-
// -------------------------------------------------------------------
|
|
329
|
-
// Controller signals (bridge mode: subscribe to Chart's signals)
|
|
330
|
-
// -------------------------------------------------------------------
|
|
331
|
-
|
|
332
|
-
const viewport: Signal<ChartViewport> = createSignal<ChartViewport>({
|
|
333
|
-
zoomLevel: initialZoomLevel,
|
|
334
|
-
plotWidth: 0,
|
|
335
|
-
plotHeight: 0,
|
|
336
|
-
dpr: currentDpr,
|
|
337
|
-
visibleFrom: 0,
|
|
338
|
-
visibleTo: 0,
|
|
339
|
-
desiredScrollLeft: undefined,
|
|
340
|
-
kWidth: currentKWidth,
|
|
341
|
-
kGap: currentKGap,
|
|
342
|
-
})
|
|
343
|
-
|
|
344
|
-
const data: Signal<ReadonlyArray<KLineData>> = createSignal(opts.data)
|
|
345
|
-
|
|
346
|
-
const themeSignal: Signal<'light' | 'dark'> = createSignal(opts.theme ?? 'light')
|
|
347
|
-
|
|
348
|
-
const indicators: Signal<ReadonlyArray<IndicatorInstance>> = createSignal<
|
|
349
|
-
ReadonlyArray<IndicatorInstance>
|
|
350
|
-
>([])
|
|
351
|
-
const subPanes: Signal<ReadonlyArray<SubPaneInfo>> = createSignal<ReadonlyArray<SubPaneInfo>>([])
|
|
352
|
-
const drawingTool: Signal<DrawingToolType | null> = createSignal<DrawingToolType | null>(null)
|
|
353
|
-
const drawings: Signal<ReadonlyArray<DrawingObject>> = createSignal<ReadonlyArray<DrawingObject>>([])
|
|
354
|
-
const paneRatios: Signal<Readonly<Record<string, number>>> = createSignal<
|
|
355
|
-
Readonly<Record<string, number>>
|
|
356
|
-
>({})
|
|
357
|
-
const interactionState: Signal<InteractionSnapshot> = createSignal(INITIAL_INTERACTION)
|
|
358
|
-
|
|
359
|
-
// -------------------------------------------------------------------
|
|
360
|
-
// Apply initial render state + seed data
|
|
361
|
-
// -------------------------------------------------------------------
|
|
362
|
-
|
|
363
|
-
try {
|
|
364
|
-
chart.applyRenderState(currentKWidth, currentKGap, initialZoomLevel)
|
|
365
|
-
} catch {
|
|
366
|
-
/* tolerate jsdom */
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
try {
|
|
370
|
-
chart.setData([...opts.data])
|
|
371
|
-
} catch {
|
|
372
|
-
/* tolerate first-paint racing */
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// Apply initial theme if non-default
|
|
376
|
-
if (opts.theme && opts.theme !== 'light') {
|
|
377
|
-
chart.setTheme(opts.theme)
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// -------------------------------------------------------------------
|
|
381
|
-
// Signal bridges — subscribe to Chart's facade signals and forward
|
|
382
|
-
// -------------------------------------------------------------------
|
|
383
|
-
|
|
384
|
-
const unsubs: Array<() => void> = []
|
|
385
|
-
|
|
386
|
-
// viewport: after zoom/scroll through facade methods
|
|
387
|
-
unsubs.push(
|
|
388
|
-
chart.viewport.subscribe(() => {
|
|
389
|
-
viewport.set(mapViewportState(chart.viewport.peek()))
|
|
390
|
-
}),
|
|
391
|
-
)
|
|
392
|
-
|
|
393
|
-
// data
|
|
394
|
-
unsubs.push(
|
|
395
|
-
chart.data.subscribe(() => data.set(chart.data.peek())),
|
|
396
|
-
)
|
|
397
|
-
|
|
398
|
-
// theme
|
|
399
|
-
unsubs.push(
|
|
400
|
-
chart.theme.subscribe(() => themeSignal.set(chart.theme.peek())),
|
|
401
|
-
)
|
|
402
|
-
|
|
403
|
-
// indicators
|
|
404
|
-
unsubs.push(
|
|
405
|
-
chart.indicators.subscribe(() =>
|
|
406
|
-
indicators.set(chart.indicators.peek().map(mapIndicatorInstance)),
|
|
407
|
-
),
|
|
408
|
-
)
|
|
409
|
-
|
|
410
|
-
// subPanes
|
|
411
|
-
unsubs.push(
|
|
412
|
-
chart.subPanes.subscribe(() =>
|
|
413
|
-
subPanes.set(chart.subPanes.peek().map(mapSubPaneInfo)),
|
|
414
|
-
),
|
|
415
|
-
)
|
|
416
|
-
|
|
417
|
-
// drawingTool
|
|
418
|
-
unsubs.push(
|
|
419
|
-
chart.drawingTool.subscribe(() =>
|
|
420
|
-
drawingTool.set(mapDrawingTool(chart.drawingTool.peek())),
|
|
421
|
-
),
|
|
422
|
-
)
|
|
423
|
-
|
|
424
|
-
// drawings
|
|
425
|
-
unsubs.push(
|
|
426
|
-
chart.drawings.subscribe(() =>
|
|
427
|
-
drawings.set(chart.drawings.peek().map(mapDrawingObject)),
|
|
428
|
-
),
|
|
429
|
-
)
|
|
430
|
-
|
|
431
|
-
// paneRatios
|
|
432
|
-
unsubs.push(
|
|
433
|
-
chart.paneRatios.subscribe(() => paneRatios.set(mapPaneRatios(chart.paneRatios.peek()))),
|
|
434
|
-
)
|
|
435
|
-
|
|
436
|
-
// interactionState
|
|
437
|
-
unsubs.push(
|
|
438
|
-
chart.interactionState.subscribe(() =>
|
|
439
|
-
interactionState.set(mapInteractionSnapshot(chart.interactionState.peek())),
|
|
440
|
-
),
|
|
441
|
-
)
|
|
442
|
-
|
|
443
|
-
// -------------------------------------------------------------------
|
|
444
|
-
// Legacy callback for resize (chart's viewport signal doesn't fire
|
|
445
|
-
// on resize — only on zoom/scroll through facade methods)
|
|
446
|
-
// -------------------------------------------------------------------
|
|
447
|
-
|
|
448
|
-
chart.setOnViewportChange((vp: LegacyViewport) => {
|
|
449
|
-
const current = viewport.peek()
|
|
450
|
-
viewport.set({
|
|
451
|
-
...current,
|
|
452
|
-
plotWidth: vp.plotWidth,
|
|
453
|
-
plotHeight: vp.plotHeight,
|
|
454
|
-
dpr: vp.dpr > 0 ? vp.dpr : current.dpr,
|
|
455
|
-
})
|
|
456
|
-
})
|
|
457
|
-
|
|
458
|
-
// -------------------------------------------------------------------
|
|
459
|
-
// Lifecycle guard
|
|
460
|
-
// -------------------------------------------------------------------
|
|
461
|
-
|
|
462
|
-
let disposed = false
|
|
463
|
-
|
|
464
|
-
// -------------------------------------------------------------------
|
|
465
|
-
// Public methods — delegate to Chart facade
|
|
466
|
-
// -------------------------------------------------------------------
|
|
467
|
-
|
|
468
|
-
function setData(next: ReadonlyArray<KLineData>): void {
|
|
469
|
-
if (disposed) return
|
|
470
|
-
try {
|
|
471
|
-
chart.setData([...next])
|
|
472
|
-
} catch {
|
|
473
|
-
data.set([...next])
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
function appendData(next: ReadonlyArray<KLineData>): void {
|
|
478
|
-
if (disposed) return
|
|
479
|
-
const current = data.peek()
|
|
480
|
-
const merged = [...current, ...next]
|
|
481
|
-
setData(merged)
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
function setTheme(nextTheme: 'light' | 'dark'): void {
|
|
485
|
-
if (disposed) return
|
|
486
|
-
chart.setTheme(nextTheme)
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
function zoomToLevel(level: number, anchorX?: number): void {
|
|
490
|
-
if (disposed) return
|
|
491
|
-
chart.zoomToLevel(level, anchorX)
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
function zoomIn(anchorX?: number): void {
|
|
495
|
-
if (disposed) return
|
|
496
|
-
chart.zoomIn(anchorX)
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
function zoomOut(anchorX?: number): void {
|
|
500
|
-
if (disposed) return
|
|
501
|
-
chart.zoomOut(anchorX)
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
function handlePointerEvent(
|
|
505
|
-
e: PointerEvent,
|
|
506
|
-
drawingController?: DrawingControllerCallbacks,
|
|
507
|
-
): boolean {
|
|
508
|
-
if (disposed) return false
|
|
509
|
-
return chart.handlePointerEvent(e, drawingController)
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
function handleWheelEvent(e: WheelEvent): void {
|
|
513
|
-
if (disposed) return
|
|
514
|
-
chart.handleWheelEvent(e)
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
function handleScrollEvent(): void {
|
|
518
|
-
if (disposed) return
|
|
519
|
-
chart.handleScrollEvent()
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
function handlePinchZoom(delta: number, centerClientX: number): void {
|
|
523
|
-
if (disposed) return
|
|
524
|
-
chart.handlePinchZoom(delta, centerClientX)
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
function addIndicator(
|
|
528
|
-
definitionId: string,
|
|
529
|
-
role: 'main' | 'sub',
|
|
530
|
-
params?: Record<string, unknown>,
|
|
531
|
-
): string | null {
|
|
532
|
-
if (disposed) return null
|
|
533
|
-
return chart.addIndicator(definitionId, role, params)
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
function removeIndicator(instanceId: string): boolean {
|
|
537
|
-
if (disposed) return false
|
|
538
|
-
return chart.removeIndicator(instanceId)
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
function updateIndicatorParams(
|
|
542
|
-
instanceId: string,
|
|
543
|
-
params: Record<string, unknown>,
|
|
544
|
-
): boolean {
|
|
545
|
-
if (disposed) return false
|
|
546
|
-
return chart.updateIndicatorParams(instanceId, params)
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
function updateRendererConfig(name: string, config: Record<string, unknown>): void {
|
|
550
|
-
if (disposed) return
|
|
551
|
-
chart.updateRendererConfig(name, config)
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
function setDrawingTool(tool: DrawingToolType | null): void {
|
|
555
|
-
if (disposed) return
|
|
556
|
-
chart.setDrawingTool(tool)
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
function clearDrawings(): void {
|
|
560
|
-
if (disposed) return
|
|
561
|
-
chart.clearDrawings()
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
function removeDrawing(drawingId: string): void {
|
|
565
|
-
if (disposed) return
|
|
566
|
-
chart.removeDrawing(drawingId)
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
function createSubPane(paneId: string, indicatorId: string, params?: Record<string, unknown>): boolean {
|
|
570
|
-
if (disposed) return false
|
|
571
|
-
return chart.createSubPane(paneId, indicatorId as never, params as Record<string, string | number | boolean> | undefined)
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
function clearSubPanes(): void {
|
|
575
|
-
if (disposed) return
|
|
576
|
-
chart.clearSubPanes()
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
function resizeSubPane(paneId: string, deltaY: number): boolean {
|
|
580
|
-
if (disposed) return false
|
|
581
|
-
return chart.resizeSubPane(paneId, deltaY)
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
function updateCustomMarkers(markers: ReadonlyArray<CustomMarkerEntity>): void {
|
|
585
|
-
if (disposed) return
|
|
586
|
-
chart.updateCustomMarkers([...markers])
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
function clearCustomMarkers(): void {
|
|
590
|
-
if (disposed) return
|
|
591
|
-
chart.clearCustomMarkers()
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
function updateSettingsFacade(settings: Record<string, unknown>): void {
|
|
595
|
-
if (disposed) return
|
|
596
|
-
chart.updateSettingsFacade(settings)
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
function updateOptionsFacade(options: Record<string, unknown>): void {
|
|
600
|
-
if (disposed) return
|
|
601
|
-
chart.updateOptionsFacade(options)
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
function dispose(): void {
|
|
605
|
-
if (disposed) return
|
|
606
|
-
disposed = true
|
|
607
|
-
// Unsubscribe all signal bridges first
|
|
608
|
-
for (const unsub of unsubs) {
|
|
609
|
-
try {
|
|
610
|
-
unsub()
|
|
611
|
-
} catch {
|
|
612
|
-
/* best-effort */
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
try {
|
|
616
|
-
void chart.destroy()
|
|
617
|
-
} catch {
|
|
618
|
-
/* best-effort */
|
|
619
|
-
}
|
|
620
|
-
try {
|
|
621
|
-
mounted.cleanup()
|
|
622
|
-
} catch {
|
|
623
|
-
/* best-effort */
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
return {
|
|
628
|
-
viewport,
|
|
629
|
-
data,
|
|
630
|
-
theme: themeSignal,
|
|
631
|
-
indicators,
|
|
632
|
-
subPanes,
|
|
633
|
-
drawingTool,
|
|
634
|
-
drawings,
|
|
635
|
-
paneRatios,
|
|
636
|
-
interactionState,
|
|
637
|
-
catalog: DEFAULT_INDICATOR_CATALOG,
|
|
638
|
-
setData,
|
|
639
|
-
appendData,
|
|
640
|
-
updateData: setData,
|
|
641
|
-
setTheme,
|
|
642
|
-
zoomToLevel,
|
|
643
|
-
zoomIn,
|
|
644
|
-
zoomOut,
|
|
645
|
-
handlePointerEvent,
|
|
646
|
-
handleWheelEvent,
|
|
647
|
-
handleScrollEvent,
|
|
648
|
-
handlePinchZoom,
|
|
649
|
-
addIndicator,
|
|
650
|
-
removeIndicator,
|
|
651
|
-
updateIndicatorParams,
|
|
652
|
-
updateRendererConfig,
|
|
653
|
-
setDrawingTool,
|
|
654
|
-
clearDrawings,
|
|
655
|
-
removeDrawing,
|
|
656
|
-
createSubPane,
|
|
657
|
-
clearSubPanes,
|
|
658
|
-
resizeSubPane,
|
|
659
|
-
updateCustomMarkers,
|
|
660
|
-
clearCustomMarkers,
|
|
661
|
-
updateSettingsFacade,
|
|
662
|
-
updateOptionsFacade,
|
|
663
|
-
dispose,
|
|
664
|
-
}
|
|
665
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* createChartController — production ChartControllerFactory.
|
|
3
|
+
*
|
|
4
|
+
* Wraps the legacy chart engine (`src/core/chart.ts`) behind the
|
|
5
|
+
* framework-agnostic `ChartController` signal surface. Adapters
|
|
6
|
+
* (React / Vue / Angular) consume this.
|
|
7
|
+
*
|
|
8
|
+
* Boundaries owned here:
|
|
9
|
+
* - Construct the inner DOM scaffold the legacy `Chart` expects.
|
|
10
|
+
* - Bridge Chart's facade signals into controller-owned signals.
|
|
11
|
+
* - Delegate zoom / interaction / indicator / drawing methods to Chart.
|
|
12
|
+
* - Tear down DOM + listeners on dispose().
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { createSignal, type Signal } from '../reactivity'
|
|
16
|
+
import type {
|
|
17
|
+
ChartController,
|
|
18
|
+
ChartMountOptions,
|
|
19
|
+
ChartViewport,
|
|
20
|
+
DrawingToolType,
|
|
21
|
+
DrawingObject,
|
|
22
|
+
SubPaneInfo,
|
|
23
|
+
IndicatorInstance,
|
|
24
|
+
InteractionSnapshot,
|
|
25
|
+
DrawingControllerCallbacks,
|
|
26
|
+
IndicatorDefinition,
|
|
27
|
+
KLineData,
|
|
28
|
+
} from './types'
|
|
29
|
+
import type { CustomMarkerEntity } from '../engine/marker/registry'
|
|
30
|
+
import {
|
|
31
|
+
Chart,
|
|
32
|
+
type ChartOptions,
|
|
33
|
+
type Viewport as LegacyViewport,
|
|
34
|
+
type ViewportState as LegacyViewportState,
|
|
35
|
+
type IndicatorInstance as LegacyIndicatorInstance,
|
|
36
|
+
type SubPaneInfo as LegacySubPaneInfo,
|
|
37
|
+
type DrawingObject as LegacyDrawingObject,
|
|
38
|
+
type DrawingToolType as LegacyDrawingToolType,
|
|
39
|
+
type InteractionSnapshot as LegacyInteractionSnapshot,
|
|
40
|
+
} from '../engine/chart'
|
|
41
|
+
import { zoomLevelToKWidth, kGapFromKWidth } from '../engine/utils/zoom'
|
|
42
|
+
|
|
43
|
+
// Plugin-backed drawings expose `kind` instead of legacy `type`.
|
|
44
|
+
type PluginBackedDrawingObject = {
|
|
45
|
+
id: string
|
|
46
|
+
kind: string
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// Defaults
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
const DEFAULT_OPTS = {
|
|
55
|
+
yPaddingPx: 20,
|
|
56
|
+
minKWidth: 1,
|
|
57
|
+
maxKWidth: 50,
|
|
58
|
+
rightAxisWidth: 0,
|
|
59
|
+
bottomAxisHeight: 24,
|
|
60
|
+
priceLabelWidth: 60,
|
|
61
|
+
zoomLevels: 20,
|
|
62
|
+
initialZoomLevel: 3,
|
|
63
|
+
} as const
|
|
64
|
+
|
|
65
|
+
const INITIAL_INTERACTION: InteractionSnapshot = {
|
|
66
|
+
crosshairPos: null,
|
|
67
|
+
crosshairIndex: null,
|
|
68
|
+
crosshairPrice: null,
|
|
69
|
+
hoveredIndex: null,
|
|
70
|
+
activePaneId: null,
|
|
71
|
+
tooltipPos: { x: 0, y: 0 },
|
|
72
|
+
tooltipAnchorPlacement: 'right-bottom',
|
|
73
|
+
hoveredMarkerData: null,
|
|
74
|
+
hoveredCustomMarker: null,
|
|
75
|
+
isDragging: false,
|
|
76
|
+
isResizingPaneBoundary: false,
|
|
77
|
+
isHoveringPaneBoundary: false,
|
|
78
|
+
hoveredPaneBoundaryId: null,
|
|
79
|
+
isHoveringRightAxis: false,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// Indicator catalog (mirrors renderer ids registered in the engine)
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
const DEFAULT_INDICATOR_CATALOG: ReadonlyArray<IndicatorDefinition> = [
|
|
87
|
+
{ id: 'MA', label: 'MA', name: '移动平均线', role: 'main', params: [] },
|
|
88
|
+
{ id: 'BOLL', label: 'BOLL', name: '布林带', role: 'main', params: [] },
|
|
89
|
+
{ id: 'EXPMA', label: 'EXPMA', name: '指数平均线', role: 'main', params: [] },
|
|
90
|
+
{ id: 'ENE', label: 'ENE', name: '轨道线', role: 'main', params: [] },
|
|
91
|
+
{ id: 'SAR', label: 'SAR', name: '抛物线', role: 'main', params: [] },
|
|
92
|
+
{ id: 'SUPERTREND', label: 'SuperTrend', name: '超级趋势', role: 'main', params: [] },
|
|
93
|
+
{ id: 'STRUCTURE', label: 'Structure', name: 'SMC 结构', role: 'main', params: [] },
|
|
94
|
+
{ id: 'ZONES', label: 'Zones', name: 'SMC 区域', role: 'main', params: [] },
|
|
95
|
+
{ id: 'VOLUME', label: 'VOL', name: '成交量', role: 'sub', params: [] },
|
|
96
|
+
{ id: 'MACD', label: 'MACD', name: 'MACD', role: 'sub', params: [] },
|
|
97
|
+
{ id: 'RSI', label: 'RSI', name: '相对强弱', role: 'sub', params: [] },
|
|
98
|
+
{ id: 'CCI', label: 'CCI', name: '顺势指标', role: 'sub', params: [] },
|
|
99
|
+
{ id: 'STOCH', label: 'KDJ/STOCH', name: '随机指标', role: 'sub', params: [] },
|
|
100
|
+
{ id: 'MOM', label: 'MOM', name: '动量', role: 'sub', params: [] },
|
|
101
|
+
{ id: 'WMSR', label: 'WMSR', name: '威廉指标', role: 'sub', params: [] },
|
|
102
|
+
{ id: 'KST', label: 'KST', name: 'KST 振荡器', role: 'sub', params: [] },
|
|
103
|
+
{ id: 'FASTK', label: 'FASTK', name: '快速 K', role: 'sub', params: [] },
|
|
104
|
+
{ id: 'OBV', label: 'OBV', name: '能量潮', role: 'sub', params: [] },
|
|
105
|
+
{ id: 'VWAP', label: 'VWAP', name: '成交量加权均价', role: 'sub', params: [] },
|
|
106
|
+
{ id: 'VOLUME_PROFILE', label: 'VP', name: '成交量分布', role: 'sub', params: [] },
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
// DOM scaffolding
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
interface MountedDom {
|
|
114
|
+
container: HTMLDivElement
|
|
115
|
+
canvasLayer: HTMLDivElement
|
|
116
|
+
rightAxisLayer: HTMLDivElement
|
|
117
|
+
xAxisCanvas: HTMLCanvasElement
|
|
118
|
+
cleanup: () => void
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function mapViewportState(vp: LegacyViewportState): ChartViewport {
|
|
122
|
+
return {
|
|
123
|
+
zoomLevel: vp.zoomLevel,
|
|
124
|
+
plotWidth: vp.plotWidth,
|
|
125
|
+
plotHeight: vp.plotHeight,
|
|
126
|
+
dpr: vp.dpr,
|
|
127
|
+
visibleFrom: vp.visibleFrom,
|
|
128
|
+
visibleTo: vp.visibleTo,
|
|
129
|
+
desiredScrollLeft: vp.desiredScrollLeft,
|
|
130
|
+
kWidth: vp.kWidth,
|
|
131
|
+
kGap: vp.kGap,
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function mapIndicatorInstance(indicator: LegacyIndicatorInstance): IndicatorInstance {
|
|
136
|
+
return {
|
|
137
|
+
id: indicator.id,
|
|
138
|
+
definitionId: indicator.definitionId,
|
|
139
|
+
label: indicator.label,
|
|
140
|
+
name: indicator.name,
|
|
141
|
+
role: indicator.role,
|
|
142
|
+
paneId: indicator.paneId,
|
|
143
|
+
params: { ...indicator.params },
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function mapSubPaneInfo(subPane: LegacySubPaneInfo): SubPaneInfo {
|
|
148
|
+
return {
|
|
149
|
+
paneId: subPane.paneId,
|
|
150
|
+
indicatorId: subPane.indicatorId,
|
|
151
|
+
params: { ...subPane.params },
|
|
152
|
+
ratio: subPane.ratio,
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function mapDrawingTool(tool: LegacyDrawingToolType | null): DrawingToolType | null {
|
|
157
|
+
return tool
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function mapPluginDrawingKind(kind: PluginBackedDrawingObject['kind']): DrawingToolType {
|
|
161
|
+
switch (kind) {
|
|
162
|
+
case 'trend-line':
|
|
163
|
+
case 'ray':
|
|
164
|
+
case 'extended-line':
|
|
165
|
+
return 'trendline'
|
|
166
|
+
case 'horizontal-line':
|
|
167
|
+
case 'horizontal-ray':
|
|
168
|
+
case 'flat-line':
|
|
169
|
+
return 'horizontal'
|
|
170
|
+
default:
|
|
171
|
+
return 'trendline'
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function mapDrawingObject(drawing: LegacyDrawingObject | PluginBackedDrawingObject): DrawingObject {
|
|
176
|
+
return {
|
|
177
|
+
id: drawing.id,
|
|
178
|
+
type: 'type' in drawing
|
|
179
|
+
? (mapDrawingTool(drawing.type) ?? drawing.type)
|
|
180
|
+
: mapPluginDrawingKind(drawing.kind),
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function mapPaneRatios(ratios: Readonly<Record<string, number>>): Readonly<Record<string, number>> {
|
|
185
|
+
return { ...ratios }
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function mapInteractionRecord(
|
|
189
|
+
value: Record<string, any> | null | undefined,
|
|
190
|
+
): Record<string, unknown> | null {
|
|
191
|
+
if (!value) {
|
|
192
|
+
return null
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return { ...value }
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function mapInteractionSnapshot(snapshot: LegacyInteractionSnapshot): InteractionSnapshot {
|
|
199
|
+
return {
|
|
200
|
+
crosshairPos: snapshot.crosshairPos ? { ...snapshot.crosshairPos } : null,
|
|
201
|
+
crosshairIndex: snapshot.crosshairIndex,
|
|
202
|
+
crosshairPrice: snapshot.crosshairPrice,
|
|
203
|
+
hoveredIndex: snapshot.hoveredIndex,
|
|
204
|
+
activePaneId: snapshot.activePaneId,
|
|
205
|
+
tooltipPos: { ...snapshot.tooltipPos },
|
|
206
|
+
tooltipAnchorPlacement: snapshot.tooltipAnchorPlacement,
|
|
207
|
+
hoveredMarkerData: mapInteractionRecord(snapshot.hoveredMarkerData),
|
|
208
|
+
hoveredCustomMarker: mapInteractionRecord(snapshot.hoveredCustomMarker),
|
|
209
|
+
isDragging: snapshot.isDragging,
|
|
210
|
+
isResizingPaneBoundary: snapshot.isResizingPaneBoundary,
|
|
211
|
+
isHoveringPaneBoundary: snapshot.isHoveringPaneBoundary,
|
|
212
|
+
hoveredPaneBoundaryId: snapshot.hoveredPaneBoundaryId,
|
|
213
|
+
isHoveringRightAxis: snapshot.isHoveringRightAxis,
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function buildDom(container: HTMLElement): MountedDom {
|
|
218
|
+
const ownerDoc = container.ownerDocument
|
|
219
|
+
if (!ownerDoc) {
|
|
220
|
+
throw new Error(
|
|
221
|
+
'[createChartController] container has no ownerDocument; cannot build DOM scaffold',
|
|
222
|
+
)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
let chartContainer: HTMLDivElement
|
|
226
|
+
let containerCreatedByUs = false
|
|
227
|
+
if (container instanceof HTMLDivElement) {
|
|
228
|
+
chartContainer = container
|
|
229
|
+
} else {
|
|
230
|
+
chartContainer = ownerDoc.createElement('div')
|
|
231
|
+
chartContainer.style.width = '100%'
|
|
232
|
+
chartContainer.style.height = '100%'
|
|
233
|
+
chartContainer.style.position = 'relative'
|
|
234
|
+
chartContainer.style.overflow = 'auto'
|
|
235
|
+
container.appendChild(chartContainer)
|
|
236
|
+
containerCreatedByUs = true
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const scrollContent = ownerDoc.createElement('div')
|
|
240
|
+
scrollContent.className = 'klc-scroll-content'
|
|
241
|
+
scrollContent.style.position = 'relative'
|
|
242
|
+
|
|
243
|
+
const canvasLayer = ownerDoc.createElement('div')
|
|
244
|
+
canvasLayer.className = 'klc-canvas-layer'
|
|
245
|
+
canvasLayer.style.position = 'sticky'
|
|
246
|
+
canvasLayer.style.top = '0'
|
|
247
|
+
canvasLayer.style.left = '0'
|
|
248
|
+
|
|
249
|
+
const xAxisCanvas = ownerDoc.createElement('canvas')
|
|
250
|
+
xAxisCanvas.className = 'klc-x-axis-canvas'
|
|
251
|
+
|
|
252
|
+
canvasLayer.appendChild(xAxisCanvas)
|
|
253
|
+
scrollContent.appendChild(canvasLayer)
|
|
254
|
+
chartContainer.appendChild(scrollContent)
|
|
255
|
+
|
|
256
|
+
const rightAxisLayer = ownerDoc.createElement('div')
|
|
257
|
+
rightAxisLayer.className = 'klc-right-axis-host'
|
|
258
|
+
rightAxisLayer.style.position = 'absolute'
|
|
259
|
+
rightAxisLayer.style.top = '0'
|
|
260
|
+
rightAxisLayer.style.right = '0'
|
|
261
|
+
chartContainer.appendChild(rightAxisLayer)
|
|
262
|
+
|
|
263
|
+
const cleanup = (): void => {
|
|
264
|
+
try {
|
|
265
|
+
scrollContent.remove()
|
|
266
|
+
rightAxisLayer.remove()
|
|
267
|
+
if (containerCreatedByUs) {
|
|
268
|
+
chartContainer.remove()
|
|
269
|
+
}
|
|
270
|
+
} catch {
|
|
271
|
+
/* DOM may already be gone — best effort */
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return { container: chartContainer, canvasLayer, rightAxisLayer, xAxisCanvas, cleanup }
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ---------------------------------------------------------------------------
|
|
279
|
+
// Factory
|
|
280
|
+
// ---------------------------------------------------------------------------
|
|
281
|
+
|
|
282
|
+
export function createChartController(opts: ChartMountOptions): ChartController {
|
|
283
|
+
if (!opts) {
|
|
284
|
+
throw new Error('[createChartController] opts is required')
|
|
285
|
+
}
|
|
286
|
+
if (!opts.container) {
|
|
287
|
+
throw new Error('[createChartController] opts.container must be a non-null HTMLElement')
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const mounted = buildDom(opts.container)
|
|
291
|
+
const initialZoomLevel = opts.initialZoomLevel ?? DEFAULT_OPTS.initialZoomLevel
|
|
292
|
+
const zoomLevelCount = opts.zoomLevels ?? DEFAULT_OPTS.zoomLevels
|
|
293
|
+
|
|
294
|
+
const chartOptions: ChartOptions = {
|
|
295
|
+
yPaddingPx: DEFAULT_OPTS.yPaddingPx,
|
|
296
|
+
rightAxisWidth: DEFAULT_OPTS.rightAxisWidth,
|
|
297
|
+
bottomAxisHeight: DEFAULT_OPTS.bottomAxisHeight,
|
|
298
|
+
minKWidth: DEFAULT_OPTS.minKWidth,
|
|
299
|
+
maxKWidth: DEFAULT_OPTS.maxKWidth,
|
|
300
|
+
priceLabelWidth: DEFAULT_OPTS.priceLabelWidth,
|
|
301
|
+
panes: [{ id: 'main', ratio: 1 }],
|
|
302
|
+
paneGap: 0,
|
|
303
|
+
zoomLevels: zoomLevelCount,
|
|
304
|
+
initialZoomLevel,
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const chart = new Chart(
|
|
308
|
+
{
|
|
309
|
+
container: mounted.container,
|
|
310
|
+
canvasLayer: mounted.canvasLayer,
|
|
311
|
+
rightAxisLayer: mounted.rightAxisLayer,
|
|
312
|
+
xAxisCanvas: mounted.xAxisCanvas,
|
|
313
|
+
},
|
|
314
|
+
chartOptions,
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
const currentDpr = typeof window !== 'undefined' && window.devicePixelRatio > 0
|
|
318
|
+
? window.devicePixelRatio
|
|
319
|
+
: 1
|
|
320
|
+
const currentKWidth = zoomLevelToKWidth(initialZoomLevel, {
|
|
321
|
+
minKWidth: DEFAULT_OPTS.minKWidth,
|
|
322
|
+
maxKWidth: DEFAULT_OPTS.maxKWidth,
|
|
323
|
+
zoomLevelCount,
|
|
324
|
+
dpr: currentDpr,
|
|
325
|
+
})
|
|
326
|
+
const currentKGap = kGapFromKWidth(currentKWidth, currentDpr)
|
|
327
|
+
|
|
328
|
+
// -------------------------------------------------------------------
|
|
329
|
+
// Controller signals (bridge mode: subscribe to Chart's signals)
|
|
330
|
+
// -------------------------------------------------------------------
|
|
331
|
+
|
|
332
|
+
const viewport: Signal<ChartViewport> = createSignal<ChartViewport>({
|
|
333
|
+
zoomLevel: initialZoomLevel,
|
|
334
|
+
plotWidth: 0,
|
|
335
|
+
plotHeight: 0,
|
|
336
|
+
dpr: currentDpr,
|
|
337
|
+
visibleFrom: 0,
|
|
338
|
+
visibleTo: 0,
|
|
339
|
+
desiredScrollLeft: undefined,
|
|
340
|
+
kWidth: currentKWidth,
|
|
341
|
+
kGap: currentKGap,
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
const data: Signal<ReadonlyArray<KLineData>> = createSignal(opts.data)
|
|
345
|
+
|
|
346
|
+
const themeSignal: Signal<'light' | 'dark'> = createSignal(opts.theme ?? 'light')
|
|
347
|
+
|
|
348
|
+
const indicators: Signal<ReadonlyArray<IndicatorInstance>> = createSignal<
|
|
349
|
+
ReadonlyArray<IndicatorInstance>
|
|
350
|
+
>([])
|
|
351
|
+
const subPanes: Signal<ReadonlyArray<SubPaneInfo>> = createSignal<ReadonlyArray<SubPaneInfo>>([])
|
|
352
|
+
const drawingTool: Signal<DrawingToolType | null> = createSignal<DrawingToolType | null>(null)
|
|
353
|
+
const drawings: Signal<ReadonlyArray<DrawingObject>> = createSignal<ReadonlyArray<DrawingObject>>([])
|
|
354
|
+
const paneRatios: Signal<Readonly<Record<string, number>>> = createSignal<
|
|
355
|
+
Readonly<Record<string, number>>
|
|
356
|
+
>({})
|
|
357
|
+
const interactionState: Signal<InteractionSnapshot> = createSignal(INITIAL_INTERACTION)
|
|
358
|
+
|
|
359
|
+
// -------------------------------------------------------------------
|
|
360
|
+
// Apply initial render state + seed data
|
|
361
|
+
// -------------------------------------------------------------------
|
|
362
|
+
|
|
363
|
+
try {
|
|
364
|
+
chart.applyRenderState(currentKWidth, currentKGap, initialZoomLevel)
|
|
365
|
+
} catch {
|
|
366
|
+
/* tolerate jsdom */
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
try {
|
|
370
|
+
chart.setData([...opts.data])
|
|
371
|
+
} catch {
|
|
372
|
+
/* tolerate first-paint racing */
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Apply initial theme if non-default
|
|
376
|
+
if (opts.theme && opts.theme !== 'light') {
|
|
377
|
+
chart.setTheme(opts.theme)
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// -------------------------------------------------------------------
|
|
381
|
+
// Signal bridges — subscribe to Chart's facade signals and forward
|
|
382
|
+
// -------------------------------------------------------------------
|
|
383
|
+
|
|
384
|
+
const unsubs: Array<() => void> = []
|
|
385
|
+
|
|
386
|
+
// viewport: after zoom/scroll through facade methods
|
|
387
|
+
unsubs.push(
|
|
388
|
+
chart.viewport.subscribe(() => {
|
|
389
|
+
viewport.set(mapViewportState(chart.viewport.peek()))
|
|
390
|
+
}),
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
// data
|
|
394
|
+
unsubs.push(
|
|
395
|
+
chart.data.subscribe(() => data.set(chart.data.peek())),
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
// theme
|
|
399
|
+
unsubs.push(
|
|
400
|
+
chart.theme.subscribe(() => themeSignal.set(chart.theme.peek())),
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
// indicators
|
|
404
|
+
unsubs.push(
|
|
405
|
+
chart.indicators.subscribe(() =>
|
|
406
|
+
indicators.set(chart.indicators.peek().map(mapIndicatorInstance)),
|
|
407
|
+
),
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
// subPanes
|
|
411
|
+
unsubs.push(
|
|
412
|
+
chart.subPanes.subscribe(() =>
|
|
413
|
+
subPanes.set(chart.subPanes.peek().map(mapSubPaneInfo)),
|
|
414
|
+
),
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
// drawingTool
|
|
418
|
+
unsubs.push(
|
|
419
|
+
chart.drawingTool.subscribe(() =>
|
|
420
|
+
drawingTool.set(mapDrawingTool(chart.drawingTool.peek())),
|
|
421
|
+
),
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
// drawings
|
|
425
|
+
unsubs.push(
|
|
426
|
+
chart.drawings.subscribe(() =>
|
|
427
|
+
drawings.set(chart.drawings.peek().map(mapDrawingObject)),
|
|
428
|
+
),
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
// paneRatios
|
|
432
|
+
unsubs.push(
|
|
433
|
+
chart.paneRatios.subscribe(() => paneRatios.set(mapPaneRatios(chart.paneRatios.peek()))),
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
// interactionState
|
|
437
|
+
unsubs.push(
|
|
438
|
+
chart.interactionState.subscribe(() =>
|
|
439
|
+
interactionState.set(mapInteractionSnapshot(chart.interactionState.peek())),
|
|
440
|
+
),
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
// -------------------------------------------------------------------
|
|
444
|
+
// Legacy callback for resize (chart's viewport signal doesn't fire
|
|
445
|
+
// on resize — only on zoom/scroll through facade methods)
|
|
446
|
+
// -------------------------------------------------------------------
|
|
447
|
+
|
|
448
|
+
chart.setOnViewportChange((vp: LegacyViewport) => {
|
|
449
|
+
const current = viewport.peek()
|
|
450
|
+
viewport.set({
|
|
451
|
+
...current,
|
|
452
|
+
plotWidth: vp.plotWidth,
|
|
453
|
+
plotHeight: vp.plotHeight,
|
|
454
|
+
dpr: vp.dpr > 0 ? vp.dpr : current.dpr,
|
|
455
|
+
})
|
|
456
|
+
})
|
|
457
|
+
|
|
458
|
+
// -------------------------------------------------------------------
|
|
459
|
+
// Lifecycle guard
|
|
460
|
+
// -------------------------------------------------------------------
|
|
461
|
+
|
|
462
|
+
let disposed = false
|
|
463
|
+
|
|
464
|
+
// -------------------------------------------------------------------
|
|
465
|
+
// Public methods — delegate to Chart facade
|
|
466
|
+
// -------------------------------------------------------------------
|
|
467
|
+
|
|
468
|
+
function setData(next: ReadonlyArray<KLineData>): void {
|
|
469
|
+
if (disposed) return
|
|
470
|
+
try {
|
|
471
|
+
chart.setData([...next])
|
|
472
|
+
} catch {
|
|
473
|
+
data.set([...next])
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function appendData(next: ReadonlyArray<KLineData>): void {
|
|
478
|
+
if (disposed) return
|
|
479
|
+
const current = data.peek()
|
|
480
|
+
const merged = [...current, ...next]
|
|
481
|
+
setData(merged)
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function setTheme(nextTheme: 'light' | 'dark'): void {
|
|
485
|
+
if (disposed) return
|
|
486
|
+
chart.setTheme(nextTheme)
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function zoomToLevel(level: number, anchorX?: number): void {
|
|
490
|
+
if (disposed) return
|
|
491
|
+
chart.zoomToLevel(level, anchorX)
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function zoomIn(anchorX?: number): void {
|
|
495
|
+
if (disposed) return
|
|
496
|
+
chart.zoomIn(anchorX)
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function zoomOut(anchorX?: number): void {
|
|
500
|
+
if (disposed) return
|
|
501
|
+
chart.zoomOut(anchorX)
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function handlePointerEvent(
|
|
505
|
+
e: PointerEvent,
|
|
506
|
+
drawingController?: DrawingControllerCallbacks,
|
|
507
|
+
): boolean {
|
|
508
|
+
if (disposed) return false
|
|
509
|
+
return chart.handlePointerEvent(e, drawingController)
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function handleWheelEvent(e: WheelEvent): void {
|
|
513
|
+
if (disposed) return
|
|
514
|
+
chart.handleWheelEvent(e)
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function handleScrollEvent(): void {
|
|
518
|
+
if (disposed) return
|
|
519
|
+
chart.handleScrollEvent()
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function handlePinchZoom(delta: number, centerClientX: number): void {
|
|
523
|
+
if (disposed) return
|
|
524
|
+
chart.handlePinchZoom(delta, centerClientX)
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function addIndicator(
|
|
528
|
+
definitionId: string,
|
|
529
|
+
role: 'main' | 'sub',
|
|
530
|
+
params?: Record<string, unknown>,
|
|
531
|
+
): string | null {
|
|
532
|
+
if (disposed) return null
|
|
533
|
+
return chart.addIndicator(definitionId, role, params)
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function removeIndicator(instanceId: string): boolean {
|
|
537
|
+
if (disposed) return false
|
|
538
|
+
return chart.removeIndicator(instanceId)
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function updateIndicatorParams(
|
|
542
|
+
instanceId: string,
|
|
543
|
+
params: Record<string, unknown>,
|
|
544
|
+
): boolean {
|
|
545
|
+
if (disposed) return false
|
|
546
|
+
return chart.updateIndicatorParams(instanceId, params)
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function updateRendererConfig(name: string, config: Record<string, unknown>): void {
|
|
550
|
+
if (disposed) return
|
|
551
|
+
chart.updateRendererConfig(name, config)
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function setDrawingTool(tool: DrawingToolType | null): void {
|
|
555
|
+
if (disposed) return
|
|
556
|
+
chart.setDrawingTool(tool)
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function clearDrawings(): void {
|
|
560
|
+
if (disposed) return
|
|
561
|
+
chart.clearDrawings()
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function removeDrawing(drawingId: string): void {
|
|
565
|
+
if (disposed) return
|
|
566
|
+
chart.removeDrawing(drawingId)
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function createSubPane(paneId: string, indicatorId: string, params?: Record<string, unknown>): boolean {
|
|
570
|
+
if (disposed) return false
|
|
571
|
+
return chart.createSubPane(paneId, indicatorId as never, params as Record<string, string | number | boolean> | undefined)
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function clearSubPanes(): void {
|
|
575
|
+
if (disposed) return
|
|
576
|
+
chart.clearSubPanes()
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function resizeSubPane(paneId: string, deltaY: number): boolean {
|
|
580
|
+
if (disposed) return false
|
|
581
|
+
return chart.resizeSubPane(paneId, deltaY)
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function updateCustomMarkers(markers: ReadonlyArray<CustomMarkerEntity>): void {
|
|
585
|
+
if (disposed) return
|
|
586
|
+
chart.updateCustomMarkers([...markers])
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function clearCustomMarkers(): void {
|
|
590
|
+
if (disposed) return
|
|
591
|
+
chart.clearCustomMarkers()
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
function updateSettingsFacade(settings: Record<string, unknown>): void {
|
|
595
|
+
if (disposed) return
|
|
596
|
+
chart.updateSettingsFacade(settings)
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function updateOptionsFacade(options: Record<string, unknown>): void {
|
|
600
|
+
if (disposed) return
|
|
601
|
+
chart.updateOptionsFacade(options)
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function dispose(): void {
|
|
605
|
+
if (disposed) return
|
|
606
|
+
disposed = true
|
|
607
|
+
// Unsubscribe all signal bridges first
|
|
608
|
+
for (const unsub of unsubs) {
|
|
609
|
+
try {
|
|
610
|
+
unsub()
|
|
611
|
+
} catch {
|
|
612
|
+
/* best-effort */
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
try {
|
|
616
|
+
void chart.destroy()
|
|
617
|
+
} catch {
|
|
618
|
+
/* best-effort */
|
|
619
|
+
}
|
|
620
|
+
try {
|
|
621
|
+
mounted.cleanup()
|
|
622
|
+
} catch {
|
|
623
|
+
/* best-effort */
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
return {
|
|
628
|
+
viewport,
|
|
629
|
+
data,
|
|
630
|
+
theme: themeSignal,
|
|
631
|
+
indicators,
|
|
632
|
+
subPanes,
|
|
633
|
+
drawingTool,
|
|
634
|
+
drawings,
|
|
635
|
+
paneRatios,
|
|
636
|
+
interactionState,
|
|
637
|
+
catalog: DEFAULT_INDICATOR_CATALOG,
|
|
638
|
+
setData,
|
|
639
|
+
appendData,
|
|
640
|
+
updateData: setData,
|
|
641
|
+
setTheme,
|
|
642
|
+
zoomToLevel,
|
|
643
|
+
zoomIn,
|
|
644
|
+
zoomOut,
|
|
645
|
+
handlePointerEvent,
|
|
646
|
+
handleWheelEvent,
|
|
647
|
+
handleScrollEvent,
|
|
648
|
+
handlePinchZoom,
|
|
649
|
+
addIndicator,
|
|
650
|
+
removeIndicator,
|
|
651
|
+
updateIndicatorParams,
|
|
652
|
+
updateRendererConfig,
|
|
653
|
+
setDrawingTool,
|
|
654
|
+
clearDrawings,
|
|
655
|
+
removeDrawing,
|
|
656
|
+
createSubPane,
|
|
657
|
+
clearSubPanes,
|
|
658
|
+
resizeSubPane,
|
|
659
|
+
updateCustomMarkers,
|
|
660
|
+
clearCustomMarkers,
|
|
661
|
+
updateSettingsFacade,
|
|
662
|
+
updateOptionsFacade,
|
|
663
|
+
dispose,
|
|
664
|
+
}
|
|
665
|
+
}
|