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