@363045841yyt/klinechart-core 0.8.1-alpha.4 → 0.8.2

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 (167) hide show
  1. package/dist/controllers/createChartController.d.ts.map +1 -1
  2. package/dist/controllers/createChartController.js +21 -1
  3. package/dist/controllers/createChartController.js.map +1 -1
  4. package/dist/controllers/types.d.ts +6 -1
  5. package/dist/controllers/types.d.ts.map +1 -1
  6. package/dist/data-fetchers/baostock.js +3 -3
  7. package/dist/data-fetchers/baostock.js.map +1 -1
  8. package/dist/data-fetchers/dataBuffer.d.ts +5 -1
  9. package/dist/data-fetchers/dataBuffer.d.ts.map +1 -1
  10. package/dist/data-fetchers/dataBuffer.js +85 -48
  11. package/dist/data-fetchers/dataBuffer.js.map +1 -1
  12. package/dist/data-fetchers/gotdx.d.ts +3 -0
  13. package/dist/data-fetchers/gotdx.d.ts.map +1 -0
  14. package/dist/data-fetchers/gotdx.js +101 -0
  15. package/dist/data-fetchers/gotdx.js.map +1 -0
  16. package/dist/data-fetchers/hundred-mock.d.ts.map +1 -1
  17. package/dist/data-fetchers/hundred-mock.js +28 -5
  18. package/dist/data-fetchers/hundred-mock.js.map +1 -1
  19. package/dist/data-fetchers/index.d.ts +1 -0
  20. package/dist/data-fetchers/index.d.ts.map +1 -1
  21. package/dist/data-fetchers/index.js +1 -0
  22. package/dist/data-fetchers/index.js.map +1 -1
  23. package/dist/data-fetchers/router.d.ts.map +1 -1
  24. package/dist/data-fetchers/router.js +3 -0
  25. package/dist/data-fetchers/router.js.map +1 -1
  26. package/dist/data-fetchers/thousand-mock.d.ts.map +1 -1
  27. package/dist/data-fetchers/thousand-mock.js +24 -14
  28. package/dist/data-fetchers/thousand-mock.js.map +1 -1
  29. package/dist/data-fetchers/tradingview.d.ts.map +1 -1
  30. package/dist/data-fetchers/tradingview.js +12 -6
  31. package/dist/data-fetchers/tradingview.js.map +1 -1
  32. package/dist/engine/chart.d.ts +29 -367
  33. package/dist/engine/chart.d.ts.map +1 -1
  34. package/dist/engine/chart.js +239 -1842
  35. package/dist/engine/chart.js.map +1 -1
  36. package/dist/engine/chartContext.d.ts +24 -0
  37. package/dist/engine/chartContext.d.ts.map +1 -0
  38. package/dist/engine/chartContext.js +19 -0
  39. package/dist/engine/chartContext.js.map +1 -0
  40. package/dist/engine/chartTypes.d.ts +77 -0
  41. package/dist/engine/chartTypes.d.ts.map +1 -0
  42. package/dist/engine/chartTypes.js +2 -0
  43. package/dist/engine/chartTypes.js.map +1 -0
  44. package/dist/engine/data/chartDataManager.d.ts +103 -0
  45. package/dist/engine/data/chartDataManager.d.ts.map +1 -0
  46. package/dist/engine/data/chartDataManager.js +593 -0
  47. package/dist/engine/data/chartDataManager.js.map +1 -0
  48. package/dist/engine/indicators/chartIndicatorManager.d.ts +102 -0
  49. package/dist/engine/indicators/chartIndicatorManager.d.ts.map +1 -0
  50. package/dist/engine/indicators/chartIndicatorManager.js +437 -0
  51. package/dist/engine/indicators/chartIndicatorManager.js.map +1 -0
  52. package/dist/engine/layout/chartPaneLayout.d.ts +53 -0
  53. package/dist/engine/layout/chartPaneLayout.d.ts.map +1 -0
  54. package/dist/engine/layout/chartPaneLayout.js +388 -0
  55. package/dist/engine/layout/chartPaneLayout.js.map +1 -0
  56. package/dist/engine/render/chartRenderer.d.ts +86 -0
  57. package/dist/engine/render/chartRenderer.d.ts.map +1 -0
  58. package/dist/engine/render/chartRenderer.js +440 -0
  59. package/dist/engine/render/chartRenderer.js.map +1 -0
  60. package/dist/engine/renderers/Indicator/mainIndicatorLegend.d.ts.map +1 -1
  61. package/dist/engine/renderers/Indicator/mainIndicatorLegend.js +73 -7
  62. package/dist/engine/renderers/Indicator/mainIndicatorLegend.js.map +1 -1
  63. package/dist/engine/renderers/comparisonLine.d.ts.map +1 -1
  64. package/dist/engine/renderers/comparisonLine.js +25 -11
  65. package/dist/engine/renderers/comparisonLine.js.map +1 -1
  66. package/dist/engine/renderers/timeAxis.d.ts.map +1 -1
  67. package/dist/engine/renderers/timeAxis.js +1 -0
  68. package/dist/engine/renderers/timeAxis.js.map +1 -1
  69. package/dist/engine/subPaneManager.d.ts +27 -6
  70. package/dist/engine/subPaneManager.d.ts.map +1 -1
  71. package/dist/engine/subPaneManager.js +54 -56
  72. package/dist/engine/subPaneManager.js.map +1 -1
  73. package/dist/engine/utils/chartZoomController.d.ts +33 -0
  74. package/dist/engine/utils/chartZoomController.d.ts.map +1 -0
  75. package/dist/engine/utils/chartZoomController.js +66 -0
  76. package/dist/engine/utils/chartZoomController.js.map +1 -0
  77. package/dist/engine/viewport/chartViewportManager.d.ts +72 -0
  78. package/dist/engine/viewport/chartViewportManager.d.ts.map +1 -0
  79. package/dist/engine/viewport/chartViewportManager.js +249 -0
  80. package/dist/engine/viewport/chartViewportManager.js.map +1 -0
  81. package/dist/index.d.ts +1 -0
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +1 -0
  84. package/dist/index.js.map +1 -1
  85. package/dist/plugin/types.d.ts +3 -0
  86. package/dist/plugin/types.d.ts.map +1 -1
  87. package/dist/plugin/types.js.map +1 -1
  88. package/dist/semantic/index.d.ts +1 -1
  89. package/dist/semantic/index.d.ts.map +1 -1
  90. package/dist/semantic/index.js.map +1 -1
  91. package/dist/semantic/schema.json +1 -1
  92. package/dist/semantic/types.d.ts +2 -1
  93. package/dist/semantic/types.d.ts.map +1 -1
  94. package/dist/tokens/theme-china.d.ts.map +1 -1
  95. package/dist/tokens/theme-china.js +0 -4
  96. package/dist/tokens/theme-china.js.map +1 -1
  97. package/dist/tokens/theme-dark.d.ts.map +1 -1
  98. package/dist/tokens/theme-dark.js +0 -4
  99. package/dist/tokens/theme-dark.js.map +1 -1
  100. package/dist/tokens/theme-light.d.ts.map +1 -1
  101. package/dist/tokens/theme-light.js +1 -5
  102. package/dist/tokens/theme-light.js.map +1 -1
  103. package/dist/tokens/types.d.ts +0 -4
  104. package/dist/tokens/types.d.ts.map +1 -1
  105. package/dist/types/price.d.ts +2 -0
  106. package/dist/types/price.d.ts.map +1 -1
  107. package/dist/types/price.js.map +1 -1
  108. package/dist/utils/dateFormat.d.ts +25 -0
  109. package/dist/utils/dateFormat.d.ts.map +1 -1
  110. package/dist/utils/dateFormat.js +78 -0
  111. package/dist/utils/dateFormat.js.map +1 -1
  112. package/dist/utils/kLineDraw/axis.d.ts +2 -0
  113. package/dist/utils/kLineDraw/axis.d.ts.map +1 -1
  114. package/dist/utils/kLineDraw/axis.js +11 -6
  115. package/dist/utils/kLineDraw/axis.js.map +1 -1
  116. package/dist/version.d.ts +1 -1
  117. package/dist/version.d.ts.map +1 -1
  118. package/dist/version.js +1 -1
  119. package/dist/version.js.map +1 -1
  120. package/package.json +1 -1
  121. package/src/controllers/createChartController.ts +39 -10
  122. package/src/controllers/types.ts +6 -1
  123. package/src/data-fetchers/__tests__/dataBuffer.test.ts +5 -2
  124. package/src/data-fetchers/baostock.ts +3 -3
  125. package/src/data-fetchers/dataBuffer.ts +70 -23
  126. package/src/data-fetchers/gotdx.ts +138 -0
  127. package/src/data-fetchers/hundred-mock.ts +35 -5
  128. package/src/data-fetchers/index.ts +1 -0
  129. package/src/data-fetchers/router.ts +3 -0
  130. package/src/data-fetchers/thousand-mock.ts +30 -14
  131. package/src/data-fetchers/tradingview.ts +12 -6
  132. package/src/engine/__tests__/subPaneManager.test.ts +154 -0
  133. package/src/engine/chart.ts +252 -2250
  134. package/src/engine/chartContext.ts +34 -0
  135. package/src/engine/chartTypes.ts +88 -0
  136. package/src/engine/data/chartDataManager.ts +695 -0
  137. package/src/engine/indicators/__tests__/chartIndicatorManager.test.ts +103 -0
  138. package/src/engine/indicators/chartIndicatorManager.ts +566 -0
  139. package/src/engine/layout/chartPaneLayout.ts +474 -0
  140. package/src/engine/render/chartRenderer.ts +581 -0
  141. package/src/engine/renderers/Indicator/mainIndicatorLegend.ts +99 -13
  142. package/src/engine/renderers/__tests__/boll.renderer.test.ts +1 -0
  143. package/src/engine/renderers/__tests__/ene.renderer.test.ts +1 -0
  144. package/src/engine/renderers/__tests__/expma.renderer.test.ts +1 -0
  145. package/src/engine/renderers/__tests__/ma.renderer.test.ts +1 -0
  146. package/src/engine/renderers/__tests__/mainIndicatorLegend.renderer.test.ts +1 -0
  147. package/src/engine/renderers/__tests__/yAxis.renderer.test.ts +1 -0
  148. package/src/engine/renderers/comparisonLine.ts +25 -11
  149. package/src/engine/renderers/timeAxis.ts +1 -0
  150. package/src/engine/subPaneManager.ts +75 -59
  151. package/src/engine/utils/chartZoomController.ts +104 -0
  152. package/src/engine/viewport/chartViewportManager.ts +310 -0
  153. package/src/index.ts +1 -0
  154. package/src/plugin/types.ts +3 -0
  155. package/src/semantic/index.ts +1 -0
  156. package/src/semantic/schema.json +1 -1
  157. package/src/semantic/types.ts +3 -1
  158. package/src/tokens/__tests__/__snapshots__/baseline.test.ts.snap +1 -9
  159. package/src/tokens/theme-china.ts +0 -4
  160. package/src/tokens/theme-dark.ts +0 -4
  161. package/src/tokens/theme-light.ts +2 -6
  162. package/src/tokens/types.ts +0 -4
  163. package/src/types/price.ts +2 -0
  164. package/src/utils/dateFormat.ts +85 -0
  165. package/src/utils/kLineDraw/axis.ts +13 -6
  166. package/src/version.ts +1 -1
  167. package/src/engine/chart.d.ts +0 -626
