@qfo/qfchart 0.8.0 → 0.8.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 (50) hide show
  1. package/dist/index.d.ts +319 -12
  2. package/dist/qfchart.min.browser.js +32 -16
  3. package/dist/qfchart.min.es.js +32 -16
  4. package/package.json +1 -1
  5. package/src/QFChart.ts +98 -262
  6. package/src/components/AbstractPlugin.ts +234 -104
  7. package/src/components/DrawingEditor.ts +297 -248
  8. package/src/components/DrawingRendererRegistry.ts +13 -0
  9. package/src/components/GraphicBuilder.ts +2 -2
  10. package/src/components/LayoutManager.ts +41 -35
  11. package/src/components/SeriesBuilder.ts +10 -10
  12. package/src/components/TooltipFormatter.ts +1 -1
  13. package/src/index.ts +17 -6
  14. package/src/plugins/ABCDPatternTool/ABCDPatternDrawingRenderer.ts +112 -0
  15. package/src/plugins/ABCDPatternTool/ABCDPatternTool.ts +136 -0
  16. package/src/plugins/ABCDPatternTool/index.ts +2 -0
  17. package/src/plugins/CypherPatternTool/CypherPatternDrawingRenderer.ts +80 -0
  18. package/src/plugins/CypherPatternTool/CypherPatternTool.ts +84 -0
  19. package/src/plugins/CypherPatternTool/index.ts +2 -0
  20. package/src/plugins/FibSpeedResistanceFanTool/FibSpeedResistanceFanDrawingRenderer.ts +163 -0
  21. package/src/plugins/FibSpeedResistanceFanTool/FibSpeedResistanceFanTool.ts +210 -0
  22. package/src/plugins/FibSpeedResistanceFanTool/index.ts +2 -0
  23. package/src/plugins/FibTrendExtensionTool/FibTrendExtensionDrawingRenderer.ts +141 -0
  24. package/src/plugins/FibTrendExtensionTool/FibTrendExtensionTool.ts +188 -0
  25. package/src/plugins/FibTrendExtensionTool/index.ts +2 -0
  26. package/src/plugins/FibonacciChannelTool/FibonacciChannelDrawingRenderer.ts +128 -0
  27. package/src/plugins/FibonacciChannelTool/FibonacciChannelTool.ts +231 -0
  28. package/src/plugins/FibonacciChannelTool/index.ts +2 -0
  29. package/src/plugins/FibonacciTool/FibonacciDrawingRenderer.ts +107 -0
  30. package/src/plugins/{FibonacciTool.ts → FibonacciTool/FibonacciTool.ts} +195 -192
  31. package/src/plugins/FibonacciTool/index.ts +2 -0
  32. package/src/plugins/HeadAndShouldersTool/HeadAndShouldersDrawingRenderer.ts +95 -0
  33. package/src/plugins/HeadAndShouldersTool/HeadAndShouldersTool.ts +97 -0
  34. package/src/plugins/HeadAndShouldersTool/index.ts +2 -0
  35. package/src/plugins/LineTool/LineDrawingRenderer.ts +49 -0
  36. package/src/plugins/{LineTool.ts → LineTool/LineTool.ts} +161 -190
  37. package/src/plugins/LineTool/index.ts +2 -0
  38. package/src/plugins/{MeasureTool.ts → MeasureTool/MeasureTool.ts} +324 -344
  39. package/src/plugins/MeasureTool/index.ts +1 -0
  40. package/src/plugins/ThreeDrivesPatternTool/ThreeDrivesPatternDrawingRenderer.ts +106 -0
  41. package/src/plugins/ThreeDrivesPatternTool/ThreeDrivesPatternTool.ts +98 -0
  42. package/src/plugins/ThreeDrivesPatternTool/index.ts +2 -0
  43. package/src/plugins/ToolGroup.ts +211 -0
  44. package/src/plugins/TrianglePatternTool/TrianglePatternDrawingRenderer.ts +107 -0
  45. package/src/plugins/TrianglePatternTool/TrianglePatternTool.ts +98 -0
  46. package/src/plugins/TrianglePatternTool/index.ts +2 -0
  47. package/src/plugins/XABCDPatternTool/XABCDPatternDrawingRenderer.ts +178 -0
  48. package/src/plugins/XABCDPatternTool/XABCDPatternTool.ts +213 -0
  49. package/src/plugins/XABCDPatternTool/index.ts +2 -0
  50. package/src/types.ts +37 -11
