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

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 (234) 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/data/timeShareBuffer.d.ts +3 -0
  14. package/dist/data/timeShareBuffer.d.ts.map +1 -1
  15. package/dist/data/timeShareBuffer.js +13 -0
  16. package/dist/data/timeShareBuffer.js.map +1 -1
  17. package/dist/engine/chart.d.ts +9 -9
  18. package/dist/engine/chart.d.ts.map +1 -1
  19. package/dist/engine/chart.js +87 -101
  20. package/dist/engine/chart.js.map +1 -1
  21. package/dist/engine/chartTypes.d.ts +5 -0
  22. package/dist/engine/chartTypes.d.ts.map +1 -1
  23. package/dist/engine/controller/interaction.d.ts +4 -2
  24. package/dist/engine/controller/interaction.d.ts.map +1 -1
  25. package/dist/engine/controller/interaction.js +18 -10
  26. package/dist/engine/controller/interaction.js.map +1 -1
  27. package/dist/engine/data/chartDataManager.d.ts +14 -18
  28. package/dist/engine/data/chartDataManager.d.ts.map +1 -1
  29. package/dist/engine/data/chartDataManager.js +55 -37
  30. package/dist/engine/data/chartDataManager.js.map +1 -1
  31. package/dist/engine/data/incrementalLoadHint.d.ts +2 -4
  32. package/dist/engine/data/incrementalLoadHint.d.ts.map +1 -1
  33. package/dist/engine/data/incrementalLoadHint.js +5 -2
  34. package/dist/engine/data/incrementalLoadHint.js.map +1 -1
  35. package/dist/engine/data/scrollCompensator.d.ts +3 -13
  36. package/dist/engine/data/scrollCompensator.d.ts.map +1 -1
  37. package/dist/engine/data/scrollCompensator.js +13 -14
  38. package/dist/engine/data/scrollCompensator.js.map +1 -1
  39. package/dist/engine/indicators/chartIndicatorManager.d.ts +16 -17
  40. package/dist/engine/indicators/chartIndicatorManager.d.ts.map +1 -1
  41. package/dist/engine/indicators/chartIndicatorManager.js +37 -37
  42. package/dist/engine/indicators/chartIndicatorManager.js.map +1 -1
  43. package/dist/engine/layout/chartPaneLayout.d.ts +11 -7
  44. package/dist/engine/layout/chartPaneLayout.d.ts.map +1 -1
  45. package/dist/engine/layout/chartPaneLayout.js +58 -16
  46. package/dist/engine/layout/chartPaneLayout.js.map +1 -1
  47. package/dist/engine/modes/timeShareMath.d.ts +56 -0
  48. package/dist/engine/modes/timeShareMath.d.ts.map +1 -0
  49. package/dist/engine/modes/timeShareMath.js +90 -0
  50. package/dist/engine/modes/timeShareMath.js.map +1 -0
  51. package/dist/engine/modes/timeShareMode.d.ts +5 -0
  52. package/dist/engine/modes/timeShareMode.d.ts.map +1 -1
  53. package/dist/engine/modes/timeShareMode.js +27 -27
  54. package/dist/engine/modes/timeShareMode.js.map +1 -1
  55. package/dist/engine/paneRenderer.d.ts +2 -0
  56. package/dist/engine/paneRenderer.d.ts.map +1 -1
  57. package/dist/engine/paneRenderer.js +15 -5
  58. package/dist/engine/paneRenderer.js.map +1 -1
  59. package/dist/engine/render/chartRenderer.d.ts +28 -22
  60. package/dist/engine/render/chartRenderer.d.ts.map +1 -1
  61. package/dist/engine/render/chartRenderer.js +143 -85
  62. package/dist/engine/render/chartRenderer.js.map +1 -1
  63. package/dist/engine/render/layers/leftYAxisLayer.d.ts +9 -2
  64. package/dist/engine/render/layers/leftYAxisLayer.d.ts.map +1 -1
  65. package/dist/engine/render/layers/leftYAxisLayer.js +11 -2
  66. package/dist/engine/render/layers/leftYAxisLayer.js.map +1 -1
  67. package/dist/engine/render/layers/mainIndicatorLegendLayer.d.ts +6 -4
  68. package/dist/engine/render/layers/mainIndicatorLegendLayer.d.ts.map +1 -1
  69. package/dist/engine/render/layers/mainIndicatorLegendLayer.js +6 -9
  70. package/dist/engine/render/layers/mainIndicatorLegendLayer.js.map +1 -1
  71. package/dist/engine/render/layers/mainIndicatorLegendLayer.test.d.ts +2 -0
  72. package/dist/engine/render/layers/mainIndicatorLegendLayer.test.d.ts.map +1 -0
  73. package/dist/engine/render/layers/mainIndicatorLegendLayer.test.js +19 -0
  74. package/dist/engine/render/layers/mainIndicatorLegendLayer.test.js.map +1 -0
  75. package/dist/engine/render/layers/yAxisLayer.d.ts +9 -2
  76. package/dist/engine/render/layers/yAxisLayer.d.ts.map +1 -1
  77. package/dist/engine/render/layers/yAxisLayer.js +11 -2
  78. package/dist/engine/render/layers/yAxisLayer.js.map +1 -1
  79. package/dist/engine/renderers/Indicator/mainIndicatorLegend.d.ts +7 -3
  80. package/dist/engine/renderers/Indicator/mainIndicatorLegend.d.ts.map +1 -1
  81. package/dist/engine/renderers/Indicator/mainIndicatorLegend.js +234 -330
  82. package/dist/engine/renderers/Indicator/mainIndicatorLegend.js.map +1 -1
  83. package/dist/engine/renderers/Indicator/mainIndicatorLegendContext.d.ts +73 -0
  84. package/dist/engine/renderers/Indicator/mainIndicatorLegendContext.d.ts.map +1 -0
  85. package/dist/engine/renderers/Indicator/mainIndicatorLegendContext.js +182 -0
  86. package/dist/engine/renderers/Indicator/mainIndicatorLegendContext.js.map +1 -0
  87. package/dist/engine/renderers/leftYAxis.d.ts +15 -2
  88. package/dist/engine/renderers/leftYAxis.d.ts.map +1 -1
  89. package/dist/engine/renderers/leftYAxis.js +56 -16
  90. package/dist/engine/renderers/leftYAxis.js.map +1 -1
  91. package/dist/engine/renderers/timeShare.d.ts.map +1 -1
  92. package/dist/engine/renderers/timeShare.js +20 -9
  93. package/dist/engine/renderers/timeShare.js.map +1 -1
  94. package/dist/engine/renderers/yAxis.d.ts +16 -6
  95. package/dist/engine/renderers/yAxis.d.ts.map +1 -1
  96. package/dist/engine/renderers/yAxis.js +49 -16
  97. package/dist/engine/renderers/yAxis.js.map +1 -1
  98. package/dist/engine/state/viewportState.d.ts +4 -1
  99. package/dist/engine/state/viewportState.d.ts.map +1 -1
  100. package/dist/engine/state/viewportState.js +40 -8
  101. package/dist/engine/state/viewportState.js.map +1 -1
  102. package/dist/engine/subPaneManager.d.ts +5 -3
  103. package/dist/engine/subPaneManager.d.ts.map +1 -1
  104. package/dist/engine/subPaneManager.js +3 -2
  105. package/dist/engine/subPaneManager.js.map +1 -1
  106. package/dist/engine/utils/chartZoomController.d.ts +9 -7
  107. package/dist/engine/utils/chartZoomController.d.ts.map +1 -1
  108. package/dist/engine/utils/chartZoomController.js +13 -12
  109. package/dist/engine/utils/chartZoomController.js.map +1 -1
  110. package/dist/engine/viewport/chartViewportManager.d.ts +5 -16
  111. package/dist/engine/viewport/chartViewportManager.d.ts.map +1 -1
  112. package/dist/engine/viewport/chartViewportManager.js +7 -48
  113. package/dist/engine/viewport/chartViewportManager.js.map +1 -1
  114. package/dist/engine/viewport/viewport.d.ts +14 -0
  115. package/dist/engine/viewport/viewport.d.ts.map +1 -1
  116. package/dist/engine/viewport/viewport.js +10 -0
  117. package/dist/engine/viewport/viewport.js.map +1 -1
  118. package/dist/foundation/config/chartSettings.d.ts +0 -5
  119. package/dist/foundation/config/chartSettings.d.ts.map +1 -1
  120. package/dist/foundation/config/chartSettings.js +14 -0
  121. package/dist/foundation/config/chartSettings.js.map +1 -1
  122. package/dist/foundation/plugin/rendererPluginManager.test.js +15 -0
  123. package/dist/foundation/plugin/rendererPluginManager.test.js.map +1 -1
  124. package/dist/foundation/plugin/types.d.ts +3 -0
  125. package/dist/foundation/plugin/types.d.ts.map +1 -1
  126. package/dist/foundation/plugin/types.js.map +1 -1
  127. package/dist/foundation/reactivity/frameTransaction.d.ts +11 -0
  128. package/dist/foundation/reactivity/frameTransaction.d.ts.map +1 -1
  129. package/dist/foundation/reactivity/frameTransaction.js +17 -2
  130. package/dist/foundation/reactivity/frameTransaction.js.map +1 -1
  131. package/dist/foundation/utils/kLineDraw/axis.d.ts +3 -0
  132. package/dist/foundation/utils/kLineDraw/axis.d.ts.map +1 -1
  133. package/dist/foundation/utils/kLineDraw/axis.js +40 -9
  134. package/dist/foundation/utils/kLineDraw/axis.js.map +1 -1
  135. package/dist/foundation/utils/sessionTimeLabels.d.ts +59 -0
  136. package/dist/foundation/utils/sessionTimeLabels.d.ts.map +1 -0
  137. package/dist/foundation/utils/sessionTimeLabels.js +170 -0
  138. package/dist/foundation/utils/sessionTimeLabels.js.map +1 -0
  139. package/dist/foundation/utils/timeShareAxisLabels.d.ts +27 -0
  140. package/dist/foundation/utils/timeShareAxisLabels.d.ts.map +1 -0
  141. package/dist/foundation/utils/timeShareAxisLabels.js +45 -0
  142. package/dist/foundation/utils/timeShareAxisLabels.js.map +1 -0
  143. package/dist/rendering/render/Renderer.d.ts +58 -74
  144. package/dist/rendering/render/Renderer.d.ts.map +1 -1
  145. package/dist/rendering/render/Renderer.js +8 -18
  146. package/dist/rendering/render/Renderer.js.map +1 -1
  147. package/dist/rendering/render/createWebGPURenderer.d.ts.map +1 -1
  148. package/dist/rendering/render/createWebGPURenderer.js +30 -17
  149. package/dist/rendering/render/createWebGPURenderer.js.map +1 -1
  150. package/dist/rendering/render/createWebGPUSurfaceBackend.js +3 -3
  151. package/dist/rendering/render/createWebGPUSurfaceBackend.js.map +1 -1
  152. package/dist/rendering/render/physicalLine.d.ts +4 -0
  153. package/dist/rendering/render/physicalLine.d.ts.map +1 -0
  154. package/dist/rendering/render/physicalLine.js +37 -0
  155. package/dist/rendering/render/physicalLine.js.map +1 -0
  156. package/dist/rendering/render/physicalRegion.d.ts +14 -0
  157. package/dist/rendering/render/physicalRegion.d.ts.map +1 -0
  158. package/dist/rendering/render/physicalRegion.js +15 -0
  159. package/dist/rendering/render/physicalRegion.js.map +1 -0
  160. package/dist/rendering/scene/createLayerFromPlugin.d.ts.map +1 -1
  161. package/dist/rendering/scene/createLayerFromPlugin.js +8 -3
  162. package/dist/rendering/scene/createLayerFromPlugin.js.map +1 -1
  163. package/dist/rendering/scene/createScene.d.ts.map +1 -1
  164. package/dist/rendering/scene/createScene.js +4 -3
  165. package/dist/rendering/scene/createScene.js.map +1 -1
  166. package/dist/version.d.ts +1 -1
  167. package/dist/version.js +1 -1
  168. package/package.json +1 -1
  169. package/src/__tests__/stateKernel.test.ts +1 -0
  170. package/src/controllers/createChartController.ts +9 -8
  171. package/src/controllers/index.ts +11 -0
  172. package/src/controllers/types.ts +7 -0
  173. package/src/data/__tests__/timeShareBuffer.test.ts +45 -0
  174. package/src/data/binance.ts +1 -1
  175. package/src/data/timeShareBuffer.ts +15 -0
  176. package/src/engine/__tests__/chart.dpr.test.ts +31 -2
  177. package/src/engine/__tests__/paneRenderer.resize.test.ts +52 -54
  178. package/src/engine/__tests__/subPaneManager.test.ts +5 -3
  179. package/src/engine/chart.ts +105 -115
  180. package/src/engine/chartTypes.ts +5 -0
  181. package/src/engine/controller/__tests__/interaction.dpr.test.ts +39 -2
  182. package/src/engine/controller/interaction.ts +18 -10
  183. package/src/engine/data/__tests__/chartDataManager.incrementalLoad.test.ts +37 -17
  184. package/src/engine/data/__tests__/scrollCompensator.geometry.test.ts +87 -75
  185. package/src/engine/data/chartDataManager.ts +64 -59
  186. package/src/engine/data/incrementalLoadHint.ts +8 -7
  187. package/src/engine/data/scrollCompensator.ts +16 -24
  188. package/src/engine/indicators/__tests__/chartIndicatorManager.test.ts +18 -26
  189. package/src/engine/indicators/chartIndicatorManager.ts +62 -62
  190. package/src/engine/layout/chartPaneLayout.ts +73 -21
  191. package/src/engine/modes/__tests__/timeShareMath.test.ts +185 -0
  192. package/src/engine/modes/__tests__/timeShareMode.test.ts +81 -0
  193. package/src/engine/modes/timeShareMath.ts +150 -0
  194. package/src/engine/modes/timeShareMode.ts +35 -28
  195. package/src/engine/paneRenderer.ts +17 -10
  196. package/src/engine/render/chartRenderer.ts +214 -145
  197. package/src/engine/render/layers/leftYAxisLayer.ts +29 -7
  198. package/src/engine/render/layers/mainIndicatorLegendLayer.test.ts +26 -0
  199. package/src/engine/render/layers/mainIndicatorLegendLayer.ts +13 -10
  200. package/src/engine/render/layers/yAxisLayer.ts +29 -7
  201. package/src/engine/renderers/Indicator/mainIndicatorLegend.ts +270 -403
  202. package/src/engine/renderers/Indicator/mainIndicatorLegendContext.ts +290 -0
  203. package/src/engine/renderers/__tests__/mainIndicatorLegend.renderer.test.ts +73 -2
  204. package/src/engine/renderers/__tests__/yAxis.renderer.test.ts +10 -6
  205. package/src/engine/renderers/leftYAxis.ts +60 -18
  206. package/src/engine/renderers/timeShare.ts +25 -9
  207. package/src/engine/renderers/yAxis.ts +57 -22
  208. package/src/engine/state/__tests__/settingsState.test.ts +9 -0
  209. package/src/engine/state/viewportState.ts +44 -8
  210. package/src/engine/subPaneManager.ts +8 -5
  211. package/src/engine/utils/chartZoomController.ts +22 -19
  212. package/src/engine/viewport/__tests__/visibleRange.clamp.test.ts +50 -0
  213. package/src/engine/viewport/chartViewportManager.ts +9 -60
  214. package/src/engine/viewport/viewport.ts +14 -0
  215. package/src/foundation/config/chartSettings.ts +13 -0
  216. package/src/foundation/plugin/rendererPluginManager.test.ts +18 -0
  217. package/src/foundation/plugin/types.ts +3 -0
  218. package/src/foundation/reactivity/frameTransaction.ts +20 -6
  219. package/src/foundation/utils/__tests__/sessionTimeLabels.test.ts +91 -0
  220. package/src/foundation/utils/kLineDraw/axis.ts +50 -9
  221. package/src/foundation/utils/sessionTimeLabels.ts +237 -0
  222. package/src/foundation/utils/timeShareAxisLabels.ts +94 -0
  223. package/src/rendering/render/Renderer.ts +69 -85
  224. package/src/rendering/render/__tests__/physicalLine.test.ts +51 -0
  225. package/src/rendering/render/__tests__/physicalRegion.test.ts +23 -0
  226. package/src/rendering/render/__tests__/webgpuRenderer.test.ts +31 -1
  227. package/src/rendering/render/__tests__/webgpuSurfaceBackend.test.ts +11 -0
  228. package/src/rendering/render/createWebGPURenderer.ts +30 -17
  229. package/src/rendering/render/createWebGPUSurfaceBackend.ts +3 -3
  230. package/src/rendering/render/physicalLine.ts +45 -0
  231. package/src/rendering/render/physicalRegion.ts +32 -0
  232. package/src/rendering/scene/createLayerFromPlugin.ts +8 -3
  233. package/src/rendering/scene/createScene.ts +4 -3
  234. package/src/version.ts +1 -1
