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

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 (110) 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 +82 -48
  11. package/dist/data-fetchers/dataBuffer.js.map +1 -1
  12. package/dist/data-fetchers/tradingview.d.ts.map +1 -1
  13. package/dist/data-fetchers/tradingview.js +4 -5
  14. package/dist/data-fetchers/tradingview.js.map +1 -1
  15. package/dist/engine/chart.d.ts +29 -367
  16. package/dist/engine/chart.d.ts.map +1 -1
  17. package/dist/engine/chart.js +239 -1842
  18. package/dist/engine/chart.js.map +1 -1
  19. package/dist/engine/chartContext.d.ts +24 -0
  20. package/dist/engine/chartContext.d.ts.map +1 -0
  21. package/dist/engine/chartContext.js +19 -0
  22. package/dist/engine/chartContext.js.map +1 -0
  23. package/dist/engine/chartTypes.d.ts +77 -0
  24. package/dist/engine/chartTypes.d.ts.map +1 -0
  25. package/dist/engine/chartTypes.js +2 -0
  26. package/dist/engine/chartTypes.js.map +1 -0
  27. package/dist/engine/data/chartDataManager.d.ts +102 -0
  28. package/dist/engine/data/chartDataManager.d.ts.map +1 -0
  29. package/dist/engine/data/chartDataManager.js +590 -0
  30. package/dist/engine/data/chartDataManager.js.map +1 -0
  31. package/dist/engine/indicators/chartIndicatorManager.d.ts +102 -0
  32. package/dist/engine/indicators/chartIndicatorManager.d.ts.map +1 -0
  33. package/dist/engine/indicators/chartIndicatorManager.js +437 -0
  34. package/dist/engine/indicators/chartIndicatorManager.js.map +1 -0
  35. package/dist/engine/layout/chartPaneLayout.d.ts +53 -0
  36. package/dist/engine/layout/chartPaneLayout.d.ts.map +1 -0
  37. package/dist/engine/layout/chartPaneLayout.js +388 -0
  38. package/dist/engine/layout/chartPaneLayout.js.map +1 -0
  39. package/dist/engine/render/chartRenderer.d.ts +86 -0
  40. package/dist/engine/render/chartRenderer.d.ts.map +1 -0
  41. package/dist/engine/render/chartRenderer.js +438 -0
  42. package/dist/engine/render/chartRenderer.js.map +1 -0
  43. package/dist/engine/renderers/Indicator/mainIndicatorLegend.d.ts.map +1 -1
  44. package/dist/engine/renderers/Indicator/mainIndicatorLegend.js +73 -7
  45. package/dist/engine/renderers/Indicator/mainIndicatorLegend.js.map +1 -1
  46. package/dist/engine/renderers/comparisonLine.d.ts.map +1 -1
  47. package/dist/engine/renderers/comparisonLine.js +25 -11
  48. package/dist/engine/renderers/comparisonLine.js.map +1 -1
  49. package/dist/engine/subPaneManager.d.ts +27 -6
  50. package/dist/engine/subPaneManager.d.ts.map +1 -1
  51. package/dist/engine/subPaneManager.js +54 -56
  52. package/dist/engine/subPaneManager.js.map +1 -1
  53. package/dist/engine/utils/chartZoomController.d.ts +33 -0
  54. package/dist/engine/utils/chartZoomController.d.ts.map +1 -0
  55. package/dist/engine/utils/chartZoomController.js +66 -0
  56. package/dist/engine/utils/chartZoomController.js.map +1 -0
  57. package/dist/engine/viewport/chartViewportManager.d.ts +72 -0
  58. package/dist/engine/viewport/chartViewportManager.d.ts.map +1 -0
  59. package/dist/engine/viewport/chartViewportManager.js +249 -0
  60. package/dist/engine/viewport/chartViewportManager.js.map +1 -0
  61. package/dist/plugin/types.d.ts +1 -0
  62. package/dist/plugin/types.d.ts.map +1 -1
  63. package/dist/plugin/types.js.map +1 -1
  64. package/dist/tokens/theme-china.d.ts.map +1 -1
  65. package/dist/tokens/theme-china.js +0 -4
  66. package/dist/tokens/theme-china.js.map +1 -1
  67. package/dist/tokens/theme-dark.d.ts.map +1 -1
  68. package/dist/tokens/theme-dark.js +0 -4
  69. package/dist/tokens/theme-dark.js.map +1 -1
  70. package/dist/tokens/theme-light.d.ts.map +1 -1
  71. package/dist/tokens/theme-light.js +1 -5
  72. package/dist/tokens/theme-light.js.map +1 -1
  73. package/dist/tokens/types.d.ts +0 -4
  74. package/dist/tokens/types.d.ts.map +1 -1
  75. package/dist/types/price.d.ts +2 -0
  76. package/dist/types/price.d.ts.map +1 -1
  77. package/dist/types/price.js.map +1 -1
  78. package/dist/version.d.ts +1 -1
  79. package/dist/version.d.ts.map +1 -1
  80. package/dist/version.js +1 -1
  81. package/dist/version.js.map +1 -1
  82. package/package.json +1 -1
  83. package/src/controllers/createChartController.ts +39 -10
  84. package/src/controllers/types.ts +6 -1
  85. package/src/data-fetchers/baostock.ts +3 -3
  86. package/src/data-fetchers/dataBuffer.ts +64 -23
  87. package/src/data-fetchers/tradingview.ts +4 -5
  88. package/src/engine/__tests__/subPaneManager.test.ts +154 -0
  89. package/src/engine/chart.ts +252 -2250
  90. package/src/engine/chartContext.ts +34 -0
  91. package/src/engine/chartTypes.ts +88 -0
  92. package/src/engine/data/chartDataManager.ts +691 -0
  93. package/src/engine/indicators/__tests__/chartIndicatorManager.test.ts +103 -0
  94. package/src/engine/indicators/chartIndicatorManager.ts +566 -0
  95. package/src/engine/layout/chartPaneLayout.ts +474 -0
  96. package/src/engine/render/chartRenderer.ts +579 -0
  97. package/src/engine/renderers/Indicator/mainIndicatorLegend.ts +99 -13
  98. package/src/engine/renderers/comparisonLine.ts +25 -11
  99. package/src/engine/subPaneManager.ts +75 -59
  100. package/src/engine/utils/chartZoomController.ts +104 -0
  101. package/src/engine/viewport/chartViewportManager.ts +310 -0
  102. package/src/plugin/types.ts +1 -0
  103. package/src/tokens/__tests__/__snapshots__/baseline.test.ts.snap +1 -9
  104. package/src/tokens/theme-china.ts +0 -4
  105. package/src/tokens/theme-dark.ts +0 -4
  106. package/src/tokens/theme-light.ts +2 -6
  107. package/src/tokens/types.ts +0 -4
  108. package/src/types/price.ts +2 -0
  109. package/src/version.ts +1 -1
  110. package/src/engine/chart.d.ts +0 -626
