@363045841yyt/klinechart-core 0.9.0-alpha.2 → 0.9.0-alpha.4
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/dist/controllers/createChartController.d.ts.map +1 -1
- package/dist/controllers/createChartController.js +9 -8
- package/dist/controllers/createChartController.js.map +1 -1
- package/dist/controllers/index.d.ts +1 -0
- package/dist/controllers/index.d.ts.map +1 -1
- package/dist/controllers/index.js.map +1 -1
- package/dist/controllers/types.d.ts +5 -0
- package/dist/controllers/types.d.ts.map +1 -1
- package/dist/data/binance.d.ts +1 -1
- package/dist/data/binance.d.ts.map +1 -1
- package/dist/data/binance.js +1 -1
- package/dist/data/binance.js.map +1 -1
- package/dist/data/timeShareBuffer.d.ts +3 -0
- package/dist/data/timeShareBuffer.d.ts.map +1 -1
- package/dist/data/timeShareBuffer.js +13 -0
- package/dist/data/timeShareBuffer.js.map +1 -1
- package/dist/engine/chart.d.ts +9 -9
- package/dist/engine/chart.d.ts.map +1 -1
- package/dist/engine/chart.js +87 -101
- package/dist/engine/chart.js.map +1 -1
- package/dist/engine/chartTypes.d.ts +5 -0
- package/dist/engine/chartTypes.d.ts.map +1 -1
- package/dist/engine/controller/interaction.d.ts +4 -2
- package/dist/engine/controller/interaction.d.ts.map +1 -1
- package/dist/engine/controller/interaction.js +18 -10
- package/dist/engine/controller/interaction.js.map +1 -1
- package/dist/engine/data/chartDataManager.d.ts +14 -18
- package/dist/engine/data/chartDataManager.d.ts.map +1 -1
- package/dist/engine/data/chartDataManager.js +55 -37
- package/dist/engine/data/chartDataManager.js.map +1 -1
- package/dist/engine/data/incrementalLoadHint.d.ts +2 -4
- package/dist/engine/data/incrementalLoadHint.d.ts.map +1 -1
- package/dist/engine/data/incrementalLoadHint.js +5 -2
- package/dist/engine/data/incrementalLoadHint.js.map +1 -1
- package/dist/engine/data/scrollCompensator.d.ts +3 -13
- package/dist/engine/data/scrollCompensator.d.ts.map +1 -1
- package/dist/engine/data/scrollCompensator.js +13 -14
- package/dist/engine/data/scrollCompensator.js.map +1 -1
- package/dist/engine/indicators/chartIndicatorManager.d.ts +16 -17
- package/dist/engine/indicators/chartIndicatorManager.d.ts.map +1 -1
- package/dist/engine/indicators/chartIndicatorManager.js +37 -37
- package/dist/engine/indicators/chartIndicatorManager.js.map +1 -1
- package/dist/engine/layout/chartPaneLayout.d.ts +11 -7
- package/dist/engine/layout/chartPaneLayout.d.ts.map +1 -1
- package/dist/engine/layout/chartPaneLayout.js +58 -16
- package/dist/engine/layout/chartPaneLayout.js.map +1 -1
- package/dist/engine/modes/timeShareMath.d.ts +56 -0
- package/dist/engine/modes/timeShareMath.d.ts.map +1 -0
- package/dist/engine/modes/timeShareMath.js +90 -0
- package/dist/engine/modes/timeShareMath.js.map +1 -0
- package/dist/engine/modes/timeShareMode.d.ts +5 -0
- package/dist/engine/modes/timeShareMode.d.ts.map +1 -1
- package/dist/engine/modes/timeShareMode.js +27 -27
- package/dist/engine/modes/timeShareMode.js.map +1 -1
- package/dist/engine/paneRenderer.d.ts +2 -0
- package/dist/engine/paneRenderer.d.ts.map +1 -1
- package/dist/engine/paneRenderer.js +15 -5
- package/dist/engine/paneRenderer.js.map +1 -1
- package/dist/engine/render/chartRenderer.d.ts +28 -22
- package/dist/engine/render/chartRenderer.d.ts.map +1 -1
- package/dist/engine/render/chartRenderer.js +143 -85
- package/dist/engine/render/chartRenderer.js.map +1 -1
- package/dist/engine/render/layers/leftYAxisLayer.d.ts +9 -2
- package/dist/engine/render/layers/leftYAxisLayer.d.ts.map +1 -1
- package/dist/engine/render/layers/leftYAxisLayer.js +11 -2
- package/dist/engine/render/layers/leftYAxisLayer.js.map +1 -1
- package/dist/engine/render/layers/mainIndicatorLegendLayer.d.ts +6 -4
- package/dist/engine/render/layers/mainIndicatorLegendLayer.d.ts.map +1 -1
- package/dist/engine/render/layers/mainIndicatorLegendLayer.js +6 -9
- package/dist/engine/render/layers/mainIndicatorLegendLayer.js.map +1 -1
- package/dist/engine/render/layers/mainIndicatorLegendLayer.test.d.ts +2 -0
- package/dist/engine/render/layers/mainIndicatorLegendLayer.test.d.ts.map +1 -0
- package/dist/engine/render/layers/mainIndicatorLegendLayer.test.js +19 -0
- package/dist/engine/render/layers/mainIndicatorLegendLayer.test.js.map +1 -0
- package/dist/engine/render/layers/yAxisLayer.d.ts +9 -2
- package/dist/engine/render/layers/yAxisLayer.d.ts.map +1 -1
- package/dist/engine/render/layers/yAxisLayer.js +11 -2
- package/dist/engine/render/layers/yAxisLayer.js.map +1 -1
- package/dist/engine/renderers/Indicator/mainIndicatorLegend.d.ts +7 -3
- package/dist/engine/renderers/Indicator/mainIndicatorLegend.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/mainIndicatorLegend.js +234 -330
- package/dist/engine/renderers/Indicator/mainIndicatorLegend.js.map +1 -1
- package/dist/engine/renderers/Indicator/mainIndicatorLegendContext.d.ts +73 -0
- package/dist/engine/renderers/Indicator/mainIndicatorLegendContext.d.ts.map +1 -0
- package/dist/engine/renderers/Indicator/mainIndicatorLegendContext.js +182 -0
- package/dist/engine/renderers/Indicator/mainIndicatorLegendContext.js.map +1 -0
- package/dist/engine/renderers/leftYAxis.d.ts +15 -2
- package/dist/engine/renderers/leftYAxis.d.ts.map +1 -1
- package/dist/engine/renderers/leftYAxis.js +56 -16
- package/dist/engine/renderers/leftYAxis.js.map +1 -1
- package/dist/engine/renderers/timeShare.d.ts.map +1 -1
- package/dist/engine/renderers/timeShare.js +20 -9
- package/dist/engine/renderers/timeShare.js.map +1 -1
- package/dist/engine/renderers/yAxis.d.ts +16 -6
- package/dist/engine/renderers/yAxis.d.ts.map +1 -1
- package/dist/engine/renderers/yAxis.js +49 -16
- package/dist/engine/renderers/yAxis.js.map +1 -1
- package/dist/engine/state/viewportState.d.ts +4 -1
- package/dist/engine/state/viewportState.d.ts.map +1 -1
- package/dist/engine/state/viewportState.js +40 -8
- package/dist/engine/state/viewportState.js.map +1 -1
- package/dist/engine/subPaneManager.d.ts +5 -3
- package/dist/engine/subPaneManager.d.ts.map +1 -1
- package/dist/engine/subPaneManager.js +3 -2
- package/dist/engine/subPaneManager.js.map +1 -1
- package/dist/engine/utils/chartZoomController.d.ts +9 -7
- package/dist/engine/utils/chartZoomController.d.ts.map +1 -1
- package/dist/engine/utils/chartZoomController.js +13 -12
- package/dist/engine/utils/chartZoomController.js.map +1 -1
- package/dist/engine/viewport/chartViewportManager.d.ts +5 -16
- package/dist/engine/viewport/chartViewportManager.d.ts.map +1 -1
- package/dist/engine/viewport/chartViewportManager.js +7 -48
- package/dist/engine/viewport/chartViewportManager.js.map +1 -1
- package/dist/engine/viewport/viewport.d.ts +14 -0
- package/dist/engine/viewport/viewport.d.ts.map +1 -1
- package/dist/engine/viewport/viewport.js +10 -0
- package/dist/engine/viewport/viewport.js.map +1 -1
- package/dist/foundation/config/chartSettings.d.ts +0 -5
- package/dist/foundation/config/chartSettings.d.ts.map +1 -1
- package/dist/foundation/config/chartSettings.js +14 -0
- package/dist/foundation/config/chartSettings.js.map +1 -1
- package/dist/foundation/plugin/rendererPluginManager.test.js +15 -0
- package/dist/foundation/plugin/rendererPluginManager.test.js.map +1 -1
- package/dist/foundation/plugin/types.d.ts +3 -0
- package/dist/foundation/plugin/types.d.ts.map +1 -1
- package/dist/foundation/plugin/types.js.map +1 -1
- package/dist/foundation/reactivity/frameTransaction.d.ts +11 -0
- package/dist/foundation/reactivity/frameTransaction.d.ts.map +1 -1
- package/dist/foundation/reactivity/frameTransaction.js +17 -2
- package/dist/foundation/reactivity/frameTransaction.js.map +1 -1
- package/dist/foundation/utils/kLineDraw/axis.d.ts +3 -0
- package/dist/foundation/utils/kLineDraw/axis.d.ts.map +1 -1
- package/dist/foundation/utils/kLineDraw/axis.js +40 -9
- package/dist/foundation/utils/kLineDraw/axis.js.map +1 -1
- package/dist/foundation/utils/sessionTimeLabels.d.ts +59 -0
- package/dist/foundation/utils/sessionTimeLabels.d.ts.map +1 -0
- package/dist/foundation/utils/sessionTimeLabels.js +170 -0
- package/dist/foundation/utils/sessionTimeLabels.js.map +1 -0
- package/dist/foundation/utils/timeShareAxisLabels.d.ts +27 -0
- package/dist/foundation/utils/timeShareAxisLabels.d.ts.map +1 -0
- package/dist/foundation/utils/timeShareAxisLabels.js +45 -0
- package/dist/foundation/utils/timeShareAxisLabels.js.map +1 -0
- package/dist/rendering/render/Renderer.d.ts +58 -74
- package/dist/rendering/render/Renderer.d.ts.map +1 -1
- package/dist/rendering/render/Renderer.js +8 -18
- package/dist/rendering/render/Renderer.js.map +1 -1
- package/dist/rendering/render/createWebGPURenderer.d.ts.map +1 -1
- package/dist/rendering/render/createWebGPURenderer.js +30 -17
- package/dist/rendering/render/createWebGPURenderer.js.map +1 -1
- package/dist/rendering/render/createWebGPUSurfaceBackend.js +3 -3
- package/dist/rendering/render/createWebGPUSurfaceBackend.js.map +1 -1
- package/dist/rendering/render/physicalLine.d.ts +4 -0
- package/dist/rendering/render/physicalLine.d.ts.map +1 -0
- package/dist/rendering/render/physicalLine.js +37 -0
- package/dist/rendering/render/physicalLine.js.map +1 -0
- package/dist/rendering/render/physicalRegion.d.ts +14 -0
- package/dist/rendering/render/physicalRegion.d.ts.map +1 -0
- package/dist/rendering/render/physicalRegion.js +15 -0
- package/dist/rendering/render/physicalRegion.js.map +1 -0
- package/dist/rendering/scene/createLayerFromPlugin.d.ts.map +1 -1
- package/dist/rendering/scene/createLayerFromPlugin.js +8 -3
- package/dist/rendering/scene/createLayerFromPlugin.js.map +1 -1
- package/dist/rendering/scene/createScene.d.ts.map +1 -1
- package/dist/rendering/scene/createScene.js +4 -3
- package/dist/rendering/scene/createScene.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/__tests__/stateKernel.test.ts +1 -0
- package/src/controllers/createChartController.ts +9 -8
- package/src/controllers/index.ts +11 -0
- package/src/controllers/types.ts +7 -0
- package/src/data/__tests__/timeShareBuffer.test.ts +45 -0
- package/src/data/binance.ts +1 -1
- package/src/data/timeShareBuffer.ts +15 -0
- package/src/engine/__tests__/chart.dpr.test.ts +31 -2
- package/src/engine/__tests__/paneRenderer.resize.test.ts +52 -54
- package/src/engine/__tests__/subPaneManager.test.ts +5 -3
- package/src/engine/chart.ts +105 -115
- package/src/engine/chartTypes.ts +5 -0
- package/src/engine/controller/__tests__/interaction.dpr.test.ts +39 -2
- package/src/engine/controller/interaction.ts +18 -10
- package/src/engine/data/__tests__/chartDataManager.incrementalLoad.test.ts +37 -17
- package/src/engine/data/__tests__/scrollCompensator.geometry.test.ts +87 -75
- package/src/engine/data/chartDataManager.ts +64 -59
- package/src/engine/data/incrementalLoadHint.ts +8 -7
- package/src/engine/data/scrollCompensator.ts +16 -24
- package/src/engine/indicators/__tests__/chartIndicatorManager.test.ts +18 -26
- package/src/engine/indicators/chartIndicatorManager.ts +62 -62
- package/src/engine/layout/chartPaneLayout.ts +73 -21
- package/src/engine/modes/__tests__/timeShareMath.test.ts +185 -0
- package/src/engine/modes/__tests__/timeShareMode.test.ts +81 -0
- package/src/engine/modes/timeShareMath.ts +150 -0
- package/src/engine/modes/timeShareMode.ts +35 -28
- package/src/engine/paneRenderer.ts +17 -10
- package/src/engine/render/chartRenderer.ts +214 -145
- package/src/engine/render/layers/leftYAxisLayer.ts +29 -7
- package/src/engine/render/layers/mainIndicatorLegendLayer.test.ts +26 -0
- package/src/engine/render/layers/mainIndicatorLegendLayer.ts +13 -10
- package/src/engine/render/layers/yAxisLayer.ts +29 -7
- package/src/engine/renderers/Indicator/mainIndicatorLegend.ts +270 -403
- package/src/engine/renderers/Indicator/mainIndicatorLegendContext.ts +290 -0
- package/src/engine/renderers/__tests__/mainIndicatorLegend.renderer.test.ts +73 -2
- package/src/engine/renderers/__tests__/yAxis.renderer.test.ts +10 -6
- package/src/engine/renderers/leftYAxis.ts +60 -18
- package/src/engine/renderers/timeShare.ts +25 -9
- package/src/engine/renderers/yAxis.ts +57 -22
- package/src/engine/state/__tests__/settingsState.test.ts +9 -0
- package/src/engine/state/viewportState.ts +44 -8
- package/src/engine/subPaneManager.ts +8 -5
- package/src/engine/utils/chartZoomController.ts +22 -19
- package/src/engine/viewport/__tests__/visibleRange.clamp.test.ts +50 -0
- package/src/engine/viewport/chartViewportManager.ts +9 -60
- package/src/engine/viewport/viewport.ts +14 -0
- package/src/foundation/config/chartSettings.ts +13 -0
- package/src/foundation/plugin/rendererPluginManager.test.ts +18 -0
- package/src/foundation/plugin/types.ts +3 -0
- package/src/foundation/reactivity/frameTransaction.ts +20 -6
- package/src/foundation/utils/__tests__/sessionTimeLabels.test.ts +91 -0
- package/src/foundation/utils/kLineDraw/axis.ts +50 -9
- package/src/foundation/utils/sessionTimeLabels.ts +237 -0
- package/src/foundation/utils/timeShareAxisLabels.ts +94 -0
- package/src/rendering/render/Renderer.ts +69 -85
- package/src/rendering/render/__tests__/physicalLine.test.ts +51 -0
- package/src/rendering/render/__tests__/physicalRegion.test.ts +23 -0
- package/src/rendering/render/__tests__/webgpuRenderer.test.ts +31 -1
- package/src/rendering/render/__tests__/webgpuSurfaceBackend.test.ts +11 -0
- package/src/rendering/render/createWebGPURenderer.ts +30 -17
- package/src/rendering/render/createWebGPUSurfaceBackend.ts +3 -3
- package/src/rendering/render/physicalLine.ts +45 -0
- package/src/rendering/render/physicalRegion.ts +32 -0
- package/src/rendering/scene/createLayerFromPlugin.ts +8 -3
- package/src/rendering/scene/createScene.ts +4 -3
- package/src/version.ts +1 -1
|
@@ -1,37 +1,37 @@
|
|
|
1
1
|
import type { RendererPlugin, RenderContext } from '../../foundation/plugin/index'
|
|
2
2
|
import { RENDERER_PRIORITY, GLOBAL_PANE_ID } from '../../foundation/plugin/index'
|
|
3
3
|
import { resolveThemeColors } from '../../foundation/tokens/index'
|
|
4
|
-
import type { KLineData } from '../../foundation/types/price'
|
|
5
4
|
import { drawCrosshairPriceLabel, drawAxisPriceLabel } from '../../foundation/utils/kLineDraw/axis'
|
|
6
5
|
import { roundToPhysicalPixel } from '../../foundation/utils/pixelAlign'
|
|
7
6
|
import { getFont, setCanvasFont } from '../../foundation/tokens/fonts'
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
* 创建 Y 轴渲染器插件
|
|
11
|
-
* 按 pane capability 决定是否绘制刻度与价格标签
|
|
12
|
-
*/
|
|
13
|
-
export function createYAxisRendererPlugin(options: {
|
|
8
|
+
type YAxisOptions = {
|
|
14
9
|
axisWidth: number
|
|
15
10
|
yPaddingPx: number
|
|
16
11
|
getCrosshair?: () => { y: number; price: number; activePaneId: string | null } | null
|
|
17
|
-
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function shouldShowRightAxis(period: string, settings: RenderContext['settings']): boolean {
|
|
15
|
+
if (period === 'timeshare') return true
|
|
16
|
+
const rightType = settings?.rightAxisType as string | undefined
|
|
17
|
+
return rightType !== 'none'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Y 轴静态层:刻度 + 价格范围带,画到 yAxisCtx(main 级刷新)
|
|
22
|
+
*/
|
|
23
|
+
export function createYAxisStaticRendererPlugin(options: YAxisOptions): RendererPlugin {
|
|
18
24
|
return {
|
|
19
25
|
name: 'yAxis',
|
|
20
|
-
version: '
|
|
21
|
-
description: 'Y
|
|
22
|
-
debugName: 'Y
|
|
26
|
+
version: '2.0.0',
|
|
27
|
+
description: 'Y轴价格刻度渲染器(静态)',
|
|
28
|
+
debugName: 'Y轴刻度',
|
|
23
29
|
paneId: GLOBAL_PANE_ID,
|
|
24
30
|
priority: RENDERER_PRIORITY.SYSTEM_YAXIS,
|
|
25
|
-
layer: 'overlay',
|
|
26
31
|
|
|
27
32
|
draw(context: RenderContext) {
|
|
28
|
-
const { ctx, pane, dpr, yAxisCtx,
|
|
29
|
-
|
|
30
|
-
// 分时模式始终显示右轴,不受设置约束
|
|
31
|
-
if (period !== 'timeshare') {
|
|
32
|
-
const rightType = context.settings?.rightAxisType as string | undefined
|
|
33
|
-
if (rightType === 'none') return
|
|
34
|
-
}
|
|
33
|
+
const { ctx, pane, dpr, yAxisCtx, period } = context
|
|
34
|
+
if (!shouldShowRightAxis(period, context.settings)) return
|
|
35
35
|
|
|
36
36
|
const tokenColors = resolveThemeColors(
|
|
37
37
|
context.theme,
|
|
@@ -39,11 +39,8 @@ export function createYAxisRendererPlugin(options: {
|
|
|
39
39
|
context.colorPresetSettings,
|
|
40
40
|
)
|
|
41
41
|
const scaleType = pane.yAxis.getScaleType()
|
|
42
|
-
|
|
43
42
|
const targetCtx = yAxisCtx || ctx
|
|
44
43
|
const axisWidth = yAxisCtx?.canvas ? yAxisCtx.canvas.width / dpr : options.axisWidth
|
|
45
|
-
const displayRange = pane.yAxis.getDisplayRange(pane.priceRange)
|
|
46
|
-
|
|
47
44
|
const isPercent = scaleType === 'percent' && pane.role === 'price'
|
|
48
45
|
|
|
49
46
|
if (pane.capabilities.showPriceAxisTicks && context.yAxisTicks) {
|
|
@@ -70,7 +67,7 @@ export function createYAxisRendererPlugin(options: {
|
|
|
70
67
|
}
|
|
71
68
|
}
|
|
72
69
|
|
|
73
|
-
//
|
|
70
|
+
// 价格范围带(先于标签,使标签覆盖在范围带之上)
|
|
74
71
|
if (context.yAxisRanges && pane.role === 'price') {
|
|
75
72
|
for (const range of context.yAxisRanges) {
|
|
76
73
|
const topY = range.topY + pane.top
|
|
@@ -83,6 +80,36 @@ export function createYAxisRendererPlugin(options: {
|
|
|
83
80
|
targetCtx.restore()
|
|
84
81
|
}
|
|
85
82
|
}
|
|
83
|
+
},
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Y 轴动态层:yAxisLabels + 十字线价签,画到 yAxisOverlayCtx(overlay 级刷新)
|
|
89
|
+
*/
|
|
90
|
+
export function createYAxisOverlayRendererPlugin(options: YAxisOptions): RendererPlugin {
|
|
91
|
+
return {
|
|
92
|
+
name: 'yAxisOverlay',
|
|
93
|
+
version: '2.0.0',
|
|
94
|
+
description: 'Y轴动态标签渲染器',
|
|
95
|
+
debugName: 'Y轴标签',
|
|
96
|
+
paneId: GLOBAL_PANE_ID,
|
|
97
|
+
priority: RENDERER_PRIORITY.SYSTEM_YAXIS + 1,
|
|
98
|
+
layer: 'overlay',
|
|
99
|
+
|
|
100
|
+
draw(context: RenderContext) {
|
|
101
|
+
const { pane, dpr, yAxisOverlayCtx, yAxisCtx, period } = context
|
|
102
|
+
if (!shouldShowRightAxis(period, context.settings)) return
|
|
103
|
+
|
|
104
|
+
const targetCtx = yAxisOverlayCtx ?? yAxisCtx
|
|
105
|
+
if (!targetCtx) return
|
|
106
|
+
|
|
107
|
+
const axisWidth = targetCtx.canvas ? targetCtx.canvas.width / dpr : options.axisWidth
|
|
108
|
+
targetCtx.clearRect(0, 0, axisWidth, pane.height)
|
|
109
|
+
|
|
110
|
+
const scaleType = pane.yAxis.getScaleType()
|
|
111
|
+
const displayRange = pane.yAxis.getDisplayRange(pane.priceRange)
|
|
112
|
+
const isPercent = scaleType === 'percent' && pane.role === 'price'
|
|
86
113
|
|
|
87
114
|
// 绘制来自 yAxisLabels 的标签(最新价格、极值点、绘图锚点等)
|
|
88
115
|
if (context.yAxisLabels && pane.role === 'price') {
|
|
@@ -151,3 +178,11 @@ export function createYAxisRendererPlugin(options: {
|
|
|
151
178
|
},
|
|
152
179
|
}
|
|
153
180
|
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* @deprecated 使用 createYAxisStaticRendererPlugin + createYAxisOverlayRendererPlugin
|
|
184
|
+
* 保留兼容:静态+动态合画到 yAxisCtx
|
|
185
|
+
*/
|
|
186
|
+
export function createYAxisRendererPlugin(options: YAxisOptions): RendererPlugin {
|
|
187
|
+
return createYAxisStaticRendererPlugin(options)
|
|
188
|
+
}
|
|
@@ -43,4 +43,13 @@ describe('settingsState', () => {
|
|
|
43
43
|
;(snap as { showGridLines?: boolean }).showGridLines = false
|
|
44
44
|
}).toThrow()
|
|
45
45
|
})
|
|
46
|
+
|
|
47
|
+
it('preserves extension keys like preClose through resolve/patch', () => {
|
|
48
|
+
expect(resolveSettings({ preClose: 12.34 }).preClose).toBe(12.34)
|
|
49
|
+
const s = createSettingsState({ preClose: 12.34 })
|
|
50
|
+
expect(s.readonly.settings.peek().preClose).toBe(12.34)
|
|
51
|
+
s.actions.patch({ showGridLines: false })
|
|
52
|
+
expect(s.readonly.settings.peek().preClose).toBe(12.34)
|
|
53
|
+
expect(s.readonly.settings.peek().showGridLines).toBe(false)
|
|
54
|
+
})
|
|
46
55
|
})
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from '../../foundation/reactivity/signal'
|
|
8
8
|
import type { Viewport, ViewportState } from '../chartTypes'
|
|
9
9
|
import type { VisibleRange } from '../layout/pane'
|
|
10
|
-
import { getVisibleRange } from '../viewport/viewport'
|
|
10
|
+
import { clampVisibleRange, getVisibleRange } from '../viewport/viewport'
|
|
11
11
|
import {
|
|
12
12
|
computeLeftLoadBufferWidth as pureLeftBuffer,
|
|
13
13
|
computeContentWidth as pureContentWidth,
|
|
@@ -197,8 +197,8 @@ export function createViewportState(signalDeps: ViewportSignalDeps) {
|
|
|
197
197
|
return vp
|
|
198
198
|
})
|
|
199
199
|
|
|
200
|
-
let
|
|
201
|
-
const
|
|
200
|
+
let _cachedRawVisibleRange: VisibleRange | null = null
|
|
201
|
+
const cachedRawVisibleRange = computed<VisibleRange>(() => {
|
|
202
202
|
const vp = cachedViewport()
|
|
203
203
|
const opts = signalDeps.options$()
|
|
204
204
|
const vr = getVisibleRange(
|
|
@@ -209,13 +209,32 @@ export function createViewportState(signalDeps: ViewportSignalDeps) {
|
|
|
209
209
|
signalDeps.dataLength$(),
|
|
210
210
|
vp.dpr,
|
|
211
211
|
)
|
|
212
|
-
if (
|
|
213
|
-
|
|
212
|
+
if (
|
|
213
|
+
_cachedRawVisibleRange &&
|
|
214
|
+
_cachedRawVisibleRange.start === vr.start &&
|
|
215
|
+
_cachedRawVisibleRange.end === vr.end
|
|
216
|
+
) {
|
|
217
|
+
return _cachedRawVisibleRange
|
|
214
218
|
}
|
|
215
|
-
|
|
219
|
+
_cachedRawVisibleRange = vr
|
|
216
220
|
return vr
|
|
217
221
|
})
|
|
218
222
|
|
|
223
|
+
// 可索引可见区间(start>=0)— 绘制 / hit-test / 指标 / ViewportState 的 SSOT
|
|
224
|
+
let _cachedVisibleRange: VisibleRange | null = null
|
|
225
|
+
const cachedVisibleRange = computed<VisibleRange>(() => {
|
|
226
|
+
const clamped = clampVisibleRange(cachedRawVisibleRange())
|
|
227
|
+
if (
|
|
228
|
+
_cachedVisibleRange &&
|
|
229
|
+
_cachedVisibleRange.start === clamped.start &&
|
|
230
|
+
_cachedVisibleRange.end === clamped.end
|
|
231
|
+
) {
|
|
232
|
+
return _cachedVisibleRange
|
|
233
|
+
}
|
|
234
|
+
_cachedVisibleRange = clamped
|
|
235
|
+
return clamped
|
|
236
|
+
})
|
|
237
|
+
|
|
219
238
|
let _cachedViewportState: ViewportState | null = null
|
|
220
239
|
const cachedViewportState = computed<ViewportState>(() => {
|
|
221
240
|
const vp = cachedViewport()
|
|
@@ -355,6 +374,9 @@ export function createViewportState(signalDeps: ViewportSignalDeps) {
|
|
|
355
374
|
scrollLeftLogical,
|
|
356
375
|
kGap,
|
|
357
376
|
viewport: cachedViewport,
|
|
377
|
+
/** raw:含左右扩窗,start 可能为 -1(增量加载左缘检测) */
|
|
378
|
+
rawVisibleRange: cachedRawVisibleRange,
|
|
379
|
+
/** clamped:start>=0,绘制 / hit-test / 指标 / ViewportState 的 SSOT */
|
|
358
380
|
visibleRange: cachedVisibleRange,
|
|
359
381
|
viewportState: cachedViewportState,
|
|
360
382
|
}
|
|
@@ -414,7 +436,7 @@ export function createViewportState(signalDeps: ViewportSignalDeps) {
|
|
|
414
436
|
/**
|
|
415
437
|
* 初始化视口状态。
|
|
416
438
|
*
|
|
417
|
-
* @remarks 从 DOM
|
|
439
|
+
* @remarks 从 DOM 容器读取首帧尺寸与 scrollLeft,并挂载 canvas 尺寸同步 effect。
|
|
418
440
|
* 重复调用安全(仅首次生效)。
|
|
419
441
|
*/
|
|
420
442
|
init() {
|
|
@@ -423,7 +445,21 @@ export function createViewportState(signalDeps: ViewportSignalDeps) {
|
|
|
423
445
|
const container = _getDom().container
|
|
424
446
|
if (!container) return
|
|
425
447
|
signals.initialized.set(true)
|
|
426
|
-
|
|
448
|
+
// 首帧尺寸写入 kernel,避免 paint 路径读 DOM fallback
|
|
449
|
+
const w = Math.max(0, Math.round(container.clientWidth))
|
|
450
|
+
const h = Math.max(0, Math.round(container.clientHeight))
|
|
451
|
+
batch(() => {
|
|
452
|
+
if (w > 0 && h > 0) {
|
|
453
|
+
signals.viewWidth.set(w)
|
|
454
|
+
signals.viewHeight.set(h)
|
|
455
|
+
}
|
|
456
|
+
// 与 resize 一致:首帧 scrollLeft 为 0 时落到 viewWidth
|
|
457
|
+
if (container.scrollLeft === 0 && w > 0) {
|
|
458
|
+
signals.requestedScrollLeft.set(w)
|
|
459
|
+
} else {
|
|
460
|
+
setRequestedScrollLeft(container.scrollLeft)
|
|
461
|
+
}
|
|
462
|
+
})
|
|
427
463
|
setupCanvasSync()
|
|
428
464
|
},
|
|
429
465
|
},
|
|
@@ -44,9 +44,11 @@ export interface SubPaneContext {
|
|
|
44
44
|
removeRenderer: (name: string) => void
|
|
45
45
|
setRendererEnabled: (name: string, enabled: boolean) => void
|
|
46
46
|
updateRendererConfig: (name: string, config: Record<string, unknown>) => void
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
getOption: () => {
|
|
48
|
+
rightAxisWidth: number
|
|
49
|
+
priceLabelWidth?: number
|
|
50
|
+
yPaddingPx: number
|
|
51
|
+
}
|
|
50
52
|
getCrosshairPos: () => { x: number; y: number } | null
|
|
51
53
|
getCrosshairPrice: () => number | null
|
|
52
54
|
getActivePaneId: () => string | null
|
|
@@ -203,7 +205,8 @@ export class SubPaneManager {
|
|
|
203
205
|
return
|
|
204
206
|
}
|
|
205
207
|
const definition = ctx.getIndicatorScheduler().getIndicatorMetadata(entry.indicatorId)
|
|
206
|
-
const
|
|
208
|
+
const opt = ctx.getOption()
|
|
209
|
+
const axisWidth = opt.rightAxisWidth + (opt.priceLabelWidth ?? 60)
|
|
207
210
|
const getCrosshair = () => {
|
|
208
211
|
const pos = ctx.getCrosshairPos()
|
|
209
212
|
const price = ctx.getCrosshairPrice()
|
|
@@ -213,7 +216,7 @@ export class SubPaneManager {
|
|
|
213
216
|
const options = {
|
|
214
217
|
axisWidth,
|
|
215
218
|
paneId: entry.paneId,
|
|
216
|
-
yPaddingPx:
|
|
219
|
+
yPaddingPx: opt.yPaddingPx,
|
|
217
220
|
getCrosshair,
|
|
218
221
|
}
|
|
219
222
|
const plugin = definition?.scaleRendererFactory
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import { computeZoom, deriveKGap } from './zoom'
|
|
2
|
+
import type { ReadonlySignal } from '../../foundation/reactivity/signal'
|
|
3
|
+
import type { OptionsStateModule } from '../state/optionsState'
|
|
2
4
|
import type { ZoomStateModule } from '../state/zoomState'
|
|
5
|
+
import type { ViewportStateModule } from '../state/viewportState'
|
|
3
6
|
|
|
4
7
|
export interface ZoomDependencies {
|
|
5
|
-
|
|
6
|
-
|
|
8
|
+
/** scroll / dpr 几何,读写走 kernel.viewport */
|
|
9
|
+
viewport: ViewportStateModule
|
|
10
|
+
/** min/max kWidth / zoomLevelCount SSOT */
|
|
11
|
+
options: OptionsStateModule
|
|
12
|
+
/** 当前周期(kGap 推导) */
|
|
13
|
+
period$: ReadonlySignal<string>
|
|
7
14
|
getClientWidth: () => number
|
|
8
15
|
getDataLength: () => number
|
|
9
16
|
getPlotWidth: () => number
|
|
10
|
-
setScrollLeft: (v: number) => void
|
|
11
17
|
onChange?: () => void
|
|
12
|
-
getMinKWidth: () => number
|
|
13
|
-
getMaxKWidth: () => number
|
|
14
|
-
getPeriod: () => string
|
|
15
|
-
zoomLevelCount: number
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
/**
|
|
@@ -39,17 +41,17 @@ export class ChartZoomController {
|
|
|
39
41
|
get currentKGap(): number {
|
|
40
42
|
return deriveKGap({
|
|
41
43
|
kWidth: this.currentKWidth,
|
|
42
|
-
dpr: this.deps.
|
|
43
|
-
period: this.deps.
|
|
44
|
+
dpr: this.deps.viewport.readonly.dpr.peek(),
|
|
45
|
+
period: this.deps.period$.peek(),
|
|
44
46
|
})
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
get zoomLevelCount(): number {
|
|
48
|
-
return this.deps.zoomLevelCount
|
|
50
|
+
return this.deps.options.readonly.options.peek().zoomLevelCount
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
zoomToLevel(level: number, anchorX?: number): void {
|
|
52
|
-
const clamped = Math.max(1, Math.min(this.
|
|
54
|
+
const clamped = Math.max(1, Math.min(this.zoomLevelCount, Math.round(level)))
|
|
53
55
|
this.applyZoom(clamped, anchorX)
|
|
54
56
|
}
|
|
55
57
|
|
|
@@ -65,7 +67,7 @@ export class ChartZoomController {
|
|
|
65
67
|
const delta = deltaY > 0 ? -1 : 1
|
|
66
68
|
const targetLevel = Math.max(
|
|
67
69
|
1,
|
|
68
|
-
Math.min(this.
|
|
70
|
+
Math.min(this.zoomLevelCount, this.currentZoomLevel + delta),
|
|
69
71
|
)
|
|
70
72
|
if (targetLevel === this.currentZoomLevel) return
|
|
71
73
|
this.applyZoom(targetLevel, viewportX)
|
|
@@ -74,7 +76,7 @@ export class ChartZoomController {
|
|
|
74
76
|
handlePinch(delta: number, centerClientX: number): void {
|
|
75
77
|
const targetLevel = Math.max(
|
|
76
78
|
1,
|
|
77
|
-
Math.min(this.
|
|
79
|
+
Math.min(this.zoomLevelCount, this.currentZoomLevel + delta),
|
|
78
80
|
)
|
|
79
81
|
if (targetLevel === this.currentZoomLevel) return
|
|
80
82
|
this.applyZoom(targetLevel, centerClientX)
|
|
@@ -84,8 +86,9 @@ export class ChartZoomController {
|
|
|
84
86
|
if (targetLevel === this.currentZoomLevel) return
|
|
85
87
|
|
|
86
88
|
const delta = targetLevel - this.currentZoomLevel
|
|
87
|
-
const logicalScrollLeft = this.deps.
|
|
88
|
-
const dpr = this.deps.
|
|
89
|
+
const logicalScrollLeft = this.deps.viewport.readonly.scrollLeftLogical.peek()
|
|
90
|
+
const dpr = this.deps.viewport.readonly.dpr.peek()
|
|
91
|
+
const opt = this.deps.options.readonly.options.peek()
|
|
89
92
|
|
|
90
93
|
const result = computeZoom(
|
|
91
94
|
delta,
|
|
@@ -95,9 +98,9 @@ export class ChartZoomController {
|
|
|
95
98
|
this.currentKWidth,
|
|
96
99
|
this.currentKGap,
|
|
97
100
|
{
|
|
98
|
-
minKWidth:
|
|
99
|
-
maxKWidth:
|
|
100
|
-
zoomLevelCount:
|
|
101
|
+
minKWidth: opt.minKWidth,
|
|
102
|
+
maxKWidth: opt.maxKWidth,
|
|
103
|
+
zoomLevelCount: opt.zoomLevelCount,
|
|
101
104
|
dpr,
|
|
102
105
|
dataLength: this.deps.getDataLength(),
|
|
103
106
|
plotWidth: this.deps.getPlotWidth(),
|
|
@@ -108,7 +111,7 @@ export class ChartZoomController {
|
|
|
108
111
|
if (!result) return
|
|
109
112
|
|
|
110
113
|
this.zoomState.actions.setZoomLevel(result.targetLevel)
|
|
111
|
-
this.deps.
|
|
114
|
+
this.deps.viewport.actions.scrollTo(result.newDomScrollLeft)
|
|
112
115
|
this.deps.onChange?.()
|
|
113
116
|
}
|
|
114
117
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { createSignal } from '../../../foundation/reactivity/signal'
|
|
4
|
+
import { createViewportState } from '../../state/viewportState'
|
|
5
|
+
import { clampVisibleRange, getVisibleRange } from '../viewport'
|
|
6
|
+
|
|
7
|
+
describe('clampVisibleRange', () => {
|
|
8
|
+
it('clamps negative start to 0 and preserves end', () => {
|
|
9
|
+
expect(clampVisibleRange({ start: -1, end: 40 })).toEqual({ start: 0, end: 40 })
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('leaves non-negative start unchanged', () => {
|
|
13
|
+
expect(clampVisibleRange({ start: 12, end: 50 })).toEqual({ start: 12, end: 50 })
|
|
14
|
+
})
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
describe('getVisibleRange raw expansion', () => {
|
|
18
|
+
it('may return start < 0 when scroll is near the left edge (expansion pad)', () => {
|
|
19
|
+
// scrollLeft=0, first bars sit near plot origin → start-1 扩窗
|
|
20
|
+
const raw = getVisibleRange(0, 800, 8, 2, 100, 1)
|
|
21
|
+
expect(raw.start).toBeLessThan(0)
|
|
22
|
+
expect(raw.end).toBeGreaterThan(0)
|
|
23
|
+
})
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
describe('viewportState visibleRange SSOT', () => {
|
|
27
|
+
it('exposes clamped visibleRange/visibleFrom while keeping rawVisibleRange for load triggers', async () => {
|
|
28
|
+
const dataLength$ = createSignal(20)
|
|
29
|
+
const module = createViewportState({
|
|
30
|
+
options$: (() => ({ bottomAxisHeight: 30, kWidth: 8, kGap: 2 })) as any,
|
|
31
|
+
dataLength$,
|
|
32
|
+
period$: (() => 'daily') as any,
|
|
33
|
+
zoomLevel$: (() => 3) as any,
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
// resize 后 scrollLeftLogical≈0(右对齐短序列或左缘),raw start 常为 -1
|
|
37
|
+
module.actions.resize(800, 400, 1)
|
|
38
|
+
module.actions.scrollTo(module.readonly.leftLoadBufferWidth.peek())
|
|
39
|
+
|
|
40
|
+
const raw = module.readonly.rawVisibleRange()
|
|
41
|
+
const clamped = module.readonly.visibleRange()
|
|
42
|
+
const vs = module.readonly.viewportState()
|
|
43
|
+
|
|
44
|
+
expect(raw.start).toBeLessThan(0)
|
|
45
|
+
expect(clamped.start).toBe(0)
|
|
46
|
+
expect(clamped.end).toBe(raw.end)
|
|
47
|
+
expect(vs.visibleFrom).toBe(0)
|
|
48
|
+
expect(vs.visibleTo).toBe(clamped.end)
|
|
49
|
+
})
|
|
50
|
+
})
|
|
@@ -1,69 +1,40 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { ChartDom, Viewport, ViewportState } from '../chartTypes'
|
|
1
|
+
import type { ChartDom } from '../chartTypes'
|
|
3
2
|
import type { ChartStateKernel } from '../state/chartStateKernel'
|
|
4
3
|
|
|
5
|
-
/**
|
|
4
|
+
/** DOM 生命周期依赖:ResizeObserver / scroll 监听 */
|
|
6
5
|
export interface ViewportDependencies {
|
|
7
6
|
getDom: () => ChartDom
|
|
8
7
|
onResizeCompleted: () => void
|
|
9
8
|
}
|
|
10
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Viewport DOM 适配层:只挂 ResizeObserver 与 scroll,读写一律走 kernel。
|
|
12
|
+
*/
|
|
11
13
|
export class ChartViewportManager {
|
|
12
14
|
private deps: ViewportDependencies
|
|
13
15
|
private kernel: ChartStateKernel
|
|
14
16
|
private resizeObserver?: ResizeObserver
|
|
15
17
|
private onScroll?: (e: Event) => void
|
|
16
18
|
|
|
17
|
-
get viewportSignal(): ReadonlySignal<ViewportState> {
|
|
18
|
-
return this.kernel.viewport.readonly.viewportState
|
|
19
|
-
}
|
|
20
|
-
|
|
21
19
|
constructor(deps: ViewportDependencies, kernel: ChartStateKernel) {
|
|
22
20
|
this.deps = deps
|
|
23
21
|
this.kernel = kernel
|
|
24
22
|
}
|
|
25
23
|
|
|
26
|
-
getCachedScrollLeft(): number {
|
|
27
|
-
return this.kernel.viewport.readonly.scrollLeft.peek()
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
getLogicalScrollLeft(): number {
|
|
31
|
-
return this.kernel.viewport.readonly.scrollLeftLogical.peek()
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
getViewport(): Viewport | null {
|
|
35
|
-
if (this.kernel.viewport.readonly.viewWidth.peek() === 0) return null
|
|
36
|
-
return this.kernel.viewport.readonly.viewport.peek()
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
getEffectiveDpr(): number {
|
|
40
|
-
return this.kernel.viewport.readonly.dpr.peek()
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
getObservedSize(): { width: number; height: number } {
|
|
44
|
-
return {
|
|
45
|
-
width: this.kernel.viewport.readonly.viewWidth.peek(),
|
|
46
|
-
height: this.kernel.viewport.readonly.viewHeight.peek(),
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
setScrollLeft(v: number): void {
|
|
51
|
-
this.kernel.viewport.actions.scrollTo(v)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
24
|
init(): void {
|
|
55
|
-
if (typeof ResizeObserver === 'undefined') return
|
|
56
|
-
|
|
57
25
|
const target = this.deps.getDom().container
|
|
58
26
|
if (!target) return
|
|
59
27
|
|
|
28
|
+
// 首帧尺寸写入 kernel,不依赖 ResizeObserver 是否可用
|
|
60
29
|
this.kernel.initViewport()
|
|
61
30
|
|
|
62
|
-
this.onScroll = (
|
|
31
|
+
this.onScroll = () => {
|
|
63
32
|
this.kernel.viewport.actions.syncFromDomScroll()
|
|
64
33
|
}
|
|
65
34
|
target.addEventListener('scroll', this.onScroll, { passive: true })
|
|
66
35
|
|
|
36
|
+
if (typeof ResizeObserver === 'undefined') return
|
|
37
|
+
|
|
67
38
|
this.resizeObserver = new ResizeObserver((entries) => {
|
|
68
39
|
const entry = entries[0]
|
|
69
40
|
if (!entry) return
|
|
@@ -119,26 +90,4 @@ export class ChartViewportManager {
|
|
|
119
90
|
this.onScroll = undefined
|
|
120
91
|
}
|
|
121
92
|
}
|
|
122
|
-
|
|
123
|
-
computeViewport(): Viewport | null {
|
|
124
|
-
const container = this.deps.getDom().container
|
|
125
|
-
if (!container) return null
|
|
126
|
-
|
|
127
|
-
const viewWidth = this.kernel.viewport.readonly.viewWidth.peek()
|
|
128
|
-
const viewHeight = this.kernel.viewport.readonly.viewHeight.peek()
|
|
129
|
-
if (viewWidth === 0 || viewHeight === 0) {
|
|
130
|
-
const fallbackW = Math.max(1, Math.round(container.clientWidth))
|
|
131
|
-
const fallbackH = Math.max(1, Math.round(container.clientHeight))
|
|
132
|
-
if (fallbackW > 0 && fallbackH > 0) {
|
|
133
|
-
this.kernel.viewport.actions.resize(fallbackW, fallbackH, this.kernel.viewport.readonly.preciseDpr.peek())
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return this.kernel.viewport.readonly.viewport.peek()
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/** @deprecated No-op — viewportState is now computed(). */
|
|
141
|
-
updateViewportSignal(): void {
|
|
142
|
-
// no-op
|
|
143
|
-
}
|
|
144
93
|
}
|
|
@@ -40,6 +40,20 @@ export function getVisibleRange(
|
|
|
40
40
|
return { start, end }
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* 将 raw visible range 钳制为可索引区间。
|
|
45
|
+
*
|
|
46
|
+
* @remarks getVisibleRange 左右各扩 1 根时 start 可能为 -1;
|
|
47
|
+
* 绘制 / hit-test / 数据下标必须用 clamp 后的 start>=0。
|
|
48
|
+
* 增量加载检测仍读 raw(start 小于 0 表示已滚到左缘扩窗)。
|
|
49
|
+
*/
|
|
50
|
+
export function clampVisibleRange(range: {
|
|
51
|
+
start: number
|
|
52
|
+
end: number
|
|
53
|
+
}): { start: number; end: number } {
|
|
54
|
+
return { start: Math.max(0, range.start), end: range.end }
|
|
55
|
+
}
|
|
56
|
+
|
|
43
57
|
/**
|
|
44
58
|
* 计算指定索引区间内的价格范围(max/min)。
|
|
45
59
|
*
|
|
@@ -158,6 +158,11 @@ export type ChartSettings = {
|
|
|
158
158
|
* @param partial - 偏好的部分设置(通常是组件 prop 传入)
|
|
159
159
|
* @returns 合并后的 ChartSettings 对象
|
|
160
160
|
*/
|
|
161
|
+
const KNOWN_SETTING_KEYS = new Set<string>([
|
|
162
|
+
...DEFAULT_SETTINGS.map((item) => item.key),
|
|
163
|
+
'colorPresetSettings',
|
|
164
|
+
])
|
|
165
|
+
|
|
161
166
|
export function resolveSettings(partial?: Partial<ChartSettings>): ChartSettings {
|
|
162
167
|
// 用 Partial<_SettingByKey> 而非 ChartSettings 避免交叉类型索引赋值报错
|
|
163
168
|
const result: Partial<_SettingByKey> = {}
|
|
@@ -170,6 +175,14 @@ export function resolveSettings(partial?: Partial<ChartSettings>): ChartSettings
|
|
|
170
175
|
;(result as ChartSettings).colorPresetSettings = normalizeColorPresetSettings(
|
|
171
176
|
partial?.colorPresetSettings,
|
|
172
177
|
)
|
|
178
|
+
// 保留扩展字段(如 preClose),避免业务元数据被 resolve 清掉
|
|
179
|
+
if (partial) {
|
|
180
|
+
for (const [key, value] of Object.entries(partial)) {
|
|
181
|
+
if (KNOWN_SETTING_KEYS.has(key)) continue
|
|
182
|
+
if (value === undefined) continue
|
|
183
|
+
;(result as Record<string, unknown>)[key] = value
|
|
184
|
+
}
|
|
185
|
+
}
|
|
173
186
|
return result as ChartSettings
|
|
174
187
|
}
|
|
175
188
|
|
|
@@ -2,6 +2,24 @@ import { describe, expect, it, vi } from 'vitest'
|
|
|
2
2
|
|
|
3
3
|
import { RendererPluginManager } from './rendererPluginManager'
|
|
4
4
|
|
|
5
|
+
describe('RendererPluginManager lifecycle', () => {
|
|
6
|
+
it('installs a renderer plugin exactly once', () => {
|
|
7
|
+
const manager = new RendererPluginManager()
|
|
8
|
+
const onInstall = vi.fn()
|
|
9
|
+
manager.setPluginHost({} as never)
|
|
10
|
+
|
|
11
|
+
manager.register({
|
|
12
|
+
name: 'test',
|
|
13
|
+
paneId: 'main',
|
|
14
|
+
priority: 0,
|
|
15
|
+
draw: vi.fn(),
|
|
16
|
+
onInstall,
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
expect(onInstall).toHaveBeenCalledTimes(1)
|
|
20
|
+
})
|
|
21
|
+
})
|
|
22
|
+
|
|
5
23
|
describe('RendererPluginManager transaction', () => {
|
|
6
24
|
it('coalesces renderer invalidations into one callback', () => {
|
|
7
25
|
const manager = new RendererPluginManager()
|
|
@@ -304,7 +304,10 @@ export interface RenderContext {
|
|
|
304
304
|
crosshairIndex?: number | null
|
|
305
305
|
// 可选的其他 Canvas 上下文
|
|
306
306
|
yAxisCtx?: CanvasRenderingContext2D
|
|
307
|
+
/** 轴区动态层(最新价标签、十字线价签) */
|
|
308
|
+
yAxisOverlayCtx?: CanvasRenderingContext2D
|
|
307
309
|
leftAxisCtx?: CanvasRenderingContext2D
|
|
310
|
+
leftAxisOverlayCtx?: CanvasRenderingContext2D
|
|
308
311
|
xAxisCtx?: CanvasRenderingContext2D
|
|
309
312
|
borderCtx?: CanvasRenderingContext2D
|
|
310
313
|
/** 覆盖层 Canvas 上下文(用于十字线、Tooltip 等动态内容) */
|