@363045841yyt/klinechart-core 0.9.0-alpha.2 → 0.9.0-alpha.3

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 (190) hide show
  1. package/dist/controllers/createChartController.d.ts.map +1 -1
  2. package/dist/controllers/createChartController.js +9 -8
  3. package/dist/controllers/createChartController.js.map +1 -1
  4. package/dist/controllers/index.d.ts +1 -0
  5. package/dist/controllers/index.d.ts.map +1 -1
  6. package/dist/controllers/index.js.map +1 -1
  7. package/dist/controllers/types.d.ts +5 -0
  8. package/dist/controllers/types.d.ts.map +1 -1
  9. package/dist/data/binance.d.ts +1 -1
  10. package/dist/data/binance.d.ts.map +1 -1
  11. package/dist/data/binance.js +1 -1
  12. package/dist/data/binance.js.map +1 -1
  13. package/dist/engine/chart.d.ts +9 -9
  14. package/dist/engine/chart.d.ts.map +1 -1
  15. package/dist/engine/chart.js +69 -96
  16. package/dist/engine/chart.js.map +1 -1
  17. package/dist/engine/chartTypes.d.ts +5 -0
  18. package/dist/engine/chartTypes.d.ts.map +1 -1
  19. package/dist/engine/controller/interaction.d.ts +4 -2
  20. package/dist/engine/controller/interaction.d.ts.map +1 -1
  21. package/dist/engine/controller/interaction.js +18 -10
  22. package/dist/engine/controller/interaction.js.map +1 -1
  23. package/dist/engine/data/chartDataManager.d.ts +12 -18
  24. package/dist/engine/data/chartDataManager.d.ts.map +1 -1
  25. package/dist/engine/data/chartDataManager.js +46 -37
  26. package/dist/engine/data/chartDataManager.js.map +1 -1
  27. package/dist/engine/data/incrementalLoadHint.d.ts +2 -4
  28. package/dist/engine/data/incrementalLoadHint.d.ts.map +1 -1
  29. package/dist/engine/data/incrementalLoadHint.js +5 -2
  30. package/dist/engine/data/incrementalLoadHint.js.map +1 -1
  31. package/dist/engine/data/scrollCompensator.d.ts +3 -13
  32. package/dist/engine/data/scrollCompensator.d.ts.map +1 -1
  33. package/dist/engine/data/scrollCompensator.js +13 -14
  34. package/dist/engine/data/scrollCompensator.js.map +1 -1
  35. package/dist/engine/indicators/chartIndicatorManager.d.ts +16 -17
  36. package/dist/engine/indicators/chartIndicatorManager.d.ts.map +1 -1
  37. package/dist/engine/indicators/chartIndicatorManager.js +37 -37
  38. package/dist/engine/indicators/chartIndicatorManager.js.map +1 -1
  39. package/dist/engine/layout/chartPaneLayout.d.ts +11 -7
  40. package/dist/engine/layout/chartPaneLayout.d.ts.map +1 -1
  41. package/dist/engine/layout/chartPaneLayout.js +58 -16
  42. package/dist/engine/layout/chartPaneLayout.js.map +1 -1
  43. package/dist/engine/paneRenderer.d.ts +2 -0
  44. package/dist/engine/paneRenderer.d.ts.map +1 -1
  45. package/dist/engine/paneRenderer.js +15 -5
  46. package/dist/engine/paneRenderer.js.map +1 -1
  47. package/dist/engine/render/chartRenderer.d.ts +28 -22
  48. package/dist/engine/render/chartRenderer.d.ts.map +1 -1
  49. package/dist/engine/render/chartRenderer.js +117 -69
  50. package/dist/engine/render/chartRenderer.js.map +1 -1
  51. package/dist/engine/render/layers/leftYAxisLayer.d.ts +9 -2
  52. package/dist/engine/render/layers/leftYAxisLayer.d.ts.map +1 -1
  53. package/dist/engine/render/layers/leftYAxisLayer.js +11 -2
  54. package/dist/engine/render/layers/leftYAxisLayer.js.map +1 -1
  55. package/dist/engine/render/layers/mainIndicatorLegendLayer.d.ts +6 -4
  56. package/dist/engine/render/layers/mainIndicatorLegendLayer.d.ts.map +1 -1
  57. package/dist/engine/render/layers/mainIndicatorLegendLayer.js +6 -9
  58. package/dist/engine/render/layers/mainIndicatorLegendLayer.js.map +1 -1
  59. package/dist/engine/render/layers/mainIndicatorLegendLayer.test.d.ts +2 -0
  60. package/dist/engine/render/layers/mainIndicatorLegendLayer.test.d.ts.map +1 -0
  61. package/dist/engine/render/layers/mainIndicatorLegendLayer.test.js +19 -0
  62. package/dist/engine/render/layers/mainIndicatorLegendLayer.test.js.map +1 -0
  63. package/dist/engine/render/layers/yAxisLayer.d.ts +9 -2
  64. package/dist/engine/render/layers/yAxisLayer.d.ts.map +1 -1
  65. package/dist/engine/render/layers/yAxisLayer.js +11 -2
  66. package/dist/engine/render/layers/yAxisLayer.js.map +1 -1
  67. package/dist/engine/renderers/Indicator/mainIndicatorLegend.d.ts +7 -3
  68. package/dist/engine/renderers/Indicator/mainIndicatorLegend.d.ts.map +1 -1
  69. package/dist/engine/renderers/Indicator/mainIndicatorLegend.js +234 -330
  70. package/dist/engine/renderers/Indicator/mainIndicatorLegend.js.map +1 -1
  71. package/dist/engine/renderers/Indicator/mainIndicatorLegendContext.d.ts +72 -0
  72. package/dist/engine/renderers/Indicator/mainIndicatorLegendContext.d.ts.map +1 -0
  73. package/dist/engine/renderers/Indicator/mainIndicatorLegendContext.js +177 -0
  74. package/dist/engine/renderers/Indicator/mainIndicatorLegendContext.js.map +1 -0
  75. package/dist/engine/renderers/leftYAxis.d.ts +15 -2
  76. package/dist/engine/renderers/leftYAxis.d.ts.map +1 -1
  77. package/dist/engine/renderers/leftYAxis.js +56 -16
  78. package/dist/engine/renderers/leftYAxis.js.map +1 -1
  79. package/dist/engine/renderers/yAxis.d.ts +16 -6
  80. package/dist/engine/renderers/yAxis.d.ts.map +1 -1
  81. package/dist/engine/renderers/yAxis.js +49 -16
  82. package/dist/engine/renderers/yAxis.js.map +1 -1
  83. package/dist/engine/state/viewportState.d.ts +4 -1
  84. package/dist/engine/state/viewportState.d.ts.map +1 -1
  85. package/dist/engine/state/viewportState.js +40 -8
  86. package/dist/engine/state/viewportState.js.map +1 -1
  87. package/dist/engine/subPaneManager.d.ts +5 -3
  88. package/dist/engine/subPaneManager.d.ts.map +1 -1
  89. package/dist/engine/subPaneManager.js +3 -2
  90. package/dist/engine/subPaneManager.js.map +1 -1
  91. package/dist/engine/utils/chartZoomController.d.ts +9 -7
  92. package/dist/engine/utils/chartZoomController.d.ts.map +1 -1
  93. package/dist/engine/utils/chartZoomController.js +13 -12
  94. package/dist/engine/utils/chartZoomController.js.map +1 -1
  95. package/dist/engine/viewport/chartViewportManager.d.ts +5 -16
  96. package/dist/engine/viewport/chartViewportManager.d.ts.map +1 -1
  97. package/dist/engine/viewport/chartViewportManager.js +7 -48
  98. package/dist/engine/viewport/chartViewportManager.js.map +1 -1
  99. package/dist/engine/viewport/viewport.d.ts +14 -0
  100. package/dist/engine/viewport/viewport.d.ts.map +1 -1
  101. package/dist/engine/viewport/viewport.js +10 -0
  102. package/dist/engine/viewport/viewport.js.map +1 -1
  103. package/dist/foundation/plugin/rendererPluginManager.test.js +15 -0
  104. package/dist/foundation/plugin/rendererPluginManager.test.js.map +1 -1
  105. package/dist/foundation/plugin/types.d.ts +3 -0
  106. package/dist/foundation/plugin/types.d.ts.map +1 -1
  107. package/dist/foundation/plugin/types.js.map +1 -1
  108. package/dist/foundation/reactivity/frameTransaction.d.ts +11 -0
  109. package/dist/foundation/reactivity/frameTransaction.d.ts.map +1 -1
  110. package/dist/foundation/reactivity/frameTransaction.js +17 -2
  111. package/dist/foundation/reactivity/frameTransaction.js.map +1 -1
  112. package/dist/rendering/render/Renderer.d.ts +58 -74
  113. package/dist/rendering/render/Renderer.d.ts.map +1 -1
  114. package/dist/rendering/render/Renderer.js +8 -18
  115. package/dist/rendering/render/Renderer.js.map +1 -1
  116. package/dist/rendering/render/createWebGPURenderer.d.ts.map +1 -1
  117. package/dist/rendering/render/createWebGPURenderer.js +30 -17
  118. package/dist/rendering/render/createWebGPURenderer.js.map +1 -1
  119. package/dist/rendering/render/createWebGPUSurfaceBackend.js +3 -3
  120. package/dist/rendering/render/createWebGPUSurfaceBackend.js.map +1 -1
  121. package/dist/rendering/render/physicalLine.d.ts +4 -0
  122. package/dist/rendering/render/physicalLine.d.ts.map +1 -0
  123. package/dist/rendering/render/physicalLine.js +37 -0
  124. package/dist/rendering/render/physicalLine.js.map +1 -0
  125. package/dist/rendering/render/physicalRegion.d.ts +14 -0
  126. package/dist/rendering/render/physicalRegion.d.ts.map +1 -0
  127. package/dist/rendering/render/physicalRegion.js +15 -0
  128. package/dist/rendering/render/physicalRegion.js.map +1 -0
  129. package/dist/rendering/scene/createLayerFromPlugin.d.ts.map +1 -1
  130. package/dist/rendering/scene/createLayerFromPlugin.js +8 -3
  131. package/dist/rendering/scene/createLayerFromPlugin.js.map +1 -1
  132. package/dist/rendering/scene/createScene.d.ts.map +1 -1
  133. package/dist/rendering/scene/createScene.js +4 -3
  134. package/dist/rendering/scene/createScene.js.map +1 -1
  135. package/dist/version.d.ts +1 -1
  136. package/dist/version.js +1 -1
  137. package/package.json +1 -1
  138. package/src/__tests__/stateKernel.test.ts +1 -0
  139. package/src/controllers/createChartController.ts +9 -8
  140. package/src/controllers/index.ts +11 -0
  141. package/src/controllers/types.ts +7 -0
  142. package/src/data/binance.ts +1 -1
  143. package/src/engine/__tests__/chart.dpr.test.ts +3 -2
  144. package/src/engine/__tests__/paneRenderer.resize.test.ts +52 -54
  145. package/src/engine/__tests__/subPaneManager.test.ts +5 -3
  146. package/src/engine/chart.ts +76 -109
  147. package/src/engine/chartTypes.ts +5 -0
  148. package/src/engine/controller/__tests__/interaction.dpr.test.ts +39 -2
  149. package/src/engine/controller/interaction.ts +18 -10
  150. package/src/engine/data/__tests__/chartDataManager.incrementalLoad.test.ts +37 -17
  151. package/src/engine/data/__tests__/scrollCompensator.geometry.test.ts +87 -75
  152. package/src/engine/data/chartDataManager.ts +54 -59
  153. package/src/engine/data/incrementalLoadHint.ts +8 -7
  154. package/src/engine/data/scrollCompensator.ts +16 -24
  155. package/src/engine/indicators/__tests__/chartIndicatorManager.test.ts +18 -26
  156. package/src/engine/indicators/chartIndicatorManager.ts +62 -62
  157. package/src/engine/layout/chartPaneLayout.ts +73 -21
  158. package/src/engine/paneRenderer.ts +17 -10
  159. package/src/engine/render/chartRenderer.ts +187 -129
  160. package/src/engine/render/layers/leftYAxisLayer.ts +29 -7
  161. package/src/engine/render/layers/mainIndicatorLegendLayer.test.ts +26 -0
  162. package/src/engine/render/layers/mainIndicatorLegendLayer.ts +13 -10
  163. package/src/engine/render/layers/yAxisLayer.ts +29 -7
  164. package/src/engine/renderers/Indicator/mainIndicatorLegend.ts +270 -403
  165. package/src/engine/renderers/Indicator/mainIndicatorLegendContext.ts +282 -0
  166. package/src/engine/renderers/__tests__/mainIndicatorLegend.renderer.test.ts +73 -2
  167. package/src/engine/renderers/__tests__/yAxis.renderer.test.ts +10 -6
  168. package/src/engine/renderers/leftYAxis.ts +60 -18
  169. package/src/engine/renderers/yAxis.ts +57 -22
  170. package/src/engine/state/viewportState.ts +44 -8
  171. package/src/engine/subPaneManager.ts +8 -5
  172. package/src/engine/utils/chartZoomController.ts +22 -19
  173. package/src/engine/viewport/__tests__/visibleRange.clamp.test.ts +50 -0
  174. package/src/engine/viewport/chartViewportManager.ts +9 -60
  175. package/src/engine/viewport/viewport.ts +14 -0
  176. package/src/foundation/plugin/rendererPluginManager.test.ts +18 -0
  177. package/src/foundation/plugin/types.ts +3 -0
  178. package/src/foundation/reactivity/frameTransaction.ts +20 -6
  179. package/src/rendering/render/Renderer.ts +69 -85
  180. package/src/rendering/render/__tests__/physicalLine.test.ts +51 -0
  181. package/src/rendering/render/__tests__/physicalRegion.test.ts +23 -0
  182. package/src/rendering/render/__tests__/webgpuRenderer.test.ts +31 -1
  183. package/src/rendering/render/__tests__/webgpuSurfaceBackend.test.ts +11 -0
  184. package/src/rendering/render/createWebGPURenderer.ts +30 -17
  185. package/src/rendering/render/createWebGPUSurfaceBackend.ts +3 -3
  186. package/src/rendering/render/physicalLine.ts +45 -0
  187. package/src/rendering/render/physicalRegion.ts +32 -0
  188. package/src/rendering/scene/createLayerFromPlugin.ts +8 -3
  189. package/src/rendering/scene/createScene.ts +4 -3
  190. package/src/version.ts +1 -1