@@ -1,104 +1,234 @@
1
- import { ChartContext, Plugin, PluginConfig, OHLCV } from "../types";
2
- import { EventType, EventHandler } from "../utils/EventBus";
3
-
4
- export abstract class AbstractPlugin implements Plugin {
5
- public id: string;
6
- public name?: string;
7
- public icon?: string;
8
-
9
- protected context!: ChartContext;
10
- private eventListeners: Array<{ event: EventType; handler: EventHandler }> =
11
- [];
12
-
13
- constructor(config: PluginConfig) {
14
- this.id = config.id;
15
- this.name = config.name;
16
- this.icon = config.icon;
17
- }
18
-
19
- public init(context: ChartContext): void {
20
- this.context = context;
21
- this.onInit();
22
- }
23
-
24
- /**
25
- * Lifecycle hook called after context is initialized.
26
- * Override this instead of init().
27
- */
28
- protected onInit(): void {}
29
-
30
- public activate(): void {
31
- this.onActivate();
32
- this.context.events.emit("plugin:activated", this.id);
33
- }
34
-
35
- /**
36
- * Lifecycle hook called when the plugin is activated.
37
- */
38
- protected onActivate(): void {}
39
-
40
- public deactivate(): void {
41
- this.onDeactivate();
42
- this.context.events.emit("plugin:deactivated", this.id);
43
- }
44
-
45
- /**
46
- * Lifecycle hook called when the plugin is deactivated.
47
- */
48
- protected onDeactivate(): void {}
49
-
50
- public destroy(): void {
51
- this.removeAllListeners();
52
- this.onDestroy();
53
- }
54
-
55
- /**
56
- * Lifecycle hook called when the plugin is destroyed.
57
- */
58
- protected onDestroy(): void {}
59
-
60
- // --- Helper Methods ---
61
-
62
- /**
63
- * Register an event listener that will be automatically cleaned up on destroy.
64
- */
65
- protected on(event: EventType, handler: EventHandler): void {
66
- this.context.events.on(event, handler);
67
- this.eventListeners.push({ event, handler });
68
- }
69
-
70
- /**
71
- * Remove a specific event listener.
72
- */
73
- protected off(event: EventType, handler: EventHandler): void {
74
- this.context.events.off(event, handler);
75
- this.eventListeners = this.eventListeners.filter(
76
- (l) => l.event !== event || l.handler !== handler
77
- );
78
- }
79
-
80
- /**
81
- * Remove all listeners registered by this plugin.
82
- */
83
- protected removeAllListeners(): void {
84
- this.eventListeners.forEach(({ event, handler }) => {
85
- this.context.events.off(event, handler);
86
- });
87
- this.eventListeners = [];
88
- }
89
-
90
- /**
91
- * Access to the ECharts instance.
92
- */
93
- protected get chart() {
94
- return this.context.getChart();
95
- }
96
-
97
- /**
98
- * Access to market data.
99
- */
100
- protected get marketData(): OHLCV[] {
101
- return this.context.getMarketData();
102
- }
103
- }
104
-
1
+ import { ChartContext, Plugin, PluginConfig, OHLCV } from "../types";
2
+ import { EventType, EventHandler } from "../utils/EventBus";
3
+ import * as echarts from "echarts";
4
+
5
+ export abstract class AbstractPlugin implements Plugin {
6
+ public id: string;
7
+ public name?: string;
8
+ public icon?: string;
9
+
10
+ protected context!: ChartContext;
11
+ private eventListeners: Array<{ event: EventType; handler: EventHandler }> =
12
+ [];
13
+
14
+ // Snap indicator
15
+ private _snapIndicator: any = null;
16
+ private _snapMoveHandler: ((e: any) => void) | null = null;
17
+ private _snapKeyDownHandler: ((e: KeyboardEvent) => void) | null = null;
18
+ private _snapKeyUpHandler: ((e: KeyboardEvent) => void) | null = null;
19
+ private _snapBlurHandler: (() => void) | null = null;
20
+ private _snapActive: boolean = false;
21
+ private _lastMouseEvent: any = null;
22
+
23
+ constructor(config: PluginConfig) {
24
+ this.id = config.id;
25
+ this.name = config.name;
26
+ this.icon = config.icon;
27
+ }
28
+
29
+ public init(context: ChartContext): void {
30
+ this.context = context;
31
+ this.onInit();
32
+ }
33
+
34
+ /**
35
+ * Lifecycle hook called after context is initialized.
36
+ * Override this instead of init().
37
+ */
38
+ protected onInit(): void {}
39
+
40
+ public activate(): void {
41
+ this.onActivate();
42
+ this._bindSnapIndicator();
43
+ this.context.events.emit("plugin:activated", this.id);
44
+ }
45
+
46
+ /**
47
+ * Lifecycle hook called when the plugin is activated.
48
+ */
49
+ protected onActivate(): void {}
50
+
51
+ public deactivate(): void {
52
+ this._unbindSnapIndicator();
53
+ this.onDeactivate();
54
+ this.context.events.emit("plugin:deactivated", this.id);
55
+ }
56
+
57
+ /**
58
+ * Lifecycle hook called when the plugin is deactivated.
59
+ */
60
+ protected onDeactivate(): void {}
61
+
62
+ public destroy(): void {
63
+ this._unbindSnapIndicator();
64
+ this.removeAllListeners();
65
+ this.onDestroy();
66
+ }
67
+
68
+ /**
69
+ * Lifecycle hook called when the plugin is destroyed.
70
+ */
71
+ protected onDestroy(): void {}
72
+
73
+ // --- Helper Methods ---
74
+
75
+ /**
76
+ * Register an event listener that will be automatically cleaned up on destroy.
77
+ */
78
+ protected on(event: EventType, handler: EventHandler): void {
79
+ this.context.events.on(event, handler);
80
+ this.eventListeners.push({ event, handler });
81
+ }
82
+
83
+ /**
84
+ * Remove a specific event listener.
85
+ */
86
+ protected off(event: EventType, handler: EventHandler): void {
87
+ this.context.events.off(event, handler);
88
+ this.eventListeners = this.eventListeners.filter(
89
+ (l) => l.event !== event || l.handler !== handler
90
+ );
91
+ }
92
+
93
+ /**
94
+ * Remove all listeners registered by this plugin.
95
+ */
96
+ protected removeAllListeners(): void {
97
+ this.eventListeners.forEach(({ event, handler }) => {
98
+ this.context.events.off(event, handler);
99
+ });
100
+ this.eventListeners = [];
101
+ }
102
+
103
+ /**
104
+ * Access to the ECharts instance.
105
+ */
106
+ protected get chart() {
107
+ return this.context.getChart();
108
+ }
109
+
110
+ /**
111
+ * Access to market data.
112
+ */
113
+ protected get marketData(): OHLCV[] {
114
+ return this.context.getMarketData();
115
+ }
116
+
117
+ /**
118
+ * Get the event point coordinates, snapping to nearest candle OHLC if Ctrl is held.
119
+ * Use this instead of [params.offsetX, params.offsetY] in click/mousemove handlers.
120
+ */
121
+ protected getPoint(params: any): [number, number] {
122
+ const x = params.offsetX;
123
+ const y = params.offsetY;
124
+ const event = params.event;
125
+ const ctrlKey = event?.ctrlKey || event?.metaKey;
126
+
127
+ if (ctrlKey) {
128
+ const snapped = this.context.snapToCandle({ x, y });
129
+ return [snapped.x, snapped.y];
130
+ }
131
+
132
+ return [x, y];
133
+ }
134
+
135
+ // --- Snap Indicator (internal) ---
136
+
137
+ private _bindSnapIndicator(): void {
138
+ const zr = this.context.getChart().getZr();
139
+
140
+ this._snapMoveHandler = (e: any) => {
141
+ this._lastMouseEvent = e;
142
+ const ctrlKey = e.event?.ctrlKey || e.event?.metaKey;
143
+ if (ctrlKey) {
144
+ this._showSnapAt(e.offsetX, e.offsetY);
145
+ } else {
146
+ this._hideSnap();
147
+ }
148
+ };
149
+
150
+ this._snapKeyDownHandler = (e: KeyboardEvent) => {
151
+ if ((e.key === "Control" || e.key === "Meta") && this._lastMouseEvent) {
152
+ this._showSnapAt(this._lastMouseEvent.offsetX, this._lastMouseEvent.offsetY);
153
+ }
154
+ };
155
+
156
+ this._snapKeyUpHandler = (e: KeyboardEvent) => {
157
+ if (e.key === "Control" || e.key === "Meta") {
158
+ this._hideSnap();
159
+ }
160
+ };
161
+
162
+ // On Mac, Cmd+Tab can swallow the keyup event — hide snap when window loses focus
163
+ this._snapBlurHandler = () => {
164
+ this._hideSnap();
165
+ };
166
+
167
+ zr.on("mousemove", this._snapMoveHandler);
168
+ window.addEventListener("keydown", this._snapKeyDownHandler);
169
+ window.addEventListener("keyup", this._snapKeyUpHandler);
170
+ window.addEventListener("blur", this._snapBlurHandler);
171
+ }
172
+
173
+ private _unbindSnapIndicator(): void {
174
+ if (this._snapMoveHandler) {
175
+ try {
176
+ this.context.getChart().getZr().off("mousemove", this._snapMoveHandler);
177
+ } catch {}
178
+ this._snapMoveHandler = null;
179
+ }
180
+ if (this._snapKeyDownHandler) {
181
+ window.removeEventListener("keydown", this._snapKeyDownHandler);
182
+ this._snapKeyDownHandler = null;
183
+ }
184
+ if (this._snapKeyUpHandler) {
185
+ window.removeEventListener("keyup", this._snapKeyUpHandler);
186
+ this._snapKeyUpHandler = null;
187
+ }
188
+ if (this._snapBlurHandler) {
189
+ window.removeEventListener("blur", this._snapBlurHandler);
190
+ this._snapBlurHandler = null;
191
+ }
192
+ this._removeSnapGraphic();
193
+ this._lastMouseEvent = null;
194
+ }
195
+
196
+ private _removeSnapGraphic(): void {
197
+ if (this._snapIndicator) {
198
+ try {
199
+ this.context.getChart().getZr().remove(this._snapIndicator);
200
+ } catch {}
201
+ this._snapIndicator = null;
202
+ this._snapActive = false;
203
+ }
204
+ }
205
+
206
+ private _showSnapAt(x: number, y: number): void {
207
+ const snapped = this.context.snapToCandle({ x, y });
208
+ const zr = this.context.getChart().getZr();
209
+ if (!this._snapIndicator) {
210
+ this._snapIndicator = new echarts.graphic.Circle({
211
+ shape: { cx: 0, cy: 0, r: 5 },
212
+ style: {
213
+ fill: "rgba(59, 130, 246, 0.3)",
214
+ stroke: "#3b82f6",
215
+ lineWidth: 1.5,
216
+ },
217
+ z: 9999,
218
+ silent: true,
219
+ });
220
+ zr.add(this._snapIndicator);
221
+ }
222
+
223
+ this._snapIndicator.setShape({ cx: snapped.x, cy: snapped.y });
224
+ this._snapIndicator.show();
225
+ this._snapActive = true;
226
+ }
227
+
228
+ private _hideSnap(): void {
229
+ if (this._snapIndicator && this._snapActive) {
230
+ this._snapIndicator.hide();
231
+ this._snapActive = false;
232
+ }
233
+ }
234
+ }