@@ -0,0 +1,579 @@
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
+ comparisonData: dataManager.getComparisonData(),
405
+ comparisonSymbols: dataManager.getComparisonSpecs(),
406
+ comparisonColors: dataManager.getComparisonColors(),
407
+ range,
408
+ scrollLeft: vp.scrollLeft,
409
+ kWidth: opt.kWidth,
410
+ kGap: opt.kGap,
411
+ dpr: vp.dpr,
412
+ paneWidth: vp.plotWidth,
413
+ kLinePositions,
414
+ kLineCenters,
415
+ kBarRects,
416
+ markerManager: this.markerManager,
417
+ crosshairIndex: this.deps.getInteraction().getCrosshairIndex(),
418
+ yAxisCtx: yAxisCtx ?? undefined,
419
+ candleWebGLSurface: candleSurface ?? undefined,
420
+ lineWebGLSurface: lineSurface ?? undefined,
421
+ zoomLevel: this.deps.getCurrentZoomLevel(),
422
+ zoomLevelCount: this.deps.getZoomLevelCount(),
423
+ viewport: {
424
+ scrollLeft: vp.scrollLeft,
425
+ plotWidth: vp.plotWidth,
426
+ plotHeight: vp.plotHeight,
427
+ },
428
+ settings: this.settings,
429
+ yAxisLabels: sharedYAxisLabels,
430
+ xAxisLabels: sharedXAxisLabels,
431
+ yAxisRanges: sharedYAxisRanges,
432
+ xAxisRanges: sharedXAxisRanges,
433
+ theme: this.deps.getTheme(),
434
+ isAsiaMarket: this.settings.isAsiaMarket as boolean,
435
+ colorPresetSettings: this.settings.colorPresetSettings,
436
+ }
437
+
438
+ if (shouldUpdateMain || shouldUpdateOverlay) {
439
+ const errors = rendererPluginManager.render(pane.id, context, level)
440
+ if (errors.length > 0) {
441
+ pluginHost.events.emit('renderer:error', { paneId: pane.id, errors })
442
+ }
443
+
444
+ const yAxisErrors = rendererPluginManager.renderPlugin('yAxis', context)
445
+ if (yAxisErrors.length > 0) {
446
+ pluginHost.events.emit('renderer:error', { paneId: pane.id, errors: yAxisErrors })
447
+ }
448
+ }
449
+ }
450
+
451
+ return { sharedXAxisLabels, sharedXAxisRanges }
452
+ }
453
+
454
+ private renderXAxis(
455
+ vp: Viewport,
456
+ range: VisibleRange,
457
+ kLinePositions: KLinePositions,
458
+ kLineCenters: number[],
459
+ kBarRects: Array<{ x: number; width: number }>,
460
+ kWidthPx: number,
461
+ sharedXAxisLabels: XAxisLabel[],
462
+ sharedXAxisRanges: XAxisRange[],
463
+ ): void {
464
+ const dom = this.deps.getDom()
465
+ const xAxisCtx = this.xAxisCtx ?? dom.xAxisCanvas.getContext('2d')
466
+ if (!this.xAxisCtx) {
467
+ this.xAxisCtx = xAxisCtx
468
+ }
469
+ if (xAxisCtx) {
470
+ const opt = this.deps.getOption()
471
+ const timeAxisContext: RenderContext = {
472
+ ctx: xAxisCtx,
473
+ pane: {
474
+ id: 'xAxis',
475
+ role: 'auxiliary',
476
+ capabilities: {
477
+ showPriceAxisTicks: false,
478
+ showCrosshairPriceLabel: false,
479
+ candleHitTest: false,
480
+ supportsPriceTranslate: false,
481
+ },
482
+ top: 0,
483
+ height: opt.bottomAxisHeight,
484
+ yAxis: {
485
+ priceToY: () => 0,
486
+ yToPrice: () => 0,
487
+ getPaddingTop: () => 0,
488
+ getPaddingBottom: () => 0,
489
+ getPriceOffset: () => 0,
490
+ getDisplayRange: (baseRange) => baseRange ?? { maxPrice: 0, minPrice: 0 },
491
+ getScaleType: () => 'linear' as const,
492
+ getBasePrice: () => null,
493
+ toPercent: () => 0,
494
+ fromPercent: () => 0,
495
+ getDisplayPercentRange: () => ({ minPct: 0, maxPct: 0 }),
496
+ },
497
+ priceRange: { maxPrice: 0, minPrice: 0 },
498
+ },
499
+ data: this.deps.getDataManager().getInternalData(),
500
+ range,
501
+ scrollLeft: vp.scrollLeft,
502
+ kWidth: opt.kWidth,
503
+ kGap: opt.kGap,
504
+ dpr: vp.dpr,
505
+ paneWidth: vp.plotWidth,
506
+ kLinePositions,
507
+ kLineCenters,
508
+ kBarRects,
509
+ xAxisCtx,
510
+ viewport: {
511
+ scrollLeft: vp.scrollLeft,
512
+ plotWidth: vp.plotWidth,
513
+ plotHeight: vp.plotHeight,
514
+ },
515
+ yAxisLabels: [],
516
+ xAxisLabels: sharedXAxisLabels,
517
+ xAxisRanges: sharedXAxisRanges,
518
+ theme: this.deps.getTheme(),
519
+ isAsiaMarket: this.settings.isAsiaMarket as boolean,
520
+ colorPresetSettings: this.settings.colorPresetSettings,
521
+ }
522
+ const errors = this.deps.getRendererPluginManager().renderPlugin('timeAxis', timeAxisContext)
523
+ if (errors.length > 0) {
524
+ this.deps.getPluginHost().events.emit('renderer:error', { paneId: 'timeAxis', errors })
525
+ }
526
+ }
527
+ }
528
+
529
+ private calcKLinePositions(range: VisibleRange): KLinePositions {
530
+ const { start, end } = range
531
+ const count = end - start
532
+
533
+ if (count <= 0) return []
534
+
535
+ const dpr = this.deps.getViewportManager().getEffectiveDpr()
536
+ const opt = this.deps.getOption()
537
+ const { unitPx, startXPx } = getPhysicalKLineConfig(opt.kWidth, opt.kGap, dpr)
538
+
539
+ const positions: number[] = new Array(count)
540
+
541
+ for (let i = 0; i < count; i++) {
542
+ const dataIndex = start + i
543
+ const leftPx = startXPx + dataIndex * unitPx
544
+ positions[i] = leftPx / dpr
545
+ }
546
+
547
+ return positions
548
+ }
549
+
550
+ private checkVisibleRangeGapWhenIdle(): void {
551
+ if (this.deps.getInteraction().isPointerDown()) return
552
+ this.deps.getDataManager().checkVisibleRangeGap()
553
+ }
554
+
555
+ private mergeNumericRanges(
556
+ left: { min: number; max: number } | null | undefined,
557
+ right: { min: number; max: number } | null | undefined,
558
+ ): { min: number; max: number } | null {
559
+ if (!left) return right ?? null
560
+ if (!right) return left
561
+ return {
562
+ min: Math.min(left.min, right.min),
563
+ max: Math.max(left.max, right.max),
564
+ }
565
+ }
566
+
567
+ clearCachedFrame(): void {
568
+ this.cachedDrawFrame = null
569
+ }
570
+
571
+ destroy(): void {
572
+ if (this.raf !== null) {
573
+ cancelAnimationFrame(this.raf)
574
+ this.raf = null
575
+ }
576
+ this.cachedDrawFrame = null
577
+ this.xAxisCtx = null
578
+ }
579
+ }