@@ -1,17 +1,10 @@
1
- import type { ChartDom, Viewport } from '../chartTypes'
2
1
  import { getPhysicalKLineConfig } from '../utils/klineConfig'
2
+ import type { ViewportStateModule } from '../state/viewportState'
3
3
 
4
4
  export interface ScrollDeps {
5
5
  getOption: () => { kWidth: number; kGap: number }
6
- getEffectiveDpr: () => number
7
- getCachedScrollLeft: () => number
8
- setScrollLeft: (v: number) => void
9
- getDom: () => ChartDom
10
- getObservedSize: () => { width: number; height: number }
11
- getViewport: () => Viewport | null
12
- /** 几何 SSOT:由 kernel viewport.readonly 注入 */
13
- getLeftLoadBufferWidth: () => number
14
- getContentWidth: () => number
6
+ /** scroll / dpr / 几何 SSOT */
7
+ viewport: ViewportStateModule
15
8
  }
16
9
 
17
10
  export const SCROLL_TRAILING_SLOTS = 30
@@ -20,53 +13,52 @@ export class ScrollCompensator {
20
13
  constructor(private deps: ScrollDeps) {}
21
14
 
22
15
  compensatePrepend(count: number): void {
23
- const dpr = this.deps.getEffectiveDpr()
16
+ const dpr = this.deps.viewport.readonly.dpr.peek()
24
17
  const opt = this.deps.getOption()
25
18
  const { unitPx } = getPhysicalKLineConfig(opt.kWidth, opt.kGap, dpr)
26
19
  const compensation = (count * unitPx) / dpr
27
- const nextScrollLeft = this.deps.getCachedScrollLeft() + compensation
28
- this.deps.setScrollLeft(nextScrollLeft)
20
+ const nextScrollLeft = this.deps.viewport.readonly.scrollLeft.peek() + compensation
21
+ this.deps.viewport.actions.scrollTo(nextScrollLeft)
29
22
  }
30
23
 
31
24
  adjustScrollAfterDataChange(dataLength: number): void {
32
- const scrollLeft = this.deps.getCachedScrollLeft()
25
+ const scrollLeft = this.deps.viewport.readonly.scrollLeft.peek()
33
26
  if (scrollLeft < 0) return
34
27
 
35
28
  if (scrollLeft <= 0) {
36
- this.deps.setScrollLeft(this.deps.getLeftLoadBufferWidth())
29
+ this.deps.viewport.actions.scrollTo(this.deps.viewport.readonly.leftLoadBufferWidth.peek())
37
30
  return
38
31
  }
39
32
 
40
- const dpr = this.deps.getEffectiveDpr()
33
+ const dpr = this.deps.viewport.readonly.dpr.peek()
41
34
  const opt = this.deps.getOption()
42
35
  const { unitPx, startXPx } = getPhysicalKLineConfig(opt.kWidth, opt.kGap, dpr)
43
36
  const totalDataWidth = (startXPx + dataLength * unitPx) / dpr
44
- const leftBuffer = this.deps.getLeftLoadBufferWidth()
37
+ const leftBuffer = this.deps.viewport.readonly.leftLoadBufferWidth.peek()
45
38
  if (scrollLeft >= leftBuffer + totalDataWidth) {
46
- this.deps.setScrollLeft(leftBuffer)
39
+ this.deps.viewport.actions.scrollTo(leftBuffer)
47
40
  }
48
41
  }
49
42
 
50
43
  scrollToRight(dataLength: number): void {
51
44
  if (dataLength === 0) return
52
- const dpr = this.deps.getEffectiveDpr()
45
+ const dpr = this.deps.viewport.readonly.dpr.peek()
53
46
  const opt = this.deps.getOption()
54
47
  const { unitPx, startXPx } = getPhysicalKLineConfig(opt.kWidth, opt.kGap, dpr)
55
48
  const lastKLineEndPx = (startXPx + dataLength * unitPx) / dpr
56
- const viewport = this.deps.getViewport()
57
- const clientWidth = viewport?.viewWidth ?? 0
49
+ const clientWidth = this.deps.viewport.readonly.viewWidth.peek()
58
50
  if (clientWidth <= 0) return
59
51
 
60
- const leftBuffer = this.deps.getLeftLoadBufferWidth()
52
+ const leftBuffer = this.deps.viewport.readonly.leftLoadBufferWidth.peek()
61
53
  let target: number
62
54
  if (lastKLineEndPx <= clientWidth) {
63
55
  target = leftBuffer - (clientWidth - lastKLineEndPx)
64
56
  } else {
65
57
  target = leftBuffer + (lastKLineEndPx - clientWidth)
66
58
  }
67
- const contentWidth = this.deps.getContentWidth()
59
+ const contentWidth = this.deps.viewport.readonly.contentWidth.peek()
68
60
  const maxScroll = Math.max(0, contentWidth - clientWidth)
69
61
  const scrollLeft = Math.round(Math.max(0, Math.min(target, maxScroll)) * dpr) / dpr
70
- this.deps.setScrollLeft(scrollLeft)
62
+ this.deps.viewport.actions.scrollTo(scrollLeft)
71
63
  }
72
64
  }
