@quantlife/qlchart 0.0.1

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 (321) hide show
  1. package/.idea/QLChart.iml +12 -0
  2. package/.idea/modules.xml +8 -0
  3. package/.idea/vcs.xml +6 -0
  4. package/README.md +75 -0
  5. package/demo/App.css +213 -0
  6. package/demo/App.tsx +46 -0
  7. package/demo/components/ControlPanel.tsx +13 -0
  8. package/demo/components/DemoNav.tsx +27 -0
  9. package/demo/index.html +16 -0
  10. package/demo/main.tsx +10 -0
  11. package/demo/pages/BasicChartDemo.tsx +61 -0
  12. package/demo/pages/DrawingDemo.tsx +22 -0
  13. package/demo/pages/IndicatorDemo.tsx +22 -0
  14. package/demo/pages/LayoutDemo.tsx +35 -0
  15. package/demo/pages/MultiPeriodDemo.tsx +31 -0
  16. package/demo/pages/ReplayDemo.tsx +195 -0
  17. package/demo/pages/SaveDemo.tsx +27 -0
  18. package/demo/pages/ThemeDemo.tsx +29 -0
  19. package/demo/standalone-demo.html +597 -0
  20. package/demo/vite.config.demo.ts +17 -0
  21. package/dist/index.d.ts +1973 -0
  22. package/dist/qlchart.js +23169 -0
  23. package/dist/style.css +1 -0
  24. package/doc/api/indicator-data-processor.md +35 -0
  25. package/doc/api-reference/.nojekyll +1 -0
  26. package/doc/api-reference/assets/hierarchy.js +1 -0
  27. package/doc/api-reference/assets/highlight.css +43 -0
  28. package/doc/api-reference/assets/icons.js +18 -0
  29. package/doc/api-reference/assets/icons.svg +1 -0
  30. package/doc/api-reference/assets/main.js +60 -0
  31. package/doc/api-reference/assets/navigation.js +1 -0
  32. package/doc/api-reference/assets/search.js +1 -0
  33. package/doc/api-reference/assets/style.css +1611 -0
  34. package/doc/api-reference/classes/ChartManager.html +16 -0
  35. package/doc/api-reference/classes/DataManager.html +13 -0
  36. package/doc/api-reference/classes/DrawingAdapter.html +64 -0
  37. package/doc/api-reference/classes/DrawingPersistence.html +21 -0
  38. package/doc/api-reference/classes/EventManager.html +12 -0
  39. package/doc/api-reference/classes/HollowCandlestickSeries.html +22 -0
  40. package/doc/api-reference/classes/IndicatorRenderer.html +20 -0
  41. package/doc/api-reference/classes/KlineReplay.html +31 -0
  42. package/doc/api-reference/classes/MockDataService.html +13 -0
  43. package/doc/api-reference/classes/MockIndicatorService.html +4 -0
  44. package/doc/api-reference/classes/OverlayIndicator.html +11 -0
  45. package/doc/api-reference/classes/PaneIndicator.html +16 -0
  46. package/doc/api-reference/classes/PaneManager.html +24 -0
  47. package/doc/api-reference/classes/RealtimeDataFeed.html +22 -0
  48. package/doc/api-reference/classes/RenkoSeries.html +22 -0
  49. package/doc/api-reference/classes/ScreenshotUtil.html +10 -0
  50. package/doc/api-reference/classes/SeriesManager.html +30 -0
  51. package/doc/api-reference/classes/ThemeManager.html +18 -0
  52. package/doc/api-reference/enums/ChartEvent.html +12 -0
  53. package/doc/api-reference/enums/IndicatorType.html +4 -0
  54. package/doc/api-reference/enums/SeriesType.html +13 -0
  55. package/doc/api-reference/functions/ChartFunctionMenu.html +1 -0
  56. package/doc/api-reference/functions/DrawingModule.html +8 -0
  57. package/doc/api-reference/functions/IndicatorPanel.html +2 -0
  58. package/doc/api-reference/functions/IndicatorTag.html +2 -0
  59. package/doc/api-reference/functions/KlineTypeSelector.html +1 -0
  60. package/doc/api-reference/functions/LayoutSwitcher.html +1 -0
  61. package/doc/api-reference/functions/PeriodSelector.html +1 -0
  62. package/doc/api-reference/functions/QLChartLayout.html +1 -0
  63. package/doc/api-reference/functions/QLChartPanel.html +10 -0
  64. package/doc/api-reference/functions/QLChartProvider.html +2 -0
  65. package/doc/api-reference/functions/QLToolbar.html +1 -0
  66. package/doc/api-reference/functions/ReplayController.html +1 -0
  67. package/doc/api-reference/functions/TimeBarModule.html +4 -0
  68. package/doc/api-reference/functions/TimeRangeSelector.html +1 -0
  69. package/doc/api-reference/functions/createIndicatorConfig.html +2 -0
  70. package/doc/api-reference/functions/getToolConfig.html +2 -0
  71. package/doc/api-reference/functions/getToolsByCategory.html +2 -0
  72. package/doc/api-reference/functions/getToolsByPriority.html +2 -0
  73. package/doc/api-reference/functions/mapLibTypeToOurs.html +2 -0
  74. package/doc/api-reference/functions/mapToolTypeToLib.html +3 -0
  75. package/doc/api-reference/functions/transformCandlestickData.html +3 -0
  76. package/doc/api-reference/functions/transformIndicatorData.html +2 -0
  77. package/doc/api-reference/functions/transformVolumeData.html +3 -0
  78. package/doc/api-reference/functions/useChart.html +4 -0
  79. package/doc/api-reference/functions/useChartStore.html +8 -0
  80. package/doc/api-reference/functions/useCrosshairSync.html +8 -0
  81. package/doc/api-reference/functions/useDrawingModule.html +1 -0
  82. package/doc/api-reference/functions/useDrawingStore.html +8 -0
  83. package/doc/api-reference/functions/useIndicatorStore.html +8 -0
  84. package/doc/api-reference/functions/useQLChartConfig.html +2 -0
  85. package/doc/api-reference/functions/useReplayStore.html +8 -0
  86. package/doc/api-reference/functions/useTheme.html +2 -0
  87. package/doc/api-reference/functions/useTimeBarStore.html +8 -0
  88. package/doc/api-reference/index.html +1 -0
  89. package/doc/api-reference/interfaces/CandlestickData.html +7 -0
  90. package/doc/api-reference/interfaces/CandlestickRawData.html +8 -0
  91. package/doc/api-reference/interfaces/ChartFunctionMenuProps.html +2 -0
  92. package/doc/api-reference/interfaces/ChartManagerCreateOptions.html +4 -0
  93. package/doc/api-reference/interfaces/ChartOptions.html +8 -0
  94. package/doc/api-reference/interfaces/ChartRequestParams.html +8 -0
  95. package/doc/api-reference/interfaces/ChartResponse.html +5 -0
  96. package/doc/api-reference/interfaces/ChartState.html +24 -0
  97. package/doc/api-reference/interfaces/ChartThemeOptions.html +5 -0
  98. package/doc/api-reference/interfaces/CrosshairData.html +5 -0
  99. package/doc/api-reference/interfaces/DrawingModuleProps.html +5 -0
  100. package/doc/api-reference/interfaces/DrawingState.html +48 -0
  101. package/doc/api-reference/interfaces/HistogramData.html +5 -0
  102. package/doc/api-reference/interfaces/HollowCandlestickData.html +14 -0
  103. package/doc/api-reference/interfaces/IndicatorConfig.html +11 -0
  104. package/doc/api-reference/interfaces/IndicatorDataPoint.html +4 -0
  105. package/doc/api-reference/interfaces/IndicatorDataResponse.html +5 -0
  106. package/doc/api-reference/interfaces/IndicatorDefinition.html +9 -0
  107. package/doc/api-reference/interfaces/IndicatorPanelProps.html +3 -0
  108. package/doc/api-reference/interfaces/IndicatorParamDef.html +8 -0
  109. package/doc/api-reference/interfaces/IndicatorRawData.html +4 -0
  110. package/doc/api-reference/interfaces/IndicatorState.html +19 -0
  111. package/doc/api-reference/interfaces/IndicatorTagProps.html +2 -0
  112. package/doc/api-reference/interfaces/KlineReplayOptions.html +4 -0
  113. package/doc/api-reference/interfaces/LayoutSwitcherProps.html +3 -0
  114. package/doc/api-reference/interfaces/LineData.html +4 -0
  115. package/doc/api-reference/interfaces/MockDataConfig.html +8 -0
  116. package/doc/api-reference/interfaces/MockIndicatorConfig.html +5 -0
  117. package/doc/api-reference/interfaces/PairInfo.html +6 -0
  118. package/doc/api-reference/interfaces/PaneInfo.html +6 -0
  119. package/doc/api-reference/interfaces/PanelConfig.html +9 -0
  120. package/doc/api-reference/interfaces/PersistenceConfig.html +12 -0
  121. package/doc/api-reference/interfaces/QLChartConfig.html +18 -0
  122. package/doc/api-reference/interfaces/QLChartLayoutProps.html +9 -0
  123. package/doc/api-reference/interfaces/QLChartPanelProps.html +13 -0
  124. package/doc/api-reference/interfaces/QLChartPanelRef.html +14 -0
  125. package/doc/api-reference/interfaces/QLToolbarProps.html +7 -0
  126. package/doc/api-reference/interfaces/RealtimeCandle.html +9 -0
  127. package/doc/api-reference/interfaces/RealtimeSubscribeFn.html +2 -0
  128. package/doc/api-reference/interfaces/RenkoData.html +16 -0
  129. package/doc/api-reference/interfaces/ReplayControllerProps.html +2 -0
  130. package/doc/api-reference/interfaces/ReplayState.html +19 -0
  131. package/doc/api-reference/interfaces/ThemeConfig.html +14 -0
  132. package/doc/api-reference/interfaces/TimeBarModuleProps.html +5 -0
  133. package/doc/api-reference/interfaces/TimeBarState.html +13 -0
  134. package/doc/api-reference/interfaces/UseChartReturn.html +4 -0
  135. package/doc/api-reference/interfaces/UseDrawingModuleOptions.html +11 -0
  136. package/doc/api-reference/interfaces/UseDrawingModuleReturn.html +6 -0
  137. package/doc/api-reference/interfaces/UseThemeReturn.html +5 -0
  138. package/doc/api-reference/types/EventHandler.html +2 -0
  139. package/doc/api-reference/types/FetchFn.html +2 -0
  140. package/doc/api-reference/types/LayoutMode.html +2 -0
  141. package/doc/api-reference/types/MarketTrend.html +2 -0
  142. package/doc/api-reference/types/ThemePreset.html +2 -0
  143. package/doc/api-reference/variables/BUILTIN_INDICATORS.html +2 -0
  144. package/doc/api-reference/variables/CATEGORY_LABELS.html +2 -0
  145. package/doc/api-reference/variables/DRAWING_TOOLS.html +3 -0
  146. package/doc/api-reference/variables/MARKET_PRESETS.html +1 -0
  147. package/doc/api-reference/variables/PAIR_PRESETS.html +1 -0
  148. package/doc/api-reference/variables/darkPreset.html +1 -0
  149. package/doc/api-reference/variables/lightPreset.html +1 -0
  150. package/doc/components/drawing-module.md +24 -0
  151. package/doc/components/indicator-list-panel.md +24 -0
  152. package/doc/components/indicator-panel.md +17 -0
  153. package/doc/components/pane-divider.md +25 -0
  154. package/doc/components/qlchart-layout.md +30 -0
  155. package/doc/components/qlchart-panel.md +93 -0
  156. package/doc/components/qlchart-provider.md +73 -0
  157. package/doc/components/qltoolbar.md +17 -0
  158. package/doc/components/replay-controller.md +23 -0
  159. package/doc/components/timebar-module.md +13 -0
  160. package/doc/core/chart-manager.md +14 -0
  161. package/doc/core/data-manager.md +33 -0
  162. package/doc/core/event-manager.md +26 -0
  163. package/doc/core/pane-manager.md +13 -0
  164. package/doc/core/series-manager.md +19 -0
  165. package/doc/core/theme-manager.md +21 -0
  166. package/doc/examples/basic-chart.md +24 -0
  167. package/doc/examples/data-format-guide.md +119 -0
  168. package/doc/examples/drawing-tools.md +30 -0
  169. package/doc/examples/indicator-properties.md +34 -0
  170. package/doc/examples/multi-pane.md +24 -0
  171. package/doc/examples/multi-panel.md +23 -0
  172. package/doc/examples/realtime-data.md +147 -0
  173. package/doc/examples/standalone-js.md +333 -0
  174. package/doc/guide/architecture.md +87 -0
  175. package/doc/guide/data-flow.md +310 -0
  176. package/doc/guide/deployment.md +59 -0
  177. package/doc/guide/drawing-properties.md +40 -0
  178. package/doc/guide/getting-started.md +94 -0
  179. package/doc/guide/pane-system.md +47 -0
  180. package/doc/guide/theme-switching.md +58 -0
  181. package/doc/hooks/use-chart.md +20 -0
  182. package/doc/hooks/use-crosshair-sync.md +14 -0
  183. package/doc/hooks/use-drawing-module.md +43 -0
  184. package/doc/hooks/use-theme.md +15 -0
  185. package/doc/index.md +33 -0
  186. package/doc/plugins/drawing/overview.md +36 -0
  187. package/doc/plugins/drawing/persistence.md +42 -0
  188. package/doc/plugins/drawing/tool-registry.md +29 -0
  189. package/doc/plugins/hollow-candlestick.md +18 -0
  190. package/doc/plugins/indicators.md +28 -0
  191. package/doc/plugins/renko.md +17 -0
  192. package/doc/plugins/replay.md +21 -0
  193. package/doc/plugins/screenshot.md +20 -0
  194. package/docs/api.md +94 -0
  195. package/package.json +54 -0
  196. package/python/qlchart/__init__.py +9 -0
  197. package/python/qlchart/__pycache__/__init__.cpython-311.pyc +0 -0
  198. package/python/qlchart/__pycache__/chart.cpython-311.pyc +0 -0
  199. package/python/qlchart/chart.py +333 -0
  200. package/python/qlchart/templates/chart_template.html +304 -0
  201. package/python/requirements.txt +1 -0
  202. package/python/setup.py +18 -0
  203. package/python/tests/__init__.py +1 -0
  204. package/python/tests/__pycache__/__init__.cpython-311.pyc +0 -0
  205. package/python/tests/__pycache__/test_chart.cpython-311-pytest-8.3.3.pyc +0 -0
  206. package/python/tests/test_chart.py +114 -0
  207. package/quantlife-qlchart-0.0.1.tgz +0 -0
  208. package/src/api/chartApi.ts +30 -0
  209. package/src/api/indicatorApi.ts +27 -0
  210. package/src/components/ChartFunctionMenu.tsx +64 -0
  211. package/src/components/PaneChartPanel.tsx +116 -0
  212. package/src/components/PaneDivider.tsx +66 -0
  213. package/src/components/QLChartLayout.tsx +151 -0
  214. package/src/components/QLChartPanel.tsx +560 -0
  215. package/src/components/QLChartProvider.tsx +90 -0
  216. package/src/components/context-menu/ChartContextMenu.tsx +139 -0
  217. package/src/components/context-menu/index.ts +2 -0
  218. package/src/components/drawing/DrawingModule.tsx +36 -0
  219. package/src/components/drawing/DrawingPropertyPanel.tsx +347 -0
  220. package/src/components/drawing/DrawingToolbar.tsx +305 -0
  221. package/src/components/drawing/index.ts +5 -0
  222. package/src/components/index.ts +43 -0
  223. package/src/components/indicator/IndicatorListPanel.tsx +94 -0
  224. package/src/components/indicator/IndicatorModal.tsx +171 -0
  225. package/src/components/indicator/IndicatorPanel.tsx +9 -0
  226. package/src/components/indicator/IndicatorPropertyPanel.tsx +130 -0
  227. package/src/components/indicator/IndicatorTag.tsx +173 -0
  228. package/src/components/indicator/index.ts +4 -0
  229. package/src/components/replay/ReplayController.css +97 -0
  230. package/src/components/replay/ReplayController.tsx +138 -0
  231. package/src/components/timebar/TimeBarModule.tsx +30 -0
  232. package/src/components/timebar/TimeRangeSelector.tsx +96 -0
  233. package/src/components/timebar/index.ts +3 -0
  234. package/src/components/toolbar/GlobalToolbar.tsx +58 -0
  235. package/src/components/toolbar/KlineTypeSelector.tsx +123 -0
  236. package/src/components/toolbar/LayoutSwitcher.tsx +45 -0
  237. package/src/components/toolbar/PeriodSelector.tsx +35 -0
  238. package/src/components/toolbar/QLToolbar.tsx +71 -0
  239. package/src/components/toolbar/TimeRangeSelector.tsx +89 -0
  240. package/src/components/ui/Modal.tsx +67 -0
  241. package/src/core/ChartManager.ts +95 -0
  242. package/src/core/DataManager.ts +427 -0
  243. package/src/core/EventManager.ts +63 -0
  244. package/src/core/IndicatorDataProcessor.ts +104 -0
  245. package/src/core/PaneManager.ts +121 -0
  246. package/src/core/RealtimeDataFeed.ts +110 -0
  247. package/src/core/SeriesManager.ts +210 -0
  248. package/src/core/ThemeManager.ts +59 -0
  249. package/src/core/index.ts +10 -0
  250. package/src/css.d.ts +4 -0
  251. package/src/hooks/useChart.ts +62 -0
  252. package/src/hooks/useCrosshairSync.ts +109 -0
  253. package/src/hooks/useDrawingModule.ts +475 -0
  254. package/src/hooks/useTheme.ts +31 -0
  255. package/src/index.ts +170 -0
  256. package/src/mock/MockDataService.ts +102 -0
  257. package/src/mock/MockIndicatorService.ts +40 -0
  258. package/src/mock/index.ts +5 -0
  259. package/src/mock/presets.ts +16 -0
  260. package/src/plugins/drawing/DrawingAdapter.ts +1762 -0
  261. package/src/plugins/drawing/DrawingPersistence.ts +273 -0
  262. package/src/plugins/drawing/DrawingPropertyTemplates.ts +327 -0
  263. package/src/plugins/drawing/DrawingSharedService.ts +125 -0
  264. package/src/plugins/drawing/DrawingToolRegistry.ts +684 -0
  265. package/src/plugins/drawing/TextLabelOverlay.ts +101 -0
  266. package/src/plugins/drawing/colorUtils.ts +53 -0
  267. package/src/plugins/drawing/index.ts +10 -0
  268. package/src/plugins/drawing/lineStyleMap.ts +46 -0
  269. package/src/plugins/drawing/migration.ts +105 -0
  270. package/src/plugins/drawing/patterns/PatternDefinitions.ts +57 -0
  271. package/src/plugins/drawing/patterns/index.ts +2 -0
  272. package/src/plugins/drawing/periodUtils.ts +51 -0
  273. package/src/plugins/indicators/AutoIndicatorRenderer.ts +204 -0
  274. package/src/plugins/indicators/IndicatorRenderer.ts +350 -0
  275. package/src/plugins/indicators/OverlayIndicator.ts +114 -0
  276. package/src/plugins/indicators/PaneIndicator.ts +137 -0
  277. package/src/plugins/indicators/index.ts +4 -0
  278. package/src/plugins/replay/KlineReplay.ts +163 -0
  279. package/src/plugins/replay/index.ts +2 -0
  280. package/src/plugins/screenshot/ScreenshotUtil.ts +123 -0
  281. package/src/plugins/screenshot/index.ts +1 -0
  282. package/src/plugins/series/HollowCandlestickSeries.ts +111 -0
  283. package/src/plugins/series/RenkoSeries.ts +104 -0
  284. package/src/plugins/series/VolumeCandlestickSeries.ts +127 -0
  285. package/src/plugins/series/index.ts +6 -0
  286. package/src/standalone.ts +386 -0
  287. package/src/store/useChartStore.ts +101 -0
  288. package/src/store/useDrawingStore.ts +135 -0
  289. package/src/store/useIndicatorStore.ts +100 -0
  290. package/src/store/usePanelRegistry.ts +50 -0
  291. package/src/store/useReplayStore.ts +42 -0
  292. package/src/store/useTimeBarStore.ts +34 -0
  293. package/src/styles/chart.css +312 -0
  294. package/src/styles/components.css +184 -0
  295. package/src/styles/context-menu.css +60 -0
  296. package/src/styles/drawing.css +524 -0
  297. package/src/styles/indicator-modal.css +216 -0
  298. package/src/styles/indicator-tag.css +210 -0
  299. package/src/styles/pane-chart.css +9 -0
  300. package/src/styles/responsive.css +71 -0
  301. package/src/styles/themes/dark.css +63 -0
  302. package/src/styles/themes/light.css +61 -0
  303. package/src/styles/toolbar.css +129 -0
  304. package/src/types/api.ts +36 -0
  305. package/src/types/chart.ts +44 -0
  306. package/src/types/drawing.ts +265 -0
  307. package/src/types/index.ts +40 -0
  308. package/src/types/indicator.ts +344 -0
  309. package/src/types/series.ts +53 -0
  310. package/src/types/theme.ts +48 -0
  311. package/src/utils/dataTransformer.ts +63 -0
  312. package/src/utils/heikinAshi.ts +41 -0
  313. package/src/utils/index.ts +3 -0
  314. package/src/utils/lineBreak.ts +88 -0
  315. package/src/utils/themePresets.ts +69 -0
  316. package/src/utils/timeFormatter.ts +29 -0
  317. package/src/utils/timeScaleUtils.ts +68 -0
  318. package/tsconfig.json +21 -0
  319. package/typedoc.json +10 -0
  320. package/vite.config.standalone.ts +31 -0
  321. package/vite.config.ts +24 -0
