@363045841yyt/klinechart-core 0.8.14 → 0.8.15-alpha.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 +5 -4
- package/README.zh-CN.md +44 -43
- package/dist/config/chartSettings.d.ts +13 -0
- package/dist/config/chartSettings.d.ts.map +1 -1
- package/dist/config/chartSettings.js +11 -0
- package/dist/config/chartSettings.js.map +1 -1
- package/dist/controllers/createChartController.d.ts.map +1 -1
- package/dist/controllers/createChartController.js +0 -1
- package/dist/controllers/createChartController.js.map +1 -1
- package/dist/controllers/types.d.ts +2 -2
- package/dist/controllers/types.d.ts.map +1 -1
- package/dist/data-fetchers/baostock.js +1 -1
- package/dist/data-fetchers/baostock.js.map +1 -1
- package/dist/data-fetchers/dataBuffer.d.ts +4 -1
- package/dist/data-fetchers/dataBuffer.d.ts.map +1 -1
- package/dist/data-fetchers/dataBuffer.js +24 -9
- package/dist/data-fetchers/dataBuffer.js.map +1 -1
- package/dist/data-fetchers/gotdx.js +2 -2
- package/dist/data-fetchers/gotdx.js.map +1 -1
- package/dist/data-fetchers/tradingview.js +1 -1
- package/dist/data-fetchers/tradingview.js.map +1 -1
- package/dist/engine/chart.d.ts +9 -16
- package/dist/engine/chart.d.ts.map +1 -1
- package/dist/engine/chart.js +158 -124
- package/dist/engine/chart.js.map +1 -1
- package/dist/engine/controller/interaction.d.ts +11 -4
- package/dist/engine/controller/interaction.d.ts.map +1 -1
- package/dist/engine/controller/interaction.js +94 -17
- package/dist/engine/controller/interaction.js.map +1 -1
- package/dist/engine/controller/tooltipPosition.d.ts +3 -0
- package/dist/engine/controller/tooltipPosition.d.ts.map +1 -1
- package/dist/engine/controller/tooltipPosition.js +15 -1
- package/dist/engine/controller/tooltipPosition.js.map +1 -1
- package/dist/engine/data/chartDataManager.d.ts +18 -6
- package/dist/engine/data/chartDataManager.d.ts.map +1 -1
- package/dist/engine/data/chartDataManager.js +149 -51
- package/dist/engine/data/chartDataManager.js.map +1 -1
- package/dist/engine/data/comparisonManager.d.ts +0 -1
- package/dist/engine/data/comparisonManager.d.ts.map +1 -1
- package/dist/engine/data/comparisonManager.js +8 -8
- package/dist/engine/data/comparisonManager.js.map +1 -1
- package/dist/engine/data/incrementalLoadHint.d.ts +6 -5
- package/dist/engine/data/incrementalLoadHint.d.ts.map +1 -1
- package/dist/engine/data/incrementalLoadHint.js +76 -58
- package/dist/engine/data/incrementalLoadHint.js.map +1 -1
- package/dist/engine/data/scrollCompensator.d.ts +1 -0
- package/dist/engine/data/scrollCompensator.d.ts.map +1 -1
- package/dist/engine/data/scrollCompensator.js +2 -2
- package/dist/engine/data/scrollCompensator.js.map +1 -1
- package/dist/engine/indicators/chartIndicatorManager.d.ts +4 -2
- package/dist/engine/indicators/chartIndicatorManager.d.ts.map +1 -1
- package/dist/engine/indicators/chartIndicatorManager.js +1 -29
- package/dist/engine/indicators/chartIndicatorManager.js.map +1 -1
- package/dist/engine/indicators/scheduler.d.ts +5 -0
- package/dist/engine/indicators/scheduler.d.ts.map +1 -1
- package/dist/engine/indicators/scheduler.js +14 -8
- package/dist/engine/indicators/scheduler.js.map +1 -1
- package/dist/engine/render/chartRenderer.d.ts +1 -0
- package/dist/engine/render/chartRenderer.d.ts.map +1 -1
- package/dist/engine/render/chartRenderer.js +13 -9
- package/dist/engine/render/chartRenderer.js.map +1 -1
- package/dist/engine/renderers/candle.js +5 -2
- package/dist/engine/renderers/candle.js.map +1 -1
- package/dist/engine/renderers/extremaMarkers.d.ts.map +1 -1
- package/dist/engine/renderers/extremaMarkers.js +2 -1
- package/dist/engine/renderers/extremaMarkers.js.map +1 -1
- package/dist/engine/renderers/leftYAxis.d.ts.map +1 -1
- package/dist/engine/renderers/leftYAxis.js +1 -0
- package/dist/engine/renderers/leftYAxis.js.map +1 -1
- package/dist/engine/renderers/subVolume.js +8 -9
- package/dist/engine/renderers/subVolume.js.map +1 -1
- package/dist/engine/renderers/yAxis.d.ts.map +1 -1
- package/dist/engine/renderers/yAxis.js +1 -0
- package/dist/engine/renderers/yAxis.js.map +1 -1
- package/dist/engine/utils/chartZoomController.d.ts +8 -10
- package/dist/engine/utils/chartZoomController.d.ts.map +1 -1
- package/dist/engine/utils/chartZoomController.js +33 -13
- package/dist/engine/utils/chartZoomController.js.map +1 -1
- package/dist/engine/utils/zoom.d.ts +12 -2
- package/dist/engine/utils/zoom.d.ts.map +1 -1
- package/dist/engine/utils/zoom.js +9 -1
- package/dist/engine/utils/zoom.js.map +1 -1
- package/dist/engine/viewport/chartViewportManager.d.ts +13 -6
- package/dist/engine/viewport/chartViewportManager.d.ts.map +1 -1
- package/dist/engine/viewport/chartViewportManager.js +37 -17
- package/dist/engine/viewport/chartViewportManager.js.map +1 -1
- package/dist/reactivity/index.d.ts +1 -1
- package/dist/reactivity/index.d.ts.map +1 -1
- package/dist/reactivity/index.js +1 -1
- package/dist/reactivity/index.js.map +1 -1
- package/dist/reactivity/signal.d.ts +37 -7
- package/dist/reactivity/signal.d.ts.map +1 -1
- package/dist/reactivity/signal.js +75 -11
- package/dist/reactivity/signal.js.map +1 -1
- package/dist/semantic/types.d.ts +1 -1
- package/dist/semantic/types.d.ts.map +1 -1
- package/dist/semantic/validator.d.ts +1 -1
- package/dist/semantic/validator.d.ts.map +1 -1
- package/dist/semantic/validator.js +5 -5
- package/dist/semantic/validator.js.map +1 -1
- package/dist/types/kLine.d.ts +1 -1
- package/dist/types/kLine.d.ts.map +1 -1
- package/dist/types/kLine.js +11 -8
- package/dist/types/kLine.js.map +1 -1
- package/dist/types/price.d.ts +4 -3
- package/dist/types/price.d.ts.map +1 -1
- package/dist/types/price.js +1 -1
- package/dist/types/price.js.map +1 -1
- package/dist/utils/viewportSide.d.ts +2 -0
- package/dist/utils/viewportSide.d.ts.map +1 -0
- package/dist/utils/viewportSide.js +4 -0
- package/dist/utils/viewportSide.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
- package/src/config/chartSettings.ts +18 -7
- package/src/controllers/createChartController.ts +0 -1
- package/src/controllers/types.ts +2 -2
- package/src/data-fetchers/baostock.ts +1 -1
- package/src/data-fetchers/dataBuffer.ts +25 -9
- package/src/data-fetchers/gotdx.ts +2 -2
- package/src/data-fetchers/tradingview.ts +1 -1
- package/src/engine/chart.ts +170 -130
- package/src/engine/controller/interaction.ts +98 -20
- package/src/engine/controller/tooltipPosition.ts +22 -1
- package/src/engine/data/chartDataManager.ts +158 -62
- package/src/engine/data/comparisonManager.ts +7 -8
- package/src/engine/data/incrementalLoadHint.ts +96 -63
- package/src/engine/data/scrollCompensator.ts +3 -2
- package/src/engine/indicators/__tests__/chartIndicatorManager.test.ts +0 -1
- package/src/engine/indicators/chartIndicatorManager.ts +5 -33
- package/src/engine/indicators/scheduler.ts +19 -11
- package/src/engine/render/chartRenderer.ts +15 -10
- package/src/engine/renderers/candle.ts +5 -3
- package/src/engine/renderers/extremaMarkers.ts +2 -1
- package/src/engine/renderers/leftYAxis.ts +1 -0
- package/src/engine/renderers/subVolume.ts +11 -12
- package/src/engine/renderers/yAxis.ts +1 -0
- package/src/engine/utils/chartZoomController.ts +40 -26
- package/src/engine/utils/zoom.ts +26 -3
- package/src/engine/viewport/chartViewportManager.ts +37 -18
- package/src/reactivity/index.ts +1 -1
- package/src/reactivity/signal.ts +71 -10
- package/src/semantic/types.ts +1 -1
- package/src/semantic/validator.ts +7 -7
- package/src/types/kLine.ts +10 -7
- package/src/types/price.ts +5 -4
- package/src/utils/viewportSide.ts +3 -0
- package/src/version.ts +1 -1
package/dist/engine/chart.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createSignal } from '../reactivity/signal';
|
|
1
|
+
import { createStateStore, createSignal } from '../reactivity/signal';
|
|
2
2
|
import { InteractionController } from './controller/interaction';
|
|
3
3
|
import { ChartDataManager } from './data/chartDataManager';
|
|
4
4
|
import { ChartIndicatorManager } from './indicators/chartIndicatorManager';
|
|
@@ -19,7 +19,7 @@ import { createVolumeLookbacks, pushToVolumeLookbacks, } from '../alerts/rolling
|
|
|
19
19
|
export { getPhysicalKLineConfig };
|
|
20
20
|
export class Chart {
|
|
21
21
|
dom;
|
|
22
|
-
|
|
22
|
+
_optionsSignal;
|
|
23
23
|
dataManager;
|
|
24
24
|
viewportManager;
|
|
25
25
|
layoutManager;
|
|
@@ -43,12 +43,8 @@ export class Chart {
|
|
|
43
43
|
_activeMode;
|
|
44
44
|
_kLineMode = new KLineMode();
|
|
45
45
|
_timeShareMode = new TimeShareMode();
|
|
46
|
-
/**
|
|
47
|
-
|
|
48
|
-
_modeSavedKGap = 0;
|
|
49
|
-
_modeSavedZoomLevel = 0;
|
|
50
|
-
/** 分时模式激活前的 pane Y 轴刻度类型(退出分时时恢复) */
|
|
51
|
-
_savedScaleTypes;
|
|
46
|
+
/** 进入分时模式时保存的快照,退出时恢复(包含 zoom/scale/indicators) */
|
|
47
|
+
_savedTimeShareState = null;
|
|
52
48
|
/** 上次预警评估的最新 K 线时间戳(用于去重) */
|
|
53
49
|
_lastAlertTimestamp = null;
|
|
54
50
|
/** 预警控制器 */
|
|
@@ -100,11 +96,11 @@ export class Chart {
|
|
|
100
96
|
this.dom = dom;
|
|
101
97
|
const { kWidth: _kWidth, kGap: _kGap, ...restOpt } = opt;
|
|
102
98
|
// Chart 不持有业务 SSOT,kWidth/kGap/zoomLevel 由外部通过 applyRenderState() 传入
|
|
103
|
-
this.
|
|
99
|
+
this._optionsSignal = createSignal({ ...restOpt, kWidth: _kWidth ?? 0, kGap: _kGap ?? 0 });
|
|
104
100
|
this._activeMode = this._kLineMode;
|
|
105
101
|
this.interaction = new InteractionController(this);
|
|
106
102
|
this.interaction.setOnInteractionChange((snapshot) => {
|
|
107
|
-
this.
|
|
103
|
+
this.state.set.interaction(snapshot);
|
|
108
104
|
});
|
|
109
105
|
this.pluginHost = createPluginHost();
|
|
110
106
|
this.rendererPluginManager = new RendererPluginManager();
|
|
@@ -112,44 +108,49 @@ export class Chart {
|
|
|
112
108
|
// 注入依赖
|
|
113
109
|
this.rendererPluginManager.setPluginHost(this.pluginHost);
|
|
114
110
|
this.rendererPluginManager.setInvalidateCallback(() => this.scheduleDraw());
|
|
111
|
+
const initialOpt = this._optionsSignal.peek();
|
|
115
112
|
this.viewportManager = new ChartViewportManager({
|
|
116
113
|
getDom: () => this.dom,
|
|
117
|
-
getBottomAxisHeight: () => this.
|
|
114
|
+
getBottomAxisHeight: () => this._optionsSignal.peek().bottomAxisHeight,
|
|
118
115
|
getLeftLoadBufferWidth: () => this.dataManager.getLeftLoadBufferWidth(),
|
|
119
116
|
getZoomLevel: () => this.zoomController.currentZoomLevel,
|
|
120
|
-
getLastVisibleRange: () => this.dataManager.
|
|
121
|
-
getKWidth: () => this.
|
|
122
|
-
getKGap: () => this.
|
|
117
|
+
getLastVisibleRange: () => this.dataManager.getCurrentVisibleRange() ?? { start: 0, end: 0 },
|
|
118
|
+
getKWidth: () => this._optionsSignal.peek().kWidth,
|
|
119
|
+
getKGap: () => this._optionsSignal.peek().kGap,
|
|
123
120
|
scheduleDraw: (level) => this.scheduleDraw(level),
|
|
124
121
|
onResizeCompleted: () => {
|
|
125
122
|
this.resize();
|
|
126
123
|
},
|
|
127
124
|
resizeSharedWebGLSurface: (plotWidth, plotHeight, dpr) => this.sharedWebGLSurface.resize(plotWidth, plotHeight, dpr),
|
|
128
125
|
});
|
|
129
|
-
this.
|
|
126
|
+
this.viewportManager.setContentWidthProvider(() => Math.max(this.dataManager.getContentWidth(), this.dataManager.getLeftLoadBufferWidth()));
|
|
127
|
+
this.layoutManager = new ChartPaneLayout(initialOpt.panes, {
|
|
130
128
|
getDom: () => this.dom,
|
|
131
|
-
getOption: () =>
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
129
|
+
getOption: () => {
|
|
130
|
+
const o = this._optionsSignal.peek();
|
|
131
|
+
return {
|
|
132
|
+
rightAxisWidth: o.rightAxisWidth,
|
|
133
|
+
leftAxisWidth: o.leftAxisWidth,
|
|
134
|
+
yPaddingPx: o.yPaddingPx,
|
|
135
|
+
priceLabelWidth: o.priceLabelWidth,
|
|
136
|
+
paneGap: o.paneGap,
|
|
137
|
+
defaultPaneMinHeightPx: o.defaultPaneMinHeightPx,
|
|
138
|
+
};
|
|
139
|
+
},
|
|
139
140
|
getViewport: () => this.viewportManager.getViewport(),
|
|
140
141
|
getSharedWebGLSurface: () => this.sharedWebGLSurface,
|
|
141
142
|
setKnownPaneIds: (ids) => this.rendererPluginManager.setKnownPaneIds(ids),
|
|
142
143
|
notifyPaneResize: (paneId, pane) => this.rendererPluginManager.notifyResize(paneId, wrapPaneInfo(pane)),
|
|
143
144
|
scheduleDraw: (level) => this.scheduleDraw(level),
|
|
144
145
|
onLayoutChange: (ratios, specs) => {
|
|
145
|
-
this.
|
|
146
|
-
this.
|
|
147
|
-
this.
|
|
146
|
+
this.state.set.paneRatios(ratios);
|
|
147
|
+
this.state.set.paneLayout(specs);
|
|
148
|
+
this._optionsSignal.set({ ...this._optionsSignal.peek(), panes: specs });
|
|
148
149
|
},
|
|
149
150
|
});
|
|
150
151
|
this.alertController = createAlertController();
|
|
151
152
|
this.dataManager = new ChartDataManager({
|
|
152
|
-
getOption: () => this.
|
|
153
|
+
getOption: () => this._optionsSignal.peek(),
|
|
153
154
|
getEffectiveDpr: () => this.viewportManager.getEffectiveDpr(),
|
|
154
155
|
getLogicalScrollLeft: () => this.viewportManager.getLogicalScrollLeft(),
|
|
155
156
|
getCachedScrollLeft: () => this.viewportManager.getCachedScrollLeft(),
|
|
@@ -162,9 +163,6 @@ export class Chart {
|
|
|
162
163
|
scheduleDraw: (level) => this.scheduleDraw(level),
|
|
163
164
|
resetInteraction: () => this.interaction.reset(),
|
|
164
165
|
getIndicatorScheduler: () => this.indicatorManager.indicatorSchedulerAccessor,
|
|
165
|
-
setPendingIndicatorDataUpdate: (v) => {
|
|
166
|
-
this.dataManager.pendingIndicatorDataUpdate = v;
|
|
167
|
-
},
|
|
168
166
|
isPointerDown: () => this.interaction.isPointerDown(),
|
|
169
167
|
onTimeShareDataReady: (dataLength) => {
|
|
170
168
|
const vp = this.viewportManager.computeViewport();
|
|
@@ -173,12 +171,8 @@ export class Chart {
|
|
|
173
171
|
const result = this._activeMode.computeKWidth(dataLength, vp.plotWidth, vp.dpr);
|
|
174
172
|
if (result) {
|
|
175
173
|
this.applyRenderState(result.kWidth, result.kGap);
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
const leftBuffer = this.dataManager.getLeftLoadBufferWidth();
|
|
179
|
-
this.viewportManager.setScrollLeft(leftBuffer);
|
|
180
|
-
this.viewportManager.applyPendingScrollLeft(container);
|
|
181
|
-
}
|
|
174
|
+
const leftBuffer = this.dataManager.getLeftLoadBufferWidth();
|
|
175
|
+
this.viewportManager.setScrollLeft(leftBuffer);
|
|
182
176
|
}
|
|
183
177
|
},
|
|
184
178
|
onDataProcessed: (data, range) => this.evaluateAlerts(data, range), // Alert 管线绑定
|
|
@@ -186,28 +180,23 @@ export class Chart {
|
|
|
186
180
|
this.zoomController = new ChartZoomController({
|
|
187
181
|
getLogicalScrollLeft: () => this.viewportManager.getLogicalScrollLeft(),
|
|
188
182
|
getCurrentDpr: () => this.viewportManager.getEffectiveDpr(),
|
|
189
|
-
getLeftLoadBufferWidth: () => this.dataManager.getLeftLoadBufferWidth(),
|
|
190
|
-
getContentWidth: () => this.dataManager.getContentWidth(),
|
|
191
183
|
getClientWidth: () => this.viewportManager.getViewport()?.viewWidth ?? this.dom.container?.clientWidth ?? 0,
|
|
184
|
+
getDataLength: () => this.dataManager.getData().length,
|
|
185
|
+
getPlotWidth: () => this.dataManager.getLeftLoadBufferWidth(),
|
|
192
186
|
setScrollLeft: (v) => {
|
|
193
187
|
this.viewportManager.setScrollLeft(v);
|
|
194
188
|
},
|
|
195
|
-
|
|
196
|
-
this.
|
|
197
|
-
this.updateViewportSignal();
|
|
198
|
-
this.scheduleDraw();
|
|
189
|
+
onChange: () => {
|
|
190
|
+
this.syncKWidthKGap();
|
|
199
191
|
},
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
zoomLevelCount: Math.max(2, Math.round(this.opt.zoomLevels ?? 20)),
|
|
205
|
-
initialZoomLevel: this.opt.initialZoomLevel ?? 1,
|
|
192
|
+
getMinKWidth: () => this._optionsSignal.peek().minKWidth,
|
|
193
|
+
getMaxKWidth: () => this._optionsSignal.peek().maxKWidth,
|
|
194
|
+
zoomLevelCount: Math.max(2, Math.round(this._optionsSignal.peek().zoomLevels ?? 20)),
|
|
195
|
+
initialZoomLevel: this._optionsSignal.peek().initialZoomLevel ?? 1,
|
|
206
196
|
});
|
|
207
|
-
// 注意:初始 kWidth/kGap 应由外部通过 applyRenderState() 传入
|
|
208
197
|
// 初始化指标管理器
|
|
209
198
|
this.indicatorManager = new ChartIndicatorManager({
|
|
210
|
-
getOption: () => this.
|
|
199
|
+
getOption: () => this._optionsSignal.peek(),
|
|
211
200
|
getPluginHost: () => this.pluginHost,
|
|
212
201
|
getRenderer: (name) => this.getRenderer(name),
|
|
213
202
|
useRenderer: (plugin, config) => this.useRenderer(plugin, config),
|
|
@@ -218,40 +207,41 @@ export class Chart {
|
|
|
218
207
|
upsertPane: (def) => this.layoutManager.upsertPane(def),
|
|
219
208
|
removePaneDefinition: (paneId) => this.layoutManager.removePaneDefinition(paneId),
|
|
220
209
|
getPaneSpecs: () => this.layoutManager.getPaneSpecs(),
|
|
221
|
-
getPaneRatiosSignal: () => this.
|
|
210
|
+
getPaneRatiosSignal: () => this.state.signals.paneRatios,
|
|
222
211
|
getInternalPaneRatios: () => this.layoutManager.getInternalPaneRatios(),
|
|
223
212
|
setInternalPaneRatio: (paneId, ratio) => this.layoutManager.setInternalPaneRatio(paneId, ratio),
|
|
224
213
|
deleteInternalPaneRatio: (paneId) => this.layoutManager.deleteInternalPaneRatio(paneId),
|
|
225
214
|
applyPaneLayoutSpecs: (specs) => this.layoutManager.applyPaneLayoutSpecs(specs),
|
|
226
|
-
getLastVisibleRange: () => this.dataManager.
|
|
215
|
+
getLastVisibleRange: () => this.dataManager.getCurrentVisibleRange() ?? { start: 0, end: 0 },
|
|
227
216
|
getCrosshairPos: () => this.interaction.crosshairPos,
|
|
228
217
|
getCrosshairPrice: () => this.interaction.crosshairPrice,
|
|
229
218
|
getActivePaneId: () => this.interaction.activePaneId,
|
|
230
219
|
scheduleDraw: (level) => this.scheduleDraw(level),
|
|
231
|
-
setPendingIndicatorDataUpdate: (v) => {
|
|
232
|
-
this.dataManager.pendingIndicatorDataUpdate = v;
|
|
233
|
-
},
|
|
234
220
|
getRenderContext: (paneId) => this.renderer?.getPaneCtxMap()?.get(paneId) ?? null,
|
|
235
221
|
addLayer: (layer) => this.renderer?.getScene()?.addLayer(layer),
|
|
236
222
|
removeLayer: (id) => this.renderer?.getScene()?.removeLayer(id) ?? false,
|
|
237
223
|
getLayer: (id) => this.renderer?.getScene()?.getLayer(id) ?? null,
|
|
238
224
|
setLayerVisibility: (id, visible) => this.renderer?.getScene()?.setLayerVisibility(id, visible),
|
|
225
|
+
getIndicatorScheduler: () => this.indicatorManager.indicatorSchedulerAccessor,
|
|
226
|
+
getRightAxisWidth: () => this._optionsSignal.peek().rightAxisWidth,
|
|
227
|
+
getPriceLabelWidth: () => this._optionsSignal.peek().priceLabelWidth ?? 60,
|
|
228
|
+
getYPaddingPx: () => this._optionsSignal.peek().yPaddingPx,
|
|
239
229
|
});
|
|
240
230
|
// Worker 异步结果就绪后串联 Alert 管线
|
|
241
231
|
this.indicatorManager.indicatorSchedulerAccessor.setOnResultsApplied(() => {
|
|
242
232
|
const data = this.dataManager.getInternalData();
|
|
243
|
-
this.evaluateAlerts(data, this.dataManager.
|
|
233
|
+
this.evaluateAlerts(data, this.dataManager.getCurrentVisibleRange() ?? { start: 0, end: 0 });
|
|
244
234
|
});
|
|
245
235
|
// 初始化渲染器
|
|
246
236
|
this.renderer = new ChartRenderer({
|
|
247
237
|
getDom: () => this.dom,
|
|
248
|
-
getOption: () => this.
|
|
238
|
+
getOption: () => this._optionsSignal.peek(),
|
|
249
239
|
getPaneRenderers: () => this.paneRenderers,
|
|
250
240
|
getInteraction: () => this.interaction,
|
|
251
241
|
getSharedWebGLSurface: () => this.sharedWebGLSurface,
|
|
252
242
|
getPluginHost: () => this.pluginHost,
|
|
253
243
|
getRendererPluginManager: () => this.rendererPluginManager,
|
|
254
|
-
getTheme: () => this.
|
|
244
|
+
getTheme: () => this.state.signals.theme.peek(),
|
|
255
245
|
getCurrentZoomLevel: () => this.zoomController.currentZoomLevel,
|
|
256
246
|
getZoomLevelCount: () => this.zoomController.zoomLevelCount,
|
|
257
247
|
getViewportManager: () => this.viewportManager,
|
|
@@ -261,6 +251,7 @@ export class Chart {
|
|
|
261
251
|
});
|
|
262
252
|
this.renderer.registerDrawingPlugins();
|
|
263
253
|
this.renderer.initCoreRenderers();
|
|
254
|
+
this.syncKWidthKGap();
|
|
264
255
|
this.viewportManager.init();
|
|
265
256
|
}
|
|
266
257
|
getViewport() {
|
|
@@ -276,26 +267,34 @@ export class Chart {
|
|
|
276
267
|
return;
|
|
277
268
|
const prev = this._activeMode;
|
|
278
269
|
if (mode === this._timeShareMode) {
|
|
279
|
-
this.
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
270
|
+
this._savedTimeShareState = {
|
|
271
|
+
zoomLevel: this.zoomController.currentZoomLevel,
|
|
272
|
+
scaleTypes: new Map(),
|
|
273
|
+
mainIndicators: [],
|
|
274
|
+
subPanes: [],
|
|
275
|
+
};
|
|
283
276
|
for (const r of this.paneRenderers) {
|
|
284
|
-
this.
|
|
277
|
+
this._savedTimeShareState.scaleTypes.set(r.getPane().id, r.getPane().yAxis.getScaleType());
|
|
285
278
|
}
|
|
279
|
+
for (const [id, entry] of this.indicatorManager.mainIndicatorsSignalPeek) {
|
|
280
|
+
this._savedTimeShareState.mainIndicators.push({ id, params: { ...entry.params } });
|
|
281
|
+
}
|
|
282
|
+
this._savedTimeShareState.subPanes = this.indicatorManager.getSubPaneEntries().map((e) => ({
|
|
283
|
+
id: e.indicatorId,
|
|
284
|
+
params: { ...e.params },
|
|
285
|
+
}));
|
|
286
286
|
}
|
|
287
287
|
else if (prev === this._timeShareMode) {
|
|
288
288
|
for (const renderer of this.paneRenderers) {
|
|
289
289
|
const p = renderer.getPane();
|
|
290
|
-
const saved = this.
|
|
290
|
+
const saved = this._savedTimeShareState?.scaleTypes.get(p.id) ?? 'linear';
|
|
291
291
|
p.yAxis.setScaleType(saved);
|
|
292
292
|
p.yAxis.setBasePrice(null);
|
|
293
293
|
}
|
|
294
|
-
this._savedScaleTypes = undefined;
|
|
295
294
|
}
|
|
296
|
-
if (this.
|
|
297
|
-
this.
|
|
298
|
-
this.
|
|
295
|
+
if (this._savedTimeShareState && mode !== this._timeShareMode) {
|
|
296
|
+
this.zoomController.setZoomLevel(this._savedTimeShareState.zoomLevel);
|
|
297
|
+
this.syncKWidthKGap();
|
|
299
298
|
}
|
|
300
299
|
prev.onDeactivate({
|
|
301
300
|
enableMainIndicator: (id, p) => this.enableMainIndicator(id, p),
|
|
@@ -311,6 +310,28 @@ export class Chart {
|
|
|
311
310
|
dataManager: this.dataManager,
|
|
312
311
|
currentPeriod: this.dataManager.currentPeriod,
|
|
313
312
|
}, prev);
|
|
313
|
+
if (mode === this._timeShareMode) {
|
|
314
|
+
for (const { id } of this._savedTimeShareState.mainIndicators) {
|
|
315
|
+
if (id !== 'TIMESHARE') {
|
|
316
|
+
this.indicatorManager.disableMainIndicator(id);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
this.indicatorManager.clearSubPanes();
|
|
320
|
+
}
|
|
321
|
+
else if (prev === this._timeShareMode) {
|
|
322
|
+
const saved = this._savedTimeShareState;
|
|
323
|
+
if (saved) {
|
|
324
|
+
for (const { id, params } of saved.mainIndicators) {
|
|
325
|
+
if (id !== 'TIMESHARE') {
|
|
326
|
+
this.indicatorManager.enableMainIndicator(id, params);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
for (const { id, params } of saved.subPanes) {
|
|
330
|
+
this.indicatorManager.addIndicator(id, 'sub', params);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
this._savedTimeShareState = null;
|
|
334
|
+
}
|
|
314
335
|
}
|
|
315
336
|
getCurrentDpr() {
|
|
316
337
|
return this.viewportManager.getEffectiveDpr();
|
|
@@ -391,27 +412,34 @@ export class Chart {
|
|
|
391
412
|
this.renderer.draw(level);
|
|
392
413
|
}
|
|
393
414
|
// ========== Render State API (Vue SSOT) ==========
|
|
415
|
+
syncKWidthKGap() {
|
|
416
|
+
const kw = this.zoomController.currentKWidth;
|
|
417
|
+
const kg = this.zoomController.currentKGap;
|
|
418
|
+
const current = this._optionsSignal.peek();
|
|
419
|
+
if (current.kWidth !== kw || current.kGap !== kg) {
|
|
420
|
+
this._optionsSignal.set({ ...current, kWidth: kw, kGap: kg });
|
|
421
|
+
}
|
|
422
|
+
this.scheduleDraw();
|
|
423
|
+
}
|
|
394
424
|
/**
|
|
395
|
-
*
|
|
396
|
-
*
|
|
397
|
-
*
|
|
425
|
+
* 应用渲染状态
|
|
426
|
+
* kWidth/kGap/zoomLevel 由 zoomController 统一管理(SSOT)。
|
|
427
|
+
* 当 zoomLevel 提供时,kWidth/kGap 由 zoomController 从 zoomLevel 推导。
|
|
428
|
+
* 当 zoomLevel 不提供时(如分时图),直接设置 kWidth/kGap。
|
|
398
429
|
*/
|
|
399
430
|
applyRenderState(kWidth, kGap, zoomLevel) {
|
|
400
|
-
const nextZoomLevel = zoomLevel !== undefined
|
|
401
|
-
? Math.max(1, Math.min(this.zoomController.zoomLevelCount, zoomLevel))
|
|
402
|
-
: this.zoomController.currentZoomLevel;
|
|
403
|
-
const renderStateChanged = this.opt.kWidth !== kWidth ||
|
|
404
|
-
this.opt.kGap !== kGap ||
|
|
405
|
-
this.zoomController.currentZoomLevel !== nextZoomLevel;
|
|
406
|
-
if (!renderStateChanged) {
|
|
407
|
-
return;
|
|
408
|
-
}
|
|
409
|
-
this.opt = { ...this.opt, kWidth, kGap };
|
|
410
431
|
if (zoomLevel !== undefined) {
|
|
411
|
-
this.zoomController.
|
|
432
|
+
const clamped = Math.max(1, Math.min(this.zoomController.zoomLevelCount, zoomLevel));
|
|
433
|
+
if (this.zoomController.currentZoomLevel === clamped)
|
|
434
|
+
return;
|
|
435
|
+
this.zoomController.setZoomLevel(clamped);
|
|
412
436
|
}
|
|
413
|
-
|
|
414
|
-
|
|
437
|
+
else {
|
|
438
|
+
if (this.zoomController.currentKWidth === kWidth && this.zoomController.currentKGap === kGap)
|
|
439
|
+
return;
|
|
440
|
+
this.zoomController.setKWidthKGap(kWidth, kGap);
|
|
441
|
+
}
|
|
442
|
+
this.syncKWidthKGap();
|
|
415
443
|
}
|
|
416
444
|
/** 获取总缩放级别数 */
|
|
417
445
|
getZoomLevelCount() {
|
|
@@ -441,7 +469,7 @@ export class Chart {
|
|
|
441
469
|
}
|
|
442
470
|
/** 获取当前 ChartOptions(返回内部当前快照) */
|
|
443
471
|
getOption() {
|
|
444
|
-
return this.
|
|
472
|
+
return this._optionsSignal.peek();
|
|
445
473
|
}
|
|
446
474
|
/**
|
|
447
475
|
* 更新配置并触发布局/重绘
|
|
@@ -458,11 +486,11 @@ export class Chart {
|
|
|
458
486
|
}
|
|
459
487
|
if (partial.panes) {
|
|
460
488
|
const nextPanes = partial.panes.map((pane) => ({ ...pane }));
|
|
461
|
-
this.
|
|
489
|
+
this._optionsSignal.set({ ...this._optionsSignal.peek(), ...partial, panes: nextPanes });
|
|
462
490
|
this.layoutManager.applyPaneLayoutSpecs(nextPanes);
|
|
463
491
|
return;
|
|
464
492
|
}
|
|
465
|
-
this.
|
|
493
|
+
this._optionsSignal.set({ ...this._optionsSignal.peek(), ...partial });
|
|
466
494
|
this.resize();
|
|
467
495
|
}
|
|
468
496
|
updatePaneLayout(panes) {
|
|
@@ -483,7 +511,7 @@ export class Chart {
|
|
|
483
511
|
/** 更新绘图对象 */
|
|
484
512
|
setDrawings(drawings) {
|
|
485
513
|
this.renderer.getDrawingStore().setAll(drawings);
|
|
486
|
-
this.
|
|
514
|
+
this.state.signals.drawings.set(drawings);
|
|
487
515
|
this.scheduleDraw();
|
|
488
516
|
}
|
|
489
517
|
/** 更新选中的绘图 ID */
|
|
@@ -719,7 +747,7 @@ export class Chart {
|
|
|
719
747
|
}
|
|
720
748
|
/** 容器尺寸变化时调用 */
|
|
721
749
|
resize() {
|
|
722
|
-
if (this._activeMode
|
|
750
|
+
if (this._activeMode === this._timeShareMode) {
|
|
723
751
|
const tsData = this.dataManager.getTimeShareData();
|
|
724
752
|
const vp = this.viewportManager.computeViewport();
|
|
725
753
|
if (!vp || vp.plotWidth <= 0)
|
|
@@ -728,12 +756,8 @@ export class Chart {
|
|
|
728
756
|
const result = this._activeMode.computeKWidth(tsData.length, vp.plotWidth, vp.dpr);
|
|
729
757
|
if (result) {
|
|
730
758
|
this.applyRenderState(result.kWidth, result.kGap);
|
|
731
|
-
const
|
|
732
|
-
|
|
733
|
-
const leftBuffer = this.dataManager.getLeftLoadBufferWidth();
|
|
734
|
-
this.viewportManager.setScrollLeft(leftBuffer);
|
|
735
|
-
this.viewportManager.applyPendingScrollLeft(container);
|
|
736
|
-
}
|
|
759
|
+
const leftBuffer = this.dataManager.getLeftLoadBufferWidth();
|
|
760
|
+
this.viewportManager.setScrollLeft(leftBuffer);
|
|
737
761
|
}
|
|
738
762
|
}
|
|
739
763
|
this.renderer.clearCachedFrame();
|
|
@@ -774,26 +798,28 @@ export class Chart {
|
|
|
774
798
|
return this.viewportManager.computeViewport();
|
|
775
799
|
}
|
|
776
800
|
// ==================== Facade API (High-level interface for adapters) ====================
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
801
|
+
state = createStateStore({
|
|
802
|
+
theme: 'light',
|
|
803
|
+
drawingTool: null,
|
|
804
|
+
drawings: [],
|
|
805
|
+
paneRatios: {},
|
|
806
|
+
paneLayout: [],
|
|
807
|
+
interaction: {
|
|
808
|
+
crosshairPos: null,
|
|
809
|
+
crosshairIndex: null,
|
|
810
|
+
crosshairPrice: null,
|
|
811
|
+
hoveredIndex: null,
|
|
812
|
+
activePaneId: null,
|
|
813
|
+
tooltipPos: { x: 0, y: 0 },
|
|
814
|
+
tooltipAnchorPlacement: 'right-bottom',
|
|
815
|
+
hoveredMarkerData: null,
|
|
816
|
+
hoveredCustomMarker: null,
|
|
817
|
+
isDragging: false,
|
|
818
|
+
isResizingPaneBoundary: false,
|
|
819
|
+
isHoveringPaneBoundary: false,
|
|
820
|
+
hoveredPaneBoundaryId: null,
|
|
821
|
+
isHoveringRightAxis: false,
|
|
822
|
+
},
|
|
797
823
|
});
|
|
798
824
|
/** 视口状态信号 */
|
|
799
825
|
get viewport() {
|
|
@@ -829,7 +855,7 @@ export class Chart {
|
|
|
829
855
|
}
|
|
830
856
|
/** 主题信号 */
|
|
831
857
|
get theme() {
|
|
832
|
-
return this.
|
|
858
|
+
return this.state.signals.theme;
|
|
833
859
|
}
|
|
834
860
|
/** 指标实例列表信号(派生信号,自动随主/副图状态更新) */
|
|
835
861
|
get indicators() {
|
|
@@ -841,22 +867,22 @@ export class Chart {
|
|
|
841
867
|
}
|
|
842
868
|
/** 当前绘图工具信号 */
|
|
843
869
|
get drawingTool() {
|
|
844
|
-
return this.
|
|
870
|
+
return this.state.signals.drawingTool;
|
|
845
871
|
}
|
|
846
872
|
/** 绘图对象列表信号 */
|
|
847
873
|
get drawings() {
|
|
848
|
-
return this.
|
|
874
|
+
return this.state.signals.drawings;
|
|
849
875
|
}
|
|
850
876
|
/** 面板比例信号 */
|
|
851
877
|
get paneRatios() {
|
|
852
|
-
return this.
|
|
878
|
+
return this.state.signals.paneRatios;
|
|
853
879
|
}
|
|
854
880
|
get paneLayout() {
|
|
855
|
-
return this.
|
|
881
|
+
return this.state.signals.paneLayout;
|
|
856
882
|
}
|
|
857
883
|
/** 交互状态信号 */
|
|
858
884
|
get interactionState() {
|
|
859
|
-
return this.
|
|
885
|
+
return this.state.signals.interaction;
|
|
860
886
|
}
|
|
861
887
|
// ---------- Data ----------
|
|
862
888
|
setData(data) {
|
|
@@ -879,9 +905,17 @@ export class Chart {
|
|
|
879
905
|
this._lastAlertTimestamp = null;
|
|
880
906
|
const primaryPeriod = specs[0]?.period;
|
|
881
907
|
if (primaryPeriod) {
|
|
908
|
+
// ⚠️ setActiveMode 必须在 dataManager.setSymbols 之前调用,
|
|
909
|
+
// 以确保 kWidth/kGap(从 zoom level 恢复)先写入 _optionsSignal,
|
|
910
|
+
// 后续 scrollLeft 恢复才能正确反推物理像素偏移。
|
|
882
911
|
this.setActiveMode(primaryPeriod === 'timeshare' ? this._timeShareMode : this._kLineMode);
|
|
883
912
|
}
|
|
884
913
|
this.dataManager.setSymbols(specs);
|
|
914
|
+
// Scroll position 恢复必须放在 setActiveMode + setSymbols 之后,
|
|
915
|
+
// 此时 kWidth/kGap 已由 zoom level 恢复写回,计算不出错。
|
|
916
|
+
if (primaryPeriod && primaryPeriod !== 'timeshare') {
|
|
917
|
+
this.dataManager.tryRestoreScrollFromSnapshot();
|
|
918
|
+
}
|
|
885
919
|
}
|
|
886
920
|
addComparisonSymbol(spec) {
|
|
887
921
|
this.dataManager.addComparisonSymbol(spec);
|
|
@@ -918,7 +952,7 @@ export class Chart {
|
|
|
918
952
|
* 设置主题(高层 API)
|
|
919
953
|
*/
|
|
920
954
|
setTheme(theme) {
|
|
921
|
-
this.
|
|
955
|
+
this.state.set.theme(theme);
|
|
922
956
|
this.scheduleDraw();
|
|
923
957
|
}
|
|
924
958
|
// ---------- Zoom ----------
|
|
@@ -1030,7 +1064,7 @@ export class Chart {
|
|
|
1030
1064
|
* 更新缓存的 scrollLeft 并触发交互 controller
|
|
1031
1065
|
*/
|
|
1032
1066
|
handleScrollEvent() {
|
|
1033
|
-
this.interaction.onScroll({ scheduleDraw: !this.
|
|
1067
|
+
this.interaction.onScroll({ scheduleDraw: !this.indicatorManager.indicatorSchedulerAccessor.busySignal.peek() });
|
|
1034
1068
|
// 更新 viewport signal 中的 visible range
|
|
1035
1069
|
this.updateViewportSignal();
|
|
1036
1070
|
}
|
|
@@ -1086,7 +1120,7 @@ export class Chart {
|
|
|
1086
1120
|
* @param tool 工具类型或 null 取消选择
|
|
1087
1121
|
*/
|
|
1088
1122
|
setDrawingTool(tool) {
|
|
1089
|
-
this.
|
|
1123
|
+
this.state.set.drawingTool(tool);
|
|
1090
1124
|
// TODO: 当 Chart 支持绘图工具切换时,在这里调用相应方法
|
|
1091
1125
|
}
|
|
1092
1126
|
/**
|