@363045841yyt/klinechart-core 0.7.3

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 (919) hide show
  1. package/README.md +201 -0
  2. package/README.zh-CN.md +201 -0
  3. package/dist/config/chartSettings.d.ts +70 -0
  4. package/dist/config/chartSettings.d.ts.map +1 -0
  5. package/dist/config/chartSettings.js +50 -0
  6. package/dist/config/chartSettings.js.map +1 -0
  7. package/dist/controllers/createChartController.d.ts +16 -0
  8. package/dist/controllers/createChartController.d.ts.map +1 -0
  9. package/dist/controllers/createChartController.js +525 -0
  10. package/dist/controllers/createChartController.js.map +1 -0
  11. package/dist/controllers/createDrawingController.d.ts +25 -0
  12. package/dist/controllers/createDrawingController.d.ts.map +1 -0
  13. package/dist/controllers/createDrawingController.js +77 -0
  14. package/dist/controllers/createDrawingController.js.map +1 -0
  15. package/dist/controllers/createIndicatorSelectorController.d.ts +19 -0
  16. package/dist/controllers/createIndicatorSelectorController.d.ts.map +1 -0
  17. package/dist/controllers/createIndicatorSelectorController.js +256 -0
  18. package/dist/controllers/createIndicatorSelectorController.js.map +1 -0
  19. package/dist/controllers/createToolbarController.d.ts +28 -0
  20. package/dist/controllers/createToolbarController.d.ts.map +1 -0
  21. package/dist/controllers/createToolbarController.js +121 -0
  22. package/dist/controllers/createToolbarController.js.map +1 -0
  23. package/dist/controllers/index.d.ts +3 -0
  24. package/dist/controllers/index.d.ts.map +1 -0
  25. package/dist/controllers/index.js +2 -0
  26. package/dist/controllers/index.js.map +1 -0
  27. package/dist/controllers/types.d.ts +218 -0
  28. package/dist/controllers/types.d.ts.map +1 -0
  29. package/dist/controllers/types.js +11 -0
  30. package/dist/controllers/types.js.map +1 -0
  31. package/dist/engine/chart-store.d.ts +75 -0
  32. package/dist/engine/chart-store.d.ts.map +1 -0
  33. package/dist/engine/chart-store.js +88 -0
  34. package/dist/engine/chart-store.js.map +1 -0
  35. package/dist/engine/chart.d.ts +618 -0
  36. package/dist/engine/chart.d.ts.map +1 -0
  37. package/dist/engine/chart.js +2285 -0
  38. package/dist/engine/chart.js.map +1 -0
  39. package/dist/engine/controller/interaction.d.ts +168 -0
  40. package/dist/engine/controller/interaction.d.ts.map +1 -0
  41. package/dist/engine/controller/interaction.js +612 -0
  42. package/dist/engine/controller/interaction.js.map +1 -0
  43. package/dist/engine/controller/markerInteraction.d.ts +29 -0
  44. package/dist/engine/controller/markerInteraction.d.ts.map +1 -0
  45. package/dist/engine/controller/markerInteraction.js +111 -0
  46. package/dist/engine/controller/markerInteraction.js.map +1 -0
  47. package/dist/engine/controller/pinchTracker.d.ts +19 -0
  48. package/dist/engine/controller/pinchTracker.d.ts.map +1 -0
  49. package/dist/engine/controller/pinchTracker.js +72 -0
  50. package/dist/engine/controller/pinchTracker.js.map +1 -0
  51. package/dist/engine/controller/tooltipPosition.d.ts +22 -0
  52. package/dist/engine/controller/tooltipPosition.d.ts.map +1 -0
  53. package/dist/engine/controller/tooltipPosition.js +31 -0
  54. package/dist/engine/controller/tooltipPosition.js.map +1 -0
  55. package/dist/engine/draw/pixelAlign.d.ts +115 -0
  56. package/dist/engine/draw/pixelAlign.d.ts.map +1 -0
  57. package/dist/engine/draw/pixelAlign.js +185 -0
  58. package/dist/engine/draw/pixelAlign.js.map +1 -0
  59. package/dist/engine/drawing/index.d.ts +48 -0
  60. package/dist/engine/drawing/index.d.ts.map +1 -0
  61. package/dist/engine/drawing/index.js +561 -0
  62. package/dist/engine/drawing/index.js.map +1 -0
  63. package/dist/engine/drawing/interaction.d.ts +76 -0
  64. package/dist/engine/drawing/interaction.d.ts.map +1 -0
  65. package/dist/engine/drawing/interaction.js +702 -0
  66. package/dist/engine/drawing/interaction.js.map +1 -0
  67. package/dist/engine/drawing/plugin.d.ts +29 -0
  68. package/dist/engine/drawing/plugin.d.ts.map +1 -0
  69. package/dist/engine/drawing/plugin.js +292 -0
  70. package/dist/engine/drawing/plugin.js.map +1 -0
  71. package/dist/engine/indicators/atrState.d.ts +17 -0
  72. package/dist/engine/indicators/atrState.d.ts.map +1 -0
  73. package/dist/engine/indicators/atrState.js +13 -0
  74. package/dist/engine/indicators/atrState.js.map +1 -0
  75. package/dist/engine/indicators/bollState.d.ts +35 -0
  76. package/dist/engine/indicators/bollState.d.ts.map +1 -0
  77. package/dist/engine/indicators/bollState.js +25 -0
  78. package/dist/engine/indicators/bollState.js.map +1 -0
  79. package/dist/engine/indicators/calculators.d.ts +466 -0
  80. package/dist/engine/indicators/calculators.d.ts.map +1 -0
  81. package/dist/engine/indicators/calculators.js +1882 -0
  82. package/dist/engine/indicators/calculators.js.map +1 -0
  83. package/dist/engine/indicators/cciState.d.ts +16 -0
  84. package/dist/engine/indicators/cciState.d.ts.map +1 -0
  85. package/dist/engine/indicators/cciState.js +12 -0
  86. package/dist/engine/indicators/cciState.js.map +1 -0
  87. package/dist/engine/indicators/chaikinVolState.d.ts +19 -0
  88. package/dist/engine/indicators/chaikinVolState.d.ts.map +1 -0
  89. package/dist/engine/indicators/chaikinVolState.js +18 -0
  90. package/dist/engine/indicators/chaikinVolState.js.map +1 -0
  91. package/dist/engine/indicators/cmfState.d.ts +17 -0
  92. package/dist/engine/indicators/cmfState.d.ts.map +1 -0
  93. package/dist/engine/indicators/cmfState.js +13 -0
  94. package/dist/engine/indicators/cmfState.js.map +1 -0
  95. package/dist/engine/indicators/demaState.d.ts +17 -0
  96. package/dist/engine/indicators/demaState.d.ts.map +1 -0
  97. package/dist/engine/indicators/demaState.js +13 -0
  98. package/dist/engine/indicators/demaState.js.map +1 -0
  99. package/dist/engine/indicators/donchianState.d.ts +24 -0
  100. package/dist/engine/indicators/donchianState.d.ts.map +1 -0
  101. package/dist/engine/indicators/donchianState.js +18 -0
  102. package/dist/engine/indicators/donchianState.js.map +1 -0
  103. package/dist/engine/indicators/eneState.d.ts +31 -0
  104. package/dist/engine/indicators/eneState.d.ts.map +1 -0
  105. package/dist/engine/indicators/eneState.js +21 -0
  106. package/dist/engine/indicators/eneState.js.map +1 -0
  107. package/dist/engine/indicators/expmaState.d.ts +31 -0
  108. package/dist/engine/indicators/expmaState.d.ts.map +1 -0
  109. package/dist/engine/indicators/expmaState.js +21 -0
  110. package/dist/engine/indicators/expmaState.js.map +1 -0
  111. package/dist/engine/indicators/fastkState.d.ts +16 -0
  112. package/dist/engine/indicators/fastkState.d.ts.map +1 -0
  113. package/dist/engine/indicators/fastkState.js +12 -0
  114. package/dist/engine/indicators/fastkState.js.map +1 -0
  115. package/dist/engine/indicators/fibState.d.ts +27 -0
  116. package/dist/engine/indicators/fibState.d.ts.map +1 -0
  117. package/dist/engine/indicators/fibState.js +13 -0
  118. package/dist/engine/indicators/fibState.js.map +1 -0
  119. package/dist/engine/indicators/hmaState.d.ts +17 -0
  120. package/dist/engine/indicators/hmaState.d.ts.map +1 -0
  121. package/dist/engine/indicators/hmaState.js +13 -0
  122. package/dist/engine/indicators/hmaState.js.map +1 -0
  123. package/dist/engine/indicators/hvState.d.ts +19 -0
  124. package/dist/engine/indicators/hvState.d.ts.map +1 -0
  125. package/dist/engine/indicators/hvState.js +14 -0
  126. package/dist/engine/indicators/hvState.js.map +1 -0
  127. package/dist/engine/indicators/ichimokuState.d.ts +45 -0
  128. package/dist/engine/indicators/ichimokuState.d.ts.map +1 -0
  129. package/dist/engine/indicators/ichimokuState.js +27 -0
  130. package/dist/engine/indicators/ichimokuState.js.map +1 -0
  131. package/dist/engine/indicators/indicator.worker.d.ts +6 -0
  132. package/dist/engine/indicators/indicator.worker.d.ts.map +1 -0
  133. package/dist/engine/indicators/indicator.worker.js +144 -0
  134. package/dist/engine/indicators/indicator.worker.js.map +1 -0
  135. package/dist/engine/indicators/indicatorDefinitionRegistry.d.ts +31 -0
  136. package/dist/engine/indicators/indicatorDefinitionRegistry.d.ts.map +1 -0
  137. package/dist/engine/indicators/indicatorDefinitionRegistry.js +38 -0
  138. package/dist/engine/indicators/indicatorDefinitionRegistry.js.map +1 -0
  139. package/dist/engine/indicators/indicatorMetadata.d.ts +88 -0
  140. package/dist/engine/indicators/indicatorMetadata.d.ts.map +1 -0
  141. package/dist/engine/indicators/indicatorMetadata.js +22 -0
  142. package/dist/engine/indicators/indicatorMetadata.js.map +1 -0
  143. package/dist/engine/indicators/indicatorRegistry.d.ts +58 -0
  144. package/dist/engine/indicators/indicatorRegistry.d.ts.map +1 -0
  145. package/dist/engine/indicators/indicatorRegistry.js +91 -0
  146. package/dist/engine/indicators/indicatorRegistry.js.map +1 -0
  147. package/dist/engine/indicators/indicatorRuntime.d.ts +132 -0
  148. package/dist/engine/indicators/indicatorRuntime.d.ts.map +1 -0
  149. package/dist/engine/indicators/indicatorRuntime.js +1354 -0
  150. package/dist/engine/indicators/indicatorRuntime.js.map +1 -0
  151. package/dist/engine/indicators/kamaState.d.ts +21 -0
  152. package/dist/engine/indicators/kamaState.d.ts.map +1 -0
  153. package/dist/engine/indicators/kamaState.js +20 -0
  154. package/dist/engine/indicators/kamaState.js.map +1 -0
  155. package/dist/engine/indicators/keltnerState.d.ts +28 -0
  156. package/dist/engine/indicators/keltnerState.d.ts.map +1 -0
  157. package/dist/engine/indicators/keltnerState.js +22 -0
  158. package/dist/engine/indicators/keltnerState.js.map +1 -0
  159. package/dist/engine/indicators/kstState.d.ts +22 -0
  160. package/dist/engine/indicators/kstState.d.ts.map +1 -0
  161. package/dist/engine/indicators/kstState.js +20 -0
  162. package/dist/engine/indicators/kstState.js.map +1 -0
  163. package/dist/engine/indicators/maState.d.ts +27 -0
  164. package/dist/engine/indicators/maState.d.ts.map +1 -0
  165. package/dist/engine/indicators/maState.js +18 -0
  166. package/dist/engine/indicators/maState.js.map +1 -0
  167. package/dist/engine/indicators/macdState.d.ts +59 -0
  168. package/dist/engine/indicators/macdState.d.ts.map +1 -0
  169. package/dist/engine/indicators/macdState.js +33 -0
  170. package/dist/engine/indicators/macdState.js.map +1 -0
  171. package/dist/engine/indicators/mfiState.d.ts +17 -0
  172. package/dist/engine/indicators/mfiState.d.ts.map +1 -0
  173. package/dist/engine/indicators/mfiState.js +13 -0
  174. package/dist/engine/indicators/mfiState.js.map +1 -0
  175. package/dist/engine/indicators/momState.d.ts +16 -0
  176. package/dist/engine/indicators/momState.d.ts.map +1 -0
  177. package/dist/engine/indicators/momState.js +12 -0
  178. package/dist/engine/indicators/momState.js.map +1 -0
  179. package/dist/engine/indicators/obvState.d.ts +15 -0
  180. package/dist/engine/indicators/obvState.d.ts.map +1 -0
  181. package/dist/engine/indicators/obvState.js +12 -0
  182. package/dist/engine/indicators/obvState.js.map +1 -0
  183. package/dist/engine/indicators/parkinsonState.d.ts +19 -0
  184. package/dist/engine/indicators/parkinsonState.d.ts.map +1 -0
  185. package/dist/engine/indicators/parkinsonState.js +14 -0
  186. package/dist/engine/indicators/parkinsonState.js.map +1 -0
  187. package/dist/engine/indicators/pivotState.d.ts +30 -0
  188. package/dist/engine/indicators/pivotState.d.ts.map +1 -0
  189. package/dist/engine/indicators/pivotState.js +20 -0
  190. package/dist/engine/indicators/pivotState.js.map +1 -0
  191. package/dist/engine/indicators/pvtState.d.ts +15 -0
  192. package/dist/engine/indicators/pvtState.d.ts.map +1 -0
  193. package/dist/engine/indicators/pvtState.js +12 -0
  194. package/dist/engine/indicators/pvtState.js.map +1 -0
  195. package/dist/engine/indicators/rocState.d.ts +17 -0
  196. package/dist/engine/indicators/rocState.d.ts.map +1 -0
  197. package/dist/engine/indicators/rocState.js +13 -0
  198. package/dist/engine/indicators/rocState.js.map +1 -0
  199. package/dist/engine/indicators/rsiState.d.ts +44 -0
  200. package/dist/engine/indicators/rsiState.d.ts.map +1 -0
  201. package/dist/engine/indicators/rsiState.js +32 -0
  202. package/dist/engine/indicators/rsiState.js.map +1 -0
  203. package/dist/engine/indicators/sarState.d.ts +27 -0
  204. package/dist/engine/indicators/sarState.d.ts.map +1 -0
  205. package/dist/engine/indicators/sarState.js +18 -0
  206. package/dist/engine/indicators/sarState.js.map +1 -0
  207. package/dist/engine/indicators/scheduler.d.ts +277 -0
  208. package/dist/engine/indicators/scheduler.d.ts.map +1 -0
  209. package/dist/engine/indicators/scheduler.js +1012 -0
  210. package/dist/engine/indicators/scheduler.js.map +1 -0
  211. package/dist/engine/indicators/soa.d.ts +116 -0
  212. package/dist/engine/indicators/soa.d.ts.map +1 -0
  213. package/dist/engine/indicators/soa.js +242 -0
  214. package/dist/engine/indicators/soa.js.map +1 -0
  215. package/dist/engine/indicators/stateComposer.d.ts +151 -0
  216. package/dist/engine/indicators/stateComposer.d.ts.map +1 -0
  217. package/dist/engine/indicators/stateComposer.js +1018 -0
  218. package/dist/engine/indicators/stateComposer.js.map +1 -0
  219. package/dist/engine/indicators/stochState.d.ts +19 -0
  220. package/dist/engine/indicators/stochState.d.ts.map +1 -0
  221. package/dist/engine/indicators/stochState.js +12 -0
  222. package/dist/engine/indicators/stochState.js.map +1 -0
  223. package/dist/engine/indicators/structureState.d.ts +44 -0
  224. package/dist/engine/indicators/structureState.d.ts.map +1 -0
  225. package/dist/engine/indicators/structureState.js +22 -0
  226. package/dist/engine/indicators/structureState.js.map +1 -0
  227. package/dist/engine/indicators/supertrendState.d.ts +23 -0
  228. package/dist/engine/indicators/supertrendState.d.ts.map +1 -0
  229. package/dist/engine/indicators/supertrendState.js +18 -0
  230. package/dist/engine/indicators/supertrendState.js.map +1 -0
  231. package/dist/engine/indicators/temaState.d.ts +17 -0
  232. package/dist/engine/indicators/temaState.d.ts.map +1 -0
  233. package/dist/engine/indicators/temaState.js +13 -0
  234. package/dist/engine/indicators/temaState.js.map +1 -0
  235. package/dist/engine/indicators/trixState.d.ts +21 -0
  236. package/dist/engine/indicators/trixState.d.ts.map +1 -0
  237. package/dist/engine/indicators/trixState.js +20 -0
  238. package/dist/engine/indicators/trixState.js.map +1 -0
  239. package/dist/engine/indicators/vmaState.d.ts +17 -0
  240. package/dist/engine/indicators/vmaState.d.ts.map +1 -0
  241. package/dist/engine/indicators/vmaState.js +13 -0
  242. package/dist/engine/indicators/vmaState.js.map +1 -0
  243. package/dist/engine/indicators/volumeProfileState.d.ts +35 -0
  244. package/dist/engine/indicators/volumeProfileState.d.ts.map +1 -0
  245. package/dist/engine/indicators/volumeProfileState.js +28 -0
  246. package/dist/engine/indicators/volumeProfileState.js.map +1 -0
  247. package/dist/engine/indicators/vwapState.d.ts +17 -0
  248. package/dist/engine/indicators/vwapState.d.ts.map +1 -0
  249. package/dist/engine/indicators/vwapState.js +15 -0
  250. package/dist/engine/indicators/vwapState.js.map +1 -0
  251. package/dist/engine/indicators/wmaState.d.ts +17 -0
  252. package/dist/engine/indicators/wmaState.d.ts.map +1 -0
  253. package/dist/engine/indicators/wmaState.js +13 -0
  254. package/dist/engine/indicators/wmaState.js.map +1 -0
  255. package/dist/engine/indicators/wmsrState.d.ts +16 -0
  256. package/dist/engine/indicators/wmsrState.d.ts.map +1 -0
  257. package/dist/engine/indicators/wmsrState.js +12 -0
  258. package/dist/engine/indicators/wmsrState.js.map +1 -0
  259. package/dist/engine/indicators/workerProtocol.d.ts +501 -0
  260. package/dist/engine/indicators/workerProtocol.d.ts.map +1 -0
  261. package/dist/engine/indicators/workerProtocol.js +20 -0
  262. package/dist/engine/indicators/workerProtocol.js.map +1 -0
  263. package/dist/engine/indicators/zonesState.d.ts +27 -0
  264. package/dist/engine/indicators/zonesState.d.ts.map +1 -0
  265. package/dist/engine/indicators/zonesState.js +18 -0
  266. package/dist/engine/indicators/zonesState.js.map +1 -0
  267. package/dist/engine/layout/pane.d.ts +104 -0
  268. package/dist/engine/layout/pane.d.ts.map +1 -0
  269. package/dist/engine/layout/pane.js +107 -0
  270. package/dist/engine/layout/pane.js.map +1 -0
  271. package/dist/engine/marker/registry.d.ts +175 -0
  272. package/dist/engine/marker/registry.d.ts.map +1 -0
  273. package/dist/engine/marker/registry.js +171 -0
  274. package/dist/engine/marker/registry.js.map +1 -0
  275. package/dist/engine/paneRenderer.d.ts +46 -0
  276. package/dist/engine/paneRenderer.d.ts.map +1 -0
  277. package/dist/engine/paneRenderer.js +121 -0
  278. package/dist/engine/paneRenderer.js.map +1 -0
  279. package/dist/engine/renderers/Indicator/atr.d.ts +18 -0
  280. package/dist/engine/renderers/Indicator/atr.d.ts.map +1 -0
  281. package/dist/engine/renderers/Indicator/atr.js +237 -0
  282. package/dist/engine/renderers/Indicator/atr.js.map +1 -0
  283. package/dist/engine/renderers/Indicator/boll.d.ts +3 -0
  284. package/dist/engine/renderers/Indicator/boll.d.ts.map +1 -0
  285. package/dist/engine/renderers/Indicator/boll.js +312 -0
  286. package/dist/engine/renderers/Indicator/boll.js.map +1 -0
  287. package/dist/engine/renderers/Indicator/cci.d.ts +23 -0
  288. package/dist/engine/renderers/Indicator/cci.d.ts.map +1 -0
  289. package/dist/engine/renderers/Indicator/cci.js +266 -0
  290. package/dist/engine/renderers/Indicator/cci.js.map +1 -0
  291. package/dist/engine/renderers/Indicator/chaikinVol.d.ts +5 -0
  292. package/dist/engine/renderers/Indicator/chaikinVol.d.ts.map +1 -0
  293. package/dist/engine/renderers/Indicator/chaikinVol.js +175 -0
  294. package/dist/engine/renderers/Indicator/chaikinVol.js.map +1 -0
  295. package/dist/engine/renderers/Indicator/cmf.d.ts +5 -0
  296. package/dist/engine/renderers/Indicator/cmf.d.ts.map +1 -0
  297. package/dist/engine/renderers/Indicator/cmf.js +175 -0
  298. package/dist/engine/renderers/Indicator/cmf.js.map +1 -0
  299. package/dist/engine/renderers/Indicator/dema.d.ts +6 -0
  300. package/dist/engine/renderers/Indicator/dema.d.ts.map +1 -0
  301. package/dist/engine/renderers/Indicator/dema.js +164 -0
  302. package/dist/engine/renderers/Indicator/dema.js.map +1 -0
  303. package/dist/engine/renderers/Indicator/donchian.d.ts +6 -0
  304. package/dist/engine/renderers/Indicator/donchian.d.ts.map +1 -0
  305. package/dist/engine/renderers/Indicator/donchian.js +181 -0
  306. package/dist/engine/renderers/Indicator/donchian.js.map +1 -0
  307. package/dist/engine/renderers/Indicator/ene.d.ts +3 -0
  308. package/dist/engine/renderers/Indicator/ene.d.ts.map +1 -0
  309. package/dist/engine/renderers/Indicator/ene.js +280 -0
  310. package/dist/engine/renderers/Indicator/ene.js.map +1 -0
  311. package/dist/engine/renderers/Indicator/expma.d.ts +3 -0
  312. package/dist/engine/renderers/Indicator/expma.d.ts.map +1 -0
  313. package/dist/engine/renderers/Indicator/expma.js +216 -0
  314. package/dist/engine/renderers/Indicator/expma.js.map +1 -0
  315. package/dist/engine/renderers/Indicator/fastk.d.ts +23 -0
  316. package/dist/engine/renderers/Indicator/fastk.d.ts.map +1 -0
  317. package/dist/engine/renderers/Indicator/fastk.js +289 -0
  318. package/dist/engine/renderers/Indicator/fastk.js.map +1 -0
  319. package/dist/engine/renderers/Indicator/fib.d.ts +5 -0
  320. package/dist/engine/renderers/Indicator/fib.d.ts.map +1 -0
  321. package/dist/engine/renderers/Indicator/fib.js +182 -0
  322. package/dist/engine/renderers/Indicator/fib.js.map +1 -0
  323. package/dist/engine/renderers/Indicator/hma.d.ts +6 -0
  324. package/dist/engine/renderers/Indicator/hma.d.ts.map +1 -0
  325. package/dist/engine/renderers/Indicator/hma.js +164 -0
  326. package/dist/engine/renderers/Indicator/hma.js.map +1 -0
  327. package/dist/engine/renderers/Indicator/hv.d.ts +5 -0
  328. package/dist/engine/renderers/Indicator/hv.d.ts.map +1 -0
  329. package/dist/engine/renderers/Indicator/hv.js +162 -0
  330. package/dist/engine/renderers/Indicator/hv.js.map +1 -0
  331. package/dist/engine/renderers/Indicator/ichimoku.d.ts +6 -0
  332. package/dist/engine/renderers/Indicator/ichimoku.d.ts.map +1 -0
  333. package/dist/engine/renderers/Indicator/ichimoku.js +220 -0
  334. package/dist/engine/renderers/Indicator/ichimoku.js.map +1 -0
  335. package/dist/engine/renderers/Indicator/index.d.ts +63 -0
  336. package/dist/engine/renderers/Indicator/index.d.ts.map +1 -0
  337. package/dist/engine/renderers/Indicator/index.js +204 -0
  338. package/dist/engine/renderers/Indicator/index.js.map +1 -0
  339. package/dist/engine/renderers/Indicator/indicatorData.d.ts +23 -0
  340. package/dist/engine/renderers/Indicator/indicatorData.d.ts.map +1 -0
  341. package/dist/engine/renderers/Indicator/indicatorData.js +617 -0
  342. package/dist/engine/renderers/Indicator/indicatorData.js.map +1 -0
  343. package/dist/engine/renderers/Indicator/kama.d.ts +6 -0
  344. package/dist/engine/renderers/Indicator/kama.d.ts.map +1 -0
  345. package/dist/engine/renderers/Indicator/kama.js +164 -0
  346. package/dist/engine/renderers/Indicator/kama.js.map +1 -0
  347. package/dist/engine/renderers/Indicator/keltner.d.ts +6 -0
  348. package/dist/engine/renderers/Indicator/keltner.d.ts.map +1 -0
  349. package/dist/engine/renderers/Indicator/keltner.js +181 -0
  350. package/dist/engine/renderers/Indicator/keltner.js.map +1 -0
  351. package/dist/engine/renderers/Indicator/kst.d.ts +23 -0
  352. package/dist/engine/renderers/Indicator/kst.d.ts.map +1 -0
  353. package/dist/engine/renderers/Indicator/kst.js +290 -0
  354. package/dist/engine/renderers/Indicator/kst.js.map +1 -0
  355. package/dist/engine/renderers/Indicator/ma.d.ts +4 -0
  356. package/dist/engine/renderers/Indicator/ma.d.ts.map +1 -0
  357. package/dist/engine/renderers/Indicator/ma.js +218 -0
  358. package/dist/engine/renderers/Indicator/ma.js.map +1 -0
  359. package/dist/engine/renderers/Indicator/macd.d.ts +51 -0
  360. package/dist/engine/renderers/Indicator/macd.d.ts.map +1 -0
  361. package/dist/engine/renderers/Indicator/macd.js +432 -0
  362. package/dist/engine/renderers/Indicator/macd.js.map +1 -0
  363. package/dist/engine/renderers/Indicator/macdLegend.d.ts +13 -0
  364. package/dist/engine/renderers/Indicator/macdLegend.d.ts.map +1 -0
  365. package/dist/engine/renderers/Indicator/macdLegend.js +116 -0
  366. package/dist/engine/renderers/Indicator/macdLegend.js.map +1 -0
  367. package/dist/engine/renderers/Indicator/mainIndicatorLegend.d.ts +11 -0
  368. package/dist/engine/renderers/Indicator/mainIndicatorLegend.d.ts.map +1 -0
  369. package/dist/engine/renderers/Indicator/mainIndicatorLegend.js +220 -0
  370. package/dist/engine/renderers/Indicator/mainIndicatorLegend.js.map +1 -0
  371. package/dist/engine/renderers/Indicator/mfi.d.ts +5 -0
  372. package/dist/engine/renderers/Indicator/mfi.d.ts.map +1 -0
  373. package/dist/engine/renderers/Indicator/mfi.js +180 -0
  374. package/dist/engine/renderers/Indicator/mfi.js.map +1 -0
  375. package/dist/engine/renderers/Indicator/mom.d.ts +23 -0
  376. package/dist/engine/renderers/Indicator/mom.d.ts.map +1 -0
  377. package/dist/engine/renderers/Indicator/mom.js +285 -0
  378. package/dist/engine/renderers/Indicator/mom.js.map +1 -0
  379. package/dist/engine/renderers/Indicator/obv.d.ts +5 -0
  380. package/dist/engine/renderers/Indicator/obv.d.ts.map +1 -0
  381. package/dist/engine/renderers/Indicator/obv.js +162 -0
  382. package/dist/engine/renderers/Indicator/obv.js.map +1 -0
  383. package/dist/engine/renderers/Indicator/parkinson.d.ts +5 -0
  384. package/dist/engine/renderers/Indicator/parkinson.d.ts.map +1 -0
  385. package/dist/engine/renderers/Indicator/parkinson.js +162 -0
  386. package/dist/engine/renderers/Indicator/parkinson.js.map +1 -0
  387. package/dist/engine/renderers/Indicator/pivot.d.ts +5 -0
  388. package/dist/engine/renderers/Indicator/pivot.d.ts.map +1 -0
  389. package/dist/engine/renderers/Indicator/pivot.js +181 -0
  390. package/dist/engine/renderers/Indicator/pivot.js.map +1 -0
  391. package/dist/engine/renderers/Indicator/pvt.d.ts +5 -0
  392. package/dist/engine/renderers/Indicator/pvt.d.ts.map +1 -0
  393. package/dist/engine/renderers/Indicator/pvt.js +162 -0
  394. package/dist/engine/renderers/Indicator/pvt.js.map +1 -0
  395. package/dist/engine/renderers/Indicator/roc.d.ts +6 -0
  396. package/dist/engine/renderers/Indicator/roc.d.ts.map +1 -0
  397. package/dist/engine/renderers/Indicator/roc.js +175 -0
  398. package/dist/engine/renderers/Indicator/roc.js.map +1 -0
  399. package/dist/engine/renderers/Indicator/rsi.d.ts +34 -0
  400. package/dist/engine/renderers/Indicator/rsi.d.ts.map +1 -0
  401. package/dist/engine/renderers/Indicator/rsi.js +351 -0
  402. package/dist/engine/renderers/Indicator/rsi.js.map +1 -0
  403. package/dist/engine/renderers/Indicator/sar.d.ts +6 -0
  404. package/dist/engine/renderers/Indicator/sar.d.ts.map +1 -0
  405. package/dist/engine/renderers/Indicator/sar.js +146 -0
  406. package/dist/engine/renderers/Indicator/sar.js.map +1 -0
  407. package/dist/engine/renderers/Indicator/scale/atr_scale.d.ts +12 -0
  408. package/dist/engine/renderers/Indicator/scale/atr_scale.d.ts.map +1 -0
  409. package/dist/engine/renderers/Indicator/scale/atr_scale.js +13 -0
  410. package/dist/engine/renderers/Indicator/scale/atr_scale.js.map +1 -0
  411. package/dist/engine/renderers/Indicator/scale/cci_scale.d.ts +12 -0
  412. package/dist/engine/renderers/Indicator/scale/cci_scale.d.ts.map +1 -0
  413. package/dist/engine/renderers/Indicator/scale/cci_scale.js +13 -0
  414. package/dist/engine/renderers/Indicator/scale/cci_scale.js.map +1 -0
  415. package/dist/engine/renderers/Indicator/scale/fastk_scale.d.ts +12 -0
  416. package/dist/engine/renderers/Indicator/scale/fastk_scale.d.ts.map +1 -0
  417. package/dist/engine/renderers/Indicator/scale/fastk_scale.js +13 -0
  418. package/dist/engine/renderers/Indicator/scale/fastk_scale.js.map +1 -0
  419. package/dist/engine/renderers/Indicator/scale/indicator_scale.d.ts +39 -0
  420. package/dist/engine/renderers/Indicator/scale/indicator_scale.d.ts.map +1 -0
  421. package/dist/engine/renderers/Indicator/scale/indicator_scale.js +120 -0
  422. package/dist/engine/renderers/Indicator/scale/indicator_scale.js.map +1 -0
  423. package/dist/engine/renderers/Indicator/scale/kst_scale.d.ts +12 -0
  424. package/dist/engine/renderers/Indicator/scale/kst_scale.d.ts.map +1 -0
  425. package/dist/engine/renderers/Indicator/scale/kst_scale.js +13 -0
  426. package/dist/engine/renderers/Indicator/scale/kst_scale.js.map +1 -0
  427. package/dist/engine/renderers/Indicator/scale/macd_scale.d.ts +15 -0
  428. package/dist/engine/renderers/Indicator/scale/macd_scale.d.ts.map +1 -0
  429. package/dist/engine/renderers/Indicator/scale/macd_scale.js +16 -0
  430. package/dist/engine/renderers/Indicator/scale/macd_scale.js.map +1 -0
  431. package/dist/engine/renderers/Indicator/scale/mom_scale.d.ts +12 -0
  432. package/dist/engine/renderers/Indicator/scale/mom_scale.d.ts.map +1 -0
  433. package/dist/engine/renderers/Indicator/scale/mom_scale.js +13 -0
  434. package/dist/engine/renderers/Indicator/scale/mom_scale.js.map +1 -0
  435. package/dist/engine/renderers/Indicator/scale/rsi_scale.d.ts +12 -0
  436. package/dist/engine/renderers/Indicator/scale/rsi_scale.d.ts.map +1 -0
  437. package/dist/engine/renderers/Indicator/scale/rsi_scale.js +13 -0
  438. package/dist/engine/renderers/Indicator/scale/rsi_scale.js.map +1 -0
  439. package/dist/engine/renderers/Indicator/scale/stoch_scale.d.ts +12 -0
  440. package/dist/engine/renderers/Indicator/scale/stoch_scale.d.ts.map +1 -0
  441. package/dist/engine/renderers/Indicator/scale/stoch_scale.js +13 -0
  442. package/dist/engine/renderers/Indicator/scale/stoch_scale.js.map +1 -0
  443. package/dist/engine/renderers/Indicator/scale/volume_scale.d.ts +15 -0
  444. package/dist/engine/renderers/Indicator/scale/volume_scale.d.ts.map +1 -0
  445. package/dist/engine/renderers/Indicator/scale/volume_scale.js +19 -0
  446. package/dist/engine/renderers/Indicator/scale/volume_scale.js.map +1 -0
  447. package/dist/engine/renderers/Indicator/scale/wmsr_scale.d.ts +12 -0
  448. package/dist/engine/renderers/Indicator/scale/wmsr_scale.d.ts.map +1 -0
  449. package/dist/engine/renderers/Indicator/scale/wmsr_scale.js +13 -0
  450. package/dist/engine/renderers/Indicator/scale/wmsr_scale.js.map +1 -0
  451. package/dist/engine/renderers/Indicator/stoch.d.ts +23 -0
  452. package/dist/engine/renderers/Indicator/stoch.d.ts.map +1 -0
  453. package/dist/engine/renderers/Indicator/stoch.js +329 -0
  454. package/dist/engine/renderers/Indicator/stoch.js.map +1 -0
  455. package/dist/engine/renderers/Indicator/structure.d.ts +5 -0
  456. package/dist/engine/renderers/Indicator/structure.d.ts.map +1 -0
  457. package/dist/engine/renderers/Indicator/structure.js +176 -0
  458. package/dist/engine/renderers/Indicator/structure.js.map +1 -0
  459. package/dist/engine/renderers/Indicator/subPaneConfig.d.ts +16 -0
  460. package/dist/engine/renderers/Indicator/subPaneConfig.d.ts.map +1 -0
  461. package/dist/engine/renderers/Indicator/subPaneConfig.js +194 -0
  462. package/dist/engine/renderers/Indicator/subPaneConfig.js.map +1 -0
  463. package/dist/engine/renderers/Indicator/supertrend.d.ts +6 -0
  464. package/dist/engine/renderers/Indicator/supertrend.d.ts.map +1 -0
  465. package/dist/engine/renderers/Indicator/supertrend.js +149 -0
  466. package/dist/engine/renderers/Indicator/supertrend.js.map +1 -0
  467. package/dist/engine/renderers/Indicator/tema.d.ts +6 -0
  468. package/dist/engine/renderers/Indicator/tema.d.ts.map +1 -0
  469. package/dist/engine/renderers/Indicator/tema.js +164 -0
  470. package/dist/engine/renderers/Indicator/tema.js.map +1 -0
  471. package/dist/engine/renderers/Indicator/trix.d.ts +6 -0
  472. package/dist/engine/renderers/Indicator/trix.d.ts.map +1 -0
  473. package/dist/engine/renderers/Indicator/trix.js +197 -0
  474. package/dist/engine/renderers/Indicator/trix.js.map +1 -0
  475. package/dist/engine/renderers/Indicator/vma.d.ts +5 -0
  476. package/dist/engine/renderers/Indicator/vma.d.ts.map +1 -0
  477. package/dist/engine/renderers/Indicator/vma.js +162 -0
  478. package/dist/engine/renderers/Indicator/vma.js.map +1 -0
  479. package/dist/engine/renderers/Indicator/volumeProfile.d.ts +5 -0
  480. package/dist/engine/renderers/Indicator/volumeProfile.d.ts.map +1 -0
  481. package/dist/engine/renderers/Indicator/volumeProfile.js +165 -0
  482. package/dist/engine/renderers/Indicator/volumeProfile.js.map +1 -0
  483. package/dist/engine/renderers/Indicator/vwap.d.ts +5 -0
  484. package/dist/engine/renderers/Indicator/vwap.d.ts.map +1 -0
  485. package/dist/engine/renderers/Indicator/vwap.js +162 -0
  486. package/dist/engine/renderers/Indicator/vwap.js.map +1 -0
  487. package/dist/engine/renderers/Indicator/wma.d.ts +6 -0
  488. package/dist/engine/renderers/Indicator/wma.d.ts.map +1 -0
  489. package/dist/engine/renderers/Indicator/wma.js +164 -0
  490. package/dist/engine/renderers/Indicator/wma.js.map +1 -0
  491. package/dist/engine/renderers/Indicator/wmsr.d.ts +23 -0
  492. package/dist/engine/renderers/Indicator/wmsr.d.ts.map +1 -0
  493. package/dist/engine/renderers/Indicator/wmsr.js +298 -0
  494. package/dist/engine/renderers/Indicator/wmsr.js.map +1 -0
  495. package/dist/engine/renderers/Indicator/zones.d.ts +5 -0
  496. package/dist/engine/renderers/Indicator/zones.d.ts.map +1 -0
  497. package/dist/engine/renderers/Indicator/zones.js +151 -0
  498. package/dist/engine/renderers/Indicator/zones.js.map +1 -0
  499. package/dist/engine/renderers/candle.d.ts +22 -0
  500. package/dist/engine/renderers/candle.d.ts.map +1 -0
  501. package/dist/engine/renderers/candle.js +356 -0
  502. package/dist/engine/renderers/candle.js.map +1 -0
  503. package/dist/engine/renderers/crosshair.d.ts +18 -0
  504. package/dist/engine/renderers/crosshair.d.ts.map +1 -0
  505. package/dist/engine/renderers/crosshair.js +54 -0
  506. package/dist/engine/renderers/crosshair.js.map +1 -0
  507. package/dist/engine/renderers/customMarkers.d.ts +7 -0
  508. package/dist/engine/renderers/customMarkers.d.ts.map +1 -0
  509. package/dist/engine/renderers/customMarkers.js +145 -0
  510. package/dist/engine/renderers/customMarkers.js.map +1 -0
  511. package/dist/engine/renderers/extremaMarkers.d.ts +6 -0
  512. package/dist/engine/renderers/extremaMarkers.d.ts.map +1 -0
  513. package/dist/engine/renderers/extremaMarkers.js +181 -0
  514. package/dist/engine/renderers/extremaMarkers.js.map +1 -0
  515. package/dist/engine/renderers/gridLines.d.ts +8 -0
  516. package/dist/engine/renderers/gridLines.d.ts.map +1 -0
  517. package/dist/engine/renderers/gridLines.js +84 -0
  518. package/dist/engine/renderers/gridLines.js.map +1 -0
  519. package/dist/engine/renderers/lastPrice.d.ts +10 -0
  520. package/dist/engine/renderers/lastPrice.d.ts.map +1 -0
  521. package/dist/engine/renderers/lastPrice.js +87 -0
  522. package/dist/engine/renderers/lastPrice.js.map +1 -0
  523. package/dist/engine/renderers/paneTitle.d.ts +41 -0
  524. package/dist/engine/renderers/paneTitle.d.ts.map +1 -0
  525. package/dist/engine/renderers/paneTitle.js +86 -0
  526. package/dist/engine/renderers/paneTitle.js.map +1 -0
  527. package/dist/engine/renderers/subVolume.d.ts +14 -0
  528. package/dist/engine/renderers/subVolume.d.ts.map +1 -0
  529. package/dist/engine/renderers/subVolume.js +247 -0
  530. package/dist/engine/renderers/subVolume.js.map +1 -0
  531. package/dist/engine/renderers/timeAxis.d.ts +15 -0
  532. package/dist/engine/renderers/timeAxis.d.ts.map +1 -0
  533. package/dist/engine/renderers/timeAxis.js +107 -0
  534. package/dist/engine/renderers/timeAxis.js.map +1 -0
  535. package/dist/engine/renderers/webgl/candleSurface.d.ts +81 -0
  536. package/dist/engine/renderers/webgl/candleSurface.d.ts.map +1 -0
  537. package/dist/engine/renderers/webgl/candleSurface.js +811 -0
  538. package/dist/engine/renderers/webgl/candleSurface.js.map +1 -0
  539. package/dist/engine/renderers/webgl/sharedWebGLSurface.d.ts +34 -0
  540. package/dist/engine/renderers/webgl/sharedWebGLSurface.d.ts.map +1 -0
  541. package/dist/engine/renderers/webgl/sharedWebGLSurface.js +102 -0
  542. package/dist/engine/renderers/webgl/sharedWebGLSurface.js.map +1 -0
  543. package/dist/engine/renderers/yAxis.d.ts +15 -0
  544. package/dist/engine/renderers/yAxis.d.ts.map +1 -0
  545. package/dist/engine/renderers/yAxis.js +93 -0
  546. package/dist/engine/renderers/yAxis.js.map +1 -0
  547. package/dist/engine/scale/logFormula.d.ts +67 -0
  548. package/dist/engine/scale/logFormula.d.ts.map +1 -0
  549. package/dist/engine/scale/logFormula.js +107 -0
  550. package/dist/engine/scale/logFormula.js.map +1 -0
  551. package/dist/engine/scale/price.d.ts +12 -0
  552. package/dist/engine/scale/price.d.ts.map +1 -0
  553. package/dist/engine/scale/price.js +20 -0
  554. package/dist/engine/scale/price.js.map +1 -0
  555. package/dist/engine/scale/priceScale.d.ts +88 -0
  556. package/dist/engine/scale/priceScale.d.ts.map +1 -0
  557. package/dist/engine/scale/priceScale.js +227 -0
  558. package/dist/engine/scale/priceScale.js.map +1 -0
  559. package/dist/engine/subPaneManager.d.ts +23 -0
  560. package/dist/engine/subPaneManager.d.ts.map +1 -0
  561. package/dist/engine/subPaneManager.js +342 -0
  562. package/dist/engine/subPaneManager.js.map +1 -0
  563. package/dist/engine/theme/colors.d.ts +223 -0
  564. package/dist/engine/theme/colors.d.ts.map +1 -0
  565. package/dist/engine/theme/colors.js +375 -0
  566. package/dist/engine/theme/colors.js.map +1 -0
  567. package/dist/engine/theme/fonts.d.ts +13 -0
  568. package/dist/engine/theme/fonts.d.ts.map +1 -0
  569. package/dist/engine/theme/fonts.js +18 -0
  570. package/dist/engine/theme/fonts.js.map +1 -0
  571. package/dist/engine/utils/klineConfig.d.ts +29 -0
  572. package/dist/engine/utils/klineConfig.d.ts.map +1 -0
  573. package/dist/engine/utils/klineConfig.js +46 -0
  574. package/dist/engine/utils/klineConfig.js.map +1 -0
  575. package/dist/engine/utils/tickCount.d.ts +9 -0
  576. package/dist/engine/utils/tickCount.d.ts.map +1 -0
  577. package/dist/engine/utils/tickCount.js +12 -0
  578. package/dist/engine/utils/tickCount.js.map +1 -0
  579. package/dist/engine/utils/tickPosition.d.ts +25 -0
  580. package/dist/engine/utils/tickPosition.d.ts.map +1 -0
  581. package/dist/engine/utils/tickPosition.js +141 -0
  582. package/dist/engine/utils/tickPosition.js.map +1 -0
  583. package/dist/engine/utils/zoom.d.ts +31 -0
  584. package/dist/engine/utils/zoom.d.ts.map +1 -0
  585. package/dist/engine/utils/zoom.js +43 -0
  586. package/dist/engine/utils/zoom.js.map +1 -0
  587. package/dist/engine/viewport/viewport.d.ts +32 -0
  588. package/dist/engine/viewport/viewport.d.ts.map +1 -0
  589. package/dist/engine/viewport/viewport.js +54 -0
  590. package/dist/engine/viewport/viewport.js.map +1 -0
  591. package/dist/index.d.ts +4 -0
  592. package/dist/index.d.ts.map +1 -0
  593. package/dist/index.js +4 -0
  594. package/dist/index.js.map +1 -0
  595. package/dist/plugin/ConfigManager.d.ts +32 -0
  596. package/dist/plugin/ConfigManager.d.ts.map +1 -0
  597. package/dist/plugin/ConfigManager.js +81 -0
  598. package/dist/plugin/ConfigManager.js.map +1 -0
  599. package/dist/plugin/EventBus.d.ts +38 -0
  600. package/dist/plugin/EventBus.d.ts.map +1 -0
  601. package/dist/plugin/EventBus.js +66 -0
  602. package/dist/plugin/EventBus.js.map +1 -0
  603. package/dist/plugin/HookSystem.d.ts +32 -0
  604. package/dist/plugin/HookSystem.d.ts.map +1 -0
  605. package/dist/plugin/HookSystem.js +86 -0
  606. package/dist/plugin/HookSystem.js.map +1 -0
  607. package/dist/plugin/PluginHost.d.ts +61 -0
  608. package/dist/plugin/PluginHost.d.ts.map +1 -0
  609. package/dist/plugin/PluginHost.js +196 -0
  610. package/dist/plugin/PluginHost.js.map +1 -0
  611. package/dist/plugin/PluginRegistry.d.ts +44 -0
  612. package/dist/plugin/PluginRegistry.d.ts.map +1 -0
  613. package/dist/plugin/PluginRegistry.js +77 -0
  614. package/dist/plugin/PluginRegistry.js.map +1 -0
  615. package/dist/plugin/StateStore.d.ts +42 -0
  616. package/dist/plugin/StateStore.d.ts.map +1 -0
  617. package/dist/plugin/StateStore.js +63 -0
  618. package/dist/plugin/StateStore.js.map +1 -0
  619. package/dist/plugin/index.d.ts +12 -0
  620. package/dist/plugin/index.d.ts.map +1 -0
  621. package/dist/plugin/index.js +15 -0
  622. package/dist/plugin/index.js.map +1 -0
  623. package/dist/plugin/rendererPluginManager.d.ts +81 -0
  624. package/dist/plugin/rendererPluginManager.d.ts.map +1 -0
  625. package/dist/plugin/rendererPluginManager.js +309 -0
  626. package/dist/plugin/rendererPluginManager.js.map +1 -0
  627. package/dist/plugin/stateKeys.d.ts +7 -0
  628. package/dist/plugin/stateKeys.d.ts.map +1 -0
  629. package/dist/plugin/stateKeys.js +7 -0
  630. package/dist/plugin/stateKeys.js.map +1 -0
  631. package/dist/plugin/types.d.ts +449 -0
  632. package/dist/plugin/types.d.ts.map +1 -0
  633. package/dist/plugin/types.js +63 -0
  634. package/dist/plugin/types.js.map +1 -0
  635. package/dist/reactivity/index.d.ts +3 -0
  636. package/dist/reactivity/index.d.ts.map +1 -0
  637. package/dist/reactivity/index.js +2 -0
  638. package/dist/reactivity/index.js.map +1 -0
  639. package/dist/reactivity/signal.d.ts +40 -0
  640. package/dist/reactivity/signal.d.ts.map +1 -0
  641. package/dist/reactivity/signal.js +92 -0
  642. package/dist/reactivity/signal.js.map +1 -0
  643. package/dist/semantic/controller.d.ts +41 -0
  644. package/dist/semantic/controller.d.ts.map +1 -0
  645. package/dist/semantic/controller.js +189 -0
  646. package/dist/semantic/controller.js.map +1 -0
  647. package/dist/semantic/drawShape.d.ts +19 -0
  648. package/dist/semantic/drawShape.d.ts.map +1 -0
  649. package/dist/semantic/drawShape.js +209 -0
  650. package/dist/semantic/drawShape.js.map +1 -0
  651. package/dist/semantic/index.d.ts +6 -0
  652. package/dist/semantic/index.d.ts.map +1 -0
  653. package/dist/semantic/index.js +4 -0
  654. package/dist/semantic/index.js.map +1 -0
  655. package/dist/semantic/schema.json +256 -0
  656. package/dist/semantic/types.d.ts +299 -0
  657. package/dist/semantic/types.d.ts.map +1 -0
  658. package/dist/semantic/types.js +6 -0
  659. package/dist/semantic/types.js.map +1 -0
  660. package/dist/semantic/validator.d.ts +48 -0
  661. package/dist/semantic/validator.d.ts.map +1 -0
  662. package/dist/semantic/validator.js +288 -0
  663. package/dist/semantic/validator.js.map +1 -0
  664. package/dist/types/kLine.d.ts +4 -0
  665. package/dist/types/kLine.d.ts.map +1 -0
  666. package/dist/types/kLine.js +12 -0
  667. package/dist/types/kLine.js.map +1 -0
  668. package/dist/types/price.d.ts +32 -0
  669. package/dist/types/price.d.ts.map +1 -0
  670. package/dist/types/price.js +19 -0
  671. package/dist/types/price.js.map +1 -0
  672. package/dist/types/volumePrice.d.ts +27 -0
  673. package/dist/types/volumePrice.d.ts.map +1 -0
  674. package/dist/types/volumePrice.js +23 -0
  675. package/dist/types/volumePrice.js.map +1 -0
  676. package/dist/utils/dateFormat.d.ts +84 -0
  677. package/dist/utils/dateFormat.d.ts.map +1 -0
  678. package/dist/utils/dateFormat.js +193 -0
  679. package/dist/utils/dateFormat.js.map +1 -0
  680. package/dist/utils/kLineDraw/axis.d.ts +151 -0
  681. package/dist/utils/kLineDraw/axis.d.ts.map +1 -0
  682. package/dist/utils/kLineDraw/axis.js +243 -0
  683. package/dist/utils/kLineDraw/axis.js.map +1 -0
  684. package/dist/utils/priceToY.d.ts +8 -0
  685. package/dist/utils/priceToY.d.ts.map +1 -0
  686. package/dist/utils/priceToY.js +19 -0
  687. package/dist/utils/priceToY.js.map +1 -0
  688. package/dist/utils/volumePrice.d.ts +55 -0
  689. package/dist/utils/volumePrice.d.ts.map +1 -0
  690. package/dist/utils/volumePrice.js +170 -0
  691. package/dist/utils/volumePrice.js.map +1 -0
  692. package/dist/version.d.ts +2 -0
  693. package/dist/version.d.ts.map +1 -0
  694. package/dist/version.js +3 -0
  695. package/dist/version.js.map +1 -0
  696. package/package.json +122 -0
  697. package/src/__tests__/signal.test.ts +124 -0
  698. package/src/config/chartSettings.ts +66 -0
  699. package/src/controllers/__tests__/drawing.test.ts +214 -0
  700. package/src/controllers/__tests__/indicatorSelector.test.ts +481 -0
  701. package/src/controllers/__tests__/toolbar.test.ts +225 -0
  702. package/src/controllers/createChartController.ts +665 -0
  703. package/src/controllers/createDrawingController.ts +96 -0
  704. package/src/controllers/createIndicatorSelectorController.ts +307 -0
  705. package/src/controllers/createToolbarController.ts +146 -0
  706. package/src/controllers/index.ts +19 -0
  707. package/src/controllers/types.ts +284 -0
  708. package/src/engine/__tests__/chart.dpr.test.ts +401 -0
  709. package/src/engine/__tests__/paneRenderer.resize.test.ts +92 -0
  710. package/src/engine/chart-store.ts +121 -0
  711. package/src/engine/chart.d.ts +618 -0
  712. package/src/engine/chart.ts +2815 -0
  713. package/src/engine/controller/__tests__/interaction.dpr.test.ts +259 -0
  714. package/src/engine/controller/interaction.ts +722 -0
  715. package/src/engine/controller/markerInteraction.ts +130 -0
  716. package/src/engine/controller/pinchTracker.ts +82 -0
  717. package/src/engine/controller/tooltipPosition.ts +48 -0
  718. package/src/engine/draw/__tests__/pixelAlign.spec.ts +177 -0
  719. package/src/engine/draw/pixelAlign.ts +260 -0
  720. package/src/engine/drawing/index.ts +655 -0
  721. package/src/engine/drawing/interaction.ts +842 -0
  722. package/src/engine/drawing/plugin.ts +343 -0
  723. package/src/engine/indicators/__tests__/__fixtures__/golden/atr.json +38 -0
  724. package/src/engine/indicators/__tests__/__fixtures__/golden/dema.json +14 -0
  725. package/src/engine/indicators/__tests__/__fixtures__/golden/hma.json +14 -0
  726. package/src/engine/indicators/__tests__/__fixtures__/golden/index.ts +55 -0
  727. package/src/engine/indicators/__tests__/__fixtures__/golden/kama.json +14 -0
  728. package/src/engine/indicators/__tests__/__fixtures__/golden/tema.json +14 -0
  729. package/src/engine/indicators/__tests__/__fixtures__/golden/wma.json +40 -0
  730. package/src/engine/indicators/__tests__/__fixtures__/synthetic.ts +65 -0
  731. package/src/engine/indicators/__tests__/_propertyAssertions.ts +76 -0
  732. package/src/engine/indicators/__tests__/atr.test.ts +153 -0
  733. package/src/engine/indicators/__tests__/calculators.test.ts +614 -0
  734. package/src/engine/indicators/__tests__/cmf-mfi.test.ts +100 -0
  735. package/src/engine/indicators/__tests__/dema.test.ts +73 -0
  736. package/src/engine/indicators/__tests__/donchian.test.ts +70 -0
  737. package/src/engine/indicators/__tests__/hma.test.ts +73 -0
  738. package/src/engine/indicators/__tests__/ichimoku.test.ts +105 -0
  739. package/src/engine/indicators/__tests__/kama.test.ts +80 -0
  740. package/src/engine/indicators/__tests__/keltner.test.ts +65 -0
  741. package/src/engine/indicators/__tests__/pivot-fib.test.ts +110 -0
  742. package/src/engine/indicators/__tests__/roc.test.ts +68 -0
  743. package/src/engine/indicators/__tests__/sar.test.ts +86 -0
  744. package/src/engine/indicators/__tests__/scheduler.test.ts +831 -0
  745. package/src/engine/indicators/__tests__/soa.test.ts +533 -0
  746. package/src/engine/indicators/__tests__/structure.test.ts +110 -0
  747. package/src/engine/indicators/__tests__/supertrend.test.ts +65 -0
  748. package/src/engine/indicators/__tests__/tema.test.ts +68 -0
  749. package/src/engine/indicators/__tests__/trix.test.ts +70 -0
  750. package/src/engine/indicators/__tests__/volatility.test.ts +117 -0
  751. package/src/engine/indicators/__tests__/volume.test.ts +115 -0
  752. package/src/engine/indicators/__tests__/volumeProfile.test.ts +74 -0
  753. package/src/engine/indicators/__tests__/vwap.test.ts +69 -0
  754. package/src/engine/indicators/__tests__/wma.test.ts +112 -0
  755. package/src/engine/indicators/__tests__/zones.test.ts +95 -0
  756. package/src/engine/indicators/atrState.ts +27 -0
  757. package/src/engine/indicators/bollState.ts +51 -0
  758. package/src/engine/indicators/calculators.ts +2593 -0
  759. package/src/engine/indicators/cciState.ts +25 -0
  760. package/src/engine/indicators/chaikinVolState.ts +32 -0
  761. package/src/engine/indicators/cmfState.ts +27 -0
  762. package/src/engine/indicators/demaState.ts +27 -0
  763. package/src/engine/indicators/donchianState.ts +43 -0
  764. package/src/engine/indicators/eneState.ts +43 -0
  765. package/src/engine/indicators/expmaState.ts +43 -0
  766. package/src/engine/indicators/fastkState.ts +25 -0
  767. package/src/engine/indicators/fibState.ts +41 -0
  768. package/src/engine/indicators/hmaState.ts +27 -0
  769. package/src/engine/indicators/hvState.ts +28 -0
  770. package/src/engine/indicators/ichimokuState.ts +70 -0
  771. package/src/engine/indicators/indicator.worker.ts +169 -0
  772. package/src/engine/indicators/indicatorDefinitionRegistry.ts +62 -0
  773. package/src/engine/indicators/indicatorMetadata.ts +110 -0
  774. package/src/engine/indicators/indicatorRegistry.ts +106 -0
  775. package/src/engine/indicators/indicatorRuntime.ts +1548 -0
  776. package/src/engine/indicators/kamaState.ts +34 -0
  777. package/src/engine/indicators/keltnerState.ts +49 -0
  778. package/src/engine/indicators/kstState.ts +42 -0
  779. package/src/engine/indicators/maState.ts +36 -0
  780. package/src/engine/indicators/macdState.ts +76 -0
  781. package/src/engine/indicators/mfiState.ts +27 -0
  782. package/src/engine/indicators/momState.ts +25 -0
  783. package/src/engine/indicators/obvState.ts +25 -0
  784. package/src/engine/indicators/parkinsonState.ts +28 -0
  785. package/src/engine/indicators/pivotState.ts +51 -0
  786. package/src/engine/indicators/pvtState.ts +25 -0
  787. package/src/engine/indicators/rocState.ts +27 -0
  788. package/src/engine/indicators/rsiState.ts +65 -0
  789. package/src/engine/indicators/sarState.ts +41 -0
  790. package/src/engine/indicators/scheduler.ts +1205 -0
  791. package/src/engine/indicators/soa.ts +352 -0
  792. package/src/engine/indicators/stateComposer.ts +1262 -0
  793. package/src/engine/indicators/stochState.ts +26 -0
  794. package/src/engine/indicators/structureState.ts +69 -0
  795. package/src/engine/indicators/supertrendState.ts +37 -0
  796. package/src/engine/indicators/temaState.ts +27 -0
  797. package/src/engine/indicators/trixState.ts +35 -0
  798. package/src/engine/indicators/vmaState.ts +27 -0
  799. package/src/engine/indicators/volumeProfileState.ts +63 -0
  800. package/src/engine/indicators/vwapState.ts +29 -0
  801. package/src/engine/indicators/wmaState.ts +27 -0
  802. package/src/engine/indicators/wmsrState.ts +25 -0
  803. package/src/engine/indicators/workerProtocol.ts +613 -0
  804. package/src/engine/indicators/zonesState.ts +47 -0
  805. package/src/engine/layout/pane.ts +161 -0
  806. package/src/engine/marker/registry.ts +266 -0
  807. package/src/engine/paneRenderer.ts +169 -0
  808. package/src/engine/renderers/Indicator/atr.ts +237 -0
  809. package/src/engine/renderers/Indicator/boll.ts +317 -0
  810. package/src/engine/renderers/Indicator/cci.ts +275 -0
  811. package/src/engine/renderers/Indicator/chaikinVol.ts +138 -0
  812. package/src/engine/renderers/Indicator/cmf.ts +137 -0
  813. package/src/engine/renderers/Indicator/dema.ts +136 -0
  814. package/src/engine/renderers/Indicator/donchian.ts +138 -0
  815. package/src/engine/renderers/Indicator/ene.ts +271 -0
  816. package/src/engine/renderers/Indicator/expma.ts +197 -0
  817. package/src/engine/renderers/Indicator/fastk.ts +316 -0
  818. package/src/engine/renderers/Indicator/fib.ts +141 -0
  819. package/src/engine/renderers/Indicator/hma.ts +136 -0
  820. package/src/engine/renderers/Indicator/hv.ts +124 -0
  821. package/src/engine/renderers/Indicator/ichimoku.ts +182 -0
  822. package/src/engine/renderers/Indicator/index.ts +241 -0
  823. package/src/engine/renderers/Indicator/indicatorData.ts +650 -0
  824. package/src/engine/renderers/Indicator/kama.ts +136 -0
  825. package/src/engine/renderers/Indicator/keltner.ts +138 -0
  826. package/src/engine/renderers/Indicator/kst.ts +302 -0
  827. package/src/engine/renderers/Indicator/ma.ts +200 -0
  828. package/src/engine/renderers/Indicator/macd.ts +477 -0
  829. package/src/engine/renderers/Indicator/macdLegend.ts +141 -0
  830. package/src/engine/renderers/Indicator/mainIndicatorLegend.ts +272 -0
  831. package/src/engine/renderers/Indicator/mfi.ts +142 -0
  832. package/src/engine/renderers/Indicator/mom.ts +311 -0
  833. package/src/engine/renderers/Indicator/obv.ts +123 -0
  834. package/src/engine/renderers/Indicator/parkinson.ts +124 -0
  835. package/src/engine/renderers/Indicator/pivot.ts +131 -0
  836. package/src/engine/renderers/Indicator/pvt.ts +123 -0
  837. package/src/engine/renderers/Indicator/roc.ts +143 -0
  838. package/src/engine/renderers/Indicator/rsi.ts +390 -0
  839. package/src/engine/renderers/Indicator/sar.ts +113 -0
  840. package/src/engine/renderers/Indicator/scale/atr_scale.ts +19 -0
  841. package/src/engine/renderers/Indicator/scale/cci_scale.ts +19 -0
  842. package/src/engine/renderers/Indicator/scale/fastk_scale.ts +19 -0
  843. package/src/engine/renderers/Indicator/scale/indicator_scale.ts +204 -0
  844. package/src/engine/renderers/Indicator/scale/kst_scale.ts +19 -0
  845. package/src/engine/renderers/Indicator/scale/macd_scale.ts +22 -0
  846. package/src/engine/renderers/Indicator/scale/mom_scale.ts +19 -0
  847. package/src/engine/renderers/Indicator/scale/rsi_scale.ts +19 -0
  848. package/src/engine/renderers/Indicator/scale/stoch_scale.ts +19 -0
  849. package/src/engine/renderers/Indicator/scale/volume_scale.ts +26 -0
  850. package/src/engine/renderers/Indicator/scale/wmsr_scale.ts +19 -0
  851. package/src/engine/renderers/Indicator/stoch.ts +359 -0
  852. package/src/engine/renderers/Indicator/structure.ts +126 -0
  853. package/src/engine/renderers/Indicator/subPaneConfig.ts +265 -0
  854. package/src/engine/renderers/Indicator/supertrend.ts +115 -0
  855. package/src/engine/renderers/Indicator/tema.ts +136 -0
  856. package/src/engine/renderers/Indicator/trix.ts +158 -0
  857. package/src/engine/renderers/Indicator/vma.ts +124 -0
  858. package/src/engine/renderers/Indicator/volumeProfile.ts +125 -0
  859. package/src/engine/renderers/Indicator/vwap.ts +123 -0
  860. package/src/engine/renderers/Indicator/wma.ts +136 -0
  861. package/src/engine/renderers/Indicator/wmsr.ts +328 -0
  862. package/src/engine/renderers/Indicator/zones.ts +105 -0
  863. package/src/engine/renderers/__tests__/boll.renderer.test.ts +314 -0
  864. package/src/engine/renderers/__tests__/ene.renderer.test.ts +305 -0
  865. package/src/engine/renderers/__tests__/expma.renderer.test.ts +279 -0
  866. package/src/engine/renderers/__tests__/ma.renderer.test.ts +426 -0
  867. package/src/engine/renderers/__tests__/mainIndicatorLegend.renderer.test.ts +502 -0
  868. package/src/engine/renderers/__tests__/yAxis.renderer.test.ts +173 -0
  869. package/src/engine/renderers/candle.ts +459 -0
  870. package/src/engine/renderers/crosshair.ts +69 -0
  871. package/src/engine/renderers/customMarkers.ts +162 -0
  872. package/src/engine/renderers/extremaMarkers.ts +246 -0
  873. package/src/engine/renderers/gridLines.ts +90 -0
  874. package/src/engine/renderers/lastPrice.ts +97 -0
  875. package/src/engine/renderers/paneTitle.ts +136 -0
  876. package/src/engine/renderers/subVolume.ts +236 -0
  877. package/src/engine/renderers/timeAxis.ts +121 -0
  878. package/src/engine/renderers/webgl/candleSurface.ts +955 -0
  879. package/src/engine/renderers/webgl/sharedWebGLSurface.ts +146 -0
  880. package/src/engine/renderers/yAxis.ts +105 -0
  881. package/src/engine/scale/__tests__/logFormula.spec.ts +148 -0
  882. package/src/engine/scale/logFormula.ts +130 -0
  883. package/src/engine/scale/price.ts +39 -0
  884. package/src/engine/scale/priceScale.ts +264 -0
  885. package/src/engine/subPaneManager.ts +427 -0
  886. package/src/engine/theme/colors.ts +642 -0
  887. package/src/engine/theme/fonts.ts +20 -0
  888. package/src/engine/utils/klineConfig.ts +49 -0
  889. package/src/engine/utils/tickCount.ts +11 -0
  890. package/src/engine/utils/tickPosition.ts +214 -0
  891. package/src/engine/utils/zoom.ts +83 -0
  892. package/src/engine/viewport/viewport.ts +67 -0
  893. package/src/index.ts +3 -0
  894. package/src/plugin/ConfigManager.ts +93 -0
  895. package/src/plugin/EventBus.ts +77 -0
  896. package/src/plugin/HookSystem.ts +106 -0
  897. package/src/plugin/PluginHost.ts +243 -0
  898. package/src/plugin/PluginRegistry.ts +92 -0
  899. package/src/plugin/StateStore.ts +73 -0
  900. package/src/plugin/index.ts +19 -0
  901. package/src/plugin/rendererPluginManager.ts +368 -0
  902. package/src/plugin/stateKeys.ts +8 -0
  903. package/src/plugin/types.ts +526 -0
  904. package/src/reactivity/index.ts +2 -0
  905. package/src/reactivity/signal.ts +119 -0
  906. package/src/semantic/controller.ts +251 -0
  907. package/src/semantic/drawShape.ts +260 -0
  908. package/src/semantic/index.ts +28 -0
  909. package/src/semantic/schema.json +256 -0
  910. package/src/semantic/types.ts +251 -0
  911. package/src/semantic/validator.ts +349 -0
  912. package/src/types/kLine.ts +13 -0
  913. package/src/types/price.ts +56 -0
  914. package/src/types/volumePrice.ts +33 -0
  915. package/src/utils/dateFormat.ts +208 -0
  916. package/src/utils/kLineDraw/axis.ts +562 -0
  917. package/src/utils/priceToY.ts +34 -0
  918. package/src/utils/volumePrice.ts +203 -0
  919. package/src/version.ts +1 -0