@@ -11,7 +11,7 @@ import {
11
11
  type ReadonlySignal,
12
12
  type Computed,
13
13
  } from '../../foundation/reactivity/signal'
14
- import type { MainIndicatorEntry } from '../state/indicatorState'
14
+ import type { IndicatorStateModule, MainIndicatorEntry } from '../state/indicatorState'
15
15
  import type { SubPaneSpec } from '../state/subPaneState'
16
16
  import type { Layer } from '../../rendering/scene/types'
17
17
  import type { KLineData } from '../../foundation/types/price'
@@ -47,6 +47,24 @@ function mainIndicatorProjectionKey(
47
47
  .join('|')
48
48
  }
49
49
 
50
+ /** 副图业务操作:create/remove/clear 会联动 pane 布局,不能只写 subPane 模块 */
51
+ export interface SubPaneOps {
52
+ readonly entries: ReadonlySignal<ReadonlyArray<SubPaneSpec>>
53
+ create: (
54
+ paneId: string,
55
+ indicatorId: string,
56
+ params: Readonly<Record<string, unknown>>,
57
+ ) => void
58
+ remove: (paneId: string) => void
59
+ replace: (
60
+ paneId: string,
61
+ indicatorId: string,
62
+ params: Readonly<Record<string, unknown>>,
63
+ ) => void
64
+ setParams: (paneId: string, params: Readonly<Record<string, unknown>>) => void
65
+ clear: () => void
66
+ }
67
+
50
68
  export interface IndicatorDependencies {
51
69
  getOption: () => ResolvedChartOptions
52
70
  getPluginHost: () => PluginHostImpl
@@ -58,7 +76,8 @@ export interface IndicatorDependencies {
58
76
  removeRenderer: (name: string) => void
59
77
  updateRendererConfig: (name: string, config: Record<string, unknown>) => void
60
78
  setRendererEnabled: (name: string, enabled: boolean) => void
61
- getPaneRatiosSignal: () => ReadonlySignal<Readonly<Record<string, number>>>
79
+ /** pane ratios SSOT */
80
+ paneRatios$: ReadonlySignal<Readonly<Record<string, number>>>
62
81
  paneSpecs$: ReadonlySignal<ReadonlyArray<PaneSpec>>
63
82
  projectPaneLayout: (
64
83
  specs: ReadonlyArray<PaneSpec>,
@@ -74,29 +93,10 @@ export interface IndicatorDependencies {
74
93
  removeLayer: (id: string) => boolean
75
94
  getLayer: (id: string) => Layer | null
76
95
  setLayerVisibility: (id: string, visible: boolean) => void
77
- getRightAxisWidth: () => number
78
- getPriceLabelWidth: () => number
79
- getYPaddingPx: () => number
80
- mainIndicators$: ReadonlySignal<ReadonlyMap<string, MainIndicatorEntry>>
81
- upsertMainIndicator: (id: string, params: Record<string, number | boolean | string>) => void
82
- removeMainIndicator: (id: string) => void
83
- setMainIndicatorParams: (id: string, params: Record<string, number | boolean | string>) => void
84
- replaceMainIndicators: (entries: ReadonlyMap<string, MainIndicatorEntry>) => void
85
- clearMainIndicators: () => void
86
- subPanes$: ReadonlySignal<ReadonlyArray<SubPaneSpec>>
87
- createSubPaneState: (
88
- paneId: string,
89
- indicatorId: string,
90
- params: Readonly<Record<string, unknown>>,
91
- ) => void
92
- removeSubPaneState: (paneId: string) => void
93
- replaceSubPaneState: (
94
- paneId: string,
95
- indicatorId: string,
96
- params: Readonly<Record<string, unknown>>,
97
- ) => void
98
- updateSubPaneStateParams: (paneId: string, params: Readonly<Record<string, unknown>>) => void
99
- clearSubPaneState: () => void
96
+ /** 主图指标状态模块 */
97
+ indicator: IndicatorStateModule
98
+ /** 副图状态 + 联动 pane 布局的复合操作 */
99
+ subPaneOps: SubPaneOps
100
100
  runRendererTransaction: (run: () => void) => void
101
101
  }
102
102
 
@@ -169,12 +169,12 @@ export class ChartIndicatorManager {
169
169
 
170
170
  // 注册副图活跃列表提供者
171
171
  this.indicatorScheduler.setActiveSubPaneProvider(() =>
172
- this.deps.subPanes$.peek().map((entry) => entry.paneId),
172
+ this.deps.subPaneOps.entries.peek().map((entry) => entry.paneId),
173
173
  )
174
174
 
175
175
  // 派生信号
176
176
  this._indicatorsComputed = computed<ReadonlyArray<IndicatorInstance>>(() => {
177
- const mainIndicators: IndicatorInstance[] = [...this.deps.mainIndicators$().entries()].map(
177
+ const mainIndicators: IndicatorInstance[] = [...this.deps.indicator.readonly.mainIndicators().entries()].map(
178
178
  ([id, entry]) => ({
179
179
  id,
180
180
  definitionId: id,
@@ -185,7 +185,7 @@ export class ChartIndicatorManager {
185
185
  }),
186
186
  )
187
187
 
188
- const subIndicators: IndicatorInstance[] = this.deps.subPanes$().map((entry) => ({
188
+ const subIndicators: IndicatorInstance[] = this.deps.subPaneOps.entries().map((entry) => ({
189
189
  id: entry.paneId,
190
190
  definitionId: entry.indicatorId,
191
191
  label: entry.indicatorId,
@@ -198,8 +198,8 @@ export class ChartIndicatorManager {
198
198
  return [...mainIndicators, ...subIndicators]
199
199
  })
200
200
  this._subPanesComputed = computed<ReadonlyArray<SubPaneInfo>>(() => {
201
- const ratios = deps.getPaneRatiosSignal()()
202
- return this.deps.subPanes$().map((entry) => ({
201
+ const ratios = deps.paneRatios$()
202
+ return this.deps.subPaneOps.entries().map((entry) => ({
203
203
  paneId: entry.paneId,
204
204
  indicatorId: entry.indicatorId,
205
205
  params: { ...entry.params },
@@ -220,12 +220,12 @@ export class ChartIndicatorManager {
220
220
  }
221
221
 
222
222
  this.projectedPaneSpecs = this.deps.paneSpecs$.peek()
223
- this.projectedPaneRatios = this.deps.getPaneRatiosSignal().peek()
223
+ this.projectedPaneRatios = this.deps.paneRatios$.peek()
224
224
  this.disposeProjection = effect(() => {
225
225
  const paneSpecs = this.deps.paneSpecs$()
226
- const paneRatios = this.deps.getPaneRatiosSignal()()
227
- const mainIndicators = this.deps.mainIndicators$()
228
- const subPanes = this.deps.subPanes$()
226
+ const paneRatios = this.deps.paneRatios$()
227
+ const mainIndicators = this.deps.indicator.readonly.mainIndicators()
228
+ const subPanes = this.deps.subPaneOps.entries()
229
229
  this.deps.runRendererTransaction(() => {
230
230
  let paneChanged = false
231
231
  if (paneSpecs !== this.projectedPaneSpecs || paneRatios !== this.projectedPaneRatios) {
@@ -250,7 +250,7 @@ export class ChartIndicatorManager {
250
250
  }
251
251
 
252
252
  get mainIndicatorsSignalPeek(): ReadonlyMap<string, MainIndicatorEntry> {
253
- return this.deps.mainIndicators$.peek()
253
+ return this.deps.indicator.readonly.mainIndicators.peek()
254
254
  }
255
255
 
256
256
  get indicatorsComputed(): Computed<ReadonlyArray<IndicatorInstance>> {
@@ -273,26 +273,26 @@ export class ChartIndicatorManager {
273
273
  return false
274
274
  }
275
275
 
276
- const existing = this.deps.mainIndicators$.peek().get(id)
276
+ const existing = this.deps.indicator.readonly.mainIndicators.peek().get(id)
277
277
 
278
278
  if (existing) {
279
279
  if (params) {
280
- this.deps.upsertMainIndicator(id, params)
280
+ this.deps.indicator.actions.upsert(id, params)
281
281
  }
282
282
  return true
283
283
  }
284
284
 
285
285
  const defaults = ChartIndicatorManager.DEFAULT_MAIN_PARAMS[id] ?? {}
286
286
  const merged = params ? { ...defaults, ...params } : defaults
287
- this.deps.upsertMainIndicator(id, merged)
287
+ this.deps.indicator.actions.upsert(id, merged)
288
288
  return true
289
289
  }
290
290
 
291
291
  disableMainIndicator(indicatorId: string): boolean {
292
292
  const id = indicatorId.toUpperCase()
293
- if (!this.deps.mainIndicators$.peek().has(id)) return false
293
+ if (!this.deps.indicator.readonly.mainIndicators.peek().has(id)) return false
294
294
 
295
- this.deps.removeMainIndicator(id)
295
+ this.deps.indicator.actions.remove(id)
296
296
  return true
297
297
  }
298
298
 
@@ -305,11 +305,11 @@ export class ChartIndicatorManager {
305
305
  }
306
306
 
307
307
  getActiveMainIndicators(): string[] {
308
- return [...this.deps.mainIndicators$.peek().keys()]
308
+ return [...this.deps.indicator.readonly.mainIndicators.peek().keys()]
309
309
  }
310
310
 
311
311
  isMainIndicatorActive(indicatorId: string): boolean {
312
- return this.deps.mainIndicators$.peek().has(indicatorId.toUpperCase())
312
+ return this.deps.indicator.readonly.mainIndicators.peek().has(indicatorId.toUpperCase())
313
313
  }
314
314
 
315
315
  updateMainIndicatorParams(
@@ -317,18 +317,18 @@ export class ChartIndicatorManager {
317
317
  params: Record<string, number | boolean | string>,
318
318
  ): void {
319
319
  const id = indicatorId.toUpperCase()
320
- if (!this.deps.mainIndicators$.peek().has(id)) return
320
+ if (!this.deps.indicator.readonly.mainIndicators.peek().has(id)) return
321
321
 
322
- this.deps.setMainIndicatorParams(id, params)
322
+ this.deps.indicator.actions.setParams(id, params)
323
323
  }
324
324
 
325
325
  getMainIndicatorParams(indicatorId: string): Record<string, number | boolean | string> | null {
326
- const params = this.deps.mainIndicators$.peek().get(indicatorId.toUpperCase())?.params
326
+ const params = this.deps.indicator.readonly.mainIndicators.peek().get(indicatorId.toUpperCase())?.params
327
327
  return params ? { ...params } : null
328
328
  }
329
329
 
330
330
  clearMainIndicators(): void {
331
- this.deps.clearMainIndicators()
331
+ this.deps.indicator.actions.clear()
332
332
  }
333
333
 
334
334
  private reconcileMainIndicators(desired: ReadonlyMap<string, MainIndicatorEntry>): boolean {
@@ -397,7 +397,7 @@ export class ChartIndicatorManager {
397
397
  }
398
398
 
399
399
  private updateIndicatorSchedulerConfig(indicatorId: string): void {
400
- const entry = this.deps.mainIndicators$.peek().get(indicatorId)
400
+ const entry = this.deps.indicator.readonly.mainIndicators.peek().get(indicatorId)
401
401
  const isActive = entry !== undefined
402
402
  const params = entry?.params ?? {}
403
403
 
@@ -419,7 +419,7 @@ export class ChartIndicatorManager {
419
419
  const newIds = indicators
420
420
  .map((i) => i.toUpperCase())
421
421
  .filter((id) => ChartIndicatorManager.ENABLE_MAIN_INDICATORS.includes(id))
422
- const prev = this.deps.mainIndicators$.peek()
422
+ const prev = this.deps.indicator.readonly.mainIndicators.peek()
423
423
 
424
424
  const next = new Map<string, MainIndicatorEntry>()
425
425
  for (const id of newIds) {
@@ -432,7 +432,7 @@ export class ChartIndicatorManager {
432
432
  })
433
433
  }
434
434
  }
435
- this.deps.replaceMainIndicators(next)
435
+ this.deps.indicator.actions.replaceAll(next)
436
436
  }
437
437
 
438
438
  // ========== 副图管理 API ==========
@@ -446,7 +446,7 @@ export class ChartIndicatorManager {
446
446
  if (!definition) {
447
447
  throw new KLineChartError('NOT_REGISTERED', `[Chart] Unknown indicator: ${indicatorId}`)
448
448
  }
449
- this.deps.createSubPaneState(
449
+ this.deps.subPaneOps.create(
450
450
  paneId,
451
451
  indicatorId,
452
452
  params ?? this.getDefaultSubPaneParams(indicatorId),
@@ -458,13 +458,13 @@ export class ChartIndicatorManager {
458
458
  indicatorId: SubIndicatorType,
459
459
  params?: Record<string, number | boolean | string>,
460
460
  ): boolean {
461
- const existing = this.deps.subPanes$.peek().find((entry) => entry.paneId === paneId)
461
+ const existing = this.deps.subPaneOps.entries.peek().find((entry) => entry.paneId === paneId)
462
462
  if (existing) {
463
463
  if (
464
464
  existing.indicatorId === indicatorId &&
465
465
  !this.subPaneManager.getMountedResources(paneId)
466
466
  ) {
467
- this.deps.replaceSubPaneState(
467
+ this.deps.subPaneOps.replace(
468
468
  paneId,
469
469
  existing.indicatorId,
470
470
  existing.params,
@@ -475,7 +475,7 @@ export class ChartIndicatorManager {
475
475
  if (!this.indicatorScheduler.getIndicatorMetadata(indicatorId)) {
476
476
  throw new KLineChartError('NOT_REGISTERED', `[Chart] Unknown indicator: ${indicatorId}`)
477
477
  }
478
- this.deps.createSubPaneState(
478
+ this.deps.subPaneOps.create(
479
479
  paneId,
480
480
  indicatorId,
481
481
  params ?? this.getDefaultSubPaneParams(indicatorId),
@@ -484,7 +484,7 @@ export class ChartIndicatorManager {
484
484
  }
485
485
 
486
486
  removeSubPane(paneId: string): void {
487
- this.deps.removeSubPaneState(paneId)
487
+ this.deps.subPaneOps.remove(paneId)
488
488
  }
489
489
 
490
490
  replaceSubPaneIndicator(
@@ -495,7 +495,7 @@ export class ChartIndicatorManager {
495
495
  if (!this.indicatorScheduler.getIndicatorMetadata(newIndicatorId)) {
496
496
  throw new KLineChartError('NOT_REGISTERED', `[Chart] Unknown indicator: ${newIndicatorId}`)
497
497
  }
498
- this.deps.replaceSubPaneState(
498
+ this.deps.subPaneOps.replace(
499
499
  paneId,
500
500
  newIndicatorId,
501
501
  params ?? this.getDefaultSubPaneParams(newIndicatorId),
@@ -503,22 +503,22 @@ export class ChartIndicatorManager {
503
503
  }
504
504
 
505
505
  updateSubPaneParams(paneId: string, params: Record<string, unknown>): void {
506
- this.deps.updateSubPaneStateParams(paneId, params)
506
+ this.deps.subPaneOps.setParams(paneId, params)
507
507
  }
508
508
 
509
509
  clearSubPanes(): void {
510
- this.deps.clearSubPaneState()
510
+ this.deps.subPaneOps.clear()
511
511
  }
512
512
 
513
513
  /**
514
514
  * @deprecated 使用 getSubPaneEntries 获取完整信息
515
515
  */
516
516
  getSubPaneIndicators(): SubIndicatorType[] {
517
- return this.deps.subPanes$.peek().map((entry) => entry.indicatorId)
517
+ return this.deps.subPaneOps.entries.peek().map((entry) => entry.indicatorId)
518
518
  }
519
519
 
520
520
  getSubPaneEntries(): SubPaneEntry[] {
521
- return this.deps.subPanes$.peek().map((entry) => ({
521
+ return this.deps.subPaneOps.entries.peek().map((entry) => ({
522
522
  ...entry,
523
523
  params: { ...entry.params },
524
524
  ...this.subPaneManager.getMountedResources(entry.paneId),
@@ -526,7 +526,7 @@ export class ChartIndicatorManager {
526
526
  }
527
527
 
528
528
  getSubPaneEntry(paneId: string): SubPaneEntry | undefined {
529
- const entry = this.deps.subPanes$.peek().find((candidate) => candidate.paneId === paneId)
529
+ const entry = this.deps.subPaneOps.entries.peek().find((candidate) => candidate.paneId === paneId)
530
530
  if (!entry) return undefined
531
531
  return {
532
532
  ...entry,
@@ -569,7 +569,7 @@ export class ChartIndicatorManager {
569
569
  removeIndicator(instanceId: string): boolean {
570
570
  const id = instanceId.toUpperCase()
571
571
 
572
- if (this.deps.mainIndicators$.peek().has(id)) {
572
+ if (this.deps.indicator.readonly.mainIndicators.peek().has(id)) {
573
573
  return this.disableMainIndicator(instanceId)
574
574
  }
575
575
 
@@ -585,7 +585,7 @@ export class ChartIndicatorManager {
585
585
  updateIndicatorParams(instanceId: string, params: Record<string, unknown>): boolean {
586
586
  const id = instanceId.toUpperCase()
587
587
 
588
- if (this.deps.mainIndicators$.peek().has(id)) {
588
+ if (this.deps.indicator.readonly.mainIndicators.peek().has(id)) {
589
589
  this.updateMainIndicatorParams(
590
590
  instanceId,
591
591
  params as Record<string, number | boolean | string>,
@@ -2,6 +2,8 @@ import type { PaneRole } from '../../foundation/plugin/index'
2
2
  import type { ChartDom, PaneSpec, Viewport } from '../chartTypes'
3
3
  import { PaneRenderer } from '../paneRenderer'
4
4
  import type { ScaleType } from '../utils/tickPosition'
5
+ import type { PaneStateModule } from '../state/paneState'
6
+ import type { ViewportStateModule } from '../state/viewportState'
5
7
 
6
8
  import { Pane, UpdateLevel } from './pane'
7
9
  import { normalizeVisiblePaneRatios as pureNormalizeVisiblePaneRatios } from './paneRatioMath'
@@ -16,14 +18,15 @@ export interface PaneLayoutDependencies {
16
18
  paneGap?: number
17
19
  defaultPaneMinHeightPx?: number
18
20
  }
19
- getViewport: () => Viewport | null
21
+ /** scroll / plot 几何 SSOT */
22
+ viewport: ViewportStateModule
20
23
  setKnownPaneIds: (ids: string[]) => void
21
24
  notifyPaneResize: (paneId: string, pane: Pane) => void
22
25
  scheduleDraw: (level?: UpdateLevel) => void
23
- getPaneRatios: () => Record<string, number>
24
- /** kernel.paneScaleTypes 投影源;initPanes 优先于此 */
25
- getPaneScaleTypes: () => ReadonlyMap<string, ScaleType>
26
- commitLayout: (ratios: Record<string, number>, specs: PaneSpec[]) => void
26
+ /** pane ratios / specs / scaleTypes SSOT */
27
+ pane: PaneStateModule
28
+ /** commitLayout 写入 kernel 后的副作用(如 ensurePaneScaleTypes) */
29
+ afterCommitLayout?: () => void
27
30
  }
28
31
 
29
32
  /**
@@ -63,7 +66,7 @@ export class ChartPaneLayout {
63
66
  * 公共读:specs 结构来自工作副本定义,ratio 字段取自 kernel(不写工作副本)。
64
67
  */
65
68
  getPaneSpecs(): PaneSpec[] {
66
- const kernelRatios = this.deps.getPaneRatios()
69
+ const kernelRatios = this.deps.pane.readonly.paneRatios.peek()
67
70
  return this._paneSpecs.map((spec) => ({
68
71
  ...spec,
69
72
  ratio: kernelRatios[spec.id] ?? spec.ratio,
@@ -75,11 +78,11 @@ export class ChartPaneLayout {
75
78
  * 公共读:直接返回 kernel paneRatios 副本,不触碰算法工作副本。
76
79
  */
77
80
  getInternalPaneRatios(): Map<string, number> {
78
- return new Map(Object.entries(this.deps.getPaneRatios()))
81
+ return new Map(Object.entries(this.deps.pane.readonly.paneRatios.peek()))
79
82
  }
80
83
 
81
84
  private syncRatiosFromKernel(): void {
82
- const kernelRatios = this.deps.getPaneRatios()
85
+ const kernelRatios = this.deps.pane.readonly.paneRatios.peek()
83
86
  this._internalPaneRatios = new Map(Object.entries(kernelRatios))
84
87
  }
85
88
 
@@ -100,17 +103,36 @@ export class ChartPaneLayout {
100
103
  return index === 0 ? 'price' : 'indicator'
101
104
  }
102
105
 
103
- private createAxisCanvas(spec: PaneSpec, pane: Pane, side: 'left' | 'right'): HTMLCanvasElement {
106
+ private createAxisCanvas(
107
+ spec: PaneSpec,
108
+ side: 'left' | 'right',
109
+ kind: 'base' | 'overlay' = 'base',
110
+ ): HTMLCanvasElement {
104
111
  const canvas = document.createElement('canvas')
105
- canvas.id = `${spec.id}-${side}Axis`
106
- canvas.className = side === 'right' ? 'right-axis' : 'left-axis'
112
+ const isRight = side === 'right'
113
+ const isOverlay = kind === 'overlay'
114
+ canvas.id = `${spec.id}-${side}Axis${isOverlay ? 'Overlay' : ''}`
115
+ canvas.className = isRight
116
+ ? isOverlay
117
+ ? 'right-axis-overlay'
118
+ : 'right-axis'
119
+ : isOverlay
120
+ ? 'left-axis-overlay'
121
+ : 'left-axis'
107
122
  canvas.style.position = 'absolute'
108
123
  canvas.style.left = '0'
124
+ if (isOverlay) {
125
+ canvas.style.pointerEvents = 'none'
126
+ canvas.style.backgroundColor = 'transparent'
127
+ canvas.style.zIndex = '1'
128
+ } else {
129
+ canvas.style.zIndex = '0'
130
+ }
109
131
  return canvas
110
132
  }
111
133
 
112
134
  private initPanes() {
113
- const kernelScaleTypes = this.deps.getPaneScaleTypes()
135
+ const kernelScaleTypes = this.deps.pane.readonly.paneScaleTypes.peek()
114
136
  const prevScaleTypes = new Map<string, ScaleType>()
115
137
  for (const r of this.paneRenderers) {
116
138
  prevScaleTypes.set(r.getPane().id, r.getPane().yAxis.getScaleType())
@@ -129,7 +151,8 @@ export class ChartPaneLayout {
129
151
 
130
152
  const mainCanvas = document.createElement('canvas')
131
153
  const overlayCanvas = document.createElement('canvas')
132
- const yAxisCanvas = this.createAxisCanvas(spec, pane, 'right')
154
+ const yAxisCanvas = this.createAxisCanvas(spec, 'right', 'base')
155
+ const yAxisOverlayCanvas = this.createAxisCanvas(spec, 'right', 'overlay')
133
156
 
134
157
  const isMain = pane.role === 'price'
135
158
 
@@ -149,10 +172,18 @@ export class ChartPaneLayout {
149
172
  overlayCanvas.style.backgroundColor = 'transparent'
150
173
  overlayCanvas.style.zIndex = '2'
151
174
 
152
- const leftYAxisCanvas = this.createAxisCanvas(spec, pane, 'left')
175
+ const leftYAxisCanvas = this.createAxisCanvas(spec, 'left', 'base')
176
+ const leftYAxisOverlayCanvas = this.createAxisCanvas(spec, 'left', 'overlay')
153
177
 
154
178
  const renderer = new PaneRenderer(
155
- { mainCanvas, overlayCanvas, yAxisCanvas, leftYAxisCanvas },
179
+ {
180
+ mainCanvas,
181
+ overlayCanvas,
182
+ yAxisCanvas,
183
+ yAxisOverlayCanvas,
184
+ leftYAxisCanvas,
185
+ leftYAxisOverlayCanvas,
186
+ },
156
187
  pane,
157
188
  {
158
189
  rightAxisWidth: this.deps.getOption().rightAxisWidth,
@@ -177,11 +208,15 @@ export class ChartPaneLayout {
177
208
  existingCanvases.forEach((canvas) => canvas.remove())
178
209
  }
179
210
  if (rightAxisLayer) {
180
- const existingAxisCanvases = rightAxisLayer.querySelectorAll('canvas.right-axis')
211
+ const existingAxisCanvases = rightAxisLayer.querySelectorAll(
212
+ 'canvas.right-axis, canvas.right-axis-overlay',
213
+ )
181
214
  existingAxisCanvases.forEach((canvas) => canvas.remove())
182
215
  }
183
216
  if (leftAxisLayer) {
184
- const existingLeftAxisCanvases = leftAxisLayer.querySelectorAll('canvas.left-axis')
217
+ const existingLeftAxisCanvases = leftAxisLayer.querySelectorAll(
218
+ 'canvas.left-axis, canvas.left-axis-overlay',
219
+ )
185
220
  existingLeftAxisCanvases.forEach((canvas) => canvas.remove())
186
221
  }
187
222
 
@@ -190,8 +225,12 @@ export class ChartPaneLayout {
190
225
  canvasLayer.appendChild(domEls.mainCanvas)
191
226
  canvasLayer.appendChild(domEls.overlayCanvas)
192
227
  rightAxisLayer.appendChild(domEls.yAxisCanvas)
228
+ rightAxisLayer.appendChild(domEls.yAxisOverlayCanvas)
193
229
  if (leftAxisLayer && domEls.leftYAxisCanvas) {
194
230
  leftAxisLayer.appendChild(domEls.leftYAxisCanvas)
231
+ if (domEls.leftYAxisOverlayCanvas) {
232
+ leftAxisLayer.appendChild(domEls.leftYAxisOverlayCanvas)
233
+ }
195
234
  }
196
235
  })
197
236
 
@@ -299,10 +338,16 @@ export class ChartPaneLayout {
299
338
  return heights
300
339
  }
301
340
 
341
+ /** viewWidth 为 0 表示尚未完成首帧尺寸 */
342
+ private peekViewport(): Viewport | null {
343
+ if (this.deps.viewport.readonly.viewWidth.peek() === 0) return null
344
+ return this.deps.viewport.readonly.viewport.peek()
345
+ }
346
+
302
347
  layoutPanes(options?: { commit?: boolean }) {
303
348
  this.syncRatiosFromKernel()
304
349
 
305
- const vp = this.deps.getViewport()
350
+ const vp = this.peekViewport()
306
351
  if (!vp) return
307
352
 
308
353
  const visibleSpecs = this._paneSpecs.filter((p) => p.visible !== false)
@@ -338,10 +383,16 @@ export class ChartPaneLayout {
338
383
  domEls.overlayCanvas.style.top = `${y}px`
339
384
  domEls.yAxisCanvas.style.top = `${y}px`
340
385
  domEls.yAxisCanvas.style.left = '0px'
386
+ domEls.yAxisOverlayCanvas.style.top = `${y}px`
387
+ domEls.yAxisOverlayCanvas.style.left = '0px'
341
388
  if (domEls.leftYAxisCanvas) {
342
389
  domEls.leftYAxisCanvas.style.top = `${y}px`
343
390
  domEls.leftYAxisCanvas.style.left = '0px'
344
391
  }
392
+ if (domEls.leftYAxisOverlayCanvas) {
393
+ domEls.leftYAxisOverlayCanvas.style.top = `${y}px`
394
+ domEls.leftYAxisOverlayCanvas.style.left = '0px'
395
+ }
345
396
 
346
397
  y += h + gap
347
398
  }
@@ -388,7 +439,7 @@ export class ChartPaneLayout {
388
439
  * commitLayout 走 buildLayoutSpecsFromWorkingCopy,避免公共读污染算法中间态。
389
440
  */
390
441
  getPaneLayoutSpecs(): PaneSpec[] {
391
- return this.buildLayoutSpecs(this.deps.getPaneRatios())
442
+ return this.buildLayoutSpecs(this.deps.pane.readonly.paneRatios.peek())
392
443
  }
393
444
 
394
445
  /** 仅算法/commit 使用:读当前工作副本 ratios,不读 kernel */
@@ -422,7 +473,8 @@ export class ChartPaneLayout {
422
473
  this._internalPaneRatios.forEach((ratio, id) => {
423
474
  ratios[id] = ratio
424
475
  })
425
- this.deps.commitLayout(ratios, this.buildLayoutSpecsFromWorkingCopy())
476
+ this.deps.pane.actions.commitLayout(ratios, this.buildLayoutSpecsFromWorkingCopy())
477
+ this.deps.afterCommitLayout?.()
426
478
  }
427
479
 
428
480
  applyPaneLayoutSpecs(panes: PaneSpec[], options?: { preferIncomingRatios?: boolean }): void {
@@ -499,7 +551,7 @@ export class ChartPaneLayout {
499
551
 
500
552
  resizePaneBoundary(upperPaneId: string, deltaY: number): boolean {
501
553
  if (!Number.isFinite(deltaY) || deltaY === 0) return false
502
- const vp = this.deps.getViewport()
554
+ const vp = this.peekViewport()
503
555
  if (!vp) return false
504
556
 
505
557
  this.syncRatiosFromKernel()
@@ -6,7 +6,9 @@ export type PaneRendererContexts = {
6
6
  mainCtx: CanvasRenderingContext2D | null
7
7
  overlayCtx: CanvasRenderingContext2D | null
8
8
  yAxisCtx: CanvasRenderingContext2D | null
9
+ yAxisOverlayCtx: CanvasRenderingContext2D | null
9
10
  leftAxisCtx: CanvasRenderingContext2D | null
11
+ leftAxisOverlayCtx: CanvasRenderingContext2D | null
10
12
  }
11
13
 
12
14
  export type PaneRendererOptions = {
@@ -17,7 +19,7 @@ export type PaneRendererOptions = {
17
19
  }
18
20
 
19
21
  /* PaneRenderer:负责单个 Pane 的 Canvas 管理与运行时状态持有
20
- 创建并管理 mainCanvas / overlayCanvas / yAxisCanvas
22
+ 创建并管理 main/overlay/yAxis/yAxisOverlay(及可选 left 轴)canvas
21
23
  持有 Pane 实例(布局、Y 轴、价格范围)
22
24
  响应 Chart 的 resize / layout 信号
23
25
  GPU 绘制经 ChartRenderer.sceneRenderer(SharedWebGLSurface),本类不再持有 per-pane surface */
@@ -52,7 +54,9 @@ export class PaneRenderer {
52
54
  mainCtx: this.dom.mainCanvas.getContext('2d'),
53
55
  overlayCtx: this.dom.overlayCanvas.getContext('2d'),
54
56
  yAxisCtx: this.dom.yAxisCanvas.getContext('2d'),
57
+ yAxisOverlayCtx: this.dom.yAxisOverlayCanvas.getContext('2d'),
55
58
  leftAxisCtx: this.dom.leftYAxisCanvas?.getContext('2d') ?? null,
59
+ leftAxisOverlayCtx: this.dom.leftYAxisOverlayCanvas?.getContext('2d') ?? null,
56
60
  }
57
61
  }
58
62
  return this.contexts
@@ -90,6 +94,7 @@ export class PaneRenderer {
90
94
  const mainCanvas = this.dom.mainCanvas
91
95
  const overlayCanvas = this.dom.overlayCanvas
92
96
  const yAxisCanvas = this.dom.yAxisCanvas
97
+ const yAxisOverlayCanvas = this.dom.yAxisOverlayCanvas
93
98
 
94
99
  // 先读取 parentClientWidth,避免在写入样式后读取触发强制回流
95
100
  const fallbackYAxisWidth = this.opt.rightAxisWidth + (this.opt.priceLabelWidth || 60)
@@ -104,22 +109,24 @@ export class PaneRenderer {
104
109
  // Overlay Canvas - 与 Main Canvas 相同尺寸
105
110
  PaneRenderer.resizeCanvas(overlayCanvas, mainWidth, mainHeight, dpr)
106
111
 
107
- // YAxis Canvas
112
+ // YAxis Canvas + overlay 轴(同尺寸)
108
113
  const yAxisWidth = Math.round(canvasYAxisWidth * dpr)
109
- PaneRenderer.resizeCanvas(yAxisCanvas, yAxisWidth, Math.round(height * dpr), dpr)
114
+ const yAxisHeight = Math.round(height * dpr)
115
+ PaneRenderer.resizeCanvas(yAxisCanvas, yAxisWidth, yAxisHeight, dpr)
116
+ PaneRenderer.resizeCanvas(yAxisOverlayCanvas, yAxisWidth, yAxisHeight, dpr)
110
117
 
111
- // Left YAxis Canvas
118
+ // Left YAxis Canvas + overlay
112
119
  const leftCanvas = this.dom.leftYAxisCanvas
120
+ const leftOverlayCanvas = this.dom.leftYAxisOverlayCanvas
113
121
  if (leftCanvas) {
114
122
  const fallbackLeftAxisWidth = this.opt.leftAxisWidth
115
123
  const leftParentWidth = leftCanvas.parentElement?.clientWidth ?? 0
116
124
  const canvasLeftAxisWidth = leftParentWidth > 0 ? leftParentWidth : fallbackLeftAxisWidth
117
- PaneRenderer.resizeCanvas(
118
- leftCanvas,
119
- Math.round(Math.max(canvasLeftAxisWidth, 0) * dpr),
120
- Math.round(height * dpr),
121
- dpr,
122
- )
125
+ const leftW = Math.round(Math.max(canvasLeftAxisWidth, 0) * dpr)
126
+ PaneRenderer.resizeCanvas(leftCanvas, leftW, yAxisHeight, dpr)
127
+ if (leftOverlayCanvas) {
128
+ PaneRenderer.resizeCanvas(leftOverlayCanvas, leftW, yAxisHeight, dpr)
129
+ }
123
130
  }
124
131
  }
125
132