@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,139 @@
1
+ import { useState, useCallback, useEffect, useRef } from 'react';
2
+ import type { DrawingToolType } from '../../types/index.js';
3
+ import { DRAWING_TOOLS } from '../../plugins/drawing/DrawingToolRegistry.js';
4
+
5
+ export interface ChartContextMenuProps {
6
+ containerRef: React.RefObject<HTMLElement | null>;
7
+ recentTools?: DrawingToolType[];
8
+ onToolSelect?: (tool: DrawingToolType) => void;
9
+ onDeleteDrawing?: () => void;
10
+ onLockDrawing?: () => void;
11
+ onHideDrawing?: () => void;
12
+ hasSelectedDrawing?: boolean;
13
+ }
14
+
15
+ interface MenuPosition {
16
+ x: number;
17
+ y: number;
18
+ }
19
+
20
+ export function ChartContextMenu({
21
+ containerRef,
22
+ recentTools = [],
23
+ onToolSelect,
24
+ onDeleteDrawing,
25
+ onLockDrawing,
26
+ onHideDrawing,
27
+ hasSelectedDrawing = false,
28
+ }: ChartContextMenuProps) {
29
+ const [visible, setVisible] = useState(false);
30
+ const [position, setPosition] = useState<MenuPosition>({ x: 0, y: 0 });
31
+ const menuRef = useRef<HTMLDivElement>(null);
32
+
33
+ const handleContextMenu = useCallback(
34
+ (e: MouseEvent) => {
35
+ e.preventDefault();
36
+ setPosition({ x: e.clientX, y: e.clientY });
37
+ setVisible(true);
38
+ },
39
+ [],
40
+ );
41
+
42
+ const handleClick = useCallback(() => {
43
+ setVisible(false);
44
+ }, []);
45
+
46
+ useEffect(() => {
47
+ const container = containerRef.current;
48
+ if (!container) return;
49
+
50
+ container.addEventListener('contextmenu', handleContextMenu);
51
+ document.addEventListener('click', handleClick);
52
+
53
+ return () => {
54
+ container.removeEventListener('contextmenu', handleContextMenu);
55
+ document.removeEventListener('click', handleClick);
56
+ };
57
+ }, [containerRef, handleContextMenu, handleClick]);
58
+
59
+ if (!visible) return null;
60
+
61
+ // 最近使用的工具(最多5个)
62
+ const recentToolConfigs = recentTools
63
+ .map((t) => DRAWING_TOOLS.find((d) => d.type === t))
64
+ .filter(Boolean)
65
+ .slice(0, 5);
66
+
67
+ return (
68
+ <div
69
+ ref={menuRef}
70
+ className="qlchart-context-menu"
71
+ style={{ left: position.x, top: position.y }}
72
+ onClick={(e) => e.stopPropagation()}
73
+ >
74
+ {hasSelectedDrawing && (
75
+ <>
76
+ <button
77
+ className="qlchart-context-menu-item"
78
+ onClick={() => {
79
+ onDeleteDrawing?.();
80
+ setVisible(false);
81
+ }}
82
+ >
83
+ 🗑️ 删除绘图
84
+ </button>
85
+ <button
86
+ className="qlchart-context-menu-item"
87
+ onClick={() => {
88
+ onLockDrawing?.();
89
+ setVisible(false);
90
+ }}
91
+ >
92
+ 🔒 锁定
93
+ </button>
94
+ <button
95
+ className="qlchart-context-menu-item"
96
+ onClick={() => {
97
+ onHideDrawing?.();
98
+ setVisible(false);
99
+ }}
100
+ >
101
+ 👁️ 隐藏
102
+ </button>
103
+ <div className="qlchart-context-menu-divider" />
104
+ </>
105
+ )}
106
+
107
+ {recentToolConfigs.length > 0 && (
108
+ <>
109
+ <div className="qlchart-context-menu-label">最近使用</div>
110
+ {recentToolConfigs.map((tool) =>
111
+ tool ? (
112
+ <button
113
+ key={tool.type}
114
+ className="qlchart-context-menu-item"
115
+ onClick={() => {
116
+ onToolSelect?.(tool.type);
117
+ setVisible(false);
118
+ }}
119
+ >
120
+ {tool.icon} {tool.label}
121
+ </button>
122
+ ) : null,
123
+ )}
124
+ <div className="qlchart-context-menu-divider" />
125
+ </>
126
+ )}
127
+
128
+ <button
129
+ className="qlchart-context-menu-item"
130
+ onClick={() => {
131
+ onToolSelect?.('cursor');
132
+ setVisible(false);
133
+ }}
134
+ >
135
+ 👆 光标
136
+ </button>
137
+ </div>
138
+ );
139
+ }
@@ -0,0 +1,2 @@
1
+ export { ChartContextMenu } from './ChartContextMenu.js';
2
+ export type { ChartContextMenuProps } from './ChartContextMenu.js';
@@ -0,0 +1,36 @@
1
+ import { useDrawingStore } from '../../store/useDrawingStore.js';
2
+ import { DrawingToolbar } from './DrawingToolbar.js';
3
+
4
+ export interface DrawingModuleProps {
5
+ /** 是否显示属性面板(默认true) */
6
+ showPropertyPanel?: boolean;
7
+ /** 自定义 className */
8
+ className?: string;
9
+ }
10
+
11
+ /**
12
+ * 绘图模块容器组件
13
+ *
14
+ * 独立管理绘图工具栏的渲染。
15
+ * 渲染位置由父组件控制(通常在QLChartPanel中)。
16
+ *
17
+ * 使用方式:
18
+ * ```tsx
19
+ * <DrawingModule />
20
+ * ```
21
+ */
22
+ export function DrawingModule({
23
+ showPropertyPanel = true,
24
+ className,
25
+ }: DrawingModuleProps) {
26
+ const drawingToolbarOpen = useDrawingStore((s) => s.drawingToolbarOpen);
27
+
28
+ return (
29
+ <>
30
+ {/* 绘图工具栏 - 竖排,渲染在图表区域 */}
31
+ {drawingToolbarOpen && (
32
+ <DrawingToolbar />
33
+ )}
34
+ </>
35
+ );
36
+ }
@@ -0,0 +1,347 @@
1
+ import { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import { createPortal } from 'react-dom';
3
+ import { useDrawingStore } from '../../store/useDrawingStore.js';
4
+ import { getPropertyTemplate } from '../../plugins/drawing/DrawingPropertyTemplates.js';
5
+ import { lineStyleToDash } from '../../plugins/drawing/lineStyleMap.js';
6
+ import { composeRgbaColor, rgbaToHex } from '../../plugins/drawing/colorUtils.js';
7
+ import type { DrawingStyleConfig, FibLevelItem, PropertyField } from '../../types/index.js';
8
+
9
+ export interface DrawingPropertyPanelProps {
10
+ onStyleChange?: (id: string, style: Partial<DrawingStyleConfig>) => void;
11
+ onDelete?: (id: string) => void;
12
+ }
13
+
14
+ export function DrawingPropertyPanel({ onStyleChange, onDelete }: DrawingPropertyPanelProps) {
15
+ const selectedDrawingId = useDrawingStore((s) => s.selectedDrawingId);
16
+ const selectedDrawingType = useDrawingStore((s) => s.selectedDrawingType);
17
+ const selectedStyle = useDrawingStore((s) => s.selectedStyle);
18
+ const showPropertyPanel = useDrawingStore((s) => s.showPropertyPanel);
19
+ const setShowPropertyPanel = useDrawingStore((s) => s.setShowPropertyPanel);
20
+ const setSelectedStyle = useDrawingStore((s) => s.setSelectedStyle);
21
+
22
+ // Bug3修复:draft 状态管理
23
+ const [draftStyle, setDraftStyle] = useState<DrawingStyleConfig | null>(null);
24
+ const [isDirty, setIsDirty] = useState(false);
25
+ // Bug4修复:删除确认 Modal
26
+ const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
27
+
28
+ useEffect(() => {
29
+ setDraftStyle(null);
30
+ setIsDirty(false);
31
+ }, [selectedDrawingId]);
32
+
33
+ const editStyle = draftStyle ?? selectedStyle;
34
+
35
+ const handleFieldChange = useCallback(
36
+ (field: PropertyField, value: string | number | boolean) => {
37
+ if (!selectedDrawingId || !editStyle) return;
38
+ const updated = { ...editStyle, [field.key]: value };
39
+ setDraftStyle(updated);
40
+ setIsDirty(true);
41
+
42
+ // 视觉属性:实时预览
43
+ const VISUAL_FIELDS = ['lineColor', 'lineWidth', 'fillColor', 'fillOpacity'];
44
+ if (VISUAL_FIELDS.includes(field.key)) {
45
+ const patch: Partial<DrawingStyleConfig> = { [field.key]: value } as any;
46
+
47
+ if (field.key === 'fillColor' || field.key === 'fillOpacity') {
48
+ const rawColor = field.key === 'fillColor'
49
+ ? String(value)
50
+ : rgbaToHex(editStyle.fillColor ?? '#2962ff');
51
+ const opacity = field.key === 'fillOpacity'
52
+ ? Number(value)
53
+ : (editStyle.fillOpacity ?? 0.2);
54
+ patch.fillColor = composeRgbaColor(rawColor, opacity);
55
+ patch.fillOpacity = opacity;
56
+ }
57
+
58
+ onStyleChange?.(selectedDrawingId, patch);
59
+ }
60
+ // lineStyle 也需要实时预览
61
+ if (field.key === 'lineStyle') {
62
+ const patch: Partial<DrawingStyleConfig> = {
63
+ lineStyle: value as string,
64
+ lineDash: lineStyleToDash(value as string),
65
+ };
66
+ onStyleChange?.(selectedDrawingId, patch);
67
+ }
68
+ // text 字段:不实时预览,需要保存按钮
69
+ // ★ Req3:fibLevels 也需要实时预览
70
+ if (field.key === 'fibLevels') {
71
+ const patch: Partial<DrawingStyleConfig> = { fibLevels: String(value) };
72
+ onStyleChange?.(selectedDrawingId, patch);
73
+ }
74
+ },
75
+ [selectedDrawingId, editStyle, onStyleChange],
76
+ );
77
+
78
+ const handleSave = useCallback(() => {
79
+ if (!selectedDrawingId || !draftStyle) return;
80
+ // 提交 text 字段
81
+ if ((draftStyle.text ?? '') !== (selectedStyle?.text ?? '')) {
82
+ onStyleChange?.(selectedDrawingId, { text: draftStyle.text ?? '' });
83
+ }
84
+ setSelectedStyle(draftStyle);
85
+ setDraftStyle(null);
86
+ setIsDirty(false);
87
+ }, [selectedDrawingId, draftStyle, selectedStyle, onStyleChange, setSelectedStyle]);
88
+
89
+ const handleCancel = useCallback(() => {
90
+ setDraftStyle(null);
91
+ setIsDirty(false);
92
+ // 回退实时预览的改动
93
+ if (selectedStyle && selectedDrawingId) {
94
+ const patch: Partial<DrawingStyleConfig> = {};
95
+ Object.assign(patch, selectedStyle);
96
+ if (selectedStyle.lineStyle) {
97
+ patch.lineDash = lineStyleToDash(selectedStyle.lineStyle);
98
+ }
99
+ onStyleChange?.(selectedDrawingId, patch);
100
+ }
101
+ }, [selectedStyle, onStyleChange, selectedDrawingId]);
102
+
103
+ const handleDelete = useCallback(() => {
104
+ // ★ Bug4修复:改为打开确认 Modal,而非直接执行删除
105
+ setDeleteConfirmOpen(true);
106
+ }, []);
107
+
108
+ const handleDeleteConfirm = useCallback(() => {
109
+ if (!selectedDrawingId) return;
110
+ onDelete?.(selectedDrawingId);
111
+ setShowPropertyPanel(false);
112
+ setDeleteConfirmOpen(false);
113
+ }, [selectedDrawingId, onDelete, setShowPropertyPanel]);
114
+
115
+ const handleDeleteCancel = useCallback(() => {
116
+ setDeleteConfirmOpen(false);
117
+ }, []);
118
+
119
+ const handleClose = useCallback(() => {
120
+ setShowPropertyPanel(false);
121
+ }, [setShowPropertyPanel]);
122
+
123
+ if (!showPropertyPanel || !selectedDrawingId || !editStyle) {
124
+ return null;
125
+ }
126
+
127
+ // ★ D12: 动态获取属性模板
128
+ const template = getPropertyTemplate(selectedDrawingType ?? '');
129
+
130
+ return (
131
+ <>
132
+ {createPortal(
133
+ <div className="qlchart-modal-overlay" onClick={handleClose}>
134
+ <div
135
+ className="qlchart-drawing-property-modal"
136
+ onClick={(e) => e.stopPropagation()}
137
+ role="dialog"
138
+ aria-modal="true"
139
+ aria-label="绘图属性编辑"
140
+ >
141
+ <div className="qlchart-drawing-pp-header">
142
+ <span>属性编辑{selectedDrawingType ? ` · ${selectedDrawingType}` : ''}</span>
143
+ <button className="qlchart-drawing-pp-close" onClick={handleClose}>✕</button>
144
+ </div>
145
+
146
+ {/* ★ D12: 动态渲染属性控件 */}
147
+ <div className="qlchart-drawing-pp-body">
148
+ {template.fields.map((field) => (
149
+ <div key={field.key} className="qlchart-drawing-pp-row">
150
+ <label>{field.label}</label>
151
+ {renderField(field, editStyle, handleFieldChange)}
152
+ </div>
153
+ ))}
154
+ </div>
155
+
156
+ <div className="qlchart-drawing-pp-actions">
157
+ {isDirty && (
158
+ <div className="qlchart-drawing-pp-edit-actions">
159
+ <button className="qlchart-drawing-pp-save" onClick={handleSave}>
160
+ ✓ 保存
161
+ </button>
162
+ <button className="qlchart-drawing-pp-cancel" onClick={handleCancel}>
163
+ ✕ 取消
164
+ </button>
165
+ </div>
166
+ )}
167
+ <button className="qlchart-drawing-pp-delete" onClick={handleDelete}>
168
+ 🗑️ 删除
169
+ </button>
170
+ </div>
171
+ </div>
172
+ </div>,
173
+ document.body,
174
+ )}
175
+ {/* ★ Bug4修复:删除确认 Modal,用 Portal 渲染到 document.body */}
176
+ {deleteConfirmOpen && createPortal(
177
+ <div className="qlchart-confirm-overlay" onClick={handleDeleteCancel}>
178
+ <div className="qlchart-confirm-modal" onClick={(e) => e.stopPropagation()}>
179
+ <div className="qlchart-confirm-icon">⚠️</div>
180
+ <div className="qlchart-confirm-title">确认删除</div>
181
+ <div className="qlchart-confirm-desc">确定要删除这个绘图吗?此操作不可撤销。</div>
182
+ <div className="qlchart-confirm-actions">
183
+ <button className="qlchart-confirm-cancel" onClick={handleDeleteCancel}>取消</button>
184
+ <button className="qlchart-confirm-ok" onClick={handleDeleteConfirm}>🗑️ 删除</button>
185
+ </div>
186
+ </div>
187
+ </div>,
188
+ document.body,
189
+ )}
190
+ </>
191
+ );
192
+ }
193
+
194
+ /** 根据字段类型动态渲染控件 */
195
+ function renderField(
196
+ field: PropertyField,
197
+ style: DrawingStyleConfig,
198
+ onChange: (field: PropertyField, value: string | number | boolean) => void,
199
+ ) {
200
+ const value = (style as any)[field.key] ?? field.default;
201
+
202
+ switch (field.type) {
203
+ case 'color':
204
+ // Bug5修复:确保 color input 始终收到 hex 格式
205
+ const colorValue = field.key === 'fillColor'
206
+ ? rgbaToHex(String(value))
207
+ : String(value);
208
+ return (
209
+ <input
210
+ type="color"
211
+ value={colorValue}
212
+ onChange={(e) => onChange(field, e.target.value)}
213
+ />
214
+ );
215
+
216
+ case 'range':
217
+ return (
218
+ <>
219
+ <input
220
+ type="range"
221
+ min={field.min}
222
+ max={field.max}
223
+ step={field.step}
224
+ value={Number(value)}
225
+ onChange={(e) => onChange(field, Number(e.target.value))}
226
+ />
227
+ <span className="qlchart-drawing-pp-value">
228
+ {field.key === 'fillOpacity' ? `${Math.round(Number(value) * 100)}%` : value}
229
+ </span>
230
+ </>
231
+ );
232
+
233
+ case 'toggle':
234
+ return (
235
+ <input
236
+ type="checkbox"
237
+ checked={Boolean(value)}
238
+ onChange={(e) => onChange(field, e.target.checked)}
239
+ />
240
+ );
241
+
242
+ case 'select':
243
+ return (
244
+ <select
245
+ value={String(value)}
246
+ onChange={(e) => onChange(field, e.target.value)}
247
+ className="qlchart-drawing-pp-select"
248
+ >
249
+ {field.options?.map((opt: { label: string; value: string }) => (
250
+ <option key={opt.value} value={opt.value}>{opt.label}</option>
251
+ ))}
252
+ </select>
253
+ );
254
+
255
+ case 'text':
256
+ return (
257
+ <input
258
+ type="text"
259
+ value={String(value)}
260
+ placeholder={field.label}
261
+ onChange={(e) => onChange(field, e.target.value)}
262
+ className="qlchart-drawing-pp-text"
263
+ />
264
+ );
265
+
266
+ case 'multiselect':
267
+ return (
268
+ <input
269
+ type="text"
270
+ value={String(value)}
271
+ placeholder={field.label}
272
+ onChange={(e) => onChange(field, e.target.value)}
273
+ className="qlchart-drawing-pp-text"
274
+ />
275
+ );
276
+
277
+ case 'fib-levels':
278
+ return <FibLevelsEditor field={field} style={style} onChange={onChange} />;
279
+
280
+ default:
281
+ return null;
282
+ }
283
+ }
284
+
285
+ /**
286
+ * ★ Req3:斐波那契级别编辑器
287
+ * 支持每条线的显隐切换和百分比值修改
288
+ */
289
+ function FibLevelsEditor({
290
+ field,
291
+ style,
292
+ onChange,
293
+ }: {
294
+ field: PropertyField;
295
+ style: DrawingStyleConfig;
296
+ onChange: (field: PropertyField, value: string | number | boolean) => void;
297
+ }) {
298
+ // 解析当前级别列表
299
+ const levels: FibLevelItem[] = useMemo(() => {
300
+ // 优先从 style 读取(已修改的值)
301
+ if (style.fibLevels) {
302
+ try {
303
+ return JSON.parse(style.fibLevels);
304
+ } catch {
305
+ // fallthrough to field defaults
306
+ }
307
+ }
308
+ // 回退到字段默认值
309
+ return field.fibLevels ?? [];
310
+ }, [style.fibLevels, field.fibLevels]);
311
+
312
+ const handleToggle = useCallback((idx: number, visible: boolean) => {
313
+ const updated = [...levels];
314
+ updated[idx] = { ...updated[idx], visible };
315
+ onChange(field, JSON.stringify(updated));
316
+ }, [levels, field, onChange]);
317
+
318
+ const handleValueChange = useCallback((idx: number, value: number) => {
319
+ const updated = [...levels];
320
+ updated[idx] = { ...updated[idx], value };
321
+ onChange(field, JSON.stringify(updated));
322
+ }, [levels, field, onChange]);
323
+
324
+ return (
325
+ <div className="qlchart-fib-levels-editor">
326
+ {levels.map((level, idx) => (
327
+ <div key={idx} className="qlchart-fib-level-row">
328
+ <input
329
+ type="checkbox"
330
+ checked={level.visible}
331
+ onChange={(e) => handleToggle(idx, e.target.checked)}
332
+ />
333
+ <input
334
+ type="number"
335
+ value={level.value}
336
+ step={0.001}
337
+ onChange={(e) => handleValueChange(idx, parseFloat(e.target.value) || 0)}
338
+ className="qlchart-fib-level-input"
339
+ />
340
+ <span className="qlchart-fib-level-label">
341
+ {(level.value * 100).toFixed(1)}%
342
+ </span>
343
+ </div>
344
+ ))}
345
+ </div>
346
+ );
347
+ }