@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
@@ -30,18 +30,16 @@ import { createDrawingRendererPlugin, createDrawingLabelOverlayPlugin } from '..
30
30
  import { ChartIndicatorManager } from '../indicators/chartIndicatorManager'
31
31
  import { UpdateLevel } from '../layout/pane'
32
32
  import type { VisibleRange } from '../layout/pane'
33
- import {
34
- MarkerManager,
35
- type CustomMarkerEntity,
36
- type MarkerManagerDeps,
37
- } from '../marker/registry'
33
+ import { MarkerManager, type CustomMarkerEntity, type MarkerManagerDeps } from '../marker/registry'
34
+ import { ASHARE_MARKET_SESSION } from '../../foundation/utils/timeShareAxisLabels'
35
+ import { resolveMarketSessionSlots } from '../../foundation/utils/timeShareAxisLabels'
36
+ import { computeTimeShareXLayout } from '../modes/timeShareMath'
38
37
  import type { ChartModeHandler } from '../modes/types'
39
38
  import { PaneRenderer } from '../paneRenderer'
40
39
  import { createTimeAxisRendererPlugin } from '../renderers/timeAxis'
41
40
  import { getPhysicalKLineConfig } from '../utils/klineConfig'
42
41
  import { calculateTickCount } from '../utils/tickCount'
43
- import { ChartViewportManager } from '../viewport/chartViewportManager'
44
- import { getVisibleRange } from '../viewport/viewport'
42
+
45
43
 
46
44
  import { createCandleLayer } from './layers/candleLayer'
47
45
  import { createComparisonLineLayer } from './layers/comparisonLineLayer'
@@ -51,11 +49,21 @@ import { createExtremaMarkersLayer } from './layers/extremaMarkersLayer'
51
49
  import { createGridLinesLayer } from './layers/gridLinesLayer'
52
50
  import { createLastPriceLabelLayer } from './layers/lastPriceLabelLayer'
53
51
  import { createLastPriceLineLayer } from './layers/lastPriceLineLayer'
54
- import { createLeftYAxisLayer } from './layers/leftYAxisLayer'
52
+ import {
53
+ createLeftYAxisOverlayLayer,
54
+ createLeftYAxisStaticLayer,
55
+ } from './layers/leftYAxisLayer'
55
56
  import { createMainIndicatorLegendLayer } from './layers/mainIndicatorLegendLayer'
56
- import { createYAxisLayer } from './layers/yAxisLayer'
57
- import { createFrameTransaction } from '../../foundation/reactivity/frameTransaction'
57
+ import { createYAxisOverlayLayer, createYAxisStaticLayer } from './layers/yAxisLayer'
58
+ import type { LayerRole } from '../../rendering/scene/types'
59
+ import {
60
+ createFrameTransaction,
61
+ type FrameTransaction,
62
+ } from '../../foundation/reactivity/frameTransaction'
58
63
  import type { ReadonlySignal } from '../../foundation/reactivity/signal'
64
+ import type { ViewportStateModule } from '../state/viewportState'
65
+ import type { ZoomStateModule } from '../state/zoomState'
66
+ import type { OptionsStateModule } from '../state/optionsState'
59
67
 
