@qfo/qfchart 0.5.0

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.
@@ -0,0 +1,192 @@
1
+ import * as echarts from "echarts";
2
+ import { AbstractPlugin } from "../components/AbstractPlugin";
3
+
4
+ export class FibonacciTool extends AbstractPlugin {
5
+ private startPoint: number[] | null = null;
6
+ private endPoint: number[] | null = null;
7
+ private state: "idle" | "drawing" | "finished" = "idle";
8
+
9
+ // Temporary ZRender elements
10
+ private graphicGroup: any = null;
11
+
12
+ // Fib levels config
13
+ private readonly levels = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1];
14
+ private readonly colors = [
15
+ "#787b86", // 0
16
+ "#f44336", // 0.236
17
+ "#ff9800", // 0.382
18
+ "#4caf50", // 0.5
19
+ "#2196f3", // 0.618
20
+ "#00bcd4", // 0.786
21
+ "#787b86", // 1
22
+ ];
23
+
24
+ constructor(options: { name?: string; icon?: string } = {}) {
25
+ super({
26
+ id: "fibonacci-tool",
27
+ name: options.name || "Fibonacci Retracement",
28
+ icon:
29
+ options.icon ||
30
+ `<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M120-80v-80h720v80H120Zm0-240v-80h720v80H120Zm0-240v-80h720v80H120Zm0-240v-80h720v80H120Z"/></svg>`,
31
+ });
32
+ }
33
+
34
+ public onActivate(): void {
35
+ this.state = "idle";
36
+ this.startPoint = null;
37
+ this.endPoint = null;
38
+ this.context.getChart().getZr().setCursorStyle("crosshair");
39
+ this.bindEvents();
40
+ }
41
+
42
+ public onDeactivate(): void {
43
+ this.state = "idle";
44
+ this.startPoint = null;
45
+ this.endPoint = null;
46
+ this.removeGraphic();
47
+ this.unbindEvents();
48
+ this.context.getChart().getZr().setCursorStyle("default");
49
+ }
50
+
51
+ private bindEvents() {
52
+ const zr = this.context.getChart().getZr();
53
+ zr.on("click", this.onClick);
54
+ zr.on("mousemove", this.onMouseMove);
55
+ }
56
+
57
+ private unbindEvents() {
58
+ const zr = this.context.getChart().getZr();
59
+ zr.off("click", this.onClick);
60
+ zr.off("mousemove", this.onMouseMove);
61
+ }
62
+
63
+ private onClick = (params: any) => {
64
+ if (this.state === "idle") {
65
+ this.state = "drawing";
66
+ this.startPoint = [params.offsetX, params.offsetY];
67
+ this.endPoint = [params.offsetX, params.offsetY];
68
+ this.initGraphic();
69
+ this.updateGraphic();
70
+ } else if (this.state === "drawing") {
71
+ this.state = "finished";
72
+ this.endPoint = [params.offsetX, params.offsetY];
73
+ this.updateGraphic();
74
+ this.saveDrawing();
75
+
76
+ // Cleanup local graphic and deactivate
77
+ this.removeGraphic();
78
+ this.context.disableTools();
79
+ }
80
+ };
81
+
82
+ private onMouseMove = (params: any) => {
83
+ if (this.state === "drawing") {
84
+ this.endPoint = [params.offsetX, params.offsetY];
85
+ this.updateGraphic();
86
+ }
87
+ };
88
+
89
+ private initGraphic() {
90
+ this.graphicGroup = new echarts.graphic.Group();
91
+ this.context.getChart().getZr().add(this.graphicGroup);
92
+ }
93
+
94
+ private removeGraphic() {
95
+ if (this.graphicGroup) {
96
+ this.context.getChart().getZr().remove(this.graphicGroup);
97
+ this.graphicGroup = null;
98
+ }
99
+ }
100
+
101
+ private updateGraphic() {
102
+ if (!this.graphicGroup || !this.startPoint || !this.endPoint) return;
103
+ this.graphicGroup.removeAll();
104
+
105
+ const x1 = this.startPoint[0];
106
+ const y1 = this.startPoint[1];
107
+ const x2 = this.endPoint[0];
108
+ const y2 = this.endPoint[1];
109
+
110
+ // Diagonal trend line
111
+ const trendLine = new echarts.graphic.Line({
112
+ shape: { x1, y1, x2, y2 },
113
+ style: {
114
+ stroke: "#999",
115
+ lineWidth: 1,
116
+ lineDash: [4, 4],
117
+ },
118
+ silent: true,
119
+ });
120
+ this.graphicGroup.add(trendLine);
121
+
122
+ // Levels
123
+ const startX = Math.min(x1, x2);
124
+ const endX = Math.max(x1, x2);
125
+ const width = endX - startX;
126
+
127
+ // Y range
128
+ const diffY = y2 - y1; // Pixel difference
129
+
130
+ this.levels.forEach((level, index) => {
131
+ const levelY = y2 - diffY * level;
132
+
133
+ const color = this.colors[index % this.colors.length];
134
+
135
+ // Line
136
+ const line = new echarts.graphic.Line({
137
+ shape: { x1: startX, y1: levelY, x2: endX, y2: levelY },
138
+ style: {
139
+ stroke: color,
140
+ lineWidth: 1,
141
+ },
142
+ silent: true,
143
+ });
144
+ this.graphicGroup.add(line);
145
+
146
+ if (index < this.levels.length - 1) {
147
+ const nextLevel = this.levels[index + 1];
148
+ const nextY = y2 - diffY * nextLevel;
149
+ const rectH = Math.abs(nextY - levelY);
150
+ const rectY = Math.min(levelY, nextY);
151
+
152
+ const rect = new echarts.graphic.Rect({
153
+ shape: { x: startX, y: rectY, width, height: rectH },
154
+ style: {
155
+ fill: this.colors[(index + 1) % this.colors.length], // Use next level's color
156
+ opacity: 0.1,
157
+ },
158
+ silent: true,
159
+ });
160
+ this.graphicGroup.add(rect);
161
+ }
162
+ });
163
+ }
164
+
165
+ private saveDrawing() {
166
+ if (!this.startPoint || !this.endPoint) return;
167
+
168
+ const start = this.context.coordinateConversion.pixelToData({
169
+ x: this.startPoint[0],
170
+ y: this.startPoint[1],
171
+ });
172
+ const end = this.context.coordinateConversion.pixelToData({
173
+ x: this.endPoint[0],
174
+ y: this.endPoint[1],
175
+ });
176
+
177
+ if (start && end) {
178
+ const paneIndex = start.paneIndex || 0;
179
+
180
+ this.context.addDrawing({
181
+ id: `fib-${Date.now()}`,
182
+ type: "fibonacci",
183
+ points: [start, end],
184
+ paneIndex: paneIndex,
185
+ style: {
186
+ color: "#3b82f6", // Default color, though individual lines use specific colors
187
+ lineWidth: 1,
188
+ },
189
+ });
190
+ }
191
+ }
192
+ }
@@ -0,0 +1,190 @@
1
+ import { AbstractPlugin } from '../components/AbstractPlugin';
2
+ import * as echarts from 'echarts';
3
+
4
+ type PluginState = 'idle' | 'drawing' | 'finished';
5
+
6
+ export class LineTool extends AbstractPlugin {
7
+ private zr!: any;
8
+ private state: PluginState = 'idle';
9
+ private startPoint: number[] | null = null;
10
+ private endPoint: number[] | null = null;
11
+
12
+ // ZRender Elements
13
+ private group: any = null;
14
+ private line: any = null;
15
+ private startCircle: any = null;
16
+ private endCircle: any = null;
17
+
18
+ constructor(options: { name?: string; icon?: string }) {
19
+ super({
20
+ id: 'trend-line',
21
+ name: options?.name || 'Trend Line',
22
+ icon:
23
+ options?.icon ||
24
+ `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="2" y1="22" x2="22" y2="2" /></svg>`,
25
+ });
26
+ }
27
+
28
+ protected onInit(): void {
29
+ this.zr = this.chart.getZr();
30
+ }
31
+
32
+ protected onActivate(): void {
33
+ this.state = 'idle';
34
+ this.chart.getZr().setCursorStyle('crosshair');
35
+
36
+ this.zr.on('click', this.onClick);
37
+ this.zr.on('mousemove', this.onMouseMove);
38
+ }
39
+
40
+ protected onDeactivate(): void {
41
+ this.state = 'idle';
42
+ this.chart.getZr().setCursorStyle('default');
43
+
44
+ this.zr.off('click', this.onClick);
45
+ this.zr.off('mousemove', this.onMouseMove);
46
+
47
+ // Clean up clear listeners
48
+ this.disableClearListeners();
49
+
50
+ // @ts-ignore - state type comparison
51
+ if (this.state === 'drawing') {
52
+ this.removeGraphic();
53
+ }
54
+ }
55
+
56
+ protected onDestroy(): void {
57
+ this.removeGraphic();
58
+ }
59
+
60
+ // --- Interaction Handlers ---
61
+
62
+ private onMouseDown = () => {
63
+ // No longer needed
64
+ };
65
+
66
+ private onChartInteraction = () => {
67
+ // No longer needed
68
+ };
69
+
70
+ private onClick = (params: any) => {
71
+ if (this.state === 'idle') {
72
+ this.state = 'drawing';
73
+ this.startPoint = [params.offsetX, params.offsetY];
74
+ this.endPoint = [params.offsetX, params.offsetY];
75
+ this.initGraphic();
76
+ this.updateGraphic();
77
+ } else if (this.state === 'drawing') {
78
+ this.state = 'finished';
79
+ this.endPoint = [params.offsetX, params.offsetY];
80
+ this.updateGraphic();
81
+
82
+ // Convert to native chart drawing
83
+ if (this.startPoint && this.endPoint) {
84
+ const start = this.context.coordinateConversion.pixelToData({
85
+ x: this.startPoint[0],
86
+ y: this.startPoint[1],
87
+ });
88
+ const end = this.context.coordinateConversion.pixelToData({
89
+ x: this.endPoint[0],
90
+ y: this.endPoint[1],
91
+ });
92
+
93
+ if (start && end) {
94
+ // Use the pane index from the start point (assume drawing starts and ends in same pane or use start pane)
95
+ const paneIndex = start.paneIndex || 0;
96
+
97
+ this.context.addDrawing({
98
+ id: `line-${Date.now()}`,
99
+ type: 'line',
100
+ points: [start, end],
101
+ paneIndex: paneIndex,
102
+ style: {
103
+ color: '#3b82f6',
104
+ lineWidth: 2,
105
+ },
106
+ });
107
+ }
108
+ }
109
+
110
+ // Cleanup local ZRender graphic as it's now part of the chart series
111
+ this.removeGraphic();
112
+ this.context.disableTools();
113
+ }
114
+ };
115
+
116
+ private saveDataCoordinates() {
117
+ // No longer needed
118
+ }
119
+
120
+ private updateGraphicFromData() {
121
+ // No longer needed
122
+ }
123
+
124
+ private enableClearListeners(): void {
125
+ // No longer needed
126
+ }
127
+
128
+ private clearHandlers: any = {};
129
+
130
+ private disableClearListeners(): void {
131
+ // No longer needed
132
+ }
133
+
134
+ private onMouseMove = (params: any) => {
135
+ if (this.state !== 'drawing') return;
136
+ this.endPoint = [params.offsetX, params.offsetY];
137
+ this.updateGraphic();
138
+ };
139
+
140
+ // --- Graphics ---
141
+
142
+ private initGraphic(): void {
143
+ if (this.group) return;
144
+
145
+ this.group = new echarts.graphic.Group();
146
+
147
+ this.line = new echarts.graphic.Line({
148
+ shape: { x1: 0, y1: 0, x2: 0, y2: 0 },
149
+ style: { stroke: '#3b82f6', lineWidth: 2 },
150
+ z: 100,
151
+ });
152
+
153
+ this.startCircle = new echarts.graphic.Circle({
154
+ shape: { cx: 0, cy: 0, r: 4 },
155
+ style: { fill: '#fff', stroke: '#3b82f6', lineWidth: 1 },
156
+ z: 101,
157
+ });
158
+
159
+ this.endCircle = new echarts.graphic.Circle({
160
+ shape: { cx: 0, cy: 0, r: 4 },
161
+ style: { fill: '#fff', stroke: '#3b82f6', lineWidth: 1 },
162
+ z: 101,
163
+ });
164
+
165
+ this.group.add(this.line);
166
+ this.group.add(this.startCircle);
167
+ this.group.add(this.endCircle);
168
+
169
+ this.zr.add(this.group);
170
+ }
171
+
172
+ private removeGraphic(): void {
173
+ if (this.group) {
174
+ this.zr.remove(this.group);
175
+ this.group = null;
176
+ this.disableClearListeners();
177
+ }
178
+ }
179
+
180
+ private updateGraphic(): void {
181
+ if (!this.startPoint || !this.endPoint || !this.group) return;
182
+
183
+ const [x1, y1] = this.startPoint;
184
+ const [x2, y2] = this.endPoint;
185
+
186
+ this.line.setShape({ x1, y1, x2, y2 });
187
+ this.startCircle.setShape({ cx: x1, cy: y1 });
188
+ this.endCircle.setShape({ cx: x2, cy: y2 });
189
+ }
190
+ }