@qfo/qfchart 0.8.0 → 0.8.2
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.
- package/dist/index.d.ts +524 -12
- package/dist/qfchart.min.browser.js +34 -18
- package/dist/qfchart.min.es.js +34 -18
- package/package.json +1 -1
- package/src/QFChart.ts +109 -272
- package/src/components/AbstractPlugin.ts +234 -104
- package/src/components/DrawingEditor.ts +297 -248
- package/src/components/DrawingRendererRegistry.ts +13 -0
- package/src/components/GraphicBuilder.ts +2 -2
- package/src/components/LayoutManager.ts +92 -52
- package/src/components/SeriesBuilder.ts +10 -10
- package/src/components/TooltipFormatter.ts +1 -1
- package/src/index.ts +25 -6
- package/src/plugins/ABCDPatternTool/ABCDPatternDrawingRenderer.ts +112 -0
- package/src/plugins/ABCDPatternTool/ABCDPatternTool.ts +136 -0
- package/src/plugins/ABCDPatternTool/index.ts +2 -0
- package/src/plugins/CrossLineTool/CrossLineDrawingRenderer.ts +49 -0
- package/src/plugins/CrossLineTool/CrossLineTool.ts +52 -0
- package/src/plugins/CrossLineTool/index.ts +2 -0
- package/src/plugins/CypherPatternTool/CypherPatternDrawingRenderer.ts +80 -0
- package/src/plugins/CypherPatternTool/CypherPatternTool.ts +84 -0
- package/src/plugins/CypherPatternTool/index.ts +2 -0
- package/src/plugins/ExtendedLineTool/ExtendedLineDrawingRenderer.ts +73 -0
- package/src/plugins/ExtendedLineTool/ExtendedLineTool.ts +173 -0
- package/src/plugins/ExtendedLineTool/index.ts +2 -0
- package/src/plugins/FibSpeedResistanceFanTool/FibSpeedResistanceFanDrawingRenderer.ts +163 -0
- package/src/plugins/FibSpeedResistanceFanTool/FibSpeedResistanceFanTool.ts +210 -0
- package/src/plugins/FibSpeedResistanceFanTool/index.ts +2 -0
- package/src/plugins/FibTrendExtensionTool/FibTrendExtensionDrawingRenderer.ts +141 -0
- package/src/plugins/FibTrendExtensionTool/FibTrendExtensionTool.ts +188 -0
- package/src/plugins/FibTrendExtensionTool/index.ts +2 -0
- package/src/plugins/FibonacciChannelTool/FibonacciChannelDrawingRenderer.ts +128 -0
- package/src/plugins/FibonacciChannelTool/FibonacciChannelTool.ts +231 -0
- package/src/plugins/FibonacciChannelTool/index.ts +2 -0
- package/src/plugins/FibonacciTool/FibonacciDrawingRenderer.ts +107 -0
- package/src/plugins/{FibonacciTool.ts → FibonacciTool/FibonacciTool.ts} +195 -192
- package/src/plugins/FibonacciTool/index.ts +2 -0
- package/src/plugins/HeadAndShouldersTool/HeadAndShouldersDrawingRenderer.ts +95 -0
- package/src/plugins/HeadAndShouldersTool/HeadAndShouldersTool.ts +97 -0
- package/src/plugins/HeadAndShouldersTool/index.ts +2 -0
- package/src/plugins/HorizontalLineTool/HorizontalLineDrawingRenderer.ts +54 -0
- package/src/plugins/HorizontalLineTool/HorizontalLineTool.ts +52 -0
- package/src/plugins/HorizontalLineTool/index.ts +2 -0
- package/src/plugins/HorizontalRayTool/HorizontalRayDrawingRenderer.ts +34 -0
- package/src/plugins/HorizontalRayTool/HorizontalRayTool.ts +52 -0
- package/src/plugins/HorizontalRayTool/index.ts +2 -0
- package/src/plugins/InfoLineTool/InfoLineDrawingRenderer.ts +72 -0
- package/src/plugins/InfoLineTool/InfoLineTool.ts +130 -0
- package/src/plugins/InfoLineTool/index.ts +2 -0
- package/src/plugins/LineTool/LineDrawingRenderer.ts +49 -0
- package/src/plugins/{LineTool.ts → LineTool/LineTool.ts} +161 -190
- package/src/plugins/LineTool/index.ts +2 -0
- package/src/plugins/{MeasureTool.ts → MeasureTool/MeasureTool.ts} +324 -344
- package/src/plugins/MeasureTool/index.ts +1 -0
- package/src/plugins/RayTool/RayDrawingRenderer.ts +69 -0
- package/src/plugins/RayTool/RayTool.ts +162 -0
- package/src/plugins/RayTool/index.ts +2 -0
- package/src/plugins/ThreeDrivesPatternTool/ThreeDrivesPatternDrawingRenderer.ts +106 -0
- package/src/plugins/ThreeDrivesPatternTool/ThreeDrivesPatternTool.ts +98 -0
- package/src/plugins/ThreeDrivesPatternTool/index.ts +2 -0
- package/src/plugins/ToolGroup.ts +211 -0
- package/src/plugins/TrendAngleTool/TrendAngleDrawingRenderer.ts +87 -0
- package/src/plugins/TrendAngleTool/TrendAngleTool.ts +176 -0
- package/src/plugins/TrendAngleTool/index.ts +2 -0
- package/src/plugins/TrianglePatternTool/TrianglePatternDrawingRenderer.ts +107 -0
- package/src/plugins/TrianglePatternTool/TrianglePatternTool.ts +98 -0
- package/src/plugins/TrianglePatternTool/index.ts +2 -0
- package/src/plugins/VerticalLineTool/VerticalLineDrawingRenderer.ts +35 -0
- package/src/plugins/VerticalLineTool/VerticalLineTool.ts +52 -0
- package/src/plugins/VerticalLineTool/index.ts +2 -0
- package/src/plugins/XABCDPatternTool/XABCDPatternDrawingRenderer.ts +178 -0
- package/src/plugins/XABCDPatternTool/XABCDPatternTool.ts +213 -0
- package/src/plugins/XABCDPatternTool/index.ts +2 -0
- package/src/types.ts +39 -11
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { DrawingRenderer, DrawingRenderContext } from '../../types';
|
|
2
|
+
|
|
3
|
+
const LEVELS = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1];
|
|
4
|
+
const COLORS = ['#787b86', '#f44336', '#ff9800', '#4caf50', '#2196f3', '#00bcd4', '#787b86'];
|
|
5
|
+
|
|
6
|
+
export class FibSpeedResistanceFanDrawingRenderer implements DrawingRenderer {
|
|
7
|
+
type = 'fib_speed_resistance_fan';
|
|
8
|
+
|
|
9
|
+
render(ctx: DrawingRenderContext): any {
|
|
10
|
+
const { drawing, pixelPoints, isSelected } = ctx;
|
|
11
|
+
const [x1, y1] = pixelPoints[0];
|
|
12
|
+
const [x2, y2] = pixelPoints[1];
|
|
13
|
+
const color = drawing.style?.color || '#3b82f6';
|
|
14
|
+
|
|
15
|
+
const dx = x2 - x1;
|
|
16
|
+
const dy = y2 - y1;
|
|
17
|
+
|
|
18
|
+
const children: any[] = [];
|
|
19
|
+
|
|
20
|
+
// Compute fan ray endpoints for each level
|
|
21
|
+
// Price rays: start → (x1 + dx, y1 + dy * level)
|
|
22
|
+
// Time rays: start → (x1 + dx * level, y1 + dy)
|
|
23
|
+
const priceRays: [number, number][] = [];
|
|
24
|
+
const timeRays: [number, number][] = [];
|
|
25
|
+
|
|
26
|
+
for (const level of LEVELS) {
|
|
27
|
+
priceRays.push([x1 + dx, y1 + dy * level]);
|
|
28
|
+
timeRays.push([x1 + dx * level, y1 + dy]);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Fill zones between adjacent price rays
|
|
32
|
+
for (let i = 0; i < priceRays.length - 1; i++) {
|
|
33
|
+
children.push({
|
|
34
|
+
type: 'polygon',
|
|
35
|
+
name: 'line',
|
|
36
|
+
shape: {
|
|
37
|
+
points: [
|
|
38
|
+
[x1, y1],
|
|
39
|
+
priceRays[i],
|
|
40
|
+
priceRays[i + 1],
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
style: {
|
|
44
|
+
fill: COLORS[(i + 1) % COLORS.length],
|
|
45
|
+
opacity: 0.06,
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Fill zones between adjacent time rays
|
|
51
|
+
for (let i = 0; i < timeRays.length - 1; i++) {
|
|
52
|
+
children.push({
|
|
53
|
+
type: 'polygon',
|
|
54
|
+
name: 'line',
|
|
55
|
+
shape: {
|
|
56
|
+
points: [
|
|
57
|
+
[x1, y1],
|
|
58
|
+
timeRays[i],
|
|
59
|
+
timeRays[i + 1],
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
style: {
|
|
63
|
+
fill: COLORS[(i + 1) % COLORS.length],
|
|
64
|
+
opacity: 0.06,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Draw price ray lines
|
|
70
|
+
LEVELS.forEach((level, index) => {
|
|
71
|
+
const [ex, ey] = priceRays[index];
|
|
72
|
+
const levelColor = COLORS[index % COLORS.length];
|
|
73
|
+
|
|
74
|
+
children.push({
|
|
75
|
+
type: 'line',
|
|
76
|
+
shape: { x1, y1, x2: ex, y2: ey },
|
|
77
|
+
style: { stroke: levelColor, lineWidth: 1 },
|
|
78
|
+
silent: true,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
children.push({
|
|
82
|
+
type: 'text',
|
|
83
|
+
style: {
|
|
84
|
+
text: `${level}`,
|
|
85
|
+
x: ex + 3,
|
|
86
|
+
y: ey - 2,
|
|
87
|
+
fill: levelColor,
|
|
88
|
+
fontSize: 9,
|
|
89
|
+
},
|
|
90
|
+
silent: true,
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Draw time ray lines
|
|
95
|
+
LEVELS.forEach((level, index) => {
|
|
96
|
+
const [ex, ey] = timeRays[index];
|
|
97
|
+
const levelColor = COLORS[index % COLORS.length];
|
|
98
|
+
|
|
99
|
+
children.push({
|
|
100
|
+
type: 'line',
|
|
101
|
+
shape: { x1, y1, x2: ex, y2: ey },
|
|
102
|
+
style: { stroke: levelColor, lineWidth: 1 },
|
|
103
|
+
silent: true,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Label on the bottom/right end
|
|
107
|
+
children.push({
|
|
108
|
+
type: 'text',
|
|
109
|
+
style: {
|
|
110
|
+
text: `${level}`,
|
|
111
|
+
x: ex - 2,
|
|
112
|
+
y: ey + 8,
|
|
113
|
+
fill: levelColor,
|
|
114
|
+
fontSize: 9,
|
|
115
|
+
},
|
|
116
|
+
silent: true,
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Bounding box edges (dashed)
|
|
121
|
+
children.push({
|
|
122
|
+
type: 'line',
|
|
123
|
+
name: 'line',
|
|
124
|
+
shape: { x1: x2, y1, x2, y2 },
|
|
125
|
+
style: { stroke: '#555', lineWidth: 1, lineDash: [3, 3] },
|
|
126
|
+
});
|
|
127
|
+
children.push({
|
|
128
|
+
type: 'line',
|
|
129
|
+
name: 'line',
|
|
130
|
+
shape: { x1, y1: y2, x2, y2 },
|
|
131
|
+
style: { stroke: '#555', lineWidth: 1, lineDash: [3, 3] },
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Diagonal (start to end)
|
|
135
|
+
children.push({
|
|
136
|
+
type: 'line',
|
|
137
|
+
name: 'line',
|
|
138
|
+
shape: { x1, y1, x2, y2 },
|
|
139
|
+
style: { stroke: '#999', lineWidth: 1, lineDash: [4, 4] },
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Control points
|
|
143
|
+
children.push({
|
|
144
|
+
type: 'circle',
|
|
145
|
+
name: 'point-0',
|
|
146
|
+
shape: { cx: x1, cy: y1, r: 4 },
|
|
147
|
+
style: { fill: '#fff', stroke: color, lineWidth: 1, opacity: isSelected ? 1 : 0 },
|
|
148
|
+
z: 100,
|
|
149
|
+
});
|
|
150
|
+
children.push({
|
|
151
|
+
type: 'circle',
|
|
152
|
+
name: 'point-1',
|
|
153
|
+
shape: { cx: x2, cy: y2, r: 4 },
|
|
154
|
+
style: { fill: '#fff', stroke: color, lineWidth: 1, opacity: isSelected ? 1 : 0 },
|
|
155
|
+
z: 100,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
type: 'group',
|
|
160
|
+
children,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import * as echarts from 'echarts';
|
|
2
|
+
import { AbstractPlugin } from '../../components/AbstractPlugin';
|
|
3
|
+
import { FibSpeedResistanceFanDrawingRenderer } from './FibSpeedResistanceFanDrawingRenderer';
|
|
4
|
+
|
|
5
|
+
const LEVELS = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1];
|
|
6
|
+
const COLORS = ['#787b86', '#f44336', '#ff9800', '#4caf50', '#2196f3', '#00bcd4', '#787b86'];
|
|
7
|
+
|
|
8
|
+
export class FibSpeedResistanceFanTool extends AbstractPlugin {
|
|
9
|
+
private startPoint: number[] | null = null;
|
|
10
|
+
private endPoint: number[] | null = null;
|
|
11
|
+
private state: 'idle' | 'drawing' | 'finished' = 'idle';
|
|
12
|
+
|
|
13
|
+
// Temporary ZRender elements
|
|
14
|
+
private graphicGroup: any = null;
|
|
15
|
+
|
|
16
|
+
constructor(options: { name?: string; icon?: string } = {}) {
|
|
17
|
+
super({
|
|
18
|
+
id: 'fib-speed-resistance-fan-tool',
|
|
19
|
+
name: options.name || 'Fib Speed Resistance Fan',
|
|
20
|
+
icon:
|
|
21
|
+
options.icon ||
|
|
22
|
+
`<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#e3e3e3"><path d="M2 21L22 3M2 21l20-6M2 21l20-9M2 21l20-12M2 21l20-15M2 21l6-18M2 21l9-18M2 21l12-18M2 21l15-18" stroke="#e3e3e3" stroke-width="1" fill="none"/></svg>`,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
protected onInit(): void {
|
|
27
|
+
this.context.registerDrawingRenderer(new FibSpeedResistanceFanDrawingRenderer());
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
protected onActivate(): void {
|
|
31
|
+
this.state = 'idle';
|
|
32
|
+
this.startPoint = null;
|
|
33
|
+
this.endPoint = null;
|
|
34
|
+
this.context.getChart().getZr().setCursorStyle('crosshair');
|
|
35
|
+
this.bindEvents();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
protected onDeactivate(): void {
|
|
39
|
+
this.state = 'idle';
|
|
40
|
+
this.startPoint = null;
|
|
41
|
+
this.endPoint = null;
|
|
42
|
+
this.removeGraphic();
|
|
43
|
+
this.unbindEvents();
|
|
44
|
+
this.context.getChart().getZr().setCursorStyle('default');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private bindEvents() {
|
|
48
|
+
const zr = this.context.getChart().getZr();
|
|
49
|
+
zr.on('click', this.onClick);
|
|
50
|
+
zr.on('mousemove', this.onMouseMove);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private unbindEvents() {
|
|
54
|
+
const zr = this.context.getChart().getZr();
|
|
55
|
+
zr.off('click', this.onClick);
|
|
56
|
+
zr.off('mousemove', this.onMouseMove);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private onClick = (params: any) => {
|
|
60
|
+
if (this.state === 'idle') {
|
|
61
|
+
this.state = 'drawing';
|
|
62
|
+
this.startPoint = this.getPoint(params);
|
|
63
|
+
this.endPoint = this.getPoint(params);
|
|
64
|
+
this.initGraphic();
|
|
65
|
+
this.updateGraphic();
|
|
66
|
+
} else if (this.state === 'drawing') {
|
|
67
|
+
this.state = 'finished';
|
|
68
|
+
this.endPoint = this.getPoint(params);
|
|
69
|
+
this.updateGraphic();
|
|
70
|
+
this.saveDrawing();
|
|
71
|
+
|
|
72
|
+
this.removeGraphic();
|
|
73
|
+
this.context.disableTools();
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
private onMouseMove = (params: any) => {
|
|
78
|
+
if (this.state === 'drawing') {
|
|
79
|
+
this.endPoint = this.getPoint(params);
|
|
80
|
+
this.updateGraphic();
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
private initGraphic() {
|
|
85
|
+
this.graphicGroup = new echarts.graphic.Group();
|
|
86
|
+
this.context.getChart().getZr().add(this.graphicGroup);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private removeGraphic() {
|
|
90
|
+
if (this.graphicGroup) {
|
|
91
|
+
this.context.getChart().getZr().remove(this.graphicGroup);
|
|
92
|
+
this.graphicGroup = null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private updateGraphic() {
|
|
97
|
+
if (!this.graphicGroup || !this.startPoint || !this.endPoint) return;
|
|
98
|
+
this.graphicGroup.removeAll();
|
|
99
|
+
|
|
100
|
+
const x1 = this.startPoint[0];
|
|
101
|
+
const y1 = this.startPoint[1];
|
|
102
|
+
const x2 = this.endPoint[0];
|
|
103
|
+
const y2 = this.endPoint[1];
|
|
104
|
+
|
|
105
|
+
const dx = x2 - x1;
|
|
106
|
+
const dy = y2 - y1;
|
|
107
|
+
|
|
108
|
+
// Price rays and time rays
|
|
109
|
+
LEVELS.forEach((level, index) => {
|
|
110
|
+
const color = COLORS[index % COLORS.length];
|
|
111
|
+
|
|
112
|
+
// Price ray: start → (x1 + dx, y1 + dy * level)
|
|
113
|
+
this.graphicGroup.add(
|
|
114
|
+
new echarts.graphic.Line({
|
|
115
|
+
shape: { x1, y1, x2: x1 + dx, y2: y1 + dy * level },
|
|
116
|
+
style: { stroke: color, lineWidth: 1 },
|
|
117
|
+
silent: true,
|
|
118
|
+
}),
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// Time ray: start → (x1 + dx * level, y1 + dy)
|
|
122
|
+
this.graphicGroup.add(
|
|
123
|
+
new echarts.graphic.Line({
|
|
124
|
+
shape: { x1, y1, x2: x1 + dx * level, y2: y1 + dy },
|
|
125
|
+
style: { stroke: color, lineWidth: 1 },
|
|
126
|
+
silent: true,
|
|
127
|
+
}),
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Fill between adjacent price rays
|
|
132
|
+
for (let i = 0; i < LEVELS.length - 1; i++) {
|
|
133
|
+
const pr1: [number, number] = [x1 + dx, y1 + dy * LEVELS[i]];
|
|
134
|
+
const pr2: [number, number] = [x1 + dx, y1 + dy * LEVELS[i + 1]];
|
|
135
|
+
|
|
136
|
+
this.graphicGroup.add(
|
|
137
|
+
new echarts.graphic.Polygon({
|
|
138
|
+
shape: { points: [[x1, y1], pr1, pr2] },
|
|
139
|
+
style: { fill: COLORS[(i + 1) % COLORS.length], opacity: 0.06 },
|
|
140
|
+
silent: true,
|
|
141
|
+
}),
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Fill between adjacent time rays
|
|
146
|
+
for (let i = 0; i < LEVELS.length - 1; i++) {
|
|
147
|
+
const tr1: [number, number] = [x1 + dx * LEVELS[i], y1 + dy];
|
|
148
|
+
const tr2: [number, number] = [x1 + dx * LEVELS[i + 1], y1 + dy];
|
|
149
|
+
|
|
150
|
+
this.graphicGroup.add(
|
|
151
|
+
new echarts.graphic.Polygon({
|
|
152
|
+
shape: { points: [[x1, y1], tr1, tr2] },
|
|
153
|
+
style: { fill: COLORS[(i + 1) % COLORS.length], opacity: 0.06 },
|
|
154
|
+
silent: true,
|
|
155
|
+
}),
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Bounding box edges
|
|
160
|
+
this.graphicGroup.add(
|
|
161
|
+
new echarts.graphic.Line({
|
|
162
|
+
shape: { x1: x2, y1, x2, y2 },
|
|
163
|
+
style: { stroke: '#555', lineWidth: 1, lineDash: [3, 3] },
|
|
164
|
+
silent: true,
|
|
165
|
+
}),
|
|
166
|
+
);
|
|
167
|
+
this.graphicGroup.add(
|
|
168
|
+
new echarts.graphic.Line({
|
|
169
|
+
shape: { x1, y1: y2, x2, y2 },
|
|
170
|
+
style: { stroke: '#555', lineWidth: 1, lineDash: [3, 3] },
|
|
171
|
+
silent: true,
|
|
172
|
+
}),
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
// Diagonal
|
|
176
|
+
this.graphicGroup.add(
|
|
177
|
+
new echarts.graphic.Line({
|
|
178
|
+
shape: { x1, y1, x2, y2 },
|
|
179
|
+
style: { stroke: '#999', lineWidth: 1, lineDash: [4, 4] },
|
|
180
|
+
silent: true,
|
|
181
|
+
}),
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private saveDrawing() {
|
|
186
|
+
if (!this.startPoint || !this.endPoint) return;
|
|
187
|
+
|
|
188
|
+
const start = this.context.coordinateConversion.pixelToData({
|
|
189
|
+
x: this.startPoint[0],
|
|
190
|
+
y: this.startPoint[1],
|
|
191
|
+
});
|
|
192
|
+
const end = this.context.coordinateConversion.pixelToData({
|
|
193
|
+
x: this.endPoint[0],
|
|
194
|
+
y: this.endPoint[1],
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
if (start && end) {
|
|
198
|
+
this.context.addDrawing({
|
|
199
|
+
id: `fib-fan-${Date.now()}`,
|
|
200
|
+
type: 'fib_speed_resistance_fan',
|
|
201
|
+
points: [start, end],
|
|
202
|
+
paneIndex: start.paneIndex || 0,
|
|
203
|
+
style: {
|
|
204
|
+
color: '#3b82f6',
|
|
205
|
+
lineWidth: 1,
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { DrawingRenderer, DrawingRenderContext } from '../../types';
|
|
2
|
+
|
|
3
|
+
const LEVELS = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1, 1.272, 1.618, 2, 2.618];
|
|
4
|
+
const COLORS = [
|
|
5
|
+
'#787b86', '#f44336', '#ff9800', '#4caf50', '#2196f3',
|
|
6
|
+
'#00bcd4', '#787b86', '#e91e63', '#9c27b0', '#673ab7', '#3f51b5',
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
export class FibTrendExtensionDrawingRenderer implements DrawingRenderer {
|
|
10
|
+
type = 'fib_trend_extension';
|
|
11
|
+
|
|
12
|
+
render(ctx: DrawingRenderContext): any {
|
|
13
|
+
const { drawing, pixelPoints, isSelected, api } = ctx;
|
|
14
|
+
const color = drawing.style?.color || '#3b82f6';
|
|
15
|
+
if (pixelPoints.length < 3) return;
|
|
16
|
+
|
|
17
|
+
const [x1, y1] = pixelPoints[0]; // Trend start
|
|
18
|
+
const [x2, y2] = pixelPoints[1]; // Trend end
|
|
19
|
+
const [x3, y3] = pixelPoints[2]; // Retracement point
|
|
20
|
+
|
|
21
|
+
const pts = drawing.points;
|
|
22
|
+
const trendMove = pts[1].value - pts[0].value; // Signed price move
|
|
23
|
+
|
|
24
|
+
// Horizontal extent: from min(x1,x2,x3) to max(x1,x2,x3) + extra width
|
|
25
|
+
const minX = Math.min(x1, x2, x3);
|
|
26
|
+
const maxX = Math.max(x1, x2, x3);
|
|
27
|
+
const extraWidth = (maxX - minX) * 0.5;
|
|
28
|
+
const lineLeft = minX;
|
|
29
|
+
const lineRight = maxX + extraWidth;
|
|
30
|
+
|
|
31
|
+
const children: any[] = [];
|
|
32
|
+
|
|
33
|
+
// Compute all extension level Y positions
|
|
34
|
+
const levelData: { level: number; y: number; price: number; color: string }[] = [];
|
|
35
|
+
for (let i = 0; i < LEVELS.length; i++) {
|
|
36
|
+
const level = LEVELS[i];
|
|
37
|
+
const price = pts[2].value + trendMove * level;
|
|
38
|
+
// Convert price to pixel Y using the api
|
|
39
|
+
// We use the retracement point's x as reference for coord lookup
|
|
40
|
+
const pxCoord = api.coord([
|
|
41
|
+
pts[2].timeIndex + (ctx as any).drawing.points[2].timeIndex - pts[2].timeIndex,
|
|
42
|
+
price,
|
|
43
|
+
]);
|
|
44
|
+
// Actually, we can compute Y directly: the relationship between y3 and the
|
|
45
|
+
// trend pixel distance gives us the scale.
|
|
46
|
+
// trendMove maps to (y2 - y1) in pixels (inverted because Y axis is flipped)
|
|
47
|
+
// So for a given level: pixelY = y3 - (y2 - y1) * level
|
|
48
|
+
const py = y3 + (y2 - y1) * level;
|
|
49
|
+
|
|
50
|
+
levelData.push({ level, y: py, price, color: COLORS[i % COLORS.length] });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Fill zones between adjacent levels
|
|
54
|
+
for (let i = 0; i < levelData.length - 1; i++) {
|
|
55
|
+
const curr = levelData[i];
|
|
56
|
+
const next = levelData[i + 1];
|
|
57
|
+
const rectY = Math.min(curr.y, next.y);
|
|
58
|
+
const rectH = Math.abs(next.y - curr.y);
|
|
59
|
+
|
|
60
|
+
children.push({
|
|
61
|
+
type: 'rect',
|
|
62
|
+
name: 'line',
|
|
63
|
+
shape: { x: lineLeft, y: rectY, width: lineRight - lineLeft, height: rectH },
|
|
64
|
+
style: { fill: next.color, opacity: 0.06 },
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Level lines and labels
|
|
69
|
+
for (const ld of levelData) {
|
|
70
|
+
children.push({
|
|
71
|
+
type: 'line',
|
|
72
|
+
shape: { x1: lineLeft, y1: ld.y, x2: lineRight, y2: ld.y },
|
|
73
|
+
style: { stroke: ld.color, lineWidth: 1 },
|
|
74
|
+
silent: true,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
children.push({
|
|
78
|
+
type: 'text',
|
|
79
|
+
style: {
|
|
80
|
+
text: `${ld.level} (${ld.price.toFixed(2)})`,
|
|
81
|
+
x: lineRight + 4,
|
|
82
|
+
y: ld.y - 6,
|
|
83
|
+
fill: ld.color,
|
|
84
|
+
fontSize: 9,
|
|
85
|
+
},
|
|
86
|
+
silent: true,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Trend line (click1 → click2) dashed
|
|
91
|
+
children.push({
|
|
92
|
+
type: 'line',
|
|
93
|
+
name: 'line',
|
|
94
|
+
shape: { x1, y1, x2, y2 },
|
|
95
|
+
style: { stroke: '#2196f3', lineWidth: 1.5, lineDash: [5, 4] },
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Retracement line (click2 → click3) dashed
|
|
99
|
+
children.push({
|
|
100
|
+
type: 'line',
|
|
101
|
+
name: 'line',
|
|
102
|
+
shape: { x1: x2, y1: y2, x2: x3, y2: y3 },
|
|
103
|
+
style: { stroke: '#ff9800', lineWidth: 1.5, lineDash: [5, 4] },
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Control points
|
|
107
|
+
children.push({
|
|
108
|
+
type: 'circle', name: 'point-0',
|
|
109
|
+
shape: { cx: x1, cy: y1, r: 4 },
|
|
110
|
+
style: { fill: '#fff', stroke: color, lineWidth: 1, opacity: isSelected ? 1 : 0 },
|
|
111
|
+
z: 100,
|
|
112
|
+
});
|
|
113
|
+
children.push({
|
|
114
|
+
type: 'circle', name: 'point-1',
|
|
115
|
+
shape: { cx: x2, cy: y2, r: 4 },
|
|
116
|
+
style: { fill: '#fff', stroke: color, lineWidth: 1, opacity: isSelected ? 1 : 0 },
|
|
117
|
+
z: 100,
|
|
118
|
+
});
|
|
119
|
+
children.push({
|
|
120
|
+
type: 'circle', name: 'point-2',
|
|
121
|
+
shape: { cx: x3, cy: y3, r: 4 },
|
|
122
|
+
style: { fill: '#fff', stroke: color, lineWidth: 1, opacity: isSelected ? 1 : 0 },
|
|
123
|
+
z: 100,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Vertex labels
|
|
127
|
+
const labels = ['1', '2', '3'];
|
|
128
|
+
const points = [pixelPoints[0], pixelPoints[1], pixelPoints[2]];
|
|
129
|
+
for (let i = 0; i < 3; i++) {
|
|
130
|
+
const [px, py] = points[i];
|
|
131
|
+
const isHigh = (i === 0 || py <= points[i - 1][1]) && (i === 2 || py <= points[i + 1]?.[1]);
|
|
132
|
+
children.push({
|
|
133
|
+
type: 'text',
|
|
134
|
+
style: { text: labels[i], x: px, y: isHigh ? py - 14 : py + 16, fill: '#e2e8f0', fontSize: 12, fontWeight: 'bold', align: 'center', verticalAlign: 'middle' },
|
|
135
|
+
silent: true,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return { type: 'group', children };
|
|
140
|
+
}
|
|
141
|
+
}
|