drawlib 0.2.3.dev3__py3-none-any.whl → 0.2.3.dev5__py3-none-any.whl
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.
Potentially problematic release.
This version of drawlib might be problematic. Click here for more details.
- drawlib/__init__.py +1 -1
- drawlib/v0_2/apis.py +3 -1
- drawlib/v0_2/private/core_canvas/base.py +53 -1
- drawlib/v0_2/private/core_canvas/canvas.py +7 -3
- drawlib/v0_2/private/core_canvas/line.py +29 -23
- drawlib/v0_2/private/core_canvas/original_arrow.py +335 -250
- drawlib/v0_2/private/core_canvas/patches.py +19 -69
- drawlib/v0_2/private/smartarts/bubblespeech.py +16 -16
- drawlib/v0_2/private/util.py +116 -1
- drawlib/v0_2/private/validators/args.py +13 -13
- {drawlib-0.2.3.dev3.dist-info → drawlib-0.2.3.dev5.dist-info}/METADATA +1 -1
- {drawlib-0.2.3.dev3.dist-info → drawlib-0.2.3.dev5.dist-info}/RECORD +15 -15
- {drawlib-0.2.3.dev3.dist-info → drawlib-0.2.3.dev5.dist-info}/LICENSE.txt +0 -0
- {drawlib-0.2.3.dev3.dist-info → drawlib-0.2.3.dev5.dist-info}/WHEEL +0 -0
- {drawlib-0.2.3.dev3.dist-info → drawlib-0.2.3.dev5.dist-info}/entry_points.txt +0 -0
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"""Canvas's original arrow feature implementation module."""
|
|
12
12
|
|
|
13
13
|
import math
|
|
14
|
-
from typing import List, Literal, Optional, Tuple, Union
|
|
14
|
+
from typing import Any, List, Literal, Optional, Tuple, Union
|
|
15
15
|
|
|
16
16
|
from matplotlib.patches import PathPatch
|
|
17
17
|
from matplotlib.path import Path
|
|
@@ -26,6 +26,8 @@ from drawlib.v0_2.private.util import (
|
|
|
26
26
|
error_handler,
|
|
27
27
|
get_angle,
|
|
28
28
|
get_distance,
|
|
29
|
+
get_rotated_path_points,
|
|
30
|
+
get_rotated_points,
|
|
29
31
|
)
|
|
30
32
|
|
|
31
33
|
|
|
@@ -144,7 +146,7 @@ class CanvasOriginalArrowFeature(CanvasBase):
|
|
|
144
146
|
)
|
|
145
147
|
|
|
146
148
|
@error_handler
|
|
147
|
-
def
|
|
149
|
+
def arrow_polyline(
|
|
148
150
|
self,
|
|
149
151
|
xys: List[Tuple[float, float]],
|
|
150
152
|
tail_width: float,
|
|
@@ -184,119 +186,59 @@ class CanvasOriginalArrowFeature(CanvasBase):
|
|
|
184
186
|
)
|
|
185
187
|
validator.validate_shape_args(locals())
|
|
186
188
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
+
def point_on_line(a: Tuple[float, float], b: Tuple[float, float], n: float) -> Tuple[float, float]:
|
|
190
|
+
ab = (b[0] - a[0], b[1] - a[1])
|
|
191
|
+
ab_length = math.sqrt(ab[0] ** 2 + ab[1] ** 2)
|
|
192
|
+
ab_unit = (ab[0] / ab_length, ab[1] / ab_length)
|
|
193
|
+
scaled_vector = (ab_unit[0] * n, ab_unit[1] * n)
|
|
194
|
+
new_point = (a[0] + scaled_vector[0], a[1] + scaled_vector[1])
|
|
195
|
+
return new_point
|
|
196
|
+
|
|
197
|
+
def point_parallel_to_line(
|
|
198
|
+
a: Tuple[float, float], b: Tuple[float, float], distance: float
|
|
199
|
+
) -> Tuple[float, float]:
|
|
200
|
+
ab = (b[0] - a[0], b[1] - a[1])
|
|
201
|
+
ab_length = math.sqrt(ab[0] ** 2 + ab[1] ** 2)
|
|
202
|
+
ab_unit = (ab[0] / ab_length, ab[1] / ab_length)
|
|
203
|
+
ab_perpendicular = (-ab_unit[1], ab_unit[0])
|
|
204
|
+
scaled_vector = (ab_perpendicular[0] * distance, ab_perpendicular[1] * distance)
|
|
205
|
+
new_point = (a[0] + scaled_vector[0], a[1] + scaled_vector[1])
|
|
206
|
+
return new_point
|
|
189
207
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
arrowhead_end_tail_xy1 = ArrowsHelper.get_point_on_line(
|
|
193
|
-
parallel_xys1[len(parallel_xys1) - 1],
|
|
194
|
-
parallel_xys1[len(parallel_xys1) - 2],
|
|
195
|
-
head_length,
|
|
196
|
-
)
|
|
197
|
-
arrowhead_end_tail_xy2 = ArrowsHelper.get_point_on_line(
|
|
198
|
-
parallel_xys2[len(parallel_xys1) - 1],
|
|
199
|
-
parallel_xys2[len(parallel_xys1) - 2],
|
|
200
|
-
head_length,
|
|
201
|
-
)
|
|
202
|
-
|
|
203
|
-
xy1, xy2 = ArrowsHelper.get_parallel_line_xys(xys[0], xys[1], head_width / 2)
|
|
204
|
-
arrowhead_start_head_xy1 = ArrowsHelper.get_point_on_line(xy1, xy2, head_length)
|
|
205
|
-
xy1, xy2 = ArrowsHelper.get_parallel_line_xys(xys[0], xys[1], head_width / 2 * -1)
|
|
206
|
-
arrowhead_start_head_xy2 = ArrowsHelper.get_point_on_line(xy1, xy2, head_length)
|
|
207
|
-
xy1, xy2 = ArrowsHelper.get_parallel_line_xys(
|
|
208
|
-
xys[len(xys) - 1], xys[len(xys) - 2], head_width / 2 * -1
|
|
209
|
-
) # since poinrts are reversed, having * -1 here
|
|
210
|
-
arrowhead_end_head_xy1 = ArrowsHelper.get_point_on_line(xy1, xy2, head_length)
|
|
211
|
-
xy1, xy2 = ArrowsHelper.get_parallel_line_xys(xys[len(xys) - 1], xys[len(xys) - 2], head_width / 2)
|
|
212
|
-
arrowhead_end_head_xy2 = ArrowsHelper.get_point_on_line(xy1, xy2, head_length)
|
|
213
|
-
|
|
214
|
-
points1: List[Tuple[float, float]] = []
|
|
215
|
-
if head == "->":
|
|
216
|
-
points1.append(parallel_xys1[0])
|
|
217
|
-
else:
|
|
218
|
-
points1.append(arrowhead_start_tail_xy1)
|
|
219
|
-
points1.extend(parallel_xys1[1:-1])
|
|
220
|
-
if head == "<-":
|
|
221
|
-
points1.append(parallel_xys1[len(parallel_xys1) - 1])
|
|
208
|
+
if head in {"<-", "<->"}:
|
|
209
|
+
xys_start = point_on_line(xys[0], xys[1], head_length)
|
|
222
210
|
else:
|
|
223
|
-
|
|
211
|
+
xys_start = xys[0]
|
|
224
212
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
points2.append(parallel_xys2[len(parallel_xys2) - 1])
|
|
228
|
-
else:
|
|
229
|
-
points2.append(arrowhead_end_tail_xy2)
|
|
230
|
-
temp = parallel_xys2[1:-1]
|
|
231
|
-
temp.reverse()
|
|
232
|
-
points2.extend(temp)
|
|
233
|
-
if head == "->":
|
|
234
|
-
points2.append(parallel_xys2[0])
|
|
235
|
-
else:
|
|
236
|
-
points2.append(arrowhead_start_tail_xy2)
|
|
237
|
-
|
|
238
|
-
# apply r here
|
|
239
|
-
if r != 0:
|
|
240
|
-
path_points1: List[
|
|
241
|
-
Union[
|
|
242
|
-
Tuple[float, float],
|
|
243
|
-
Tuple[Tuple[float, float], Tuple[float, float]],
|
|
244
|
-
Tuple[Tuple[float, float], Tuple[float, float], Tuple[float, float]],
|
|
245
|
-
]
|
|
246
|
-
] = ArrowsHelper.get_path_points(points1, r)
|
|
247
|
-
path_points2: List[
|
|
248
|
-
Union[
|
|
249
|
-
Tuple[float, float],
|
|
250
|
-
Tuple[Tuple[float, float], Tuple[float, float]],
|
|
251
|
-
Tuple[Tuple[float, float], Tuple[float, float], Tuple[float, float]],
|
|
252
|
-
]
|
|
253
|
-
] = ArrowsHelper.get_path_points(points2, r)
|
|
213
|
+
if head in {"->", "<->"}:
|
|
214
|
+
xys_end = point_on_line(xys[-1], xys[-2], head_length)
|
|
254
215
|
else:
|
|
255
|
-
|
|
256
|
-
path_points2 = points2 # type: ignore
|
|
216
|
+
xys_end = xys[-1]
|
|
257
217
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
218
|
+
new_xys = xys[1:-1]
|
|
219
|
+
new_xys.insert(0, xys_start)
|
|
220
|
+
new_xys.append(xys_end)
|
|
221
|
+
aph = ArrowPolylineHelper(new_xys, r, 100)
|
|
222
|
+
parallel_xys1 = aph.get_parallel_points(tail_width / 2)
|
|
223
|
+
parallel_xys2 = aph.get_parallel_points(tail_width / -2)
|
|
224
|
+
parallel_xys2.reverse()
|
|
264
225
|
|
|
265
|
-
if head
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
# create Path
|
|
273
|
-
vertices = [path_points1[0]]
|
|
274
|
-
codes = [Path.MOVETO]
|
|
275
|
-
for p in path_points1[1:]:
|
|
276
|
-
length = len(p)
|
|
277
|
-
if length not in {2, 3}:
|
|
278
|
-
raise ValueError()
|
|
279
|
-
|
|
280
|
-
if not isinstance(p[0], tuple):
|
|
281
|
-
vertices.append(p)
|
|
282
|
-
codes.append(Path.LINETO)
|
|
283
|
-
|
|
284
|
-
elif length == 2:
|
|
285
|
-
vertices.extend([p[0], p[1]]) # type: ignore
|
|
286
|
-
codes.extend([Path.CURVE3] * 2)
|
|
287
|
-
|
|
288
|
-
else:
|
|
289
|
-
vertices.extend([p[0], p[1], p[2]]) # type: ignore
|
|
290
|
-
codes.extend([Path.CURVE4] * 3)
|
|
291
|
-
|
|
292
|
-
vertices.append(path_points1[0])
|
|
293
|
-
codes.append(Path.CLOSEPOLY)
|
|
294
|
-
path = Path(vertices=vertices, codes=codes)
|
|
226
|
+
if head in {"<-", "<->"}:
|
|
227
|
+
ahp1 = point_parallel_to_line(xys_start, xys[1], head_width / 2)
|
|
228
|
+
ahp2 = point_parallel_to_line(xys_start, xys[1], head_width / -2)
|
|
229
|
+
parallel_xys1.insert(0, xys[0])
|
|
230
|
+
parallel_xys1.insert(1, ahp1)
|
|
231
|
+
parallel_xys2.append(ahp2)
|
|
295
232
|
|
|
296
|
-
|
|
233
|
+
if head in {"->", "<->"}:
|
|
234
|
+
ahp3 = point_parallel_to_line(xys_end, xys[-2], head_width / -2)
|
|
235
|
+
ahp4 = point_parallel_to_line(xys_end, xys[-2], head_width / 2)
|
|
236
|
+
parallel_xys1.append(ahp3)
|
|
237
|
+
parallel_xys1.append(xys[-1])
|
|
238
|
+
parallel_xys1.append(ahp4)
|
|
297
239
|
|
|
298
|
-
|
|
299
|
-
self.
|
|
240
|
+
parallel_xys1.extend(parallel_xys2)
|
|
241
|
+
self.polygon(xys=parallel_xys1, style=style, text="", textstyle=None)
|
|
300
242
|
|
|
301
243
|
@error_handler
|
|
302
244
|
def arrow_arc(
|
|
@@ -308,8 +250,9 @@ class CanvasOriginalArrowFeature(CanvasBase):
|
|
|
308
250
|
head_width: float,
|
|
309
251
|
head_angle: float = 10,
|
|
310
252
|
head: Literal["->", "<-", "<->"] = "->",
|
|
311
|
-
|
|
312
|
-
|
|
253
|
+
angle_start: float = 0,
|
|
254
|
+
angle_end: float = 180,
|
|
255
|
+
angle: float = 0,
|
|
313
256
|
style: Union[ShapeStyle, str, None] = None,
|
|
314
257
|
) -> None:
|
|
315
258
|
"""Draw arc arrow.
|
|
@@ -322,8 +265,9 @@ class CanvasOriginalArrowFeature(CanvasBase):
|
|
|
322
265
|
head_width: float: The width of the head of the arrow.
|
|
323
266
|
head_angle: float: The angle of the arrowhead in degrees (default is 10).
|
|
324
267
|
head: Literal["->", "<-", "<->"]: The arrowhead style ("->", "<-", "<->").
|
|
325
|
-
|
|
326
|
-
|
|
268
|
+
angle_start: float: The starting angle of the arc in degrees (default is 0).
|
|
269
|
+
angle_end: float: The ending angle of the arc in degrees (default is 180).
|
|
270
|
+
angle (float): The angle of ellipse.
|
|
327
271
|
style: Union[ShapeStyle, str, None]: Optional shape style.
|
|
328
272
|
|
|
329
273
|
Returns:
|
|
@@ -343,27 +287,27 @@ class CanvasOriginalArrowFeature(CanvasBase):
|
|
|
343
287
|
height_ext = height + tail_width
|
|
344
288
|
|
|
345
289
|
if head in {"<-", "<->"}:
|
|
346
|
-
|
|
347
|
-
|
|
290
|
+
angle_start2 = angle_start + head_angle
|
|
291
|
+
angle_start2 %= 360
|
|
348
292
|
else:
|
|
349
|
-
|
|
293
|
+
angle_start2 = angle_start
|
|
350
294
|
|
|
351
295
|
if head in {"->", "<->"}:
|
|
352
|
-
|
|
353
|
-
|
|
296
|
+
angle_end2 = angle_end - head_angle
|
|
297
|
+
angle_end2 %= 360
|
|
354
298
|
else:
|
|
355
|
-
|
|
299
|
+
angle_end2 = angle_end
|
|
356
300
|
|
|
357
|
-
if
|
|
358
|
-
pp1_fa = -1 * (360 -
|
|
359
|
-
pp1_ta =
|
|
360
|
-
pp2_fa =
|
|
301
|
+
if angle_start2 > angle_end2:
|
|
302
|
+
pp1_fa = -1 * (360 - angle_start2)
|
|
303
|
+
pp1_ta = angle_end2
|
|
304
|
+
pp2_fa = angle_end2
|
|
361
305
|
pp2_ta = pp1_fa
|
|
362
306
|
else:
|
|
363
|
-
pp1_fa =
|
|
364
|
-
pp1_ta =
|
|
365
|
-
pp2_fa =
|
|
366
|
-
pp2_ta =
|
|
307
|
+
pp1_fa = angle_start2
|
|
308
|
+
pp1_ta = angle_end2
|
|
309
|
+
pp2_fa = angle_end2
|
|
310
|
+
pp2_ta = angle_start2
|
|
367
311
|
|
|
368
312
|
path_points1 = LineArcHelper.get_ellipse_path_points(
|
|
369
313
|
xy,
|
|
@@ -380,8 +324,8 @@ class CanvasOriginalArrowFeature(CanvasBase):
|
|
|
380
324
|
pp2_ta,
|
|
381
325
|
)
|
|
382
326
|
|
|
383
|
-
if
|
|
384
|
-
|
|
327
|
+
if angle_start > angle_end:
|
|
328
|
+
angle_start = -1 * (360 - angle_start)
|
|
385
329
|
|
|
386
330
|
if head in {"<-", "<->"}:
|
|
387
331
|
path_points1.insert(
|
|
@@ -390,7 +334,7 @@ class CanvasOriginalArrowFeature(CanvasBase):
|
|
|
390
334
|
xy,
|
|
391
335
|
width,
|
|
392
336
|
height,
|
|
393
|
-
|
|
337
|
+
angle_start,
|
|
394
338
|
),
|
|
395
339
|
)
|
|
396
340
|
path_points1.insert(
|
|
@@ -399,7 +343,7 @@ class CanvasOriginalArrowFeature(CanvasBase):
|
|
|
399
343
|
xy,
|
|
400
344
|
width - head_width,
|
|
401
345
|
height - head_width,
|
|
402
|
-
|
|
346
|
+
angle_start2,
|
|
403
347
|
),
|
|
404
348
|
)
|
|
405
349
|
path_points2.append(
|
|
@@ -407,7 +351,7 @@ class CanvasOriginalArrowFeature(CanvasBase):
|
|
|
407
351
|
xy,
|
|
408
352
|
width + head_width,
|
|
409
353
|
height + head_width,
|
|
410
|
-
|
|
354
|
+
angle_start2,
|
|
411
355
|
)
|
|
412
356
|
)
|
|
413
357
|
|
|
@@ -417,7 +361,7 @@ class CanvasOriginalArrowFeature(CanvasBase):
|
|
|
417
361
|
xy,
|
|
418
362
|
width - head_width,
|
|
419
363
|
height - head_width,
|
|
420
|
-
|
|
364
|
+
angle_end2,
|
|
421
365
|
)
|
|
422
366
|
)
|
|
423
367
|
path_points1.append(
|
|
@@ -425,7 +369,7 @@ class CanvasOriginalArrowFeature(CanvasBase):
|
|
|
425
369
|
xy,
|
|
426
370
|
width,
|
|
427
371
|
height,
|
|
428
|
-
|
|
372
|
+
angle_end,
|
|
429
373
|
)
|
|
430
374
|
)
|
|
431
375
|
path_points2.insert(
|
|
@@ -434,12 +378,15 @@ class CanvasOriginalArrowFeature(CanvasBase):
|
|
|
434
378
|
xy,
|
|
435
379
|
width + head_width,
|
|
436
380
|
height + head_width,
|
|
437
|
-
|
|
381
|
+
angle_end2,
|
|
438
382
|
),
|
|
439
383
|
)
|
|
440
384
|
|
|
441
385
|
path_points1.extend(path_points2)
|
|
442
386
|
|
|
387
|
+
if angle != 0:
|
|
388
|
+
path_points1 = get_rotated_path_points(path_points1, xy, angle) # type: ignore
|
|
389
|
+
|
|
443
390
|
# create Path
|
|
444
391
|
vertices = [path_points1[0]]
|
|
445
392
|
codes = [Path.MOVETO]
|
|
@@ -469,112 +416,300 @@ class CanvasOriginalArrowFeature(CanvasBase):
|
|
|
469
416
|
options = ShapeUtil.get_shape_options(style)
|
|
470
417
|
self._artists.append(PathPatch(path=path, **options))
|
|
471
418
|
|
|
419
|
+
@error_handler
|
|
420
|
+
def arrow_l(
|
|
421
|
+
self,
|
|
422
|
+
xy: Tuple[float, float],
|
|
423
|
+
width: float,
|
|
424
|
+
height: float,
|
|
425
|
+
tail_width: float,
|
|
426
|
+
head_width: float,
|
|
427
|
+
head_length: float,
|
|
428
|
+
head: Literal["->", "<-", "<->"] = "->",
|
|
429
|
+
r: float = 0,
|
|
430
|
+
angle: float = 0,
|
|
431
|
+
style: Union[ShapeStyle, str, None] = None,
|
|
432
|
+
) -> None:
|
|
433
|
+
"""Draw "L" shape arrow.
|
|
434
|
+
|
|
435
|
+
Args:
|
|
436
|
+
xy: Tuple[float, float]: The center point of L arrow.
|
|
437
|
+
width: float: The width L arrow.
|
|
438
|
+
height: float: The height of L arrow.
|
|
439
|
+
tail_width: float: The width of the tail of the arrow.
|
|
440
|
+
head_width: float: The width of the head of the arrow.
|
|
441
|
+
head_length: float: Length of the arrow head.
|
|
442
|
+
head: Literal["->", "<-", "<->"]: Arrow head style ("->", "<-", "<->").
|
|
443
|
+
r (float, optional): Radius for rounded connections (default is 0.0).
|
|
444
|
+
angle: float: rotate angle.
|
|
445
|
+
style: Union[ShapeStyle, str, None]: Optional shape style.
|
|
446
|
+
|
|
447
|
+
Returns:
|
|
448
|
+
None
|
|
449
|
+
"""
|
|
450
|
+
style, _ = ShapeUtil.format_styles(
|
|
451
|
+
style,
|
|
452
|
+
None,
|
|
453
|
+
dtheme.arrowstyles.get,
|
|
454
|
+
dtheme.arrowtextstyles.get,
|
|
455
|
+
)
|
|
456
|
+
validator.validate_shape_args(locals())
|
|
457
|
+
|
|
458
|
+
p1 = (xy[0] - width / 2, xy[1] + height / 2)
|
|
459
|
+
p2 = (xy[0] - width / 2, xy[1] - height / 2)
|
|
460
|
+
p3 = (xy[0] + width / 2, xy[1] - height / 2)
|
|
461
|
+
|
|
462
|
+
xys = [p1, p2, p3]
|
|
463
|
+
if angle != 0:
|
|
464
|
+
xys = get_rotated_points(xys=xys, center=xy, angle=angle)
|
|
465
|
+
|
|
466
|
+
self.arrow_polyline(
|
|
467
|
+
xys=xys,
|
|
468
|
+
tail_width=tail_width,
|
|
469
|
+
head_width=head_width,
|
|
470
|
+
head_length=head_length,
|
|
471
|
+
head=head,
|
|
472
|
+
r=r,
|
|
473
|
+
style=style,
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
@error_handler
|
|
477
|
+
def arrow_u(
|
|
478
|
+
self,
|
|
479
|
+
xy: Tuple[float, float],
|
|
480
|
+
width: float,
|
|
481
|
+
height: float,
|
|
482
|
+
tail_width: float,
|
|
483
|
+
head_width: float,
|
|
484
|
+
head_length: float,
|
|
485
|
+
head: Literal["->", "<-", "<->"] = "->",
|
|
486
|
+
r: float = 0,
|
|
487
|
+
angle: float = 0,
|
|
488
|
+
style: Union[ShapeStyle, str, None] = None,
|
|
489
|
+
) -> None:
|
|
490
|
+
"""Draw "U" shape arrow.
|
|
491
|
+
|
|
492
|
+
Args:
|
|
493
|
+
xy: Tuple[float, float]: The center point U arrow.
|
|
494
|
+
width: float: The width of the U arrow.
|
|
495
|
+
height: float: The height of the U arrow.
|
|
496
|
+
tail_width: float: The width of the tail of the arrow.
|
|
497
|
+
head_width: float: The width of the head of the arrow.
|
|
498
|
+
head_length: float: Length of the arrow head.
|
|
499
|
+
head: Literal["->", "<-", "<->"]: Arrow head style ("->", "<-", "<->").
|
|
500
|
+
r (float, optional): Radius for rounded connections (default is 0.0).
|
|
501
|
+
angle: float: rotate angle.
|
|
502
|
+
style: Union[ShapeStyle, str, None]: Optional shape style.
|
|
503
|
+
|
|
504
|
+
Returns:
|
|
505
|
+
None
|
|
506
|
+
"""
|
|
507
|
+
style, _ = ShapeUtil.format_styles(
|
|
508
|
+
style,
|
|
509
|
+
None,
|
|
510
|
+
dtheme.arrowstyles.get,
|
|
511
|
+
dtheme.arrowtextstyles.get,
|
|
512
|
+
)
|
|
513
|
+
validator.validate_shape_args(locals())
|
|
514
|
+
|
|
515
|
+
p1 = (xy[0] - width / 2, xy[1] + height / 2)
|
|
516
|
+
p2 = (xy[0] - width / 2, xy[1] - height / 2)
|
|
517
|
+
p3 = (xy[0] + width / 2, xy[1] - height / 2)
|
|
518
|
+
p4 = (xy[0] + width / 2, xy[1] + height / 2)
|
|
519
|
+
|
|
520
|
+
xys = [p1, p2, p3, p4]
|
|
521
|
+
if angle != 0:
|
|
522
|
+
xys = get_rotated_points(xys=xys, center=xy, angle=angle)
|
|
523
|
+
|
|
524
|
+
self.arrow_polyline(
|
|
525
|
+
xys=xys,
|
|
526
|
+
tail_width=tail_width,
|
|
527
|
+
head_width=head_width,
|
|
528
|
+
head_length=head_length,
|
|
529
|
+
head=head,
|
|
530
|
+
r=r,
|
|
531
|
+
style=style,
|
|
532
|
+
)
|
|
472
533
|
|
|
473
|
-
|
|
534
|
+
|
|
535
|
+
class ArrowPolylineHelper:
|
|
474
536
|
"""Internal class"""
|
|
475
537
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
cls,
|
|
538
|
+
def __init__(
|
|
539
|
+
self,
|
|
479
540
|
xys: List[Tuple[float, float]],
|
|
480
541
|
r: float,
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
Tuple[float, float],
|
|
484
|
-
Tuple[Tuple[float, float], Tuple[float, float]],
|
|
485
|
-
Tuple[Tuple[float, float], Tuple[float, float], Tuple[float, float]],
|
|
486
|
-
]
|
|
487
|
-
]:
|
|
542
|
+
num_points: int = 100,
|
|
543
|
+
) -> None:
|
|
488
544
|
"""Internal function"""
|
|
489
545
|
|
|
490
|
-
def
|
|
546
|
+
def get_mid_points(
|
|
491
547
|
a: Tuple[float, float],
|
|
492
548
|
b: Tuple[float, float],
|
|
493
|
-
r: float,
|
|
494
549
|
) -> Tuple[Tuple[float, float], Tuple[float, float]]:
|
|
495
|
-
# Calculate the vector from A to B
|
|
496
550
|
ab = [b[0] - a[0], b[1] - a[1]]
|
|
497
|
-
|
|
498
|
-
# Calculate the distance from A to B
|
|
499
551
|
ab_distance = math.sqrt(ab[0] ** 2 + ab[1] ** 2)
|
|
500
|
-
|
|
501
|
-
# Normalize the vector AB to get the unit vector
|
|
502
552
|
ab_unit = [ab[0] / ab_distance, ab[1] / ab_distance]
|
|
503
|
-
|
|
504
|
-
# Calculate point C: A + X * unit vector AB
|
|
505
553
|
c = (a[0] + r * ab_unit[0], a[1] + r * ab_unit[1])
|
|
506
|
-
|
|
507
|
-
# Calculate point D: B - X * unit vector AB
|
|
508
554
|
d = (b[0] - r * ab_unit[0], b[1] - r * ab_unit[1])
|
|
509
|
-
|
|
510
555
|
return c, d
|
|
511
556
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
557
|
+
def bernstein_poly(i: int, n: int, t: float) -> float:
|
|
558
|
+
return math.comb(n, i) * (t**i) * ((1 - t) ** (n - i))
|
|
559
|
+
|
|
560
|
+
def get_points(
|
|
561
|
+
bezier_points: List[Tuple[float, float]],
|
|
562
|
+
) -> List[Tuple[float, float]]:
|
|
563
|
+
n = len(bezier_points) - 1
|
|
564
|
+
curve = []
|
|
565
|
+
for t in [i / (num_points - 1) for i in range(num_points)]:
|
|
566
|
+
x = sum(bernstein_poly(i, n, t) * bezier_points[i][0] for i in range(n + 1))
|
|
567
|
+
y = sum(bernstein_poly(i, n, t) * bezier_points[i][1] for i in range(n + 1))
|
|
568
|
+
curve.append((x, y))
|
|
569
|
+
return curve
|
|
570
|
+
|
|
571
|
+
def remove_consecutive_duplicates(lst: List[Any]) -> List[Any]:
|
|
572
|
+
return [v for i, v in enumerate(lst) if i == 0 or v != lst[i - 1]]
|
|
573
|
+
|
|
574
|
+
self._r = r
|
|
575
|
+
if r == 0:
|
|
576
|
+
self._original_points = xys[:]
|
|
577
|
+
return
|
|
578
|
+
|
|
579
|
+
points = []
|
|
580
|
+
bezier_start: Tuple[float, float] = (0, 0)
|
|
520
581
|
last_i = len(xys) - 2
|
|
521
582
|
# last_xy = (0, 0)
|
|
522
|
-
for i
|
|
583
|
+
for i in range(len(xys)):
|
|
584
|
+
# first straight line
|
|
523
585
|
if i == 0:
|
|
524
|
-
_,
|
|
525
|
-
|
|
586
|
+
_, bezier_start = get_mid_points(xys[0], xys[1])
|
|
587
|
+
points.append(xys[0])
|
|
526
588
|
continue
|
|
527
589
|
|
|
590
|
+
# last straight line
|
|
528
591
|
if i == last_i:
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
592
|
+
p1, _ = get_mid_points(xys[i], xys[i + 1])
|
|
593
|
+
bezier_points = [bezier_start, xys[i], p1]
|
|
594
|
+
points.extend(get_points(bezier_points))
|
|
595
|
+
points.append(xys[i + 1])
|
|
532
596
|
break
|
|
533
597
|
|
|
534
|
-
p1, p2 =
|
|
535
|
-
|
|
536
|
-
|
|
598
|
+
p1, p2 = get_mid_points(xys[i], xys[i + 1])
|
|
599
|
+
bezier_points = [bezier_start, xys[i], p1]
|
|
600
|
+
points.extend(get_points(bezier_points))
|
|
601
|
+
bezier_start = p2
|
|
537
602
|
|
|
538
|
-
|
|
539
|
-
return path_points
|
|
603
|
+
self._original_points = remove_consecutive_duplicates(points)
|
|
540
604
|
|
|
541
|
-
|
|
542
|
-
def get_point_on_line(
|
|
543
|
-
cls,
|
|
544
|
-
xy1: Tuple[float, float],
|
|
545
|
-
xy2: Tuple[float, float],
|
|
546
|
-
distance: float,
|
|
547
|
-
) -> Tuple[float, float]:
|
|
605
|
+
def get_parallel_points(self, distance: float) -> List[Tuple[float, float]]:
|
|
548
606
|
"""Internal function"""
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
v_length = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
|
|
553
|
-
u_x = (x2 - x1) / v_length
|
|
554
|
-
u_y = (y2 - y1) / v_length
|
|
555
|
-
|
|
556
|
-
x = x1 + distance * u_x
|
|
557
|
-
y = y1 + distance * u_y
|
|
607
|
+
if self._r == 0:
|
|
608
|
+
return self._get_parallel_straight_points(distance)
|
|
609
|
+
return self._get_parallel_curved_points(distance)
|
|
558
610
|
|
|
559
|
-
|
|
611
|
+
def _get_parallel_curved_points(self, distance: float) -> List[Tuple[float, float]]:
|
|
612
|
+
"""Internal function"""
|
|
560
613
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
614
|
+
def get_distance(p1: Tuple[float, float], p2: Tuple[float, float]) -> float:
|
|
615
|
+
return math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2)
|
|
616
|
+
|
|
617
|
+
parallel_curve_points = []
|
|
618
|
+
for i in range(1, len(self._original_points)):
|
|
619
|
+
p0, p1 = self._original_points[i - 1], self._original_points[i]
|
|
620
|
+
dx, dy = p1[0] - p0[0], p1[1] - p0[1]
|
|
621
|
+
length = get_distance(p0, p1)
|
|
622
|
+
print(p0, p1, length)
|
|
623
|
+
nx, ny = -dy / length, dx / length
|
|
624
|
+
px0, py0 = p0[0] + distance * nx, p0[1] + distance * ny
|
|
625
|
+
px1, py1 = p1[0] + distance * nx, p1[1] + distance * ny
|
|
626
|
+
parallel_curve_points.append((px0, py0))
|
|
627
|
+
if i == len(self._original_points) - 1:
|
|
628
|
+
parallel_curve_points.append((px1, py1))
|
|
629
|
+
|
|
630
|
+
return parallel_curve_points
|
|
631
|
+
|
|
632
|
+
def _get_parallel_straight_points(self, distance: float) -> List[Tuple[float, float]]:
|
|
567
633
|
"""Internal function"""
|
|
634
|
+
|
|
635
|
+
def get_parallel_line_xys(
|
|
636
|
+
xy1: Tuple[float, float],
|
|
637
|
+
xy2: Tuple[float, float],
|
|
638
|
+
) -> Tuple[Tuple[float, float], Tuple[float, float]]:
|
|
639
|
+
"""Internal function"""
|
|
640
|
+
x1, y1 = xy1
|
|
641
|
+
x2, y2 = xy2
|
|
642
|
+
|
|
643
|
+
# calc vector
|
|
644
|
+
vx = x2 - x1
|
|
645
|
+
vy = y2 - y1
|
|
646
|
+
length = math.sqrt(vx**2 + vy**2)
|
|
647
|
+
dx = -vy / length * distance
|
|
648
|
+
dy = vx / length * distance
|
|
649
|
+
|
|
650
|
+
# add vector
|
|
651
|
+
x1_prime = x1 + dx
|
|
652
|
+
y1_prime = y1 + dy
|
|
653
|
+
x2_prime = x2 + dx
|
|
654
|
+
y2_prime = y2 + dy
|
|
655
|
+
|
|
656
|
+
return (x1_prime, y1_prime), (x2_prime, y2_prime)
|
|
657
|
+
|
|
658
|
+
def find_lines_intersection(
|
|
659
|
+
xy1: Tuple[float, float],
|
|
660
|
+
xy2: Tuple[float, float],
|
|
661
|
+
xy3: Tuple[float, float],
|
|
662
|
+
xy4: Tuple[float, float],
|
|
663
|
+
) -> Tuple[float, float]:
|
|
664
|
+
"""Internal function"""
|
|
665
|
+
x1, y1 = xy1
|
|
666
|
+
x2, y2 = xy2
|
|
667
|
+
x3, y3 = xy3
|
|
668
|
+
x4, y4 = xy4
|
|
669
|
+
|
|
670
|
+
# Check if lines are vertical
|
|
671
|
+
if x1 == x2 and x3 == x4:
|
|
672
|
+
raise ValueError(f'line "{xy1}" -> "{xy2}" and "{xy3}" => "{xy4}" are parallel.')
|
|
673
|
+
|
|
674
|
+
# Handle vertical lines
|
|
675
|
+
if x1 == x2:
|
|
676
|
+
m2 = (y4 - y3) / (x4 - x3)
|
|
677
|
+
b2 = y3 - m2 * x3
|
|
678
|
+
x = x1
|
|
679
|
+
y = m2 * x + b2
|
|
680
|
+
return (x, y)
|
|
681
|
+
|
|
682
|
+
if x3 == x4:
|
|
683
|
+
m1 = (y2 - y1) / (x2 - x1)
|
|
684
|
+
b1 = y1 - m1 * x1
|
|
685
|
+
x = x3
|
|
686
|
+
y = m1 * x + b1
|
|
687
|
+
return (x, y)
|
|
688
|
+
|
|
689
|
+
m1 = (y2 - y1) / (x2 - x1)
|
|
690
|
+
b1 = y1 - m1 * x1
|
|
691
|
+
m2 = (y4 - y3) / (x4 - x3)
|
|
692
|
+
b2 = y3 - m2 * x3
|
|
693
|
+
|
|
694
|
+
if m1 == m2:
|
|
695
|
+
raise ValueError(f'line "{xy1}" -> "{xy2}" and "{xy3}" => "{xy4}" are parallel.')
|
|
696
|
+
|
|
697
|
+
x = (b2 - b1) / (m1 - m2)
|
|
698
|
+
y = m1 * x + b1
|
|
699
|
+
|
|
700
|
+
return (x, y)
|
|
701
|
+
|
|
702
|
+
xys = self._original_points
|
|
568
703
|
parallel_lines: List[Tuple[Tuple[float, float], Tuple[float, float]]] = []
|
|
569
704
|
for i in range(len(xys) - 1):
|
|
570
|
-
xy1, xy2 =
|
|
705
|
+
xy1, xy2 = get_parallel_line_xys(xys[i], xys[i + 1])
|
|
571
706
|
parallel_lines.append((xy1, xy2))
|
|
572
707
|
|
|
573
708
|
points: List[Tuple[float, float]] = []
|
|
574
709
|
for i in range(len(parallel_lines) - 1):
|
|
575
710
|
xy1, xy2 = parallel_lines[i]
|
|
576
711
|
xy3, xy4 = parallel_lines[i + 1]
|
|
577
|
-
xy =
|
|
712
|
+
xy = find_lines_intersection(xy1, xy2, xy3, xy4)
|
|
578
713
|
points.append(xy)
|
|
579
714
|
|
|
580
715
|
first_point = parallel_lines[0][0]
|
|
@@ -584,53 +719,3 @@ class ArrowsHelper:
|
|
|
584
719
|
points.append(last_point)
|
|
585
720
|
|
|
586
721
|
return points
|
|
587
|
-
|
|
588
|
-
@classmethod
|
|
589
|
-
def get_parallel_line_xys(
|
|
590
|
-
cls,
|
|
591
|
-
xy1: Tuple[float, float],
|
|
592
|
-
xy2: Tuple[float, float],
|
|
593
|
-
distance: float,
|
|
594
|
-
) -> Tuple[Tuple[float, float], Tuple[float, float]]:
|
|
595
|
-
"""Internal function"""
|
|
596
|
-
x1, y1 = xy1
|
|
597
|
-
x2, y2 = xy2
|
|
598
|
-
|
|
599
|
-
# calc vector
|
|
600
|
-
vx = x2 - x1
|
|
601
|
-
vy = y2 - y1
|
|
602
|
-
length = math.sqrt(vx**2 + vy**2)
|
|
603
|
-
dx = -vy / length * distance
|
|
604
|
-
dy = vx / length * distance
|
|
605
|
-
|
|
606
|
-
# add vector
|
|
607
|
-
x1_prime = x1 + dx
|
|
608
|
-
y1_prime = y1 + dy
|
|
609
|
-
x2_prime = x2 + dx
|
|
610
|
-
y2_prime = y2 + dy
|
|
611
|
-
|
|
612
|
-
return (x1_prime, y1_prime), (x2_prime, y2_prime)
|
|
613
|
-
|
|
614
|
-
@classmethod
|
|
615
|
-
def find_lines_intersection(
|
|
616
|
-
cls,
|
|
617
|
-
xy1: Tuple[float, float],
|
|
618
|
-
xy2: Tuple[float, float],
|
|
619
|
-
xy3: Tuple[float, float],
|
|
620
|
-
xy4: Tuple[float, float],
|
|
621
|
-
) -> Tuple[float, float]:
|
|
622
|
-
"""Internal function"""
|
|
623
|
-
x1, y1 = xy1
|
|
624
|
-
x2, y2 = xy2
|
|
625
|
-
x3, y3 = xy3
|
|
626
|
-
x4, y4 = xy4
|
|
627
|
-
|
|
628
|
-
m1 = (y2 - y1) / (x2 - x1)
|
|
629
|
-
b1 = y1 - m1 * x1
|
|
630
|
-
m2 = (y4 - y3) / (x4 - x3)
|
|
631
|
-
b2 = y3 - m2 * x3
|
|
632
|
-
|
|
633
|
-
x = (b2 - b1) / (m1 - m2)
|
|
634
|
-
y = m1 * x + b1
|
|
635
|
-
|
|
636
|
-
return (x, y)
|