@363045841yyt/klinechart-core 0.7.3 → 0.7.5-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/README.md +201 -201
  2. package/README.zh-CN.md +201 -201
  3. package/dist/engine/renderers/webgl/candleSurface.js +47 -47
  4. package/dist/version.d.ts +1 -1
  5. package/dist/version.d.ts.map +1 -1
  6. package/dist/version.js +1 -2
  7. package/dist/version.js.map +1 -1
  8. package/package.json +129 -122
  9. package/src/__tests__/signal.test.ts +124 -124
  10. package/src/config/chartSettings.ts +66 -66
  11. package/src/controllers/__tests__/drawing.test.ts +214 -214
  12. package/src/controllers/__tests__/indicatorSelector.test.ts +481 -481
  13. package/src/controllers/__tests__/toolbar.test.ts +225 -225
  14. package/src/controllers/createChartController.ts +665 -665
  15. package/src/controllers/createDrawingController.ts +96 -96
  16. package/src/controllers/createIndicatorSelectorController.ts +307 -307
  17. package/src/controllers/createToolbarController.ts +146 -146
  18. package/src/controllers/index.ts +19 -19
  19. package/src/controllers/types.ts +284 -284
  20. package/src/engine/__tests__/chart.dpr.test.ts +401 -401
  21. package/src/engine/__tests__/paneRenderer.resize.test.ts +92 -92
  22. package/src/engine/chart-store.ts +121 -121
  23. package/src/engine/chart.d.ts +617 -617
  24. package/src/engine/chart.ts +2815 -2815
  25. package/src/engine/controller/__tests__/interaction.dpr.test.ts +259 -259
  26. package/src/engine/controller/interaction.ts +722 -722
  27. package/src/engine/controller/markerInteraction.ts +130 -130
  28. package/src/engine/controller/pinchTracker.ts +82 -82
  29. package/src/engine/controller/tooltipPosition.ts +48 -48
  30. package/src/engine/draw/__tests__/pixelAlign.spec.ts +176 -176
  31. package/src/engine/draw/pixelAlign.ts +259 -259
  32. package/src/engine/drawing/index.ts +655 -655
  33. package/src/engine/drawing/interaction.ts +842 -842
  34. package/src/engine/drawing/plugin.ts +343 -343
  35. package/src/engine/indicators/__tests__/__fixtures__/golden/atr.json +38 -38
  36. package/src/engine/indicators/__tests__/__fixtures__/golden/dema.json +14 -14
  37. package/src/engine/indicators/__tests__/__fixtures__/golden/hma.json +14 -14
  38. package/src/engine/indicators/__tests__/__fixtures__/golden/index.ts +55 -55
  39. package/src/engine/indicators/__tests__/__fixtures__/golden/kama.json +14 -14
  40. package/src/engine/indicators/__tests__/__fixtures__/golden/tema.json +14 -14
  41. package/src/engine/indicators/__tests__/__fixtures__/golden/wma.json +40 -40
  42. package/src/engine/indicators/__tests__/__fixtures__/synthetic.ts +65 -65
  43. package/src/engine/indicators/__tests__/_propertyAssertions.ts +76 -76
  44. package/src/engine/indicators/__tests__/atr.test.ts +153 -153
  45. package/src/engine/indicators/__tests__/calculators.test.ts +614 -614
  46. package/src/engine/indicators/__tests__/cmf-mfi.test.ts +100 -100
  47. package/src/engine/indicators/__tests__/dema.test.ts +73 -73
  48. package/src/engine/indicators/__tests__/donchian.test.ts +70 -70
  49. package/src/engine/indicators/__tests__/hma.test.ts +73 -73
  50. package/src/engine/indicators/__tests__/ichimoku.test.ts +105 -105
  51. package/src/engine/indicators/__tests__/kama.test.ts +80 -80
  52. package/src/engine/indicators/__tests__/keltner.test.ts +65 -65
  53. package/src/engine/indicators/__tests__/pivot-fib.test.ts +110 -110
  54. package/src/engine/indicators/__tests__/roc.test.ts +68 -68
  55. package/src/engine/indicators/__tests__/sar.test.ts +86 -86
  56. package/src/engine/indicators/__tests__/scheduler.test.ts +831 -831
  57. package/src/engine/indicators/__tests__/soa.test.ts +533 -533
  58. package/src/engine/indicators/__tests__/structure.test.ts +110 -110
  59. package/src/engine/indicators/__tests__/supertrend.test.ts +65 -65
  60. package/src/engine/indicators/__tests__/tema.test.ts +68 -68
  61. package/src/engine/indicators/__tests__/trix.test.ts +70 -70
  62. package/src/engine/indicators/__tests__/volatility.test.ts +117 -117
  63. package/src/engine/indicators/__tests__/volume.test.ts +115 -115
  64. package/src/engine/indicators/__tests__/volumeProfile.test.ts +74 -74
  65. package/src/engine/indicators/__tests__/vwap.test.ts +69 -69
  66. package/src/engine/indicators/__tests__/wma.test.ts +112 -112
  67. package/src/engine/indicators/__tests__/zones.test.ts +95 -95
  68. package/src/engine/indicators/atrState.ts +27 -27
  69. package/src/engine/indicators/bollState.ts +51 -51
  70. package/src/engine/indicators/calculators.ts +2593 -2593
  71. package/src/engine/indicators/cciState.ts +25 -25
  72. package/src/engine/indicators/chaikinVolState.ts +32 -32
  73. package/src/engine/indicators/cmfState.ts +27 -27
  74. package/src/engine/indicators/demaState.ts +27 -27
  75. package/src/engine/indicators/donchianState.ts +43 -43
  76. package/src/engine/indicators/eneState.ts +43 -43
  77. package/src/engine/indicators/expmaState.ts +43 -43
  78. package/src/engine/indicators/fastkState.ts +25 -25
  79. package/src/engine/indicators/fibState.ts +41 -41
  80. package/src/engine/indicators/hmaState.ts +27 -27
  81. package/src/engine/indicators/hvState.ts +28 -28
  82. package/src/engine/indicators/ichimokuState.ts +70 -70
  83. package/src/engine/indicators/indicator.worker.ts +169 -169
  84. package/src/engine/indicators/indicatorDefinitionRegistry.ts +62 -62
  85. package/src/engine/indicators/indicatorMetadata.ts +110 -110
  86. package/src/engine/indicators/indicatorRegistry.ts +106 -106
  87. package/src/engine/indicators/indicatorRuntime.ts +1548 -1548
  88. package/src/engine/indicators/kamaState.ts +34 -34
  89. package/src/engine/indicators/keltnerState.ts +49 -49
  90. package/src/engine/indicators/kstState.ts +42 -42
  91. package/src/engine/indicators/maState.ts +36 -36
  92. package/src/engine/indicators/macdState.ts +76 -76
  93. package/src/engine/indicators/mfiState.ts +27 -27
  94. package/src/engine/indicators/momState.ts +25 -25
  95. package/src/engine/indicators/obvState.ts +25 -25
  96. package/src/engine/indicators/parkinsonState.ts +28 -28
  97. package/src/engine/indicators/pivotState.ts +51 -51
  98. package/src/engine/indicators/pvtState.ts +25 -25
  99. package/src/engine/indicators/rocState.ts +27 -27
  100. package/src/engine/indicators/rsiState.ts +65 -65
  101. package/src/engine/indicators/sarState.ts +41 -41
  102. package/src/engine/indicators/scheduler.ts +1205 -1205
  103. package/src/engine/indicators/soa.ts +352 -352
  104. package/src/engine/indicators/stateComposer.ts +1262 -1262
  105. package/src/engine/indicators/stochState.ts +26 -26
  106. package/src/engine/indicators/structureState.ts +69 -69
  107. package/src/engine/indicators/supertrendState.ts +37 -37
  108. package/src/engine/indicators/temaState.ts +27 -27
  109. package/src/engine/indicators/trixState.ts +35 -35
  110. package/src/engine/indicators/vmaState.ts +27 -27
  111. package/src/engine/indicators/volumeProfileState.ts +63 -63
  112. package/src/engine/indicators/vwapState.ts +29 -29
  113. package/src/engine/indicators/wmaState.ts +27 -27
  114. package/src/engine/indicators/wmsrState.ts +25 -25
  115. package/src/engine/indicators/workerProtocol.ts +613 -613
  116. package/src/engine/indicators/zonesState.ts +47 -47
  117. package/src/engine/layout/pane.ts +161 -161
  118. package/src/engine/marker/registry.ts +265 -265
  119. package/src/engine/paneRenderer.ts +169 -169
  120. package/src/engine/renderers/Indicator/atr.ts +237 -237
  121. package/src/engine/renderers/Indicator/boll.ts +317 -317
  122. package/src/engine/renderers/Indicator/cci.ts +275 -275
  123. package/src/engine/renderers/Indicator/chaikinVol.ts +138 -138
  124. package/src/engine/renderers/Indicator/cmf.ts +137 -137
  125. package/src/engine/renderers/Indicator/dema.ts +136 -136
  126. package/src/engine/renderers/Indicator/donchian.ts +137 -137
  127. package/src/engine/renderers/Indicator/ene.ts +271 -271
  128. package/src/engine/renderers/Indicator/expma.ts +197 -197
  129. package/src/engine/renderers/Indicator/fastk.ts +316 -316
  130. package/src/engine/renderers/Indicator/fib.ts +141 -141
  131. package/src/engine/renderers/Indicator/hma.ts +136 -136
  132. package/src/engine/renderers/Indicator/hv.ts +124 -124
  133. package/src/engine/renderers/Indicator/ichimoku.ts +181 -181
  134. package/src/engine/renderers/Indicator/index.ts +241 -241
  135. package/src/engine/renderers/Indicator/indicatorData.ts +650 -650
  136. package/src/engine/renderers/Indicator/kama.ts +136 -136
  137. package/src/engine/renderers/Indicator/keltner.ts +137 -137
  138. package/src/engine/renderers/Indicator/kst.ts +302 -302
  139. package/src/engine/renderers/Indicator/ma.ts +200 -200
  140. package/src/engine/renderers/Indicator/macd.ts +477 -477
  141. package/src/engine/renderers/Indicator/macdLegend.ts +141 -141
  142. package/src/engine/renderers/Indicator/mainIndicatorLegend.ts +272 -272
  143. package/src/engine/renderers/Indicator/mfi.ts +142 -142
  144. package/src/engine/renderers/Indicator/mom.ts +311 -311
  145. package/src/engine/renderers/Indicator/obv.ts +123 -123
  146. package/src/engine/renderers/Indicator/parkinson.ts +124 -124
  147. package/src/engine/renderers/Indicator/pivot.ts +131 -131
  148. package/src/engine/renderers/Indicator/pvt.ts +123 -123
  149. package/src/engine/renderers/Indicator/roc.ts +143 -143
  150. package/src/engine/renderers/Indicator/rsi.ts +390 -390
  151. package/src/engine/renderers/Indicator/sar.ts +113 -113
  152. package/src/engine/renderers/Indicator/scale/atr_scale.ts +19 -19
  153. package/src/engine/renderers/Indicator/scale/cci_scale.ts +19 -19
  154. package/src/engine/renderers/Indicator/scale/fastk_scale.ts +19 -19
  155. package/src/engine/renderers/Indicator/scale/indicator_scale.ts +204 -204
  156. package/src/engine/renderers/Indicator/scale/kst_scale.ts +19 -19
  157. package/src/engine/renderers/Indicator/scale/macd_scale.ts +22 -22
  158. package/src/engine/renderers/Indicator/scale/mom_scale.ts +19 -19
  159. package/src/engine/renderers/Indicator/scale/rsi_scale.ts +19 -19
  160. package/src/engine/renderers/Indicator/scale/stoch_scale.ts +19 -19
  161. package/src/engine/renderers/Indicator/scale/volume_scale.ts +26 -26
  162. package/src/engine/renderers/Indicator/scale/wmsr_scale.ts +19 -19
  163. package/src/engine/renderers/Indicator/stoch.ts +359 -359
  164. package/src/engine/renderers/Indicator/structure.ts +126 -126
  165. package/src/engine/renderers/Indicator/subPaneConfig.ts +265 -265
  166. package/src/engine/renderers/Indicator/supertrend.ts +115 -115
  167. package/src/engine/renderers/Indicator/tema.ts +136 -136
  168. package/src/engine/renderers/Indicator/trix.ts +158 -158
  169. package/src/engine/renderers/Indicator/vma.ts +124 -124
  170. package/src/engine/renderers/Indicator/volumeProfile.ts +125 -125
  171. package/src/engine/renderers/Indicator/vwap.ts +123 -123
  172. package/src/engine/renderers/Indicator/wma.ts +136 -136
  173. package/src/engine/renderers/Indicator/wmsr.ts +328 -328
  174. package/src/engine/renderers/Indicator/zones.ts +104 -104
  175. package/src/engine/renderers/__tests__/boll.renderer.test.ts +314 -314
  176. package/src/engine/renderers/__tests__/ene.renderer.test.ts +305 -305
  177. package/src/engine/renderers/__tests__/expma.renderer.test.ts +279 -279
  178. package/src/engine/renderers/__tests__/ma.renderer.test.ts +426 -426
  179. package/src/engine/renderers/__tests__/mainIndicatorLegend.renderer.test.ts +502 -502
  180. package/src/engine/renderers/__tests__/yAxis.renderer.test.ts +173 -173
  181. package/src/engine/renderers/candle.ts +459 -459
  182. package/src/engine/renderers/crosshair.ts +69 -69
  183. package/src/engine/renderers/customMarkers.ts +162 -162
  184. package/src/engine/renderers/extremaMarkers.ts +246 -246
  185. package/src/engine/renderers/gridLines.ts +90 -90
  186. package/src/engine/renderers/lastPrice.ts +97 -97
  187. package/src/engine/renderers/paneTitle.ts +136 -136
  188. package/src/engine/renderers/subVolume.ts +236 -236
  189. package/src/engine/renderers/timeAxis.ts +121 -121
  190. package/src/engine/renderers/webgl/candleSurface.ts +955 -955
  191. package/src/engine/renderers/webgl/sharedWebGLSurface.ts +146 -146
  192. package/src/engine/renderers/yAxis.ts +105 -105
  193. package/src/engine/scale/__tests__/logFormula.spec.ts +148 -148
  194. package/src/engine/scale/logFormula.ts +130 -130
  195. package/src/engine/scale/price.ts +39 -39
  196. package/src/engine/scale/priceScale.ts +264 -264
  197. package/src/engine/subPaneManager.ts +427 -427
  198. package/src/engine/theme/colors.ts +642 -642
  199. package/src/engine/theme/fonts.ts +20 -20
  200. package/src/engine/utils/klineConfig.ts +49 -49
  201. package/src/engine/utils/tickCount.ts +11 -11
  202. package/src/engine/utils/tickPosition.ts +214 -214
  203. package/src/engine/utils/zoom.ts +83 -83
  204. package/src/engine/viewport/viewport.ts +67 -67
  205. package/src/index.ts +3 -3
  206. package/src/plugin/ConfigManager.ts +93 -93
  207. package/src/plugin/EventBus.ts +77 -77
  208. package/src/plugin/HookSystem.ts +106 -106
  209. package/src/plugin/PluginHost.ts +243 -243
  210. package/src/plugin/PluginRegistry.ts +92 -92
  211. package/src/plugin/StateStore.ts +73 -73
  212. package/src/plugin/index.ts +19 -19
  213. package/src/plugin/rendererPluginManager.ts +368 -368
  214. package/src/plugin/stateKeys.ts +8 -8
  215. package/src/plugin/types.ts +526 -526
  216. package/src/reactivity/index.ts +2 -2
  217. package/src/reactivity/signal.ts +119 -119
  218. package/src/semantic/controller.ts +251 -251
  219. package/src/semantic/drawShape.ts +260 -260
  220. package/src/semantic/index.ts +28 -28
  221. package/src/semantic/schema.json +256 -256
  222. package/src/semantic/types.ts +251 -251
  223. package/src/semantic/validator.ts +349 -349
  224. package/src/types/kLine.ts +13 -13
  225. package/src/types/price.ts +56 -56
  226. package/src/types/volumePrice.ts +33 -33
  227. package/src/utils/dateFormat.ts +208 -208
  228. package/src/utils/kLineDraw/axis.ts +562 -562
  229. package/src/utils/priceToY.ts +34 -34
  230. package/src/utils/volumePrice.ts +202 -202
  231. package/src/version.ts +1 -1
