|
|
@@ -1,4 +1,4 @@
|
|
1
|
|
-import { uniqueId } from 'utils/utils'
|
|
|
1
|
+import { getArcLength, lerp, uniqueId } from 'utils/utils'
|
|
2
|
2
|
import vec from 'utils/vec'
|
|
3
|
3
|
import {
|
|
4
|
4
|
getSvgPathFromStroke,
|
|
|
@@ -7,7 +7,7 @@ import {
|
|
7
|
7
|
translateBounds,
|
|
8
|
8
|
pointsBetween,
|
|
9
|
9
|
} from 'utils/utils'
|
|
10
|
|
-import { ArrowShape, Bounds, ShapeHandle, ShapeType } from 'types'
|
|
|
10
|
+import { ArrowShape, Bounds, DashStyle, ShapeHandle, ShapeType } from 'types'
|
|
11
|
11
|
import { registerShapeUtils } from './index'
|
|
12
|
12
|
import { circleFromThreePoints, isAngleBetween } from 'utils/utils'
|
|
13
|
13
|
import { pointInBounds } from 'utils/bounds'
|
|
|
@@ -16,22 +16,20 @@ import {
|
|
16
|
16
|
intersectLineSegmentBounds,
|
|
17
|
17
|
} from 'utils/intersections'
|
|
18
|
18
|
import { pointInCircle } from 'utils/hitTests'
|
|
19
|
|
-import { defaultStyle, getShapeStyle } from 'lib/shape-styles'
|
|
|
19
|
+import {
|
|
|
20
|
+ defaultStyle,
|
|
|
21
|
+ getShapeStyle,
|
|
|
22
|
+ getStrokeDashArray,
|
|
|
23
|
+} from 'lib/shape-styles'
|
|
20
|
24
|
import getStroke from 'perfect-freehand'
|
|
|
25
|
+import React from 'react'
|
|
21
|
26
|
|
|
22
|
|
-const ctpCache = new WeakMap<ArrowShape['handles'], number[]>()
|
|
23
|
27
|
const pathCache = new WeakMap<ArrowShape, string>([])
|
|
24
|
28
|
|
|
|
29
|
+// A cache for semi-expensive circles calculated from three points
|
|
25
|
30
|
function getCtp(shape: ArrowShape) {
|
|
26
|
|
- if (!ctpCache.has(shape.handles)) {
|
|
27
|
|
- const { start, end, bend } = shape.handles
|
|
28
|
|
- ctpCache.set(
|
|
29
|
|
- shape.handles,
|
|
30
|
|
- circleFromThreePoints(start.point, end.point, bend.point)
|
|
31
|
|
- )
|
|
32
|
|
- }
|
|
33
|
|
-
|
|
34
|
|
- return ctpCache.get(shape.handles)
|
|
|
31
|
+ const { start, end, bend } = shape.handles
|
|
|
32
|
+ return circleFromThreePoints(start.point, end.point, bend.point)
|
|
35
|
33
|
}
|
|
36
|
34
|
|
|
37
|
35
|
const arrow = registerShapeUtils<ArrowShape>({
|
|
|
@@ -40,10 +38,6 @@ const arrow = registerShapeUtils<ArrowShape>({
|
|
40
|
38
|
create(props) {
|
|
41
|
39
|
const {
|
|
42
|
40
|
point = [0, 0],
|
|
43
|
|
- points = [
|
|
44
|
|
- [0, 0],
|
|
45
|
|
- [0, 1],
|
|
46
|
|
- ],
|
|
47
|
41
|
handles = {
|
|
48
|
42
|
start: {
|
|
49
|
43
|
id: 'start',
|
|
|
@@ -77,7 +71,6 @@ const arrow = registerShapeUtils<ArrowShape>({
|
|
77
|
71
|
isLocked: false,
|
|
78
|
72
|
isHidden: false,
|
|
79
|
73
|
bend: 0,
|
|
80
|
|
- points,
|
|
81
|
74
|
handles,
|
|
82
|
75
|
decorations: {
|
|
83
|
76
|
start: null,
|
|
|
@@ -94,63 +87,123 @@ const arrow = registerShapeUtils<ArrowShape>({
|
|
94
|
87
|
},
|
|
95
|
88
|
|
|
96
|
89
|
render(shape) {
|
|
97
|
|
- const { id, bend, handles } = shape
|
|
|
90
|
+ const { id, bend, handles, style } = shape
|
|
98
|
91
|
const { start, end, bend: _bend } = handles
|
|
99
|
92
|
|
|
100
|
|
- const arrowDist = vec.dist(start.point, end.point)
|
|
101
|
|
-
|
|
102
|
|
- const showCircle = !vec.isEqual(
|
|
|
93
|
+ const isStraightLine = vec.isEqual(
|
|
103
|
94
|
_bend.point,
|
|
104
|
95
|
vec.med(start.point, end.point)
|
|
105
|
96
|
)
|
|
106
|
97
|
|
|
107
|
|
- const style = getShapeStyle(shape.style)
|
|
|
98
|
+ const styles = getShapeStyle(style)
|
|
108
|
99
|
|
|
109
|
|
- let body: JSX.Element
|
|
110
|
|
-
|
|
111
|
|
- if (showCircle) {
|
|
112
|
|
- if (!ctpCache.has(handles)) {
|
|
113
|
|
- ctpCache.set(
|
|
114
|
|
- handles,
|
|
115
|
|
- circleFromThreePoints(start.point, end.point, _bend.point)
|
|
116
|
|
- )
|
|
117
|
|
- }
|
|
118
|
|
-
|
|
119
|
|
- const circle = getCtp(shape)
|
|
|
100
|
+ const strokeWidth = +styles.strokeWidth
|
|
120
|
101
|
|
|
|
102
|
+ if (isStraightLine) {
|
|
|
103
|
+ // Render a straight arrow as a freehand path.
|
|
121
|
104
|
if (!pathCache.has(shape)) {
|
|
122
|
|
- renderPath(
|
|
123
|
|
- shape,
|
|
124
|
|
- vec.angle([circle[0], circle[1]], end.point) -
|
|
125
|
|
- vec.angle(start.point, end.point) +
|
|
126
|
|
- (Math.PI / 2) * (bend > 0 ? 0.98 : -0.98)
|
|
127
|
|
- )
|
|
|
105
|
+ renderPath(shape)
|
|
128
|
106
|
}
|
|
129
|
107
|
|
|
|
108
|
+ const offset = -vec.dist(start.point, end.point) + strokeWidth
|
|
|
109
|
+
|
|
130
|
110
|
const path = pathCache.get(shape)
|
|
131
|
111
|
|
|
132
|
|
- body = (
|
|
133
|
|
- <>
|
|
|
112
|
+ return (
|
|
|
113
|
+ <g id={id}>
|
|
|
114
|
+ {/* Improves hit testing */}
|
|
134
|
115
|
<path
|
|
135
|
|
- d={getArrowArcPath(start, end, circle, bend)}
|
|
|
116
|
+ d={path}
|
|
|
117
|
+ stroke="transparent"
|
|
136
|
118
|
fill="none"
|
|
137
|
|
- strokeWidth={(+style.strokeWidth * 1.85).toString()}
|
|
|
119
|
+ strokeWidth={Math.max(8, strokeWidth * 2)}
|
|
138
|
120
|
strokeLinecap="round"
|
|
|
121
|
+ strokeDasharray="none"
|
|
139
|
122
|
/>
|
|
140
|
|
- <path d={path} strokeWidth={+style.strokeWidth * 1.5} />
|
|
141
|
|
- </>
|
|
|
123
|
+ {/* Arrowshaft */}
|
|
|
124
|
+ <circle
|
|
|
125
|
+ cx={start.point[0]}
|
|
|
126
|
+ cy={start.point[1]}
|
|
|
127
|
+ r={strokeWidth}
|
|
|
128
|
+ fill={styles.stroke}
|
|
|
129
|
+ stroke="none"
|
|
|
130
|
+ />
|
|
|
131
|
+ <path
|
|
|
132
|
+ d={path}
|
|
|
133
|
+ fill="none"
|
|
|
134
|
+ strokeWidth={
|
|
|
135
|
+ strokeWidth * (style.dash === DashStyle.Solid ? 1 : 1.618)
|
|
|
136
|
+ }
|
|
|
137
|
+ strokeDashoffset={offset}
|
|
|
138
|
+ strokeLinecap="round"
|
|
|
139
|
+ />
|
|
|
140
|
+ {/* Arrowhead */}
|
|
|
141
|
+ {style.dash !== DashStyle.Solid && (
|
|
|
142
|
+ <path
|
|
|
143
|
+ d={getArrowHeadPath(shape, 0)}
|
|
|
144
|
+ strokeWidth={strokeWidth * 1.618}
|
|
|
145
|
+ strokeDasharray="none"
|
|
|
146
|
+ fill="none"
|
|
|
147
|
+ />
|
|
|
148
|
+ )}
|
|
|
149
|
+ </g>
|
|
142
|
150
|
)
|
|
143
|
|
- } else {
|
|
144
|
|
- if (!pathCache.has(shape)) {
|
|
145
|
|
- renderPath(shape)
|
|
146
|
|
- }
|
|
|
151
|
+ }
|
|
147
|
152
|
|
|
148
|
|
- const path = pathCache.get(shape)
|
|
|
153
|
+ const circle = getCtp(shape)
|
|
149
|
154
|
|
|
150
|
|
- body = <path d={path} />
|
|
|
155
|
+ if (!pathCache.has(shape)) {
|
|
|
156
|
+ renderPath(
|
|
|
157
|
+ shape,
|
|
|
158
|
+ vec.angle([circle[0], circle[1]], end.point) -
|
|
|
159
|
+ vec.angle(start.point, end.point) +
|
|
|
160
|
+ (Math.PI / 2) * (bend > 0 ? 0.98 : -0.98)
|
|
|
161
|
+ )
|
|
151
|
162
|
}
|
|
152
|
163
|
|
|
153
|
|
- return <g id={id}>{body}</g>
|
|
|
164
|
+ const path = getArrowArcPath(start, end, circle, bend)
|
|
|
165
|
+
|
|
|
166
|
+ const strokeDashOffset = getStrokeDashOffsetForArc(
|
|
|
167
|
+ shape,
|
|
|
168
|
+ circle,
|
|
|
169
|
+ strokeWidth
|
|
|
170
|
+ )
|
|
|
171
|
+
|
|
|
172
|
+ return (
|
|
|
173
|
+ <g id={id}>
|
|
|
174
|
+ {/* Improves hit testing */}
|
|
|
175
|
+ <path
|
|
|
176
|
+ d={path}
|
|
|
177
|
+ stroke="transparent"
|
|
|
178
|
+ fill="none"
|
|
|
179
|
+ strokeWidth={Math.max(8, strokeWidth * 2)}
|
|
|
180
|
+ strokeLinecap="round"
|
|
|
181
|
+ strokeDasharray="none"
|
|
|
182
|
+ />
|
|
|
183
|
+ {/* Arrow Shaft */}
|
|
|
184
|
+ <circle
|
|
|
185
|
+ cx={start.point[0]}
|
|
|
186
|
+ cy={start.point[1]}
|
|
|
187
|
+ r={strokeWidth}
|
|
|
188
|
+ fill={styles.stroke}
|
|
|
189
|
+ stroke="none"
|
|
|
190
|
+ />
|
|
|
191
|
+ <path
|
|
|
192
|
+ d={path}
|
|
|
193
|
+ fill="none"
|
|
|
194
|
+ strokeWidth={strokeWidth * 1.618}
|
|
|
195
|
+ strokeLinecap="round"
|
|
|
196
|
+ strokeDashoffset={strokeDashOffset}
|
|
|
197
|
+ />
|
|
|
198
|
+ {/* Arrowhead */}
|
|
|
199
|
+ <path
|
|
|
200
|
+ d={pathCache.get(shape)}
|
|
|
201
|
+ strokeWidth={strokeWidth * 1.618}
|
|
|
202
|
+ strokeDasharray="none"
|
|
|
203
|
+ fill="none"
|
|
|
204
|
+ />
|
|
|
205
|
+ </g>
|
|
|
206
|
+ )
|
|
154
|
207
|
},
|
|
155
|
208
|
|
|
156
|
209
|
rotateBy(shape, delta) {
|
|
|
@@ -179,17 +232,20 @@ const arrow = registerShapeUtils<ArrowShape>({
|
|
179
|
232
|
|
|
180
|
233
|
getBounds(shape) {
|
|
181
|
234
|
if (!this.boundsCache.has(shape)) {
|
|
182
|
|
- const { start, end } = shape.handles
|
|
183
|
|
- this.boundsCache.set(shape, getBoundsFromPoints([start.point, end.point]))
|
|
|
235
|
+ const { start, bend, end } = shape.handles
|
|
|
236
|
+ this.boundsCache.set(
|
|
|
237
|
+ shape,
|
|
|
238
|
+ getBoundsFromPoints([start.point, bend.point, end.point])
|
|
|
239
|
+ )
|
|
184
|
240
|
}
|
|
185
|
241
|
|
|
186
|
242
|
return translateBounds(this.boundsCache.get(shape), shape.point)
|
|
187
|
243
|
},
|
|
188
|
244
|
|
|
189
|
245
|
getRotatedBounds(shape) {
|
|
190
|
|
- const { start, end } = shape.handles
|
|
|
246
|
+ const { start, bend, end } = shape.handles
|
|
191
|
247
|
return translateBounds(
|
|
192
|
|
- getBoundsFromPoints([start.point, end.point], shape.rotation),
|
|
|
248
|
+ getBoundsFromPoints([start.point, bend.point, end.point], shape.rotation),
|
|
193
|
249
|
shape.point
|
|
194
|
250
|
)
|
|
195
|
251
|
},
|
|
|
@@ -200,7 +256,7 @@ const arrow = registerShapeUtils<ArrowShape>({
|
|
200
|
256
|
},
|
|
201
|
257
|
|
|
202
|
258
|
hitTest(shape, point) {
|
|
203
|
|
- const { start, end, bend } = shape.handles
|
|
|
259
|
+ const { start, end } = shape.handles
|
|
204
|
260
|
if (shape.bend === 0) {
|
|
205
|
261
|
return (
|
|
206
|
262
|
vec.distanceToLineSegment(
|
|
|
@@ -239,33 +295,42 @@ const arrow = registerShapeUtils<ArrowShape>({
|
|
239
|
295
|
transform(shape, bounds, { initialShape, scaleX, scaleY }) {
|
|
240
|
296
|
const initialShapeBounds = this.getBounds(initialShape)
|
|
241
|
297
|
|
|
|
298
|
+ // let nw = initialShape.point[0] / initialShapeBounds.width
|
|
|
299
|
+ // let nh = initialShape.point[1] / initialShapeBounds.height
|
|
|
300
|
+
|
|
|
301
|
+ // shape.point = [
|
|
|
302
|
+ // bounds.width * (scaleX < 0 ? 1 - nw : nw),
|
|
|
303
|
+ // bounds.height * (scaleY < 0 ? 1 - nh : nh),
|
|
|
304
|
+ // ]
|
|
|
305
|
+
|
|
242
|
306
|
shape.point = [bounds.minX, bounds.minY]
|
|
243
|
307
|
|
|
244
|
|
- shape.points = shape.points.map((_, i) => {
|
|
245
|
|
- const [x, y] = initialShape.points[i]
|
|
|
308
|
+ const handles = ['start', 'end']
|
|
|
309
|
+
|
|
|
310
|
+ handles.forEach((handle) => {
|
|
|
311
|
+ const [x, y] = initialShape.handles[handle].point
|
|
246
|
312
|
let nw = x / initialShapeBounds.width
|
|
247
|
313
|
let nh = y / initialShapeBounds.height
|
|
248
|
314
|
|
|
249
|
|
- if (i === 1) {
|
|
250
|
|
- let [x0, y0] = initialShape.points[0]
|
|
251
|
|
- if (x0 === x) nw = 1
|
|
252
|
|
- if (y0 === y) nh = 1
|
|
253
|
|
- }
|
|
254
|
|
-
|
|
255
|
|
- return [
|
|
|
315
|
+ shape.handles[handle].point = [
|
|
256
|
316
|
bounds.width * (scaleX < 0 ? 1 - nw : nw),
|
|
257
|
317
|
bounds.height * (scaleY < 0 ? 1 - nh : nh),
|
|
258
|
318
|
]
|
|
259
|
319
|
})
|
|
260
|
320
|
|
|
261
|
|
- const { start, end, bend } = shape.handles
|
|
|
321
|
+ const { start, bend, end } = shape.handles
|
|
|
322
|
+
|
|
|
323
|
+ const dist = vec.dist(start.point, end.point)
|
|
|
324
|
+
|
|
|
325
|
+ const midPoint = vec.med(start.point, end.point)
|
|
|
326
|
+
|
|
|
327
|
+ const bendDist = (dist / 2) * initialShape.bend
|
|
262
|
328
|
|
|
263
|
|
- start.point = shape.points[0]
|
|
264
|
|
- end.point = shape.points[1]
|
|
|
329
|
+ const u = vec.uni(vec.vec(start.point, end.point))
|
|
265
|
330
|
|
|
266
|
|
- bend.point = getBendPoint(shape)
|
|
|
331
|
+ const point = vec.add(midPoint, vec.mul(vec.per(u), bendDist))
|
|
267
|
332
|
|
|
268
|
|
- shape.points = [shape.handles.start.point, shape.handles.end.point]
|
|
|
333
|
+ bend.point = Math.abs(bendDist) < 10 ? midPoint : point
|
|
269
|
334
|
|
|
270
|
335
|
return this
|
|
271
|
336
|
},
|
|
|
@@ -279,10 +344,6 @@ const arrow = registerShapeUtils<ArrowShape>({
|
|
279
|
344
|
|
|
280
|
345
|
shape.handles[handle.id] = handle
|
|
281
|
346
|
|
|
282
|
|
- if (handle.index < 2) {
|
|
283
|
|
- shape.points[handle.index] = handle.point
|
|
284
|
|
- }
|
|
285
|
|
-
|
|
286
|
347
|
const { start, end, bend } = shape.handles
|
|
287
|
348
|
|
|
288
|
349
|
const dist = vec.dist(start.point, end.point)
|
|
|
@@ -327,6 +388,8 @@ const arrow = registerShapeUtils<ArrowShape>({
|
|
327
|
388
|
end.point = vec.sub(end.point, offset)
|
|
328
|
389
|
bend.point = vec.sub(bend.point, offset)
|
|
329
|
390
|
|
|
|
391
|
+ shape.handles = { ...shape.handles }
|
|
|
392
|
+
|
|
330
|
393
|
return this
|
|
331
|
394
|
},
|
|
332
|
395
|
|
|
|
@@ -375,47 +438,6 @@ function getBendPoint(shape: ArrowShape) {
|
|
375
|
438
|
: vec.add(midPoint, vec.mul(vec.per(u), bendDist))
|
|
376
|
439
|
}
|
|
377
|
440
|
|
|
378
|
|
-function getResizeOffset(a: Bounds, b: Bounds) {
|
|
379
|
|
- const { minX: x0, minY: y0, width: w0, height: h0 } = a
|
|
380
|
|
- const { minX: x1, minY: y1, width: w1, height: h1 } = b
|
|
381
|
|
-
|
|
382
|
|
- let delta: number[]
|
|
383
|
|
-
|
|
384
|
|
- if (h0 === h1 && w0 !== w1) {
|
|
385
|
|
- if (x0 !== x1) {
|
|
386
|
|
- // moving left edge, pin right edge
|
|
387
|
|
- delta = vec.sub([x1, y1 + h1 / 2], [x0, y0 + h0 / 2])
|
|
388
|
|
- } else {
|
|
389
|
|
- // moving right edge, pin left edge
|
|
390
|
|
- delta = vec.sub([x1 + w1, y1 + h1 / 2], [x0 + w0, y0 + h0 / 2])
|
|
391
|
|
- }
|
|
392
|
|
- } else if (h0 !== h1 && w0 === w1) {
|
|
393
|
|
- if (y0 !== y1) {
|
|
394
|
|
- // moving top edge, pin bottom edge
|
|
395
|
|
- delta = vec.sub([x1 + w1 / 2, y1], [x0 + w0 / 2, y0])
|
|
396
|
|
- } else {
|
|
397
|
|
- // moving bottom edge, pin top edge
|
|
398
|
|
- delta = vec.sub([x1 + w1 / 2, y1 + h1], [x0 + w0 / 2, y0 + h0])
|
|
399
|
|
- }
|
|
400
|
|
- } else if (x0 !== x1) {
|
|
401
|
|
- if (y0 !== y1) {
|
|
402
|
|
- // moving top left, pin bottom right
|
|
403
|
|
- delta = vec.sub([x1, y1], [x0, y0])
|
|
404
|
|
- } else {
|
|
405
|
|
- // moving bottom left, pin top right
|
|
406
|
|
- delta = vec.sub([x1, y1 + h1], [x0, y0 + h0])
|
|
407
|
|
- }
|
|
408
|
|
- } else if (y0 !== y1) {
|
|
409
|
|
- // moving top right, pin bottom left
|
|
410
|
|
- delta = vec.sub([x1 + w1, y1], [x0 + w0, y0])
|
|
411
|
|
- } else {
|
|
412
|
|
- // moving bottom right, pin top left
|
|
413
|
|
- delta = vec.sub([x1 + w1, y1 + h1], [x0 + w0, y0 + h0])
|
|
414
|
|
- }
|
|
415
|
|
-
|
|
416
|
|
- return delta
|
|
417
|
|
-}
|
|
418
|
|
-
|
|
419
|
441
|
function renderPath(shape: ArrowShape, endAngle = 0) {
|
|
420
|
442
|
const { style, id } = shape
|
|
421
|
443
|
const { start, end } = shape.handles
|
|
|
@@ -424,42 +446,34 @@ function renderPath(shape: ArrowShape, endAngle = 0) {
|
|
424
|
446
|
|
|
425
|
447
|
const strokeWidth = +getShapeStyle(style).strokeWidth * 2
|
|
426
|
448
|
|
|
427
|
|
- const arrowDist = vec.dist(start.point, end.point)
|
|
428
|
|
-
|
|
429
|
449
|
const styles = getShapeStyle(shape.style)
|
|
430
|
450
|
|
|
431
|
|
- const sw = +styles.strokeWidth
|
|
432
|
|
-
|
|
433
|
|
- const length = Math.min(arrowDist / 2, 24 + sw * 2)
|
|
434
|
|
- const u = vec.uni(vec.vec(start.point, end.point))
|
|
435
|
|
- const v = vec.rot(vec.mul(vec.neg(u), length), endAngle)
|
|
|
451
|
+ const sw = strokeWidth
|
|
436
|
452
|
|
|
437
|
453
|
// Start
|
|
438
|
454
|
const a = start.point
|
|
439
|
455
|
|
|
|
456
|
+ // End
|
|
|
457
|
+ const b = end.point
|
|
|
458
|
+
|
|
440
|
459
|
// Middle
|
|
441
|
460
|
const m = vec.add(
|
|
442
|
461
|
vec.lrp(start.point, end.point, 0.25 + Math.abs(getRandom()) / 2),
|
|
443
|
462
|
[getRandom() * sw, getRandom() * sw]
|
|
444
|
463
|
)
|
|
445
|
464
|
|
|
446
|
|
- // End
|
|
447
|
|
- const b = end.point
|
|
|
465
|
+ // Left and right sides of the arrowhead
|
|
|
466
|
+ let { left: c, right: d } = getArrowHeadPoints(shape, endAngle)
|
|
448
|
467
|
|
|
449
|
|
- // Left
|
|
450
|
|
- let c = vec.add(
|
|
451
|
|
- end.point,
|
|
452
|
|
- vec.rot(v, Math.PI / 6 + (Math.PI / 8) * getRandom())
|
|
453
|
|
- )
|
|
|
468
|
+ // Switch which side of the arrow is drawn first
|
|
|
469
|
+ if (getRandom() > 0) [c, d] = [d, c]
|
|
454
|
470
|
|
|
455
|
|
- // Right
|
|
456
|
|
- let d = vec.add(
|
|
457
|
|
- end.point,
|
|
458
|
|
- vec.rot(v, -(Math.PI / 6) + (Math.PI / 8) * getRandom())
|
|
459
|
|
- )
|
|
460
|
|
-
|
|
461
|
|
- if (getRandom() > 0.5) {
|
|
462
|
|
- ;[c, d] = [d, c]
|
|
|
471
|
+ if (style.dash !== DashStyle.Solid) {
|
|
|
472
|
+ pathCache.set(
|
|
|
473
|
+ shape,
|
|
|
474
|
+ (endAngle ? ['M', c, 'L', b, d] : ['M', a, 'L', b]).join(' ')
|
|
|
475
|
+ )
|
|
|
476
|
+ return
|
|
463
|
477
|
}
|
|
464
|
478
|
|
|
465
|
479
|
const points = endAngle
|
|
|
@@ -471,7 +485,7 @@ function renderPath(shape: ArrowShape, endAngle = 0) {
|
|
471
|
485
|
...pointsBetween(d, b),
|
|
472
|
486
|
]
|
|
473
|
487
|
: [
|
|
474
|
|
- // The shaft too
|
|
|
488
|
+ // The arrow shaft
|
|
475
|
489
|
b,
|
|
476
|
490
|
a,
|
|
477
|
491
|
...pointsBetween(a, m),
|
|
|
@@ -493,3 +507,60 @@ function renderPath(shape: ArrowShape, endAngle = 0) {
|
|
493
|
507
|
|
|
494
|
508
|
pathCache.set(shape, getSvgPathFromStroke(stroke))
|
|
495
|
509
|
}
|
|
|
510
|
+
|
|
|
511
|
+function getArrowHeadPath(shape: ArrowShape, endAngle = 0) {
|
|
|
512
|
+ const { end } = shape.handles
|
|
|
513
|
+ const { left, right } = getArrowHeadPoints(shape, endAngle)
|
|
|
514
|
+ return ['M', left, 'L', end.point, right].join(' ')
|
|
|
515
|
+}
|
|
|
516
|
+
|
|
|
517
|
+function getArrowHeadPoints(shape: ArrowShape, endAngle = 0) {
|
|
|
518
|
+ const { start, end } = shape.handles
|
|
|
519
|
+
|
|
|
520
|
+ const stroke = +getShapeStyle(shape.style).strokeWidth * 2
|
|
|
521
|
+
|
|
|
522
|
+ const arrowDist = vec.dist(start.point, end.point)
|
|
|
523
|
+
|
|
|
524
|
+ const arrowHeadlength = Math.min(arrowDist / 3, stroke * 4)
|
|
|
525
|
+
|
|
|
526
|
+ // Unit vector from start to end
|
|
|
527
|
+ const u = vec.uni(vec.vec(start.point, end.point))
|
|
|
528
|
+
|
|
|
529
|
+ // The end of the arrowhead wings
|
|
|
530
|
+ const v = vec.rot(vec.mul(vec.neg(u), arrowHeadlength), endAngle)
|
|
|
531
|
+
|
|
|
532
|
+ // Use the shape's random seed to create minor offsets for the angles
|
|
|
533
|
+ const getRandom = rng(shape.id)
|
|
|
534
|
+
|
|
|
535
|
+ return {
|
|
|
536
|
+ left: vec.add(
|
|
|
537
|
+ end.point,
|
|
|
538
|
+ vec.rot(v, Math.PI / 6 + (Math.PI / 8) * getRandom())
|
|
|
539
|
+ ),
|
|
|
540
|
+ right: vec.add(
|
|
|
541
|
+ end.point,
|
|
|
542
|
+ vec.rot(v, -(Math.PI / 6) + (Math.PI / 8) * getRandom())
|
|
|
543
|
+ ),
|
|
|
544
|
+ }
|
|
|
545
|
+}
|
|
|
546
|
+
|
|
|
547
|
+function getStrokeDashOffsetForArc(
|
|
|
548
|
+ shape: ArrowShape,
|
|
|
549
|
+ circle: number[],
|
|
|
550
|
+ strokeWidth: number
|
|
|
551
|
+) {
|
|
|
552
|
+ const { start, end } = shape.handles
|
|
|
553
|
+
|
|
|
554
|
+ const sweep = getArcLength(
|
|
|
555
|
+ [circle[0], circle[1]],
|
|
|
556
|
+ circle[2],
|
|
|
557
|
+ start.point,
|
|
|
558
|
+ end.point
|
|
|
559
|
+ )
|
|
|
560
|
+
|
|
|
561
|
+ return Math.abs(shape.bend) === 1
|
|
|
562
|
+ ? -strokeWidth / 2
|
|
|
563
|
+ : shape.bend < 0
|
|
|
564
|
+ ? sweep + strokeWidth
|
|
|
565
|
+ : -sweep + strokeWidth
|
|
|
566
|
+}
|