@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,130 @@
1
+ import { useCallback } from 'react';
2
+ import { COMMON_FIELDS, type PropertyField } from '../../types/drawing.js';
3
+
4
+ export interface IndicatorPropertyPanelProps {
5
+ /** 指标key */
6
+ indicatorKey: string;
7
+ /** 当前样式值 */
8
+ values: Record<string, string | number | boolean>;
9
+ /** 样式变更回调 */
10
+ onStyleChange: (key: string, value: string | number | boolean) => void;
11
+ /** 关闭面板 */
12
+ onClose: () => void;
13
+ }
14
+
15
+ /** 指标属性模板 */
16
+ const INDICATOR_PROPERTY_TEMPLATES: Record<string, PropertyField[]> = {
17
+ 'MA': [
18
+ COMMON_FIELDS.lineColor('#FF6D00'),
19
+ COMMON_FIELDS.lineWidth(1),
20
+ ],
21
+ 'EMA': [
22
+ COMMON_FIELDS.lineColor('#2962FF'),
23
+ COMMON_FIELDS.lineWidth(1),
24
+ ],
25
+ 'BOLL': [
26
+ COMMON_FIELDS.lineColor('#FF6D00'),
27
+ COMMON_FIELDS.lineWidth(1),
28
+ COMMON_FIELDS.fillColor('#2962FF'),
29
+ COMMON_FIELDS.fillOpacity(0.1),
30
+ ],
31
+ 'MACD': [
32
+ { key: 'difColor', label: 'DIF颜色', type: 'color', default: '#2962FF' },
33
+ { key: 'deaColor', label: 'DEA颜色', type: 'color', default: '#FF6D00' },
34
+ { key: 'histUpColor', label: '柱状涨色', type: 'color', default: '#26a69a' },
35
+ { key: 'histDownColor', label: '柱状跌色', type: 'color', default: '#ef5350' },
36
+ ],
37
+ 'RSI': [
38
+ COMMON_FIELDS.lineColor('#AB47BC'),
39
+ COMMON_FIELDS.lineWidth(1),
40
+ ],
41
+ 'KDJ': [
42
+ { key: 'kColor', label: 'K线颜色', type: 'color', default: '#FFFFFF' },
43
+ { key: 'dColor', label: 'D线颜色', type: 'color', default: '#FFFF00' },
44
+ { key: 'jColor', label: 'J线颜色', type: 'color', default: '#FF00FF' },
45
+ ],
46
+ 'VOL': [
47
+ { key: 'upColor', label: '涨色', type: 'color', default: 'rgba(38, 166, 154, 0.35)' },
48
+ { key: 'downColor', label: '跌色', type: 'color', default: 'rgba(239, 83, 80, 0.35)' },
49
+ ],
50
+ };
51
+
52
+ /**
53
+ * IndicatorPropertyPanel - 指标属性编辑面板
54
+ * 根据指标类型动态渲染属性控件
55
+ */
56
+ export function IndicatorPropertyPanel({
57
+ indicatorKey,
58
+ values,
59
+ onStyleChange,
60
+ onClose,
61
+ }: IndicatorPropertyPanelProps) {
62
+ const fields = INDICATOR_PROPERTY_TEMPLATES[indicatorKey] ?? [];
63
+
64
+ const renderField = useCallback((field: PropertyField) => {
65
+ const value = values[field.key] ?? field.default;
66
+
67
+ switch (field.type) {
68
+ case 'color':
69
+ return (
70
+ <div key={field.key} className="qlchart-indicator-pp-row">
71
+ <label>{field.label}</label>
72
+ <input
73
+ type="color"
74
+ value={value as string}
75
+ onChange={(e) => onStyleChange(field.key, e.target.value)}
76
+ />
77
+ </div>
78
+ );
79
+ case 'range':
80
+ return (
81
+ <div key={field.key} className="qlchart-indicator-pp-row">
82
+ <label>{field.label}</label>
83
+ <input
84
+ type="range"
85
+ min={field.min}
86
+ max={field.max}
87
+ step={field.step}
88
+ value={value as number}
89
+ onChange={(e) => onStyleChange(field.key, Number(e.target.value))}
90
+ />
91
+ <span>{value as number}</span>
92
+ </div>
93
+ );
94
+ case 'toggle':
95
+ return (
96
+ <div key={field.key} className="qlchart-indicator-pp-row">
97
+ <label>{field.label}</label>
98
+ <input
99
+ type="checkbox"
100
+ checked={value as boolean}
101
+ onChange={(e) => onStyleChange(field.key, e.target.checked)}
102
+ />
103
+ </div>
104
+ );
105
+ case 'select':
106
+ return (
107
+ <div key={field.key} className="qlchart-indicator-pp-row">
108
+ <label>{field.label}</label>
109
+ <select value={value as string} onChange={(e) => onStyleChange(field.key, e.target.value)}>
110
+ {field.options?.map(opt => (
111
+ <option key={opt.value} value={opt.value}>{opt.label}</option>
112
+ ))}
113
+ </select>
114
+ </div>
115
+ );
116
+ }
117
+ }, [values, onStyleChange]);
118
+
119
+ if (fields.length === 0) return null;
120
+
121
+ return (
122
+ <div className="qlchart-indicator-property-panel">
123
+ <div className="qlchart-indicator-pp-header">
124
+ <span>{indicatorKey} 属性</span>
125
+ <button onClick={onClose}>✕</button>
126
+ </div>
127
+ {fields.map(renderField)}
128
+ </div>
129
+ );
130
+ }
@@ -0,0 +1,173 @@
1
+ import { useState, useCallback } from 'react';
2
+ import { createPortal } from 'react-dom';
3
+ import { useIndicatorStore } from '../../store/useIndicatorStore.js';
4
+ import type { IndicatorConfig } from '../../types/index.js';
5
+ import '../../styles/indicator-tag.css';
6
+
7
+ export interface IndicatorTagProps {
8
+ config: IndicatorConfig;
9
+ }
10
+
11
+ interface ParamDef {
12
+ name: string;
13
+ label: string;
14
+ defaultValue: number;
15
+ min: number;
16
+ max: number;
17
+ step: number;
18
+ }
19
+
20
+ function getParamDefs(name: string): ParamDef[] {
21
+ const defs: Record<string, ParamDef[]> = {
22
+ MA: [{ name: 'period', label: '周期', defaultValue: 20, min: 2, max: 500, step: 1 }],
23
+ EMA: [{ name: 'period', label: '周期', defaultValue: 20, min: 2, max: 500, step: 1 }],
24
+ BOLL: [
25
+ { name: 'period', label: '周期', defaultValue: 20, min: 2, max: 200, step: 1 },
26
+ { name: 'stdDev', label: '标准差', defaultValue: 2, min: 0.5, max: 5, step: 0.1 },
27
+ ],
28
+ MACD: [
29
+ { name: 'fast', label: '快线', defaultValue: 12, min: 2, max: 100, step: 1 },
30
+ { name: 'slow', label: '慢线', defaultValue: 26, min: 2, max: 200, step: 1 },
31
+ { name: 'signal', label: '信号线', defaultValue: 9, min: 2, max: 100, step: 1 },
32
+ ],
33
+ RSI: [{ name: 'period', label: '周期', defaultValue: 14, min: 2, max: 100, step: 1 }],
34
+ KDJ: [
35
+ { name: 'kPeriod', label: 'K周期', defaultValue: 9, min: 2, max: 100, step: 1 },
36
+ { name: 'dPeriod', label: 'D周期', defaultValue: 3, min: 1, max: 50, step: 1 },
37
+ { name: 'jSmooth', label: 'J平滑', defaultValue: 3, min: 1, max: 50, step: 1 },
38
+ ],
39
+ CCI: [{ name: 'period', label: '周期', defaultValue: 20, min: 2, max: 200, step: 1 }],
40
+ };
41
+ return defs[name] ?? [];
42
+ }
43
+
44
+ /** 指标标签 - 显示指标名+当前值+配置按钮 */
45
+ export function IndicatorTag({ config }: IndicatorTagProps) {
46
+ const removeIndicator = useIndicatorStore((s) => s.removeIndicator);
47
+ const toggleIndicator = useIndicatorStore((s) => s.toggleIndicator);
48
+ const updateIndicatorParams = useIndicatorStore((s) => s.updateIndicatorParams);
49
+ const indicatorDataCache = useIndicatorStore((s) => s.indicatorDataCache);
50
+
51
+ // ★ V3 需求3:Modal 状态 + 草稿参数
52
+ const [configModalOpen, setConfigModalOpen] = useState(false);
53
+ const [draftParams, setDraftParams] = useState<Record<string, number>>({});
54
+
55
+ const handleRemove = useCallback(() => {
56
+ removeIndicator(config.id);
57
+ }, [config.id, removeIndicator]);
58
+
59
+ const handleToggleVisible = useCallback(() => {
60
+ toggleIndicator(config.id);
61
+ }, [config.id, toggleIndicator]);
62
+
63
+ // ★ 打开 Modal:初始化草稿
64
+ const handleOpenConfig = useCallback(() => {
65
+ setDraftParams({ ...(config.params ?? {}) });
66
+ setConfigModalOpen(true);
67
+ }, [config.params]);
68
+
69
+ // ★ 保存:应用草稿到 store
70
+ const handleSaveConfig = useCallback(() => {
71
+ updateIndicatorParams(config.id, draftParams);
72
+ setConfigModalOpen(false);
73
+ setDraftParams({});
74
+ }, [config.id, draftParams, updateIndicatorParams]);
75
+
76
+ // ★ 取消:丢弃草稿
77
+ const handleCancelConfig = useCallback(() => {
78
+ setConfigModalOpen(false);
79
+ setDraftParams({});
80
+ }, []);
81
+
82
+ // ★ 草稿参数变化
83
+ const handleParamChange = useCallback((name: string, value: number) => {
84
+ setDraftParams(prev => ({ ...prev, [name]: value }));
85
+ }, []);
86
+
87
+ // 获取最新值
88
+ const data = indicatorDataCache[config.name];
89
+ const latestPoint = data?.data[data.data.length - 1];
90
+ const latestValue = latestPoint
91
+ ? Object.values(latestPoint.values)[0]?.toFixed(2)
92
+ : '-';
93
+
94
+ const paramDefs = getParamDefs(config.name);
95
+
96
+ return (
97
+ <>
98
+ <span
99
+ className={`qlchart-indicator-tag ${config.visible ? 'visible' : 'hidden'}`}
100
+ >
101
+ <span className="qlchart-indicator-tag-label">{config.label}</span>
102
+ <span className="qlchart-indicator-tag-value">{latestValue}</span>
103
+ {paramDefs.length > 0 && (
104
+ <button
105
+ className="qlchart-indicator-tag-config"
106
+ onClick={handleOpenConfig}
107
+ title="配置"
108
+ >
109
+ ⚙️
110
+ </button>
111
+ )}
112
+ <button
113
+ className="qlchart-indicator-tag-toggle"
114
+ onClick={handleToggleVisible}
115
+ title={config.visible ? '隐藏' : '显示'}
116
+ >
117
+ {config.visible ? '👁' : '👁‍🗨'}
118
+ </button>
119
+ <button
120
+ className="qlchart-indicator-tag-close"
121
+ onClick={handleRemove}
122
+ title="移除"
123
+ >
124
+
125
+ </button>
126
+ </span>
127
+
128
+ {/* ★ V3 需求3:标准 Portal Modal + 草稿模式 */}
129
+ {configModalOpen && createPortal(
130
+ <div className="qlchart-modal-overlay" onClick={handleCancelConfig}>
131
+ <div
132
+ className="qlchart-indicator-config-modal"
133
+ onClick={(e) => e.stopPropagation()}
134
+ role="dialog"
135
+ aria-modal="true"
136
+ aria-label={`${config.label} 参数配置`}
137
+ >
138
+ <div className="qlchart-indicator-config-header">
139
+ <span>{config.label} 参数配置</span>
140
+ <button className="qlchart-indicator-config-close" onClick={handleCancelConfig}>✕</button>
141
+ </div>
142
+
143
+ <div className="qlchart-indicator-config-body">
144
+ {paramDefs.map((pd) => (
145
+ <div key={pd.name} className="qlchart-indicator-config-row">
146
+ <label>{pd.label}</label>
147
+ <input
148
+ type="number"
149
+ value={draftParams[pd.name] ?? pd.defaultValue}
150
+ onChange={(e) => handleParamChange(pd.name, Number(e.target.value))}
151
+ min={pd.min}
152
+ max={pd.max}
153
+ step={pd.step}
154
+ />
155
+ </div>
156
+ ))}
157
+ </div>
158
+
159
+ <div className="qlchart-indicator-config-footer">
160
+ <button className="qlchart-indicator-config-cancel" onClick={handleCancelConfig}>
161
+ 取消
162
+ </button>
163
+ <button className="qlchart-indicator-config-save" onClick={handleSaveConfig}>
164
+ 保存
165
+ </button>
166
+ </div>
167
+ </div>
168
+ </div>,
169
+ document.body,
170
+ )}
171
+ </>
172
+ );
173
+ }
@@ -0,0 +1,4 @@
1
+ export { IndicatorPanel } from './IndicatorPanel.js';
2
+ export type { IndicatorPanelProps } from './IndicatorPanel.js';
3
+ export { IndicatorTag } from './IndicatorTag.js';
4
+ export type { IndicatorTagProps } from './IndicatorTag.js';
@@ -0,0 +1,97 @@
1
+ .qlchart-replay-controller {
2
+ display: flex;
3
+ align-items: center;
4
+ gap: 8px;
5
+ height: 48px;
6
+ padding: 0 12px;
7
+ background: rgba(30, 30, 40, 0.9);
8
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
9
+ backdrop-filter: blur(8px);
10
+ flex-shrink: 0;
11
+ }
12
+
13
+ .qlchart-replay-btn {
14
+ display: flex;
15
+ align-items: center;
16
+ justify-content: center;
17
+ width: 32px;
18
+ height: 32px;
19
+ border: none;
20
+ border-radius: 4px;
21
+ background: rgba(255, 255, 255, 0.1);
22
+ color: #e0e0e0;
23
+ cursor: pointer;
24
+ font-size: 14px;
25
+ transition: background 0.15s;
26
+ }
27
+
28
+ .qlchart-replay-btn:hover {
29
+ background: rgba(255, 255, 255, 0.2);
30
+ }
31
+
32
+ .qlchart-replay-btn-primary.playing {
33
+ background: rgba(38, 166, 154, 0.3);
34
+ }
35
+
36
+ .qlchart-replay-progress {
37
+ display: flex;
38
+ align-items: center;
39
+ gap: 8px;
40
+ flex: 1;
41
+ min-width: 120px;
42
+ }
43
+
44
+ .qlchart-replay-progress-text {
45
+ font-size: 12px;
46
+ color: #aaa;
47
+ white-space: nowrap;
48
+ min-width: 60px;
49
+ text-align: right;
50
+ }
51
+
52
+ .qlchart-replay-speed {
53
+ display: flex;
54
+ align-items: center;
55
+ gap: 6px;
56
+ }
57
+
58
+ .qlchart-replay-speed-label {
59
+ font-size: 12px;
60
+ color: #aaa;
61
+ }
62
+
63
+ .qlchart-replay-speed-value {
64
+ font-size: 12px;
65
+ color: #e0e0e0;
66
+ min-width: 32px;
67
+ text-align: center;
68
+ }
69
+
70
+ .qlchart-replay-slider {
71
+ -webkit-appearance: none;
72
+ appearance: none;
73
+ height: 4px;
74
+ border-radius: 2px;
75
+ background: rgba(255, 255, 255, 0.2);
76
+ outline: none;
77
+ cursor: pointer;
78
+ }
79
+
80
+ .qlchart-replay-slider::-webkit-slider-thumb {
81
+ -webkit-appearance: none;
82
+ appearance: none;
83
+ width: 12px;
84
+ height: 12px;
85
+ border-radius: 50%;
86
+ background: #26a69a;
87
+ cursor: pointer;
88
+ }
89
+
90
+ .qlchart-replay-slider::-moz-range-thumb {
91
+ width: 12px;
92
+ height: 12px;
93
+ border-radius: 50%;
94
+ background: #26a69a;
95
+ cursor: pointer;
96
+ border: none;
97
+ }
@@ -0,0 +1,138 @@
1
+ import { useCallback, useEffect, useRef } from 'react';
2
+ import { useReplayStore } from '../../store/useReplayStore.js';
3
+ import { KlineReplay } from '../../plugins/replay/KlineReplay.js';
4
+ import type { ISeriesApi, SeriesType as LWCSeriesType } from 'lightweight-charts';
5
+
6
+ import './ReplayController.css';
7
+
8
+ export interface ReplayControllerProps {
9
+ replay: KlineReplay | null;
10
+ }
11
+
12
+ export function ReplayController({ replay }: ReplayControllerProps) {
13
+ const isReplayMode = useReplayStore((s) => s.isReplayMode);
14
+ const isPlaying = useReplayStore((s) => s.isPlaying);
15
+ const speed = useReplayStore((s) => s.speed);
16
+ const progress = useReplayStore((s) => s.progress);
17
+ const setPlaying = useReplayStore((s) => s.setPlaying);
18
+ const setSpeed = useReplayStore((s) => s.setSpeed);
19
+ const setProgress = useReplayStore((s) => s.setProgress);
20
+ const exitReplay = useReplayStore((s) => s.exitReplay);
21
+
22
+ // 同步回放进度
23
+ useEffect(() => {
24
+ if (!replay) return;
25
+ replay.setOnProgress((current, total) => {
26
+ setProgress(current, total);
27
+ });
28
+ return () => {
29
+ replay.setOnProgress(() => {});
30
+ };
31
+ }, [replay, setProgress]);
32
+
33
+ const handlePlayPause = useCallback(() => {
34
+ if (!replay) return;
35
+ if (isPlaying) {
36
+ replay.pause();
37
+ setPlaying(false);
38
+ } else {
39
+ if (progress.current === 0) {
40
+ replay.play();
41
+ } else {
42
+ replay.resume();
43
+ }
44
+ setPlaying(true);
45
+ }
46
+ }, [replay, isPlaying, progress.current, setPlaying]);
47
+
48
+ const handleStop = useCallback(() => {
49
+ if (!replay) return;
50
+ replay.stop();
51
+ setPlaying(false);
52
+ exitReplay();
53
+ }, [replay, setPlaying, exitReplay]);
54
+
55
+ const handlePrev = useCallback(() => {
56
+ if (!replay) return;
57
+ replay.prev();
58
+ }, [replay]);
59
+
60
+ const handleNext = useCallback(() => {
61
+ if (!replay) return;
62
+ replay.next();
63
+ }, [replay]);
64
+
65
+ const handleSpeedChange = useCallback(
66
+ (e: React.ChangeEvent<HTMLInputElement>) => {
67
+ // 滑块 0.5x~5x → 2000ms~200ms
68
+ const multiplier = parseFloat(e.target.value);
69
+ const ms = Math.round(1000 / multiplier);
70
+ setSpeed(ms);
71
+ replay?.setSpeed(ms);
72
+ },
73
+ [replay, setSpeed],
74
+ );
75
+
76
+ const handleProgressChange = useCallback(
77
+ (e: React.ChangeEvent<HTMLInputElement>) => {
78
+ if (!replay) return;
79
+ const index = parseInt(e.target.value, 10);
80
+ replay.goto(index);
81
+ },
82
+ [replay],
83
+ );
84
+
85
+ if (!isReplayMode) return null;
86
+
87
+ const speedMultiplier = Math.round((1000 / speed) * 10) / 10;
88
+ const progressPercent = progress.total > 0 ? (progress.current / progress.total) * 100 : 0;
89
+
90
+ return (
91
+ <div className="qlchart-replay-controller">
92
+ <button className="qlchart-replay-btn" onClick={handleStop} title="停止">
93
+
94
+ </button>
95
+ <button className="qlchart-replay-btn" onClick={handlePrev} title="上一根">
96
+
97
+ </button>
98
+ <button
99
+ className={`qlchart-replay-btn qlchart-replay-btn-primary ${isPlaying ? 'playing' : ''}`}
100
+ onClick={handlePlayPause}
101
+ title={isPlaying ? '暂停' : '播放'}
102
+ >
103
+ {isPlaying ? '⏸' : '▶'}
104
+ </button>
105
+ <button className="qlchart-replay-btn" onClick={handleNext} title="下一根">
106
+
107
+ </button>
108
+
109
+ <div className="qlchart-replay-progress">
110
+ <input
111
+ type="range"
112
+ min={0}
113
+ max={Math.max(progress.total - 1, 0)}
114
+ value={Math.max(progress.current - 1, 0)}
115
+ onChange={handleProgressChange}
116
+ className="qlchart-replay-slider"
117
+ />
118
+ <span className="qlchart-replay-progress-text">
119
+ {progress.current}/{progress.total}
120
+ </span>
121
+ </div>
122
+
123
+ <div className="qlchart-replay-speed">
124
+ <span className="qlchart-replay-speed-label">速度</span>
125
+ <input
126
+ type="range"
127
+ min={0.5}
128
+ max={5}
129
+ step={0.5}
130
+ value={speedMultiplier}
131
+ onChange={handleSpeedChange}
132
+ className="qlchart-replay-slider"
133
+ />
134
+ <span className="qlchart-replay-speed-value">{speedMultiplier}x</span>
135
+ </div>
136
+ </div>
137
+ );
138
+ }
@@ -0,0 +1,30 @@
1
+ import { useChartStore } from '../../store/useChartStore.js';
2
+ import { usePanelRegistry } from '../../store/usePanelRegistry.js';
3
+ import { TimeRangeSelector } from './TimeRangeSelector.js';
4
+
5
+ export interface TimeBarModuleProps {
6
+ /** 样式变体 */
7
+ variant?: 'toolbar' | 'standalone';
8
+ /** 自定义 className */
9
+ className?: string;
10
+ }
11
+
12
+ /**
13
+ * 时间栏模块容器组件
14
+ *
15
+ * 通过 PanelRegistry 获取活跃面板的 chartManager,
16
+ * 不再依赖 props 传递。
17
+ */
18
+ export function TimeBarModule({
19
+ variant = 'toolbar',
20
+ className,
21
+ }: TimeBarModuleProps) {
22
+ const activePanelId = useChartStore((s) => s.activePanelId);
23
+ const getHandle = usePanelRegistry((s) => s.getHandle);
24
+ const handle = activePanelId ? getHandle(activePanelId) : undefined;
25
+ const chartManager = handle?.chartManager ?? null;
26
+
27
+ return (
28
+ <TimeRangeSelector chartManager={chartManager} />
29
+ );
30
+ }
@@ -0,0 +1,96 @@
1
+ import { useTimeBarStore } from '../../store/useTimeBarStore.js';
2
+ import type { UTCTimestamp } from 'lightweight-charts';
3
+
4
+ interface TimeRangeSelectorProps {
5
+ chartManager?: { getChart: () => any } | null;
6
+ }
7
+
8
+ const TIME_RANGES = [
9
+ { label: '5D', days: 5 },
10
+ { label: '1M', days: 30 },
11
+ { label: '3M', days: 90 },
12
+ { label: '6M', days: 180 },
13
+ { label: 'YTD', days: -1 },
14
+ { label: '1Y', days: 365 },
15
+ { label: '2Y', days: 730 },
16
+ { label: '5Y', days: 1825 },
17
+ { label: 'Max', days: 0 },
18
+ ];
19
+
20
+ export function TimeRangeSelector({ chartManager }: TimeRangeSelectorProps) {
21
+ // 从Store获取状态
22
+ const showDatePicker = useTimeBarStore((s) => s.showDatePicker);
23
+ const customStartDate = useTimeBarStore((s) => s.customStartDate);
24
+ const customEndDate = useTimeBarStore((s) => s.customEndDate);
25
+ const activeRangeLabel = useTimeBarStore((s) => s.activeRangeLabel);
26
+ const setShowDatePicker = useTimeBarStore((s) => s.setShowDatePicker);
27
+ const setActiveRangeLabel = useTimeBarStore((s) => s.setActiveRangeLabel);
28
+ const setCustomDateRange = useTimeBarStore((s) => s.setCustomDateRange);
29
+
30
+ const handleClick = (days: number, label: string) => {
31
+ const chart = chartManager?.getChart();
32
+ if (!chart) return;
33
+
34
+ setActiveRangeLabel(label);
35
+
36
+ if (days === 0) {
37
+ chart.timeScale().fitContent();
38
+ return;
39
+ }
40
+
41
+ const endTime = Date.now();
42
+ let startTime: number;
43
+
44
+ if (days === -1) {
45
+ // YTD
46
+ startTime = new Date(new Date().getFullYear(), 0, 1).getTime();
47
+ } else {
48
+ startTime = endTime - days * 24 * 60 * 60 * 1000;
49
+ }
50
+
51
+ chart.timeScale().setVisibleRange({
52
+ from: (startTime / 1000) as UTCTimestamp,
53
+ to: (endTime / 1000) as UTCTimestamp,
54
+ });
55
+ };
56
+
57
+ const applyDateRange = () => {
58
+ const chart = chartManager?.getChart();
59
+ if (!chart || !customStartDate || !customEndDate) return;
60
+
61
+ chart.timeScale().setVisibleRange({
62
+ from: (new Date(customStartDate).getTime() / 1000) as UTCTimestamp,
63
+ to: (new Date(customEndDate).getTime() / 1000) as UTCTimestamp,
64
+ });
65
+ setShowDatePicker(false);
66
+ };
67
+
68
+ return (
69
+ <div className="qlchart-toolbar-group">
70
+ {TIME_RANGES.map((range) => (
71
+ <button
72
+ key={range.label}
73
+ className={`qlchart-toolbar-btn ${activeRangeLabel === range.label ? 'active' : ''}`}
74
+ onClick={() => handleClick(range.days, range.label)}
75
+ >
76
+ {range.label}
77
+ </button>
78
+ ))}
79
+ <button
80
+ className="qlchart-toolbar-btn"
81
+ onClick={() => setShowDatePicker(!showDatePicker)}
82
+ title="日期范围"
83
+ >
84
+ 📅
85
+ </button>
86
+ {showDatePicker && (
87
+ <div className="qlchart-daterange-picker">
88
+ <input type="date" value={customStartDate} onChange={(e) => setCustomDateRange(e.target.value, customEndDate)} />
89
+ <span>~</span>
90
+ <input type="date" value={customEndDate} onChange={(e) => setCustomDateRange(customStartDate, e.target.value)} />
91
+ <button onClick={applyDateRange}>应用</button>
92
+ </div>
93
+ )}
94
+ </div>
95
+ );
96
+ }
@@ -0,0 +1,3 @@
1
+ export { TimeBarModule } from './TimeBarModule.js';
2
+ export type { TimeBarModuleProps } from './TimeBarModule.js';
3
+ export { TimeRangeSelector } from './TimeRangeSelector.js';