@@ -1,69 +1,69 @@
1
- import type { RendererPlugin, RenderContext } from '../../plugin'
2
- import { RENDERER_PRIORITY, GLOBAL_PANE_ID } from '../../plugin'
3
- import { createHorizontalLineRect, createVerticalLineRect } from '../draw/pixelAlign'
4
- import { getColors } from '../theme/colors'
5
-
6
- /**
7
- * 创建十字线渲染器插件
8
- * 垂直线绘制到所有面板,水平线只绘制到活跃面板
9
- */
10
- export function createCrosshairRendererPlugin(options: {
11
- getCrosshairState: () => {
12
- pos: { x: number; y: number } | null
13
- activePaneId: string | null
14
- isDragging: boolean
15
- /** 十字线指向的价格(用于价格轴平移时跟随) */
16
- price: number | null
17
- }
18
- }): RendererPlugin {
19
- return {
20
- name: 'crosshair',
21
- version: '1.0.0',
22
- description: '十字线渲染器',
23
- debugName: '十字线',
24
- paneId: GLOBAL_PANE_ID,
25
- priority: RENDERER_PRIORITY.SYSTEM_CROSSHAIR,
26
- layer: 'overlay',
27
-
28
- draw(context: RenderContext) {
29
- const { pane, dpr, paneWidth, overlayCtx } = context
30
- const colors = getColors(context.theme)
31
- const state = options.getCrosshairState()
32
-
33
- if (state.isDragging || !state.pos) return
34
-
35
- const { x } = state.pos
36
- const isActive = pane.id === state.activePaneId
37
-
38
- // 使用价格计算 Y 坐标(支持价格轴平移)
39
- let localY = -1
40
- if (isActive && state.price !== null) {
41
- localY = pane.yAxis.priceToY(state.price)
42
- }
43
-
44
- // 优先使用 overlayCtx,若不存在则跳过(不回落到主画布)
45
- const ctx = overlayCtx
46
- if (!ctx) return
47
-
48
- ctx.save()
49
- ctx.beginPath()
50
- ctx.rect(0, 0, paneWidth, pane.height)
51
- ctx.clip()
52
-
53
- ctx.fillStyle = colors.CROSSHAIR.LINE
54
-
55
- // 绘制垂直线
56
- const v = createVerticalLineRect(x, 0, pane.height, dpr)
57
- if (v) ctx.fillRect(v.x, v.y, v.width, v.height)
58
-
59
- // 绘制水平线(仅在活跃面板)
60
- if (isActive && localY >= 0) {
61
- const safeY = Math.min(localY, pane.height - 1 / dpr)
62
- const h = createHorizontalLineRect(0, paneWidth, safeY, dpr)
63
- if (h) ctx.fillRect(h.x, h.y, h.width, h.height)
64
- }
65
-
66
- ctx.restore()
67
- },
68
- }
69
- }
1
+ import type { RendererPlugin, RenderContext } from '../../plugin'
2
+ import { RENDERER_PRIORITY, GLOBAL_PANE_ID } from '../../plugin'
3
+ import { createHorizontalLineRect, createVerticalLineRect } from '../draw/pixelAlign'
4
+ import { getColors } from '../theme/colors'
5
+
6
+ /**
7
+ * 创建十字线渲染器插件
8
+ * 垂直线绘制到所有面板,水平线只绘制到活跃面板
9
+ */
10
+ export function createCrosshairRendererPlugin(options: {
11
+ getCrosshairState: () => {
12
+ pos: { x: number; y: number } | null
13
+ activePaneId: string | null
14
+ isDragging: boolean
15
+ /** 十字线指向的价格(用于价格轴平移时跟随) */
16
+ price: number | null
17
+ }
18
+ }): RendererPlugin {
19
+ return {
20
+ name: 'crosshair',
21
+ version: '1.0.0',
22
+ description: '十字线渲染器',
23
+ debugName: '十字线',
24
+ paneId: GLOBAL_PANE_ID,
25
+ priority: RENDERER_PRIORITY.SYSTEM_CROSSHAIR,
26
+ layer: 'overlay',
27
+
28
+ draw(context: RenderContext) {
29
+ const { pane, dpr, paneWidth, overlayCtx } = context
30
+ const colors = getColors(context.theme)
31
+ const state = options.getCrosshairState()
32
+
33
+ if (state.isDragging || !state.pos) return
34
+
35
+ const { x } = state.pos
36
+ const isActive = pane.id === state.activePaneId
37
+
38
+ // 使用价格计算 Y 坐标(支持价格轴平移)
39
+ let localY = -1
40
+ if (isActive && state.price !== null) {
41
+ localY = pane.yAxis.priceToY(state.price)
42
+ }
43
+
44
+ // 优先使用 overlayCtx,若不存在则跳过(不回落到主画布)
45
+ const ctx = overlayCtx
46
+ if (!ctx) return
47
+
48
+ ctx.save()
49
+ ctx.beginPath()
50
+ ctx.rect(0, 0, paneWidth, pane.height)
51
+ ctx.clip()
52
+
53
+ ctx.fillStyle = colors.CROSSHAIR.LINE
54
+
55
+ // 绘制垂直线
56
+ const v = createVerticalLineRect(x, 0, pane.height, dpr)
57
+ if (v) ctx.fillRect(v.x, v.y, v.width, v.height)
58
+
59
+ // 绘制水平线(仅在活跃面板)
60
+ if (isActive && localY >= 0) {
61
+ const safeY = Math.min(localY, pane.height - 1 / dpr)
62
+ const h = createHorizontalLineRect(0, paneWidth, safeY, dpr)
63
+ if (h) ctx.fillRect(h.x, h.y, h.width, h.height)
64
+ }
65
+
66
+ ctx.restore()
67
+ },
68
+ }
69
+ }
@@ -1,162 +1,162 @@
1
- import type { RendererPlugin, RenderContext, MarkerManagerLike } from '../../plugin'
2
- import { RENDERER_PRIORITY, GLOBAL_PANE_ID } from '../../plugin'
3
- import type { KLineData } from '../../types/price'
4
- import type { CustomMarkerEntity, CustomMarkerShape } from '../marker/registry'
5
- import { drawShape, drawLabel, hitTestShape } from '../../semantic/drawShape'
6
- import { roundToPhysicalPixel } from '../draw/pixelAlign'
7
-
8
- /** 默认标记尺寸(相对于 K 线宽度的缩放因子) */
9
- const DEFAULT_SIZE_SCALE = 1.2
10
- /** 最大标记尺寸(像素) */
11
- const MAX_MARKER_SIZE = 24
12
- /** 最小标记尺寸(像素) */
13
- const MIN_MARKER_SIZE = 6
14
-
15
- /** 根据形状判断默认渲染位置(true = 上方,false = 下方) */
16
- function isShapeRenderAboveKLine(shape: CustomMarkerShape): boolean {
17
- switch (shape) {
18
- case 'arrow_up':
19
- case 'flag':
20
- return true // 上涨信号、重要事件 → 默认在K线上方
21
- case 'arrow_down':
22
- case 'circle':
23
- case 'rectangle':
24
- case 'diamond':
25
- default:
26
- return false // 下跌信号、普通标记 → 默认在K线下方
27
- }
28
- }
29
-
30
- /**
31
- * 创建自定义标记渲染器插件
32
- * 负责渲染 semanticConfig 中配置的 customMarkers
33
- */
34
- export function createCustomMarkersRenderer(): RendererPlugin {
35
- return {
36
- name: 'customMarkers',
37
- version: '1.0.0',
38
- description: '自定义标记渲染器',
39
- debugName: '自定义标记',
40
- paneId: GLOBAL_PANE_ID,
41
- priority: RENDERER_PRIORITY.OVERLAY,
42
-
43
- draw(context: RenderContext): void {
44
- const { ctx, pane, data, range, scrollLeft, kWidth, kLineCenters, dpr, markerManager, zoomLevel } = context
45
- if (!markerManager) return
46
- if ((zoomLevel ?? 1) < 2) return
47
-
48
- const customMarkers = markerManager.getCustomMarkers() as CustomMarkerEntity[]
49
- if (!customMarkers || customMarkers.length === 0) return
50
-
51
- const klineData = data as KLineData[]
52
- if (!klineData.length) return
53
- if (pane.role !== 'price') return
54
-
55
- ctx.save()
56
- ctx.translate(-scrollLeft, 0)
57
-
58
- for (const marker of customMarkers) {
59
- // 1. timestamp → index(直接在原数组上二分查找)
60
- const kIndex = findIndexByTimestamp(klineData, marker.timestamp)
61
- if (kIndex === -1) continue
62
-
63
- // 2. 检查是否在可视区域内
64
- if (kIndex < range.start || kIndex >= range.end) continue
65
-
66
- // 3. 计算像素坐标
67
- const posIndex = kIndex - range.start
68
- if (posIndex < 0 || posIndex >= kLineCenters.length) continue
69
-
70
- const pixelX = kLineCenters[posIndex]!
71
-
72
- const kData = klineData[kIndex]!
73
- const userSize = marker.style?.size
74
- const actualSize = calculateMarkerSize(kWidth, userSize)
75
-
76
- // 4. 判断标记渲染位置(上方或下方)
77
- // 如果 offset.y < 0,说明用户想渲染在上方;否则根据形状默认判断
78
- const isAboveKLine = (marker.offset?.y ?? 0) < 0
79
- ? true
80
- : (marker.offset?.y ?? 0) > 0
81
- ? false
82
- : isShapeRenderAboveKLine(marker.shape)
83
-
84
- // 5. 计算 Y 坐标
85
- let pixelY: number
86
- if (isAboveKLine) {
87
- // 在K线上方(基于 high)
88
- const highY = pane.yAxis.priceToY(kData.high)
89
- pixelY = highY - actualSize / 2 - 4 + (marker.offset?.y ?? 0)
90
- } else {
91
- // 在K线下方(基于 low)
92
- const lowY = pane.yAxis.priceToY(kData.low)
93
- pixelY = lowY + actualSize / 2 + 4 + (marker.offset?.y ?? 0)
94
- }
95
-
96
- // 6. 应用 X 偏移
97
- const finalX = pixelX + (marker.offset?.x ?? 0)
98
- const finalY = pixelY
99
-
100
- // 7. 物理像素对齐
101
- // X: kLineCenters 已对齐,直接使用;Y: priceToY 是浮点数,需要对齐
102
- const alignedX = finalX
103
- const alignedY = finalY
104
- // 确保 size*2*dpr 为偶数,使 size/2 在物理像素上为整数
105
- const alignedSize = Math.round(actualSize * dpr / 2) * 2 / dpr
106
-
107
- // 8. 绘制形状和标签(使用对齐后的坐标和尺寸)
108
- drawShape(ctx, marker.shape, alignedX, alignedY, alignedSize, marker.style || {})
109
- if (marker.label) {
110
- // 标签位置:标记在K线上方 → 文字在标记上方;标记在K线下方 → 文字在标记下方
111
- drawLabel(ctx, marker.label, alignedX, alignedY, alignedSize, marker.style || {}, isAboveKLine)
112
- }
113
-
114
- // 9. 记录位置和实际大小用于 hitTest(使用对齐后的值)
115
- markerManager.setCustomMarkerPosition(marker.id, alignedX - scrollLeft, alignedY, alignedSize, marker.shape)
116
- }
117
-
118
- ctx.restore()
119
- },
120
- }
121
- }
122
-
123
- /**
124
- * 计算标记实际显示大小
125
- * @param kWidth K线宽度
126
- * @param userSize 用户配置的最大尺寸(可选)
127
- * @returns 实际显示大小(像素)
128
- */
129
- function calculateMarkerSize(kWidth: number, userSize?: number): number {
130
- // 基于 kWidth 的基准大小
131
- const baseSize = kWidth * DEFAULT_SIZE_SCALE
132
-
133
- // 如果用户指定了最大尺寸,则限制不超过该值
134
- const maxSize = userSize ?? MAX_MARKER_SIZE
135
-
136
- // 最终大小:取基准大小和最大值中的较小者,但不小于最小值
137
- return Math.max(MIN_MARKER_SIZE, Math.min(baseSize, maxSize))
138
- }
139
-
140
- /**
141
- * 直接在有序数据上二分查找 timestamp 对应的 index
142
- * 避免每帧分配新数组
143
- */
144
- function findIndexByTimestamp(data: KLineData[], targetTs: number): number {
145
- let left = 0
146
- let right = data.length - 1
147
-
148
- while (left <= right) {
149
- const mid = Math.floor((left + right) / 2)
150
- const midTs = data[mid]?.timestamp
151
- if (midTs === undefined) break
152
-
153
- if (midTs === targetTs) return mid
154
- if (midTs < targetTs) {
155
- left = mid + 1
156
- } else {
157
- right = mid - 1
158
- }
159
- }
160
-
161
- return -1
162
- }
1
+ import type { RendererPlugin, RenderContext, MarkerManagerLike } from '../../plugin'
2
+ import { RENDERER_PRIORITY, GLOBAL_PANE_ID } from '../../plugin'
3
+ import type { KLineData } from '../../types/price'
4
+ import type { CustomMarkerEntity, CustomMarkerShape } from '../marker/registry'
5
+ import { drawShape, drawLabel, hitTestShape } from '../../semantic/drawShape'
6
+ import { roundToPhysicalPixel } from '../draw/pixelAlign'
7
+
8
+ /** 默认标记尺寸(相对于 K 线宽度的缩放因子) */
9
+ const DEFAULT_SIZE_SCALE = 1.2
10
+ /** 最大标记尺寸(像素) */
11
+ const MAX_MARKER_SIZE = 24
12
+ /** 最小标记尺寸(像素) */
13
+ const MIN_MARKER_SIZE = 6
14
+
15
+ /** 根据形状判断默认渲染位置(true = 上方,false = 下方) */
16
+ function isShapeRenderAboveKLine(shape: CustomMarkerShape): boolean {
17
+ switch (shape) {
18
+ case 'arrow_up':
19
+ case 'flag':
20
+ return true // 上涨信号、重要事件 → 默认在K线上方
21
+ case 'arrow_down':
22
+ case 'circle':
23
+ case 'rectangle':
24
+ case 'diamond':
25
+ default:
26
+ return false // 下跌信号、普通标记 → 默认在K线下方
27
+ }
28
+ }
29
+
30
+ /**
31
+ * 创建自定义标记渲染器插件
32
+ * 负责渲染 semanticConfig 中配置的 customMarkers
33
+ */
34
+ export function createCustomMarkersRenderer(): RendererPlugin {
35
+ return {
36
+ name: 'customMarkers',
37
+ version: '1.0.0',
38
+ description: '自定义标记渲染器',
39
+ debugName: '自定义标记',
40
+ paneId: GLOBAL_PANE_ID,
41
+ priority: RENDERER_PRIORITY.OVERLAY,
42
+
43
+ draw(context: RenderContext): void {
44
+ const { ctx, pane, data, range, scrollLeft, kWidth, kLineCenters, dpr, markerManager, zoomLevel } = context
45
+ if (!markerManager) return
46
+ if ((zoomLevel ?? 1) < 2) return
47
+
48
+ const customMarkers = markerManager.getCustomMarkers() as CustomMarkerEntity[]
49
+ if (!customMarkers || customMarkers.length === 0) return
50
+
51
+ const klineData = data as KLineData[]
52
+ if (!klineData.length) return
53
+ if (pane.role !== 'price') return
54
+
55
+ ctx.save()
56
+ ctx.translate(-scrollLeft, 0)
57
+
58
+ for (const marker of customMarkers) {
59
+ // 1. timestamp → index(直接在原数组上二分查找)
60
+ const kIndex = findIndexByTimestamp(klineData, marker.timestamp)
61
+ if (kIndex === -1) continue
62
+
63
+ // 2. 检查是否在可视区域内
64
+ if (kIndex < range.start || kIndex >= range.end) continue
65
+
66
+ // 3. 计算像素坐标
67
+ const posIndex = kIndex - range.start
68
+ if (posIndex < 0 || posIndex >= kLineCenters.length) continue
69
+
70
+ const pixelX = kLineCenters[posIndex]!
71
+
72
+ const kData = klineData[kIndex]!
73
+ const userSize = marker.style?.size
74
+ const actualSize = calculateMarkerSize(kWidth, userSize)
75
+
76
+ // 4. 判断标记渲染位置(上方或下方)
77
+ // 如果 offset.y < 0,说明用户想渲染在上方;否则根据形状默认判断
78
+ const isAboveKLine = (marker.offset?.y ?? 0) < 0
79
+ ? true
80
+ : (marker.offset?.y ?? 0) > 0
81
+ ? false
82
+ : isShapeRenderAboveKLine(marker.shape)
83
+
84
+ // 5. 计算 Y 坐标
85
+ let pixelY: number
86
+ if (isAboveKLine) {
87
+ // 在K线上方(基于 high)
88
+ const highY = pane.yAxis.priceToY(kData.high)
89
+ pixelY = highY - actualSize / 2 - 4 + (marker.offset?.y ?? 0)
90
+ } else {
91
+ // 在K线下方(基于 low)
92
+ const lowY = pane.yAxis.priceToY(kData.low)
93
+ pixelY = lowY + actualSize / 2 + 4 + (marker.offset?.y ?? 0)
94
+ }
95
+
96
+ // 6. 应用 X 偏移
97
+ const finalX = pixelX + (marker.offset?.x ?? 0)
98
+ const finalY = pixelY
99
+
100
+ // 7. 物理像素对齐
101
+ // X: kLineCenters 已对齐,直接使用;Y: priceToY 是浮点数,需要对齐
102
+ const alignedX = finalX
103
+ const alignedY = finalY
104
+ // 确保 size*2*dpr 为偶数,使 size/2 在物理像素上为整数
105
+ const alignedSize = Math.round(actualSize * dpr / 2) * 2 / dpr
106
+
107
+ // 8. 绘制形状和标签(使用对齐后的坐标和尺寸)
108
+ drawShape(ctx, marker.shape, alignedX, alignedY, alignedSize, marker.style || {})
109
+ if (marker.label) {
110
+ // 标签位置:标记在K线上方 → 文字在标记上方;标记在K线下方 → 文字在标记下方
111
+ drawLabel(ctx, marker.label, alignedX, alignedY, alignedSize, marker.style || {}, isAboveKLine)
112
+ }
113
+
114
+ // 9. 记录位置和实际大小用于 hitTest(使用对齐后的值)
115
+ markerManager.setCustomMarkerPosition(marker.id, alignedX - scrollLeft, alignedY, alignedSize, marker.shape)
116
+ }
117
+
118
+ ctx.restore()
119
+ },
120
+ }
121
+ }
122
+
123
+ /**
124
+ * 计算标记实际显示大小
125
+ * @param kWidth K线宽度
126
+ * @param userSize 用户配置的最大尺寸(可选)
127
+ * @returns 实际显示大小(像素)
128
+ */
129
+ function calculateMarkerSize(kWidth: number, userSize?: number): number {
130
+ // 基于 kWidth 的基准大小
131
+ const baseSize = kWidth * DEFAULT_SIZE_SCALE
132
+
133
+ // 如果用户指定了最大尺寸,则限制不超过该值
134
+ const maxSize = userSize ?? MAX_MARKER_SIZE
135
+
136
+ // 最终大小:取基准大小和最大值中的较小者,但不小于最小值
137
+ return Math.max(MIN_MARKER_SIZE, Math.min(baseSize, maxSize))
138
+ }
139
+
140
+ /**
141
+ * 直接在有序数据上二分查找 timestamp 对应的 index
142
+ * 避免每帧分配新数组
143
+ */
144
+ function findIndexByTimestamp(data: KLineData[], targetTs: number): number {
145
+ let left = 0
146
+ let right = data.length - 1
147
+
148
+ while (left <= right) {
149
+ const mid = Math.floor((left + right) / 2)
150
+ const midTs = data[mid]?.timestamp
151
+ if (midTs === undefined) break
152
+
153
+ if (midTs === targetTs) return mid
154
+ if (midTs < targetTs) {
155
+ left = mid + 1
156
+ } else {
157
+ right = mid - 1
158
+ }
159
+ }
160
+
161
+ return -1
162
+ }