@@ -52,7 +52,7 @@ function createMockDeps() {
52
52
  upsertPane: vi.fn(),
53
53
  removePaneDefinition: vi.fn(),
54
54
  getPaneSpecs: vi.fn(() => []),
55
- getPaneRatiosSignal: () => paneRatiosSignal,
55
+ paneRatios$: paneRatiosSignal,
56
56
  paneSpecs$: paneSpecsSignal,
57
57
  getInternalPaneRatios: vi.fn(() => new Map()),
58
58
  setInternalPaneRatio: vi.fn(),
@@ -68,30 +68,22 @@ function createMockDeps() {
68
68
  removeLayer: vi.fn(() => false),
69
69
  getLayer: vi.fn(() => null),
70
70
  setLayerVisibility: vi.fn(),
71
- mainIndicators$: indicatorState.readonly.mainIndicators,
72
- upsertMainIndicator: (id, p) => indicatorState.actions.upsert(id, p),
73
- removeMainIndicator: (id) => indicatorState.actions.remove(id),
74
- setMainIndicatorParams: (id, p) => indicatorState.actions.setParams(id, p),
75
- replaceMainIndicators: (entries) => indicatorState.actions.replaceAll(entries),
76
- clearMainIndicators: () => indicatorState.actions.clear(),
77
- subPanes$: subPaneState.readonly.entries,
78
- createSubPaneState: vi.fn((paneId, indicatorId, params) =>
79
- subPaneState.actions.upsert({ paneId, indicatorId, params }),
80
- ),
81
- removeSubPaneState: vi.fn((paneId) => subPaneState.actions.remove(paneId)),
82
- replaceSubPaneState: vi.fn((paneId, indicatorId, params) =>
83
- subPaneState.actions.replace({ paneId, indicatorId, params }),
84
- ),
85
- updateSubPaneStateParams: vi.fn((paneId, params) =>
86
- subPaneState.actions.setParams(paneId, params),
87
- ),
88
- clearSubPaneState: vi.fn(() => subPaneState.actions.clear()),
71
+ indicator: indicatorState,
72
+ subPaneOps: {
73
+ entries: subPaneState.readonly.entries,
74
+ create: vi.fn((paneId, indicatorId, params) =>
75
+ subPaneState.actions.upsert({ paneId, indicatorId, params }),
76
+ ),
77
+ remove: vi.fn((paneId) => subPaneState.actions.remove(paneId)),
78
+ replace: vi.fn((paneId, indicatorId, params) =>
79
+ subPaneState.actions.replace({ paneId, indicatorId, params }),
80
+ ),
81
+ setParams: vi.fn((paneId, params) => subPaneState.actions.setParams(paneId, params)),
82
+ clear: vi.fn(() => subPaneState.actions.clear()),
83
+ },
89
84
  projectPaneLayout: vi.fn(),
90
85
  runRendererTransaction: (run) => run(),
91
86
  getIndicatorScheduler: vi.fn(),
92
- getRightAxisWidth: () => 60,
93
- getPriceLabelWidth: () => 60,
94
- getYPaddingPx: () => 4,
95
87
  } as IndicatorDependencies & { rendererMap: Map<string, any> }
96
88
  }