60
68
  type ResolvedChartOptions = Omit<ChartOptions, 'kWidth' | 'kGap'> & {
61
69
  kWidth: number
@@ -89,15 +97,12 @@ type FrameContext = {
89
97
  zoomLevelCount: number
90
98
  }
91
99
 
92
- /** 帧事务输入:仅重绘级别;多次 schedule 浅合并后由 mergeUpdateLevel 升格 */
100
+ /** 帧事务输入:合并多次 scheduleDraw level */
93
101
  type FrameDrawInput = {
94
102
  level: UpdateLevel
95
103
  }
96
104
 
97
- /**
98
- * 帧事务快照:单代际绘制契约。
99
- * generation 由 FrameTransaction 写入;frame 为 null 表示无可画数据。
100
- */
105
+ /** 单帧快照,frame 为 null 表示无可画数据 */
101
106
  type FrameDrawSnapshot = {
102
107
  generation: number
103
108
  level: UpdateLevel
@@ -105,7 +110,7 @@ type FrameDrawSnapshot = {
105
110
  }
106
111
 
107
112
  /** Main 与 Overlay 合并为 All,其余取更全或后者 */
108
- function mergeUpdateLevel(current: UpdateLevel, next: UpdateLevel): UpdateLevel {
113
+ export function mergeUpdateLevel(current: UpdateLevel, next: UpdateLevel): UpdateLevel {
109
114
  if (current === UpdateLevel.All || next === UpdateLevel.All) return UpdateLevel.All
110
115
  if (current === next) return current
111
116
  if (
@@ -125,10 +130,14 @@ export interface RendererDependencies {
125
130
  getSceneRenderer: () => Renderer
126
131
  getPluginHost: () => PluginHostImpl
127
132
  getRendererPluginManager: () => RendererPluginManager
128
- getTheme: () => 'light' | 'dark'
129
- getCurrentZoomLevel: () => number
130
- getZoomLevelCount: () => number
131
- getViewportManager: () => ChartViewportManager
133
+ /** 生效主题 SSOT */
134
+ theme$: ReadonlySignal<'light' | 'dark'>
135
+ /** zoomLevel / kWidth SSOT */
136
+ zoom: ZoomStateModule
137
+ /** zoomLevelCount 等 options SSOT */
138
+ options: OptionsStateModule
139
+ /** scroll / dpr / plot 几何 SSOT */
140
+ viewport: ViewportStateModule
132
141
  getDataManager: () => ChartDataManager
133
142
  getIndicatorManager: () => ChartIndicatorManager
134
143
  getActiveMode: () => ChartModeHandler
@@ -137,6 +146,10 @@ export interface RendererDependencies {
137
146
  drawings$: DrawingStoreDeps['drawings$']
138
147
  selectedDrawingId$: DrawingStoreDeps['selectedDrawingId$']
139
148
  getOverlay?: DrawingStoreDeps['getOverlay']
149
+ /** 主图图例上下文发布(canvas / external 均触发;draw 内回调) */
150
+ onLegendContext?: (
151
+ ctx: import('../renderers/Indicator/mainIndicatorLegendContext').LegendTemplateContext | null,
152
+ ) => void
140
153
  }
141
154
 
142
155
  export class ChartRenderer {
@@ -147,14 +160,9 @@ export class ChartRenderer {
147
160
  * 帧事务:scheduleDraw 只写输入并合并 rAF;flush 内 prepare → seal → paint。
148
161
  * 绘制阶段不再反向写 kernel 几何(seal 在 render 回调最前、与 paint 同代)。
149
162
  */
150
- private readonly frameTx: ReturnType<
151
- typeof createFrameTransaction<FrameDrawInput, FrameDrawSnapshot>
152
- >
163
+ private readonly frameTx: FrameTransaction<FrameDrawInput, FrameDrawSnapshot>
153
164
 
154
- /**
155
- * 与 frameTx pending 同步的重绘级别镜像。
156
- * FrameTransaction 对 level 是浅覆盖,Main/Overlay 升格在此完成。
157
- */
165
+ /** 合并多次 scheduleDraw 的 level,合并后写入 frameTx */
158
166
  private pendingLevel: UpdateLevel = UpdateLevel.All
159
167
 
160
168
  /** 已排队的 rAF 句柄;null 表示当前没有挂起的帧调度 */
@@ -207,11 +215,13 @@ export class ChartRenderer {
207
215
  render: (snapshot) => {
208
216
  // generation 0 占位不绘制
209
217
  if (snapshot.generation === 0) return
210
- // seal 几何 flush hover(同代)→ paint;禁止 paint 中途再写 kernel 几何
218
+ // 把本帧 K 线信息(kLinePositions,range,kWidthPx,kLineCenters)写入 InteractionController,保证 hover 命中与本帧一致
211
219
  if (snapshot.frame) {
212
220
  this.sealFrameGeometry(snapshot.frame)
213
221
  }
222
+ // 用本帧几何将鼠标坐标吸附到最近 K 线,算出十字线位置
214
223
  this.deps.getInteraction().flushPendingHover()
224
+ // 绘制:清 canvas → 构建 RenderContext → 遍历 pane 调 scene.paintPane → endFrame(GPU 一次性 submit 所有 pane)→ 时间轴
215
225
  this.drawWithFrame(snapshot.level, snapshot.frame)
216
226
  },
217
227
  schedule: (run) => {
@@ -280,11 +290,15 @@ export class ChartRenderer {
280
290
  this.scene.addLayer(layer)
281
291
  }
282
292
  {
283
- const layer = createMainIndicatorLegendLayer(
284
- { yPaddingPx: opt.yPaddingPx },
293
+ const { layer, plugin } = createMainIndicatorLegendLayer(
294
+ {
295
+ yPaddingPx: opt.yPaddingPx,
296
+ onContext: this.deps.onLegendContext,
297
+ },
285
298
  getCtx('main'),
286
- this.deps.getPluginHost(),
287
299
  )
300
+ // 注册进 Manager,使 updateRendererConfig('mainIndicatorLegend') 可切换 renderMode
301
+ this.deps.getRendererPluginManager().register(plugin)
288
302
  this.scene.addLayer(layer)
289
303
  }
290
304
  {
@@ -302,42 +316,38 @@ export class ChartRenderer {
302
316
  this.scene.addLayer(layer)
303
317
  }
304
318
  {
305
- const layer = createYAxisLayer(
306
- {
307
- axisWidth,
308
- yPaddingPx: opt.yPaddingPx,
309
- getCrosshair: () => {
310
- const pos = interaction.crosshairPos
311
- const price = interaction.crosshairPrice
312
- const activePaneId = interaction.activePaneId
313
- if (pos && price !== null) {
314
- return { y: pos.y, price, activePaneId }
315
- }
316
- return null
317
- },
319
+ const yAxisOpts = {
320
+ axisWidth,
321
+ yPaddingPx: opt.yPaddingPx,
322
+ getCrosshair: () => {
323
+ const pos = interaction.crosshairPos
324
+ const price = interaction.crosshairPrice
325
+ const activePaneId = interaction.activePaneId
326
+ if (pos && price !== null) {
327
+ return { y: pos.y, price, activePaneId }
328
+ }
329
+ return null
318
330
  },
319
- getCtxForCurrentPane,
320
- )
321
- this.scene.addLayer(layer)
331
+ }
332
+ this.scene.addLayer(createYAxisStaticLayer(yAxisOpts, getCtxForCurrentPane))
333
+ this.scene.addLayer(createYAxisOverlayLayer(yAxisOpts, getCtxForCurrentPane))
322
334
  }
323
335
  {
324
- const layer = createLeftYAxisLayer(
325
- {
326
- axisWidth: opt.leftAxisWidth,
327
- yPaddingPx: opt.yPaddingPx,
328
- getCrosshair: () => {
329
- const pos = interaction.crosshairPos
330
- const price = interaction.crosshairPrice
331
- const activePaneId = interaction.activePaneId
332
- if (pos && price !== null) {
333
- return { y: pos.y, price, activePaneId }
334
- }
335
- return null
336
- },
336
+ const leftYAxisOpts = {
337
+ axisWidth: opt.leftAxisWidth,
338
+ yPaddingPx: opt.yPaddingPx,
339
+ getCrosshair: () => {
340
+ const pos = interaction.crosshairPos
341
+ const price = interaction.crosshairPrice
342
+ const activePaneId = interaction.activePaneId
343
+ if (pos && price !== null) {
344
+ return { y: pos.y, price, activePaneId }
345
+ }
346
+ return null
337
347
  },
338
- getCtxForCurrentPane,
339
- )
340
- this.scene.addLayer(layer)
348
+ }
349
+ this.scene.addLayer(createLeftYAxisStaticLayer(leftYAxisOpts, getCtxForCurrentPane))
350
+ this.scene.addLayer(createLeftYAxisOverlayLayer(leftYAxisOpts, getCtxForCurrentPane))
341
351
  }
342
352
  }
343
353
 
@@ -395,6 +405,7 @@ export class ChartRenderer {
395
405
  * @param level - Main 只画主层,Overlay 只画覆盖层(crosshair 等),All 全画
396
406
  */
397
407
  scheduleDraw(level: UpdateLevel = UpdateLevel.All): void {
408
+ // 已经有待执行的下一帧,只合并 level,不重复 scheduleFlush
398
409
  if (this.raf !== null) {
399
410
  this.pendingLevel = mergeUpdateLevel(this.pendingLevel, level)
400
411
  this.frameTx.writeInput({ level: this.pendingLevel })
@@ -402,16 +413,16 @@ export class ChartRenderer {
402
413
  }
403
414
  this.pendingLevel = level
404
415
  this.frameTx.writeInput({ level })
416
+ // 提交帧,下次 rAF 上屏
405
417
  this.frameTx.scheduleFlush()
406
418
  }
407
419
 
408
420
  /**
409
- * 同步绘制一帧,走与 rAF 相同的 flush 管线(prepare → seal → paint)。
410
- *
411
- * 若已有 rAF 挂起:与 pendingLevel 合并后同步 flush(消费合并后的 level)。
412
- * 若正处于帧事务非 idle(render/publish 重入):只写输入并调度下一帧,禁止嵌套 flush。
421
+ * 立即同步重绘一帧,不走 rAF
413
422
  *
414
- * @param level - 同 scheduleDraw
423
+ * 若已有 rAF 挂起:合并本次 level 后同步 flush,把挂起的也带走。
424
+ * 若正处于帧事务非 idle(paint 重入中):只写输入,调度下一帧,不嵌套 flush。
425
+ * 与 scheduleDraw 的唯一区别:同步还是异步。
415
426
  */
416
427
  draw(level: UpdateLevel = UpdateLevel.All): void {
417
428
  if (this.frameTx.phase !== 'idle') {
@@ -428,10 +439,6 @@ export class ChartRenderer {
428
439
  }
429
440
  this.frameTx.writeInput({ level: this.pendingLevel })
430
441
  this.frameTx.flush()
431
- // 同步 flush 已消费本帧;若 flush 中又 scheduleDraw,raf 非 null,保留 pendingLevel
432
- if (this.raf === null) {
433
- this.pendingLevel = UpdateLevel.All
434
- }
435
442
  }
436
443
 
437
444
  /**
@@ -444,9 +451,11 @@ export class ChartRenderer {
444
451
  .setKLinePositions(frame.kLinePositions, frame.range, frame.kWidthPx, frame.kLineCenters)
445
452
  }
446
453
 
454
+ /** 将 prepareFrameData 的帧几何按 level 画到 canvas,含所有 pane 的 main/overlay/yAxis 及时间轴 */
447
455
  private drawWithFrame(level: UpdateLevel, frame: FrameContext | null): void {
448
456
  this.markerManager.clear()
449
457
 
458
+ // frame 为空(无数据或首帧),清理画布后跳过绘制
450
459
  if (!frame) {
451
460
  const dataManager = this.deps.getDataManager()
452
461
  if (dataManager.getInternalData().length === 0 && dataManager.getTimeShareData().length === 0)
@@ -459,7 +468,9 @@ export class ChartRenderer {
459
468
  const dataManager = this.deps.getDataManager()
460
469
  const mode = this.deps.getActiveMode()
461
470
  if (mode.useIndicatorScheduler) {
471
+ // 获取指标管理器实例(持有 scheduler、状态、reconcile 逻辑)
462
472
  const indicatorManager = this.deps.getIndicatorManager()
473
+ // 将主图指标列表(含参数)同步给 scheduler,使其在本帧预计算价格区间
463
474
  indicatorManager.indicatorSchedulerAccessor.setActiveMainIndicators(
464
475
  [...indicatorManager.mainIndicatorsSignalPeek.entries()].map(([id, entry]) => ({
465
476
  id,
@@ -474,13 +485,13 @@ export class ChartRenderer {
474
485
 
475
486
  const renderData = frame.data
476
487
 
488
+ // 遍历所有 pane,清 canvas → 构建 RenderContext → scene.paintPane
477
489
  const { sharedXAxisLabels, sharedXAxisRanges } = this.renderPanes(
478
490
  vp,
479
491
  range,
480
492
  kLinePositions,
481
493
  kLineCenters,
482
494
  kBarRects,
483
- kWidthPx,
484
495
  mainIndicatorRange,
485
496
  hasCrosshair,
486
497
  useCachedFrame,
@@ -489,6 +500,7 @@ export class ChartRenderer {
489
500
  )
490
501
 
491
502
  this.overlayHadCrosshair = hasCrosshair
503
+ // 画底部时间轴(独立 layer,不进 scene)
492
504
  this.renderXAxis(
493
505
  vp,
494
506
  range,
@@ -503,42 +515,35 @@ export class ChartRenderer {
503
515
  }
504
516
 
505
517
  /**
506
- * 准备一帧的绘制数据:viewport、可见区间、K 线位置。
518
+ * 计算一帧的 viewport、可见区间、K 线位置。
507
519
  *
508
- * Overlay 且上次画完没清缓存时,直接复用 cachedDrawFrame,不重复
509
- * viewport bar 位置。非缓存路径会刷新 cachedDrawFrame。
510
- * range 变了会调用 checkVisibleRangeGapWhenIdle,方便空闲时补数据。
511
- * TimeShare 模式按 plotWidth 平分 bar,不走 K 线物理宽度那套。
512
- *
513
- * @param level - Overlay 可走缓存,Main/All 强制重算
514
- * @returns 无 viewport 或无数据时返回 null,调用方自己清屏或跳过
520
+ * Overlay 时复用 cachedDrawFrame 跳过重算,Main/All 强制刷新缓存。
521
+ * range 变化时调 checkVisibleRangeGapWhenIdle 触发空闲补数据。
522
+ * TimeShare 模式按 plotWidth 平分 bar,覆盖 K 线位置。
515
523
  */
524
+ /** viewWidth 为 0 表示尚未完成首帧尺寸 */
525
+ private peekViewport(): Viewport | null {
526
+ if (this.deps.viewport.readonly.viewWidth.peek() === 0) return null
527
+ return this.deps.viewport.readonly.viewport.peek()
528
+ }
529
+
516
530
  private prepareFrameData(level: UpdateLevel): FrameContext | null {
517
531
  const useCachedFrame = level === UpdateLevel.Overlay && this.cachedDrawFrame !== null
518
532
 
519
- const vp = useCachedFrame
520
- ? this.cachedDrawFrame!.viewport
521
- : this.deps.getViewportManager().computeViewport()
533
+ const vp = useCachedFrame ? this.cachedDrawFrame!.viewport : this.peekViewport()
522
534
  if (!vp) return null
523
535
 
524
536
  const internalData = this.deps.getDataManager().getRenderData() as KLineData[]
525
537
  if (internalData.length === 0) return null
526
538
 
527
539
  const opt = this.deps.getOption()
528
- const rawRange = useCachedFrame
540
+ // 可见区间 SSOT 在 viewportState:clamped 可索引;raw 含扩窗(start 可能为 -1)
541
+ const range = useCachedFrame
529
542
  ? this.cachedDrawFrame!.range
530
- : (() => {
531
- const { start, end } = getVisibleRange(
532
- vp.scrollLeft,
533
- vp.plotWidth,
534
- opt.kWidth,
535
- opt.kGap,
536
- internalData.length,
537
- vp.dpr,
538
- )
539
- return { start, end }
540
- })()
541
- const range = { start: Math.max(0, rawRange.start), end: rawRange.end }
543
+ : this.deps.viewport.readonly.visibleRange.peek()
544
+ const rawRange = useCachedFrame
545
+ ? (this._prevFrameRange?.raw ?? range)
546
+ : this.deps.viewport.readonly.rawVisibleRange.peek()
542
547
 
543
548
  const dataManager = this.deps.getDataManager()
544
549
  const mode = this.deps.getActiveMode()
@@ -568,6 +573,7 @@ export class ChartRenderer {
568
573
  kWidthPx = this.cachedDrawFrame!.kWidthPx
569
574
  } else {
570
575
  const physConfig = getPhysicalKLineConfig(opt.kWidth, opt.kGap, vp.dpr)
576
+ // bar 宽度取奇数,保证 center 对齐整数像素
571
577
  let barWidthPx = Math.max(1, physConfig.unitPx - 1)
572
578
  if (barWidthPx % 2 === 0) barWidthPx -= 1
573
579
 
@@ -577,6 +583,7 @@ export class ChartRenderer {
577
583
  for (let i = 0; i < kLinePositions.length; i++) {
578
584
  const x = kLinePositions[i]!
579
585
  const leftPx = Math.round(x * vp.dpr)
586
+ // 影线 center = left + 半根物理宽度
580
587
  const wickXPx = leftPx + (physConfig.kWidthPx - 1) / 2
581
588
  kLineCenters[i] = wickXPx / vp.dpr
582
589
 
@@ -584,28 +591,31 @@ export class ChartRenderer {
584
591
  kBarRects[i] = { x: barLeftPx / vp.dpr, width: barWidthPx / vp.dpr }
585
592
  }
586
593
 
594
+ // TimeShare:按全天 sessionSlots 划分宽度,已到达点落在时间线上,未到时段右侧留白
587
595
  if (mode.debugName === 'TimeShare') {
588
- const totalWidth = vp.plotWidth
589
596
  const count = kLineCenters.length
590
- if (count > 0) {
591
- const dpr = vp.dpr
592
- const step = totalWidth / count
593
- for (let i = 0; i < count; i++) {
594
- kLineCenters[i] = Math.round((i + 0.5) * step * dpr) / dpr
595
- kLinePositions[i] = Math.round(i * step * dpr) / dpr
596
- }
597
- kWidthPx = Math.round((totalWidth * dpr) / count)
598
-
599
- const logicalBarWidth = Math.max(1, step * 0.6)
600
- const barWidthPx = Math.round(logicalBarWidth * dpr)
601
- const halfBarPx = Math.floor(barWidthPx / 2)
597
+ const marketSession =
598
+ 'marketSession' in mode && mode.marketSession
599
+ ? (mode as { marketSession: typeof ASHARE_MARKET_SESSION }).marketSession
600
+ : ASHARE_MARKET_SESSION
601
+ const layout = computeTimeShareXLayout({
602
+ arrivedCount: count,
603
+ sessionSlots: resolveMarketSessionSlots(marketSession),
604
+ totalWidth: vp.plotWidth,
605
+ dpr: vp.dpr,
606
+ })
607
+ if (layout) {
608
+ const halfBarPx = Math.floor((layout.barWidth * vp.dpr) / 2)
602
609
  for (let i = 0; i < count; i++) {
603
- const centerPx = Math.round(kLineCenters[i] * dpr)
610
+ kLineCenters[i] = layout.centers[i]!
611
+ kLinePositions[i] = layout.lefts[i]!
612
+ const centerPx = Math.round(layout.centers[i]! * vp.dpr)
604
613
  kBarRects[i] = {
605
- x: (centerPx - halfBarPx) / dpr,
606
- width: barWidthPx / dpr,
614
+ x: (centerPx - halfBarPx) / vp.dpr,
615
+ width: layout.barWidth,
607
616
  }
608
617
  }
618
+ kWidthPx = layout.kWidthPx
609
619
  } else {
610
620
  kWidthPx = getPhysicalKLineConfig(opt.kWidth, opt.kGap, vp.dpr).kWidthPx
611
621
  }
@@ -631,8 +641,8 @@ export class ChartRenderer {
631
641
  kWidthPx,
632
642
  useCachedFrame,
633
643
  data: internalData,
634
- zoomLevel: this.deps.getCurrentZoomLevel(),
635
- zoomLevelCount: this.deps.getZoomLevelCount(),
644
+ zoomLevel: this.deps.zoom.readonly.zoomLevel.peek(),
645
+ zoomLevelCount: this.deps.options.readonly.options.peek().zoomLevelCount,
636
646
  }
637
647
  }
638
648
 
@@ -648,20 +658,28 @@ export class ChartRenderer {
648
658
  }
649
659
 
650
660
  clearAllCanvases(): void {
651
- const vp = this.deps.getViewportManager().computeViewport()
661
+ const vp = this.peekViewport()
652
662
  if (!vp) return
653
663
  for (const r of this.deps.getPaneRenderers()) {
654
- const { mainCtx, overlayCtx, yAxisCtx, leftAxisCtx } = r.getContexts()
664
+ const { mainCtx, overlayCtx, yAxisCtx, yAxisOverlayCtx, leftAxisCtx, leftAxisOverlayCtx } =
665
+ r.getContexts()
655
666
  const pane = r.getPane()
656
667
  mainCtx?.clearRect(0, 0, vp.plotWidth + 1, pane.height + 2 / vp.dpr)
657
668
  overlayCtx?.clearRect(0, 0, vp.plotWidth + 1, pane.height + 2 / vp.dpr)
658
- yAxisCtx?.clearRect(0, 0, vp.plotWidth + 1, pane.height + 2 / vp.dpr)
669
+ yAxisCtx?.clearRect(0, 0, (yAxisCtx.canvas?.width ?? 0) / vp.dpr || vp.plotWidth + 1, pane.height + 2 / vp.dpr)
670
+ yAxisOverlayCtx?.clearRect(
671
+ 0,
672
+ 0,
673
+ (yAxisOverlayCtx.canvas?.width ?? 0) / vp.dpr || vp.plotWidth + 1,
674
+ pane.height + 2 / vp.dpr,
675
+ )
659
676
  if (leftAxisCtx) {
660
- const leftCanvas = leftAxisCtx.canvas
661
- if (leftCanvas) {
662
- const laW = leftCanvas.width / vp.dpr
663
- leftAxisCtx.clearRect(0, 0, laW, pane.height + 2 / vp.dpr)
664
- }
677
+ const laW = (leftAxisCtx.canvas?.width ?? 0) / vp.dpr || vp.plotWidth + 1
678
+ leftAxisCtx.clearRect(0, 0, laW, pane.height + 2 / vp.dpr)
679
+ }
680
+ if (leftAxisOverlayCtx) {
681
+ const laW = (leftAxisOverlayCtx.canvas?.width ?? 0) / vp.dpr || vp.plotWidth + 1
682
+ leftAxisOverlayCtx.clearRect(0, 0, laW, pane.height + 2 / vp.dpr)
665
683
  }
666
684
  }
667
685
  const xCtx = this.xAxisCtx
@@ -683,19 +701,20 @@ export class ChartRenderer {
683
701
  }
684
702
  }
685
703
 
704
+ /** 遍历所有 pane,逐 pane 清 canvas → 构建 RenderContext → beginFrame → scene.paintPane,所有 pane 结束后 endFrame 统一提交 GPU / 时间轴 */
686
705
  private renderPanes(
687
706
  vp: Viewport,
688
707
  range: VisibleRange,
689
708
  kLinePositions: KLinePositions,
690
709
  kLineCenters: number[],
691
710
  kBarRects: Array<{ x: number; width: number }>,
692
- kWidthPx: number,
693
711
  mainIndicatorRange: { min: number; max: number } | null,
694
712
  hasCrosshair: boolean,
695
713
  useCachedFrame: boolean,
696
714
  level: UpdateLevel,
697
715
  renderData: unknown[],
698
716
  ): { sharedXAxisLabels: XAxisLabel[]; sharedXAxisRanges: XAxisRange[] } {
717
+ // 跨 pane 共享的 Y/X 轴 labels 和 ranges(各 layer 写入,时间轴层读取)
699
718
  const sharedYAxisLabels: YAxisLabel[] = []
700
719
  const sharedXAxisLabels: XAxisLabel[] = []
701
720
  const sharedYAxisRanges: YAxisRange[] = []
@@ -704,10 +723,28 @@ export class ChartRenderer {
704
723
  const dataManager = this.deps.getDataManager()
705
724
  const mode = this.deps.getActiveMode()
706
725
 
726
+ // main canvas 只画非 overlay 角色;overlay canvas 只画 overlay 角色
727
+ const MAIN_CANVAS_ROLES: readonly LayerRole[] = [
728
+ 'background',
729
+ 'primary',
730
+ 'indicator',
731
+ 'component',
732
+ 'drawing',
733
+ ]
734
+
735
+ // 遍历主图 pane 和所有子图 pane,每个 pane 有一组独立 canvas 以及对应更新级别(main/overlay/yAxis)
707
736
  for (const renderer of this.deps.getPaneRenderers()) {
708
737
  const pane = renderer.getPane()
709
- const { mainCtx, overlayCtx, yAxisCtx, leftAxisCtx } = renderer.getContexts()
710
-
738
+ const {
739
+ mainCtx,
740
+ overlayCtx,
741
+ yAxisCtx,
742
+ yAxisOverlayCtx,
743
+ leftAxisCtx,
744
+ leftAxisOverlayCtx,
745
+ } = renderer.getContexts()
746
+
747
+ // 非缓存帧:合并指标与对比标的极值,更新 pane Y 轴范围
711
748
  if (!useCachedFrame) {
712
749
  const indicatorRange =
713
750
  pane.role === 'price' && mode.useIndicatorScheduler ? mainIndicatorRange : null
@@ -720,17 +757,21 @@ export class ChartRenderer {
720
757
  }
721
758
  }
722
759
 
760
+ // 根据 UpdateLevel 决定清哪些 canvas
723
761
  const shouldUpdateMain = level === UpdateLevel.Main || level === UpdateLevel.All
762
+ // Overlay 单独重绘:有十字线才画;overlayHadCrosshair 保证十字线消失时最后清一次
724
763
  const shouldUpdateOverlay =
725
764
  level === UpdateLevel.All ||
726
765
  (level === UpdateLevel.Overlay && (hasCrosshair || this.overlayHadCrosshair))
727
766
 
767
+ // 清 main canvas
728
768
  if (shouldUpdateMain && mainCtx) {
729
769
  mainCtx.setTransform(1, 0, 0, 1, 0, 0)
730
770
  mainCtx.scale(vp.dpr, vp.dpr)
731
771
  mainCtx.clearRect(0, 0, vp.plotWidth + 1, pane.height + 2 / vp.dpr)
732
772
  }
733
773
 
774
+ // 清 overlay canvas
734
775
  if (shouldUpdateOverlay && overlayCtx) {
735
776
  const overlayWidth = overlayCtx.canvas.width / vp.dpr
736
777
  overlayCtx.setTransform(1, 0, 0, 1, 0, 0)
@@ -738,15 +779,26 @@ export class ChartRenderer {
738
779
  overlayCtx.clearRect(0, 0, overlayWidth + 1, pane.height + 2 / vp.dpr)
739
780
  }
740
781
 
741
- if (yAxisCtx && !useCachedFrame) {
782
+ // Y 轴静态 canvas(Main/All)
783
+ if (shouldUpdateMain && yAxisCtx) {
742
784
  const yAxisWidth = yAxisCtx.canvas.width / vp.dpr
743
785
  this.clearAxisCtx(yAxisCtx, vp.dpr, yAxisWidth, pane.height)
744
786
  }
745
- if (leftAxisCtx && !useCachedFrame) {
787
+ if (shouldUpdateMain && leftAxisCtx) {
746
788
  const leftAxisWidth = leftAxisCtx.canvas.width / vp.dpr
747
789
  this.clearAxisCtx(leftAxisCtx, vp.dpr, leftAxisWidth, pane.height)
748
790
  }
791
+ // 清 Y 轴动态 canvas(Overlay/All)
792
+ if (shouldUpdateOverlay && yAxisOverlayCtx) {
793
+ const yAxisWidth = yAxisOverlayCtx.canvas.width / vp.dpr
794
+ this.clearAxisCtx(yAxisOverlayCtx, vp.dpr, yAxisWidth, pane.height)
795
+ }
796
+ if (shouldUpdateOverlay && leftAxisOverlayCtx) {
797
+ const leftAxisWidth = leftAxisOverlayCtx.canvas.width / vp.dpr
798
+ this.clearAxisCtx(leftAxisOverlayCtx, vp.dpr, leftAxisWidth, pane.height)
799
+ }
749
800
 
801
+ // 构造本 pane 的 RenderContext,供所有 layer 读取
750
802
  const opt = this.deps.getOption()
751
803
  const context: RenderContext = {
752
804
  ctx: mainCtx!,
@@ -769,26 +821,35 @@ export class ChartRenderer {
769
821
  markerManager: this.markerManager,
770
822
  crosshairIndex: this.deps.getInteraction().getCrosshairIndex(),
771
823
  yAxisCtx: yAxisCtx ?? undefined,
824
+ yAxisOverlayCtx: yAxisOverlayCtx ?? undefined,
772
825
  leftAxisCtx: leftAxisCtx ?? undefined,
773
- zoomLevel: this.deps.getCurrentZoomLevel(),
774
- zoomLevelCount: this.deps.getZoomLevelCount(),
826
+ leftAxisOverlayCtx: leftAxisOverlayCtx ?? undefined,
827
+ zoomLevel: this.deps.zoom.readonly.zoomLevel.peek(),
828
+ zoomLevelCount: this.deps.options.readonly.options.peek().zoomLevelCount,
775
829
  viewport: {
776
830
  scrollLeft: vp.scrollLeft,
777
831
  plotWidth: vp.plotWidth,
778
832
  plotHeight: vp.plotHeight,
779
833
  },
780
- settings: this.settings,
834
+ settings: {
835
+ ...this.settings,
836
+ // 分时昨收优先读 series 元数据,settings 作回退
837
+ preClose:
838
+ dataManager.getTimeSharePreClose() ??
839
+ (this.settings.preClose as number | undefined),
840
+ },
781
841
  yAxisLabels: sharedYAxisLabels,
782
842
  xAxisLabels: sharedXAxisLabels,
783
843
  yAxisRanges: sharedYAxisRanges,
784
844
  xAxisRanges: sharedXAxisRanges,
785
- theme: this.deps.getTheme(),
845
+ theme: this.deps.theme$.peek(),
786
846
  isAsiaMarket: this.settings.isAsiaMarket as boolean,
787
847
  colorPresetSettings: this.settings.colorPresetSettings,
788
848
  monthKeys: dataManager.getMonthKeys() ?? undefined,
789
849
  dayKeys: dataManager.getDayKeys() ?? undefined,
790
850
  }
791
851
 
852
+ // 计算本 pane 的 Y 轴刻度(等分 + yToPrice 映射)
792
853
  {
793
854
  const pt = pane.yAxis.getPaddingTop()
794
855
  const pb = pane.yAxis.getPaddingBottom()
@@ -811,24 +872,32 @@ export class ChartRenderer {
811
872
 
812
873
  const region = { x: 0, y: pane.top, width: vp.plotWidth, height: pane.height, dpr: vp.dpr }
813
874
  const sceneRenderer = this.deps.getSceneRenderer()
875
+ const paneRole = (pane.id === 'main' ? 'main' : 'sub') as PaneRole
876
+ // 画 main canvas(非 overlay 角色 layer)
814
877
  if (shouldUpdateMain) {
878
+ // 标记后续 GPU 绘制属于此 region
815
879
  sceneRenderer.beginFrame(region)
816
- this.scene.paintPane({
817
- renderer: sceneRenderer,
818
- region,
819
- paneRole: (pane.id === 'main' ? 'main' : 'sub') as PaneRole,
820
- paneId: pane.id,
821
- frameNumber: this.frameCount++,
822
- deltaMs: 0,
823
- })
880
+ // 遍历 Scene 中该 pane 的可见 layer,逐层 paint
881
+ this.scene.paintPane(
882
+ {
883
+ renderer: sceneRenderer,
884
+ region,
885
+ paneRole,
886
+ paneId: pane.id,
887
+ frameNumber: this.frameCount++,
888
+ deltaMs: 0,
889
+ },
890
+ MAIN_CANVAS_ROLES,
891
+ )
824
892
  }
825
- if (shouldUpdateOverlay && !shouldUpdateMain) {
893
+ // overlay canvas(仅 overlay 角色 layer);All 级也画
894
+ if (shouldUpdateOverlay) {
826
895
  sceneRenderer.beginFrame(region)
827
896
  this.scene.paintPane(
828
897
  {
829
898
  renderer: sceneRenderer,
830
899
  region,
831
- paneRole: (pane.id === 'main' ? 'main' : 'sub') as PaneRole,
900
+ paneRole,
832
901
  paneId: pane.id,
833
902
  frameNumber: this.frameCount++,
834
903
  deltaMs: 0,
@@ -838,7 +907,7 @@ export class ChartRenderer {
838
907
  }
839
908
  }
840
909
 
841
- // WebGPU: one submit after all panes recorded draws
910
+ // 所有 pane 绘制完成后统一提交 GPU(WebGPU 单次 queue.submit,WebGL 单次 flush)
842
911
  this.deps.getSceneRenderer().endFrame()
843
912
 
844
913
  return { sharedXAxisLabels, sharedXAxisRanges }
@@ -911,7 +980,7 @@ export class ChartRenderer {
911
980
  yAxisLabels: [],
912
981
  xAxisLabels: sharedXAxisLabels,
913
982
  xAxisRanges: sharedXAxisRanges,
914
- theme: this.deps.getTheme(),
983
+ theme: this.deps.theme$.peek(),
915
984
  isAsiaMarket: this.settings.isAsiaMarket as boolean,
916
985
  colorPresetSettings: this.settings.colorPresetSettings,
917
986
  monthKeys: dataManager.getMonthKeys() ?? undefined,
@@ -935,7 +1004,7 @@ export class ChartRenderer {
935
1004
 
936
1005
  if (count <= 0) return []
937
1006
 
938
- const dpr = this.deps.getViewportManager().getEffectiveDpr()
1007
+ const dpr = this.deps.viewport.readonly.dpr.peek()
939
1008
  const opt = this.deps.getOption()
940
1009
  const { unitPx, startXPx } = getPhysicalKLineConfig(opt.kWidth, opt.kGap, dpr)
941
1010