@363045841yyt/klinechart-core 0.7.5 → 0.7.6
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.
- package/README.md +8 -8
- package/README.zh-CN.md +8 -8
- package/dist/controllers/createChartController.d.ts.map +1 -1
- package/dist/controllers/createChartController.js +145 -21
- package/dist/controllers/createChartController.js.map +1 -1
- package/dist/controllers/index.d.ts +9 -1
- package/dist/controllers/index.d.ts.map +1 -1
- package/dist/controllers/index.js +9 -0
- package/dist/controllers/index.js.map +1 -1
- package/dist/controllers/types.d.ts +65 -8
- package/dist/controllers/types.d.ts.map +1 -1
- package/dist/engine/chart.d.ts +2 -12
- package/dist/engine/chart.d.ts.map +1 -1
- package/dist/engine/chart.js +28 -31
- package/dist/engine/chart.js.map +1 -1
- package/dist/engine/controller/interaction.d.ts +1 -1
- package/dist/engine/controller/interaction.d.ts.map +1 -1
- package/dist/engine/controller/interaction.js +10 -2
- package/dist/engine/controller/interaction.js.map +1 -1
- package/dist/engine/drawing/interaction.d.ts +3 -3
- package/dist/engine/drawing/interaction.d.ts.map +1 -1
- package/dist/engine/drawing/interaction.js +38 -46
- package/dist/engine/drawing/interaction.js.map +1 -1
- package/dist/engine/renderers/paneTitle.d.ts +5 -24
- package/dist/engine/renderers/paneTitle.d.ts.map +1 -1
- package/dist/engine/renderers/paneTitle.js +10 -5
- package/dist/engine/renderers/paneTitle.js.map +1 -1
- package/dist/engine/renderers/webgl/candleSurface.d.ts +4 -4
- package/dist/engine/renderers/webgl/candleSurface.d.ts.map +1 -1
- package/dist/engine/renderers/webgl/candleSurface.js +36 -56
- package/dist/engine/renderers/webgl/candleSurface.js.map +1 -1
- package/dist/engine/subPaneManager.d.ts +2 -0
- package/dist/engine/subPaneManager.d.ts.map +1 -1
- package/dist/engine/subPaneManager.js +25 -1
- package/dist/engine/subPaneManager.js.map +1 -1
- package/dist/semantic/controller.d.ts +1 -2
- package/dist/semantic/controller.d.ts.map +1 -1
- package/dist/semantic/index.d.ts +1 -1
- package/dist/semantic/index.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +6 -6
- package/src/controllers/createChartController.ts +158 -29
- package/src/controllers/index.ts +33 -0
- package/src/controllers/types.ts +79 -8
- package/src/engine/chart.ts +28 -37
- package/src/engine/controller/interaction.ts +9 -2
- package/src/engine/drawing/interaction.ts +38 -47
- package/src/engine/renderers/paneTitle.ts +16 -25
- package/src/engine/renderers/webgl/candleSurface.ts +40 -56
- package/src/engine/subPaneManager.ts +28 -1
- package/src/semantic/controller.ts +1 -1
- package/src/semantic/index.ts +1 -1
- package/src/version.ts +1 -1
- package/dist/engine/chart-store.d.ts +0 -75
- package/dist/engine/chart-store.d.ts.map +0 -1
- package/dist/engine/chart-store.js +0 -88
- package/dist/engine/chart-store.js.map +0 -1
- package/src/engine/chart-store.ts +0 -121
package/src/engine/chart.ts
CHANGED
|
@@ -9,7 +9,6 @@ import { PaneRenderer } from './paneRenderer'
|
|
|
9
9
|
import { SharedWebGLSurface } from './renderers/webgl/sharedWebGLSurface'
|
|
10
10
|
import { MarkerManager, type CustomMarkerEntity } from './marker/registry'
|
|
11
11
|
import { getPhysicalKLineConfig, calcKWidthPx } from './utils/klineConfig'
|
|
12
|
-
import { computeContentWidth } from './chart-store'
|
|
13
12
|
import { computeZoom, computeZoomToLevel, type ZoomConfig } from './utils/zoom'
|
|
14
13
|
import { IndicatorScheduler } from './indicators/scheduler'
|
|
15
14
|
import { getRegisteredIndicatorDefinitions } from './indicators/indicatorDefinitionRegistry'
|
|
@@ -214,21 +213,12 @@ export class Chart {
|
|
|
214
213
|
/** pane ratio 状态(按 paneId 维护,sum=1 仅对可见 pane) */
|
|
215
214
|
private _internalPaneRatios: Map<string, number> = new Map()
|
|
216
215
|
|
|
217
|
-
/** 视口变化回调(供外部同步 DPR/尺寸) */
|
|
218
|
-
private onViewportChange?: (viewport: Viewport) => void
|
|
219
|
-
|
|
220
216
|
/** 共享 X 轴上下文缓存 */
|
|
221
217
|
private xAxisCtx: CanvasRenderingContext2D | null = null
|
|
222
218
|
|
|
223
219
|
/** Chart 级共享 WebGL canvas/context */
|
|
224
220
|
private sharedWebGLSurface: SharedWebGLSurface
|
|
225
221
|
|
|
226
|
-
/** pane 布局回流回调(Chart -> UI 单向) */
|
|
227
|
-
private onPaneLayoutChange?: (panes: PaneSpec[]) => void
|
|
228
|
-
|
|
229
|
-
/** 数据变化回调(供外部同步 dataLength) */
|
|
230
|
-
private onDataChange?: (data: KLineData[]) => void
|
|
231
|
-
|
|
232
222
|
/** 当前缩放级别(1 ~ zoomLevelCount) */
|
|
233
223
|
private currentZoomLevel: number = 1
|
|
234
224
|
|
|
@@ -1243,21 +1233,6 @@ export class Chart {
|
|
|
1243
1233
|
return this.zoomLevelCount
|
|
1244
1234
|
}
|
|
1245
1235
|
|
|
1246
|
-
/** 注册视口变化回调 */
|
|
1247
|
-
setOnViewportChange(cb: (viewport: Viewport) => void) {
|
|
1248
|
-
this.onViewportChange = cb
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
|
-
/** 注册 pane 布局回流回调 */
|
|
1252
|
-
setOnPaneLayoutChange(cb: (panes: PaneSpec[]) => void) {
|
|
1253
|
-
this.onPaneLayoutChange = cb
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
/** 注册数据变化回调 */
|
|
1257
|
-
setOnDataChange(cb: (data: KLineData[]) => void) {
|
|
1258
|
-
this.onDataChange = cb
|
|
1259
|
-
}
|
|
1260
|
-
|
|
1261
1236
|
/** 获取所有 PaneRenderer */
|
|
1262
1237
|
getPaneRenderers(): PaneRenderer[] {
|
|
1263
1238
|
return this.paneRenderers
|
|
@@ -1436,7 +1411,7 @@ export class Chart {
|
|
|
1436
1411
|
})
|
|
1437
1412
|
this._paneRatiosSignal.set(ratios)
|
|
1438
1413
|
|
|
1439
|
-
this.
|
|
1414
|
+
this._paneLayoutSignal.set(this.getPaneLayoutSpecs())
|
|
1440
1415
|
}
|
|
1441
1416
|
|
|
1442
1417
|
private applyPaneLayoutSpecs(panes: PaneSpec[]): void {
|
|
@@ -1781,7 +1756,6 @@ export class Chart {
|
|
|
1781
1756
|
updateData(data: KLineData[]) {
|
|
1782
1757
|
this._internalData = data ?? []
|
|
1783
1758
|
this._dataSignal.set([...this._internalData])
|
|
1784
|
-
this.onDataChange?.(this._internalData)
|
|
1785
1759
|
|
|
1786
1760
|
// 重算 DOM scrollLeft 状态, 防止左右滚动超出数据长度范围
|
|
1787
1761
|
const container = this.dom.container
|
|
@@ -1848,13 +1822,16 @@ export class Chart {
|
|
|
1848
1822
|
|
|
1849
1823
|
/** 获取内容总宽度(用于外部 scroll-content 撑开 scrollWidth) */
|
|
1850
1824
|
getContentWidth(): number {
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1825
|
+
const dataLength = this._internalData.length
|
|
1826
|
+
if (dataLength === 0) return 0
|
|
1827
|
+
const kWidth = this.opt.kWidth
|
|
1828
|
+
const kGap = this.opt.kGap
|
|
1829
|
+
const viewWidth = this._internalViewport?.plotWidth ?? 0
|
|
1830
|
+
const dpr = this.getEffectiveDpr()
|
|
1831
|
+
const TRAILING_DRAWING_SLOTS = 24
|
|
1832
|
+
const { startXPx, unitPx } = getPhysicalKLineConfig(kWidth, kGap, dpr)
|
|
1833
|
+
const dataPlotWidth = (startXPx + (dataLength + TRAILING_DRAWING_SLOTS) * unitPx) / dpr
|
|
1834
|
+
return Math.max(dataPlotWidth, viewWidth)
|
|
1858
1835
|
}
|
|
1859
1836
|
|
|
1860
1837
|
|
|
@@ -1936,8 +1913,6 @@ export class Chart {
|
|
|
1936
1913
|
// 清理渲染器插件管理器(会调用所有 onUninstall)
|
|
1937
1914
|
this.rendererPluginManager.clear()
|
|
1938
1915
|
|
|
1939
|
-
this.onViewportChange = undefined
|
|
1940
|
-
this.onPaneLayoutChange = undefined
|
|
1941
1916
|
this.indicatorScheduler.destroy()
|
|
1942
1917
|
await this.pluginHost.destroy()
|
|
1943
1918
|
}
|
|
@@ -2255,7 +2230,18 @@ export class Chart {
|
|
|
2255
2230
|
|
|
2256
2231
|
this._internalViewport = vp
|
|
2257
2232
|
if (viewportChanged) {
|
|
2258
|
-
this.
|
|
2233
|
+
const current = this._viewportSignal.peek()
|
|
2234
|
+
this._viewportSignal.set({
|
|
2235
|
+
zoomLevel: current.zoomLevel,
|
|
2236
|
+
plotWidth: vp.plotWidth,
|
|
2237
|
+
plotHeight: vp.plotHeight,
|
|
2238
|
+
dpr: vp.dpr > 0 ? vp.dpr : current.dpr,
|
|
2239
|
+
visibleFrom: current.visibleFrom,
|
|
2240
|
+
visibleTo: current.visibleTo,
|
|
2241
|
+
desiredScrollLeft: current.desiredScrollLeft,
|
|
2242
|
+
kWidth: current.kWidth,
|
|
2243
|
+
kGap: current.kGap,
|
|
2244
|
+
})
|
|
2259
2245
|
}
|
|
2260
2246
|
return vp
|
|
2261
2247
|
}
|
|
@@ -2280,6 +2266,7 @@ export class Chart {
|
|
|
2280
2266
|
private _drawingToolSignal = createSignal<DrawingToolType | null>(null)
|
|
2281
2267
|
private _drawingsSignal = createSignal<ReadonlyArray<import('../plugin').DrawingObject>>([])
|
|
2282
2268
|
private _paneRatiosSignal = createSignal<Readonly<Record<string, number>>>({})
|
|
2269
|
+
private _paneLayoutSignal = createSignal<PaneSpec[]>([])
|
|
2283
2270
|
private _interactionSignal = createSignal<InteractionSnapshot>({
|
|
2284
2271
|
crosshairPos: null,
|
|
2285
2272
|
crosshairIndex: null,
|
|
@@ -2369,6 +2356,10 @@ export class Chart {
|
|
|
2369
2356
|
return this._paneRatiosSignal
|
|
2370
2357
|
}
|
|
2371
2358
|
|
|
2359
|
+
get paneLayout(): Signal<PaneSpec[]> {
|
|
2360
|
+
return this._paneLayoutSignal
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2372
2363
|
/** 交互状态信号 */
|
|
2373
2364
|
get interactionState(): Signal<InteractionSnapshot> {
|
|
2374
2365
|
return this._interactionSignal
|
|
@@ -102,10 +102,17 @@ export class InteractionController {
|
|
|
102
102
|
|
|
103
103
|
constructor(chart: Chart) {
|
|
104
104
|
this.chart = chart
|
|
105
|
+
this.setupPinchZoom()
|
|
105
106
|
}
|
|
106
107
|
|
|
107
|
-
|
|
108
|
-
this.pinchTracker.setOnPinchZoom(
|
|
108
|
+
private setupPinchZoom(): void {
|
|
109
|
+
this.pinchTracker.setOnPinchZoom((delta, centerClientX) => {
|
|
110
|
+
const container = this.chart.getDom().container
|
|
111
|
+
if (!container) return
|
|
112
|
+
const rect = container.getBoundingClientRect()
|
|
113
|
+
const centerX = centerClientX - rect.left
|
|
114
|
+
this.chart.handlePinchZoom(delta, centerX)
|
|
115
|
+
})
|
|
109
116
|
}
|
|
110
117
|
|
|
111
118
|
/** 更新用户设置 */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DrawingObject, DrawingKind, DrawingAnchor, DrawingStyle } from '../../plugin'
|
|
2
|
-
import type {
|
|
2
|
+
import type { DrawingChartAdapter } from '../../controllers/types'
|
|
3
3
|
import { getPhysicalKLineConfig } from '../utils/klineConfig'
|
|
4
4
|
import { computeLinearRegression } from './index'
|
|
5
5
|
|
|
@@ -55,7 +55,7 @@ const LINE_HIT_RADIUS = 6
|
|
|
55
55
|
* 封装绘图工具的交互逻辑,与 Vue 组件解耦
|
|
56
56
|
*/
|
|
57
57
|
export class DrawingInteractionController {
|
|
58
|
-
private
|
|
58
|
+
private adapter: DrawingChartAdapter
|
|
59
59
|
private activeTool: DrawingToolId = 'cursor'
|
|
60
60
|
private pendingAnchors: DrawingAnchorInput[] = []
|
|
61
61
|
private drawings: DrawingObject[] = []
|
|
@@ -87,8 +87,8 @@ export class DrawingInteractionController {
|
|
|
87
87
|
'disjoint-channel',
|
|
88
88
|
]
|
|
89
89
|
|
|
90
|
-
constructor(
|
|
91
|
-
this.
|
|
90
|
+
constructor(adapter: DrawingChartAdapter) {
|
|
91
|
+
this.adapter = adapter
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
setCallbacks(callbacks: DrawingInteractionCallbacks) {
|
|
@@ -114,7 +114,7 @@ export class DrawingInteractionController {
|
|
|
114
114
|
|
|
115
115
|
setDrawings(drawings: DrawingObject[]) {
|
|
116
116
|
this.drawings = drawings
|
|
117
|
-
this.
|
|
117
|
+
this.adapter.setDrawings(drawings)
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
clear() {
|
|
@@ -133,7 +133,7 @@ export class DrawingInteractionController {
|
|
|
133
133
|
this.drawings = this.drawings.map((d) =>
|
|
134
134
|
d.id === drawingId ? { ...d, style: { ...d.style, ...style } } : d
|
|
135
135
|
)
|
|
136
|
-
this.
|
|
136
|
+
this.adapter.setDrawings(this.drawings)
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
removeDrawing(drawingId: string): void {
|
|
@@ -141,7 +141,7 @@ export class DrawingInteractionController {
|
|
|
141
141
|
if (this.selectedDrawingId === drawingId) {
|
|
142
142
|
this.setSelected(null)
|
|
143
143
|
}
|
|
144
|
-
this.
|
|
144
|
+
this.adapter.setDrawings(this.drawings)
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
/**
|
|
@@ -287,7 +287,7 @@ export class DrawingInteractionController {
|
|
|
287
287
|
}
|
|
288
288
|
}
|
|
289
289
|
|
|
290
|
-
this.
|
|
290
|
+
this.adapter.setDrawings([...this.drawings])
|
|
291
291
|
return true
|
|
292
292
|
}
|
|
293
293
|
|
|
@@ -400,7 +400,7 @@ export class DrawingInteractionController {
|
|
|
400
400
|
|
|
401
401
|
this.drawings = this.drawings.filter((d) => d.id !== this.previewDrawingId)
|
|
402
402
|
this.drawings = [...this.drawings, preview]
|
|
403
|
-
this.
|
|
403
|
+
this.adapter.setDrawings(this.drawings)
|
|
404
404
|
return true
|
|
405
405
|
}
|
|
406
406
|
|
|
@@ -446,7 +446,7 @@ export class DrawingInteractionController {
|
|
|
446
446
|
drawing: DrawingObject,
|
|
447
447
|
regressionGeometryCache?: Map<string, RegressionChannelGeometry | null>,
|
|
448
448
|
): LineSegment[] {
|
|
449
|
-
const viewport = this.
|
|
449
|
+
const viewport = this.adapter.getViewport()
|
|
450
450
|
if (!viewport) return []
|
|
451
451
|
|
|
452
452
|
if (drawing.kind === 'regression-channel') {
|
|
@@ -458,12 +458,11 @@ export class DrawingInteractionController {
|
|
|
458
458
|
const screen = this.anchorToScreen(drawing.anchors[0]!)
|
|
459
459
|
if (!screen) return []
|
|
460
460
|
|
|
461
|
-
const
|
|
462
|
-
|
|
463
|
-
if (!pane) return []
|
|
461
|
+
const paneInfo = this.adapter.getPaneInfo('main')
|
|
462
|
+
if (!paneInfo) return []
|
|
464
463
|
|
|
465
464
|
const right = viewport.plotWidth
|
|
466
|
-
const bottom =
|
|
465
|
+
const bottom = paneInfo.height
|
|
467
466
|
|
|
468
467
|
switch (drawing.kind) {
|
|
469
468
|
case 'horizontal-line':
|
|
@@ -582,7 +581,7 @@ export class DrawingInteractionController {
|
|
|
582
581
|
const cached = regressionGeometryCache?.get(drawing.id)
|
|
583
582
|
if (cached !== undefined) return cached
|
|
584
583
|
|
|
585
|
-
const data = this.
|
|
584
|
+
const data = this.adapter.getData()
|
|
586
585
|
if (data.length === 0 || drawing.anchors.length < 2) {
|
|
587
586
|
regressionGeometryCache?.set(drawing.id, null)
|
|
588
587
|
return null
|
|
@@ -645,21 +644,17 @@ export class DrawingInteractionController {
|
|
|
645
644
|
// ============ 坐标转换 ============
|
|
646
645
|
|
|
647
646
|
private anchorToScreen(anchor: DrawingAnchor): { x: number; y: number } | null {
|
|
648
|
-
const viewport = this.
|
|
647
|
+
const viewport = this.adapter.getViewport()
|
|
649
648
|
if (!viewport) return null
|
|
650
649
|
|
|
651
|
-
const
|
|
652
|
-
const dpr = this.
|
|
653
|
-
const { startXPx, unitPx } = getPhysicalKLineConfig(
|
|
650
|
+
const { kWidth, kGap } = this.adapter.getKWidthKGap()
|
|
651
|
+
const dpr = this.adapter.getCurrentDpr()
|
|
652
|
+
const { startXPx, unitPx } = getPhysicalKLineConfig(kWidth, kGap, dpr)
|
|
654
653
|
if (!Number.isFinite(anchor.index)) return null
|
|
655
654
|
|
|
656
655
|
const x = (startXPx + anchor.index * unitPx + (unitPx - 1) / 2) / dpr - viewport.scrollLeft
|
|
657
656
|
|
|
658
|
-
const
|
|
659
|
-
const pane = paneRenderer?.getPane()
|
|
660
|
-
if (!pane) return null
|
|
661
|
-
|
|
662
|
-
const y = pane.yAxis.priceToY(anchor.price)
|
|
657
|
+
const y = this.adapter.priceToY('main', anchor.price)
|
|
663
658
|
return { x, y }
|
|
664
659
|
}
|
|
665
660
|
|
|
@@ -667,23 +662,22 @@ export class DrawingInteractionController {
|
|
|
667
662
|
screenX: number,
|
|
668
663
|
screenY: number
|
|
669
664
|
): DrawingAnchorInput | null {
|
|
670
|
-
const data = this.
|
|
671
|
-
const viewport = this.
|
|
665
|
+
const data = this.adapter.getData()
|
|
666
|
+
const viewport = this.adapter.getViewport()
|
|
672
667
|
if (!viewport || data.length === 0) return null
|
|
673
668
|
|
|
674
|
-
const logicalIndex = this.
|
|
669
|
+
const logicalIndex = this.adapter.getLogicalIndexAtX(screenX)
|
|
675
670
|
if (logicalIndex === null) return null
|
|
676
671
|
|
|
677
|
-
const
|
|
678
|
-
|
|
679
|
-
if (!pane) return null
|
|
672
|
+
const paneInfo = this.adapter.getPaneInfo('main')
|
|
673
|
+
if (!paneInfo) return null
|
|
680
674
|
|
|
681
|
-
const timestamp = this.
|
|
675
|
+
const timestamp = this.adapter.getTimestampAtLogicalIndex(logicalIndex) ?? undefined
|
|
682
676
|
|
|
683
677
|
return {
|
|
684
678
|
index: logicalIndex,
|
|
685
679
|
time: timestamp ?? undefined,
|
|
686
|
-
price:
|
|
680
|
+
price: this.adapter.yToPrice('main', screenY - paneInfo.top),
|
|
687
681
|
}
|
|
688
682
|
}
|
|
689
683
|
|
|
@@ -693,22 +687,22 @@ export class DrawingInteractionController {
|
|
|
693
687
|
const newId = drawing?.id ?? null
|
|
694
688
|
if (this.selectedDrawingId === newId) return
|
|
695
689
|
this.selectedDrawingId = newId
|
|
696
|
-
this.
|
|
690
|
+
this.adapter.setSelectedDrawingId(newId)
|
|
697
691
|
this.callbacks.onDrawingSelected?.(drawing)
|
|
698
692
|
}
|
|
699
693
|
|
|
700
694
|
private removePreview() {
|
|
701
695
|
if (!this.drawings.some((d) => d.id === this.previewDrawingId)) return
|
|
702
696
|
this.drawings = this.drawings.filter((d) => d.id !== this.previewDrawingId)
|
|
703
|
-
this.
|
|
697
|
+
this.adapter.setDrawings(this.drawings)
|
|
704
698
|
}
|
|
705
699
|
|
|
706
700
|
private resolveAnchorFromPointer(
|
|
707
701
|
e: PointerEvent,
|
|
708
702
|
container: HTMLElement
|
|
709
703
|
): DrawingAnchorInput | null {
|
|
710
|
-
const data = this.
|
|
711
|
-
const viewport = this.
|
|
704
|
+
const data = this.adapter.getData()
|
|
705
|
+
const viewport = this.adapter.getViewport()
|
|
712
706
|
if (!viewport || data.length === 0) return null
|
|
713
707
|
|
|
714
708
|
const rect = container.getBoundingClientRect()
|
|
@@ -718,21 +712,18 @@ export class DrawingInteractionController {
|
|
|
718
712
|
return null
|
|
719
713
|
}
|
|
720
714
|
|
|
721
|
-
const
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
})
|
|
725
|
-
const pane = paneRenderer?.getPane()
|
|
726
|
-
if (!pane) return null
|
|
715
|
+
const paneInfo = this.adapter.getPaneInfo('main')
|
|
716
|
+
if (!paneInfo) return null
|
|
717
|
+
if (mouseY < paneInfo.top || mouseY > paneInfo.top + paneInfo.height) return null
|
|
727
718
|
|
|
728
|
-
const logicalIndex = this.
|
|
719
|
+
const logicalIndex = this.adapter.getLogicalIndexAtX(mouseX)
|
|
729
720
|
if (logicalIndex === null) return null
|
|
730
|
-
const timestamp = this.
|
|
721
|
+
const timestamp = this.adapter.getTimestampAtLogicalIndex(logicalIndex) ?? undefined
|
|
731
722
|
|
|
732
723
|
return {
|
|
733
724
|
index: logicalIndex,
|
|
734
725
|
time: timestamp ?? undefined,
|
|
735
|
-
price:
|
|
726
|
+
price: this.adapter.yToPrice('main', mouseY - paneInfo.top),
|
|
736
727
|
}
|
|
737
728
|
}
|
|
738
729
|
|
|
@@ -754,7 +745,7 @@ export class DrawingInteractionController {
|
|
|
754
745
|
}
|
|
755
746
|
|
|
756
747
|
this.drawings = [...this.drawings, drawing]
|
|
757
|
-
this.
|
|
748
|
+
this.adapter.setDrawings(this.drawings)
|
|
758
749
|
this.callbacks.onDrawingCreated?.(drawing)
|
|
759
750
|
this.activeTool = 'cursor'
|
|
760
751
|
this.callbacks.onToolChange?.('cursor')
|
|
@@ -801,7 +792,7 @@ export class DrawingInteractionController {
|
|
|
801
792
|
}
|
|
802
793
|
|
|
803
794
|
this.drawings = [...this.drawings, drawing]
|
|
804
|
-
this.
|
|
795
|
+
this.adapter.setDrawings(this.drawings)
|
|
805
796
|
this.callbacks.onDrawingCreated?.(drawing)
|
|
806
797
|
this.activeTool = 'cursor'
|
|
807
798
|
this.callbacks.onToolChange?.('cursor')
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RendererPluginWithHost, RenderContext, PluginHost } from '../../plugin'
|
|
2
2
|
import { RENDERER_PRIORITY } from '../../plugin'
|
|
3
3
|
import { getColors } from '../theme/colors'
|
|
4
4
|
import { getFont, setCanvasFont } from '../theme/fonts'
|
|
5
|
+
import { SUB_PANE_INDICATOR_CONFIGS } from './Indicator/subPaneConfig'
|
|
6
|
+
import type { SubIndicatorType } from './Indicator'
|
|
5
7
|
|
|
6
8
|
const textWidthCache = new Map<string, number>()
|
|
7
9
|
const TEXT_WIDTH_CACHE_LIMIT = 256
|
|
@@ -21,49 +23,30 @@ function measureTextWidth(ctx: CanvasRenderingContext2D, text: string): number {
|
|
|
21
23
|
return width
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
/**
|
|
25
|
-
* 单个数值项
|
|
26
|
-
*/
|
|
27
26
|
export interface TitleValueItem {
|
|
28
|
-
/** 标签(如 "DIF"、"DEA") */
|
|
29
27
|
label: string
|
|
30
|
-
/** 数值 */
|
|
31
28
|
value: number
|
|
32
|
-
/** 颜色 */
|
|
33
29
|
color: string
|
|
34
30
|
}
|
|
35
31
|
|
|
36
|
-
/**
|
|
37
|
-
* 标题信息(由指标渲染器提供)
|
|
38
|
-
*/
|
|
39
32
|
export interface TitleInfo {
|
|
40
|
-
/** 指标名称(如 "MACD") */
|
|
41
33
|
name: string
|
|
42
|
-
/** 参数列表(如 [12, 26, 9]) */
|
|
43
34
|
params?: number[]
|
|
44
|
-
/** 数值项列表 */
|
|
45
35
|
values?: TitleValueItem[]
|
|
46
36
|
}
|
|
47
37
|
|
|
48
38
|
export interface PaneTitleOptions {
|
|
49
|
-
/** 面板 ID */
|
|
50
39
|
paneId: string
|
|
51
|
-
/** 标题文本(静态模式) */
|
|
52
40
|
title: string
|
|
53
|
-
/** 副标题/描述 */
|
|
54
41
|
description?: string
|
|
55
|
-
/** Y 偏移(逻辑像素) */
|
|
56
42
|
yOffset?: number
|
|
57
|
-
|
|
58
|
-
|
|
43
|
+
indicatorId: SubIndicatorType
|
|
44
|
+
params: Record<string, unknown>
|
|
59
45
|
}
|
|
60
46
|
|
|
61
|
-
|
|
62
|
-
* 创建面板标题渲染器插件
|
|
63
|
-
* 在面板左上角显示标题,支持动态指标数值显示
|
|
64
|
-
*/
|
|
65
|
-
export function createPaneTitleRendererPlugin(options: PaneTitleOptions): RendererPlugin {
|
|
47
|
+
export function createPaneTitleRendererPlugin(options: PaneTitleOptions): RendererPluginWithHost {
|
|
66
48
|
let currentOptions = { ...options }
|
|
49
|
+
let pluginHost: PluginHost | null = null
|
|
67
50
|
|
|
68
51
|
return {
|
|
69
52
|
name: `paneTitle_${options.paneId}`,
|
|
@@ -74,6 +57,10 @@ export function createPaneTitleRendererPlugin(options: PaneTitleOptions): Render
|
|
|
74
57
|
priority: RENDERER_PRIORITY.FOREGROUND,
|
|
75
58
|
layer: 'overlay',
|
|
76
59
|
|
|
60
|
+
onInstall(host: PluginHost) {
|
|
61
|
+
pluginHost = host
|
|
62
|
+
},
|
|
63
|
+
|
|
77
64
|
draw(context: RenderContext) {
|
|
78
65
|
const { overlayCtx, pane, paneWidth } = context
|
|
79
66
|
const colors = getColors(context.theme)
|
|
@@ -89,7 +76,11 @@ export function createPaneTitleRendererPlugin(options: PaneTitleOptions): Render
|
|
|
89
76
|
overlayCtx.textAlign = 'left'
|
|
90
77
|
overlayCtx.textBaseline = 'top'
|
|
91
78
|
|
|
92
|
-
const
|
|
79
|
+
const config = SUB_PANE_INDICATOR_CONFIGS[currentOptions.indicatorId]
|
|
80
|
+
const crosshairIndex = context.crosshairIndex ?? null
|
|
81
|
+
const titleInfo = config && pluginHost
|
|
82
|
+
? config.getTitleInfo(context.data, crosshairIndex, currentOptions.params as Record<string, number | boolean | string>, pluginHost, currentOptions.paneId)
|
|
83
|
+
: null
|
|
93
84
|
|
|
94
85
|
if (titleInfo) {
|
|
95
86
|
let currentX = x
|
|
@@ -46,7 +46,7 @@ type LineWebGLHandles = {
|
|
|
46
46
|
basic: BasicLineWebGLHandles
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
type
|
|
49
|
+
type MsaaTargets = {
|
|
50
50
|
samples: number
|
|
51
51
|
widthPx: number
|
|
52
52
|
heightPx: number
|
|
@@ -325,7 +325,7 @@ export class LineWebGLSurface {
|
|
|
325
325
|
private fillScratch = new Float32Array(0)
|
|
326
326
|
private lineScratch = new Float32Array(0)
|
|
327
327
|
private region: WebGLRegion | null = null
|
|
328
|
-
private msaaTargets:
|
|
328
|
+
private msaaTargets: MsaaTargets | null = null
|
|
329
329
|
|
|
330
330
|
// Geometry cache: 以 points 数组引用 + halfWidth 为 key,避免每帧重算法线/miter
|
|
331
331
|
private geoCache = new WeakMap<Array<{ x: number; y: number }>, Map<number, { vertices: Float32Array; vertexCount: number }>>()
|
|
@@ -390,16 +390,10 @@ export class LineWebGLSurface {
|
|
|
390
390
|
const colorValue = parseColor(line.color)
|
|
391
391
|
if (!colorValue) return false
|
|
392
392
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
} else {
|
|
398
|
-
const geometry = this.getLineGeometry(line)
|
|
399
|
-
if (!geometry) return false
|
|
400
|
-
drawCmds.push({ colorValue, mode: gl.TRIANGLES, firstVertex: totalFloats / 2, pointCount: geometry.vertexCount })
|
|
401
|
-
totalFloats += geometry.vertices.length
|
|
402
|
-
}
|
|
393
|
+
const geometry = this.getLineGeometry(line)
|
|
394
|
+
if (!geometry) return false
|
|
395
|
+
drawCmds.push({ colorValue, mode: gl.TRIANGLES, firstVertex: totalFloats / 2, pointCount: geometry.vertexCount })
|
|
396
|
+
totalFloats += geometry.vertices.length
|
|
403
397
|
}
|
|
404
398
|
|
|
405
399
|
if (this.lineScratch.length < totalFloats) {
|
|
@@ -407,24 +401,13 @@ export class LineWebGLSurface {
|
|
|
407
401
|
}
|
|
408
402
|
let floatOffset = 0
|
|
409
403
|
for (const line of lines) {
|
|
410
|
-
const vertices = line
|
|
411
|
-
? this.getThinLineVertices(line.points).vertices
|
|
412
|
-
: this.getLineGeometry(line)!.vertices
|
|
404
|
+
const vertices = this.getLineGeometry(line)!.vertices
|
|
413
405
|
this.lineScratch.set(vertices, floatOffset)
|
|
414
406
|
floatOffset += vertices.length
|
|
415
407
|
}
|
|
416
408
|
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
const useMsaa = msaaTargets !== null
|
|
420
|
-
|
|
421
|
-
if (useMsaa) {
|
|
422
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, msaaTargets.msaaFramebuffer)
|
|
423
|
-
gl.viewport(0, 0, msaaTargets.widthPx, msaaTargets.heightPx)
|
|
424
|
-
gl.disable(gl.SCISSOR_TEST)
|
|
425
|
-
gl.clearColor(0, 0, 0, 0)
|
|
426
|
-
gl.clear(gl.COLOR_BUFFER_BIT)
|
|
427
|
-
} else if (!this.shared.bindRegion(region)) {
|
|
409
|
+
const msaaRender = this.beginMsaaRender(gl, region)
|
|
410
|
+
if (!msaaRender && !this.shared.bindRegion(region)) {
|
|
428
411
|
return false
|
|
429
412
|
}
|
|
430
413
|
|
|
@@ -448,36 +431,14 @@ export class LineWebGLSurface {
|
|
|
448
431
|
|
|
449
432
|
gl.bindVertexArray(null)
|
|
450
433
|
|
|
451
|
-
if (
|
|
452
|
-
this.
|
|
434
|
+
if (msaaRender) {
|
|
435
|
+
this.resolveMsaaToSharedRegion(gl, msaaRender.targets, msaaRender.physical)
|
|
453
436
|
}
|
|
454
437
|
|
|
455
438
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null)
|
|
456
439
|
return true
|
|
457
440
|
}
|
|
458
441
|
|
|
459
|
-
private getThinLineVertices(points: Array<{ x: number; y: number }>): { vertices: Float32Array; vertexCount: number } {
|
|
460
|
-
let widthMap = this.geoCache.get(points)
|
|
461
|
-
if (widthMap) {
|
|
462
|
-
const cached = widthMap.get(0)
|
|
463
|
-
if (cached) return cached
|
|
464
|
-
} else {
|
|
465
|
-
widthMap = new Map()
|
|
466
|
-
this.geoCache.set(points, widthMap)
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
const vertexCount = points.length
|
|
470
|
-
const vertices = new Float32Array(vertexCount * 2)
|
|
471
|
-
let writeIndex = 0
|
|
472
|
-
for (const point of points) {
|
|
473
|
-
vertices[writeIndex++] = point.x
|
|
474
|
-
vertices[writeIndex++] = point.y
|
|
475
|
-
}
|
|
476
|
-
const result = { vertices, vertexCount }
|
|
477
|
-
widthMap.set(0, result)
|
|
478
|
-
return result
|
|
479
|
-
}
|
|
480
|
-
|
|
481
442
|
private getLineGeometry(line: LineStrip): { vertices: Float32Array; vertexCount: number } | null {
|
|
482
443
|
const halfWidth = line.width / 2
|
|
483
444
|
let widthMap = this.geoCache.get(line.points)
|
|
@@ -521,7 +482,11 @@ export class LineWebGLSurface {
|
|
|
521
482
|
}
|
|
522
483
|
|
|
523
484
|
const gl = this.shared.getGL()
|
|
524
|
-
|
|
485
|
+
const region = this.region
|
|
486
|
+
if (!gl || !region) return false
|
|
487
|
+
|
|
488
|
+
const msaaRender = this.beginMsaaRender(gl, region)
|
|
489
|
+
if (!msaaRender && !this.shared.bindRegion(region)) return false
|
|
525
490
|
|
|
526
491
|
gl.useProgram(handles.basic.program)
|
|
527
492
|
gl.bindVertexArray(handles.basic.vao)
|
|
@@ -538,6 +503,12 @@ export class LineWebGLSurface {
|
|
|
538
503
|
gl.uniform4f(handles.basic.colorLocation, colorValue[0], colorValue[1], colorValue[2], colorValue[3])
|
|
539
504
|
gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertexCount)
|
|
540
505
|
gl.bindVertexArray(null)
|
|
506
|
+
|
|
507
|
+
if (msaaRender) {
|
|
508
|
+
this.resolveMsaaToSharedRegion(gl, msaaRender.targets, msaaRender.physical)
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null)
|
|
541
512
|
return true
|
|
542
513
|
}
|
|
543
514
|
|
|
@@ -545,7 +516,7 @@ export class LineWebGLSurface {
|
|
|
545
516
|
const handles = this.handles
|
|
546
517
|
const gl = this.shared.getGL()
|
|
547
518
|
if (gl) {
|
|
548
|
-
this.
|
|
519
|
+
this.destroyMsaaTargets(gl)
|
|
549
520
|
}
|
|
550
521
|
if (!handles) {
|
|
551
522
|
this.vertexCapacity = 0
|
|
@@ -563,7 +534,20 @@ export class LineWebGLSurface {
|
|
|
563
534
|
this.vertexCapacity = 0
|
|
564
535
|
}
|
|
565
536
|
|
|
566
|
-
private
|
|
537
|
+
private beginMsaaRender(gl: WebGL2RenderingContext, region: WebGLRegion): { targets: MsaaTargets; physical: PhysicalRegion } | null {
|
|
538
|
+
const physical = this.shared.getPhysicalRegion(region)
|
|
539
|
+
const targets = physical ? this.ensureMsaaTargets(gl, physical) : null
|
|
540
|
+
if (!physical || !targets) return null
|
|
541
|
+
|
|
542
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, targets.msaaFramebuffer)
|
|
543
|
+
gl.viewport(0, 0, targets.widthPx, targets.heightPx)
|
|
544
|
+
gl.disable(gl.SCISSOR_TEST)
|
|
545
|
+
gl.clearColor(0, 0, 0, 0)
|
|
546
|
+
gl.clear(gl.COLOR_BUFFER_BIT)
|
|
547
|
+
return { targets, physical }
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
private ensureMsaaTargets(gl: WebGL2RenderingContext, physical: PhysicalRegion): MsaaTargets | null {
|
|
567
551
|
const preferredSamples = 4
|
|
568
552
|
const maxSamples = Number(gl.getParameter(gl.MAX_SAMPLES)) || 0
|
|
569
553
|
const samples = Math.max(1, Math.min(preferredSamples, maxSamples))
|
|
@@ -579,7 +563,7 @@ export class LineWebGLSurface {
|
|
|
579
563
|
return existing
|
|
580
564
|
}
|
|
581
565
|
|
|
582
|
-
this.
|
|
566
|
+
this.destroyMsaaTargets(gl)
|
|
583
567
|
|
|
584
568
|
const msaaFramebuffer = gl.createFramebuffer()
|
|
585
569
|
const msaaColorRenderbuffer = gl.createRenderbuffer()
|
|
@@ -643,7 +627,7 @@ export class LineWebGLSurface {
|
|
|
643
627
|
return targets
|
|
644
628
|
}
|
|
645
629
|
|
|
646
|
-
private
|
|
630
|
+
private destroyMsaaTargets(gl: WebGL2RenderingContext): void {
|
|
647
631
|
const targets = this.msaaTargets
|
|
648
632
|
if (!targets) return
|
|
649
633
|
gl.deleteFramebuffer(targets.msaaFramebuffer)
|
|
@@ -653,7 +637,7 @@ export class LineWebGLSurface {
|
|
|
653
637
|
this.msaaTargets = null
|
|
654
638
|
}
|
|
655
639
|
|
|
656
|
-
private
|
|
640
|
+
private resolveMsaaToSharedRegion(gl: WebGL2RenderingContext, targets: MsaaTargets, physical: PhysicalRegion): void {
|
|
657
641
|
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, targets.msaaFramebuffer)
|
|
658
642
|
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, targets.resolveFramebuffer)
|
|
659
643
|
gl.blitFramebuffer(
|