@@ -0,0 +1,581 @@
1
+ import type { KLineData } from '../../types/price'
2
+ import type { ChartSettings } from '../../config/chartSettings'
3
+ import type { SymbolSpec } from '../../controllers/types'
4
+ import { getVisibleRange } from '../viewport/viewport'
5
+ import type { RendererPlugin, RendererPluginWithHost, PluginHostImpl, RenderContext, YAxisLabel, XAxisLabel, YAxisRange, XAxisRange } from '../../plugin'
6
+ import { RendererPluginManager, wrapPaneInfo } from '../../plugin'
7
+ import type { ChartDom, PaneSpec, ChartOptions, KLinePositions, Viewport, ViewportState } from '../chartTypes'
8
+ import { PaneRenderer } from '../paneRenderer'
9
+ import { SharedWebGLSurface } from '../renderers/webgl/sharedWebGLSurface'
10
+ import { MarkerManager, type CustomMarkerEntity } from '../marker/registry'
11
+ import { getPhysicalKLineConfig } from '../utils/klineConfig'
12
+ import { ChartViewportManager } from '../viewport/chartViewportManager'
13
+ import { ChartDataManager } from '../data/chartDataManager'
14
+ import { ChartIndicatorManager } from '../indicators/chartIndicatorManager'
15
+ import { InteractionController } from '../controller/interaction'
16
+ import { UpdateLevel } from '../layout/pane'
17
+ import type { VisibleRange } from '../layout/pane'
18
+ import { DrawingStore } from '../drawing'
19
+ import { createMainIndicatorLegendRendererPlugin } from '../renderers/Indicator/mainIndicatorLegend'
20
+ import { createDrawingRendererPlugin, createDrawingLabelOverlayPlugin } from '../drawing/plugin'
21
+ import { createGridLinesRendererPlugin } from '../renderers/gridLines'
22
+ import { createCandleRenderer } from '../renderers/candle'
23
+ import { createComparisonLineRenderer } from '../renderers/comparisonLine'
24
+ import { createLastPriceLineRendererPlugin, createLastPriceLabelRegistrarPlugin } from '../renderers/lastPrice'
25
+ import { createCustomMarkersRenderer } from '../renderers/customMarkers'
26
+ import { createExtremaMarkersRendererPlugin } from '../renderers/extremaMarkers'
27
+ import { createYAxisRendererPlugin } from '../renderers/yAxis'
28
+ import { createCrosshairRendererPlugin } from '../renderers/crosshair'
29
+ import { createTimeAxisRendererPlugin } from '../renderers/timeAxis'
30
+
31
+ type ResolvedChartOptions = Omit<ChartOptions, 'kWidth' | 'kGap'> & {
32
+ kWidth: number
33
+ kGap: number
34
+ }
35
+
36
+ export type FrameContext = {
37
+ vp: Viewport
38
+ range: VisibleRange
39
+ kLinePositions: KLinePositions
40
+ kLineCenters: number[]
41
+ kBarRects: Array<{ x: number; width: number }>
42
+ kWidthPx: number
43
+ useCachedFrame: boolean
44
+ data: KLineData[]
45
+ mainIndicatorRange: { min: number; max: number } | null
46
+ hasCrosshair: boolean
47
+ zoomLevel: number
48
+ zoomLevelCount: number
49
+ }
50
+
51
+ export interface RendererDependencies {
52
+ getDom: () => ChartDom
53
+ getOption: () => ResolvedChartOptions
54
+ getPaneRenderers: () => PaneRenderer[]
55
+ getInteraction: () => InteractionController
56
+ getSharedWebGLSurface: () => SharedWebGLSurface
57
+ getPluginHost: () => PluginHostImpl
58
+ getRendererPluginManager: () => RendererPluginManager
59
+ getTheme: () => 'light' | 'dark'
60
+ getCurrentZoomLevel: () => number
61
+ getZoomLevelCount: () => number
62
+ getViewportManager: () => ChartViewportManager
63
+ getDataManager: () => ChartDataManager
64
+ getIndicatorManager: () => ChartIndicatorManager
65
+ }
66
+
67
+ export class ChartRenderer {
68
+ private deps: RendererDependencies
69
+
70
+ private raf: number | null = null
71
+ private pendingUpdateLevel: UpdateLevel = UpdateLevel.All
72
+
73
+ readonly markerManager: MarkerManager
74
+ readonly drawingStore: DrawingStore
75
+ private settings: ChartSettings = {}
76
+ private overlayHadCrosshair = false
77
+ private xAxisCtx: CanvasRenderingContext2D | null = null
78
+
79
+ private cachedDrawFrame: {
80
+ viewport: Viewport
81
+ range: VisibleRange
82
+ kLinePositions: KLinePositions
83
+ kLineCenters: number[]
84
+ kBarRects: Array<{ x: number; width: number }>
85
+ kWidthPx: number
86
+ } | null = null
87
+
88
+ constructor(deps: RendererDependencies) {
89
+ this.deps = deps
90
+ this.markerManager = new MarkerManager()
91
+ this.drawingStore = new DrawingStore()
92
+ }
93
+
94
+ initCoreRenderers(): void {
95
+ const opt = this.deps.getOption()
96
+ const axisWidth = opt.rightAxisWidth + (opt.priceLabelWidth ?? 0)
97
+ const interaction = this.deps.getInteraction()
98
+
99
+ this.useDrawingPlugin(createGridLinesRendererPlugin())
100
+ this.useDrawingPlugin(createCandleRenderer())
101
+ this.useDrawingPlugin(createComparisonLineRenderer())
102
+ this.useDrawingPlugin(createLastPriceLineRendererPlugin())
103
+ this.useDrawingPlugin(createLastPriceLabelRegistrarPlugin())
104
+ this.useDrawingPlugin(createCustomMarkersRenderer())
105
+ this.useDrawingPlugin(createExtremaMarkersRendererPlugin())
106
+ this.useDrawingPlugin(createMainIndicatorLegendRendererPlugin({
107
+ yPaddingPx: opt.yPaddingPx,
108
+ }))
109
+ this.useDrawingPlugin(createYAxisRendererPlugin({
110
+ axisWidth,
111
+ yPaddingPx: opt.yPaddingPx,
112
+ getCrosshair: () => {
113
+ const pos = interaction.crosshairPos
114
+ const price = interaction.crosshairPrice
115
+ const activePaneId = interaction.activePaneId
116
+ if (pos && price !== null) {
117
+ return { y: pos.y, price, activePaneId }
118
+ }
119
+ return null
120
+ },
121
+ }))
122
+ this.useDrawingPlugin(createCrosshairRendererPlugin({
123
+ getCrosshairState: () => ({
124
+ pos: interaction.crosshairPos,
125
+ activePaneId: interaction.activePaneId,
126
+ isDragging: interaction.isDraggingState(),
127
+ price: interaction.crosshairPrice,
128
+ }),
129
+ }))
130
+ this.useDrawingPlugin(createTimeAxisRendererPlugin({
131
+ height: opt.bottomAxisHeight,
132
+ getCrosshair: () => {
133
+ const pos = interaction.crosshairPos
134
+ const idx = interaction.crosshairIndex
135
+ if (pos && idx !== null) {
136
+ return { x: pos.x, index: idx }
137
+ }
138
+ return null
139
+ },
140
+ }))
141
+ }
142
+
143
+ registerDrawingPlugins(): void {
144
+ this.useDrawingPlugin(createDrawingRendererPlugin({ store: this.drawingStore }))
145
+ this.useDrawingPlugin(createDrawingLabelOverlayPlugin({ store: this.drawingStore }))
146
+ }
147
+
148
+ private useDrawingPlugin(plugin: RendererPlugin | RendererPluginWithHost, config?: Record<string, unknown>): void {
149
+ this.deps.getRendererPluginManager().register(plugin)
150
+ if (config && plugin.setConfig) {
151
+ plugin.setConfig(config)
152
+ }
153
+ }
154
+
155
+ getMarkerManager(): MarkerManager {
156
+ return this.markerManager
157
+ }
158
+
159
+ getDrawingStore(): DrawingStore {
160
+ return this.drawingStore
161
+ }
162
+
163
+ getSettings(): ChartSettings {
164
+ return this.settings
165
+ }
166
+
167
+ updateSettings(settings: ChartSettings): void {
168
+ this.settings = { ...settings }
169
+ }
170
+
171
+ scheduleDraw(level: UpdateLevel = UpdateLevel.All): void {
172
+ if (this.raf !== null) {
173
+ if (this.pendingUpdateLevel === UpdateLevel.All) return
174
+ if (level === UpdateLevel.All) {
175
+ this.pendingUpdateLevel = UpdateLevel.All
176
+ return
177
+ }
178
+ if (
179
+ (this.pendingUpdateLevel === UpdateLevel.Main && level === UpdateLevel.Overlay) ||
180
+ (this.pendingUpdateLevel === UpdateLevel.Overlay && level === UpdateLevel.Main)
181
+ ) {
182
+ this.pendingUpdateLevel = UpdateLevel.All
183
+ return
184
+ }
185
+ return
186
+ }
187
+
188
+ this.pendingUpdateLevel = level
189
+ this.raf = requestAnimationFrame(() => {
190
+ this.raf = null
191
+ const levelToDraw = this.pendingUpdateLevel
192
+ this.pendingUpdateLevel = UpdateLevel.All
193
+ this.draw(levelToDraw)
194
+ const c = this.deps.getDom().container
195
+ if (c) {
196
+ this.deps.getViewportManager().applyPendingScrollLeft(c)
197
+ }
198
+ })
199
+ }
200
+
201
+ draw(level: UpdateLevel = UpdateLevel.All): void {
202
+ this.markerManager.clear()
203
+
204
+ const frame = this.prepareFrameData(level)
205
+ if (!frame) {
206
+ if (this.deps.getDataManager().getInternalData().length === 0) this.clearAllCanvases()
207
+ return
208
+ }
209
+
210
+ const { vp, range, kLinePositions, kLineCenters, kBarRects, kWidthPx, useCachedFrame } = frame
211
+
212
+ this.deps.getInteraction().setKLinePositions(kLinePositions, range, kWidthPx)
213
+
214
+ const indicatorManager = this.deps.getIndicatorManager()
215
+ indicatorManager.indicatorSchedulerAccessor.setActiveMainIndicators(
216
+ [...indicatorManager.mainIndicatorsSignalPeek.entries()].map(([id, entry]) => ({ id, params: entry.params })),
217
+ )
218
+ const mainIndicatorRange = useCachedFrame ? null : indicatorManager.indicatorSchedulerAccessor.getMainIndicatorPriceRange()
219
+ const hasCrosshair = this.deps.getInteraction().getCrosshairIndex() !== null
220
+
221
+ const { sharedXAxisLabels, sharedXAxisRanges } = this.renderPanes(
222
+ vp, range, kLinePositions, kLineCenters, kBarRects, kWidthPx,
223
+ mainIndicatorRange, hasCrosshair, useCachedFrame, level,
224
+ )
225
+
226
+ this.overlayHadCrosshair = hasCrosshair
227
+ this.renderXAxis(vp, range, kLinePositions, kLineCenters, kBarRects, kWidthPx, sharedXAxisLabels, sharedXAxisRanges)
228
+ }
229
+
230
+ private prepareFrameData(level: UpdateLevel): FrameContext | null {
231
+ const useCachedFrame = level === UpdateLevel.Overlay && this.cachedDrawFrame !== null
232
+
233
+ const vp = useCachedFrame ? this.cachedDrawFrame!.viewport : this.deps.getViewportManager().computeViewport()
234
+ if (!vp) return null
235
+
236
+ const internalData = this.deps.getDataManager().getInternalData()
237
+ if (internalData.length === 0) return null
238
+
239
+ const opt = this.deps.getOption()
240
+ const rawRange = useCachedFrame
241
+ ? this.cachedDrawFrame!.range
242
+ : (() => {
243
+ const { start, end } = getVisibleRange(
244
+ vp.scrollLeft,
245
+ vp.plotWidth,
246
+ opt.kWidth,
247
+ opt.kGap,
248
+ internalData.length,
249
+ vp.dpr,
250
+ )
251
+ return { start, end }
252
+ })()
253
+ const range = { start: Math.max(0, rawRange.start), end: rawRange.end }
254
+
255
+ const dataManager = this.deps.getDataManager()
256
+ if (!useCachedFrame && (
257
+ range.start !== dataManager.lastVisibleRange.start
258
+ || range.end !== dataManager.lastVisibleRange.end
259
+ || rawRange.start !== dataManager.lastRawVisibleRange.start
260
+ || rawRange.end !== dataManager.lastRawVisibleRange.end
261
+ )) {
262
+ this.deps.getIndicatorManager().indicatorSchedulerAccessor.updateVisibleRange(range)
263
+ dataManager.lastVisibleRange = range
264
+ dataManager.lastRawVisibleRange = rawRange
265
+ this.checkVisibleRangeGapWhenIdle()
266
+ }
267
+
268
+ const kLinePositions = useCachedFrame
269
+ ? this.cachedDrawFrame!.kLinePositions
270
+ : this.calcKLinePositions(range)
271
+
272
+ let kLineCenters: number[]
273
+ let kBarRects: Array<{ x: number; width: number }>
274
+ let kWidthPx: number
275
+
276
+ if (useCachedFrame) {
277
+ kLineCenters = this.cachedDrawFrame!.kLineCenters
278
+ kBarRects = this.cachedDrawFrame!.kBarRects
279
+ kWidthPx = this.cachedDrawFrame!.kWidthPx
280
+ } else {
281
+ const physConfig = getPhysicalKLineConfig(opt.kWidth, opt.kGap, vp.dpr)
282
+ let barWidthPx = Math.max(1, physConfig.unitPx - 1)
283
+ if (barWidthPx % 2 === 0) barWidthPx -= 1
284
+
285
+ kLineCenters = new Array(kLinePositions.length)
286
+ kBarRects = new Array(kLinePositions.length)
287
+
288
+ for (let i = 0; i < kLinePositions.length; i++) {
289
+ const x = kLinePositions[i]!
290
+ const leftPx = Math.round(x * vp.dpr)
291
+ const wickXPx = leftPx + (physConfig.kWidthPx - 1) / 2
292
+ kLineCenters[i] = wickXPx / vp.dpr
293
+
294
+ const barLeftPx = wickXPx - (barWidthPx - 1) / 2
295
+ kBarRects[i] = { x: barLeftPx / vp.dpr, width: barWidthPx / vp.dpr }
296
+ }
297
+
298
+ kWidthPx = getPhysicalKLineConfig(opt.kWidth, opt.kGap, vp.dpr).kWidthPx
299
+ this.cachedDrawFrame = {
300
+ viewport: { ...vp },
301
+ range: { ...range },
302
+ kLinePositions,
303
+ kLineCenters,
304
+ kBarRects,
305
+ kWidthPx,
306
+ }
307
+ }
308
+
309
+ return {
310
+ vp, range, kLinePositions, kLineCenters, kBarRects, kWidthPx, useCachedFrame,
311
+ data: internalData,
312
+ mainIndicatorRange: null,
313
+ hasCrosshair: false,
314
+ zoomLevel: this.deps.getCurrentZoomLevel(),
315
+ zoomLevelCount: this.deps.getZoomLevelCount(),
316
+ }
317
+ }
318
+
319
+ clearAllCanvases(): void {
320
+ const vp = this.deps.getViewportManager().computeViewport()
321
+ if (!vp) return
322
+ for (const r of this.deps.getPaneRenderers()) {
323
+ const { mainCtx, overlayCtx, yAxisCtx } = r.getContexts()
324
+ const pane = r.getPane()
325
+ mainCtx?.clearRect(0, 0, vp.plotWidth + 1, pane.height + 2 / vp.dpr)
326
+ overlayCtx?.clearRect(0, 0, vp.plotWidth + 1, pane.height + 2 / vp.dpr)
327
+ yAxisCtx?.clearRect(0, 0, vp.plotWidth + 1, pane.height + 2 / vp.dpr)
328
+ }
329
+ const xCtx = this.xAxisCtx
330
+ if (xCtx) {
331
+ const xW = xCtx.canvas.width
332
+ const xH = xCtx.canvas.height
333
+ xCtx.clearRect(0, 0, xW, xH)
334
+ }
335
+ }
336
+
337
+ private renderPanes(
338
+ vp: Viewport,
339
+ range: VisibleRange,
340
+ kLinePositions: KLinePositions,
341
+ kLineCenters: number[],
342
+ kBarRects: Array<{ x: number; width: number }>,
343
+ kWidthPx: number,
344
+ mainIndicatorRange: { min: number; max: number } | null,
345
+ hasCrosshair: boolean,
346
+ useCachedFrame: boolean,
347
+ level: UpdateLevel,
348
+ ): { sharedXAxisLabels: XAxisLabel[]; sharedXAxisRanges: XAxisRange[] } {
349
+ const sharedYAxisLabels: YAxisLabel[] = []
350
+ const sharedXAxisLabels: XAxisLabel[] = []
351
+ const sharedYAxisRanges: YAxisRange[] = []
352
+ const sharedXAxisRanges: XAxisRange[] = []
353
+
354
+ const dataManager = this.deps.getDataManager()
355
+ const rendererPluginManager = this.deps.getRendererPluginManager()
356
+ const pluginHost = this.deps.getPluginHost()
357
+
358
+ for (const renderer of this.deps.getPaneRenderers()) {
359
+ const pane = renderer.getPane()
360
+ const { mainCtx, overlayCtx, yAxisCtx } = renderer.getContexts()
361
+ const { candleSurface, lineSurface } = renderer.getWebGL()
362
+
363
+ if (!useCachedFrame) {
364
+ const indicatorRange = pane.role === 'price' ? mainIndicatorRange : null
365
+ const comparisonRange = pane.id === 'main' ? dataManager.getComparisonEquivalentPriceRange(range) : null
366
+ const mergedRange = this.mergeNumericRanges(indicatorRange, comparisonRange)
367
+ pane.updateRange(dataManager.getInternalData(), range, mergedRange)
368
+ if (pane.id === 'main' && this.settings.disableMainPaneVerticalScroll) {
369
+ pane.yAxis.resetTransform()
370
+ }
371
+ }
372
+
373
+ const shouldUpdateMain = level === UpdateLevel.Main || level === UpdateLevel.All
374
+ const shouldUpdateOverlay = level === UpdateLevel.All || (level === UpdateLevel.Overlay && (hasCrosshair || this.overlayHadCrosshair))
375
+
376
+ if (shouldUpdateMain && mainCtx) {
377
+ mainCtx.setTransform(1, 0, 0, 1, 0, 0)
378
+ mainCtx.scale(vp.dpr, vp.dpr)
379
+ mainCtx.clearRect(0, 0, vp.plotWidth + 1, pane.height + 2 / vp.dpr)
380
+ candleSurface?.clear()
381
+ lineSurface?.clear()
382
+ }
383
+
384
+ if (shouldUpdateOverlay && overlayCtx) {
385
+ const overlayWidth = overlayCtx.canvas.width / vp.dpr
386
+ overlayCtx.setTransform(1, 0, 0, 1, 0, 0)
387
+ overlayCtx.scale(vp.dpr, vp.dpr)
388
+ overlayCtx.clearRect(0, 0, overlayWidth + 1, pane.height + 2 / vp.dpr)
389
+ }
390
+
391
+ if (yAxisCtx && !useCachedFrame) {
392
+ const yAxisWidth = yAxisCtx.canvas.width / vp.dpr
393
+ yAxisCtx.setTransform(1, 0, 0, 1, 0, 0)
394
+ yAxisCtx.scale(vp.dpr, vp.dpr)
395
+ yAxisCtx.clearRect(0, 0, yAxisWidth, pane.height + 2 / vp.dpr)
396
+ }
397
+
398
+ const opt = this.deps.getOption()
399
+ const context: RenderContext = {
400
+ ctx: mainCtx!,
401
+ overlayCtx: overlayCtx ?? undefined,
402
+ pane: wrapPaneInfo(pane),
403
+ data: dataManager.getInternalData(),
404
+ period: dataManager.currentPeriod,
405
+ comparisonData: dataManager.getComparisonData(),
406
+ comparisonSymbols: dataManager.getComparisonSpecs(),
407
+ comparisonColors: dataManager.getComparisonColors(),
408
+ range,
409
+ scrollLeft: vp.scrollLeft,
410
+ kWidth: opt.kWidth,
411
+ kGap: opt.kGap,
412
+ dpr: vp.dpr,
413
+ paneWidth: vp.plotWidth,
414
+ kLinePositions,
415
+ kLineCenters,
416
+ kBarRects,
417
+ markerManager: this.markerManager,
418
+ crosshairIndex: this.deps.getInteraction().getCrosshairIndex(),
419
+ yAxisCtx: yAxisCtx ?? undefined,
420
+ candleWebGLSurface: candleSurface ?? undefined,
421
+ lineWebGLSurface: lineSurface ?? undefined,
422
+ zoomLevel: this.deps.getCurrentZoomLevel(),
423
+ zoomLevelCount: this.deps.getZoomLevelCount(),
424
+ viewport: {
425
+ scrollLeft: vp.scrollLeft,
426
+ plotWidth: vp.plotWidth,
427
+ plotHeight: vp.plotHeight,
428
+ },
429
+ settings: this.settings,
430
+ yAxisLabels: sharedYAxisLabels,
431
+ xAxisLabels: sharedXAxisLabels,
432
+ yAxisRanges: sharedYAxisRanges,
433
+ xAxisRanges: sharedXAxisRanges,
434
+ theme: this.deps.getTheme(),
435
+ isAsiaMarket: this.settings.isAsiaMarket as boolean,
436
+ colorPresetSettings: this.settings.colorPresetSettings,
437
+ }
438
+
439
+ if (shouldUpdateMain || shouldUpdateOverlay) {
440
+ const errors = rendererPluginManager.render(pane.id, context, level)
441
+ if (errors.length > 0) {
442
+ pluginHost.events.emit('renderer:error', { paneId: pane.id, errors })
443
+ }
444
+
445
+ const yAxisErrors = rendererPluginManager.renderPlugin('yAxis', context)
446
+ if (yAxisErrors.length > 0) {
447
+ pluginHost.events.emit('renderer:error', { paneId: pane.id, errors: yAxisErrors })
448
+ }
449
+ }
450
+ }
451
+
452
+ return { sharedXAxisLabels, sharedXAxisRanges }
453
+ }
454
+
455
+ private renderXAxis(
456
+ vp: Viewport,
457
+ range: VisibleRange,
458
+ kLinePositions: KLinePositions,
459
+ kLineCenters: number[],
460
+ kBarRects: Array<{ x: number; width: number }>,
461
+ kWidthPx: number,
462
+ sharedXAxisLabels: XAxisLabel[],
463
+ sharedXAxisRanges: XAxisRange[],
464
+ ): void {
465
+ const dom = this.deps.getDom()
466
+ const xAxisCtx = this.xAxisCtx ?? dom.xAxisCanvas.getContext('2d')
467
+ if (!this.xAxisCtx) {
468
+ this.xAxisCtx = xAxisCtx
469
+ }
470
+ if (xAxisCtx) {
471
+ const opt = this.deps.getOption()
472
+ const timeAxisContext: RenderContext = {
473
+ ctx: xAxisCtx,
474
+ pane: {
475
+ id: 'xAxis',
476
+ role: 'auxiliary',
477
+ capabilities: {
478
+ showPriceAxisTicks: false,
479
+ showCrosshairPriceLabel: false,
480
+ candleHitTest: false,
481
+ supportsPriceTranslate: false,
482
+ },
483
+ top: 0,
484
+ height: opt.bottomAxisHeight,
485
+ yAxis: {
486
+ priceToY: () => 0,
487
+ yToPrice: () => 0,
488
+ getPaddingTop: () => 0,
489
+ getPaddingBottom: () => 0,
490
+ getPriceOffset: () => 0,
491
+ getDisplayRange: (baseRange) => baseRange ?? { maxPrice: 0, minPrice: 0 },
492
+ getScaleType: () => 'linear' as const,
493
+ getBasePrice: () => null,
494
+ toPercent: () => 0,
495
+ fromPercent: () => 0,
496
+ getDisplayPercentRange: () => ({ minPct: 0, maxPct: 0 }),
497
+ },
498
+ priceRange: { maxPrice: 0, minPrice: 0 },
499
+ },
500
+ period: this.deps.getDataManager().currentPeriod,
501
+ data: this.deps.getDataManager().getInternalData(),
502
+ range,
503
+ scrollLeft: vp.scrollLeft,
504
+ kWidth: opt.kWidth,
505
+ kGap: opt.kGap,
506
+ dpr: vp.dpr,
507
+ paneWidth: vp.plotWidth,
508
+ kLinePositions,
509
+ kLineCenters,
510
+ kBarRects,
511
+ xAxisCtx,
512
+ viewport: {
513
+ scrollLeft: vp.scrollLeft,
514
+ plotWidth: vp.plotWidth,
515
+ plotHeight: vp.plotHeight,
516
+ },
517
+ yAxisLabels: [],
518
+ xAxisLabels: sharedXAxisLabels,
519
+ xAxisRanges: sharedXAxisRanges,
520
+ theme: this.deps.getTheme(),
521
+ isAsiaMarket: this.settings.isAsiaMarket as boolean,
522
+ colorPresetSettings: this.settings.colorPresetSettings,
523
+ }
524
+ const errors = this.deps.getRendererPluginManager().renderPlugin('timeAxis', timeAxisContext)
525
+ if (errors.length > 0) {
526
+ this.deps.getPluginHost().events.emit('renderer:error', { paneId: 'timeAxis', errors })
527
+ }
528
+ }
529
+ }
530
+
531
+ private calcKLinePositions(range: VisibleRange): KLinePositions {
532
+ const { start, end } = range
533
+ const count = end - start
534
+
535
+ if (count <= 0) return []
536
+
537
+ const dpr = this.deps.getViewportManager().getEffectiveDpr()
538
+ const opt = this.deps.getOption()
539
+ const { unitPx, startXPx } = getPhysicalKLineConfig(opt.kWidth, opt.kGap, dpr)
540
+
541
+ const positions: number[] = new Array(count)
542
+
543
+ for (let i = 0; i < count; i++) {
544
+ const dataIndex = start + i
545
+ const leftPx = startXPx + dataIndex * unitPx
546
+ positions[i] = leftPx / dpr
547
+ }
548
+
549
+ return positions
550
+ }
551
+
552
+ private checkVisibleRangeGapWhenIdle(): void {
553
+ if (this.deps.getInteraction().isPointerDown()) return
554
+ this.deps.getDataManager().checkVisibleRangeGap()
555
+ }
556
+
557
+ private mergeNumericRanges(
558
+ left: { min: number; max: number } | null | undefined,
559
+ right: { min: number; max: number } | null | undefined,
560
+ ): { min: number; max: number } | null {
561
+ if (!left) return right ?? null
562
+ if (!right) return left
563
+ return {
564
+ min: Math.min(left.min, right.min),
565
+ max: Math.max(left.max, right.max),
566
+ }
567
+ }
568
+
569
+ clearCachedFrame(): void {
570
+ this.cachedDrawFrame = null
571
+ }
572
+
573
+ destroy(): void {
574
+ if (this.raf !== null) {
575
+ cancelAnimationFrame(this.raf)
576
+ this.raf = null
577
+ }
578
+ this.cachedDrawFrame = null
579
+ this.xAxisCtx = null
580
+ }
581
+ }