apexify.js 5.1.0 → 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.
@@ -1,206 +1,206 @@
1
- import { SKRSContext2D } from '@napi-rs/canvas';
2
- import { createGradientFill } from "../Image/imageProperties";
3
- import { CustomOptions } from "../types";
4
- import { drawArrow, drawMarker, createSmoothPath, createCatmullRomPath, applyLinePattern, applyLineTexture, getPointOnLinePath } from "./advancedLines";
5
-
6
-
7
- export async function customLines(ctx: SKRSContext2D, options: CustomOptions[]): Promise<void> {
8
- // Enable high-quality anti-aliasing for ultra-smooth lines (graph quality)
9
- ctx.imageSmoothingEnabled = true;
10
- ctx.imageSmoothingQuality = 'high';
11
-
12
- let previousEndCoordinates: { x: number; y: number } | null = null;
13
- let currentStyle: CustomOptions['lineStyle'] | null = null;
14
- let inSingleLineSequence = false;
15
-
16
- for (let i = 0; i < options.length; i++) {
17
- const customOption = options[i];
18
- const { startCoordinates, endCoordinates, lineStyle, path, arrow, markers } = customOption;
19
- const isSingleLine = lineStyle?.singleLine;
20
-
21
- // Collect all points for path rendering
22
- const allPoints: Array<{ x: number; y: number }> = [];
23
- if (i === 0 || !isSingleLine) {
24
- allPoints.push(startCoordinates);
25
- }
26
- allPoints.push(endCoordinates);
27
-
28
- if (isSingleLine && !inSingleLineSequence) {
29
- currentStyle = lineStyle;
30
- inSingleLineSequence = true;
31
- ctx.beginPath();
32
- ctx.moveTo(startCoordinates.x, startCoordinates.y);
33
- }
34
-
35
- if (!isSingleLine && inSingleLineSequence) {
36
- ctx.stroke();
37
- if (currentStyle) {
38
- applyStroke(ctx, currentStyle, startCoordinates, endCoordinates);
39
- }
40
- inSingleLineSequence = false;
41
- currentStyle = null;
42
- }
43
-
44
- const start = inSingleLineSequence && previousEndCoordinates
45
- ? previousEndCoordinates
46
- : startCoordinates;
47
-
48
- if (!inSingleLineSequence) {
49
- ctx.beginPath();
50
- ctx.moveTo(start.x, start.y);
51
- }
52
-
53
- // Apply path smoothing if specified
54
- if (path && allPoints.length >= 2) {
55
- ctx.beginPath();
56
- if (path.type === 'smooth') {
57
- createSmoothPath(ctx, allPoints, path.tension ?? 0.5, path.closed ?? false);
58
- } else if (path.type === 'catmull-rom') {
59
- createCatmullRomPath(ctx, allPoints, path.tension ?? 0.5, path.closed ?? false);
60
- } else if (path.type === 'bezier' && allPoints.length >= 4) {
61
- ctx.moveTo(allPoints[0].x, allPoints[0].y);
62
- for (let j = 1; j < allPoints.length - 2; j += 3) {
63
- if (j + 2 < allPoints.length) {
64
- ctx.bezierCurveTo(
65
- allPoints[j].x, allPoints[j].y,
66
- allPoints[j + 1].x, allPoints[j + 1].y,
67
- allPoints[j + 2].x, allPoints[j + 2].y
68
- );
69
- }
70
- }
71
- } else {
72
- ctx.lineTo(endCoordinates.x, endCoordinates.y);
73
- }
74
- } else {
75
- ctx.lineTo(endCoordinates.x, endCoordinates.y);
76
- }
77
-
78
- const appliedStyle = inSingleLineSequence ? currentStyle : lineStyle;
79
- ctx.lineWidth = appliedStyle?.width || 1;
80
-
81
- if (appliedStyle?.gradient) {
82
- ctx.strokeStyle = createGradientFill(ctx, appliedStyle.gradient, { x: start.x, y: start.y, w: endCoordinates.x - start.x, h: endCoordinates.y - start.y });
83
- } else {
84
- ctx.strokeStyle = appliedStyle?.color || 'black';
85
- }
86
-
87
- ctx.lineJoin = appliedStyle?.lineJoin || 'miter';
88
- ctx.lineCap = appliedStyle?.lineCap || 'butt';
89
-
90
- // Apply line patterns
91
- if (appliedStyle?.pattern) {
92
- applyLinePattern(ctx, appliedStyle.pattern);
93
- } else if (appliedStyle?.lineDash) {
94
- ctx.setLineDash(appliedStyle.lineDash.dashArray || []);
95
- ctx.lineDashOffset = appliedStyle.lineDash.offset || 0;
96
- } else {
97
- ctx.setLineDash([]);
98
- }
99
-
100
- // Apply line texture if specified
101
- if (appliedStyle?.texture) {
102
- await applyLineTexture(ctx, appliedStyle.texture, appliedStyle.width || 1,
103
- Math.sqrt(Math.pow(endCoordinates.x - start.x, 2) + Math.pow(endCoordinates.y - start.y, 2)));
104
- }
105
-
106
- if (typeof appliedStyle?.lineRadius === 'number' && appliedStyle.lineRadius > 0) {
107
- const radius = appliedStyle.lineRadius;
108
- const dx = endCoordinates.x - start.x;
109
- const dy = endCoordinates.y - start.y;
110
- const angle = Math.atan2(dy, dx);
111
-
112
- ctx.lineCap = "round";
113
- ctx.stroke();
114
-
115
- ctx.beginPath();
116
- ctx.arc(start.x, start.y, radius, angle + Math.PI / 2, angle - Math.PI / 2, true);
117
- ctx.arc(endCoordinates.x, endCoordinates.y, radius, angle - Math.PI / 2, angle + Math.PI / 2, false);
118
- ctx.fill();
119
- }
120
-
121
- else if (appliedStyle?.lineRadius === 'circular') {
122
- const dx = endCoordinates.x - start.x;
123
- const dy = endCoordinates.y - start.y;
124
- const length = Math.sqrt(dx * dx + dy * dy);
125
-
126
- ctx.beginPath();
127
- ctx.arcTo(start.x, start.y, endCoordinates.x, endCoordinates.y, length / 2);
128
- ctx.arcTo(endCoordinates.x, endCoordinates.y, start.x, start.y, length / 2);
129
- ctx.closePath();
130
- ctx.stroke();
131
- }
132
-
133
- if (!inSingleLineSequence || i === options.length - 1) {
134
- ctx.stroke();
135
- if (appliedStyle) {
136
- applyStroke(ctx, appliedStyle, startCoordinates, endCoordinates);
137
- }
138
- }
139
-
140
- // Draw arrows if specified
141
- if (arrow) {
142
- const dx = endCoordinates.x - start.x;
143
- const dy = endCoordinates.y - start.y;
144
- const angle = Math.atan2(dy, dx);
145
- const arrowColor = arrow.color || appliedStyle?.color || 'black';
146
- const arrowSize = arrow.size || 10;
147
-
148
- if (arrow.start) {
149
- drawArrow(ctx, start.x, start.y, angle + Math.PI, arrowSize, arrow.style || 'filled', arrowColor);
150
- }
151
- if (arrow.end) {
152
- drawArrow(ctx, endCoordinates.x, endCoordinates.y, angle, arrowSize, arrow.style || 'filled', arrowColor);
153
- }
154
- }
155
-
156
- // Draw markers if specified
157
- if (markers && markers.length > 0) {
158
- const linePoints = [start, endCoordinates];
159
- for (const marker of markers) {
160
- const point = getPointOnLinePath(linePoints, marker.position);
161
- drawMarker(ctx, point.x, point.y, marker.shape, marker.size, marker.color);
162
- }
163
- }
164
-
165
- previousEndCoordinates = endCoordinates;
166
-
167
- if (!isSingleLine) {
168
- currentStyle = null;
169
- inSingleLineSequence = false;
170
- }
171
- }
172
- }
173
-
174
- function applyStroke(ctx: SKRSContext2D, style: CustomOptions['lineStyle'] | undefined, start: { x: number; y: number }, end: { x: number; y: number }): void {
175
- if (!style || !style.stroke) {
176
- return;
177
- }
178
-
179
- if (style.stroke) {
180
- const { color, width, gradient, lineRadius, lineCap } = style.stroke;
181
- const prevStrokeStyle = ctx.strokeStyle;
182
- const prevLineWidth = ctx.lineWidth;
183
- const prevLineCap = ctx.lineCap;
184
-
185
- if (gradient) {
186
- ctx.strokeStyle = createGradientFill(ctx, gradient, { x: start.x, y: start.y, w: end.x - start.x, h: end.y - start.y });
187
- } else {
188
- ctx.strokeStyle = color || prevStrokeStyle;
189
- }
190
-
191
- ctx.lineWidth = width || prevLineWidth;
192
- ctx.lineCap = lineCap || prevLineCap;
193
- ctx.stroke();
194
-
195
- if (typeof lineRadius === 'number' && lineRadius > 0) {
196
- ctx.beginPath();
197
- ctx.arc(start.x, start.y, lineRadius, 0, Math.PI * 2);
198
- ctx.arc(end.x, end.y, lineRadius, 0, Math.PI * 2);
199
- ctx.fill();
200
- }
201
-
202
- ctx.strokeStyle = prevStrokeStyle;
203
- ctx.lineWidth = prevLineWidth;
204
- ctx.lineCap = prevLineCap;
205
- }
206
- }
1
+ import { SKRSContext2D } from '@napi-rs/canvas';
2
+ import { createGradientFill } from "../Image/imageProperties";
3
+ import { CustomOptions } from "../types";
4
+ import { drawArrow, drawMarker, createSmoothPath, createCatmullRomPath, applyLinePattern, applyLineTexture, getPointOnLinePath } from "./advancedLines";
5
+
6
+
7
+ export async function customLines(ctx: SKRSContext2D, options: CustomOptions[]): Promise<void> {
8
+ // Enable high-quality anti-aliasing for ultra-smooth lines (graph quality)
9
+ ctx.imageSmoothingEnabled = true;
10
+ ctx.imageSmoothingQuality = 'high';
11
+
12
+ let previousEndCoordinates: { x: number; y: number } | null = null;
13
+ let currentStyle: CustomOptions['lineStyle'] | null = null;
14
+ let inSingleLineSequence = false;
15
+
16
+ for (let i = 0; i < options.length; i++) {
17
+ const customOption = options[i];
18
+ const { startCoordinates, endCoordinates, lineStyle, path, arrow, markers } = customOption;
19
+ const isSingleLine = lineStyle?.singleLine;
20
+
21
+ // Collect all points for path rendering
22
+ const allPoints: Array<{ x: number; y: number }> = [];
23
+ if (i === 0 || !isSingleLine) {
24
+ allPoints.push(startCoordinates);
25
+ }
26
+ allPoints.push(endCoordinates);
27
+
28
+ if (isSingleLine && !inSingleLineSequence) {
29
+ currentStyle = lineStyle;
30
+ inSingleLineSequence = true;
31
+ ctx.beginPath();
32
+ ctx.moveTo(startCoordinates.x, startCoordinates.y);
33
+ }
34
+
35
+ if (!isSingleLine && inSingleLineSequence) {
36
+ ctx.stroke();
37
+ if (currentStyle) {
38
+ applyStroke(ctx, currentStyle, startCoordinates, endCoordinates);
39
+ }
40
+ inSingleLineSequence = false;
41
+ currentStyle = null;
42
+ }
43
+
44
+ const start = inSingleLineSequence && previousEndCoordinates
45
+ ? previousEndCoordinates
46
+ : startCoordinates;
47
+
48
+ if (!inSingleLineSequence) {
49
+ ctx.beginPath();
50
+ ctx.moveTo(start.x, start.y);
51
+ }
52
+
53
+ // Apply path smoothing if specified
54
+ if (path && allPoints.length >= 2) {
55
+ ctx.beginPath();
56
+ if (path.type === 'smooth') {
57
+ createSmoothPath(ctx, allPoints, path.tension ?? 0.5, path.closed ?? false);
58
+ } else if (path.type === 'catmull-rom') {
59
+ createCatmullRomPath(ctx, allPoints, path.tension ?? 0.5, path.closed ?? false);
60
+ } else if (path.type === 'bezier' && allPoints.length >= 4) {
61
+ ctx.moveTo(allPoints[0].x, allPoints[0].y);
62
+ for (let j = 1; j < allPoints.length - 2; j += 3) {
63
+ if (j + 2 < allPoints.length) {
64
+ ctx.bezierCurveTo(
65
+ allPoints[j].x, allPoints[j].y,
66
+ allPoints[j + 1].x, allPoints[j + 1].y,
67
+ allPoints[j + 2].x, allPoints[j + 2].y
68
+ );
69
+ }
70
+ }
71
+ } else {
72
+ ctx.lineTo(endCoordinates.x, endCoordinates.y);
73
+ }
74
+ } else {
75
+ ctx.lineTo(endCoordinates.x, endCoordinates.y);
76
+ }
77
+
78
+ const appliedStyle = inSingleLineSequence ? currentStyle : lineStyle;
79
+ ctx.lineWidth = appliedStyle?.width || 1;
80
+
81
+ if (appliedStyle?.gradient) {
82
+ ctx.strokeStyle = createGradientFill(ctx, appliedStyle.gradient, { x: start.x, y: start.y, w: endCoordinates.x - start.x, h: endCoordinates.y - start.y });
83
+ } else {
84
+ ctx.strokeStyle = appliedStyle?.color || 'black';
85
+ }
86
+
87
+ ctx.lineJoin = appliedStyle?.lineJoin || 'miter';
88
+ ctx.lineCap = appliedStyle?.lineCap || 'butt';
89
+
90
+ // Apply line patterns
91
+ if (appliedStyle?.pattern) {
92
+ applyLinePattern(ctx, appliedStyle.pattern);
93
+ } else if (appliedStyle?.lineDash) {
94
+ ctx.setLineDash(appliedStyle.lineDash.dashArray || []);
95
+ ctx.lineDashOffset = appliedStyle.lineDash.offset || 0;
96
+ } else {
97
+ ctx.setLineDash([]);
98
+ }
99
+
100
+ // Apply line texture if specified
101
+ if (appliedStyle?.texture) {
102
+ await applyLineTexture(ctx, appliedStyle.texture, appliedStyle.width || 1,
103
+ Math.sqrt(Math.pow(endCoordinates.x - start.x, 2) + Math.pow(endCoordinates.y - start.y, 2)));
104
+ }
105
+
106
+ if (typeof appliedStyle?.lineRadius === 'number' && appliedStyle.lineRadius > 0) {
107
+ const radius = appliedStyle.lineRadius;
108
+ const dx = endCoordinates.x - start.x;
109
+ const dy = endCoordinates.y - start.y;
110
+ const angle = Math.atan2(dy, dx);
111
+
112
+ ctx.lineCap = "round";
113
+ ctx.stroke();
114
+
115
+ ctx.beginPath();
116
+ ctx.arc(start.x, start.y, radius, angle + Math.PI / 2, angle - Math.PI / 2, true);
117
+ ctx.arc(endCoordinates.x, endCoordinates.y, radius, angle - Math.PI / 2, angle + Math.PI / 2, false);
118
+ ctx.fill();
119
+ }
120
+
121
+ else if (appliedStyle?.lineRadius === 'circular') {
122
+ const dx = endCoordinates.x - start.x;
123
+ const dy = endCoordinates.y - start.y;
124
+ const length = Math.sqrt(dx * dx + dy * dy);
125
+
126
+ ctx.beginPath();
127
+ ctx.arcTo(start.x, start.y, endCoordinates.x, endCoordinates.y, length / 2);
128
+ ctx.arcTo(endCoordinates.x, endCoordinates.y, start.x, start.y, length / 2);
129
+ ctx.closePath();
130
+ ctx.stroke();
131
+ }
132
+
133
+ if (!inSingleLineSequence || i === options.length - 1) {
134
+ ctx.stroke();
135
+ if (appliedStyle) {
136
+ applyStroke(ctx, appliedStyle, startCoordinates, endCoordinates);
137
+ }
138
+ }
139
+
140
+ // Draw arrows if specified
141
+ if (arrow) {
142
+ const dx = endCoordinates.x - start.x;
143
+ const dy = endCoordinates.y - start.y;
144
+ const angle = Math.atan2(dy, dx);
145
+ const arrowColor = arrow.color || appliedStyle?.color || 'black';
146
+ const arrowSize = arrow.size || 10;
147
+
148
+ if (arrow.start) {
149
+ drawArrow(ctx, start.x, start.y, angle + Math.PI, arrowSize, arrow.style || 'filled', arrowColor);
150
+ }
151
+ if (arrow.end) {
152
+ drawArrow(ctx, endCoordinates.x, endCoordinates.y, angle, arrowSize, arrow.style || 'filled', arrowColor);
153
+ }
154
+ }
155
+
156
+ // Draw markers if specified
157
+ if (markers && markers.length > 0) {
158
+ const linePoints = [start, endCoordinates];
159
+ for (const marker of markers) {
160
+ const point = getPointOnLinePath(linePoints, marker.position);
161
+ drawMarker(ctx, point.x, point.y, marker.shape, marker.size, marker.color);
162
+ }
163
+ }
164
+
165
+ previousEndCoordinates = endCoordinates;
166
+
167
+ if (!isSingleLine) {
168
+ currentStyle = null;
169
+ inSingleLineSequence = false;
170
+ }
171
+ }
172
+ }
173
+
174
+ function applyStroke(ctx: SKRSContext2D, style: CustomOptions['lineStyle'] | undefined, start: { x: number; y: number }, end: { x: number; y: number }): void {
175
+ if (!style || !style.stroke) {
176
+ return;
177
+ }
178
+
179
+ if (style.stroke) {
180
+ const { color, width, gradient, lineRadius, lineCap } = style.stroke;
181
+ const prevStrokeStyle = ctx.strokeStyle;
182
+ const prevLineWidth = ctx.lineWidth;
183
+ const prevLineCap = ctx.lineCap;
184
+
185
+ if (gradient) {
186
+ ctx.strokeStyle = createGradientFill(ctx, gradient, { x: start.x, y: start.y, w: end.x - start.x, h: end.y - start.y });
187
+ } else {
188
+ ctx.strokeStyle = color || prevStrokeStyle;
189
+ }
190
+
191
+ ctx.lineWidth = width || prevLineWidth;
192
+ ctx.lineCap = lineCap || prevLineCap;
193
+ ctx.stroke();
194
+
195
+ if (typeof lineRadius === 'number' && lineRadius > 0) {
196
+ ctx.beginPath();
197
+ ctx.arc(start.x, start.y, lineRadius, 0, Math.PI * 2);
198
+ ctx.arc(end.x, end.y, lineRadius, 0, Math.PI * 2);
199
+ ctx.fill();
200
+ }
201
+
202
+ ctx.strokeStyle = prevStrokeStyle;
203
+ ctx.lineWidth = prevLineWidth;
204
+ ctx.lineCap = prevLineCap;
205
+ }
206
+ }