@363045841yyt/klinechart-core 0.7.5-alpha.2 → 0.7.6

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.
Files changed (66) hide show
  1. package/README.md +8 -8
  2. package/README.zh-CN.md +8 -8
  3. package/dist/controllers/createChartController.d.ts.map +1 -1
  4. package/dist/controllers/createChartController.js +145 -21
  5. package/dist/controllers/createChartController.js.map +1 -1
  6. package/dist/controllers/index.d.ts +10 -1
  7. package/dist/controllers/index.d.ts.map +1 -1
  8. package/dist/controllers/index.js +10 -0
  9. package/dist/controllers/index.js.map +1 -1
  10. package/dist/controllers/types.d.ts +65 -8
  11. package/dist/controllers/types.d.ts.map +1 -1
  12. package/dist/engine/chart.d.ts +13 -31
  13. package/dist/engine/chart.d.ts.map +1 -1
  14. package/dist/engine/chart.js +120 -140
  15. package/dist/engine/chart.js.map +1 -1
  16. package/dist/engine/controller/interaction.d.ts +1 -1
  17. package/dist/engine/controller/interaction.d.ts.map +1 -1
  18. package/dist/engine/controller/interaction.js +10 -2
  19. package/dist/engine/controller/interaction.js.map +1 -1
  20. package/dist/engine/drawing/interaction.d.ts +3 -3
  21. package/dist/engine/drawing/interaction.d.ts.map +1 -1
  22. package/dist/engine/drawing/interaction.js +38 -46
  23. package/dist/engine/drawing/interaction.js.map +1 -1
  24. package/dist/engine/renderers/Indicator/indicatorData.d.ts +1 -0
  25. package/dist/engine/renderers/Indicator/indicatorData.d.ts.map +1 -1
  26. package/dist/engine/renderers/Indicator/indicatorData.js +1 -1
  27. package/dist/engine/renderers/Indicator/indicatorData.js.map +1 -1
  28. package/dist/engine/renderers/paneTitle.d.ts +5 -24
  29. package/dist/engine/renderers/paneTitle.d.ts.map +1 -1
  30. package/dist/engine/renderers/paneTitle.js +10 -5
  31. package/dist/engine/renderers/paneTitle.js.map +1 -1
  32. package/dist/engine/renderers/webgl/candleSurface.d.ts +4 -4
  33. package/dist/engine/renderers/webgl/candleSurface.d.ts.map +1 -1
  34. package/dist/engine/renderers/webgl/candleSurface.js +36 -56
  35. package/dist/engine/renderers/webgl/candleSurface.js.map +1 -1
  36. package/dist/engine/subPaneManager.d.ts +6 -0
  37. package/dist/engine/subPaneManager.d.ts.map +1 -1
  38. package/dist/engine/subPaneManager.js +38 -1
  39. package/dist/engine/subPaneManager.js.map +1 -1
  40. package/dist/semantic/controller.d.ts +1 -2
  41. package/dist/semantic/controller.d.ts.map +1 -1
  42. package/dist/semantic/index.d.ts +1 -1
  43. package/dist/semantic/index.d.ts.map +1 -1
  44. package/dist/version.d.ts +1 -1
  45. package/dist/version.d.ts.map +1 -1
  46. package/dist/version.js +1 -1
  47. package/dist/version.js.map +1 -1
  48. package/package.json +6 -6
  49. package/src/controllers/createChartController.ts +158 -29
  50. package/src/controllers/index.ts +34 -0
  51. package/src/controllers/types.ts +79 -8
  52. package/src/engine/chart.ts +133 -154
  53. package/src/engine/controller/interaction.ts +9 -2
  54. package/src/engine/drawing/interaction.ts +38 -47
  55. package/src/engine/renderers/Indicator/indicatorData.ts +1 -1
  56. package/src/engine/renderers/paneTitle.ts +16 -25
  57. package/src/engine/renderers/webgl/candleSurface.ts +40 -56
  58. package/src/engine/subPaneManager.ts +43 -1
  59. package/src/semantic/controller.ts +1 -1
  60. package/src/semantic/index.ts +1 -1
  61. package/src/version.ts +1 -1
  62. package/dist/engine/chart-store.d.ts +0 -75
  63. package/dist/engine/chart-store.d.ts.map +0 -1
  64. package/dist/engine/chart-store.js +0 -88
  65. package/dist/engine/chart-store.js.map +0 -1
  66. package/src/engine/chart-store.ts +0 -121