97
89
 
@@ -169,9 +161,9 @@ describe('ChartIndicatorManager', () => {
169
161
  it('creates sub-pane business state before projecting runtime resources', () => {
170
162
  expect(manager.createSubPane('RSI_0', 'RSI', { period1: 6 })).toBe(true)
171
163
 
172
- expect(deps.createSubPaneState).toHaveBeenCalledTimes(1)
164
+ expect(deps.subPaneOps.create).toHaveBeenCalledTimes(1)
173
165
  expect(deps.useRenderer).toHaveBeenCalled()
174
- expect(deps.createSubPaneState.mock.invocationCallOrder[0]).toBeLessThan(
166
+ expect(deps.subPaneOps.create.mock.invocationCallOrder[0]).toBeLessThan(
175
167
  deps.useRenderer.mock.invocationCallOrder[0]!,
176
168
  )
177
169
  expect(manager.getSubPaneEntry('RSI_0')?.params).toEqual({ period1: 6 })
@@ -183,7 +175,7 @@ describe('ChartIndicatorManager', () => {
183
175
 
184
176
  expect(manager.createSubPane('RSI_0', 'MACD', { fast: 5 })).toBe(true)
185
177
 
186
- expect(deps.replaceSubPaneState).not.toHaveBeenCalled()
178
+ expect(deps.subPaneOps.replace).not.toHaveBeenCalled()
187
179
  expect(manager.getSubPaneEntry('RSI_0')?.indicatorId).toBe('RSI')
188
180
  expect(manager.getSubPaneEntry('RSI_0')?.params).toEqual({ period1: 6 })
189
181
  })
@@ -213,7 +205,7 @@ describe('ChartIndicatorManager', () => {
213
205
  manager.destroy()
214
206
  vi.clearAllMocks()
215
207
 
216
- deps.upsertMainIndicator('MA', { ma5: true })
208
+ deps.indicator.actions.upsert('MA', { ma5: true })
217
209
 
218
210
  expect(deps.useRenderer).not.toHaveBeenCalled()
219
211
  expect(deps.scheduleDraw).not.toHaveBeenCalled()
@@ -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>,