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.
- package/CHANGELOG.md +23 -38
- package/README.md +5 -9
- package/lib/Canvas/utils/Custom/advancedLines.ts +387 -387
- package/lib/Canvas/utils/Custom/customLines.ts +206 -206
- package/lib/Canvas/utils/types.ts +983 -983
- package/package.json +198 -198
|
@@ -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
|
+
}
|