@@ -1,4 +1,4 @@
1
- import { createSignal } from '../reactivity/signal';
1
+ import { createSignal, computed } from '../reactivity/signal';
2
2
  import { getVisibleRange } from './viewport/viewport';
3
3
  import { Pane, UpdateLevel } from './layout/pane';
4
4
  import { InteractionController } from './controller/interaction';
@@ -6,7 +6,6 @@ import { PaneRenderer } from './paneRenderer';
6
6
  import { SharedWebGLSurface } from './renderers/webgl/sharedWebGLSurface';
7
7
  import { MarkerManager } from './marker/registry';
8
8
  import { getPhysicalKLineConfig, calcKWidthPx } from './utils/klineConfig';
9
- import { computeContentWidth } from './chart-store';
10
9
  import { computeZoom } from './utils/zoom';
11
10
  import { IndicatorScheduler } from './indicators/scheduler';
12
11
  import { getRegisteredIndicatorDefinitions } from './indicators/indicatorDefinitionRegistry';
@@ -74,16 +73,10 @@ export class Chart {
74
73
  settings = {};
75
74
  /** pane ratio 状态(按 paneId 维护,sum=1 仅对可见 pane) */
76
75
  _internalPaneRatios = new Map();
77
- /** 视口变化回调(供外部同步 DPR/尺寸) */
78
- onViewportChange;
79
76
  /** 共享 X 轴上下文缓存 */
80
77
  xAxisCtx = null;
81
78
  /** Chart 级共享 WebGL canvas/context */
82
79
  sharedWebGLSurface;
83
- /** pane 布局回流回调(Chart -> UI 单向) */
84
- onPaneLayoutChange;
85
- /** 数据变化回调(供外部同步 dataLength) */
86
- onDataChange;
87
80
  /** 当前缩放级别(1 ~ zoomLevelCount) */
88
81
  currentZoomLevel = 1;
89
82
  /** 缩放级别总数 */
@@ -97,11 +90,11 @@ export class Chart {
97
90
  /** Overlay 帧复用的最近主渲染结果 */
98
91
  cachedDrawFrame = null;
99
92
  /** 副图管理器 */
100
- subPaneManager;
101
- /** 当前激活的主图指标列表(如 ['boll', 'ma']) */
102
- activeMainIndicators = new Set();
103
- /** 主图指标参数配置 */
104
- mainIndicatorParams = {
93
+ subPaneManager = new SubPaneManager();
94
+ /** 主图指标激活状态与参数(存在即激活,默认参数在 enable 时初始化) */
95
+ _mainIndicatorsSignal = createSignal(new Map());
96
+ /** 主图指标默认参数 */
97
+ static DEFAULT_MAIN_PARAMS = {
105
98
  MA: { ma5: true, ma10: true, ma20: true, ma30: true, ma60: true },
106
99
  BOLL: { period: 20, multiplier: 2, showUpper: true, showMiddle: true, showLower: true, showBand: true },
107
100
  EXPMA: { fastPeriod: 12, slowPeriod: 50 },
@@ -133,26 +126,29 @@ export class Chart {
133
126
  console.warn(`[Chart] 未知的主图指标: ${indicatorId}`);
134
127
  return false;
135
128
  }
136
- if (this.activeMainIndicators.has(id)) {
129
+ const map = this._mainIndicatorsSignal.peek();
130
+ const existing = map.get(id);
131
+ if (existing) {
137
132
  // 已启用,更新参数
138
133
  if (params) {
139
- this.mainIndicatorParams[id] = { ...this.mainIndicatorParams[id], ...params };
134
+ const next = new Map(map);
135
+ next.set(id, { params: { ...existing.params, ...params } });
136
+ this._mainIndicatorsSignal.set(next);
140
137
  this.updateIndicatorSchedulerConfig(id);
141
- this.syncIndicatorsSignal();
142
138
  }
143
139
  return true;
144
140
  }
145
- this.activeMainIndicators.add(id);
146
141
  // 合并默认参数和传入参数
147
- if (params) {
148
- this.mainIndicatorParams[id] = { ...this.mainIndicatorParams[id], ...params };
149
- }
142
+ const defaults = Chart.DEFAULT_MAIN_PARAMS[id] ?? {};
143
+ const merged = params ? { ...defaults, ...params } : defaults;
144
+ const next = new Map(map);
145
+ next.set(id, { params: merged });
146
+ this._mainIndicatorsSignal.set(next);
150
147
  // 启用对应的渲染器
151
148
  this.enableMainIndicatorRenderer(id);
152
149
  // 更新调度器配置
153
150
  this.updateIndicatorSchedulerConfig(id);
154
151
  this.scheduleDraw();
155
- this.syncIndicatorsSignal();
156
152
  return true;
157
153
  }
158
154
  /**
@@ -162,15 +158,17 @@ export class Chart {
162
158
  */
163
159
  disableMainIndicator(indicatorId) {
164
160
  const id = indicatorId.toUpperCase();
165
- if (!this.activeMainIndicators.has(id))
161
+ const map = this._mainIndicatorsSignal.peek();
162
+ if (!map.has(id))
166
163
  return false;
167
- this.activeMainIndicators.delete(id);
164
+ const next = new Map(map);
165
+ next.delete(id);
166
+ this._mainIndicatorsSignal.set(next);
168
167
  // 禁用对应的渲染器
169
168
  this.disableMainIndicatorRenderer(id);
170
169
  // 更新调度器配置
171
170
  this.updateIndicatorSchedulerConfig(id);
172
171
  this.scheduleDraw();
173
- this.syncIndicatorsSignal();
174
172
  return true;
175
173
  }
176
174
  /**
@@ -191,14 +189,14 @@ export class Chart {
191
189
  * @returns 激活的指标ID数组
192
190
  */
193
191
  getActiveMainIndicators() {
194
- return Array.from(this.activeMainIndicators);
192
+ return [...this._mainIndicatorsSignal.peek().keys()];
195
193
  }
196
194
  /**
197
195
  * 检查主图指标是否激活
198
196
  * @param indicatorId 指标ID
199
197
  */
200
198
  isMainIndicatorActive(indicatorId) {
201
- return this.activeMainIndicators.has(indicatorId.toUpperCase());
199
+ return this._mainIndicatorsSignal.peek().has(indicatorId.toUpperCase());
202
200
  }
203
201
  /**
204
202
  * 更新主图指标参数
@@ -207,38 +205,41 @@ export class Chart {
207
205
  */
208
206
  updateMainIndicatorParams(indicatorId, params) {
209
207
  const id = indicatorId.toUpperCase();
210
- if (!this.mainIndicatorParams[id]) {
211
- this.mainIndicatorParams[id] = {};
212
- }
213
- this.mainIndicatorParams[id] = { ...this.mainIndicatorParams[id], ...params };
208
+ const map = this._mainIndicatorsSignal.peek();
209
+ const entry = map.get(id);
210
+ if (!entry)
211
+ return;
212
+ const merged = { ...entry.params, ...params };
213
+ const next = new Map(map);
214
+ next.set(id, { params: merged });
215
+ this._mainIndicatorsSignal.set(next);
214
216
  // 同步更新渲染器配置
215
217
  const rendererName = id.toLowerCase();
216
218
  const renderer = this.getRenderer(rendererName);
217
219
  if (renderer && renderer.setConfig) {
218
- renderer.setConfig(this.mainIndicatorParams[id]);
220
+ renderer.setConfig(merged);
219
221
  }
220
222
  // 更新调度器
221
223
  this.updateIndicatorSchedulerConfig(id);
222
224
  this.scheduleDraw();
223
- this.syncIndicatorsSignal();
224
225
  }
225
226
  /**
226
227
  * 获取主图指标参数
227
228
  * @param indicatorId 指标ID
228
229
  */
229
230
  getMainIndicatorParams(indicatorId) {
230
- return this.mainIndicatorParams[indicatorId.toUpperCase()] ?? null;
231
+ return this._mainIndicatorsSignal.peek().get(indicatorId.toUpperCase())?.params ?? null;
231
232
  }
232
233
  /**
233
234
  * 清除所有主图指标
234
235
  */
235
236
  clearMainIndicators() {
236
- for (const id of this.activeMainIndicators) {
237
+ const map = this._mainIndicatorsSignal.peek();
238
+ for (const id of map.keys()) {
237
239
  this.disableMainIndicatorRenderer(id);
238
240
  }
239
- this.activeMainIndicators.clear();
241
+ this._mainIndicatorsSignal.set(new Map());
240
242
  this.scheduleDraw();
241
- this.syncIndicatorsSignal();
242
243
  }
243
244
  /**
244
245
  * 启用主图指标渲染器(内部方法)
@@ -395,8 +396,9 @@ export class Chart {
395
396
  * 更新调度器配置(内部方法)
396
397
  */
397
398
  updateIndicatorSchedulerConfig(indicatorId) {
398
- const isActive = this.activeMainIndicators.has(indicatorId);
399
- const params = this.mainIndicatorParams[indicatorId] || {};
399
+ const entry = this._mainIndicatorsSignal.peek().get(indicatorId);
400
+ const isActive = entry !== undefined;
401
+ const params = entry?.params ?? {};
400
402
  switch (indicatorId) {
401
403
  case 'MA':
402
404
  this.indicatorScheduler.updateMAConfig({
@@ -475,7 +477,7 @@ export class Chart {
475
477
  setActiveMainIndicators(indicators) {
476
478
  // 计算需要启用和禁用的指标
477
479
  const newSet = new Set(indicators.map(i => i.toUpperCase()));
478
- const currentSet = new Set(this.activeMainIndicators);
480
+ const currentSet = new Set(this._mainIndicatorsSignal.peek().keys());
479
481
  // 禁用不再激活的
480
482
  for (const id of currentSet) {
481
483
  if (!newSet.has(id)) {
@@ -523,11 +525,20 @@ export class Chart {
523
525
  this.indicatorScheduler.registerIndicator(definition);
524
526
  }
525
527
  this.indicatorScheduler.setInvalidateCallback(() => this.scheduleDraw());
526
- // 初始化副图管理器
527
- this.subPaneManager = new SubPaneManager();
528
528
  // 注册副图活跃列表提供者,调度器据此只计算启用的副图
529
529
  this.indicatorScheduler.setActiveSubPaneProvider(() => this.subPaneManager.getPaneIds());
530
530
  this.initPanes();
531
+ // dev: 主副图状态变更日志
532
+ if (import.meta.env?.MODE !== 'production') {
533
+ this._indicatorsComputed.subscribe(() => {
534
+ const instances = this._indicatorsComputed.peek();
535
+ console.log('[Chart] indicators signal changed:', instances);
536
+ });
537
+ this._subPanesComputed.subscribe(() => {
538
+ const subPanes = this._subPanesComputed.peek();
539
+ console.log('[Chart] subPanes signal changed:', subPanes);
540
+ });
541
+ }
531
542
  // 注册绘图主插件(负责绘制 shape,layer: 'main')
532
543
  this.useRenderer(createDrawingRendererPlugin({ store: this.drawingStore }));
533
544
  // 注册绘图标签插件(负责推送选中绘图的轴标签,layer: 'overlay')
@@ -707,7 +718,7 @@ export class Chart {
707
718
  // 3. 更新交互控制器坐标映射
708
719
  this.interaction.setKLinePositions(kLinePositions, range, kWidthPx);
709
720
  // 4. 通知调度器当前活跃主图指标 + 获取价格范围
710
- this.indicatorScheduler.setActiveMainIndicators(Array.from(this.activeMainIndicators));
721
+ this.indicatorScheduler.setActiveMainIndicators([...this._mainIndicatorsSignal.peek().keys()]);
711
722
  const mainIndicatorRange = useCachedFrame ? null : this.indicatorScheduler.getMainIndicatorPriceRange();
712
723
  const hasCrosshair = this.interaction.getCrosshairIndex() !== null;
713
724
  // 5. 遍历所有 Pane 渲染主层 / overlay / Y 轴
@@ -936,18 +947,6 @@ export class Chart {
936
947
  getZoomLevelCount() {
937
948
  return this.zoomLevelCount;
938
949
  }
939
- /** 注册视口变化回调 */
940
- setOnViewportChange(cb) {
941
- this.onViewportChange = cb;
942
- }
943
- /** 注册 pane 布局回流回调 */
944
- setOnPaneLayoutChange(cb) {
945
- this.onPaneLayoutChange = cb;
946
- }
947
- /** 注册数据变化回调 */
948
- setOnDataChange(cb) {
949
- this.onDataChange = cb;
950
- }
951
950
  /** 获取所有 PaneRenderer */
952
951
  getPaneRenderers() {
953
952
  return this.paneRenderers;
@@ -1101,8 +1100,7 @@ export class Chart {
1101
1100
  ratios[id] = ratio;
1102
1101
  });
1103
1102
  this._paneRatiosSignal.set(ratios);
1104
- this.syncSubPanesSignal();
1105
- this.onPaneLayoutChange?.(this.getPaneLayoutSpecs());
1103
+ this._paneLayoutSignal.set(this.getPaneLayoutSpecs());
1106
1104
  }
1107
1105
  applyPaneLayoutSpecs(panes) {
1108
1106
  this.opt.panes = panes.map((spec) => ({ ...spec }));
@@ -1257,8 +1255,6 @@ export class Chart {
1257
1255
  }
1258
1256
  this.upsertPane({ id: paneId, ratio: this._internalPaneRatios.get(paneId) ?? 1, visible: true, role: 'indicator' });
1259
1257
  const success = this.subPaneManager.create(this, paneId, indicatorId, params ?? this.getDefaultSubPaneParams(indicatorId));
1260
- this.syncIndicatorsSignal();
1261
- this.syncSubPanesSignal();
1262
1258
  return success;
1263
1259
  }
1264
1260
  /**
@@ -1267,9 +1263,6 @@ export class Chart {
1267
1263
  */
1268
1264
  removeSubPane(paneId) {
1269
1265
  this.subPaneManager.remove(this, paneId);
1270
- this._internalPaneRatios.delete(paneId);
1271
- this.syncIndicatorsSignal();
1272
- this.syncSubPanesSignal();
1273
1266
  }
1274
1267
  /**
1275
1268
  * 替换副图的指标类型
@@ -1279,8 +1272,6 @@ export class Chart {
1279
1272
  */
1280
1273
  replaceSubPaneIndicator(paneId, newIndicatorId, params) {
1281
1274
  this.subPaneManager.replaceIndicator(this, paneId, newIndicatorId, params ?? this.getDefaultSubPaneParams(newIndicatorId));
1282
- this.syncIndicatorsSignal();
1283
- this.syncSubPanesSignal();
1284
1275
  }
1285
1276
  /**
1286
1277
  * 更新副图指标参数
@@ -1289,7 +1280,6 @@ export class Chart {
1289
1280
  */
1290
1281
  updateSubPaneParams(paneId, params) {
1291
1282
  this.subPaneManager.updateParams(this, paneId, params);
1292
- this.syncIndicatorsSignal();
1293
1283
  }
1294
1284
  /**
1295
1285
  * 清除所有副图面板
@@ -1307,8 +1297,6 @@ export class Chart {
1307
1297
  }
1308
1298
  // 更新布局,移除所有副图 pane
1309
1299
  this.applyPaneLayoutSpecs(this.opt.panes.filter((spec) => !subPaneIds.includes(spec.id)));
1310
- this.syncIndicatorsSignal();
1311
- this.syncSubPanesSignal();
1312
1300
  }
1313
1301
  /**
1314
1302
  * 获取当前所有副图指标类型
@@ -1424,7 +1412,6 @@ export class Chart {
1424
1412
  updateData(data) {
1425
1413
  this._internalData = data ?? [];
1426
1414
  this._dataSignal.set([...this._internalData]);
1427
- this.onDataChange?.(this._internalData);
1428
1415
  // 重算 DOM scrollLeft 状态, 防止左右滚动超出数据长度范围
1429
1416
  const container = this.dom.container;
1430
1417
  if (container) {
@@ -1482,13 +1469,17 @@ export class Chart {
1482
1469
  }
1483
1470
  /** 获取内容总宽度(用于外部 scroll-content 撑开 scrollWidth) */
1484
1471
  getContentWidth() {
1485
- return computeContentWidth({
1486
- dataLength: this._internalData.length,
1487
- kWidth: this.opt.kWidth,
1488
- kGap: this.opt.kGap,
1489
- viewWidth: this._internalViewport?.plotWidth ?? 0,
1490
- viewportDpr: this.getEffectiveDpr(),
1491
- });
1472
+ const dataLength = this._internalData.length;
1473
+ if (dataLength === 0)
1474
+ return 0;
1475
+ const kWidth = this.opt.kWidth;
1476
+ const kGap = this.opt.kGap;
1477
+ const viewWidth = this._internalViewport?.plotWidth ?? 0;
1478
+ const dpr = this.getEffectiveDpr();
1479
+ const TRAILING_DRAWING_SLOTS = 24;
1480
+ const { startXPx, unitPx } = getPhysicalKLineConfig(kWidth, kGap, dpr);
1481
+ const dataPlotWidth = (startXPx + (dataLength + TRAILING_DRAWING_SLOTS) * unitPx) / dpr;
1482
+ return Math.max(dataPlotWidth, viewWidth);
1492
1483
  }
1493
1484
  /** 容器尺寸变化时调用 */
1494
1485
  resize() {
@@ -1559,8 +1550,6 @@ export class Chart {
1559
1550
  this.sharedWebGLSurface.destroy();
1560
1551
  // 清理渲染器插件管理器(会调用所有 onUninstall)
1561
1552
  this.rendererPluginManager.clear();
1562
- this.onViewportChange = undefined;
1563
- this.onPaneLayoutChange = undefined;
1564
1553
  this.indicatorScheduler.destroy();
1565
1554
  await this.pluginHost.destroy();
1566
1555
  }
@@ -1829,7 +1818,18 @@ export class Chart {
1829
1818
  || prevViewport.dpr !== vp.dpr;
1830
1819
  this._internalViewport = vp;
1831
1820
  if (viewportChanged) {
1832
- this.onViewportChange?.(vp);
1821
+ const current = this._viewportSignal.peek();
1822
+ this._viewportSignal.set({
1823
+ zoomLevel: current.zoomLevel,
1824
+ plotWidth: vp.plotWidth,
1825
+ plotHeight: vp.plotHeight,
1826
+ dpr: vp.dpr > 0 ? vp.dpr : current.dpr,
1827
+ visibleFrom: current.visibleFrom,
1828
+ visibleTo: current.visibleTo,
1829
+ desiredScrollLeft: current.desiredScrollLeft,
1830
+ kWidth: current.kWidth,
1831
+ kGap: current.kGap,
1832
+ });
1833
1833
  }
1834
1834
  return vp;
1835
1835
  }
@@ -1848,11 +1848,10 @@ export class Chart {
1848
1848
  });
1849
1849
  _dataSignal = createSignal([]);
1850
1850
  _themeSignal = createSignal('light');
1851
- _indicatorsSignal = createSignal([]);
1852
- _subPanesSignal = createSignal([]);
1853
1851
  _drawingToolSignal = createSignal(null);
1854
1852
  _drawingsSignal = createSignal([]);
1855
1853
  _paneRatiosSignal = createSignal({});
1854
+ _paneLayoutSignal = createSignal([]);
1856
1855
  _interactionSignal = createSignal({
1857
1856
  crosshairPos: null,
1858
1857
  crosshairIndex: null,
@@ -1869,6 +1868,35 @@ export class Chart {
1869
1868
  hoveredPaneBoundaryId: null,
1870
1869
  isHoveringRightAxis: false,
1871
1870
  });
1871
+ _indicatorsComputed = computed(() => {
1872
+ const mainIndicators = [...this._mainIndicatorsSignal().entries()].map(([id, entry]) => ({
1873
+ id,
1874
+ definitionId: id,
1875
+ label: id,
1876
+ name: id,
1877
+ role: 'main',
1878
+ params: { ...entry.params },
1879
+ }));
1880
+ const subIndicators = this.subPaneManager.entriesSignal().map(entry => ({
1881
+ id: entry.paneId,
1882
+ definitionId: entry.indicatorId,
1883
+ label: entry.indicatorId,
1884
+ name: entry.indicatorId,
1885
+ role: 'sub',
1886
+ paneId: entry.paneId,
1887
+ params: { ...entry.params },
1888
+ }));
1889
+ return [...mainIndicators, ...subIndicators];
1890
+ });
1891
+ _subPanesComputed = computed(() => {
1892
+ const ratios = this._paneRatiosSignal();
1893
+ return this.subPaneManager.entriesSignal().map(entry => ({
1894
+ paneId: entry.paneId,
1895
+ indicatorId: entry.indicatorId,
1896
+ params: { ...entry.params },
1897
+ ratio: ratios[entry.paneId] ?? 1,
1898
+ }));
1899
+ });
1872
1900
  /** 视口状态信号 */
1873
1901
  get viewport() {
1874
1902
  return this._viewportSignal;
@@ -1881,13 +1909,13 @@ export class Chart {
1881
1909
  get theme() {
1882
1910
  return this._themeSignal;
1883
1911
  }
1884
- /** 指标实例列表信号 */
1912
+ /** 指标实例列表信号(派生信号,自动随主/副图状态更新) */
1885
1913
  get indicators() {
1886
- return this._indicatorsSignal;
1914
+ return this._indicatorsComputed;
1887
1915
  }
1888
- /** 子图信息信号 */
1916
+ /** 子图信息信号(派生信号,自动随副图条目/比例更新) */
1889
1917
  get subPanes() {
1890
- return this._subPanesSignal;
1918
+ return this._subPanesComputed;
1891
1919
  }
1892
1920
  /** 当前绘图工具信号 */
1893
1921
  get drawingTool() {
@@ -1901,6 +1929,9 @@ export class Chart {
1901
1929
  get paneRatios() {
1902
1930
  return this._paneRatiosSignal;
1903
1931
  }
1932
+ get paneLayout() {
1933
+ return this._paneLayoutSignal;
1934
+ }
1904
1935
  /** 交互状态信号 */
1905
1936
  get interactionState() {
1906
1937
  return this._interactionSignal;
@@ -2120,8 +2151,6 @@ export class Chart {
2120
2151
  const success = this.enableMainIndicator(definitionId, params);
2121
2152
  if (!success)
2122
2153
  return null;
2123
- // 更新 indicators signal
2124
- this.syncIndicatorsSignal();
2125
2154
  return definitionId.toUpperCase();
2126
2155
  }
2127
2156
  else {
@@ -2130,9 +2159,6 @@ export class Chart {
2130
2159
  const success = this.createSubPane(paneId, definitionId, params);
2131
2160
  if (!success)
2132
2161
  return null;
2133
- // 更新 signals
2134
- this.syncIndicatorsSignal();
2135
- this.syncSubPanesSignal();
2136
2162
  return paneId;
2137
2163
  }
2138
2164
  }
@@ -2143,23 +2169,16 @@ export class Chart {
2143
2169
  */
2144
2170
  removeIndicator(instanceId) {
2145
2171
  const id = instanceId.toUpperCase();
2146
- // 先尝试作为主图指标移除(直接检查内部状态,不依赖 signal)
2147
- if (this.activeMainIndicators.has(id)) {
2148
- const success = this.disableMainIndicator(instanceId);
2149
- if (success) {
2150
- this.syncIndicatorsSignal();
2151
- }
2152
- return success;
2172
+ // 先尝试作为主图指标移除
2173
+ if (this._mainIndicatorsSignal.peek().has(id)) {
2174
+ return this.disableMainIndicator(instanceId);
2153
2175
  }
2154
- // 再尝试作为副图指标移除(检查 sub pane 是否存在)
2176
+ // 再尝试作为副图指标移除
2155
2177
  const subPaneEntry = this.getSubPaneEntry(instanceId);
2156
2178
  if (subPaneEntry) {
2157
2179
  this.removeSubPane(instanceId);
2158
- this.syncIndicatorsSignal();
2159
- this.syncSubPanesSignal();
2160
2180
  return true;
2161
2181
  }
2162
- // 都没找到,返回 false
2163
2182
  return false;
2164
2183
  }
2165
2184
  /**
@@ -2170,20 +2189,17 @@ export class Chart {
2170
2189
  */
2171
2190
  updateIndicatorParams(instanceId, params) {
2172
2191
  const id = instanceId.toUpperCase();
2173
- // 先尝试作为主图指标更新(直接检查内部状态)
2174
- if (this.activeMainIndicators.has(id)) {
2192
+ // 先尝试作为主图指标更新
2193
+ if (this._mainIndicatorsSignal.peek().has(id)) {
2175
2194
  this.updateMainIndicatorParams(instanceId, params);
2176
- this.syncIndicatorsSignal();
2177
2195
  return true;
2178
2196
  }
2179
2197
  // 再尝试作为副图指标更新
2180
2198
  const subPaneEntry = this.getSubPaneEntry(instanceId);
2181
2199
  if (subPaneEntry) {
2182
2200
  this.updateSubPaneParams(instanceId, params);
2183
- this.syncIndicatorsSignal();
2184
2201
  return true;
2185
2202
  }
2186
- // 都没找到
2187
2203
  return false;
2188
2204
  }
2189
2205
  /**
@@ -2197,42 +2213,6 @@ export class Chart {
2197
2213
  console.warn('[Chart] reorderIndicators not fully implemented yet');
2198
2214
  return false;
2199
2215
  }
2200
- /**
2201
- * 同步 indicators signal
2202
- */
2203
- syncIndicatorsSignal() {
2204
- const mainIndicators = this.getActiveMainIndicators().map(id => ({
2205
- id,
2206
- definitionId: id,
2207
- label: id,
2208
- name: id,
2209
- role: 'main',
2210
- params: this.getMainIndicatorParams(id) ?? {},
2211
- }));
2212
- const subIndicators = this.getSubPaneEntries().map(entry => ({
2213
- id: entry.paneId,
2214
- definitionId: entry.indicatorId,
2215
- label: entry.indicatorId,
2216
- name: entry.indicatorId,
2217
- role: 'sub',
2218
- paneId: entry.paneId,
2219
- params: entry.params,
2220
- }));
2221
- this._indicatorsSignal.set([...mainIndicators, ...subIndicators]);
2222
- }
2223
- /**
2224
- * 同步 sub panes signal
2225
- */
2226
- syncSubPanesSignal() {
2227
- const entries = this.getSubPaneEntries();
2228
- const subPanes = entries.map(entry => ({
2229
- paneId: entry.paneId,
2230
- indicatorId: entry.indicatorId,
2231
- params: entry.params,
2232
- ratio: this._internalPaneRatios.get(entry.paneId) ?? 1,
2233
- }));
2234
- this._subPanesSignal.set(subPanes);
2235
- }
2236
2216
  // ---------- Sub Panes ----------
2237
2217
  /**
2238
2218
  * 调整子图大小(高层 API)