@@ -0,0 +1,163 @@
1
+ import type { ISeriesApi, SeriesType as LWCSeriesType, Time } from 'lightweight-charts';
2
+
3
+ /** K线回放选项 */
4
+ export interface KlineReplayOptions {
5
+ /** 每根K线的间隔时间(ms),默认 1000 */
6
+ speed: number;
7
+ }
8
+
9
+ /** K线回放引擎 */
10
+ export class KlineReplay {
11
+ private allData: any[] = [];
12
+ private currentIndex: number = -1;
13
+ private timerId: ReturnType<typeof setInterval> | null = null;
14
+ private speed: number = 1000;
15
+ private series: ISeriesApi<LWCSeriesType> | null = null;
16
+ private onProgress: ((current: number, total: number) => void) | null = null;
17
+
18
+ /** 绑定序列 */
19
+ attach(series: ISeriesApi<LWCSeriesType>): void {
20
+ this.series = series;
21
+ }
22
+
23
+ /** 设置进度回调 */
24
+ setOnProgress(cb: (current: number, total: number) => void): void {
25
+ this.onProgress = cb;
26
+ }
27
+
28
+ /** 加载全部K线数据(回放前调用) */
29
+ load(data: any[]): void {
30
+ this.allData = data;
31
+ this.currentIndex = -1;
32
+ }
33
+
34
+ /** 开始回放:从第一根开始逐根播放 */
35
+ play(): void {
36
+ if (!this.series || this.allData.length === 0) return;
37
+
38
+ this.currentIndex = 0;
39
+ // 只显示第一根
40
+ (this.series as any).setData([this.allData[0]]);
41
+ this.emitProgress();
42
+
43
+ this.startTimer();
44
+ }
45
+
46
+ /** 暂停回放 */
47
+ pause(): void {
48
+ this.clearTimer();
49
+ }
50
+
51
+ /** 继续回放 */
52
+ resume(): void {
53
+ if (!this.series || this.allData.length === 0) return;
54
+ if (this.currentIndex < 0) {
55
+ this.currentIndex = 0;
56
+ }
57
+ this.startTimer();
58
+ }
59
+
60
+ /** 停止回放并恢复全部数据 */
61
+ stop(): void {
62
+ this.clearTimer();
63
+ if (this.series && this.allData.length > 0) {
64
+ (this.series as any).setData(this.allData);
65
+ }
66
+ this.currentIndex = -1;
67
+ this.emitProgress();
68
+ }
69
+
70
+ /** 手动步进到下一根 */
71
+ next(): void {
72
+ if (!this.series || this.allData.length === 0) return;
73
+ if (this.currentIndex < 0) this.currentIndex = 0;
74
+
75
+ const nextIndex = this.currentIndex + 1;
76
+ if (nextIndex >= this.allData.length) return;
77
+
78
+ this.currentIndex = nextIndex;
79
+ (this.series as any).update(this.allData[this.currentIndex]);
80
+ this.emitProgress();
81
+ }
82
+
83
+ /** 手动步进到上一根 */
84
+ prev(): void {
85
+ if (!this.series || this.allData.length === 0 || this.currentIndex <= 0) return;
86
+
87
+ const prevIndex = this.currentIndex - 1;
88
+ // 需要重新 setData 到 prevIndex 位置
89
+ this.currentIndex = prevIndex;
90
+ (this.series as any).setData(this.allData.slice(0, this.currentIndex + 1));
91
+ this.emitProgress();
92
+ }
93
+
94
+ /** 跳转到指定索引位置 */
95
+ goto(index: number): void {
96
+ if (!this.series || this.allData.length === 0) return;
97
+
98
+ const clampedIndex = Math.max(0, Math.min(index, this.allData.length - 1));
99
+ this.currentIndex = clampedIndex;
100
+ (this.series as any).setData(this.allData.slice(0, this.currentIndex + 1));
101
+ this.emitProgress();
102
+ }
103
+
104
+ /** 设置播放速度 */
105
+ setSpeed(ms: number): void {
106
+ this.speed = Math.max(50, Math.min(ms, 5000));
107
+ // 如果正在播放,重启定时器
108
+ if (this.timerId !== null) {
109
+ this.clearTimer();
110
+ this.startTimer();
111
+ }
112
+ }
113
+
114
+ /** 获取当前进度 */
115
+ getProgress(): { current: number; total: number } {
116
+ return {
117
+ current: Math.max(0, this.currentIndex + 1),
118
+ total: this.allData.length,
119
+ };
120
+ }
121
+
122
+ /** 是否正在播放 */
123
+ get isPlaying(): boolean {
124
+ return this.timerId !== null;
125
+ }
126
+
127
+ /** 销毁,清理定时器 */
128
+ dispose(): void {
129
+ this.clearTimer();
130
+ this.series = null;
131
+ this.allData = [];
132
+ this.currentIndex = -1;
133
+ this.onProgress = null;
134
+ }
135
+
136
+ private startTimer(): void {
137
+ this.clearTimer();
138
+ this.timerId = setInterval(() => {
139
+ const nextIndex = this.currentIndex + 1;
140
+ if (nextIndex >= this.allData.length) {
141
+ this.clearTimer();
142
+ this.emitProgress();
143
+ return;
144
+ }
145
+ this.currentIndex = nextIndex;
146
+ (this.series as any).update(this.allData[this.currentIndex]);
147
+ this.emitProgress();
148
+ }, this.speed);
149
+ }
150
+
151
+ private clearTimer(): void {
152
+ if (this.timerId !== null) {
153
+ clearInterval(this.timerId);
154
+ this.timerId = null;
155
+ }
156
+ }
157
+
158
+ private emitProgress(): void {
159
+ if (this.onProgress) {
160
+ this.onProgress(this.currentIndex + 1, this.allData.length);
161
+ }
162
+ }
163
+ }
@@ -0,0 +1,2 @@
1
+ export { KlineReplay } from './KlineReplay.js';
2
+ export type { KlineReplayOptions } from './KlineReplay.js';
@@ -0,0 +1,123 @@
1
+ import type { IChartApi } from 'lightweight-charts';
2
+
3
+ /**
4
+ * 截图导出工具
5
+ * 从 lightweight-charts v5 的 DOM 容器中获取 canvas 并导出
6
+ */
7
+ export class ScreenshotUtil {
8
+ /**
9
+ * 从 chart 实例获取 canvas 元素
10
+ * lightweight-charts v5 没有 takeScreenshot(),需要从 DOM 获取
11
+ */
12
+ private static getChartCanvas(chart: IChartApi): HTMLCanvasElement | null {
13
+ // v5 内部 API:尝试获取 chart widget 的 canvas
14
+ const chartWidget = (chart as any)?._chartWidget;
15
+ if (chartWidget?.canvasElement) {
16
+ const canvas = chartWidget.canvasElement();
17
+ if (canvas instanceof HTMLCanvasElement) return canvas;
18
+ }
19
+
20
+ // 备选:通过 DOM 查找 chart 容器中的 canvas
21
+ // chart 内部会引用 container element
22
+ const paneWidget = (chart as any)?._paneWidget;
23
+ if (paneWidget?._canvasElement) {
24
+ const canvas = paneWidget._canvasElement;
25
+ if (canvas instanceof HTMLCanvasElement) return canvas;
26
+ }
27
+
28
+ // 最终备选:尝试从内部结构获取
29
+ const internalChart = (chart as any)?._private;
30
+ if (internalChart?.canvasElement) {
31
+ return internalChart.canvasElement;
32
+ }
33
+
34
+ return null;
35
+ }
36
+
37
+ /**
38
+ * 从 chart 容器 DOM 获取所有 canvas 并合并
39
+ */
40
+ private static getCanvasFromContainer(container: HTMLElement): HTMLCanvasElement | null {
41
+ const canvases = container.querySelectorAll('canvas');
42
+ return canvases.length > 0 ? canvases[0] : null;
43
+ }
44
+
45
+ /**
46
+ * 截取图表为 PNG base64 data URL
47
+ */
48
+ static toPng(chart: IChartApi, container?: HTMLElement): string {
49
+ // 首先尝试从 chart 实例获取
50
+ let canvas = ScreenshotUtil.getChartCanvas(chart);
51
+
52
+ // 如果失败,尝试从容器 DOM 获取
53
+ if (!canvas && container) {
54
+ canvas = ScreenshotUtil.getCanvasFromContainer(container);
55
+ }
56
+
57
+ if (!canvas) {
58
+ console.warn('[ScreenshotUtil] Cannot find chart canvas');
59
+ return '';
60
+ }
61
+
62
+ try {
63
+ return canvas.toDataURL('image/png');
64
+ } catch (err) {
65
+ console.error('[ScreenshotUtil] Failed to export canvas:', err);
66
+ return '';
67
+ }
68
+ }
69
+
70
+ /**
71
+ * 截取并下载为 PNG 文件
72
+ */
73
+ static download(chart: IChartApi, container?: HTMLElement, filename?: string): void {
74
+ const dataUrl = ScreenshotUtil.toPng(chart, container);
75
+ if (!dataUrl) {
76
+ console.warn('[ScreenshotUtil] No screenshot data available');
77
+ return;
78
+ }
79
+
80
+ const link = document.createElement('a');
81
+ link.download = filename ?? `chart_${Date.now()}.png`;
82
+ link.href = dataUrl;
83
+ document.body.appendChild(link);
84
+ link.click();
85
+ document.body.removeChild(link);
86
+ }
87
+
88
+ /**
89
+ * 截取并复制到剪贴板
90
+ */
91
+ static async copyToClipboard(chart: IChartApi, container?: HTMLElement): Promise<boolean> {
92
+ let canvas = ScreenshotUtil.getChartCanvas(chart);
93
+
94
+ if (!canvas && container) {
95
+ canvas = ScreenshotUtil.getCanvasFromContainer(container);
96
+ }
97
+
98
+ if (!canvas) {
99
+ console.warn('[ScreenshotUtil] Cannot find chart canvas for clipboard');
100
+ return false;
101
+ }
102
+
103
+ try {
104
+ const blob = await new Promise<Blob | null>((resolve) =>
105
+ canvas!.toBlob(resolve, 'image/png'),
106
+ );
107
+
108
+ if (!blob) {
109
+ console.warn('[ScreenshotUtil] Failed to create blob from canvas');
110
+ return false;
111
+ }
112
+
113
+ await navigator.clipboard.write([
114
+ new ClipboardItem({ 'image/png': blob }),
115
+ ]);
116
+
117
+ return true;
118
+ } catch (err) {
119
+ console.error('[ScreenshotUtil] Failed to copy to clipboard:', err);
120
+ return false;
121
+ }
122
+ }
123
+ }
@@ -0,0 +1 @@
1
+ export { ScreenshotUtil } from './ScreenshotUtil.js';
@@ -0,0 +1,111 @@
1
+ import type {
2
+ ICustomSeriesPaneView,
3
+ ICustomSeriesPaneRenderer,
4
+ PaneRendererCustomData,
5
+ CustomData,
6
+ CustomSeriesWhitespaceData,
7
+ CustomSeriesPricePlotValues,
8
+ PriceToCoordinateConverter,
9
+ CustomSeriesOptions,
10
+ Coordinate,
11
+ CustomSeriesHitTestResult,
12
+ } from 'lightweight-charts';
13
+
14
+ /** 空心蜡烛图数据 */
15
+ export interface HollowCandlestickData extends CustomData {
16
+ open: number;
17
+ high: number;
18
+ low: number;
19
+ close: number;
20
+ }
21
+
22
+ /** 渲染器 */
23
+ class HollowCandlestickRenderer implements ICustomSeriesPaneRenderer {
24
+ private _data: PaneRendererCustomData<unknown, HollowCandlestickData> | null = null;
25
+
26
+ setData(data: PaneRendererCustomData<unknown, HollowCandlestickData> | null): void {
27
+ this._data = data;
28
+ }
29
+
30
+ draw(
31
+ target: any,
32
+ priceConverter: PriceToCoordinateConverter,
33
+ _isHovered: boolean,
34
+ _hitTestData?: unknown,
35
+ ): void {
36
+ if (!this._data || !this._data.bars.length) return;
37
+
38
+ target.useMediaCoordinateSpace((scope: { context: CanvasRenderingContext2D; mediaSize: { width: number; height: number } }) => {
39
+ const ctx = scope.context;
40
+ const bars = this._data!.bars;
41
+ const barSpacing = this._data!.barSpacing;
42
+
43
+ // 计算蜡烛宽度(不超过间距的 80%)
44
+ const candleWidth = Math.max(1, Math.floor(barSpacing * 0.8));
45
+ const halfCandle = candleWidth / 2;
46
+
47
+ for (const bar of bars) {
48
+ const { open, high, low, close } = bar.originalData;
49
+ const x = bar.x;
50
+
51
+ const isUp = close >= open;
52
+
53
+ // 颜色:涨绿跌红
54
+ const color = isUp ? '#26a69a' : '#ef5350';
55
+
56
+ // 坐标转换
57
+ const openY = priceConverter(open);
58
+ const closeY = priceConverter(close);
59
+ const highY = priceConverter(high);
60
+ const lowY = priceConverter(low);
61
+
62
+ if (openY === null || closeY === null || highY === null || lowY === null) continue;
63
+
64
+ // 画影线(上下影线)
65
+ ctx.strokeStyle = color;
66
+ ctx.lineWidth = 1;
67
+ ctx.beginPath();
68
+ ctx.moveTo(x, highY);
69
+ ctx.lineTo(x, lowY);
70
+ ctx.stroke();
71
+
72
+ // 画空心矩形(实体)
73
+ const bodyTop = Math.min(openY, closeY);
74
+ const bodyBottom = Math.max(openY, closeY);
75
+ const bodyHeight = Math.max(1, bodyBottom - bodyTop);
76
+
77
+ ctx.strokeStyle = color;
78
+ ctx.lineWidth = 1;
79
+ ctx.strokeRect(x - halfCandle, bodyTop, candleWidth, bodyHeight);
80
+ }
81
+ });
82
+ }
83
+ }
84
+
85
+ /** 空心蜡烛图 PaneView */
86
+ export class HollowCandlestickSeries implements ICustomSeriesPaneView<unknown, HollowCandlestickData, CustomSeriesOptions> {
87
+ private _renderer = new HollowCandlestickRenderer();
88
+
89
+ defaultOptions(): CustomSeriesOptions {
90
+ return {} as CustomSeriesOptions;
91
+ }
92
+
93
+ renderer(): ICustomSeriesPaneRenderer {
94
+ return this._renderer;
95
+ }
96
+
97
+ update(
98
+ data: PaneRendererCustomData<unknown, HollowCandlestickData>,
99
+ _seriesOptions: CustomSeriesOptions,
100
+ ): void {
101
+ this._renderer.setData(data);
102
+ }
103
+
104
+ priceValueBuilder(plotRow: HollowCandlestickData): CustomSeriesPricePlotValues {
105
+ return [plotRow.high, plotRow.low, plotRow.close];
106
+ }
107
+
108
+ isWhitespace(data: HollowCandlestickData | CustomSeriesWhitespaceData<unknown>): data is CustomSeriesWhitespaceData<unknown> {
109
+ return !('open' in data && 'close' in data);
110
+ }
111
+ }
@@ -0,0 +1,104 @@
1
+ import type {
2
+ ICustomSeriesPaneView,
3
+ ICustomSeriesPaneRenderer,
4
+ PaneRendererCustomData,
5
+ CustomData,
6
+ CustomSeriesWhitespaceData,
7
+ CustomSeriesPricePlotValues,
8
+ PriceToCoordinateConverter,
9
+ CustomSeriesOptions,
10
+ Coordinate,
11
+ CustomSeriesHitTestResult,
12
+ } from 'lightweight-charts';
13
+
14
+ /** 砖形图数据(后端提供) */
15
+ export interface RenkoData extends CustomData {
16
+ /** 砖块开盘价 */
17
+ open: number;
18
+ /** 砖块收盘价 */
19
+ close: number;
20
+ /** 方向 */
21
+ type: 'up' | 'down';
22
+ }
23
+
24
+ /** 砖形图渲染器 */
25
+ class RenkoRenderer implements ICustomSeriesPaneRenderer {
26
+ private _data: PaneRendererCustomData<unknown, RenkoData> | null = null;
27
+
28
+ setData(data: PaneRendererCustomData<unknown, RenkoData> | null): void {
29
+ this._data = data;
30
+ }
31
+
32
+ draw(
33
+ target: any,
34
+ priceConverter: PriceToCoordinateConverter,
35
+ _isHovered: boolean,
36
+ _hitTestData?: unknown,
37
+ ): void {
38
+ if (!this._data || !this._data.bars.length) return;
39
+
40
+ target.useMediaCoordinateSpace((scope: { context: CanvasRenderingContext2D; mediaSize: { width: number; height: number } }) => {
41
+ const ctx = scope.context;
42
+ const bars = this._data!.bars;
43
+ const barSpacing = this._data!.barSpacing;
44
+
45
+ // 砖块宽度为间距的 80%
46
+ const brickWidth = Math.max(2, Math.floor(barSpacing * 0.8));
47
+ const halfBrick = brickWidth / 2;
48
+
49
+ for (const bar of bars) {
50
+ const { open, close, type } = bar.originalData;
51
+ const x = bar.x;
52
+
53
+ const isUp = type === 'up';
54
+ const fillColor = isUp ? '#26a69a' : '#ef5350';
55
+ const borderColor = isUp ? '#2bbd8f' : '#ff6b6b';
56
+
57
+ const openY = priceConverter(open);
58
+ const closeY = priceConverter(close);
59
+
60
+ if (openY === null || closeY === null) continue;
61
+
62
+ const brickTop = Math.min(openY, closeY);
63
+ const brickHeight = Math.max(1, Math.abs(closeY - openY));
64
+
65
+ // 填充矩形
66
+ ctx.fillStyle = fillColor;
67
+ ctx.fillRect(x - halfBrick, brickTop, brickWidth, brickHeight);
68
+
69
+ // 边框
70
+ ctx.strokeStyle = borderColor;
71
+ ctx.lineWidth = 1;
72
+ ctx.strokeRect(x - halfBrick, brickTop, brickWidth, brickHeight);
73
+ }
74
+ });
75
+ }
76
+ }
77
+
78
+ /** 砖形图 PaneView */
79
+ export class RenkoSeries implements ICustomSeriesPaneView<unknown, RenkoData, CustomSeriesOptions> {
80
+ private _renderer = new RenkoRenderer();
81
+
82
+ defaultOptions(): CustomSeriesOptions {
83
+ return {} as CustomSeriesOptions;
84
+ }
85
+
86
+ renderer(): ICustomSeriesPaneRenderer {
87
+ return this._renderer;
88
+ }
89
+
90
+ update(
91
+ data: PaneRendererCustomData<unknown, RenkoData>,
92
+ _seriesOptions: CustomSeriesOptions,
93
+ ): void {
94
+ this._renderer.setData(data);
95
+ }
96
+
97
+ priceValueBuilder(plotRow: RenkoData): CustomSeriesPricePlotValues {
98
+ return [plotRow.open, plotRow.close];
99
+ }
100
+
101
+ isWhitespace(data: RenkoData | CustomSeriesWhitespaceData<unknown>): data is CustomSeriesWhitespaceData<unknown> {
102
+ return !('open' in data && 'close' in data);
103
+ }
104
+ }
@@ -0,0 +1,127 @@
1
+ import type {
2
+ ICustomSeriesPaneView,
3
+ ICustomSeriesPaneRenderer,
4
+ PaneRendererCustomData,
5
+ CustomData,
6
+ CustomSeriesWhitespaceData,
7
+ CustomSeriesPricePlotValues,
8
+ PriceToCoordinateConverter,
9
+ CustomSeriesOptions,
10
+ } from 'lightweight-charts';
11
+
12
+ /** 成交量蜡烛图数据 */
13
+ export interface VolumeCandlestickData extends CustomData {
14
+ open: number;
15
+ high: number;
16
+ low: number;
17
+ close: number;
18
+ volume: number;
19
+ }
20
+
21
+ /** 成交量蜡烛图渲染器 */
22
+ class VolumeCandlestickRenderer implements ICustomSeriesPaneRenderer {
23
+ private _data: PaneRendererCustomData<unknown, VolumeCandlestickData> | null = null;
24
+
25
+ setData(data: PaneRendererCustomData<unknown, VolumeCandlestickData> | null): void {
26
+ this._data = data;
27
+ }
28
+
29
+ draw(
30
+ target: any,
31
+ priceConverter: PriceToCoordinateConverter,
32
+ _isHovered: boolean,
33
+ _hitTestData?: unknown,
34
+ ): void {
35
+ if (!this._data || !this._data.bars.length) return;
36
+
37
+ target.useMediaCoordinateSpace((scope: { context: CanvasRenderingContext2D; mediaSize: { width: number; height: number } }) => {
38
+ const ctx = scope.context;
39
+ const bars = this._data!.bars;
40
+ const barSpacing = this._data!.barSpacing;
41
+
42
+ // 计算最大成交量
43
+ let maxVolume = 0;
44
+ for (const bar of bars) {
45
+ const vol = bar.originalData.volume ?? 0;
46
+ if (vol > maxVolume) maxVolume = vol;
47
+ }
48
+ if (maxVolume === 0) maxVolume = 1;
49
+
50
+ const maxCandleWidth = Math.max(1, Math.floor(barSpacing * 0.8));
51
+ const minCandleWidth = Math.max(1, Math.floor(barSpacing * 0.15));
52
+
53
+ for (const bar of bars) {
54
+ const { open, high, low, close, volume } = bar.originalData;
55
+ const x = bar.x;
56
+ const isUp = close >= open;
57
+
58
+ const color = isUp ? '#26a69a' : '#ef5350';
59
+
60
+ // 坐标转换
61
+ const openY = priceConverter(open);
62
+ const closeY = priceConverter(close);
63
+ const highY = priceConverter(high);
64
+ const lowY = priceConverter(low);
65
+
66
+ if (openY === null || closeY === null || highY === null || lowY === null) continue;
67
+
68
+ // 蜡烛宽度按 volume/maxVolume 比例缩放
69
+ const volRatio = (volume ?? 0) / maxVolume;
70
+ const candleWidth = Math.max(minCandleWidth, Math.floor(maxCandleWidth * volRatio));
71
+ const halfCandle = candleWidth / 2;
72
+
73
+ // 画影线
74
+ ctx.strokeStyle = color;
75
+ ctx.lineWidth = 1;
76
+ ctx.beginPath();
77
+ ctx.moveTo(x, highY);
78
+ ctx.lineTo(x, lowY);
79
+ ctx.stroke();
80
+
81
+ // 画实体(涨实跌空)
82
+ const bodyTop = Math.min(openY, closeY);
83
+ const bodyBottom = Math.max(openY, closeY);
84
+ const bodyHeight = Math.max(1, bodyBottom - bodyTop);
85
+
86
+ if (isUp) {
87
+ // 涨 — 实心
88
+ ctx.fillStyle = color;
89
+ ctx.fillRect(x - halfCandle, bodyTop, candleWidth, bodyHeight);
90
+ } else {
91
+ // 跌 — 空心
92
+ ctx.strokeStyle = color;
93
+ ctx.lineWidth = 1;
94
+ ctx.strokeRect(x - halfCandle, bodyTop, candleWidth, bodyHeight);
95
+ }
96
+ }
97
+ });
98
+ }
99
+ }
100
+
101
+ /** 成交量蜡烛图 PaneView */
102
+ export class VolumeCandlestickSeries implements ICustomSeriesPaneView<unknown, VolumeCandlestickData, CustomSeriesOptions> {
103
+ private _renderer = new VolumeCandlestickRenderer();
104
+
105
+ defaultOptions(): CustomSeriesOptions {
106
+ return {} as CustomSeriesOptions;
107
+ }
108
+
109
+ renderer(): ICustomSeriesPaneRenderer {
110
+ return this._renderer;
111
+ }
112
+
113
+ update(
114
+ data: PaneRendererCustomData<unknown, VolumeCandlestickData>,
115
+ _seriesOptions: CustomSeriesOptions,
116
+ ): void {
117
+ this._renderer.setData(data);
118
+ }
119
+
120
+ priceValueBuilder(plotRow: VolumeCandlestickData): CustomSeriesPricePlotValues {
121
+ return [plotRow.high, plotRow.low, plotRow.close];
122
+ }
123
+
124
+ isWhitespace(data: VolumeCandlestickData | CustomSeriesWhitespaceData<unknown>): data is CustomSeriesWhitespaceData<unknown> {
125
+ return !('open' in data && 'close' in data);
126
+ }
127
+ }
@@ -0,0 +1,6 @@
1
+ export { HollowCandlestickSeries } from './HollowCandlestickSeries.js';
2
+ export type { HollowCandlestickData } from './HollowCandlestickSeries.js';
3
+ export { RenkoSeries } from './RenkoSeries.js';
4
+ export type { RenkoData } from './RenkoSeries.js';
5
+ export { VolumeCandlestickSeries } from './VolumeCandlestickSeries.js';
6
+ export type { VolumeCandlestickData } from './VolumeCandlestickSeries.js';