@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.
- package/dist/index.d.ts +319 -12
- package/dist/qfchart.min.browser.js +32 -16
- package/dist/qfchart.min.es.js +32 -16
- package/package.json +1 -1
- package/src/QFChart.ts +98 -262
- 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 +41 -35
- package/src/components/SeriesBuilder.ts +10 -10
- package/src/components/TooltipFormatter.ts +1 -1
- package/src/index.ts +17 -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/CypherPatternTool/CypherPatternDrawingRenderer.ts +80 -0
- package/src/plugins/CypherPatternTool/CypherPatternTool.ts +84 -0
- package/src/plugins/CypherPatternTool/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/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/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/TrianglePatternTool/TrianglePatternDrawingRenderer.ts +107 -0
- package/src/plugins/TrianglePatternTool/TrianglePatternTool.ts +98 -0
- package/src/plugins/TrianglePatternTool/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 +37 -11
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { DrawingRenderer, DrawingRenderContext } from '../../types';
|
|
2
|
+
|
|
3
|
+
const LABELS = ['X', 'A', 'B', 'C', 'D'];
|
|
4
|
+
const LEG_COLORS = ['#2196f3', '#ff9800', '#4caf50', '#f44336'];
|
|
5
|
+
const FILL_COLOR_1 = 'rgba(33, 150, 243, 0.08)';
|
|
6
|
+
const FILL_COLOR_2 = 'rgba(244, 67, 54, 0.08)';
|
|
7
|
+
|
|
8
|
+
export class XABCDPatternDrawingRenderer implements DrawingRenderer {
|
|
9
|
+
type = 'xabcd_pattern';
|
|
10
|
+
|
|
11
|
+
render(ctx: DrawingRenderContext): any {
|
|
12
|
+
const { drawing, pixelPoints, isSelected } = ctx;
|
|
13
|
+
const color = drawing.style?.color || '#3b82f6';
|
|
14
|
+
|
|
15
|
+
if (pixelPoints.length < 2) return;
|
|
16
|
+
|
|
17
|
+
const children: any[] = [];
|
|
18
|
+
|
|
19
|
+
// Fill triangles XAB and BCD
|
|
20
|
+
if (pixelPoints.length >= 3) {
|
|
21
|
+
children.push({
|
|
22
|
+
type: 'polygon',
|
|
23
|
+
name: 'line',
|
|
24
|
+
shape: {
|
|
25
|
+
points: pixelPoints.slice(0, 3).map(([x, y]) => [x, y]),
|
|
26
|
+
},
|
|
27
|
+
style: { fill: FILL_COLOR_1, opacity: 1 },
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
if (pixelPoints.length >= 5) {
|
|
31
|
+
children.push({
|
|
32
|
+
type: 'polygon',
|
|
33
|
+
name: 'line',
|
|
34
|
+
shape: {
|
|
35
|
+
points: pixelPoints.slice(2, 5).map(([x, y]) => [x, y]),
|
|
36
|
+
},
|
|
37
|
+
style: { fill: FILL_COLOR_2, opacity: 1 },
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Leg lines X→A→B→C→D
|
|
42
|
+
for (let i = 0; i < pixelPoints.length - 1; i++) {
|
|
43
|
+
const [x1, y1] = pixelPoints[i];
|
|
44
|
+
const [x2, y2] = pixelPoints[i + 1];
|
|
45
|
+
const legColor = LEG_COLORS[i % LEG_COLORS.length];
|
|
46
|
+
|
|
47
|
+
children.push({
|
|
48
|
+
type: 'line',
|
|
49
|
+
name: 'line',
|
|
50
|
+
shape: { x1, y1, x2, y2 },
|
|
51
|
+
style: { stroke: legColor, lineWidth: drawing.style?.lineWidth || 2 },
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Dashed connector lines: X→B, A→C, B→D (retrace references)
|
|
56
|
+
const connectors: [number, number][] = [[0, 2], [1, 3], [2, 4]];
|
|
57
|
+
for (const [from, to] of connectors) {
|
|
58
|
+
if (from < pixelPoints.length && to < pixelPoints.length) {
|
|
59
|
+
const [x1, y1] = pixelPoints[from];
|
|
60
|
+
const [x2, y2] = pixelPoints[to];
|
|
61
|
+
children.push({
|
|
62
|
+
type: 'line',
|
|
63
|
+
shape: { x1, y1, x2, y2 },
|
|
64
|
+
style: { stroke: '#555', lineWidth: 1, lineDash: [4, 4] },
|
|
65
|
+
silent: true,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Fibonacci ratio labels on legs
|
|
71
|
+
if (drawing.points.length >= 3) {
|
|
72
|
+
// AB/XA ratio
|
|
73
|
+
const xa = Math.abs(drawing.points[1].value - drawing.points[0].value);
|
|
74
|
+
const ab = Math.abs(drawing.points[2].value - drawing.points[1].value);
|
|
75
|
+
if (xa !== 0) {
|
|
76
|
+
const ratio = (ab / xa).toFixed(3);
|
|
77
|
+
const mx = (pixelPoints[1][0] + pixelPoints[2][0]) / 2;
|
|
78
|
+
const my = (pixelPoints[1][1] + pixelPoints[2][1]) / 2;
|
|
79
|
+
children.push({
|
|
80
|
+
type: 'text',
|
|
81
|
+
style: { text: ratio, x: mx + 8, y: my, fill: '#ff9800', fontSize: 10 },
|
|
82
|
+
silent: true,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (drawing.points.length >= 4) {
|
|
87
|
+
// BC/AB ratio
|
|
88
|
+
const ab = Math.abs(drawing.points[2].value - drawing.points[1].value);
|
|
89
|
+
const bc = Math.abs(drawing.points[3].value - drawing.points[2].value);
|
|
90
|
+
if (ab !== 0) {
|
|
91
|
+
const ratio = (bc / ab).toFixed(3);
|
|
92
|
+
const mx = (pixelPoints[2][0] + pixelPoints[3][0]) / 2;
|
|
93
|
+
const my = (pixelPoints[2][1] + pixelPoints[3][1]) / 2;
|
|
94
|
+
children.push({
|
|
95
|
+
type: 'text',
|
|
96
|
+
style: { text: ratio, x: mx + 8, y: my, fill: '#4caf50', fontSize: 10 },
|
|
97
|
+
silent: true,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (drawing.points.length >= 5) {
|
|
102
|
+
// CD/BC ratio
|
|
103
|
+
const bc = Math.abs(drawing.points[3].value - drawing.points[2].value);
|
|
104
|
+
const cd = Math.abs(drawing.points[4].value - drawing.points[3].value);
|
|
105
|
+
if (bc !== 0) {
|
|
106
|
+
const ratio = (cd / bc).toFixed(3);
|
|
107
|
+
const mx = (pixelPoints[3][0] + pixelPoints[4][0]) / 2;
|
|
108
|
+
const my = (pixelPoints[3][1] + pixelPoints[4][1]) / 2;
|
|
109
|
+
children.push({
|
|
110
|
+
type: 'text',
|
|
111
|
+
style: { text: ratio, x: mx + 8, y: my, fill: '#f44336', fontSize: 10 },
|
|
112
|
+
silent: true,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// XA/AD ratio (overall retracement)
|
|
117
|
+
const xa = Math.abs(drawing.points[1].value - drawing.points[0].value);
|
|
118
|
+
const ad = Math.abs(drawing.points[4].value - drawing.points[1].value);
|
|
119
|
+
if (xa !== 0) {
|
|
120
|
+
const ratio = (ad / xa).toFixed(3);
|
|
121
|
+
// Place near D point
|
|
122
|
+
const [dx, dy] = pixelPoints[4];
|
|
123
|
+
children.push({
|
|
124
|
+
type: 'text',
|
|
125
|
+
style: { text: `AD/XA: ${ratio}`, x: dx + 10, y: dy + 14, fill: '#aaa', fontSize: 9 },
|
|
126
|
+
silent: true,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Vertex labels (X, A, B, C, D)
|
|
132
|
+
for (let i = 0; i < pixelPoints.length && i < LABELS.length; i++) {
|
|
133
|
+
const [px, py] = pixelPoints[i];
|
|
134
|
+
// Place label above or below the point based on neighbors
|
|
135
|
+
const isLocalHigh =
|
|
136
|
+
(i === 0 || py <= pixelPoints[i - 1][1]) &&
|
|
137
|
+
(i === pixelPoints.length - 1 || py <= pixelPoints[i + 1]?.[1]);
|
|
138
|
+
const labelY = isLocalHigh ? py - 14 : py + 16;
|
|
139
|
+
|
|
140
|
+
children.push({
|
|
141
|
+
type: 'text',
|
|
142
|
+
style: {
|
|
143
|
+
text: LABELS[i],
|
|
144
|
+
x: px,
|
|
145
|
+
y: labelY,
|
|
146
|
+
fill: '#e2e8f0',
|
|
147
|
+
fontSize: 12,
|
|
148
|
+
fontWeight: 'bold',
|
|
149
|
+
align: 'center',
|
|
150
|
+
verticalAlign: 'middle',
|
|
151
|
+
},
|
|
152
|
+
silent: true,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Control points
|
|
157
|
+
for (let i = 0; i < pixelPoints.length; i++) {
|
|
158
|
+
const [px, py] = pixelPoints[i];
|
|
159
|
+
children.push({
|
|
160
|
+
type: 'circle',
|
|
161
|
+
name: `point-${i}`,
|
|
162
|
+
shape: { cx: px, cy: py, r: 4 },
|
|
163
|
+
style: {
|
|
164
|
+
fill: '#fff',
|
|
165
|
+
stroke: color,
|
|
166
|
+
lineWidth: 1,
|
|
167
|
+
opacity: isSelected ? 1 : 0,
|
|
168
|
+
},
|
|
169
|
+
z: 100,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
type: 'group',
|
|
175
|
+
children,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import * as echarts from 'echarts';
|
|
2
|
+
import { AbstractPlugin } from '../../components/AbstractPlugin';
|
|
3
|
+
import { XABCDPatternDrawingRenderer } from './XABCDPatternDrawingRenderer';
|
|
4
|
+
|
|
5
|
+
const LABELS = ['X', 'A', 'B', 'C', 'D'];
|
|
6
|
+
const LEG_COLORS = ['#2196f3', '#ff9800', '#4caf50', '#f44336'];
|
|
7
|
+
const TOTAL_POINTS = 5;
|
|
8
|
+
|
|
9
|
+
export class XABCDPatternTool extends AbstractPlugin {
|
|
10
|
+
private points: number[][] = [];
|
|
11
|
+
private state: 'idle' | 'drawing' | 'finished' = 'idle';
|
|
12
|
+
private graphicGroup: any = null;
|
|
13
|
+
|
|
14
|
+
constructor(options: { name?: string; icon?: string } = {}) {
|
|
15
|
+
super({
|
|
16
|
+
id: 'xabcd-pattern-tool',
|
|
17
|
+
name: options.name || 'XABCD Pattern',
|
|
18
|
+
icon:
|
|
19
|
+
options.icon ||
|
|
20
|
+
`<svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#e3e3e3" stroke-width="1.5"><polyline points="2,18 6,6 11,14 16,4 21,16"/><circle cx="2" cy="18" r="1.5" fill="#e3e3e3"/><circle cx="6" cy="6" r="1.5" fill="#e3e3e3"/><circle cx="11" cy="14" r="1.5" fill="#e3e3e3"/><circle cx="16" cy="4" r="1.5" fill="#e3e3e3"/><circle cx="21" cy="16" r="1.5" fill="#e3e3e3"/></svg>`,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
protected onInit(): void {
|
|
25
|
+
this.context.registerDrawingRenderer(new XABCDPatternDrawingRenderer());
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
protected onActivate(): void {
|
|
29
|
+
this.state = 'idle';
|
|
30
|
+
this.points = [];
|
|
31
|
+
this.context.getChart().getZr().setCursorStyle('crosshair');
|
|
32
|
+
this.bindEvents();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
protected onDeactivate(): void {
|
|
36
|
+
this.state = 'idle';
|
|
37
|
+
this.points = [];
|
|
38
|
+
this.removeGraphic();
|
|
39
|
+
this.unbindEvents();
|
|
40
|
+
this.context.getChart().getZr().setCursorStyle('default');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private bindEvents() {
|
|
44
|
+
const zr = this.context.getChart().getZr();
|
|
45
|
+
zr.on('click', this.onClick);
|
|
46
|
+
zr.on('mousemove', this.onMouseMove);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private unbindEvents() {
|
|
50
|
+
const zr = this.context.getChart().getZr();
|
|
51
|
+
zr.off('click', this.onClick);
|
|
52
|
+
zr.off('mousemove', this.onMouseMove);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private onClick = (params: any) => {
|
|
56
|
+
const pt = this.getPoint(params);
|
|
57
|
+
|
|
58
|
+
if (this.state === 'idle') {
|
|
59
|
+
this.state = 'drawing';
|
|
60
|
+
this.points = [pt, [...pt]]; // First point + cursor preview
|
|
61
|
+
this.initGraphic();
|
|
62
|
+
this.updateGraphic();
|
|
63
|
+
} else if (this.state === 'drawing') {
|
|
64
|
+
// Replace the preview point with the confirmed click
|
|
65
|
+
this.points[this.points.length - 1] = pt;
|
|
66
|
+
|
|
67
|
+
if (this.points.length >= TOTAL_POINTS) {
|
|
68
|
+
// All 5 points placed
|
|
69
|
+
this.state = 'finished';
|
|
70
|
+
this.updateGraphic();
|
|
71
|
+
this.saveDrawing();
|
|
72
|
+
this.removeGraphic();
|
|
73
|
+
this.context.disableTools();
|
|
74
|
+
} else {
|
|
75
|
+
// Add a new preview point for the next position
|
|
76
|
+
this.points.push([...pt]);
|
|
77
|
+
this.updateGraphic();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
private onMouseMove = (params: any) => {
|
|
83
|
+
if (this.state !== 'drawing' || this.points.length < 2) return;
|
|
84
|
+
// Update the last (preview) point
|
|
85
|
+
this.points[this.points.length - 1] = this.getPoint(params);
|
|
86
|
+
this.updateGraphic();
|
|
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) return;
|
|
103
|
+
this.graphicGroup.removeAll();
|
|
104
|
+
|
|
105
|
+
const pts = this.points;
|
|
106
|
+
|
|
107
|
+
// Fill triangles
|
|
108
|
+
if (pts.length >= 3) {
|
|
109
|
+
this.graphicGroup.add(
|
|
110
|
+
new echarts.graphic.Polygon({
|
|
111
|
+
shape: { points: pts.slice(0, 3) },
|
|
112
|
+
style: { fill: 'rgba(33, 150, 243, 0.08)' },
|
|
113
|
+
silent: true,
|
|
114
|
+
}),
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
if (pts.length >= 5) {
|
|
118
|
+
this.graphicGroup.add(
|
|
119
|
+
new echarts.graphic.Polygon({
|
|
120
|
+
shape: { points: pts.slice(2, 5) },
|
|
121
|
+
style: { fill: 'rgba(244, 67, 54, 0.08)' },
|
|
122
|
+
silent: true,
|
|
123
|
+
}),
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Leg lines
|
|
128
|
+
for (let i = 0; i < pts.length - 1; i++) {
|
|
129
|
+
const [x1, y1] = pts[i];
|
|
130
|
+
const [x2, y2] = pts[i + 1];
|
|
131
|
+
this.graphicGroup.add(
|
|
132
|
+
new echarts.graphic.Line({
|
|
133
|
+
shape: { x1, y1, x2, y2 },
|
|
134
|
+
style: { stroke: LEG_COLORS[i % LEG_COLORS.length], lineWidth: 2 },
|
|
135
|
+
silent: true,
|
|
136
|
+
}),
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Dashed connectors X→B, A→C, B→D
|
|
141
|
+
const connectors: [number, number][] = [[0, 2], [1, 3], [2, 4]];
|
|
142
|
+
for (const [from, to] of connectors) {
|
|
143
|
+
if (from < pts.length && to < pts.length) {
|
|
144
|
+
const [x1, y1] = pts[from];
|
|
145
|
+
const [x2, y2] = pts[to];
|
|
146
|
+
this.graphicGroup.add(
|
|
147
|
+
new echarts.graphic.Line({
|
|
148
|
+
shape: { x1, y1, x2, y2 },
|
|
149
|
+
style: { stroke: '#555', lineWidth: 1, lineDash: [4, 4] },
|
|
150
|
+
silent: true,
|
|
151
|
+
}),
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Vertex labels
|
|
157
|
+
for (let i = 0; i < pts.length && i < LABELS.length; i++) {
|
|
158
|
+
const [px, py] = pts[i];
|
|
159
|
+
const isLocalHigh =
|
|
160
|
+
(i === 0 || py <= pts[i - 1][1]) &&
|
|
161
|
+
(i === pts.length - 1 || py <= pts[i + 1]?.[1]);
|
|
162
|
+
const labelY = isLocalHigh ? py - 14 : py + 16;
|
|
163
|
+
|
|
164
|
+
this.graphicGroup.add(
|
|
165
|
+
new echarts.graphic.Text({
|
|
166
|
+
style: {
|
|
167
|
+
text: LABELS[i],
|
|
168
|
+
x: px,
|
|
169
|
+
y: labelY,
|
|
170
|
+
fill: '#e2e8f0',
|
|
171
|
+
fontSize: 12,
|
|
172
|
+
fontWeight: 'bold',
|
|
173
|
+
align: 'center',
|
|
174
|
+
verticalAlign: 'middle',
|
|
175
|
+
},
|
|
176
|
+
silent: true,
|
|
177
|
+
}),
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Point circles
|
|
182
|
+
for (let i = 0; i < pts.length; i++) {
|
|
183
|
+
const [px, py] = pts[i];
|
|
184
|
+
this.graphicGroup.add(
|
|
185
|
+
new echarts.graphic.Circle({
|
|
186
|
+
shape: { cx: px, cy: py, r: 4 },
|
|
187
|
+
style: { fill: '#fff', stroke: '#3b82f6', lineWidth: 1.5 },
|
|
188
|
+
z: 101,
|
|
189
|
+
silent: true,
|
|
190
|
+
}),
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private saveDrawing() {
|
|
196
|
+
const dataPoints = this.points.map((pt) =>
|
|
197
|
+
this.context.coordinateConversion.pixelToData({ x: pt[0], y: pt[1] }),
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
if (dataPoints.every((p) => p !== null)) {
|
|
201
|
+
this.context.addDrawing({
|
|
202
|
+
id: `xabcd-${Date.now()}`,
|
|
203
|
+
type: 'xabcd_pattern',
|
|
204
|
+
points: dataPoints as any[],
|
|
205
|
+
paneIndex: dataPoints[0]!.paneIndex || 0,
|
|
206
|
+
style: {
|
|
207
|
+
color: '#3b82f6',
|
|
208
|
+
lineWidth: 2,
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -106,6 +106,7 @@ export interface QFChartOptions {
|
|
|
106
106
|
};
|
|
107
107
|
dataZoom?: {
|
|
108
108
|
visible?: boolean;
|
|
109
|
+
pannable?: boolean; // Keep pan/drag when visible=false (default true)
|
|
109
110
|
position?: 'top' | 'bottom';
|
|
110
111
|
height?: number; // height in %, default 6
|
|
111
112
|
start?: number; // 0-100, default 50
|
|
@@ -117,17 +118,17 @@ export interface QFChartOptions {
|
|
|
117
118
|
triggerOn?: 'mousemove' | 'click' | 'none'; // When to show tooltip/crosshair, default 'mousemove'
|
|
118
119
|
};
|
|
119
120
|
grid?: {
|
|
120
|
-
show?: boolean;
|
|
121
|
-
lineColor?: string;
|
|
122
|
-
lineOpacity?: number;
|
|
123
|
-
borderColor?: string;
|
|
124
|
-
borderShow?: boolean;
|
|
121
|
+
show?: boolean; // Show/hide split lines (default true)
|
|
122
|
+
lineColor?: string; // Split line color (default '#334155')
|
|
123
|
+
lineOpacity?: number; // Split line opacity (default 0.5 main, 0.3 indicator panes)
|
|
124
|
+
borderColor?: string; // Axis line color (default '#334155')
|
|
125
|
+
borderShow?: boolean; // Show/hide axis border lines (default true)
|
|
125
126
|
};
|
|
126
127
|
layout?: {
|
|
127
|
-
mainPaneHeight?: string;
|
|
128
|
-
gap?: number;
|
|
129
|
-
left?: string;
|
|
130
|
-
right?: string;
|
|
128
|
+
mainPaneHeight?: string; // e.g. "60%"
|
|
129
|
+
gap?: number; // Gap between panes in % (default ~5)
|
|
130
|
+
left?: string; // Grid left margin (default '10%')
|
|
131
|
+
right?: string; // Grid right margin (default '10%')
|
|
131
132
|
};
|
|
132
133
|
watermark?: boolean; // Default true
|
|
133
134
|
}
|
|
@@ -176,14 +177,20 @@ export interface ChartContext {
|
|
|
176
177
|
// Interaction Locking
|
|
177
178
|
lockChart(): void;
|
|
178
179
|
unlockChart(): void;
|
|
180
|
+
|
|
181
|
+
// Drawing Renderer Registration
|
|
182
|
+
registerDrawingRenderer(renderer: DrawingRenderer): void;
|
|
183
|
+
|
|
184
|
+
// Snap to nearest candle OHLC value
|
|
185
|
+
snapToCandle(point: Coordinate): Coordinate;
|
|
179
186
|
}
|
|
180
187
|
|
|
181
|
-
export type DrawingType =
|
|
188
|
+
export type DrawingType = string;
|
|
182
189
|
|
|
183
190
|
export interface DrawingElement {
|
|
184
191
|
id: string;
|
|
185
192
|
type: DrawingType;
|
|
186
|
-
points: DataCoordinate[];
|
|
193
|
+
points: DataCoordinate[];
|
|
187
194
|
paneIndex?: number; // Pane where this drawing belongs (default 0)
|
|
188
195
|
style?: {
|
|
189
196
|
color?: string;
|
|
@@ -191,6 +198,25 @@ export interface DrawingElement {
|
|
|
191
198
|
};
|
|
192
199
|
}
|
|
193
200
|
|
|
201
|
+
// Drawing Renderer System
|
|
202
|
+
|
|
203
|
+
export interface DrawingRenderContext {
|
|
204
|
+
drawing: DrawingElement;
|
|
205
|
+
/** Pixel coords for each point, in the same order as drawing.points */
|
|
206
|
+
pixelPoints: [number, number][];
|
|
207
|
+
/** Whether this drawing is currently selected */
|
|
208
|
+
isSelected: boolean;
|
|
209
|
+
/** The ECharts custom series api object */
|
|
210
|
+
api: any;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export interface DrawingRenderer {
|
|
214
|
+
/** The drawing type this renderer handles */
|
|
215
|
+
type: string;
|
|
216
|
+
/** Return an ECharts custom series renderItem group element */
|
|
217
|
+
render(ctx: DrawingRenderContext): any;
|
|
218
|
+
}
|
|
219
|
+
|
|
194
220
|
export interface PluginConfig {
|
|
195
221
|
id: string;
|
|
196
222
|
name?: string;
|