@@ -0,0 +1,2815 @@
1
+ import type { KLineData } from '../types/price'
2
+ import type { ChartSettings } from '../config/chartSettings'
3
+ import { createSignal, type Signal } from '../reactivity/signal'
4
+ import { getVisibleRange } from './viewport/viewport'
5
+ import { Pane, type VisibleRange, UpdateLevel } from './layout/pane'
6
+ import { InteractionController, type InteractionSnapshot } from './controller/interaction'
7
+ export type { InteractionSnapshot }
8
+ import { PaneRenderer } from './paneRenderer'
9
+ import { SharedWebGLSurface } from './renderers/webgl/sharedWebGLSurface'
10
+ import { MarkerManager, type CustomMarkerEntity } from './marker/registry'
11
+ import { getPhysicalKLineConfig, calcKWidthPx } from './utils/klineConfig'
12
+ import { computeContentWidth } from './chart-store'
13
+ import { computeZoom, computeZoomToLevel, type ZoomConfig } from './utils/zoom'
14
+ import { IndicatorScheduler } from './indicators/scheduler'
15
+ import { getRegisteredIndicatorDefinitions } from './indicators/indicatorDefinitionRegistry'
16
+ import { SubPaneManager, type SubPaneEntry } from './subPaneManager'
17
+
18
+ import {
19
+ createPluginHost,
20
+ type PluginHostImpl,
21
+ RendererPluginManager,
22
+ type RendererPlugin,
23
+ type RendererPluginWithHost,
24
+ type RenderContext,
25
+ wrapPaneInfo,
26
+ type PaneRole,
27
+ type PaneCapabilities,
28
+ type YAxisLabel,
29
+ type XAxisLabel,
30
+ type YAxisRange,
31
+ type XAxisRange,
32
+ } from '../plugin'
33
+ import { createSubIndicatorRenderer, type SubIndicatorType } from './renderers/Indicator'
34
+ import { createMARendererPlugin } from './renderers/Indicator/ma'
35
+ import { createBOLLRendererPlugin } from './renderers/Indicator/boll'
36
+ import { createEXPMARendererPlugin } from './renderers/Indicator/expma'
37
+ import { createENERendererPlugin } from './renderers/Indicator/ene'
38
+ import { createWMARendererPlugin } from './renderers/Indicator/wma'
39
+ import { createDEMARendererPlugin } from './renderers/Indicator/dema'
40
+ import { createTEMARendererPlugin } from './renderers/Indicator/tema'
41
+ import { createHMARendererPlugin } from './renderers/Indicator/hma'
42
+ import { createKAMARendererPlugin } from './renderers/Indicator/kama'
43
+ import { createSARRendererPlugin } from './renderers/Indicator/sar'
44
+ import { createSuperTrendRendererPlugin } from './renderers/Indicator/supertrend'
45
+ import { createKeltnerRendererPlugin } from './renderers/Indicator/keltner'
46
+ import { createDonchianRendererPlugin } from './renderers/Indicator/donchian'
47
+ import { createIchimokuRendererPlugin } from './renderers/Indicator/ichimoku'
48
+ import { createPivotRendererPlugin } from './renderers/Indicator/pivot'
49
+ import { createFibRendererPlugin } from './renderers/Indicator/fib'
50
+ import { createStructureRendererPlugin } from './renderers/Indicator/structure'
51
+ import { createZonesRendererPlugin } from './renderers/Indicator/zones'
52
+ import { createMainIndicatorLegendRendererPlugin } from './renderers/Indicator/mainIndicatorLegend'
53
+ import { DrawingStore } from './drawing'
54
+ import { createDrawingRendererPlugin, createDrawingLabelOverlayPlugin } from './drawing/plugin'
55
+ import { createGridLinesRendererPlugin } from './renderers/gridLines'
56
+ import { createCandleRenderer } from './renderers/candle'
57
+ import { createLastPriceLineRendererPlugin, createLastPriceLabelRegistrarPlugin } from './renderers/lastPrice'
58
+ import { createCustomMarkersRenderer } from './renderers/customMarkers'
59
+ import { createYAxisRendererPlugin } from './renderers/yAxis'
60
+ import { createCrosshairRendererPlugin } from './renderers/crosshair'
61
+ import { createTimeAxisRendererPlugin } from './renderers/timeAxis'
62
+ import type { BOLLSchedulerConfig, EXPMASchedulerConfig, ENESchedulerConfig, WMASchedulerConfig, DEMASchedulerConfig, TEMASchedulerConfig, HMASchedulerConfig, KAMASchedulerConfig, SARSchedulerConfig, SuperTrendSchedulerConfig, KeltnerSchedulerConfig, DonchianSchedulerConfig, IchimokuSchedulerConfig, PivotSchedulerConfig, FibSchedulerConfig, StructureSchedulerConfig, ZonesSchedulerConfig } from './indicators/scheduler'
63
+
64
+ // 重新导出以保持向后兼容
65
+ export { getPhysicalKLineConfig, calcKWidthPx }
66
+
67
+ /**
68
+ * 图表 DOM 元素引用
69
+ * @property container 图表容器 div
70
+ * @property canvasLayer Canvas 层容器 div(包含所有绘制 canvas)
71
+ */
72
+ /**
73
+ * 图表 DOM 元素引用
74
+ * @property container 图表容器 div
75
+ * @property canvasLayer Canvas 层容器 div(包含所有绘制 canvas)
76
+ * @property xAxisCanvas X 轴时间轴 canvas
77
+ */
78
+ export type ChartDom = {
79
+ container: HTMLDivElement
80
+ canvasLayer: HTMLDivElement
81
+ rightAxisLayer: HTMLDivElement
82
+ xAxisCanvas: HTMLCanvasElement
83
+ }
84
+
85
+ /**
86
+ * Pane 面板配置
87
+ * @property id Pane 标识符
88
+ * @property ratio Pane 高度占比
89
+ * @property visible 是否可见(默认 true)
90
+ */
91
+ export type PaneSpec = {
92
+ id: string
93
+ ratio: number
94
+ visible?: boolean
95
+ minHeightPx?: number
96
+ role?: PaneRole
97
+ capabilities?: Partial<PaneCapabilities>
98
+ }
99
+
100
+ export type PaneRendererDom = {
101
+ mainCanvas: HTMLCanvasElement
102
+ overlayCanvas: HTMLCanvasElement
103
+ yAxisCanvas: HTMLCanvasElement
104
+ }
105
+
106
+ export type ChartOptions = {
107
+ /** K 线宽度(可选,由 zoomLevel 派生) */
108
+ kWidth?: number
109
+ /** K 线间隙(可选,由 DPR 计算) */
110
+ kGap?: number
111
+ yPaddingPx: number
112
+ rightAxisWidth: number
113
+ bottomAxisHeight: number
114
+ minKWidth: number
115
+ maxKWidth: number
116
+ panes: PaneSpec[]
117
+
118
+ /** pane 之间的真实分隔空隙(逻辑像素) */
119
+ paneGap?: number
120
+
121
+ /** 价格标签额外宽度(用于显示涨跌幅,默认 60px) */
122
+ priceLabelWidth?: number
123
+
124
+ /** pane 最小高度(逻辑像素,默认 60) */
125
+ defaultPaneMinHeightPx?: number
126
+
127
+ /**
128
+ * 缩放级别数量(默认 10)
129
+ * - 将 minKWidth ~ maxKWidth 划分为多少个离散级别
130
+ * - 例如 10 表示有 10 个缩放级别(1-10)
131
+ */
132
+ zoomLevels?: number
133
+
134
+ /**
135
+ * 初始缩放级别(1 ~ zoomLevels,默认 1)
136
+ * 未指定时默认为最小级别
137
+ */
138
+ initialZoomLevel?: number
139
+ }
140
+
141
+ /** K 线起始 x 坐标数组,positions[i] 表示第 i 根 K 线的起始 x 坐标(逻辑像素) */
142
+ export type KLinePositions = number[]
143
+
144
+ export type Viewport = {
145
+ viewWidth: number
146
+ viewHeight: number
147
+ plotWidth: number
148
+ plotHeight: number
149
+ scrollLeft: number
150
+ dpr: number
151
+ }
152
+
153
+ type ResolvedChartOptions = Omit<ChartOptions, 'kWidth' | 'kGap'> & {
154
+ kWidth: number
155
+ kGap: number
156
+ }
157
+
158
+ type FrameData = {
159
+ vp: Viewport
160
+ range: VisibleRange
161
+ kLinePositions: KLinePositions
162
+ kLineCenters: number[]
163
+ kBarRects: Array<{ x: number; width: number }>
164
+ kWidthPx: number
165
+ useCachedFrame: boolean
166
+ }
167
+
168
+ export class Chart {
169
+ private dom: ChartDom
170
+ private opt: ResolvedChartOptions
171
+ private _internalData: KLineData[] = []
172
+
173
+ private raf: number | null = null
174
+ private pendingUpdateLevel: UpdateLevel = UpdateLevel.All
175
+ private _internalViewport: Viewport | null = null
176
+
177
+ private paneRenderers: PaneRenderer[] = []
178
+ private markerManager: MarkerManager
179
+ private drawingStore = new DrawingStore()
180
+ readonly interaction: InteractionController
181
+
182
+ /** 插件宿主 */
183
+ private pluginHost: PluginHostImpl
184
+
185
+ /** 渲染器插件管理器 */
186
+ private rendererPluginManager: RendererPluginManager
187
+
188
+ /** 精确 DPR(来自 ResizeObserver 的 devicePixelContentBoxSize) */
189
+ private preciseDpr = 0
190
+
191
+ /** 统一监听容器尺寸与 DPR 变化 */
192
+ private resizeObserver?: ResizeObserver
193
+
194
+ /** scroll 事件处理器引用(用于 cleanup) */
195
+ private onScroll?: () => void
196
+
197
+ /** 最近一次观测到的容器尺寸 */
198
+ private observedSize = { width: 0, height: 0 }
199
+
200
+ /** 缓存的 scrollLeft(通过 scroll 事件同步,避免每帧读取 DOM 触发强制回流) */
201
+ private cachedScrollLeft = 0
202
+
203
+ /** overlay 上一帧是否有十字线(用于判断何时需要清除) */
204
+ private overlayHadCrosshair = false
205
+
206
+ /** 用户设置配置(传递给渲染器) */
207
+ private settings: ChartSettings = {}
208
+
209
+ /** pane ratio 状态(按 paneId 维护,sum=1 仅对可见 pane) */
210
+ private _internalPaneRatios: Map<string, number> = new Map()
211
+
212
+ /** 视口变化回调(供外部同步 DPR/尺寸) */
213
+ private onViewportChange?: (viewport: Viewport) => void
214
+
215
+ /** 共享 X 轴上下文缓存 */
216
+ private xAxisCtx: CanvasRenderingContext2D | null = null
217
+
218
+ /** Chart 级共享 WebGL canvas/context */
219
+ private sharedWebGLSurface: SharedWebGLSurface
220
+
221
+ /** pane 布局回流回调(Chart -> UI 单向) */
222
+ private onPaneLayoutChange?: (panes: PaneSpec[]) => void
223
+
224
+ /** 数据变化回调(供外部同步 dataLength) */
225
+ private onDataChange?: (data: KLineData[]) => void
226
+
227
+ /** 当前缩放级别(1 ~ zoomLevelCount) */
228
+ private currentZoomLevel: number = 1
229
+
230
+ /** 缩放级别总数 */
231
+ private readonly zoomLevelCount: number
232
+
233
+ /** 指标调度器(负责计算 MA 等指标并写入 StateStore)
234
+ * TODO: 阶段5迁移为插件注册,Scheduler 通过事件监听 data/viewport 变更,Chart 不直接持有
235
+ */
236
+ private indicatorScheduler: IndicatorScheduler
237
+
238
+ /** 上次可见范围(用于检测视口变化) */
239
+ private lastVisibleRange: VisibleRange = { start: 0, end: 0 }
240
+
241
+ /** Overlay 帧复用的最近主渲染结果 */
242
+ private cachedDrawFrame: {
243
+ viewport: Viewport
244
+ range: VisibleRange
245
+ kLinePositions: KLinePositions
246
+ kLineCenters: number[]
247
+ kBarRects: Array<{ x: number; width: number }>
248
+ kWidthPx: number
249
+ } | null = null
250
+
251
+ /** 副图管理器 */
252
+ private subPaneManager: SubPaneManager
253
+
254
+ /** 当前激活的主图指标列表(如 ['boll', 'ma']) */
255
+ private activeMainIndicators: Set<string> = new Set()
256
+
257
+ /** 主图指标参数配置 */
258
+ private mainIndicatorParams: Record<string, Record<string, number | boolean | string>> = {
259
+ MA: { ma5: true, ma10: true, ma20: true, ma30: true, ma60: true },
260
+ BOLL: { period: 20, multiplier: 2, showUpper: true, showMiddle: true, showLower: true, showBand: true },
261
+ EXPMA: { fastPeriod: 12, slowPeriod: 50 },
262
+ ENE: { period: 10, deviation: 11 },
263
+ WMA: { period: 10, showWMA: true },
264
+ DEMA: { period: 14, showDEMA: true },
265
+ TEMA: { period: 14, showTEMA: true },
266
+ HMA: { period: 14, showHMA: true },
267
+ KAMA: { period: 10, fastPeriod: 2, slowPeriod: 30, showKAMA: true },
268
+ SAR: { step: 0.02, maxStep: 0.2, showSAR: true },
269
+ SUPERTREND: { atrPeriod: 10, multiplier: 3, showSuperTrend: true },
270
+ KELTNER: { emaPeriod: 20, atrPeriod: 10, multiplier: 2, showUpper: true, showMiddle: true, showLower: true },
271
+ DONCHIAN: { period: 20, showUpper: true, showMiddle: true, showLower: true },
272
+ ICHIMOKU: { tenkanPeriod: 9, kijunPeriod: 26, spanBPeriod: 52, displacement: 26, showTenkan: true, showKijun: true, showSpanA: true, showSpanB: true, showChikou: true, showCloud: true },
273
+ PIVOT: { showPP: true, showR1: true, showR2: true, showR3: false, showS1: true, showS2: true, showS3: false },
274
+ FIB: { period: 50, showLevels: true },
275
+ STRUCTURE: { leftWindow: 2, rightWindow: 2, breakoutSource: 'close', showSwingLabels: true, showBOS: true, showCHOCH: true, showProvisional: false },
276
+ ZONES: { showFVG: true, showOB: true, showFilledZones: false, obLookback: 5 },
277
+ }
278
+
279
+ /**
280
+ * 启用主图指标
281
+ * @param indicatorId 指标ID
282
+ * @param params 可选的指标参数
283
+ * @returns 是否成功启用
284
+ */
285
+ enableMainIndicator(indicatorId: string, params?: Record<string, number | boolean | string>): boolean {
286
+ const id = indicatorId.toUpperCase()
287
+ if (!['MA', 'BOLL', 'EXPMA', 'ENE', 'WMA', 'DEMA', 'TEMA', 'HMA', 'KAMA', 'SAR', 'SUPERTREND', 'KELTNER', 'DONCHIAN', 'ICHIMOKU', 'PIVOT', 'FIB', 'STRUCTURE', 'ZONES'].includes(id)) {
288
+ console.warn(`[Chart] 未知的主图指标: ${indicatorId}`)
289
+ return false
290
+ }
291
+
292
+ if (this.activeMainIndicators.has(id)) {
293
+ // 已启用,更新参数
294
+ if (params) {
295
+ this.mainIndicatorParams[id] = { ...this.mainIndicatorParams[id], ...params }
296
+ this.updateIndicatorSchedulerConfig(id)
297
+ this.syncIndicatorsSignal()
298
+ }
299
+ return true
300
+ }
301
+
302
+ this.activeMainIndicators.add(id)
303
+
304
+ // 合并默认参数和传入参数
305
+ if (params) {
306
+ this.mainIndicatorParams[id] = { ...this.mainIndicatorParams[id], ...params }
307
+ }
308
+
309
+ // 启用对应的渲染器
310
+ this.enableMainIndicatorRenderer(id)
311
+
312
+ // 更新调度器配置
313
+ this.updateIndicatorSchedulerConfig(id)
314
+
315
+ this.scheduleDraw()
316
+ this.syncIndicatorsSignal()
317
+ return true
318
+ }
319
+
320
+ /**
321
+ * 禁用主图指标
322
+ * @param indicatorId 指标ID
323
+ * @returns 是否成功禁用
324
+ */
325
+ disableMainIndicator(indicatorId: string): boolean {
326
+ const id = indicatorId.toUpperCase()
327
+ if (!this.activeMainIndicators.has(id)) return false
328
+
329
+ this.activeMainIndicators.delete(id)
330
+
331
+ // 禁用对应的渲染器
332
+ this.disableMainIndicatorRenderer(id)
333
+
334
+ // 更新调度器配置
335
+ this.updateIndicatorSchedulerConfig(id)
336
+
337
+ this.scheduleDraw()
338
+ this.syncIndicatorsSignal()
339
+ return true
340
+ }
341
+
342
+ /**
343
+ * 切换主图指标启用状态
344
+ * @param indicatorId 指标ID
345
+ * @param enabled 是否启用
346
+ */
347
+ toggleMainIndicator(indicatorId: string, enabled: boolean): void {
348
+ if (enabled) {
349
+ this.enableMainIndicator(indicatorId)
350
+ } else {
351
+ this.disableMainIndicator(indicatorId)
352
+ }
353
+ }
354
+
355
+ /**
356
+ * 获取当前激活的主图指标列表
357
+ * @returns 激活的指标ID数组
358
+ */
359
+ getActiveMainIndicators(): string[] {
360
+ return Array.from(this.activeMainIndicators)
361
+ }
362
+
363
+ /**
364
+ * 检查主图指标是否激活
365
+ * @param indicatorId 指标ID
366
+ */
367
+ isMainIndicatorActive(indicatorId: string): boolean {
368
+ return this.activeMainIndicators.has(indicatorId.toUpperCase())
369
+ }
370
+
371
+ /**
372
+ * 更新主图指标参数
373
+ * @param indicatorId 指标ID
374
+ * @param params 参数对象
375
+ */
376
+ updateMainIndicatorParams(indicatorId: string, params: Record<string, number | boolean | string>): void {
377
+ const id = indicatorId.toUpperCase()
378
+ if (!this.mainIndicatorParams[id]) {
379
+ this.mainIndicatorParams[id] = {}
380
+ }
381
+ this.mainIndicatorParams[id] = { ...this.mainIndicatorParams[id], ...params }
382
+
383
+ // 同步更新渲染器配置
384
+ const rendererName = id.toLowerCase()
385
+ const renderer = this.getRenderer(rendererName)
386
+ if (renderer && renderer.setConfig) {
387
+ renderer.setConfig(this.mainIndicatorParams[id])
388
+ }
389
+
390
+ // 更新调度器
391
+ this.updateIndicatorSchedulerConfig(id)
392
+ this.scheduleDraw()
393
+ this.syncIndicatorsSignal()
394
+ }
395
+
396
+ /**
397
+ * 获取主图指标参数
398
+ * @param indicatorId 指标ID
399
+ */
400
+ getMainIndicatorParams(indicatorId: string): Record<string, number | boolean | string> | null {
401
+ return this.mainIndicatorParams[indicatorId.toUpperCase()] ?? null
402
+ }
403
+
404
+ /**
405
+ * 清除所有主图指标
406
+ */
407
+ clearMainIndicators(): void {
408
+ for (const id of this.activeMainIndicators) {
409
+ this.disableMainIndicatorRenderer(id)
410
+ }
411
+ this.activeMainIndicators.clear()
412
+ this.scheduleDraw()
413
+ this.syncIndicatorsSignal()
414
+ }
415
+
416
+ /**
417
+ * 启用主图指标渲染器(内部方法)
418
+ */
419
+ private enableMainIndicatorRenderer(indicatorId: string): void {
420
+ const rendererMap: Record<string, () => void> = {
421
+ 'MA': () => {
422
+ if (!this.getRenderer('ma')) {
423
+ this.useRenderer(createMARendererPlugin())
424
+ }
425
+ this.setRendererEnabled('ma', true)
426
+ },
427
+ 'BOLL': () => {
428
+ if (!this.getRenderer('boll')) {
429
+ this.useRenderer(createBOLLRendererPlugin())
430
+ }
431
+ this.setRendererEnabled('boll', true)
432
+ },
433
+ 'EXPMA': () => {
434
+ if (!this.getRenderer('expma')) {
435
+ this.useRenderer(createEXPMARendererPlugin())
436
+ }
437
+ this.setRendererEnabled('expma', true)
438
+ },
439
+ 'ENE': () => {
440
+ if (!this.getRenderer('ene')) {
441
+ this.useRenderer(createENERendererPlugin())
442
+ }
443
+ this.setRendererEnabled('ene', true)
444
+ },
445
+ 'WMA': () => {
446
+ if (!this.getRenderer('wma_main')) {
447
+ this.useRenderer(createWMARendererPlugin({ paneId: 'main' }))
448
+ }
449
+ this.setRendererEnabled('wma_main', true)
450
+ },
451
+ 'DEMA': () => {
452
+ if (!this.getRenderer('dema_main')) {
453
+ this.useRenderer(createDEMARendererPlugin({ paneId: 'main' }))
454
+ }
455
+ this.setRendererEnabled('dema_main', true)
456
+ },
457
+ 'TEMA': () => {
458
+ if (!this.getRenderer('tema_main')) {
459
+ this.useRenderer(createTEMARendererPlugin({ paneId: 'main' }))
460
+ }
461
+ this.setRendererEnabled('tema_main', true)
462
+ },
463
+ 'HMA': () => {
464
+ if (!this.getRenderer('hma_main')) {
465
+ this.useRenderer(createHMARendererPlugin({ paneId: 'main' }))
466
+ }
467
+ this.setRendererEnabled('hma_main', true)
468
+ },
469
+ 'KAMA': () => {
470
+ if (!this.getRenderer('kama_main')) {
471
+ this.useRenderer(createKAMARendererPlugin({ paneId: 'main' }))
472
+ }
473
+ this.setRendererEnabled('kama_main', true)
474
+ },
475
+ 'SAR': () => {
476
+ if (!this.getRenderer('sar_main')) {
477
+ this.useRenderer(createSARRendererPlugin({ paneId: 'main' }))
478
+ }
479
+ this.setRendererEnabled('sar_main', true)
480
+ },
481
+ 'SUPERTREND': () => {
482
+ if (!this.getRenderer('supertrend_main')) {
483
+ this.useRenderer(createSuperTrendRendererPlugin({ paneId: 'main' }))
484
+ }
485
+ this.setRendererEnabled('supertrend_main', true)
486
+ },
487
+ 'KELTNER': () => {
488
+ if (!this.getRenderer('keltner_main')) {
489
+ this.useRenderer(createKeltnerRendererPlugin({ paneId: 'main' }))
490
+ }
491
+ this.setRendererEnabled('keltner_main', true)
492
+ },
493
+ 'DONCHIAN': () => {
494
+ if (!this.getRenderer('donchian_main')) {
495
+ this.useRenderer(createDonchianRendererPlugin({ paneId: 'main' }))
496
+ }
497
+ this.setRendererEnabled('donchian_main', true)
498
+ },
499
+ 'ICHIMOKU': () => {
500
+ if (!this.getRenderer('ichimoku_main')) {
501
+ this.useRenderer(createIchimokuRendererPlugin({ paneId: 'main' }))
502
+ }
503
+ this.setRendererEnabled('ichimoku_main', true)
504
+ },
505
+ 'PIVOT': () => {
506
+ if (!this.getRenderer('pivot_main')) {
507
+ this.useRenderer(createPivotRendererPlugin({ paneId: 'main' }))
508
+ }
509
+ this.setRendererEnabled('pivot_main', true)
510
+ },
511
+ 'FIB': () => {
512
+ if (!this.getRenderer('fib_main')) {
513
+ this.useRenderer(createFibRendererPlugin({ paneId: 'main' }))
514
+ }
515
+ this.setRendererEnabled('fib_main', true)
516
+ },
517
+ 'STRUCTURE': () => {
518
+ if (!this.getRenderer('structure_main')) {
519
+ this.useRenderer(createStructureRendererPlugin({ paneId: 'main' }))
520
+ }
521
+ this.setRendererEnabled('structure_main', true)
522
+ },
523
+ 'ZONES': () => {
524
+ if (!this.getRenderer('zones_main')) {
525
+ this.useRenderer(createZonesRendererPlugin({ paneId: 'main' }))
526
+ }
527
+ this.setRendererEnabled('zones_main', true)
528
+ },
529
+ }
530
+
531
+ const fn = rendererMap[indicatorId]
532
+ if (fn) fn()
533
+
534
+ // 确保图例渲染器已注册
535
+ if (!this.getRenderer('mainIndicatorLegend')) {
536
+ this.useRenderer(createMainIndicatorLegendRendererPlugin({ yPaddingPx: this.opt.yPaddingPx }))
537
+ }
538
+ }
539
+
540
+ /**
541
+ * 禁用主图指标渲染器(内部方法)
542
+ */
543
+ private disableMainIndicatorRenderer(indicatorId: string): void {
544
+ const rendererMap: Record<string, string> = {
545
+ 'MA': 'ma',
546
+ 'BOLL': 'boll',
547
+ 'EXPMA': 'expma',
548
+ 'ENE': 'ene',
549
+ 'WMA': 'wma_main',
550
+ 'DEMA': 'dema_main',
551
+ 'TEMA': 'tema_main',
552
+ 'HMA': 'hma_main',
553
+ 'KAMA': 'kama_main',
554
+ 'SAR': 'sar_main',
555
+ 'SUPERTREND': 'supertrend_main',
556
+ 'KELTNER': 'keltner_main',
557
+ 'DONCHIAN': 'donchian_main',
558
+ 'ICHIMOKU': 'ichimoku_main',
559
+ 'PIVOT': 'pivot_main',
560
+ 'FIB': 'fib_main',
561
+ 'STRUCTURE': 'structure_main',
562
+ 'ZONES': 'zones_main',
563
+ }
564
+
565
+ const rendererName = rendererMap[indicatorId]
566
+ if (rendererName) {
567
+ this.setRendererEnabled(rendererName, false)
568
+ }
569
+ }
570
+
571
+ /**
572
+ * 更新调度器配置(内部方法)
573
+ */
574
+ private updateIndicatorSchedulerConfig(indicatorId: string): void {
575
+ const isActive = this.activeMainIndicators.has(indicatorId)
576
+ const params = this.mainIndicatorParams[indicatorId] || {}
577
+
578
+ switch (indicatorId) {
579
+ case 'MA':
580
+ this.indicatorScheduler.updateMAConfig({
581
+ ma5: isActive,
582
+ ma10: isActive,
583
+ ma20: isActive,
584
+ ma30: isActive,
585
+ ma60: isActive,
586
+ })
587
+ break
588
+ case 'BOLL':
589
+ if (isActive) {
590
+ this.indicatorScheduler.updateBOLLConfig(params as unknown as BOLLSchedulerConfig)
591
+ } else {
592
+ this.indicatorScheduler.updateBOLLConfig({ ...params, showUpper: false, showMiddle: false, showLower: false, showBand: false } as unknown as BOLLSchedulerConfig)
593
+ }
594
+ break
595
+ case 'EXPMA':
596
+ if (isActive) {
597
+ this.indicatorScheduler.updateEXPMAConfig(params as unknown as EXPMASchedulerConfig)
598
+ }
599
+ break
600
+ case 'ENE':
601
+ if (isActive) {
602
+ this.indicatorScheduler.updateENEConfig(params as unknown as ENESchedulerConfig)
603
+ }
604
+ break
605
+ case 'WMA':
606
+ this.indicatorScheduler.updateWMAConfig({ ...params, showWMA: isActive } as unknown as WMASchedulerConfig, 'main')
607
+ break
608
+ case 'DEMA':
609
+ this.indicatorScheduler.updateDEMAConfig({ ...params, showDEMA: isActive } as unknown as DEMASchedulerConfig, 'main')
610
+ break
611
+ case 'TEMA':
612
+ this.indicatorScheduler.updateTEMAConfig({ ...params, showTEMA: isActive } as unknown as TEMASchedulerConfig, 'main')
613
+ break
614
+ case 'HMA':
615
+ this.indicatorScheduler.updateHMAConfig({ ...params, showHMA: isActive } as unknown as HMASchedulerConfig, 'main')
616
+ break
617
+ case 'KAMA':
618
+ this.indicatorScheduler.updateKAMAConfig({ ...params, showKAMA: isActive } as unknown as KAMASchedulerConfig, 'main')
619
+ break
620
+ case 'SAR':
621
+ this.indicatorScheduler.updateSARConfig({ ...params, showSAR: isActive } as unknown as SARSchedulerConfig, 'main')
622
+ break
623
+ case 'SUPERTREND':
624
+ this.indicatorScheduler.updateSuperTrendConfig({ ...params, showSuperTrend: isActive } as unknown as SuperTrendSchedulerConfig, 'main')
625
+ break
626
+ case 'KELTNER':
627
+ this.indicatorScheduler.updateKeltnerConfig({ ...params, showUpper: isActive, showMiddle: isActive, showLower: isActive } as unknown as KeltnerSchedulerConfig, 'main')
628
+ break
629
+ case 'DONCHIAN':
630
+ this.indicatorScheduler.updateDonchianConfig({ ...params, showUpper: isActive, showMiddle: isActive, showLower: isActive } as unknown as DonchianSchedulerConfig, 'main')
631
+ break
632
+ case 'ICHIMOKU':
633
+ this.indicatorScheduler.updateIchimokuConfig({ ...params, showTenkan: isActive, showKijun: isActive, showSpanA: isActive, showSpanB: isActive, showChikou: isActive, showCloud: isActive } as unknown as IchimokuSchedulerConfig, 'main')
634
+ break
635
+ case 'PIVOT':
636
+ this.indicatorScheduler.updatePivotConfig({ ...params, showPP: isActive, showR1: isActive, showR2: isActive, showR3: isActive, showS1: isActive, showS2: isActive, showS3: isActive } as unknown as PivotSchedulerConfig, 'main')
637
+ break
638
+ case 'FIB':
639
+ this.indicatorScheduler.updateFibConfig({ ...params, showLevels: isActive } as unknown as FibSchedulerConfig, 'main')
640
+ break
641
+ case 'STRUCTURE':
642
+ this.indicatorScheduler.updateStructureConfig({ ...params, showSwingLabels: isActive, showBOS: isActive, showCHOCH: isActive } as unknown as StructureSchedulerConfig, 'main')
643
+ break
644
+ case 'ZONES':
645
+ this.indicatorScheduler.updateZonesConfig({ ...params, showFVG: isActive, showOB: isActive, showFilledZones: isActive } as unknown as ZonesSchedulerConfig, 'main')
646
+ break
647
+ }
648
+ }
649
+
650
+ /**
651
+ * @deprecated 使用 enableMainIndicator/disableMainIndicator 替代
652
+ */
653
+ setActiveMainIndicators(indicators: string[]): void {
654
+ // 计算需要启用和禁用的指标
655
+ const newSet = new Set(indicators.map(i => i.toUpperCase()))
656
+ const currentSet = new Set(this.activeMainIndicators)
657
+
658
+ // 禁用不再激活的
659
+ for (const id of currentSet) {
660
+ if (!newSet.has(id)) {
661
+ this.disableMainIndicator(id)
662
+ }
663
+ }
664
+
665
+ // 启用新激活的
666
+ for (const id of newSet) {
667
+ if (!currentSet.has(id)) {
668
+ this.enableMainIndicator(id)
669
+ }
670
+ }
671
+ }
672
+
673
+ /**
674
+ * 创建图表实例
675
+ * @param dom 由 Vue 组件传入的 DOM 句柄
676
+ * @param opt 初始配置
677
+ */
678
+ constructor(dom: ChartDom, opt: ChartOptions) {
679
+ this.dom = dom
680
+ const { kWidth: _kWidth, kGap: _kGap, ...restOpt } = opt
681
+ // Chart 不持有业务 SSOT,kWidth/kGap/zoomLevel 由外部通过 applyRenderState() 传入
682
+ this.opt = { ...restOpt, kWidth: _kWidth ?? 0, kGap: _kGap ?? 0 }
683
+ this.interaction = new InteractionController(this)
684
+ this.interaction.setOnInteractionChange((snapshot) => {
685
+ this._interactionSignal.set(snapshot)
686
+ })
687
+ this.markerManager = new MarkerManager()
688
+ this.pluginHost = createPluginHost()
689
+ this.rendererPluginManager = new RendererPluginManager()
690
+ this.sharedWebGLSurface = new SharedWebGLSurface()
691
+
692
+ // 注入依赖
693
+ this.rendererPluginManager.setPluginHost(this.pluginHost)
694
+ this.rendererPluginManager.setInvalidateCallback(() => this.scheduleDraw())
695
+
696
+ this.syncPaneRatiosFromSpecs(this.opt.panes)
697
+
698
+ // 缩放级别由外部 SSOT 管理,Chart 只接收不计算
699
+ this.zoomLevelCount = Math.max(2, Math.round(this.opt.zoomLevels ?? 20))
700
+ this.currentZoomLevel = this.opt.initialZoomLevel ?? 1
701
+ this.currentZoomLevel = Math.max(1, Math.min(this.zoomLevelCount, this.currentZoomLevel))
702
+ // 注意:初始 kWidth/kGap 应由外部通过 applyRenderState() 传入
703
+
704
+ // 初始化指标调度器
705
+ this.indicatorScheduler = new IndicatorScheduler()
706
+ this.indicatorScheduler.setPluginHost(this.pluginHost)
707
+ for (const definition of getRegisteredIndicatorDefinitions()) {
708
+ this.indicatorScheduler.registerIndicator(definition)
709
+ }
710
+ this.indicatorScheduler.setInvalidateCallback(() => this.scheduleDraw())
711
+
712
+ // 初始化副图管理器
713
+ this.subPaneManager = new SubPaneManager()
714
+ // 注册副图活跃列表提供者,调度器据此只计算启用的副图
715
+ this.indicatorScheduler.setActiveSubPaneProvider(
716
+ () => this.subPaneManager.getPaneIds(),
717
+ )
718
+
719
+ this.initPanes()
720
+ // 注册绘图主插件(负责绘制 shape,layer: 'main')
721
+ this.useRenderer(createDrawingRendererPlugin({ store: this.drawingStore }))
722
+ // 注册绘图标签插件(负责推送选中绘图的轴标签,layer: 'overlay')
723
+ // 注意:此插件依赖 overlay 更新级别,若将来添加 Main 级别需调整
724
+ this.useRenderer(createDrawingLabelOverlayPlugin({ store: this.drawingStore }))
725
+ this.initCoreRenderers()
726
+ this.initResizeObserver()
727
+ }
728
+
729
+
730
+ private initCoreRenderers(): void {
731
+ const axisWidth = this.opt.rightAxisWidth + (this.opt.priceLabelWidth ?? 0)
732
+
733
+ this.useRenderer(createGridLinesRendererPlugin())
734
+ this.useRenderer(createCandleRenderer())
735
+ this.useRenderer(createLastPriceLineRendererPlugin())
736
+ this.useRenderer(createLastPriceLabelRegistrarPlugin())
737
+ this.useRenderer(createCustomMarkersRenderer())
738
+ this.useRenderer(createMainIndicatorLegendRendererPlugin({
739
+ yPaddingPx: this.opt.yPaddingPx,
740
+ }))
741
+ this.useRenderer(createYAxisRendererPlugin({
742
+ axisWidth,
743
+ yPaddingPx: this.opt.yPaddingPx,
744
+ getCrosshair: () => {
745
+ const pos = this.interaction.crosshairPos
746
+ const price = this.interaction.crosshairPrice
747
+ const activePaneId = this.interaction.activePaneId
748
+ if (pos && price !== null) {
749
+ return { y: pos.y, price, activePaneId }
750
+ }
751
+ return null
752
+ },
753
+ }))
754
+ this.useRenderer(createCrosshairRendererPlugin({
755
+ getCrosshairState: () => ({
756
+ pos: this.interaction.crosshairPos,
757
+ activePaneId: this.interaction.activePaneId,
758
+ isDragging: this.interaction.isDraggingState(),
759
+ price: this.interaction.crosshairPrice,
760
+ }),
761
+ }))
762
+ this.useRenderer(createTimeAxisRendererPlugin({
763
+ height: this.opt.bottomAxisHeight,
764
+ getCrosshair: () => {
765
+ const pos = this.interaction.crosshairPos
766
+ const idx = this.interaction.crosshairIndex
767
+ if (pos && idx !== null) {
768
+ return { x: pos.x, index: idx }
769
+ }
770
+ return null
771
+ },
772
+ }))
773
+ }
774
+
775
+
776
+ private initResizeObserver() {
777
+ if (typeof ResizeObserver === 'undefined') return
778
+
779
+ const target = this.dom.container
780
+ if (!target) return
781
+
782
+ // 初始化 scrollLeft 缓存
783
+ this.cachedScrollLeft = target.scrollLeft
784
+ this.onScroll = () => { this.cachedScrollLeft = target.scrollLeft }
785
+ target.addEventListener('scroll', this.onScroll, { passive: true })
786
+
787
+ this.resizeObserver = new ResizeObserver((entries) => {
788
+ const entry = entries[0]
789
+ if (!entry) return
790
+
791
+ const prevWidth = this.observedSize.width
792
+ const prevHeight = this.observedSize.height
793
+ const prevDpr = this.preciseDpr
794
+
795
+ this.updateObservedMetrics(entry)
796
+
797
+ const widthChanged = this.observedSize.width !== prevWidth
798
+ const heightChanged = this.observedSize.height !== prevHeight
799
+ const dprChanged = this.preciseDpr !== prevDpr
800
+ if ((import.meta as any).env?.MODE !== 'production') {
801
+ console.log(
802
+ `[Chart] resize observer: ` +
803
+ `size ${prevWidth}x${prevHeight} -> ${this.observedSize.width}x${this.observedSize.height} ` +
804
+ `dpr ${prevDpr} -> ${this.preciseDpr} ` +
805
+ `changed: ${widthChanged || heightChanged ? 'size' : ''}${widthChanged || heightChanged && dprChanged ? '+' : ''}${dprChanged ? 'dpr' : ''}`
806
+ )
807
+ }
808
+ if (widthChanged || heightChanged || dprChanged) {
809
+ this.resize()
810
+ }
811
+ })
812
+
813
+ try {
814
+ this.resizeObserver.observe(target, { box: 'device-pixel-content-box' as ResizeObserverBoxOptions })
815
+ } catch {
816
+ this.resizeObserver.observe(target)
817
+ }
818
+ }
819
+
820
+
821
+
822
+ private updateObservedMetrics(entry: ResizeObserverEntry) {
823
+ const cssWidth = Math.max(1, Math.round(entry.contentRect.width))
824
+ const cssHeight = Math.max(1, Math.round(entry.contentRect.height))
825
+ this.observedSize.width = cssWidth
826
+ this.observedSize.height = cssHeight
827
+
828
+ const pixelSize = entry.devicePixelContentBoxSize?.[0]
829
+ const cssSize = entry.contentBoxSize?.[0]
830
+ if (!pixelSize || !cssSize || cssSize.inlineSize <= 0) {
831
+ this.preciseDpr = 0
832
+ return
833
+ }
834
+
835
+ const raw = pixelSize.inlineSize / cssSize.inlineSize
836
+ this.preciseDpr = Math.round(raw * 64) / 64
837
+ }
838
+
839
+ private getEffectiveDpr(): number {
840
+ let dpr = this.preciseDpr > 0
841
+ ? this.preciseDpr
842
+ : Math.round((window.devicePixelRatio || 1) * 64) / 64
843
+ if (dpr < 1) dpr = 1
844
+ return dpr
845
+ }
846
+
847
+ getViewport(): Viewport | null {
848
+ return this._internalViewport
849
+ }
850
+
851
+ getCurrentDpr(): number {
852
+ return this.getEffectiveDpr()
853
+ }
854
+
855
+ /** 获取缓存的 scrollLeft(避免读取 DOM 触发强制回流) */
856
+ getCachedScrollLeft(): number {
857
+ return this.cachedScrollLeft
858
+ }
859
+
860
+ /** 获取插件宿主 */
861
+ get plugin(): PluginHostImpl {
862
+ return this.pluginHost
863
+ }
864
+
865
+ // ========== 渲染器插件 API ==========
866
+
867
+ /** 安装渲染器插件 */
868
+ useRenderer(plugin: RendererPlugin | RendererPluginWithHost, config?: Record<string, unknown>): void {
869
+ this.rendererPluginManager.register(plugin)
870
+ if (config && plugin.setConfig) {
871
+ plugin.setConfig(config)
872
+ }
873
+ }
874
+
875
+ /** 移除渲染器插件 */
876
+ removeRenderer(name: string): void {
877
+ this.rendererPluginManager.unregister(name)
878
+ }
879
+
880
+ /** 获取渲染器插件 */
881
+ getRenderer<T extends RendererPlugin = RendererPlugin>(name: string): T | undefined {
882
+ return this.rendererPluginManager.getPlugin<T>(name)
883
+ }
884
+
885
+ /** 更新渲染器配置(自动重绘) */
886
+ updateRendererConfig(name: string, config: Record<string, unknown>): void {
887
+ this.rendererPluginManager.updateConfig(name, config)
888
+ }
889
+
890
+ /** 启用/禁用渲染器 */
891
+ setRendererEnabled(name: string, enabled: boolean): void {
892
+ this.rendererPluginManager.setEnabled(name, enabled)
893
+ }
894
+
895
+ /** 获取所有渲染器 */
896
+ getAllRenderers(): RendererPlugin[] {
897
+ return this.rendererPluginManager.getAllPlugins()
898
+ }
899
+
900
+ /** 更新用户设置(触发重绘) */
901
+ updateSettings(settings: ChartSettings): void {
902
+ this.settings = { ...settings }
903
+ this.interaction.updateSettings(settings)
904
+
905
+ // 同步对数刻度设置到所有 pane
906
+ const scaleType = settings.logarithmicScale ? 'log' : 'linear'
907
+ for (const renderer of this.paneRenderers) {
908
+ renderer.getPane().yAxis.setScaleType(scaleType)
909
+ }
910
+
911
+ this.scheduleDraw()
912
+ }
913
+
914
+ /**
915
+ * 绘制一帧
916
+ * @param level 更新级别,决定渲染哪些层
917
+ */
918
+ draw(level: UpdateLevel = UpdateLevel.All) {
919
+ // 1. 重置 Marker 标记
920
+ this.markerManager.clear()
921
+
922
+ // 2. 准备帧数据(视口 / 可见范围 / K 线坐标,优先走缓存)
923
+ const frame = this.prepareFrameData(level)
924
+ if (!frame) return
925
+
926
+ const { vp, range, kLinePositions, kLineCenters, kBarRects, kWidthPx, useCachedFrame } = frame
927
+
928
+ // 3. 更新交互控制器坐标映射
929
+ this.interaction.setKLinePositions(kLinePositions, range, kWidthPx)
930
+
931
+ // 4. 通知调度器当前活跃主图指标 + 获取价格范围
932
+ this.indicatorScheduler.setActiveMainIndicators(Array.from(this.activeMainIndicators))
933
+ const mainIndicatorRange = useCachedFrame ? null : this.indicatorScheduler.getMainIndicatorPriceRange()
934
+ const hasCrosshair = this.interaction.getCrosshairIndex() !== null
935
+
936
+ // 5. 遍历所有 Pane 渲染主层 / overlay / Y 轴
937
+ const { sharedXAxisLabels, sharedXAxisRanges } = this.renderPanes(
938
+ vp, range, kLinePositions, kLineCenters, kBarRects, kWidthPx,
939
+ mainIndicatorRange, hasCrosshair, useCachedFrame, level,
940
+ )
941
+
942
+ // 6. 持久化十字线状态供下帧判断清除
943
+ this.overlayHadCrosshair = hasCrosshair
944
+
945
+ // 7. 渲染 X 轴时间轴
946
+ this.renderXAxis(vp, range, kLinePositions, kLineCenters, kBarRects, kWidthPx, sharedXAxisLabels, sharedXAxisRanges)
947
+ }
948
+
949
+ private prepareFrameData(level: UpdateLevel): FrameData | null {
950
+ const useCachedFrame = level === UpdateLevel.Overlay && this.cachedDrawFrame !== null
951
+
952
+ const vp = useCachedFrame ? this.cachedDrawFrame!.viewport : this.computeViewport()
953
+ if (!vp) return null
954
+
955
+ if (this._internalData.length === 0) return null
956
+
957
+ const range = useCachedFrame
958
+ ? this.cachedDrawFrame!.range
959
+ : (() => {
960
+ const { start, end } = getVisibleRange(
961
+ vp.scrollLeft,
962
+ vp.plotWidth,
963
+ this.opt.kWidth,
964
+ this.opt.kGap,
965
+ this._internalData.length,
966
+ vp.dpr
967
+ )
968
+ return { start, end }
969
+ })()
970
+
971
+ if (!useCachedFrame && (range.start !== this.lastVisibleRange.start || range.end !== this.lastVisibleRange.end)) {
972
+ this.indicatorScheduler.updateVisibleRange(range)
973
+ this.lastVisibleRange = range
974
+ }
975
+
976
+ const kLinePositions = useCachedFrame
977
+ ? this.cachedDrawFrame!.kLinePositions
978
+ : this.calcKLinePositions(range)
979
+
980
+ let kLineCenters: number[]
981
+ let kBarRects: Array<{ x: number; width: number }>
982
+ let kWidthPx: number
983
+
984
+ if (useCachedFrame) {
985
+ kLineCenters = this.cachedDrawFrame!.kLineCenters
986
+ kBarRects = this.cachedDrawFrame!.kBarRects
987
+ kWidthPx = this.cachedDrawFrame!.kWidthPx
988
+ } else {
989
+ const physConfig = getPhysicalKLineConfig(this.opt.kWidth, this.opt.kGap, vp.dpr)
990
+ let barWidthPx = Math.max(1, physConfig.unitPx - 1)
991
+ if (barWidthPx % 2 === 0) barWidthPx -= 1
992
+
993
+ kLineCenters = new Array(kLinePositions.length)
994
+ kBarRects = new Array(kLinePositions.length)
995
+
996
+ for (let i = 0; i < kLinePositions.length; i++) {
997
+ const x = kLinePositions[i]!
998
+ const leftPx = Math.round(x * vp.dpr)
999
+ const wickXPx = leftPx + (physConfig.kWidthPx - 1) / 2
1000
+ kLineCenters[i] = wickXPx / vp.dpr
1001
+
1002
+ const barLeftPx = wickXPx - (barWidthPx - 1) / 2
1003
+ kBarRects[i] = { x: barLeftPx / vp.dpr, width: barWidthPx / vp.dpr }
1004
+ }
1005
+
1006
+ kWidthPx = getPhysicalKLineConfig(this.opt.kWidth, this.opt.kGap, vp.dpr).kWidthPx
1007
+ this.cachedDrawFrame = {
1008
+ viewport: { ...vp },
1009
+ range: { ...range },
1010
+ kLinePositions,
1011
+ kLineCenters,
1012
+ kBarRects,
1013
+ kWidthPx,
1014
+ }
1015
+ }
1016
+
1017
+ return { vp, range, kLinePositions, kLineCenters, kBarRects, kWidthPx, useCachedFrame }
1018
+ }
1019
+
1020
+ private renderPanes(
1021
+ vp: Viewport,
1022
+ range: VisibleRange,
1023
+ kLinePositions: KLinePositions,
1024
+ kLineCenters: number[],
1025
+ kBarRects: Array<{ x: number; width: number }>,
1026
+ kWidthPx: number,
1027
+ mainIndicatorRange: { min: number; max: number } | null,
1028
+ hasCrosshair: boolean,
1029
+ useCachedFrame: boolean,
1030
+ level: UpdateLevel,
1031
+ ): { sharedXAxisLabels: XAxisLabel[]; sharedXAxisRanges: XAxisRange[] } {
1032
+ const sharedYAxisLabels: YAxisLabel[] = []
1033
+ const sharedXAxisLabels: XAxisLabel[] = []
1034
+ const sharedYAxisRanges: YAxisRange[] = []
1035
+ const sharedXAxisRanges: XAxisRange[] = []
1036
+
1037
+ for (const renderer of this.paneRenderers) {
1038
+ const pane = renderer.getPane()
1039
+ const { mainCtx, overlayCtx, yAxisCtx } = renderer.getContexts()
1040
+ const { candleSurface, lineSurface } = renderer.getWebGL()
1041
+
1042
+ if (!useCachedFrame) {
1043
+ const indicatorRange = pane.role === 'price' ? mainIndicatorRange : null
1044
+ pane.updateRange(this._internalData, range, indicatorRange)
1045
+ }
1046
+
1047
+ const shouldUpdateMain = level === UpdateLevel.Main || level === UpdateLevel.All
1048
+ const shouldUpdateOverlay = level === UpdateLevel.All || (level === UpdateLevel.Overlay && (hasCrosshair || this.overlayHadCrosshair))
1049
+
1050
+ if (shouldUpdateMain && mainCtx) {
1051
+ mainCtx.setTransform(1, 0, 0, 1, 0, 0)
1052
+ mainCtx.scale(vp.dpr, vp.dpr)
1053
+ mainCtx.clearRect(0, 0, vp.plotWidth + 1, pane.height + 2 / vp.dpr)
1054
+ candleSurface?.clear()
1055
+ lineSurface?.clear()
1056
+ }
1057
+
1058
+ if (shouldUpdateOverlay && overlayCtx) {
1059
+ const overlayWidth = overlayCtx.canvas.width / vp.dpr
1060
+ overlayCtx.setTransform(1, 0, 0, 1, 0, 0)
1061
+ overlayCtx.scale(vp.dpr, vp.dpr)
1062
+ overlayCtx.clearRect(0, 0, overlayWidth + 1, pane.height + 2 / vp.dpr)
1063
+ }
1064
+
1065
+ if (yAxisCtx && !useCachedFrame) {
1066
+ const yAxisWidth = yAxisCtx.canvas.width / vp.dpr
1067
+ yAxisCtx.setTransform(1, 0, 0, 1, 0, 0)
1068
+ yAxisCtx.scale(vp.dpr, vp.dpr)
1069
+ yAxisCtx.clearRect(0, 0, yAxisWidth, pane.height + 2 / vp.dpr)
1070
+ }
1071
+
1072
+ const context: RenderContext = {
1073
+ ctx: mainCtx!,
1074
+ overlayCtx: overlayCtx ?? undefined,
1075
+ pane: wrapPaneInfo(pane),
1076
+ data: this._internalData,
1077
+ range,
1078
+ scrollLeft: vp.scrollLeft,
1079
+ kWidth: this.opt.kWidth,
1080
+ kGap: this.opt.kGap,
1081
+ dpr: vp.dpr,
1082
+ paneWidth: vp.plotWidth,
1083
+ kLinePositions,
1084
+ kLineCenters,
1085
+ kBarRects,
1086
+ markerManager: this.markerManager,
1087
+ crosshairIndex: this.interaction.getCrosshairIndex(),
1088
+ yAxisCtx: yAxisCtx ?? undefined,
1089
+ candleWebGLSurface: candleSurface ?? undefined,
1090
+ lineWebGLSurface: lineSurface ?? undefined,
1091
+ zoomLevel: this.currentZoomLevel,
1092
+ zoomLevelCount: this.zoomLevelCount,
1093
+ viewport: {
1094
+ scrollLeft: vp.scrollLeft,
1095
+ plotWidth: vp.plotWidth,
1096
+ plotHeight: vp.plotHeight,
1097
+ },
1098
+ settings: this.settings,
1099
+ yAxisLabels: sharedYAxisLabels,
1100
+ xAxisLabels: sharedXAxisLabels,
1101
+ yAxisRanges: sharedYAxisRanges,
1102
+ xAxisRanges: sharedXAxisRanges,
1103
+ theme: this._themeSignal.peek(),
1104
+ }
1105
+
1106
+ if (shouldUpdateMain || shouldUpdateOverlay) {
1107
+ const errors = this.rendererPluginManager.render(pane.id, context, level)
1108
+ if (errors.length > 0) {
1109
+ this.pluginHost.events.emit('renderer:error', { paneId: pane.id, errors })
1110
+ }
1111
+
1112
+ const yAxisErrors = this.rendererPluginManager.renderPlugin('yAxis', context)
1113
+ if (yAxisErrors.length > 0) {
1114
+ this.pluginHost.events.emit('renderer:error', { paneId: pane.id, errors: yAxisErrors })
1115
+ }
1116
+ }
1117
+ }
1118
+
1119
+ return { sharedXAxisLabels, sharedXAxisRanges }
1120
+ }
1121
+
1122
+ private renderXAxis(
1123
+ vp: Viewport,
1124
+ range: VisibleRange,
1125
+ kLinePositions: KLinePositions,
1126
+ kLineCenters: number[],
1127
+ kBarRects: Array<{ x: number; width: number }>,
1128
+ kWidthPx: number,
1129
+ sharedXAxisLabels: XAxisLabel[],
1130
+ sharedXAxisRanges: XAxisRange[],
1131
+ ): void {
1132
+ const xAxisCtx = this.xAxisCtx ?? this.dom.xAxisCanvas.getContext('2d')
1133
+ if (!this.xAxisCtx) {
1134
+ this.xAxisCtx = xAxisCtx
1135
+ }
1136
+ if (xAxisCtx) {
1137
+ const timeAxisContext: RenderContext = {
1138
+ ctx: xAxisCtx,
1139
+ pane: {
1140
+ id: 'xAxis',
1141
+ role: 'auxiliary',
1142
+ capabilities: {
1143
+ showPriceAxisTicks: false,
1144
+ showCrosshairPriceLabel: false,
1145
+ candleHitTest: false,
1146
+ supportsPriceTranslate: false,
1147
+ },
1148
+ top: 0,
1149
+ height: this.opt.bottomAxisHeight,
1150
+ yAxis: {
1151
+ priceToY: () => 0,
1152
+ yToPrice: () => 0,
1153
+ getPaddingTop: () => 0,
1154
+ getPaddingBottom: () => 0,
1155
+ getPriceOffset: () => 0,
1156
+ getDisplayRange: (baseRange) => baseRange ?? { maxPrice: 0, minPrice: 0 },
1157
+ getScaleType: () => 'linear' as const,
1158
+ },
1159
+ priceRange: { maxPrice: 0, minPrice: 0 },
1160
+ },
1161
+ data: this._internalData,
1162
+ range,
1163
+ scrollLeft: vp.scrollLeft,
1164
+ kWidth: this.opt.kWidth,
1165
+ kGap: this.opt.kGap,
1166
+ dpr: vp.dpr,
1167
+ paneWidth: vp.plotWidth,
1168
+ kLinePositions,
1169
+ kLineCenters,
1170
+ kBarRects,
1171
+ xAxisCtx,
1172
+ viewport: {
1173
+ scrollLeft: vp.scrollLeft,
1174
+ plotWidth: vp.plotWidth,
1175
+ plotHeight: vp.plotHeight,
1176
+ },
1177
+ yAxisLabels: [],
1178
+ xAxisLabels: sharedXAxisLabels,
1179
+ xAxisRanges: sharedXAxisRanges,
1180
+ theme: this._themeSignal.peek(),
1181
+ }
1182
+ const errors = this.rendererPluginManager.renderPlugin('timeAxis', timeAxisContext)
1183
+ if (errors.length > 0) {
1184
+ this.pluginHost.events.emit('renderer:error', { paneId: 'timeAxis', errors })
1185
+ }
1186
+ }
1187
+ }
1188
+
1189
+ // ========== Render State API (Vue SSOT) ==========
1190
+
1191
+ /**
1192
+ * 应用渲染状态(由 Vue/Store 层在状态更新后调用)
1193
+ * Chart 不拥有业务 SSOT,只负责接收参数并渲染
1194
+ * 这是写入 opt.kWidth/kGap 和 currentZoomLevel 的唯一入口
1195
+ */
1196
+ applyRenderState(kWidth: number, kGap: number, zoomLevel?: number): void {
1197
+ const nextZoomLevel = zoomLevel !== undefined
1198
+ ? Math.max(1, Math.min(this.zoomLevelCount, zoomLevel))
1199
+ : this.currentZoomLevel
1200
+ const renderStateChanged = this.opt.kWidth !== kWidth
1201
+ || this.opt.kGap !== kGap
1202
+ || this.currentZoomLevel !== nextZoomLevel
1203
+
1204
+ if (!renderStateChanged) {
1205
+ return
1206
+ }
1207
+
1208
+ this.opt = { ...this.opt, kWidth, kGap }
1209
+ if (zoomLevel !== undefined) {
1210
+ this.currentZoomLevel = nextZoomLevel
1211
+ }
1212
+ this.updateViewportSignal()
1213
+ this.scheduleDraw()
1214
+ }
1215
+
1216
+ /** 获取总缩放级别数 */
1217
+ getZoomLevelCount(): number {
1218
+ return this.zoomLevelCount
1219
+ }
1220
+
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
+ /** 获取所有 PaneRenderer */
1237
+ getPaneRenderers(): PaneRenderer[] {
1238
+ return this.paneRenderers
1239
+ }
1240
+
1241
+ /** 获取 MarkerManager(供 InteractionController 使用) */
1242
+ getMarkerManager(): MarkerManager {
1243
+ return this.markerManager
1244
+ }
1245
+
1246
+ /** 更新自定义标记 */
1247
+ updateCustomMarkers(markers: CustomMarkerEntity[]): void {
1248
+ this.markerManager.setCustomMarkers(markers)
1249
+ this.scheduleDraw()
1250
+ }
1251
+
1252
+ /** 清除自定义标记 */
1253
+ clearCustomMarkers(): void {
1254
+ this.markerManager.clearCustomMarkers()
1255
+ this.scheduleDraw()
1256
+ }
1257
+
1258
+ /** 获取 ChartDom(供 InteractionController 使用) */
1259
+ getDom() {
1260
+ return this.dom
1261
+ }
1262
+
1263
+ /** 获取当前 ChartOptions(返回内部当前快照) */
1264
+ getOption() {
1265
+ return this.opt
1266
+ }
1267
+
1268
+ /**
1269
+ * 计算 K 线起始 x 坐标数组,与 candle.ts 的像素对齐方式保持一致
1270
+ * @param range 可见 K 线索引范围
1271
+ * @returns x 坐标数组(逻辑像素,经过物理像素对齐)
1272
+ */
1273
+ calcKLinePositions(range: VisibleRange): KLinePositions {
1274
+ const { start, end } = range
1275
+ const count = end - start
1276
+
1277
+ // 边界检查:防止负数或零长度数组
1278
+ if (count <= 0) {
1279
+ return []
1280
+ }
1281
+
1282
+ const dpr = this.getEffectiveDpr()
1283
+
1284
+ // 统一使用 getPhysicalKLineConfig,确保与渲染完全一致
1285
+ const { unitPx, startXPx } = getPhysicalKLineConfig(this.opt.kWidth, this.opt.kGap, dpr)
1286
+
1287
+ const positions: number[] = new Array(count)
1288
+
1289
+ for (let i = 0; i < count; i++) {
1290
+ const dataIndex = start + i
1291
+ const leftPx = startXPx + dataIndex * unitPx
1292
+ positions[i] = leftPx / dpr
1293
+ }
1294
+
1295
+ return positions
1296
+ }
1297
+
1298
+ /**
1299
+ * 更新配置并触发布局/重绘
1300
+ * @param partial 部分配置项
1301
+ */
1302
+ updateOptions(partial: Partial<ChartOptions>) {
1303
+ // 缩放参数由 zoomLevel 派生,不允许直接修改
1304
+ if (partial.kWidth !== undefined) {
1305
+ console.warn('[Chart] kWidth cannot be set directly. Use applyRenderState() instead.')
1306
+ delete partial.kWidth
1307
+ }
1308
+ if (partial.kGap !== undefined) {
1309
+ delete partial.kGap
1310
+ }
1311
+
1312
+ if (partial.panes) {
1313
+ const nextPanes = partial.panes.map((pane) => ({ ...pane }))
1314
+ this.opt = { ...this.opt, ...partial, panes: nextPanes }
1315
+ this.applyPaneLayoutSpecs(nextPanes)
1316
+ return
1317
+ }
1318
+
1319
+ this.opt = { ...this.opt, ...partial }
1320
+ this.resize()
1321
+ }
1322
+
1323
+ /** 更新 pane 布局配置
1324
+ * @param panes 新的 pane 配置数组
1325
+ *
1326
+ * 显式整盘替换:清空之前 user-resize 留下的 paneRatios 缓存,让 spec 中的 ratio
1327
+ * 真正生效。`addPane`/`upsertPane`/`removePaneDefinition` 走 `applyPaneLayoutSpecs`
1328
+ * 时仍保留 prev 值以记住用户拖拽过的高度——只有显式的 layout replacement 才重置。
1329
+ */
1330
+ updatePaneLayout(panes: PaneSpec[]): void {
1331
+ this._internalPaneRatios.clear()
1332
+ this.applyPaneLayoutSpecs(panes)
1333
+ }
1334
+
1335
+ setPaneDefinitions(defs: PaneSpec[]): void {
1336
+ this.applyPaneLayoutSpecs(defs)
1337
+ }
1338
+
1339
+ upsertPane(def: PaneSpec): void {
1340
+ const idx = this.opt.panes.findIndex((pane) => pane.id === def.id)
1341
+ if (idx === -1) {
1342
+ this.applyPaneLayoutSpecs([...this.opt.panes, { ...def }])
1343
+ return
1344
+ }
1345
+
1346
+ const next = [...this.opt.panes]
1347
+ next[idx] = { ...next[idx], ...def }
1348
+ this.applyPaneLayoutSpecs(next)
1349
+ }
1350
+
1351
+ removePaneDefinition(paneId: string): void {
1352
+ if (!this.opt.panes.some((pane) => pane.id === paneId)) return
1353
+ this._internalPaneRatios.delete(paneId)
1354
+ this.applyPaneLayoutSpecs(this.opt.panes.filter((pane) => pane.id !== paneId))
1355
+ }
1356
+
1357
+ bindIndicatorToPane(paneId: string, indicatorId: SubIndicatorType, params?: Record<string, number | boolean | string>): void {
1358
+ const paneExists = this.opt.panes.some((pane) => pane.id === paneId)
1359
+ if (!paneExists) {
1360
+ this.upsertPane({ id: paneId, ratio: 1, visible: true, role: 'indicator' })
1361
+ }
1362
+
1363
+ const rendererName = `${indicatorId.toLowerCase()}_${paneId}`
1364
+ const existing = this.getRenderer(rendererName)
1365
+ if (existing) {
1366
+ if (params) this.updateRendererConfig(rendererName, params)
1367
+ return
1368
+ }
1369
+
1370
+ const renderer = createSubIndicatorRenderer({ indicatorId, paneId })
1371
+ this.useRenderer(renderer, params)
1372
+ }
1373
+
1374
+ /** 更新绘图对象 */
1375
+ setDrawings(drawings: import('../plugin').DrawingObject[]): void {
1376
+ this.drawingStore.setAll(drawings)
1377
+ this._drawingsSignal.set(drawings)
1378
+ this.scheduleDraw()
1379
+ }
1380
+
1381
+ /** 更新选中的绘图 ID */
1382
+ setSelectedDrawingId(id: string | null): void {
1383
+ if (this.drawingStore.getSelectedId() === id) return
1384
+ this.drawingStore.setSelectedId(id)
1385
+ this.scheduleDraw()
1386
+ }
1387
+
1388
+ /** 获取当前 pane 布局快照(含 ratio) */
1389
+ getPaneLayoutSpecs(): PaneSpec[] {
1390
+ const visible = this.opt.panes.filter(p => p.visible !== false)
1391
+ const sum = visible.reduce((s, p) => s + (this._internalPaneRatios.get(p.id) ?? p.ratio ?? 0), 0)
1392
+ const safeSum = sum > 0 ? sum : 1
1393
+ return this.opt.panes.map((spec) => {
1394
+ const base = this._internalPaneRatios.get(spec.id) ?? spec.ratio ?? 0
1395
+ const ratio = spec.visible === false ? base : base / safeSum
1396
+ const pane = this.paneRenderers.find((r) => r.getPane().id === spec.id)?.getPane()
1397
+ return {
1398
+ ...spec,
1399
+ ratio,
1400
+ role: pane?.role ?? spec.role,
1401
+ capabilities: pane ? { ...pane.capabilities } : spec.capabilities,
1402
+ }
1403
+ })
1404
+ }
1405
+
1406
+ private emitPaneLayoutChange(): void {
1407
+ // 同步 pane ratios 到 signal
1408
+ const ratios: Record<string, number> = {}
1409
+ this._internalPaneRatios.forEach((ratio, id) => {
1410
+ ratios[id] = ratio
1411
+ })
1412
+ this._paneRatiosSignal.set(ratios)
1413
+ this.syncSubPanesSignal()
1414
+
1415
+ this.onPaneLayoutChange?.(this.getPaneLayoutSpecs())
1416
+ }
1417
+
1418
+ private applyPaneLayoutSpecs(panes: PaneSpec[]): void {
1419
+ this.opt.panes = panes.map((spec) => ({ ...spec }))
1420
+ this.syncPaneRatiosFromSpecs(this.opt.panes)
1421
+ this.initPanes()
1422
+ this.layoutPanes()
1423
+ this.emitPaneLayoutChange()
1424
+ this.scheduleDraw()
1425
+ }
1426
+
1427
+ /**
1428
+ * 调整相邻 pane 边界(支持连锁挤压)
1429
+ * @param upperPaneId 上方 pane ID(边界位于此 pane 与其下方邻居之间)
1430
+ * @param deltaY Y 方向位移(逻辑像素,正数表示边界向下,upper 增大;负数表示向上,upper 减小)
1431
+ */
1432
+ resizePaneBoundary(upperPaneId: string, deltaY: number): boolean {
1433
+ // === 1. 参数校验 ===
1434
+ if (!Number.isFinite(deltaY) || deltaY === 0) return false
1435
+ const vp = this._internalViewport
1436
+ if (!vp) return false
1437
+
1438
+ // === 2. 定位相邻 pane 对(边界两侧) ===
1439
+ const visibleSpecs = this.opt.panes.filter(p => p.visible !== false)
1440
+ const boundaryIndex = visibleSpecs.findIndex(p => p.id === upperPaneId)
1441
+ if (boundaryIndex < 0 || boundaryIndex >= visibleSpecs.length - 1) return false
1442
+
1443
+ const upperSpec = visibleSpecs[boundaryIndex]
1444
+ const lowerSpec = visibleSpecs[boundaryIndex + 1]
1445
+ if (!upperSpec || !lowerSpec) return false
1446
+
1447
+ // === 3. 收集所有 pane 当前高度 ===
1448
+ const heights = new Map<string, number>()
1449
+ for (const spec of visibleSpecs) {
1450
+ const renderer = this.paneRenderers.find(r => r.getPane().id === spec.id)
1451
+ if (renderer) {
1452
+ heights.set(spec.id, renderer.getPane().height)
1453
+ }
1454
+ }
1455
+
1456
+ // === 4. 连锁挤压/扩展 ===
1457
+ // deltaY > 0: 边界下移,upper expand,lower shrink
1458
+ // deltaY < 0: 边界上移,upper shrink,lower expand
1459
+ const expandIdx = deltaY > 0 ? boundaryIndex : boundaryIndex + 1
1460
+ const shrinkIdx = deltaY > 0 ? boundaryIndex + 1 : boundaryIndex
1461
+ const expandDir = deltaY > 0 ? -1 : 1 // expand 方向(向边界方向找)
1462
+ const shrinkDir = deltaY > 0 ? 1 : -1 // shrink 方向(远离边界方向找)
1463
+
1464
+ let remaining = Math.abs(deltaY)
1465
+
1466
+ // 先尝试 shrink(从 shrinkIdx 开始,沿 shrinkDir 方向连锁)
1467
+ let shrinkCursor = shrinkIdx
1468
+ while (remaining > 0 && shrinkCursor >= 0 && shrinkCursor < visibleSpecs.length) {
1469
+ const spec = visibleSpecs[shrinkCursor]
1470
+ if (!spec) break
1471
+
1472
+ const currentH = heights.get(spec.id) ?? 0
1473
+ const minH = this.getPaneMinHeight(spec, vp.plotHeight)
1474
+ const canShrink = Math.max(0, currentH - minH)
1475
+
1476
+ if (canShrink > 0) {
1477
+ const shrink = Math.min(canShrink, remaining)
1478
+ heights.set(spec.id, currentH - shrink)
1479
+ remaining -= shrink
1480
+ }
1481
+
1482
+ // 继续向 shrinkDir 方向找下一个可 shrink 的 pane
1483
+ if (remaining > 0) {
1484
+ shrinkCursor += shrinkDir
1485
+ }
1486
+ }
1487
+
1488
+ // 如果还有剩余(无法完全 shrink),说明拖拽无效
1489
+ if (remaining > 0) return false
1490
+
1491
+ // 将节省的高度全部加到 expand 方
1492
+ const expandSpec = visibleSpecs[expandIdx]
1493
+ if (!expandSpec) return false
1494
+ const expandCurrentH = heights.get(expandSpec.id) ?? 0
1495
+ heights.set(expandSpec.id, expandCurrentH + Math.abs(deltaY))
1496
+
1497
+ // === 5. 将像素高度转换为 ratio ===
1498
+ const gap = Math.max(0, this.opt.paneGap ?? 0)
1499
+ const totalGaps = gap * Math.max(0, visibleSpecs.length - 1)
1500
+ const availableH = Math.max(1, vp.plotHeight - totalGaps)
1501
+
1502
+ for (const spec of visibleSpecs) {
1503
+ const h = heights.get(spec.id) ?? 0
1504
+ this._internalPaneRatios.set(spec.id, h / availableH)
1505
+ }
1506
+
1507
+ // === 6. 归一化并同步 ===
1508
+ this.normalizeVisiblePaneRatios(visibleSpecs)
1509
+ this.syncPaneRatiosToSpecs()
1510
+
1511
+ // === 7. 应用布局 ===
1512
+ this.layoutPanes()
1513
+ this.emitPaneLayoutChange()
1514
+ this.scheduleDraw()
1515
+ return true
1516
+ }
1517
+
1518
+ private resolvePaneRole(spec: PaneSpec, index: number): PaneRole {
1519
+ if (spec.role) return spec.role
1520
+ return index === 0 ? 'price' : 'indicator'
1521
+ }
1522
+
1523
+
1524
+ addPane(paneId: string): void {
1525
+ if (this.opt.panes.some((spec) => spec.id === paneId)) {
1526
+ console.warn(`Pane "${paneId}" already exists`)
1527
+ return
1528
+ }
1529
+
1530
+ const hasPricePane = this.opt.panes.some((spec, index) => this.resolvePaneRole(spec, index) === 'price')
1531
+ const role: PaneRole = hasPricePane ? 'indicator' : 'price'
1532
+ this.applyPaneLayoutSpecs([
1533
+ ...this.opt.panes,
1534
+ { id: paneId, ratio: 1, visible: true, role },
1535
+ ])
1536
+ }
1537
+
1538
+ /**
1539
+ * 动态移除 pane
1540
+ * @param paneId pane 标识符
1541
+ */
1542
+ removePane(paneId: string): void {
1543
+ if (!this.opt.panes.some((spec) => spec.id === paneId)) return
1544
+
1545
+ const next = this.opt.panes.filter((spec) => spec.id !== paneId)
1546
+ this._internalPaneRatios.delete(paneId)
1547
+ this.applyPaneLayoutSpecs(next)
1548
+ }
1549
+
1550
+ /**
1551
+ * 检查 pane 是否存在
1552
+ * @param paneId pane 标识符
1553
+ */
1554
+ hasPane(paneId: string): boolean {
1555
+ return this.opt.panes.some((spec) => spec.id === paneId)
1556
+ }
1557
+
1558
+ // ========== 副图管理 API ==========
1559
+
1560
+ /**
1561
+ * 创建副图面板并注册指标渲染器
1562
+ * @param paneId 副图实例标识符(如 'RSI_0', 'MACD_0')
1563
+ * @param indicatorId 指标类型
1564
+ * @param params 指标参数
1565
+ * @returns 是否创建成功
1566
+ */
1567
+ createSubPane(paneId: string, indicatorId: SubIndicatorType, params?: Record<string, number | boolean | string>): boolean {
1568
+ // 调整 pane ratios:主图占 3,副图各占 1
1569
+ const visibleSpecs = this.opt.panes.filter((pane) => pane.visible !== false)
1570
+ const pricePanes = visibleSpecs.filter((pane, index) => this.resolvePaneRole(pane, index) === 'price')
1571
+ const indicatorPanes = visibleSpecs.filter((pane, index) => this.resolvePaneRole(pane, index) === 'indicator')
1572
+
1573
+ if (pricePanes.length === 1) {
1574
+ const pricePane = pricePanes[0]
1575
+ if (pricePane) {
1576
+ this._internalPaneRatios.set(pricePane.id, 3)
1577
+ }
1578
+ for (const pane of indicatorPanes) {
1579
+ this._internalPaneRatios.set(pane.id, 1)
1580
+ }
1581
+ this._internalPaneRatios.set(paneId, 1)
1582
+ } else {
1583
+ this._internalPaneRatios.set(paneId, 1)
1584
+ }
1585
+
1586
+ this.upsertPane({ id: paneId, ratio: this._internalPaneRatios.get(paneId) ?? 1, visible: true, role: 'indicator' })
1587
+
1588
+ const success = this.subPaneManager.create(this, paneId, indicatorId, params ?? this.getDefaultSubPaneParams(indicatorId))
1589
+ this.syncIndicatorsSignal()
1590
+ this.syncSubPanesSignal()
1591
+ return success
1592
+ }
1593
+
1594
+ /**
1595
+ * 移除副图面板及其渲染器
1596
+ * @param paneId 副图实例标识符
1597
+ */
1598
+ removeSubPane(paneId: string): void {
1599
+ this.subPaneManager.remove(this, paneId)
1600
+ this._internalPaneRatios.delete(paneId)
1601
+ this.syncIndicatorsSignal()
1602
+ this.syncSubPanesSignal()
1603
+ }
1604
+
1605
+ /**
1606
+ * 替换副图的指标类型
1607
+ * @param paneId 副图实例标识符
1608
+ * @param newIndicatorId 新的指标类型
1609
+ * @param params 新指标参数
1610
+ */
1611
+ replaceSubPaneIndicator(paneId: string, newIndicatorId: SubIndicatorType, params?: Record<string, number | boolean | string>): void {
1612
+ this.subPaneManager.replaceIndicator(this, paneId, newIndicatorId, params ?? this.getDefaultSubPaneParams(newIndicatorId))
1613
+ this.syncIndicatorsSignal()
1614
+ this.syncSubPanesSignal()
1615
+ }
1616
+
1617
+ /**
1618
+ * 更新副图指标参数
1619
+ * @param paneId 副图实例标识符
1620
+ * @param params 新参数
1621
+ */
1622
+ updateSubPaneParams(paneId: string, params: Record<string, unknown>): void {
1623
+ this.subPaneManager.updateParams(this, paneId, params)
1624
+ this.syncIndicatorsSignal()
1625
+ }
1626
+
1627
+ /**
1628
+ * 清除所有副图面板
1629
+ */
1630
+ clearSubPanes(): void {
1631
+ // 获取所有副图 paneId
1632
+ const subPaneIds = this.subPaneManager.getPaneIds()
1633
+
1634
+ if (subPaneIds.length === 0) return
1635
+
1636
+ // 移除所有副图
1637
+ this.subPaneManager.clear(this)
1638
+
1639
+ // 清理 pane ratios
1640
+ for (const paneId of subPaneIds) {
1641
+ this._internalPaneRatios.delete(paneId)
1642
+ }
1643
+
1644
+ // 更新布局,移除所有副图 pane
1645
+ this.applyPaneLayoutSpecs(this.opt.panes.filter((spec) => !subPaneIds.includes(spec.id)))
1646
+ this.syncIndicatorsSignal()
1647
+ this.syncSubPanesSignal()
1648
+ }
1649
+
1650
+ /**
1651
+ * 获取当前所有副图指标类型
1652
+ * @deprecated 使用 getSubPaneEntries 获取完整信息
1653
+ */
1654
+ getSubPaneIndicators(): SubIndicatorType[] {
1655
+ return this.subPaneManager.getAll().map((entry) => entry.indicatorId)
1656
+ }
1657
+
1658
+ /**
1659
+ * 获取所有副图条目
1660
+ */
1661
+ getSubPaneEntries(): SubPaneEntry[] {
1662
+ return this.subPaneManager.getAll()
1663
+ }
1664
+
1665
+ /**
1666
+ * 根据 paneId 获取副图条目
1667
+ * @param paneId 副图实例标识符
1668
+ */
1669
+ getSubPaneEntry(paneId: string): SubPaneEntry | undefined {
1670
+ return this.subPaneManager.getByPaneId(paneId)
1671
+ }
1672
+
1673
+ private getDefaultSubPaneParams(indicatorId: SubIndicatorType): Record<string, unknown> {
1674
+ // 默认参数定义在 SubPaneManager 中,这里导入使用
1675
+ const defaults: Record<SubIndicatorType, Record<string, unknown>> = {
1676
+ VOLUME: {},
1677
+ MACD: { fastPeriod: 12, slowPeriod: 26, signalPeriod: 9 },
1678
+ RSI: { period1: 6, period2: 12, period3: 24 },
1679
+ CCI: { period: 14, showCCI: true },
1680
+ STOCH: { n: 9, m: 3, showK: true, showD: true },
1681
+ MOM: { period: 10, showMOM: true },
1682
+ WMSR: { period: 14, showWMSR: true },
1683
+ KST: { roc1: 10, roc2: 15, roc3: 20, roc4: 30, signalPeriod: 9, showKST: true, showSignal: true },
1684
+ FASTK: { period: 9, showFASTK: true },
1685
+ ATR: { period: 14, showATR: true },
1686
+ WMA: { period: 10, showWMA: true },
1687
+ DEMA: { period: 14, showDEMA: true },
1688
+ TEMA: { period: 14, showTEMA: true },
1689
+ HMA: { period: 14, showHMA: true },
1690
+ KAMA: { period: 10, fastPeriod: 2, slowPeriod: 30, showKAMA: true },
1691
+ SAR: { step: 0.02, maxStep: 0.2, showSAR: true },
1692
+ SUPERTREND: { atrPeriod: 10, multiplier: 3, showSuperTrend: true },
1693
+ KELTNER: { emaPeriod: 20, atrPeriod: 10, multiplier: 2, showUpper: true, showMiddle: true, showLower: true },
1694
+ DONCHIAN: { period: 20, showUpper: true, showMiddle: true, showLower: true },
1695
+ ICHIMOKU: { tenkanPeriod: 9, kijunPeriod: 26, spanBPeriod: 52, displacement: 26, showTenkan: true, showKijun: true, showSpanA: true, showSpanB: true, showChikou: true, showCloud: true },
1696
+ ROC: { period: 12, showROC: true },
1697
+ TRIX: { period: 15, signalPeriod: 9, showTRIX: true, showSignal: true },
1698
+ HV: { period: 20, annualizationFactor: 252, showHV: true },
1699
+ PARKINSON: { period: 20, annualizationFactor: 252, showParkinson: true },
1700
+ CHAIKIN_VOL: { emaPeriod: 10, rocPeriod: 10, showChaikinVol: true },
1701
+ VMA: { period: 5, showVMA: true },
1702
+ OBV: { showOBV: true },
1703
+ PVT: { showPVT: true },
1704
+ VWAP: { sessionResetGapMs: 0, showVWAP: true },
1705
+ CMF: { period: 20, showCMF: true },
1706
+ MFI: { period: 14, showMFI: true },
1707
+ PIVOT: { showPP: true, showR1: true, showR2: true, showR3: false, showS1: true, showS2: true, showS3: false },
1708
+ FIB: { period: 50, showLevels: true },
1709
+ STRUCTURE: { leftWindow: 2, rightWindow: 2, breakoutSource: 'close', showSwingLabels: true, showBOS: true, showCHOCH: true, showProvisional: false },
1710
+ ZONES: { showFVG: true, showOB: true, showFilledZones: true, obLookback: 5 },
1711
+ VOLUME_PROFILE: { bins: 24, lookback: 0, valueAreaPercent: 0.7, showVolumeProfile: true },
1712
+ }
1713
+ return { ...defaults[indicatorId] }
1714
+ }
1715
+
1716
+ /** 副图渲染器名称前缀(保留向后兼容) */
1717
+ private static readonly SUB_PANE_PREFIX = 'sub_'
1718
+
1719
+ /**
1720
+ * 平移价格轴(用于主图区域上下拖动)
1721
+ * @param paneId 目标 pane ID
1722
+ * @param deltaY Y轴像素偏移(正数向下拖动)
1723
+ */
1724
+ translatePrice(paneId: string, deltaY: number): void {
1725
+ const renderer = this.paneRenderers.find(r => r.getPane().id === paneId)
1726
+ if (!renderer) return
1727
+
1728
+ const pane = renderer.getPane()
1729
+ if (!pane.capabilities.supportsPriceTranslate) return
1730
+
1731
+ const priceOffset = pane.yAxis.deltaYToPriceOffset(deltaY)
1732
+ const currentOffset = pane.yAxis.getPriceOffset()
1733
+ pane.yAxis.setPriceOffset(currentOffset + priceOffset)
1734
+ this.scheduleDraw()
1735
+ }
1736
+
1737
+ /**
1738
+ * 重置价格轴垂直偏移
1739
+ * @param paneId 目标 pane ID
1740
+ */
1741
+ resetPriceOffset(paneId: string): void {
1742
+ const renderer = this.paneRenderers.find(r => r.getPane().id === paneId)
1743
+ if (!renderer) return
1744
+ renderer.getPane().yAxis.resetPriceOffset()
1745
+ this.scheduleDraw()
1746
+ }
1747
+
1748
+ /**
1749
+ * 缩放价格轴(用于右侧刻度栏上下拖动)
1750
+ * @param paneId 目标 pane ID
1751
+ * @param deltaY Y轴像素偏移(向上拖动放大,向下拖动缩小)
1752
+ */
1753
+ scalePrice(paneId: string, deltaY: number): void {
1754
+ const renderer = this.paneRenderers.find(r => r.getPane().id === paneId)
1755
+ if (!renderer) return
1756
+
1757
+ const pane = renderer.getPane()
1758
+ if (!pane.capabilities.supportsPriceTranslate) return
1759
+
1760
+ pane.yAxis.scaleByDelta(deltaY)
1761
+ this.scheduleDraw()
1762
+ }
1763
+ /**
1764
+ * 更新数据并请求重绘
1765
+ * @param data K 线数据数组
1766
+ */
1767
+ updateData(data: KLineData[]) {
1768
+ this._internalData = data ?? []
1769
+ this._dataSignal.set([...this._internalData])
1770
+ this.onDataChange?.(this._internalData)
1771
+
1772
+ // 重算 DOM scrollLeft 状态, 防止左右滚动超出数据长度范围
1773
+ const container = this.dom.container
1774
+ if (container) {
1775
+ const contentWidth = this.getContentWidth()
1776
+ const maxScrollLeft = Math.max(0, contentWidth - container.clientWidth)
1777
+ if (this.cachedScrollLeft > maxScrollLeft) {
1778
+ container.scrollLeft = maxScrollLeft
1779
+ this.cachedScrollLeft = maxScrollLeft
1780
+ }
1781
+ }
1782
+
1783
+ // 重置交互状态
1784
+ this.interaction.reset()
1785
+
1786
+ // 触发指标计算(在 scheduleDraw 之前,确保渲染器读到最新状态)
1787
+ this.indicatorScheduler.update(this._internalData, this.lastVisibleRange)
1788
+
1789
+ this.scheduleDraw()
1790
+ }
1791
+
1792
+ /** 获取当前数据源(供 renderers 和 interaction 使用) */
1793
+ getData(): KLineData[] {
1794
+ return this._internalData
1795
+ }
1796
+
1797
+ /** 获取指标调度器(供外部控制器更新指标配置) */
1798
+ getIndicatorScheduler(): IndicatorScheduler {
1799
+ return this.indicatorScheduler
1800
+ }
1801
+
1802
+ private getTrailingSlotCount(): number {
1803
+ return 24
1804
+ }
1805
+
1806
+ getLogicalSlotCount(): number {
1807
+ return this._internalData.length + this.getTrailingSlotCount()
1808
+ }
1809
+
1810
+ getTimestampAtLogicalIndex(index: number): number | null {
1811
+ if (!Number.isInteger(index) || index < 0 || index >= this._internalData.length) return null
1812
+ return this._internalData[index]?.timestamp ?? null
1813
+ }
1814
+
1815
+ /** 根据视口内 X 坐标反查逻辑索引(允许超出最后一根 K 线) */
1816
+ getLogicalIndexAtX(mouseX: number): number | null {
1817
+ const vp = this._internalViewport
1818
+ if (!vp || this._internalData.length === 0) return null
1819
+ const dpr = this.getEffectiveDpr()
1820
+ const { startXPx, unitPx } = getPhysicalKLineConfig(this.opt.kWidth, this.opt.kGap, dpr)
1821
+ const worldX = Math.round((vp.scrollLeft + mouseX) * dpr)
1822
+ const index = Math.floor((worldX - startXPx) / unitPx)
1823
+ if (index < 0) return null
1824
+ return index
1825
+ }
1826
+
1827
+ /** 根据视口内 X 坐标反查数据索引(用于绘图落点) */
1828
+ getDataIndexAtX(mouseX: number): number | null {
1829
+ const index = this.getLogicalIndexAtX(mouseX)
1830
+ if (index === null || index >= this._internalData.length) return null
1831
+ return index
1832
+ }
1833
+
1834
+
1835
+ /** 获取内容总宽度(用于外部 scroll-content 撑开 scrollWidth) */
1836
+ getContentWidth(): number {
1837
+ return computeContentWidth({
1838
+ dataLength: this._internalData.length,
1839
+ kWidth: this.opt.kWidth,
1840
+ kGap: this.opt.kGap,
1841
+ viewWidth: this._internalViewport?.plotWidth ?? 0,
1842
+ viewportDpr: this.getEffectiveDpr(),
1843
+ })
1844
+ }
1845
+
1846
+
1847
+ /** 容器尺寸变化时调用 */
1848
+ resize() {
1849
+ const vp = this.computeViewport()
1850
+ // 防御性检查:容器尺寸无效时跳过布局
1851
+ if (!vp || vp.viewWidth < 10 || vp.viewHeight < 10) {
1852
+ return
1853
+ }
1854
+ this.cachedDrawFrame = null
1855
+ this.layoutPanes()
1856
+ this.emitPaneLayoutChange()
1857
+ this.updateViewportSignal()
1858
+ this.scheduleDraw()
1859
+ }
1860
+
1861
+ /**
1862
+ * 请求下一帧重绘(RAF 合并,支持分层更新)
1863
+ * @param level 更新级别,默认为 All
1864
+ */
1865
+ scheduleDraw(level: UpdateLevel = UpdateLevel.All): void {
1866
+ // 合并更新级别:如果已有更高级别的调度,保持高级别
1867
+ if (this.raf !== null) {
1868
+ // 已有 All 级别调度,任何新请求都忽略
1869
+ if (this.pendingUpdateLevel === UpdateLevel.All) return
1870
+ // 新请求是 All,覆盖之前的 Main/Overlay
1871
+ if (level === UpdateLevel.All) {
1872
+ this.pendingUpdateLevel = UpdateLevel.All
1873
+ return
1874
+ }
1875
+ // Main + Overlay = All
1876
+ if (
1877
+ (this.pendingUpdateLevel === UpdateLevel.Main && level === UpdateLevel.Overlay) ||
1878
+ (this.pendingUpdateLevel === UpdateLevel.Overlay && level === UpdateLevel.Main)
1879
+ ) {
1880
+ this.pendingUpdateLevel = UpdateLevel.All
1881
+ return
1882
+ }
1883
+ // 同级别或更低级别,忽略
1884
+ return
1885
+ }
1886
+
1887
+ this.pendingUpdateLevel = level
1888
+ this.raf = requestAnimationFrame(() => {
1889
+ this.raf = null
1890
+ const levelToDraw = this.pendingUpdateLevel
1891
+ this.pendingUpdateLevel = UpdateLevel.All // 重置为默认值
1892
+ this.draw(levelToDraw)
1893
+ })
1894
+ }
1895
+
1896
+ /** 销毁图表实例 */
1897
+ async destroy() {
1898
+ if (this.raf !== null) {
1899
+ cancelAnimationFrame(this.raf)
1900
+ this.raf = null
1901
+ }
1902
+
1903
+ // 清理尺寸观察器
1904
+ this.resizeObserver?.disconnect()
1905
+ this.resizeObserver = undefined
1906
+ this.preciseDpr = 0
1907
+ this.observedSize = { width: 0, height: 0 }
1908
+
1909
+ // 清理 scroll 监听
1910
+ if (this.onScroll) {
1911
+ this.dom.container?.removeEventListener('scroll', this.onScroll)
1912
+ this.onScroll = undefined
1913
+ }
1914
+
1915
+ this._internalViewport = null
1916
+ this.cachedDrawFrame = null
1917
+ this.xAxisCtx = null
1918
+ this.paneRenderers.forEach((r) => r.destroy())
1919
+ this.paneRenderers = []
1920
+ this.sharedWebGLSurface.destroy()
1921
+
1922
+ // 清理渲染器插件管理器(会调用所有 onUninstall)
1923
+ this.rendererPluginManager.clear()
1924
+
1925
+ this.onViewportChange = undefined
1926
+ this.onPaneLayoutChange = undefined
1927
+ this.indicatorScheduler.destroy()
1928
+ await this.pluginHost.destroy()
1929
+ }
1930
+
1931
+ /** 初始化所有 pane */
1932
+ private initPanes() {
1933
+ this.paneRenderers = this.opt.panes.map((spec, index) => {
1934
+ const pane = new Pane(spec.id, {
1935
+ role: this.resolvePaneRole(spec, index),
1936
+ capabilities: spec.capabilities,
1937
+ })
1938
+
1939
+ const mainCanvas = document.createElement('canvas')
1940
+ const overlayCanvas = document.createElement('canvas')
1941
+ const yAxisCanvas = document.createElement('canvas')
1942
+
1943
+ const isMain = pane.role === 'price'
1944
+
1945
+ // Main Canvas - K线、指标、网格
1946
+ mainCanvas.id = `${spec.id}-main`
1947
+ mainCanvas.className = isMain ? 'main-canvas main' : 'main-canvas sub'
1948
+ mainCanvas.style.position = 'absolute'
1949
+ mainCanvas.style.left = '0'
1950
+ mainCanvas.style.top = '0'
1951
+
1952
+ // Overlay Canvas - 十字线、Tooltip(透明,事件穿透)
1953
+ overlayCanvas.id = `${spec.id}-overlay`
1954
+ overlayCanvas.className = 'overlay-canvas'
1955
+ overlayCanvas.style.position = 'absolute'
1956
+ overlayCanvas.style.left = '0'
1957
+ overlayCanvas.style.top = '0'
1958
+ overlayCanvas.style.pointerEvents = 'none' // 事件穿透到 mainCanvas
1959
+ overlayCanvas.style.backgroundColor = 'transparent'
1960
+
1961
+ yAxisCanvas.id = `${spec.id}-yAxis`
1962
+ yAxisCanvas.className = 'right-axis'
1963
+ yAxisCanvas.style.position = 'absolute'
1964
+ yAxisCanvas.style.left = '0'
1965
+
1966
+ const renderer = new PaneRenderer(
1967
+ { mainCanvas, overlayCanvas, yAxisCanvas },
1968
+ pane,
1969
+ {
1970
+ rightAxisWidth: this.opt.rightAxisWidth,
1971
+ yPaddingPx: this.opt.yPaddingPx,
1972
+ priceLabelWidth: this.opt.priceLabelWidth,
1973
+ },
1974
+ this.sharedWebGLSurface,
1975
+ )
1976
+
1977
+ return renderer
1978
+ })
1979
+
1980
+ const canvasLayer = this.dom.canvasLayer
1981
+ const rightAxisLayer = this.dom.rightAxisLayer
1982
+ if (canvasLayer) {
1983
+ const existingCanvases = canvasLayer.querySelectorAll('canvas:not(.x-axis-canvas)')
1984
+ existingCanvases.forEach((canvas) => canvas.remove())
1985
+ }
1986
+ if (rightAxisLayer) {
1987
+ const existingAxisCanvases = rightAxisLayer.querySelectorAll('canvas.right-axis')
1988
+ existingAxisCanvases.forEach((canvas) => canvas.remove())
1989
+ }
1990
+
1991
+ this.paneRenderers.forEach((renderer) => {
1992
+ const dom = renderer.getDom()
1993
+ // 先添加 mainCanvas,再添加 overlayCanvas(overlay 在上层)
1994
+ canvasLayer.appendChild(dom.mainCanvas)
1995
+ canvasLayer.appendChild(dom.overlayCanvas)
1996
+ rightAxisLayer.appendChild(dom.yAxisCanvas)
1997
+ })
1998
+
1999
+ this.rendererPluginManager.setKnownPaneIds(this.paneRenderers.map((renderer) => renderer.getPane().id))
2000
+ }
2001
+
2002
+
2003
+ private syncPaneRatiosFromSpecs(specs: PaneSpec[]): void {
2004
+ const next = new Map<string, number>()
2005
+ for (const spec of specs) {
2006
+ const prev = this._internalPaneRatios.get(spec.id)
2007
+ const incoming = Number.isFinite(spec.ratio) ? spec.ratio : 0
2008
+ const ratio = prev !== undefined ? prev : (incoming > 0 ? incoming : 1)
2009
+ next.set(spec.id, ratio)
2010
+ }
2011
+ this._internalPaneRatios = next
2012
+ this.normalizeVisiblePaneRatios(specs)
2013
+ this.syncPaneRatiosToSpecs()
2014
+ }
2015
+
2016
+ private syncPaneRatiosToSpecs(): void {
2017
+ const visible = this.opt.panes.filter(p => p.visible !== false)
2018
+ const visibleSum = visible.reduce((s, p) => s + (this._internalPaneRatios.get(p.id) ?? p.ratio ?? 0), 0)
2019
+ const safeVisibleSum = visibleSum > 0 ? visibleSum : 1
2020
+
2021
+ this.opt.panes = this.opt.panes.map((spec) => {
2022
+ const ratio = this._internalPaneRatios.get(spec.id) ?? spec.ratio ?? 0
2023
+ if (spec.visible === false) {
2024
+ return { ...spec, ratio }
2025
+ }
2026
+ return { ...spec, ratio: ratio / safeVisibleSum }
2027
+ })
2028
+ }
2029
+
2030
+ private normalizeVisiblePaneRatios(specs: PaneSpec[]): void {
2031
+ const visible = specs.filter(p => p.visible !== false)
2032
+ if (visible.length === 0) return
2033
+
2034
+ let sum = 0
2035
+ for (const spec of visible) {
2036
+ const raw = this._internalPaneRatios.get(spec.id) ?? spec.ratio ?? 0
2037
+ const safe = Number.isFinite(raw) && raw > 0 ? raw : 0
2038
+ this._internalPaneRatios.set(spec.id, safe)
2039
+ sum += safe
2040
+ }
2041
+
2042
+ if (sum <= 0) {
2043
+ const equal = 1 / visible.length
2044
+ for (const spec of visible) {
2045
+ this._internalPaneRatios.set(spec.id, equal)
2046
+ }
2047
+ return
2048
+ }
2049
+
2050
+ for (const spec of visible) {
2051
+ const v = this._internalPaneRatios.get(spec.id) ?? 0
2052
+ this._internalPaneRatios.set(spec.id, v / sum)
2053
+ }
2054
+ }
2055
+
2056
+ private getPaneMinHeight(spec: PaneSpec, plotHeight: number): number {
2057
+ const fallback = this.opt.defaultPaneMinHeightPx ?? 120 // 最小高度
2058
+ const raw = spec.minHeightPx ?? fallback
2059
+ return Math.max(1, Math.min(Math.round(raw), Math.max(1, plotHeight)))
2060
+ }
2061
+
2062
+ private computePaneHeightsByRatio(visibleSpecs: PaneSpec[], availableH: number): number[] {
2063
+ if (visibleSpecs.length === 0) return []
2064
+
2065
+ const ratios = visibleSpecs.map(spec => this._internalPaneRatios.get(spec.id) ?? spec.ratio ?? 0)
2066
+ const ratioSum = ratios.reduce((s, r) => s + (r > 0 ? r : 0), 0)
2067
+ const safeRatios = ratioSum > 0
2068
+ ? ratios.map(r => (r > 0 ? r : 0) / ratioSum)
2069
+ : visibleSpecs.map(() => 1 / visibleSpecs.length)
2070
+
2071
+ const heights = safeRatios.map(r => Math.max(1, Math.round(availableH * r)))
2072
+ const mins = visibleSpecs.map(spec => this.getPaneMinHeight(spec, availableH))
2073
+
2074
+ for (let i = 0; i < heights.length; i++) {
2075
+ heights[i] = Math.max(heights[i]!, Math.min(mins[i]!, availableH))
2076
+ }
2077
+
2078
+ let total = heights.reduce((s, h) => s + h, 0)
2079
+
2080
+ if (total > availableH) {
2081
+ let overflow = total - availableH
2082
+ while (overflow > 0) {
2083
+ let shrunk = false
2084
+ for (let i = heights.length - 1; i >= 0 && overflow > 0; i--) {
2085
+ const minH = Math.max(1, Math.min(mins[i]!, availableH))
2086
+ const h = heights[i]!
2087
+ if (h > minH) {
2088
+ heights[i] = h - 1
2089
+ overflow--
2090
+ shrunk = true
2091
+ }
2092
+ }
2093
+ if (!shrunk) break
2094
+ }
2095
+ } else if (total < availableH) {
2096
+ heights[heights.length - 1] = (heights[heights.length - 1] ?? 1) + (availableH - total)
2097
+ }
2098
+
2099
+ total = heights.reduce((s, h) => s + h, 0)
2100
+ if (total !== availableH && heights.length > 0) {
2101
+ heights[heights.length - 1] = Math.max(1, (heights[heights.length - 1] ?? 1) + (availableH - total))
2102
+ }
2103
+
2104
+ return heights
2105
+ }
2106
+
2107
+ /** 计算每个 pane 的布局(top 和 height) */
2108
+ private layoutPanes() {
2109
+ const vp = this._internalViewport
2110
+ if (!vp) return
2111
+
2112
+ const visibleSpecs = this.opt.panes.filter(p => p.visible !== false)
2113
+ if (visibleSpecs.length === 0) return
2114
+
2115
+ const gap = Math.max(0, this.opt.paneGap ?? 0)
2116
+ let y = 0
2117
+
2118
+ const totalGaps = gap * Math.max(0, visibleSpecs.length - 1)
2119
+ const availableH = Math.max(1, vp.plotHeight - totalGaps)
2120
+
2121
+ this.normalizeVisiblePaneRatios(visibleSpecs)
2122
+ const paneHeights = this.computePaneHeightsByRatio(visibleSpecs, availableH)
2123
+
2124
+ for (let i = 0; i < visibleSpecs.length; i++) {
2125
+ const spec = visibleSpecs[i]
2126
+ if (!spec) continue
2127
+
2128
+ const renderer = this.paneRenderers.find(r => r.getPane().id === spec.id)
2129
+ if (!renderer) continue
2130
+
2131
+ const pane = renderer.getPane()
2132
+ const h = paneHeights[i] ?? 1
2133
+
2134
+ pane.setLayout(y, h)
2135
+ pane.setPadding(this.opt.yPaddingPx, this.opt.yPaddingPx)
2136
+
2137
+ renderer.resize(vp.plotWidth, h, vp.dpr)
2138
+ renderer.setWebGLRegion({
2139
+ x: 0,
2140
+ y,
2141
+ width: vp.plotWidth,
2142
+ height: h,
2143
+ dpr: vp.dpr,
2144
+ })
2145
+ this.rendererPluginManager.notifyResize(pane.id, wrapPaneInfo(pane))
2146
+ const dom = renderer.getDom()
2147
+ dom.mainCanvas.style.top = `${y}px`
2148
+ dom.overlayCanvas.style.top = `${y}px`
2149
+ dom.yAxisCanvas.style.top = `${y}px`
2150
+ dom.yAxisCanvas.style.left = '0px'
2151
+
2152
+ y += h + gap
2153
+ }
2154
+
2155
+ // 按实际像素高度回写 ratio,确保后续 resize 视觉比例稳定
2156
+ const finalAvailable = Math.max(1, availableH)
2157
+ for (const spec of visibleSpecs) {
2158
+ const renderer = this.paneRenderers.find(r => r.getPane().id === spec.id)
2159
+ if (!renderer) continue
2160
+ const h = renderer.getPane().height
2161
+ this._internalPaneRatios.set(spec.id, h / finalAvailable)
2162
+ }
2163
+ this.normalizeVisiblePaneRatios(visibleSpecs)
2164
+ this.syncPaneRatiosToSpecs()
2165
+ }
2166
+ private computeViewport(): Viewport | null {
2167
+ const container = this.dom.container
2168
+ if (!container) return null
2169
+
2170
+ const observedWidth = this.observedSize.width
2171
+ const observedHeight = this.observedSize.height
2172
+ const viewWidth = observedWidth > 0
2173
+ ? observedWidth
2174
+ : Math.max(1, Math.round(container.clientWidth))
2175
+ const viewHeight = observedHeight > 0
2176
+ ? observedHeight
2177
+ : Math.max(1, Math.round(container.clientHeight))
2178
+
2179
+ const plotWidth = Math.round(viewWidth)
2180
+ const plotHeight = Math.round(viewHeight - this.opt.bottomAxisHeight)
2181
+
2182
+ let dpr = this.getEffectiveDpr()
2183
+
2184
+ const MAX_CANVAS_PIXELS = 16 * 1024 * 1024
2185
+ const requestedPixels = viewWidth * dpr * (viewHeight * dpr)
2186
+ if (requestedPixels > MAX_CANVAS_PIXELS) {
2187
+ dpr = Math.sqrt(MAX_CANVAS_PIXELS / (viewWidth * viewHeight))
2188
+ }
2189
+
2190
+ // 对齐 scrollLeft,消除 translate 亚像素偏移
2191
+ const scrollLeft = Math.round(this.cachedScrollLeft * dpr) / dpr
2192
+
2193
+ const canvasLayerWidth = `${viewWidth}px`
2194
+ if (this.dom.canvasLayer.style.width !== canvasLayerWidth) {
2195
+ this.dom.canvasLayer.style.width = canvasLayerWidth
2196
+ }
2197
+
2198
+ const canvasLayerHeight = `${viewHeight}px`
2199
+ if (this.dom.canvasLayer.style.height !== canvasLayerHeight) {
2200
+ this.dom.canvasLayer.style.height = canvasLayerHeight
2201
+ }
2202
+
2203
+ const xAxisWidth = Math.round(plotWidth * dpr)
2204
+ if (this.dom.xAxisCanvas.width !== xAxisWidth) {
2205
+ this.dom.xAxisCanvas.width = xAxisWidth
2206
+ }
2207
+
2208
+ const xAxisHeight = Math.round(this.opt.bottomAxisHeight * dpr)
2209
+ if (this.dom.xAxisCanvas.height !== xAxisHeight) {
2210
+ this.dom.xAxisCanvas.height = xAxisHeight
2211
+ }
2212
+
2213
+ const xAxisCssWidth = `${xAxisWidth / dpr}px`
2214
+ if (this.dom.xAxisCanvas.style.width !== xAxisCssWidth) {
2215
+ this.dom.xAxisCanvas.style.width = xAxisCssWidth
2216
+ }
2217
+
2218
+ const xAxisCssHeight = `${xAxisHeight / dpr}px`
2219
+ if (this.dom.xAxisCanvas.style.height !== xAxisCssHeight) {
2220
+ this.dom.xAxisCanvas.style.height = xAxisCssHeight
2221
+ }
2222
+
2223
+ this.sharedWebGLSurface.resize(plotWidth, plotHeight, dpr)
2224
+
2225
+ const vp: Viewport = {
2226
+ viewWidth,
2227
+ viewHeight,
2228
+ plotWidth,
2229
+ plotHeight,
2230
+ scrollLeft,
2231
+ dpr,
2232
+ }
2233
+ const prevViewport = this._internalViewport
2234
+ const viewportChanged = !prevViewport
2235
+ || prevViewport.viewWidth !== vp.viewWidth
2236
+ || prevViewport.viewHeight !== vp.viewHeight
2237
+ || prevViewport.plotWidth !== vp.plotWidth
2238
+ || prevViewport.plotHeight !== vp.plotHeight
2239
+ || prevViewport.scrollLeft !== vp.scrollLeft
2240
+ || prevViewport.dpr !== vp.dpr
2241
+
2242
+ this._internalViewport = vp
2243
+ if (viewportChanged) {
2244
+ this.onViewportChange?.(vp)
2245
+ }
2246
+ return vp
2247
+ }
2248
+
2249
+ // ==================== Facade API (High-level interface for adapters) ====================
2250
+
2251
+ // ---------- Signals ----------
2252
+ private _viewportSignal = createSignal<ViewportState>({
2253
+ zoomLevel: 1,
2254
+ plotWidth: 0,
2255
+ plotHeight: 0,
2256
+ dpr: 1,
2257
+ visibleFrom: 0,
2258
+ visibleTo: 0,
2259
+ desiredScrollLeft: undefined,
2260
+ kWidth: 0,
2261
+ kGap: 1,
2262
+ })
2263
+
2264
+ private _dataSignal = createSignal<ReadonlyArray<KLineData>>([])
2265
+ private _themeSignal = createSignal<'light' | 'dark'>('light')
2266
+ private _indicatorsSignal = createSignal<ReadonlyArray<IndicatorInstance>>([])
2267
+ private _subPanesSignal = createSignal<ReadonlyArray<SubPaneInfo>>([])
2268
+ private _drawingToolSignal = createSignal<DrawingToolType | null>(null)
2269
+ private _drawingsSignal = createSignal<ReadonlyArray<import('../plugin').DrawingObject>>([])
2270
+ private _paneRatiosSignal = createSignal<Readonly<Record<string, number>>>({})
2271
+ private _interactionSignal = createSignal<InteractionSnapshot>({
2272
+ crosshairPos: null,
2273
+ crosshairIndex: null,
2274
+ crosshairPrice: null,
2275
+ hoveredIndex: null,
2276
+ activePaneId: null,
2277
+ tooltipPos: { x: 0, y: 0 },
2278
+ tooltipAnchorPlacement: 'right-bottom',
2279
+ hoveredMarkerData: null,
2280
+ hoveredCustomMarker: null,
2281
+ isDragging: false,
2282
+ isResizingPaneBoundary: false,
2283
+ isHoveringPaneBoundary: false,
2284
+ hoveredPaneBoundaryId: null,
2285
+ isHoveringRightAxis: false,
2286
+ })
2287
+
2288
+ /** 视口状态信号 */
2289
+ get viewport(): Signal<ViewportState> {
2290
+ return this._viewportSignal
2291
+ }
2292
+
2293
+ /** 数据信号 */
2294
+ get data(): Signal<ReadonlyArray<KLineData>> {
2295
+ return this._dataSignal
2296
+ }
2297
+
2298
+ /** 主题信号 */
2299
+ get theme(): Signal<'light' | 'dark'> {
2300
+ return this._themeSignal
2301
+ }
2302
+
2303
+ /** 指标实例列表信号 */
2304
+ get indicators(): Signal<ReadonlyArray<IndicatorInstance>> {
2305
+ return this._indicatorsSignal
2306
+ }
2307
+
2308
+ /** 子图信息信号 */
2309
+ get subPanes(): Signal<ReadonlyArray<SubPaneInfo>> {
2310
+ return this._subPanesSignal
2311
+ }
2312
+
2313
+ /** 当前绘图工具信号 */
2314
+ get drawingTool(): Signal<DrawingToolType | null> {
2315
+ return this._drawingToolSignal
2316
+ }
2317
+
2318
+ /** 绘图对象列表信号 */
2319
+ get drawings(): Signal<ReadonlyArray<import('../plugin').DrawingObject>> {
2320
+ return this._drawingsSignal
2321
+ }
2322
+
2323
+ /** 面板比例信号 */
2324
+ get paneRatios(): Signal<Readonly<Record<string, number>>> {
2325
+ return this._paneRatiosSignal
2326
+ }
2327
+
2328
+ /** 交互状态信号 */
2329
+ get interactionState(): Signal<InteractionSnapshot> {
2330
+ return this._interactionSignal
2331
+ }
2332
+
2333
+ // ---------- Data ----------
2334
+
2335
+ /**
2336
+ * 设置数据(高层 API)
2337
+ * 内部调用 updateData,并更新 data signal
2338
+ */
2339
+ setData(data: KLineData[]): void {
2340
+ this.updateData(data)
2341
+ }
2342
+
2343
+ /**
2344
+ * 追加数据(高层 API)
2345
+ * 合并现有数据并更新
2346
+ */
2347
+ appendData(newData: KLineData[]): void {
2348
+ const merged = [...this._internalData, ...newData]
2349
+ this.setData(merged)
2350
+ }
2351
+
2352
+ // ---------- Theme ----------
2353
+
2354
+ /**
2355
+ * 设置主题(高层 API)
2356
+ */
2357
+ setTheme(theme: 'light' | 'dark'): void {
2358
+ this._themeSignal.set(theme)
2359
+ this.scheduleDraw()
2360
+ }
2361
+
2362
+ // ---------- Zoom ----------
2363
+
2364
+ /**
2365
+ * 缩放到指定级别(高层 API)
2366
+ * 计算并应用新的 render state,更新 viewport signal
2367
+ */
2368
+ zoomToLevel(level: number, anchorX?: number): void {
2369
+ const clamped = Math.max(1, Math.min(this.zoomLevelCount, Math.round(level)))
2370
+ this.applyZoom(clamped, anchorX)
2371
+ }
2372
+
2373
+ /**
2374
+ * 放大(高层 API)
2375
+ */
2376
+ zoomIn(anchorX?: number): void {
2377
+ this.zoomToLevel(this.currentZoomLevel + 1, anchorX)
2378
+ }
2379
+
2380
+ /**
2381
+ * 缩小(高层 API)
2382
+ */
2383
+ zoomOut(anchorX?: number): void {
2384
+ this.zoomToLevel(this.currentZoomLevel - 1, anchorX)
2385
+ }
2386
+
2387
+ /**
2388
+ * 内部缩放实现
2389
+ * 使用 computeZoom 纯函数计算精确的 scrollLeft
2390
+ */
2391
+ private applyZoom(targetLevel: number, anchorViewportX?: number): void {
2392
+ if (targetLevel === this.currentZoomLevel) return
2393
+
2394
+ const delta = targetLevel - this.currentZoomLevel
2395
+ const scrollLeft = this.getCachedScrollLeft()
2396
+ const dpr = this.getCurrentDpr()
2397
+
2398
+ const result = computeZoom(
2399
+ delta,
2400
+ anchorViewportX ?? 0,
2401
+ scrollLeft,
2402
+ this.currentZoomLevel,
2403
+ this.opt.kWidth,
2404
+ this.opt.kGap,
2405
+ {
2406
+ minKWidth: this.opt.minKWidth,
2407
+ maxKWidth: this.opt.maxKWidth,
2408
+ zoomLevelCount: this.zoomLevelCount,
2409
+ dpr,
2410
+ },
2411
+ )
2412
+
2413
+ if (!result) return
2414
+
2415
+ // 应用 render state
2416
+ this.currentZoomLevel = result.targetLevel
2417
+ this.applyRenderState(result.newKWidth, result.newKGap, result.targetLevel)
2418
+
2419
+ // 更新 viewport signal
2420
+ this._viewportSignal.set({
2421
+ zoomLevel: result.targetLevel,
2422
+ plotWidth: this._internalViewport?.plotWidth ?? 0,
2423
+ plotHeight: this._internalViewport?.plotHeight ?? 0,
2424
+ dpr,
2425
+ visibleFrom: this.lastVisibleRange.start,
2426
+ visibleTo: this.lastVisibleRange.end,
2427
+ desiredScrollLeft: result.newScrollLeft,
2428
+ kWidth: result.newKWidth,
2429
+ kGap: result.newKGap,
2430
+ })
2431
+ }
2432
+
2433
+ // ---------- Interaction (Zero-config unified entry) ----------
2434
+
2435
+ /**
2436
+ * 统一指针事件处理(零配置)
2437
+ * 自动判断区域并分发给 interaction controller
2438
+ *
2439
+ * @param e 指针事件
2440
+ * @param drawingController 可选的绘图控制器,如果提供,会优先让绘图控制器处理事件
2441
+ * @returns 是否被处理(如果 drawingController 处理了返回 true,否则返回 false)
2442
+ */
2443
+ handlePointerEvent(e: PointerEvent, drawingController?: {
2444
+ onPointerDown?: (e: PointerEvent, container: HTMLElement) => boolean
2445
+ onPointerMove?: (e: PointerEvent, container: HTMLElement) => boolean
2446
+ onPointerUp?: (e: PointerEvent, container: HTMLElement) => boolean
2447
+ }): boolean {
2448
+ // 判断事件目标是否在右轴区域
2449
+ const isRightAxis = this.dom.rightAxisLayer.contains(e.target as Node)
2450
+
2451
+ switch (e.type) {
2452
+ case 'pointerdown':
2453
+ // 优先让绘图控制器处理
2454
+ if (drawingController?.onPointerDown) {
2455
+ const handled = drawingController.onPointerDown(e, this.dom.container)
2456
+ if (handled) return true
2457
+ }
2458
+ if (isRightAxis) {
2459
+ this.interaction.onRightAxisPointerDown(e)
2460
+ } else {
2461
+ this.interaction.onPointerDown(e)
2462
+ }
2463
+ return false
2464
+ case 'pointermove':
2465
+ // 优先让绘图控制器处理
2466
+ if (drawingController?.onPointerMove) {
2467
+ const handled = drawingController.onPointerMove(e, this.dom.container)
2468
+ if (handled) return true
2469
+ }
2470
+ if (isRightAxis) {
2471
+ this.interaction.onRightAxisPointerMove(e)
2472
+ } else {
2473
+ this.interaction.onPointerMove(e)
2474
+ }
2475
+ return false
2476
+ case 'pointerup':
2477
+ // 优先让绘图控制器处理
2478
+ if (drawingController?.onPointerUp) {
2479
+ const handled = drawingController.onPointerUp(e, this.dom.container)
2480
+ if (handled) return true
2481
+ }
2482
+ if (isRightAxis) {
2483
+ this.interaction.onRightAxisPointerUp(e)
2484
+ } else {
2485
+ this.interaction.onPointerUp(e)
2486
+ }
2487
+ return false
2488
+ case 'pointerleave':
2489
+ // pointerleave 通常不用于绘图,直接交给 interaction
2490
+ if (isRightAxis) {
2491
+ this.interaction.onRightAxisPointerLeave(e)
2492
+ } else {
2493
+ this.interaction.onPointerLeave(e)
2494
+ }
2495
+ return false
2496
+ default:
2497
+ return false
2498
+ }
2499
+ }
2500
+
2501
+ /**
2502
+ * 滚轮事件处理(高层 API)
2503
+ * 使用 computeZoom 计算精确的 scrollLeft,更新 viewport signal
2504
+ */
2505
+ handleWheelEvent(e: WheelEvent): void {
2506
+ const delta = e.deltaY > 0 ? -1 : 1
2507
+ const targetLevel = Math.max(1, Math.min(this.zoomLevelCount, this.currentZoomLevel + delta))
2508
+
2509
+ if (targetLevel === this.currentZoomLevel) return
2510
+
2511
+ // 获取鼠标在视口中的位置作为缩放锚点(视口局部坐标)
2512
+ const rect = this.dom.container.getBoundingClientRect()
2513
+ const mouseX = e.clientX - rect.left
2514
+
2515
+ this.applyZoom(targetLevel, mouseX)
2516
+ }
2517
+
2518
+ /**
2519
+ * 滚动事件处理(高层 API)
2520
+ * 更新缓存的 scrollLeft 并触发交互 controller
2521
+ */
2522
+ handleScrollEvent(): void {
2523
+ this.interaction.onScroll()
2524
+ // 更新 viewport signal 中的 visible range
2525
+ this.updateViewportSignal()
2526
+ }
2527
+
2528
+ /**
2529
+ * 双指捏合缩放处理(高层 API)
2530
+ * @param delta 缩放增量(+1 放大 / -1 缩小)
2531
+ * @param centerClientX 捏合中心在视口中的 X 坐标
2532
+ */
2533
+ handlePinchZoom(delta: number, centerClientX: number): void {
2534
+ const targetLevel = Math.max(1, Math.min(this.zoomLevelCount, this.currentZoomLevel + delta))
2535
+ if (targetLevel === this.currentZoomLevel) return
2536
+
2537
+ // centerClientX 已经是视口局部坐标,直接使用
2538
+ this.applyZoom(targetLevel, centerClientX)
2539
+ }
2540
+
2541
+ /**
2542
+ * 更新 viewport signal(用于滚动事件,不更新 desiredScrollLeft)
2543
+ */
2544
+ private updateViewportSignal(): void {
2545
+ const vp = this._internalViewport
2546
+ if (!vp) return
2547
+
2548
+ this._viewportSignal.set({
2549
+ zoomLevel: this.currentZoomLevel,
2550
+ plotWidth: vp.plotWidth,
2551
+ plotHeight: vp.plotHeight,
2552
+ dpr: vp.dpr,
2553
+ visibleFrom: this.lastVisibleRange.start,
2554
+ visibleTo: this.lastVisibleRange.end,
2555
+ // 滚动事件不设置 desiredScrollLeft
2556
+ desiredScrollLeft: undefined,
2557
+ kWidth: this.opt.kWidth,
2558
+ kGap: this.opt.kGap,
2559
+ })
2560
+ }
2561
+
2562
+ // ---------- Indicators (Explicit role) ----------
2563
+
2564
+ /**
2565
+ * 添加指标(高层 API,显式指定 role)
2566
+ * @param definitionId 指标定义 ID(如 'MA', 'MACD')
2567
+ * @param role 'main' 主图指标 或 'sub' 副图指标
2568
+ * @param params 指标参数
2569
+ * @returns 实例 ID(成功)或 null(失败)
2570
+ */
2571
+ addIndicator(
2572
+ definitionId: string,
2573
+ role: 'main' | 'sub',
2574
+ params?: Record<string, unknown>,
2575
+ ): string | null {
2576
+ if (role === 'main') {
2577
+ const success = this.enableMainIndicator(definitionId, params as Record<string, number | boolean | string>)
2578
+ if (!success) return null
2579
+
2580
+ // 更新 indicators signal
2581
+ this.syncIndicatorsSignal()
2582
+ return definitionId.toUpperCase()
2583
+ } else {
2584
+ // 副图指标
2585
+ const paneId = `${definitionId.toUpperCase()}_${Date.now()}`
2586
+ const success = this.createSubPane(
2587
+ paneId,
2588
+ definitionId as SubIndicatorType,
2589
+ params as Record<string, number | boolean | string>,
2590
+ )
2591
+ if (!success) return null
2592
+
2593
+ // 更新 signals
2594
+ this.syncIndicatorsSignal()
2595
+ this.syncSubPanesSignal()
2596
+ return paneId
2597
+ }
2598
+ }
2599
+
2600
+ /**
2601
+ * 移除指标(高层 API)
2602
+ * @param instanceId 指标实例 ID
2603
+ * @returns 是否成功移除
2604
+ */
2605
+ removeIndicator(instanceId: string): boolean {
2606
+ const id = instanceId.toUpperCase()
2607
+
2608
+ // 先尝试作为主图指标移除(直接检查内部状态,不依赖 signal)
2609
+ if (this.activeMainIndicators.has(id)) {
2610
+ const success = this.disableMainIndicator(instanceId)
2611
+ if (success) {
2612
+ this.syncIndicatorsSignal()
2613
+ }
2614
+ return success
2615
+ }
2616
+
2617
+ // 再尝试作为副图指标移除(检查 sub pane 是否存在)
2618
+ const subPaneEntry = this.getSubPaneEntry(instanceId)
2619
+ if (subPaneEntry) {
2620
+ this.removeSubPane(instanceId)
2621
+ this.syncIndicatorsSignal()
2622
+ this.syncSubPanesSignal()
2623
+ return true
2624
+ }
2625
+
2626
+ // 都没找到,返回 false
2627
+ return false
2628
+ }
2629
+
2630
+ /**
2631
+ * 更新指标参数(高层 API)
2632
+ * @param instanceId 指标实例 ID
2633
+ * @param params 新参数
2634
+ * @returns 是否成功更新
2635
+ */
2636
+ updateIndicatorParams(instanceId: string, params: Record<string, unknown>): boolean {
2637
+ const id = instanceId.toUpperCase()
2638
+
2639
+ // 先尝试作为主图指标更新(直接检查内部状态)
2640
+ if (this.activeMainIndicators.has(id)) {
2641
+ this.updateMainIndicatorParams(instanceId, params as Record<string, number | boolean | string>)
2642
+ this.syncIndicatorsSignal()
2643
+ return true
2644
+ }
2645
+
2646
+ // 再尝试作为副图指标更新
2647
+ const subPaneEntry = this.getSubPaneEntry(instanceId)
2648
+ if (subPaneEntry) {
2649
+ this.updateSubPaneParams(instanceId, params)
2650
+ this.syncIndicatorsSignal()
2651
+ return true
2652
+ }
2653
+
2654
+ // 都没找到
2655
+ return false
2656
+ }
2657
+
2658
+ /**
2659
+ * 重新排序指标(高层 API)
2660
+ * @param orderedInstanceIds 排序后的指标实例 ID 数组
2661
+ * @returns 是否成功
2662
+ */
2663
+ reorderIndicators(orderedInstanceIds: string[]): boolean {
2664
+ // TODO: 实现副图指标的重新排序
2665
+ // 需要调用 updatePaneLayout 来调整 pane 顺序
2666
+ console.warn('[Chart] reorderIndicators not fully implemented yet')
2667
+ return false
2668
+ }
2669
+
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
+
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
+
2711
+ // ---------- Sub Panes ----------
2712
+
2713
+ /**
2714
+ * 调整子图大小(高层 API)
2715
+ * @param paneId 面板 ID
2716
+ * @param deltaY 垂直偏移量
2717
+ * @returns 是否成功
2718
+ */
2719
+ resizeSubPane(paneId: string, deltaY: number): boolean {
2720
+ return this.resizePaneBoundary(paneId, deltaY)
2721
+ }
2722
+
2723
+ // ---------- Drawings ----------
2724
+
2725
+ /**
2726
+ * 设置当前绘图工具(高层 API)
2727
+ * @param tool 工具类型或 null 取消选择
2728
+ */
2729
+ setDrawingTool(tool: DrawingToolType | null): void {
2730
+ this._drawingToolSignal.set(tool)
2731
+ // TODO: 当 Chart 支持绘图工具切换时,在这里调用相应方法
2732
+ }
2733
+
2734
+ /**
2735
+ * 移除绘图(高层 API)
2736
+ * @param drawingId 绘图 ID
2737
+ */
2738
+ removeDrawing(drawingId: string): void {
2739
+ // TODO: 实现绘图移除
2740
+ console.warn('[Chart] removeDrawing not fully implemented yet')
2741
+ }
2742
+
2743
+ /**
2744
+ * 清除所有绘图(高层 API)
2745
+ */
2746
+ clearDrawings(): void {
2747
+ this.setDrawings([])
2748
+ }
2749
+
2750
+ // ---------- Settings ----------
2751
+
2752
+ /**
2753
+ * 更新设置(高层 API)
2754
+ * 代理到现有的 updateSettings
2755
+ */
2756
+ updateSettingsFacade(settings: Record<string, unknown>): void {
2757
+ this.updateSettings(settings as ChartSettings)
2758
+ }
2759
+
2760
+ /**
2761
+ * 更新选项(高层 API)
2762
+ * 代理到现有的 updateOptions
2763
+ */
2764
+ updateOptionsFacade(options: Partial<ChartOptions>): void {
2765
+ this.updateOptions(options)
2766
+ }
2767
+
2768
+ // ---------- Lifecycle hooks ----------
2769
+
2770
+ /**
2771
+ * 销毁图表实例
2772
+ */
2773
+ }
2774
+
2775
+ // ==================== Type definitions for Facade ====================
2776
+
2777
+ export type ViewportState = {
2778
+ zoomLevel: number
2779
+ plotWidth: number
2780
+ plotHeight: number
2781
+ dpr: number
2782
+ visibleFrom: number
2783
+ visibleTo: number
2784
+ desiredScrollLeft: number | undefined
2785
+ kWidth: number
2786
+ kGap: number
2787
+ }
2788
+
2789
+ export type IndicatorRole = 'main' | 'sub'
2790
+
2791
+ export interface IndicatorInstance {
2792
+ id: string
2793
+ definitionId: string
2794
+ label: string
2795
+ name: string
2796
+ role: IndicatorRole
2797
+ paneId?: string
2798
+ params: Record<string, unknown>
2799
+ }
2800
+
2801
+ export interface SubPaneInfo {
2802
+ paneId: string
2803
+ indicatorId: string
2804
+ params: Record<string, unknown>
2805
+ ratio: number
2806
+ }
2807
+
2808
+ export type DrawingToolType = 'trendline' | 'horizontal' | 'fib' | 'rectangle' | 'arrow'
2809
+
2810
+ export interface DrawingObject {
2811
+ id: string
2812
+ type: DrawingToolType
2813
+ }
2814
+
2815
+