@363045841yyt/klinechart-core 0.7.5-alpha.2 → 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 +10 -1
- package/dist/controllers/index.d.ts.map +1 -1
- package/dist/controllers/index.js +10 -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 +13 -31
- package/dist/engine/chart.d.ts.map +1 -1
- package/dist/engine/chart.js +120 -140
- 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/Indicator/indicatorData.d.ts +1 -0
- package/dist/engine/renderers/Indicator/indicatorData.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/indicatorData.js +1 -1
- package/dist/engine/renderers/Indicator/indicatorData.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 +6 -0
- package/dist/engine/subPaneManager.d.ts.map +1 -1
- package/dist/engine/subPaneManager.js +38 -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.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +6 -6
- package/src/controllers/createChartController.ts +158 -29
- package/src/controllers/index.ts +34 -0
- package/src/controllers/types.ts +79 -8
- package/src/engine/chart.ts +133 -154
- package/src/engine/controller/interaction.ts +9 -2
- package/src/engine/drawing/interaction.ts +38 -47
- package/src/engine/renderers/Indicator/indicatorData.ts +1 -1
- package/src/engine/renderers/paneTitle.ts +16 -25
- package/src/engine/renderers/webgl/candleSurface.ts +40 -56
- package/src/engine/subPaneManager.ts +43 -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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { KLineData } from '../types/price'
|
|
2
2
|
import type { ChartSettings } from '../config/chartSettings'
|
|
3
|
-
import { createSignal, type Signal } from '../reactivity/signal'
|
|
3
|
+
import { createSignal, computed, type Signal, type Computed } from '../reactivity/signal'
|
|
4
4
|
import { getVisibleRange } from './viewport/viewport'
|
|
5
5
|
import { Pane, type VisibleRange, UpdateLevel } from './layout/pane'
|
|
6
6
|
import { InteractionController, type InteractionSnapshot } from './controller/interaction'
|
|
@@ -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'
|
|
@@ -165,6 +164,11 @@ type FrameData = {
|
|
|
165
164
|
useCachedFrame: boolean
|
|
166
165
|
}
|
|
167
166
|
|
|
167
|
+
/** 主图指标条目,存在 = 激活 */
|
|
168
|
+
interface MainIndicatorEntry {
|
|
169
|
+
params: Record<string, number | boolean | string>
|
|
170
|
+
}
|
|
171
|
+
|
|
168
172
|
export class Chart {
|
|
169
173
|
private dom: ChartDom
|
|
170
174
|
private opt: ResolvedChartOptions
|
|
@@ -209,21 +213,12 @@ export class Chart {
|
|
|
209
213
|
/** pane ratio 状态(按 paneId 维护,sum=1 仅对可见 pane) */
|
|
210
214
|
private _internalPaneRatios: Map<string, number> = new Map()
|
|
211
215
|
|
|
212
|
-
/** 视口变化回调(供外部同步 DPR/尺寸) */
|
|
213
|
-
private onViewportChange?: (viewport: Viewport) => void
|
|
214
|
-
|
|
215
216
|
/** 共享 X 轴上下文缓存 */
|
|
216
217
|
private xAxisCtx: CanvasRenderingContext2D | null = null
|
|
217
218
|
|
|
218
219
|
/** Chart 级共享 WebGL canvas/context */
|
|
219
220
|
private sharedWebGLSurface: SharedWebGLSurface
|
|
220
221
|
|
|
221
|
-
/** pane 布局回流回调(Chart -> UI 单向) */
|
|
222
|
-
private onPaneLayoutChange?: (panes: PaneSpec[]) => void
|
|
223
|
-
|
|
224
|
-
/** 数据变化回调(供外部同步 dataLength) */
|
|
225
|
-
private onDataChange?: (data: KLineData[]) => void
|
|
226
|
-
|
|
227
222
|
/** 当前缩放级别(1 ~ zoomLevelCount) */
|
|
228
223
|
private currentZoomLevel: number = 1
|
|
229
224
|
|
|
@@ -249,13 +244,13 @@ export class Chart {
|
|
|
249
244
|
} | null = null
|
|
250
245
|
|
|
251
246
|
/** 副图管理器 */
|
|
252
|
-
private subPaneManager
|
|
247
|
+
private subPaneManager = new SubPaneManager()
|
|
253
248
|
|
|
254
|
-
/**
|
|
255
|
-
private
|
|
249
|
+
/** 主图指标激活状态与参数(存在即激活,默认参数在 enable 时初始化) */
|
|
250
|
+
private _mainIndicatorsSignal: Signal<Map<string, MainIndicatorEntry>> = createSignal<Map<string, MainIndicatorEntry>>(new Map())
|
|
256
251
|
|
|
257
|
-
/**
|
|
258
|
-
private
|
|
252
|
+
/** 主图指标默认参数 */
|
|
253
|
+
private static DEFAULT_MAIN_PARAMS: Record<string, Record<string, number | boolean | string>> = {
|
|
259
254
|
MA: { ma5: true, ma10: true, ma20: true, ma30: true, ma60: true },
|
|
260
255
|
BOLL: { period: 20, multiplier: 2, showUpper: true, showMiddle: true, showLower: true, showBand: true },
|
|
261
256
|
EXPMA: { fastPeriod: 12, slowPeriod: 50 },
|
|
@@ -289,22 +284,26 @@ export class Chart {
|
|
|
289
284
|
return false
|
|
290
285
|
}
|
|
291
286
|
|
|
292
|
-
|
|
287
|
+
const map = this._mainIndicatorsSignal.peek()
|
|
288
|
+
const existing = map.get(id)
|
|
289
|
+
|
|
290
|
+
if (existing) {
|
|
293
291
|
// 已启用,更新参数
|
|
294
292
|
if (params) {
|
|
295
|
-
|
|
293
|
+
const next = new Map(map)
|
|
294
|
+
next.set(id, { params: { ...existing.params, ...params } })
|
|
295
|
+
this._mainIndicatorsSignal.set(next)
|
|
296
296
|
this.updateIndicatorSchedulerConfig(id)
|
|
297
|
-
this.syncIndicatorsSignal()
|
|
298
297
|
}
|
|
299
298
|
return true
|
|
300
299
|
}
|
|
301
300
|
|
|
302
|
-
this.activeMainIndicators.add(id)
|
|
303
|
-
|
|
304
301
|
// 合并默认参数和传入参数
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
302
|
+
const defaults = Chart.DEFAULT_MAIN_PARAMS[id] ?? {}
|
|
303
|
+
const merged = params ? { ...defaults, ...params } : defaults
|
|
304
|
+
const next = new Map(map)
|
|
305
|
+
next.set(id, { params: merged })
|
|
306
|
+
this._mainIndicatorsSignal.set(next)
|
|
308
307
|
|
|
309
308
|
// 启用对应的渲染器
|
|
310
309
|
this.enableMainIndicatorRenderer(id)
|
|
@@ -313,7 +312,6 @@ export class Chart {
|
|
|
313
312
|
this.updateIndicatorSchedulerConfig(id)
|
|
314
313
|
|
|
315
314
|
this.scheduleDraw()
|
|
316
|
-
this.syncIndicatorsSignal()
|
|
317
315
|
return true
|
|
318
316
|
}
|
|
319
317
|
|
|
@@ -324,9 +322,12 @@ export class Chart {
|
|
|
324
322
|
*/
|
|
325
323
|
disableMainIndicator(indicatorId: string): boolean {
|
|
326
324
|
const id = indicatorId.toUpperCase()
|
|
327
|
-
|
|
325
|
+
const map = this._mainIndicatorsSignal.peek()
|
|
326
|
+
if (!map.has(id)) return false
|
|
328
327
|
|
|
329
|
-
|
|
328
|
+
const next = new Map(map)
|
|
329
|
+
next.delete(id)
|
|
330
|
+
this._mainIndicatorsSignal.set(next)
|
|
330
331
|
|
|
331
332
|
// 禁用对应的渲染器
|
|
332
333
|
this.disableMainIndicatorRenderer(id)
|
|
@@ -335,7 +336,6 @@ export class Chart {
|
|
|
335
336
|
this.updateIndicatorSchedulerConfig(id)
|
|
336
337
|
|
|
337
338
|
this.scheduleDraw()
|
|
338
|
-
this.syncIndicatorsSignal()
|
|
339
339
|
return true
|
|
340
340
|
}
|
|
341
341
|
|
|
@@ -357,7 +357,7 @@ export class Chart {
|
|
|
357
357
|
* @returns 激活的指标ID数组
|
|
358
358
|
*/
|
|
359
359
|
getActiveMainIndicators(): string[] {
|
|
360
|
-
return
|
|
360
|
+
return [...this._mainIndicatorsSignal.peek().keys()]
|
|
361
361
|
}
|
|
362
362
|
|
|
363
363
|
/**
|
|
@@ -365,7 +365,7 @@ export class Chart {
|
|
|
365
365
|
* @param indicatorId 指标ID
|
|
366
366
|
*/
|
|
367
367
|
isMainIndicatorActive(indicatorId: string): boolean {
|
|
368
|
-
return this.
|
|
368
|
+
return this._mainIndicatorsSignal.peek().has(indicatorId.toUpperCase())
|
|
369
369
|
}
|
|
370
370
|
|
|
371
371
|
/**
|
|
@@ -375,22 +375,25 @@ export class Chart {
|
|
|
375
375
|
*/
|
|
376
376
|
updateMainIndicatorParams(indicatorId: string, params: Record<string, number | boolean | string>): void {
|
|
377
377
|
const id = indicatorId.toUpperCase()
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
378
|
+
const map = this._mainIndicatorsSignal.peek()
|
|
379
|
+
const entry = map.get(id)
|
|
380
|
+
if (!entry) return
|
|
381
|
+
|
|
382
|
+
const merged = { ...entry.params, ...params }
|
|
383
|
+
const next = new Map(map)
|
|
384
|
+
next.set(id, { params: merged })
|
|
385
|
+
this._mainIndicatorsSignal.set(next)
|
|
382
386
|
|
|
383
387
|
// 同步更新渲染器配置
|
|
384
388
|
const rendererName = id.toLowerCase()
|
|
385
389
|
const renderer = this.getRenderer(rendererName)
|
|
386
390
|
if (renderer && renderer.setConfig) {
|
|
387
|
-
renderer.setConfig(
|
|
391
|
+
renderer.setConfig(merged)
|
|
388
392
|
}
|
|
389
393
|
|
|
390
394
|
// 更新调度器
|
|
391
395
|
this.updateIndicatorSchedulerConfig(id)
|
|
392
396
|
this.scheduleDraw()
|
|
393
|
-
this.syncIndicatorsSignal()
|
|
394
397
|
}
|
|
395
398
|
|
|
396
399
|
/**
|
|
@@ -398,19 +401,19 @@ export class Chart {
|
|
|
398
401
|
* @param indicatorId 指标ID
|
|
399
402
|
*/
|
|
400
403
|
getMainIndicatorParams(indicatorId: string): Record<string, number | boolean | string> | null {
|
|
401
|
-
return this.
|
|
404
|
+
return this._mainIndicatorsSignal.peek().get(indicatorId.toUpperCase())?.params ?? null
|
|
402
405
|
}
|
|
403
406
|
|
|
404
407
|
/**
|
|
405
408
|
* 清除所有主图指标
|
|
406
409
|
*/
|
|
407
410
|
clearMainIndicators(): void {
|
|
408
|
-
|
|
411
|
+
const map = this._mainIndicatorsSignal.peek()
|
|
412
|
+
for (const id of map.keys()) {
|
|
409
413
|
this.disableMainIndicatorRenderer(id)
|
|
410
414
|
}
|
|
411
|
-
this.
|
|
415
|
+
this._mainIndicatorsSignal.set(new Map())
|
|
412
416
|
this.scheduleDraw()
|
|
413
|
-
this.syncIndicatorsSignal()
|
|
414
417
|
}
|
|
415
418
|
|
|
416
419
|
/**
|
|
@@ -572,8 +575,9 @@ export class Chart {
|
|
|
572
575
|
* 更新调度器配置(内部方法)
|
|
573
576
|
*/
|
|
574
577
|
private updateIndicatorSchedulerConfig(indicatorId: string): void {
|
|
575
|
-
const
|
|
576
|
-
const
|
|
578
|
+
const entry = this._mainIndicatorsSignal.peek().get(indicatorId)
|
|
579
|
+
const isActive = entry !== undefined
|
|
580
|
+
const params = entry?.params ?? {}
|
|
577
581
|
|
|
578
582
|
switch (indicatorId) {
|
|
579
583
|
case 'MA':
|
|
@@ -653,7 +657,7 @@ export class Chart {
|
|
|
653
657
|
setActiveMainIndicators(indicators: string[]): void {
|
|
654
658
|
// 计算需要启用和禁用的指标
|
|
655
659
|
const newSet = new Set(indicators.map(i => i.toUpperCase()))
|
|
656
|
-
const currentSet = new Set(this.
|
|
660
|
+
const currentSet = new Set(this._mainIndicatorsSignal.peek().keys())
|
|
657
661
|
|
|
658
662
|
// 禁用不再激活的
|
|
659
663
|
for (const id of currentSet) {
|
|
@@ -709,14 +713,25 @@ export class Chart {
|
|
|
709
713
|
}
|
|
710
714
|
this.indicatorScheduler.setInvalidateCallback(() => this.scheduleDraw())
|
|
711
715
|
|
|
712
|
-
// 初始化副图管理器
|
|
713
|
-
this.subPaneManager = new SubPaneManager()
|
|
714
716
|
// 注册副图活跃列表提供者,调度器据此只计算启用的副图
|
|
715
717
|
this.indicatorScheduler.setActiveSubPaneProvider(
|
|
716
718
|
() => this.subPaneManager.getPaneIds(),
|
|
717
719
|
)
|
|
718
720
|
|
|
719
721
|
this.initPanes()
|
|
722
|
+
|
|
723
|
+
// dev: 主副图状态变更日志
|
|
724
|
+
if ((import.meta as any).env?.MODE !== 'production') {
|
|
725
|
+
this._indicatorsComputed.subscribe(() => {
|
|
726
|
+
const instances = this._indicatorsComputed.peek()
|
|
727
|
+
console.log('[Chart] indicators signal changed:', instances)
|
|
728
|
+
})
|
|
729
|
+
this._subPanesComputed.subscribe(() => {
|
|
730
|
+
const subPanes = this._subPanesComputed.peek()
|
|
731
|
+
console.log('[Chart] subPanes signal changed:', subPanes)
|
|
732
|
+
})
|
|
733
|
+
}
|
|
734
|
+
|
|
720
735
|
// 注册绘图主插件(负责绘制 shape,layer: 'main')
|
|
721
736
|
this.useRenderer(createDrawingRendererPlugin({ store: this.drawingStore }))
|
|
722
737
|
// 注册绘图标签插件(负责推送选中绘图的轴标签,layer: 'overlay')
|
|
@@ -929,7 +944,7 @@ export class Chart {
|
|
|
929
944
|
this.interaction.setKLinePositions(kLinePositions, range, kWidthPx)
|
|
930
945
|
|
|
931
946
|
// 4. 通知调度器当前活跃主图指标 + 获取价格范围
|
|
932
|
-
this.indicatorScheduler.setActiveMainIndicators(
|
|
947
|
+
this.indicatorScheduler.setActiveMainIndicators([...this._mainIndicatorsSignal.peek().keys()])
|
|
933
948
|
const mainIndicatorRange = useCachedFrame ? null : this.indicatorScheduler.getMainIndicatorPriceRange()
|
|
934
949
|
const hasCrosshair = this.interaction.getCrosshairIndex() !== null
|
|
935
950
|
|
|
@@ -1218,21 +1233,6 @@ export class Chart {
|
|
|
1218
1233
|
return this.zoomLevelCount
|
|
1219
1234
|
}
|
|
1220
1235
|
|
|
1221
|
-
/** 注册视口变化回调 */
|
|
1222
|
-
setOnViewportChange(cb: (viewport: Viewport) => void) {
|
|
1223
|
-
this.onViewportChange = cb
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
/** 注册 pane 布局回流回调 */
|
|
1227
|
-
setOnPaneLayoutChange(cb: (panes: PaneSpec[]) => void) {
|
|
1228
|
-
this.onPaneLayoutChange = cb
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
|
-
/** 注册数据变化回调 */
|
|
1232
|
-
setOnDataChange(cb: (data: KLineData[]) => void) {
|
|
1233
|
-
this.onDataChange = cb
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
1236
|
/** 获取所有 PaneRenderer */
|
|
1237
1237
|
getPaneRenderers(): PaneRenderer[] {
|
|
1238
1238
|
return this.paneRenderers
|
|
@@ -1410,9 +1410,8 @@ export class Chart {
|
|
|
1410
1410
|
ratios[id] = ratio
|
|
1411
1411
|
})
|
|
1412
1412
|
this._paneRatiosSignal.set(ratios)
|
|
1413
|
-
this.syncSubPanesSignal()
|
|
1414
1413
|
|
|
1415
|
-
this.
|
|
1414
|
+
this._paneLayoutSignal.set(this.getPaneLayoutSpecs())
|
|
1416
1415
|
}
|
|
1417
1416
|
|
|
1418
1417
|
private applyPaneLayoutSpecs(panes: PaneSpec[]): void {
|
|
@@ -1586,8 +1585,6 @@ export class Chart {
|
|
|
1586
1585
|
this.upsertPane({ id: paneId, ratio: this._internalPaneRatios.get(paneId) ?? 1, visible: true, role: 'indicator' })
|
|
1587
1586
|
|
|
1588
1587
|
const success = this.subPaneManager.create(this, paneId, indicatorId, params ?? this.getDefaultSubPaneParams(indicatorId))
|
|
1589
|
-
this.syncIndicatorsSignal()
|
|
1590
|
-
this.syncSubPanesSignal()
|
|
1591
1588
|
return success
|
|
1592
1589
|
}
|
|
1593
1590
|
|
|
@@ -1597,9 +1594,6 @@ export class Chart {
|
|
|
1597
1594
|
*/
|
|
1598
1595
|
removeSubPane(paneId: string): void {
|
|
1599
1596
|
this.subPaneManager.remove(this, paneId)
|
|
1600
|
-
this._internalPaneRatios.delete(paneId)
|
|
1601
|
-
this.syncIndicatorsSignal()
|
|
1602
|
-
this.syncSubPanesSignal()
|
|
1603
1597
|
}
|
|
1604
1598
|
|
|
1605
1599
|
/**
|
|
@@ -1610,8 +1604,6 @@ export class Chart {
|
|
|
1610
1604
|
*/
|
|
1611
1605
|
replaceSubPaneIndicator(paneId: string, newIndicatorId: SubIndicatorType, params?: Record<string, number | boolean | string>): void {
|
|
1612
1606
|
this.subPaneManager.replaceIndicator(this, paneId, newIndicatorId, params ?? this.getDefaultSubPaneParams(newIndicatorId))
|
|
1613
|
-
this.syncIndicatorsSignal()
|
|
1614
|
-
this.syncSubPanesSignal()
|
|
1615
1607
|
}
|
|
1616
1608
|
|
|
1617
1609
|
/**
|
|
@@ -1621,7 +1613,6 @@ export class Chart {
|
|
|
1621
1613
|
*/
|
|
1622
1614
|
updateSubPaneParams(paneId: string, params: Record<string, unknown>): void {
|
|
1623
1615
|
this.subPaneManager.updateParams(this, paneId, params)
|
|
1624
|
-
this.syncIndicatorsSignal()
|
|
1625
1616
|
}
|
|
1626
1617
|
|
|
1627
1618
|
/**
|
|
@@ -1643,8 +1634,6 @@ export class Chart {
|
|
|
1643
1634
|
|
|
1644
1635
|
// 更新布局,移除所有副图 pane
|
|
1645
1636
|
this.applyPaneLayoutSpecs(this.opt.panes.filter((spec) => !subPaneIds.includes(spec.id)))
|
|
1646
|
-
this.syncIndicatorsSignal()
|
|
1647
|
-
this.syncSubPanesSignal()
|
|
1648
1637
|
}
|
|
1649
1638
|
|
|
1650
1639
|
/**
|
|
@@ -1767,7 +1756,6 @@ export class Chart {
|
|
|
1767
1756
|
updateData(data: KLineData[]) {
|
|
1768
1757
|
this._internalData = data ?? []
|
|
1769
1758
|
this._dataSignal.set([...this._internalData])
|
|
1770
|
-
this.onDataChange?.(this._internalData)
|
|
1771
1759
|
|
|
1772
1760
|
// 重算 DOM scrollLeft 状态, 防止左右滚动超出数据长度范围
|
|
1773
1761
|
const container = this.dom.container
|
|
@@ -1834,13 +1822,16 @@ export class Chart {
|
|
|
1834
1822
|
|
|
1835
1823
|
/** 获取内容总宽度(用于外部 scroll-content 撑开 scrollWidth) */
|
|
1836
1824
|
getContentWidth(): number {
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
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)
|
|
1844
1835
|
}
|
|
1845
1836
|
|
|
1846
1837
|
|
|
@@ -1922,8 +1913,6 @@ export class Chart {
|
|
|
1922
1913
|
// 清理渲染器插件管理器(会调用所有 onUninstall)
|
|
1923
1914
|
this.rendererPluginManager.clear()
|
|
1924
1915
|
|
|
1925
|
-
this.onViewportChange = undefined
|
|
1926
|
-
this.onPaneLayoutChange = undefined
|
|
1927
1916
|
this.indicatorScheduler.destroy()
|
|
1928
1917
|
await this.pluginHost.destroy()
|
|
1929
1918
|
}
|
|
@@ -2241,7 +2230,18 @@ export class Chart {
|
|
|
2241
2230
|
|
|
2242
2231
|
this._internalViewport = vp
|
|
2243
2232
|
if (viewportChanged) {
|
|
2244
|
-
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
|
+
})
|
|
2245
2245
|
}
|
|
2246
2246
|
return vp
|
|
2247
2247
|
}
|
|
@@ -2263,11 +2263,10 @@ export class Chart {
|
|
|
2263
2263
|
|
|
2264
2264
|
private _dataSignal = createSignal<ReadonlyArray<KLineData>>([])
|
|
2265
2265
|
private _themeSignal = createSignal<'light' | 'dark'>('light')
|
|
2266
|
-
private _indicatorsSignal = createSignal<ReadonlyArray<IndicatorInstance>>([])
|
|
2267
|
-
private _subPanesSignal = createSignal<ReadonlyArray<SubPaneInfo>>([])
|
|
2268
2266
|
private _drawingToolSignal = createSignal<DrawingToolType | null>(null)
|
|
2269
2267
|
private _drawingsSignal = createSignal<ReadonlyArray<import('../plugin').DrawingObject>>([])
|
|
2270
2268
|
private _paneRatiosSignal = createSignal<Readonly<Record<string, number>>>({})
|
|
2269
|
+
private _paneLayoutSignal = createSignal<PaneSpec[]>([])
|
|
2271
2270
|
private _interactionSignal = createSignal<InteractionSnapshot>({
|
|
2272
2271
|
crosshairPos: null,
|
|
2273
2272
|
crosshairIndex: null,
|
|
@@ -2285,6 +2284,38 @@ export class Chart {
|
|
|
2285
2284
|
isHoveringRightAxis: false,
|
|
2286
2285
|
})
|
|
2287
2286
|
|
|
2287
|
+
private _indicatorsComputed = computed<ReadonlyArray<IndicatorInstance>>(() => {
|
|
2288
|
+
const mainIndicators: IndicatorInstance[] = [...this._mainIndicatorsSignal().entries()].map(([id, entry]) => ({
|
|
2289
|
+
id,
|
|
2290
|
+
definitionId: id,
|
|
2291
|
+
label: id,
|
|
2292
|
+
name: id,
|
|
2293
|
+
role: 'main' as const,
|
|
2294
|
+
params: { ...entry.params },
|
|
2295
|
+
}))
|
|
2296
|
+
|
|
2297
|
+
const subIndicators: IndicatorInstance[] = this.subPaneManager.entriesSignal().map(entry => ({
|
|
2298
|
+
id: entry.paneId,
|
|
2299
|
+
definitionId: entry.indicatorId,
|
|
2300
|
+
label: entry.indicatorId,
|
|
2301
|
+
name: entry.indicatorId,
|
|
2302
|
+
role: 'sub' as const,
|
|
2303
|
+
paneId: entry.paneId,
|
|
2304
|
+
params: { ...entry.params },
|
|
2305
|
+
}))
|
|
2306
|
+
|
|
2307
|
+
return [...mainIndicators, ...subIndicators]
|
|
2308
|
+
})
|
|
2309
|
+
private _subPanesComputed = computed<ReadonlyArray<SubPaneInfo>>(() => {
|
|
2310
|
+
const ratios = this._paneRatiosSignal()
|
|
2311
|
+
return this.subPaneManager.entriesSignal().map(entry => ({
|
|
2312
|
+
paneId: entry.paneId,
|
|
2313
|
+
indicatorId: entry.indicatorId,
|
|
2314
|
+
params: { ...entry.params },
|
|
2315
|
+
ratio: ratios[entry.paneId] ?? 1,
|
|
2316
|
+
}))
|
|
2317
|
+
})
|
|
2318
|
+
|
|
2288
2319
|
/** 视口状态信号 */
|
|
2289
2320
|
get viewport(): Signal<ViewportState> {
|
|
2290
2321
|
return this._viewportSignal
|
|
@@ -2300,14 +2331,14 @@ export class Chart {
|
|
|
2300
2331
|
return this._themeSignal
|
|
2301
2332
|
}
|
|
2302
2333
|
|
|
2303
|
-
/**
|
|
2304
|
-
get indicators():
|
|
2305
|
-
return this.
|
|
2334
|
+
/** 指标实例列表信号(派生信号,自动随主/副图状态更新) */
|
|
2335
|
+
get indicators(): Computed<ReadonlyArray<IndicatorInstance>> {
|
|
2336
|
+
return this._indicatorsComputed
|
|
2306
2337
|
}
|
|
2307
2338
|
|
|
2308
|
-
/**
|
|
2309
|
-
get subPanes():
|
|
2310
|
-
return this.
|
|
2339
|
+
/** 子图信息信号(派生信号,自动随副图条目/比例更新) */
|
|
2340
|
+
get subPanes(): Computed<ReadonlyArray<SubPaneInfo>> {
|
|
2341
|
+
return this._subPanesComputed
|
|
2311
2342
|
}
|
|
2312
2343
|
|
|
2313
2344
|
/** 当前绘图工具信号 */
|
|
@@ -2325,6 +2356,10 @@ export class Chart {
|
|
|
2325
2356
|
return this._paneRatiosSignal
|
|
2326
2357
|
}
|
|
2327
2358
|
|
|
2359
|
+
get paneLayout(): Signal<PaneSpec[]> {
|
|
2360
|
+
return this._paneLayoutSignal
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2328
2363
|
/** 交互状态信号 */
|
|
2329
2364
|
get interactionState(): Signal<InteractionSnapshot> {
|
|
2330
2365
|
return this._interactionSignal
|
|
@@ -2576,9 +2611,6 @@ export class Chart {
|
|
|
2576
2611
|
if (role === 'main') {
|
|
2577
2612
|
const success = this.enableMainIndicator(definitionId, params as Record<string, number | boolean | string>)
|
|
2578
2613
|
if (!success) return null
|
|
2579
|
-
|
|
2580
|
-
// 更新 indicators signal
|
|
2581
|
-
this.syncIndicatorsSignal()
|
|
2582
2614
|
return definitionId.toUpperCase()
|
|
2583
2615
|
} else {
|
|
2584
2616
|
// 副图指标
|
|
@@ -2589,10 +2621,6 @@ export class Chart {
|
|
|
2589
2621
|
params as Record<string, number | boolean | string>,
|
|
2590
2622
|
)
|
|
2591
2623
|
if (!success) return null
|
|
2592
|
-
|
|
2593
|
-
// 更新 signals
|
|
2594
|
-
this.syncIndicatorsSignal()
|
|
2595
|
-
this.syncSubPanesSignal()
|
|
2596
2624
|
return paneId
|
|
2597
2625
|
}
|
|
2598
2626
|
}
|
|
@@ -2605,25 +2633,18 @@ export class Chart {
|
|
|
2605
2633
|
removeIndicator(instanceId: string): boolean {
|
|
2606
2634
|
const id = instanceId.toUpperCase()
|
|
2607
2635
|
|
|
2608
|
-
//
|
|
2609
|
-
if (this.
|
|
2610
|
-
|
|
2611
|
-
if (success) {
|
|
2612
|
-
this.syncIndicatorsSignal()
|
|
2613
|
-
}
|
|
2614
|
-
return success
|
|
2636
|
+
// 先尝试作为主图指标移除
|
|
2637
|
+
if (this._mainIndicatorsSignal.peek().has(id)) {
|
|
2638
|
+
return this.disableMainIndicator(instanceId)
|
|
2615
2639
|
}
|
|
2616
2640
|
|
|
2617
|
-
//
|
|
2641
|
+
// 再尝试作为副图指标移除
|
|
2618
2642
|
const subPaneEntry = this.getSubPaneEntry(instanceId)
|
|
2619
2643
|
if (subPaneEntry) {
|
|
2620
2644
|
this.removeSubPane(instanceId)
|
|
2621
|
-
this.syncIndicatorsSignal()
|
|
2622
|
-
this.syncSubPanesSignal()
|
|
2623
2645
|
return true
|
|
2624
2646
|
}
|
|
2625
2647
|
|
|
2626
|
-
// 都没找到,返回 false
|
|
2627
2648
|
return false
|
|
2628
2649
|
}
|
|
2629
2650
|
|
|
@@ -2636,10 +2657,9 @@ export class Chart {
|
|
|
2636
2657
|
updateIndicatorParams(instanceId: string, params: Record<string, unknown>): boolean {
|
|
2637
2658
|
const id = instanceId.toUpperCase()
|
|
2638
2659
|
|
|
2639
|
-
//
|
|
2640
|
-
if (this.
|
|
2660
|
+
// 先尝试作为主图指标更新
|
|
2661
|
+
if (this._mainIndicatorsSignal.peek().has(id)) {
|
|
2641
2662
|
this.updateMainIndicatorParams(instanceId, params as Record<string, number | boolean | string>)
|
|
2642
|
-
this.syncIndicatorsSignal()
|
|
2643
2663
|
return true
|
|
2644
2664
|
}
|
|
2645
2665
|
|
|
@@ -2647,11 +2667,9 @@ export class Chart {
|
|
|
2647
2667
|
const subPaneEntry = this.getSubPaneEntry(instanceId)
|
|
2648
2668
|
if (subPaneEntry) {
|
|
2649
2669
|
this.updateSubPaneParams(instanceId, params)
|
|
2650
|
-
this.syncIndicatorsSignal()
|
|
2651
2670
|
return true
|
|
2652
2671
|
}
|
|
2653
2672
|
|
|
2654
|
-
// 都没找到
|
|
2655
2673
|
return false
|
|
2656
2674
|
}
|
|
2657
2675
|
|
|
@@ -2667,46 +2685,7 @@ export class Chart {
|
|
|
2667
2685
|
return false
|
|
2668
2686
|
}
|
|
2669
2687
|
|
|
2670
|
-
/**
|
|
2671
|
-
* 同步 indicators signal
|
|
2672
|
-
*/
|
|
2673
|
-
private syncIndicatorsSignal(): void {
|
|
2674
|
-
const mainIndicators: IndicatorInstance[] = this.getActiveMainIndicators().map(id => ({
|
|
2675
|
-
id,
|
|
2676
|
-
definitionId: id,
|
|
2677
|
-
label: id,
|
|
2678
|
-
name: id,
|
|
2679
|
-
role: 'main',
|
|
2680
|
-
params: this.getMainIndicatorParams(id) ?? {},
|
|
2681
|
-
}))
|
|
2682
|
-
|
|
2683
|
-
const subIndicators: IndicatorInstance[] = this.getSubPaneEntries().map(entry => ({
|
|
2684
|
-
id: entry.paneId,
|
|
2685
|
-
definitionId: entry.indicatorId,
|
|
2686
|
-
label: entry.indicatorId,
|
|
2687
|
-
name: entry.indicatorId,
|
|
2688
|
-
role: 'sub',
|
|
2689
|
-
paneId: entry.paneId,
|
|
2690
|
-
params: entry.params,
|
|
2691
|
-
}))
|
|
2692
2688
|
|
|
2693
|
-
this._indicatorsSignal.set([...mainIndicators, ...subIndicators])
|
|
2694
|
-
}
|
|
2695
|
-
|
|
2696
|
-
/**
|
|
2697
|
-
* 同步 sub panes signal
|
|
2698
|
-
*/
|
|
2699
|
-
private syncSubPanesSignal(): void {
|
|
2700
|
-
const entries = this.getSubPaneEntries()
|
|
2701
|
-
const subPanes: SubPaneInfo[] = entries.map(entry => ({
|
|
2702
|
-
paneId: entry.paneId,
|
|
2703
|
-
indicatorId: entry.indicatorId,
|
|
2704
|
-
params: entry.params,
|
|
2705
|
-
ratio: this._internalPaneRatios.get(entry.paneId) ?? 1,
|
|
2706
|
-
}))
|
|
2707
|
-
|
|
2708
|
-
this._subPanesSignal.set(subPanes)
|
|
2709
|
-
}
|
|
2710
2689
|
|
|
2711
2690
|
// ---------- Sub Panes ----------
|
|
2712
2691
|
|
|
@@ -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
|
/** 更新用户设置 */
|