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.

@@ -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 arrows(
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
- parallel_xys1 = ArrowsHelper.get_parallel_lines_xys(xys, tail_width / 2)
188
- parallel_xys2 = ArrowsHelper.get_parallel_lines_xys(xys, tail_width / 2 * -1)
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
- arrowhead_start_tail_xy1 = ArrowsHelper.get_point_on_line(parallel_xys1[0], parallel_xys1[1], head_length)
191
- arrowhead_start_tail_xy2 = ArrowsHelper.get_point_on_line(parallel_xys2[0], parallel_xys2[1], head_length)
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
- points1.append(arrowhead_end_tail_xy1)
211
+ xys_start = xys[0]
224
212
 
225
- points2: List[Tuple[float, float]] = []
226
- if head == "<-":
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
- path_points1 = points1 # type: ignore
256
- path_points2 = points2 # type: ignore
216
+ xys_end = xys[-1]
257
217
 
258
- if head != "->":
259
- path_points1.insert(0, xys[0])
260
- path_points1.insert(1, arrowhead_start_head_xy1)
261
- if head != "<-":
262
- path_points1.append(arrowhead_end_head_xy1)
263
- path_points1.append(xys[len(xys) - 1])
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
- path_points2.insert(0, arrowhead_end_head_xy2)
267
- if head != "->":
268
- path_points2.append(arrowhead_start_head_xy2)
269
-
270
- path_points1.extend(path_points2)
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
- # create PathPatch
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
- options = ShapeUtil.get_shape_options(style)
299
- self._artists.append(PathPatch(path=path, **options))
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
- from_angle: float = 0,
312
- to_angle: float = 180,
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
- from_angle: float: The starting angle of the arc in degrees (default is 0).
326
- to_angle: float: The ending angle of the arc in degrees (default is 180).
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
- from_angle2 = from_angle + head_angle
347
- from_angle2 %= 360
290
+ angle_start2 = angle_start + head_angle
291
+ angle_start2 %= 360
348
292
  else:
349
- from_angle2 = from_angle
293
+ angle_start2 = angle_start
350
294
 
351
295
  if head in {"->", "<->"}:
352
- to_angle2 = to_angle - head_angle
353
- to_angle2 %= 360
296
+ angle_end2 = angle_end - head_angle
297
+ angle_end2 %= 360
354
298
  else:
355
- to_angle2 = to_angle
299
+ angle_end2 = angle_end
356
300
 
357
- if from_angle2 > to_angle2:
358
- pp1_fa = -1 * (360 - from_angle2)
359
- pp1_ta = to_angle2
360
- pp2_fa = to_angle2
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 = from_angle2
364
- pp1_ta = to_angle2
365
- pp2_fa = to_angle2
366
- pp2_ta = from_angle2
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 from_angle > to_angle:
384
- from_angle = -1 * (360 - from_angle)
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
- from_angle,
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
- from_angle2,
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
- from_angle2,
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
- to_angle2,
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
- to_angle,
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
- to_angle2,
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
- class ArrowsHelper:
534
+
535
+ class ArrowPolylineHelper:
474
536
  """Internal class"""
475
537
 
476
- @classmethod
477
- def get_path_points(
478
- cls,
538
+ def __init__(
539
+ self,
479
540
  xys: List[Tuple[float, float]],
480
541
  r: float,
481
- ) -> List[
482
- Union[
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 _get_mid_points(
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
- path_points: List[
513
- Union[
514
- Tuple[float, float],
515
- Tuple[Tuple[float, float], Tuple[float, float]],
516
- Tuple[Tuple[float, float], Tuple[float, float], Tuple[float, float]],
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, xy in enumerate(xys):
583
+ for i in range(len(xys)):
584
+ # first straight line
523
585
  if i == 0:
524
- _, p = _get_mid_points(xy, xys[i + 1], r)
525
- path_points.append(p)
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
- p, _ = _get_mid_points(xy, xys[i + 1], r)
530
- path_points.append((xy, p))
531
- path_points.append(xys[i + 1])
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 = _get_mid_points(xy, xys[i + 1], r)
535
- path_points.append((xy, p1))
536
- path_points.append(p2)
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
- path_points.insert(0, xys[0])
539
- return path_points
603
+ self._original_points = remove_consecutive_duplicates(points)
540
604
 
541
- @classmethod
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
- x1, y1 = xy1
550
- x2, y2 = xy2
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
- return (x, y)
611
+ def _get_parallel_curved_points(self, distance: float) -> List[Tuple[float, float]]:
612
+ """Internal function"""
560
613
 
561
- @classmethod
562
- def get_parallel_lines_xys(
563
- cls,
564
- xys: List[Tuple[float, float]],
565
- distance: float,
566
- ) -> List[Tuple[float, float]]:
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 = cls.get_parallel_line_xys(xys[i], xys[i + 1], distance)
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 = cls.find_lines_intersection(xy1, xy2, xy3, xy4)
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)