apexify.js 5.0.4 → 5.1.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/CHANGELOG.md +337 -137
- package/README.md +65 -12
- package/dist/cjs/Canvas/ApexPainter.d.ts +272 -0
- package/dist/cjs/Canvas/ApexPainter.d.ts.map +1 -1
- package/dist/cjs/Canvas/ApexPainter.js +2275 -125
- package/dist/cjs/Canvas/ApexPainter.js.map +1 -1
- package/dist/cjs/Canvas/utils/Custom/advancedLines.d.ts +4 -4
- package/dist/cjs/Canvas/utils/Custom/advancedLines.d.ts.map +1 -1
- package/dist/cjs/Canvas/utils/Custom/advancedLines.js +63 -21
- package/dist/cjs/Canvas/utils/Custom/advancedLines.js.map +1 -1
- package/dist/cjs/Canvas/utils/Custom/customLines.d.ts.map +1 -1
- package/dist/cjs/Canvas/utils/Custom/customLines.js +3 -0
- package/dist/cjs/Canvas/utils/Custom/customLines.js.map +1 -1
- package/dist/cjs/Canvas/utils/types.d.ts +5 -1
- package/dist/cjs/Canvas/utils/types.d.ts.map +1 -1
- package/dist/cjs/Canvas/utils/types.js.map +1 -1
- package/dist/esm/Canvas/ApexPainter.d.ts +272 -0
- package/dist/esm/Canvas/ApexPainter.d.ts.map +1 -1
- package/dist/esm/Canvas/ApexPainter.js +2275 -125
- package/dist/esm/Canvas/ApexPainter.js.map +1 -1
- package/dist/esm/Canvas/utils/Custom/advancedLines.d.ts +4 -4
- package/dist/esm/Canvas/utils/Custom/advancedLines.d.ts.map +1 -1
- package/dist/esm/Canvas/utils/Custom/advancedLines.js +63 -21
- package/dist/esm/Canvas/utils/Custom/advancedLines.js.map +1 -1
- package/dist/esm/Canvas/utils/Custom/customLines.d.ts.map +1 -1
- package/dist/esm/Canvas/utils/Custom/customLines.js +3 -0
- package/dist/esm/Canvas/utils/Custom/customLines.js.map +1 -1
- package/dist/esm/Canvas/utils/types.d.ts +5 -1
- package/dist/esm/Canvas/utils/types.d.ts.map +1 -1
- package/dist/esm/Canvas/utils/types.js.map +1 -1
- package/lib/Canvas/ApexPainter.ts +2973 -136
- package/lib/Canvas/utils/Custom/advancedLines.ts +387 -335
- package/lib/Canvas/utils/Custom/customLines.ts +206 -202
- package/lib/Canvas/utils/types.ts +983 -979
- package/package.json +198 -200
|
@@ -1,335 +1,387 @@
|
|
|
1
|
-
import { SKRSContext2D, Image, loadImage } from '@napi-rs/canvas';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import fs from 'fs';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Draws an arrow at the end of a line
|
|
7
|
-
* @param ctx - Canvas 2D context
|
|
8
|
-
* @param x - Arrow tip X
|
|
9
|
-
* @param y - Arrow tip Y
|
|
10
|
-
* @param angle - Arrow angle in radians
|
|
11
|
-
* @param size - Arrow size
|
|
12
|
-
* @param style - Arrow style
|
|
13
|
-
* @param color - Arrow color
|
|
14
|
-
*/
|
|
15
|
-
export function drawArrow(
|
|
16
|
-
ctx: SKRSContext2D,
|
|
17
|
-
x: number,
|
|
18
|
-
y: number,
|
|
19
|
-
angle: number,
|
|
20
|
-
size: number,
|
|
21
|
-
style: 'filled' | 'outline',
|
|
22
|
-
color: string
|
|
23
|
-
): void {
|
|
24
|
-
ctx.save();
|
|
25
|
-
ctx.translate(x, y);
|
|
26
|
-
ctx.rotate(angle);
|
|
27
|
-
|
|
28
|
-
const arrowHeadLength = size;
|
|
29
|
-
const arrowHeadWidth = size * 0.6;
|
|
30
|
-
|
|
31
|
-
ctx.beginPath();
|
|
32
|
-
ctx.moveTo(0, 0);
|
|
33
|
-
ctx.lineTo(-arrowHeadLength, -arrowHeadWidth);
|
|
34
|
-
ctx.lineTo(-arrowHeadLength * 0.7, 0);
|
|
35
|
-
ctx.lineTo(-arrowHeadLength, arrowHeadWidth);
|
|
36
|
-
ctx.closePath();
|
|
37
|
-
|
|
38
|
-
if (style === 'filled') {
|
|
39
|
-
ctx.fillStyle = color;
|
|
40
|
-
ctx.fill();
|
|
41
|
-
} else {
|
|
42
|
-
ctx.strokeStyle = color;
|
|
43
|
-
ctx.lineWidth = 2;
|
|
44
|
-
ctx.stroke();
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
ctx.restore();
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Draws a marker on a path
|
|
52
|
-
* @param ctx - Canvas 2D context
|
|
53
|
-
* @param x - Marker X position
|
|
54
|
-
* @param y - Marker Y position
|
|
55
|
-
* @param shape - Marker shape
|
|
56
|
-
* @param size - Marker size
|
|
57
|
-
* @param color - Marker color
|
|
58
|
-
*/
|
|
59
|
-
export function drawMarker(
|
|
60
|
-
ctx: SKRSContext2D,
|
|
61
|
-
x: number,
|
|
62
|
-
y: number,
|
|
63
|
-
shape: 'circle' | 'square' | 'diamond' | 'arrow',
|
|
64
|
-
size: number,
|
|
65
|
-
color: string
|
|
66
|
-
): void {
|
|
67
|
-
ctx.save();
|
|
68
|
-
ctx.fillStyle = color;
|
|
69
|
-
ctx.translate(x, y);
|
|
70
|
-
|
|
71
|
-
switch (shape) {
|
|
72
|
-
case 'circle':
|
|
73
|
-
ctx.beginPath();
|
|
74
|
-
ctx.arc(0, 0, size / 2, 0, Math.PI * 2);
|
|
75
|
-
ctx.fill();
|
|
76
|
-
break;
|
|
77
|
-
|
|
78
|
-
case 'square':
|
|
79
|
-
ctx.fillRect(-size / 2, -size / 2, size, size);
|
|
80
|
-
break;
|
|
81
|
-
|
|
82
|
-
case 'diamond':
|
|
83
|
-
ctx.beginPath();
|
|
84
|
-
ctx.moveTo(0, -size / 2);
|
|
85
|
-
ctx.lineTo(size / 2, 0);
|
|
86
|
-
ctx.lineTo(0, size / 2);
|
|
87
|
-
ctx.lineTo(-size / 2, 0);
|
|
88
|
-
ctx.closePath();
|
|
89
|
-
ctx.fill();
|
|
90
|
-
break;
|
|
91
|
-
|
|
92
|
-
case 'arrow':
|
|
93
|
-
ctx.beginPath();
|
|
94
|
-
ctx.moveTo(0, -size / 2);
|
|
95
|
-
ctx.lineTo(size / 2, size / 2);
|
|
96
|
-
ctx.lineTo(-size / 2, size / 2);
|
|
97
|
-
ctx.closePath();
|
|
98
|
-
ctx.fill();
|
|
99
|
-
break;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
ctx.restore();
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Creates a smooth path from points
|
|
107
|
-
* @param ctx - Canvas 2D context
|
|
108
|
-
* @param points - Array of points
|
|
109
|
-
* @param tension - Smoothness (0-1)
|
|
110
|
-
* @param closed - Whether path is closed
|
|
111
|
-
*/
|
|
112
|
-
export function createSmoothPath(
|
|
113
|
-
ctx: SKRSContext2D,
|
|
114
|
-
points: Array<{ x: number; y: number }>,
|
|
115
|
-
tension: number = 0.5,
|
|
116
|
-
closed: boolean = false
|
|
117
|
-
): void {
|
|
118
|
-
if (points.length < 2) return;
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
ctx.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
*
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
)
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
)
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
1
|
+
import { SKRSContext2D, Image, loadImage } from '@napi-rs/canvas';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Draws an arrow at the end of a line
|
|
7
|
+
* @param ctx - Canvas 2D context
|
|
8
|
+
* @param x - Arrow tip X
|
|
9
|
+
* @param y - Arrow tip Y
|
|
10
|
+
* @param angle - Arrow angle in radians
|
|
11
|
+
* @param size - Arrow size
|
|
12
|
+
* @param style - Arrow style
|
|
13
|
+
* @param color - Arrow color
|
|
14
|
+
*/
|
|
15
|
+
export function drawArrow(
|
|
16
|
+
ctx: SKRSContext2D,
|
|
17
|
+
x: number,
|
|
18
|
+
y: number,
|
|
19
|
+
angle: number,
|
|
20
|
+
size: number,
|
|
21
|
+
style: 'filled' | 'outline',
|
|
22
|
+
color: string
|
|
23
|
+
): void {
|
|
24
|
+
ctx.save();
|
|
25
|
+
ctx.translate(x, y);
|
|
26
|
+
ctx.rotate(angle);
|
|
27
|
+
|
|
28
|
+
const arrowHeadLength = size;
|
|
29
|
+
const arrowHeadWidth = size * 0.6;
|
|
30
|
+
|
|
31
|
+
ctx.beginPath();
|
|
32
|
+
ctx.moveTo(0, 0);
|
|
33
|
+
ctx.lineTo(-arrowHeadLength, -arrowHeadWidth);
|
|
34
|
+
ctx.lineTo(-arrowHeadLength * 0.7, 0);
|
|
35
|
+
ctx.lineTo(-arrowHeadLength, arrowHeadWidth);
|
|
36
|
+
ctx.closePath();
|
|
37
|
+
|
|
38
|
+
if (style === 'filled') {
|
|
39
|
+
ctx.fillStyle = color;
|
|
40
|
+
ctx.fill();
|
|
41
|
+
} else {
|
|
42
|
+
ctx.strokeStyle = color;
|
|
43
|
+
ctx.lineWidth = 2;
|
|
44
|
+
ctx.stroke();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
ctx.restore();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Draws a marker on a path
|
|
52
|
+
* @param ctx - Canvas 2D context
|
|
53
|
+
* @param x - Marker X position
|
|
54
|
+
* @param y - Marker Y position
|
|
55
|
+
* @param shape - Marker shape
|
|
56
|
+
* @param size - Marker size
|
|
57
|
+
* @param color - Marker color
|
|
58
|
+
*/
|
|
59
|
+
export function drawMarker(
|
|
60
|
+
ctx: SKRSContext2D,
|
|
61
|
+
x: number,
|
|
62
|
+
y: number,
|
|
63
|
+
shape: 'circle' | 'square' | 'diamond' | 'arrow',
|
|
64
|
+
size: number,
|
|
65
|
+
color: string
|
|
66
|
+
): void {
|
|
67
|
+
ctx.save();
|
|
68
|
+
ctx.fillStyle = color;
|
|
69
|
+
ctx.translate(x, y);
|
|
70
|
+
|
|
71
|
+
switch (shape) {
|
|
72
|
+
case 'circle':
|
|
73
|
+
ctx.beginPath();
|
|
74
|
+
ctx.arc(0, 0, size / 2, 0, Math.PI * 2);
|
|
75
|
+
ctx.fill();
|
|
76
|
+
break;
|
|
77
|
+
|
|
78
|
+
case 'square':
|
|
79
|
+
ctx.fillRect(-size / 2, -size / 2, size, size);
|
|
80
|
+
break;
|
|
81
|
+
|
|
82
|
+
case 'diamond':
|
|
83
|
+
ctx.beginPath();
|
|
84
|
+
ctx.moveTo(0, -size / 2);
|
|
85
|
+
ctx.lineTo(size / 2, 0);
|
|
86
|
+
ctx.lineTo(0, size / 2);
|
|
87
|
+
ctx.lineTo(-size / 2, 0);
|
|
88
|
+
ctx.closePath();
|
|
89
|
+
ctx.fill();
|
|
90
|
+
break;
|
|
91
|
+
|
|
92
|
+
case 'arrow':
|
|
93
|
+
ctx.beginPath();
|
|
94
|
+
ctx.moveTo(0, -size / 2);
|
|
95
|
+
ctx.lineTo(size / 2, size / 2);
|
|
96
|
+
ctx.lineTo(-size / 2, size / 2);
|
|
97
|
+
ctx.closePath();
|
|
98
|
+
ctx.fill();
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
ctx.restore();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Creates a smooth path from points using Cardinal Spline (graph-like smoothness)
|
|
107
|
+
* @param ctx - Canvas 2D context
|
|
108
|
+
* @param points - Array of points
|
|
109
|
+
* @param tension - Smoothness (0-1, default 0.5, lower = smoother)
|
|
110
|
+
* @param closed - Whether path is closed
|
|
111
|
+
*/
|
|
112
|
+
export function createSmoothPath(
|
|
113
|
+
ctx: SKRSContext2D,
|
|
114
|
+
points: Array<{ x: number; y: number }>,
|
|
115
|
+
tension: number = 0.5,
|
|
116
|
+
closed: boolean = false
|
|
117
|
+
): void {
|
|
118
|
+
if (points.length < 2) return;
|
|
119
|
+
|
|
120
|
+
// Enable anti-aliasing for smoother rendering
|
|
121
|
+
ctx.imageSmoothingEnabled = true;
|
|
122
|
+
ctx.imageSmoothingQuality = 'high';
|
|
123
|
+
|
|
124
|
+
ctx.beginPath();
|
|
125
|
+
ctx.moveTo(points[0].x, points[0].y);
|
|
126
|
+
|
|
127
|
+
if (points.length === 2) {
|
|
128
|
+
ctx.lineTo(points[1].x, points[1].y);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Cardinal spline - produces graph-like smooth curves
|
|
133
|
+
// Ultra-high resolution for maximum smoothness (like professional graphing libraries)
|
|
134
|
+
const segmentsPerCurve = 50; // Increased to 50 for ultra-smooth curves
|
|
135
|
+
|
|
136
|
+
for (let i = 0; i < points.length - 1; i++) {
|
|
137
|
+
const p0 = i > 0 ? points[i - 1] : (closed ? points[points.length - 1] : points[i]);
|
|
138
|
+
const p1 = points[i];
|
|
139
|
+
const p2 = points[i + 1];
|
|
140
|
+
const p3 = i < points.length - 2 ? points[i + 2] : (closed ? points[0] : p2);
|
|
141
|
+
|
|
142
|
+
// Cardinal spline control points (optimized for maximum smoothness)
|
|
143
|
+
const t = (1 - tension) * 0.5; // Convert tension to cardinal spline parameter
|
|
144
|
+
|
|
145
|
+
const cp1x = p1.x + t * (p2.x - p0.x);
|
|
146
|
+
const cp1y = p1.y + t * (p2.y - p0.y);
|
|
147
|
+
const cp2x = p2.x - t * (p3.x - p1.x);
|
|
148
|
+
const cp2y = p2.y - t * (p3.y - p1.y);
|
|
149
|
+
|
|
150
|
+
// Draw ultra-smooth curve with many segments for graph-like quality
|
|
151
|
+
for (let s = 0; s <= segmentsPerCurve; s++) {
|
|
152
|
+
const t = s / segmentsPerCurve;
|
|
153
|
+
const point = cubicBezier(p1, { x: cp1x, y: cp1y }, { x: cp2x, y: cp2y }, p2, t);
|
|
154
|
+
|
|
155
|
+
if (s === 0 && i === 0) {
|
|
156
|
+
ctx.moveTo(point.x, point.y);
|
|
157
|
+
} else {
|
|
158
|
+
ctx.lineTo(point.x, point.y);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (closed) {
|
|
164
|
+
ctx.closePath();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Cubic Bezier interpolation helper
|
|
170
|
+
*/
|
|
171
|
+
function cubicBezier(
|
|
172
|
+
p0: { x: number; y: number },
|
|
173
|
+
p1: { x: number; y: number },
|
|
174
|
+
p2: { x: number; y: number },
|
|
175
|
+
p3: { x: number; y: number },
|
|
176
|
+
t: number
|
|
177
|
+
): { x: number; y: number } {
|
|
178
|
+
const mt = 1 - t;
|
|
179
|
+
const mt2 = mt * mt;
|
|
180
|
+
const mt3 = mt2 * mt;
|
|
181
|
+
const t2 = t * t;
|
|
182
|
+
const t3 = t2 * t;
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
x: mt3 * p0.x + 3 * mt2 * t * p1.x + 3 * mt * t2 * p2.x + t3 * p3.x,
|
|
186
|
+
y: mt3 * p0.y + 3 * mt2 * t * p1.y + 3 * mt * t2 * p2.y + t3 * p3.y
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Creates a Catmull-Rom spline path (ultra-smooth, passes through all points - best for graphs)
|
|
192
|
+
* @param ctx - Canvas 2D context
|
|
193
|
+
* @param points - Array of points
|
|
194
|
+
* @param tension - Tension (0-1, default 0.5, lower = tighter curves)
|
|
195
|
+
* @param closed - Whether path is closed
|
|
196
|
+
*/
|
|
197
|
+
export function createCatmullRomPath(
|
|
198
|
+
ctx: SKRSContext2D,
|
|
199
|
+
points: Array<{ x: number; y: number }>,
|
|
200
|
+
tension: number = 0.5,
|
|
201
|
+
closed: boolean = false
|
|
202
|
+
): void {
|
|
203
|
+
if (points.length < 2) return;
|
|
204
|
+
|
|
205
|
+
// Enable anti-aliasing for smoother rendering
|
|
206
|
+
ctx.imageSmoothingEnabled = true;
|
|
207
|
+
ctx.imageSmoothingQuality = 'high';
|
|
208
|
+
|
|
209
|
+
ctx.beginPath();
|
|
210
|
+
ctx.moveTo(points[0].x, points[0].y);
|
|
211
|
+
|
|
212
|
+
if (points.length === 2) {
|
|
213
|
+
ctx.lineTo(points[1].x, points[1].y);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const segments = closed ? points.length : points.length - 1;
|
|
218
|
+
const segmentsPerCurve = 60; // Ultra-high resolution for maximum smoothness (graph quality)
|
|
219
|
+
|
|
220
|
+
for (let i = 0; i < segments; i++) {
|
|
221
|
+
const p0 = closed && i === 0 ? points[points.length - 1] : (i > 0 ? points[i - 1] : points[i]);
|
|
222
|
+
const p1 = points[i];
|
|
223
|
+
const p2 = points[(i + 1) % points.length];
|
|
224
|
+
const p3 = closed && i === segments - 1 ? points[0] : (i < points.length - 2 ? points[i + 2] : p2);
|
|
225
|
+
|
|
226
|
+
// Draw ultra-smooth curve with maximum resolution for graph-like quality
|
|
227
|
+
for (let s = 0; s <= segmentsPerCurve; s++) {
|
|
228
|
+
const t = s / segmentsPerCurve;
|
|
229
|
+
const point = catmullRom(p0, p1, p2, p3, t, tension);
|
|
230
|
+
|
|
231
|
+
if (s === 0 && i === 0) {
|
|
232
|
+
ctx.moveTo(point.x, point.y);
|
|
233
|
+
} else {
|
|
234
|
+
ctx.lineTo(point.x, point.y);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (closed) {
|
|
240
|
+
ctx.closePath();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Catmull-Rom spline interpolation (corrected formula)
|
|
246
|
+
* This creates curves that pass through all control points
|
|
247
|
+
*/
|
|
248
|
+
function catmullRom(
|
|
249
|
+
p0: { x: number; y: number },
|
|
250
|
+
p1: { x: number; y: number },
|
|
251
|
+
p2: { x: number; y: number },
|
|
252
|
+
p3: { x: number; y: number },
|
|
253
|
+
t: number,
|
|
254
|
+
tension: number
|
|
255
|
+
): { x: number; y: number } {
|
|
256
|
+
const t2 = t * t;
|
|
257
|
+
const t3 = t2 * t;
|
|
258
|
+
|
|
259
|
+
// Proper Catmull-Rom spline formula with tension
|
|
260
|
+
// Tension typically ranges from 0 (tight) to 1 (loose)
|
|
261
|
+
const s = (1 - tension) * 0.5;
|
|
262
|
+
|
|
263
|
+
const x = (2 * p1.x) +
|
|
264
|
+
s * ((-p0.x + p2.x) * t +
|
|
265
|
+
(2 * p0.x - 5 * p1.x + 4 * p2.x - p3.x) * t2 +
|
|
266
|
+
(-p0.x + 3 * p1.x - 3 * p2.x + p3.x) * t3);
|
|
267
|
+
|
|
268
|
+
const y = (2 * p1.y) +
|
|
269
|
+
s * ((-p0.y + p2.y) * t +
|
|
270
|
+
(2 * p0.y - 5 * p1.y + 4 * p2.y - p3.y) * t2 +
|
|
271
|
+
(-p0.y + 3 * p1.y - 3 * p2.y + p3.y) * t3);
|
|
272
|
+
|
|
273
|
+
return { x: x * 0.5, y: y * 0.5 };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Applies line pattern
|
|
278
|
+
* @param ctx - Canvas 2D context
|
|
279
|
+
* @param pattern - Pattern configuration
|
|
280
|
+
*/
|
|
281
|
+
export function applyLinePattern(
|
|
282
|
+
ctx: SKRSContext2D,
|
|
283
|
+
pattern: {
|
|
284
|
+
type: 'dots' | 'dashes' | 'custom';
|
|
285
|
+
segments?: number[];
|
|
286
|
+
offset?: number;
|
|
287
|
+
}
|
|
288
|
+
): void {
|
|
289
|
+
switch (pattern.type) {
|
|
290
|
+
case 'dots':
|
|
291
|
+
ctx.setLineDash([2, 4]);
|
|
292
|
+
break;
|
|
293
|
+
|
|
294
|
+
case 'dashes':
|
|
295
|
+
ctx.setLineDash([10, 5]);
|
|
296
|
+
break;
|
|
297
|
+
|
|
298
|
+
case 'custom':
|
|
299
|
+
if (pattern.segments && pattern.segments.length > 0) {
|
|
300
|
+
ctx.setLineDash(pattern.segments);
|
|
301
|
+
}
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (pattern.offset !== undefined) {
|
|
306
|
+
ctx.lineDashOffset = pattern.offset;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Applies texture to line
|
|
312
|
+
* @param ctx - Canvas 2D context
|
|
313
|
+
* @param textureSource - Texture image source
|
|
314
|
+
* @param lineWidth - Line width
|
|
315
|
+
* @param lineLength - Approximate line length
|
|
316
|
+
*/
|
|
317
|
+
export async function applyLineTexture(
|
|
318
|
+
ctx: SKRSContext2D,
|
|
319
|
+
textureSource: string | Buffer,
|
|
320
|
+
lineWidth: number,
|
|
321
|
+
lineLength: number
|
|
322
|
+
): Promise<void> {
|
|
323
|
+
try {
|
|
324
|
+
let textureImage: Image;
|
|
325
|
+
if (Buffer.isBuffer(textureSource)) {
|
|
326
|
+
textureImage = await loadImage(textureSource);
|
|
327
|
+
} else if (textureSource.startsWith('http')) {
|
|
328
|
+
textureImage = await loadImage(textureSource);
|
|
329
|
+
} else {
|
|
330
|
+
const texturePath = path.join(process.cwd(), textureSource);
|
|
331
|
+
textureImage = await loadImage(fs.readFileSync(texturePath));
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Create pattern from texture
|
|
335
|
+
const pattern = ctx.createPattern(textureImage, 'repeat');
|
|
336
|
+
if (pattern) {
|
|
337
|
+
ctx.strokeStyle = pattern;
|
|
338
|
+
}
|
|
339
|
+
} catch (error) {
|
|
340
|
+
console.error('Error applying line texture:', error);
|
|
341
|
+
// Fallback to current stroke style
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Gets points along a path for marker placement
|
|
347
|
+
* @param points - Path points
|
|
348
|
+
* @param position - Position along path (0-1)
|
|
349
|
+
*/
|
|
350
|
+
export function getPointOnLinePath(
|
|
351
|
+
points: Array<{ x: number; y: number }>,
|
|
352
|
+
position: number
|
|
353
|
+
): { x: number; y: number } {
|
|
354
|
+
if (points.length < 2) return points[0] || { x: 0, y: 0 };
|
|
355
|
+
|
|
356
|
+
// Calculate total path length
|
|
357
|
+
let totalLength = 0;
|
|
358
|
+
const segmentLengths: number[] = [];
|
|
359
|
+
|
|
360
|
+
for (let i = 0; i < points.length - 1; i++) {
|
|
361
|
+
const dx = points[i + 1].x - points[i].x;
|
|
362
|
+
const dy = points[i + 1].y - points[i].y;
|
|
363
|
+
const length = Math.sqrt(dx * dx + dy * dy);
|
|
364
|
+
segmentLengths.push(length);
|
|
365
|
+
totalLength += length;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Find target position
|
|
369
|
+
const targetLength = totalLength * Math.max(0, Math.min(1, position));
|
|
370
|
+
let currentLength = 0;
|
|
371
|
+
|
|
372
|
+
for (let i = 0; i < segmentLengths.length; i++) {
|
|
373
|
+
if (currentLength + segmentLengths[i] >= targetLength) {
|
|
374
|
+
const segmentT = (targetLength - currentLength) / segmentLengths[i];
|
|
375
|
+
const dx = points[i + 1].x - points[i].x;
|
|
376
|
+
const dy = points[i + 1].y - points[i].y;
|
|
377
|
+
return {
|
|
378
|
+
x: points[i].x + dx * segmentT,
|
|
379
|
+
y: points[i].y + dy * segmentT
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
currentLength += segmentLengths[i];
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return points[points.length - 1];
|
|
386
|
+
}
|
|
387
|
+
|