drawlib 0.2.2__tar.gz → 0.2.3__tar.gz
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.
- {drawlib-0.2.2 → drawlib-0.2.3}/PKG-INFO +1 -1
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/__init__.py +1 -1
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/apis.py +5 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/core/model.py +19 -1
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/core/util.py +59 -1
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/core_canvas/base.py +53 -1
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/core_canvas/canvas.py +8 -1
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/core_canvas/line.py +196 -3
- drawlib-0.2.3/drawlib/v0_2/private/core_canvas/original_arrow.py +718 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/core_canvas/patches.py +19 -69
- drawlib-0.2.3/drawlib/v0_2/private/smartarts/boxlist.py +253 -0
- drawlib-0.2.3/drawlib/v0_2/private/smartarts/boxtree.py +114 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/smartarts/bubblespeech.py +16 -16
- drawlib-0.2.3/drawlib/v0_2/private/smartarts/bulletpoints.py +188 -0
- drawlib-0.2.3/drawlib/v0_2/private/smartarts/dsart.py +25 -0
- drawlib-0.2.3/drawlib/v0_2/private/smartarts/gridlayout.py +327 -0
- drawlib-0.2.3/drawlib/v0_2/private/smartarts/pyramid.py +431 -0
- drawlib-0.2.3/drawlib/v0_2/private/smartarts/table.py +683 -0
- drawlib-0.2.3/drawlib/v0_2/private/smartarts/tree.py +309 -0
- drawlib-0.2.3/drawlib/v0_2/private/umls/activity.py +10 -0
- drawlib-0.2.3/drawlib/v0_2/private/umls/class_.py +10 -0
- drawlib-0.2.3/drawlib/v0_2/private/umls/communication.py +10 -0
- drawlib-0.2.3/drawlib/v0_2/private/umls/component.py +10 -0
- drawlib-0.2.3/drawlib/v0_2/private/umls/deployment.py +10 -0
- drawlib-0.2.3/drawlib/v0_2/private/umls/duml.py +12 -0
- drawlib-0.2.3/drawlib/v0_2/private/umls/object.py +10 -0
- drawlib-0.2.3/drawlib/v0_2/private/umls/package.py +10 -0
- drawlib-0.2.3/drawlib/v0_2/private/umls/sequence.py +10 -0
- drawlib-0.2.3/drawlib/v0_2/private/umls/state_machine.py +10 -0
- drawlib-0.2.3/drawlib/v0_2/private/umls/usecase.py +10 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/util.py +116 -1
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/validators/args.py +26 -11
- {drawlib-0.2.2 → drawlib-0.2.3}/pyproject.toml +3 -2
- drawlib-0.2.2/drawlib/v0_2/private/core_canvas/original_arrow.py +0 -139
- drawlib-0.2.2/drawlib/v0_2/private/smartarts/dsart.py +0 -15
- {drawlib-0.2.2 → drawlib-0.2.3}/LICENSE.txt +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/__main__.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/apis.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/assets/__init__.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/assets/v0_2/__init__.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/assets/v0_2/fonticons/__init__.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/assets/v0_2/fonts/__init__.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/py.typed +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/__init__.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/__main__.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/__init__.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/command.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/core/__init__.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/core/colors.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/core/dimage.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/core/fonts.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/core/model_system_default.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/core/theme.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/core/theme_officials.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/core/theme_style_caches.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/core_canvas/__init__.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/core_canvas/image.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/core_canvas/original_polygon.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/core_canvas/text.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/download.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/dutil/__init__.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/dutil/dutil_canvas.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/dutil/dutil_color.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/dutil/dutil_script.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/dutil/settings.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/icons/__init__.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/icons/phosphor.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/icons/util.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/logging.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/smartarts/__init__.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/smartarts/sourcecode.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/validators/__init__.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/validators/color.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/validators/coordinate.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/validators/icon.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/validators/image.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/validators/line.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/validators/shape.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/validators/smartarts.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/validators/style.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/validators/text.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/drawlib/v0_2/private/validators/types.py +0 -0
- {drawlib-0.2.2 → drawlib-0.2.3}/pypi.md +0 -0
|
@@ -14,7 +14,7 @@ import sys
|
|
|
14
14
|
from typing import Final, List
|
|
15
15
|
|
|
16
16
|
# please update here when you release new version
|
|
17
|
-
LIB_VERSION = "0.2.
|
|
17
|
+
LIB_VERSION: Final[str] = "0.2.3"
|
|
18
18
|
|
|
19
19
|
# please list active main committers (1+ commits per month)
|
|
20
20
|
AUTHORS: Final[List[str]] = [
|
|
@@ -54,6 +54,10 @@ from drawlib.v0_2.private.core_canvas.canvas import (
|
|
|
54
54
|
arc,
|
|
55
55
|
# shape original
|
|
56
56
|
arrow,
|
|
57
|
+
arrow_arc,
|
|
58
|
+
arrow_l,
|
|
59
|
+
arrow_polyline,
|
|
60
|
+
arrow_u,
|
|
57
61
|
chevron,
|
|
58
62
|
circle,
|
|
59
63
|
# base
|
|
@@ -66,6 +70,7 @@ from drawlib.v0_2.private.core_canvas.canvas import (
|
|
|
66
70
|
image,
|
|
67
71
|
# line
|
|
68
72
|
line,
|
|
73
|
+
line_arc,
|
|
69
74
|
line_bezier1,
|
|
70
75
|
line_bezier2,
|
|
71
76
|
line_curved,
|
|
@@ -883,6 +883,7 @@ class ShapeTextStyle:
|
|
|
883
883
|
angle: Optional[float] = None
|
|
884
884
|
flip: Optional[bool] = None
|
|
885
885
|
xy_shift: Optional[Tuple[float, float]] = None
|
|
886
|
+
xy_abs_shift: Optional[Tuple[float, float]] = None
|
|
886
887
|
|
|
887
888
|
@error_handler
|
|
888
889
|
def copy(self) -> ShapeTextStyle:
|
|
@@ -1107,7 +1108,7 @@ class ShapeTextStyle:
|
|
|
1107
1108
|
|
|
1108
1109
|
@xy_shift.setter
|
|
1109
1110
|
@error_handler
|
|
1110
|
-
def xy_shift(self, value: Optional[float]) -> None:
|
|
1111
|
+
def xy_shift(self, value: Optional[Tuple[float, float]]) -> None:
|
|
1111
1112
|
"""Setter of xy_shift"""
|
|
1112
1113
|
if isinstance(value, property) or value is None:
|
|
1113
1114
|
self._xy_shift = None
|
|
@@ -1116,6 +1117,23 @@ class ShapeTextStyle:
|
|
|
1116
1117
|
coordinate_validator.validate_xy("ShapeTextStyle.xy_shift", value)
|
|
1117
1118
|
self._xy_shift = value
|
|
1118
1119
|
|
|
1120
|
+
@property # type: ignore[no-redef]
|
|
1121
|
+
@error_handler
|
|
1122
|
+
def xy_abs_shift(self) -> Optional[Tuple[float, float]]:
|
|
1123
|
+
"""Getter of xy_abs_shift"""
|
|
1124
|
+
return self._xy_abs_shift
|
|
1125
|
+
|
|
1126
|
+
@xy_abs_shift.setter
|
|
1127
|
+
@error_handler
|
|
1128
|
+
def xy_abs_shift(self, value: Optional[Tuple[float, float]]) -> None:
|
|
1129
|
+
"""Setter of xy_abs_shift"""
|
|
1130
|
+
if isinstance(value, property) or value is None:
|
|
1131
|
+
self._xy_abs_shift = None
|
|
1132
|
+
return
|
|
1133
|
+
|
|
1134
|
+
coordinate_validator.validate_xy("ShapeTextStyle.xy_abs_shift", value)
|
|
1135
|
+
self._xy_abs_shift = value
|
|
1136
|
+
|
|
1119
1137
|
|
|
1120
1138
|
@dataclasses.dataclass
|
|
1121
1139
|
class TextStyle:
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"""Utility module for converting drawlib data to matplotlib data."""
|
|
12
12
|
|
|
13
13
|
import math
|
|
14
|
-
from typing import Any, Callable, Dict, Literal, Optional, Tuple, Union
|
|
14
|
+
from typing import Any, Callable, Dict, List, Literal, Optional, Tuple, Union
|
|
15
15
|
|
|
16
16
|
from matplotlib.font_manager import FontProperties
|
|
17
17
|
from matplotlib.text import Text
|
|
@@ -238,6 +238,58 @@ class ImageUtil:
|
|
|
238
238
|
class LineUtil:
|
|
239
239
|
"""A utility class for handling line styles and options."""
|
|
240
240
|
|
|
241
|
+
@staticmethod
|
|
242
|
+
def sanitize_xys(xys: List[Tuple[float, float]]) -> List[Tuple[float, float]]:
|
|
243
|
+
def remove_consecutive_duplicates(xys: List[Tuple[float, float]]) -> List[Tuple[float, float]]:
|
|
244
|
+
return [v for i, v in enumerate(xys) if i == 0 or v != xys[i - 1]]
|
|
245
|
+
|
|
246
|
+
def merge_straight_lines(xys: List[Tuple[float, float]]) -> List[Tuple[float, float]]: # noqa: C901
|
|
247
|
+
points: List[Tuple[float, float]] = []
|
|
248
|
+
|
|
249
|
+
skip_next = False
|
|
250
|
+
for i in range(len(xys) - 1):
|
|
251
|
+
if skip_next:
|
|
252
|
+
skip_next = False
|
|
253
|
+
continue
|
|
254
|
+
|
|
255
|
+
if i == 0:
|
|
256
|
+
points.append(xys[i])
|
|
257
|
+
continue
|
|
258
|
+
|
|
259
|
+
x0, y0 = points[-1]
|
|
260
|
+
x1, y1 = xys[i]
|
|
261
|
+
x2, y2 = xys[i + 1]
|
|
262
|
+
if x1 - x0 == 0:
|
|
263
|
+
if x2 - x1 == 0:
|
|
264
|
+
points.append(xys[i + 1])
|
|
265
|
+
skip_next = True
|
|
266
|
+
continue
|
|
267
|
+
else:
|
|
268
|
+
points.append(xys[i])
|
|
269
|
+
if i == len(xys) - 2:
|
|
270
|
+
points.append(xys[i + 1])
|
|
271
|
+
continue
|
|
272
|
+
|
|
273
|
+
if x2 - x1 == 0:
|
|
274
|
+
points.append(xys[i])
|
|
275
|
+
if i == len(xys) - 2:
|
|
276
|
+
points.append(xys[i + 1])
|
|
277
|
+
continue
|
|
278
|
+
|
|
279
|
+
m1 = (y1 - y0) / (x1 - x0)
|
|
280
|
+
m2 = (y2 - y1) / (x2 - x1)
|
|
281
|
+
if m1 == m2:
|
|
282
|
+
skip_next = True
|
|
283
|
+
points.append(xys[i + 1])
|
|
284
|
+
else:
|
|
285
|
+
points.append(xys[i])
|
|
286
|
+
if i == len(xys) - 2:
|
|
287
|
+
points.append(xys[i + 1])
|
|
288
|
+
|
|
289
|
+
return points
|
|
290
|
+
|
|
291
|
+
return merge_straight_lines(remove_consecutive_duplicates(xys))
|
|
292
|
+
|
|
241
293
|
@staticmethod
|
|
242
294
|
def format_style(
|
|
243
295
|
style: Union[LineStyle, str, None],
|
|
@@ -498,6 +550,7 @@ class ShapeUtil:
|
|
|
498
550
|
text_angle = (text_angle + 180) % 360
|
|
499
551
|
|
|
500
552
|
x, y = xy
|
|
553
|
+
# shift
|
|
501
554
|
if style.xy_shift is not None:
|
|
502
555
|
x_shift, y_shift = style.xy_shift
|
|
503
556
|
if shape_angle == 0:
|
|
@@ -510,6 +563,11 @@ class ShapeUtil:
|
|
|
510
563
|
x += rotated_x_shift
|
|
511
564
|
y += rotated_y_shift
|
|
512
565
|
|
|
566
|
+
# absolute shift
|
|
567
|
+
if style.xy_abs_shift is not None:
|
|
568
|
+
x += style.xy_abs_shift[0]
|
|
569
|
+
y += style.xy_abs_shift[1]
|
|
570
|
+
|
|
513
571
|
# only check color. ignore alignment
|
|
514
572
|
options = TextUtil.get_text_options(style)
|
|
515
573
|
if "horizontalalignment" in options:
|
|
@@ -18,7 +18,10 @@ import matplotlib.lines
|
|
|
18
18
|
import matplotlib.text
|
|
19
19
|
import PIL.Image
|
|
20
20
|
from matplotlib import pyplot
|
|
21
|
-
from matplotlib.patches import
|
|
21
|
+
from matplotlib.patches import (
|
|
22
|
+
PathPatch,
|
|
23
|
+
Polygon,
|
|
24
|
+
)
|
|
22
25
|
from matplotlib.path import Path
|
|
23
26
|
|
|
24
27
|
import drawlib.v0_2.private.validators.args as validator
|
|
@@ -235,6 +238,55 @@ class CanvasBase:
|
|
|
235
238
|
# Shape
|
|
236
239
|
#
|
|
237
240
|
|
|
241
|
+
@error_handler
|
|
242
|
+
def polygon(
|
|
243
|
+
self,
|
|
244
|
+
xys: List[Tuple[float, float]],
|
|
245
|
+
style: Union[ShapeStyle, str, None] = None,
|
|
246
|
+
text: str = "",
|
|
247
|
+
textsize: Optional[float] = None,
|
|
248
|
+
textstyle: Union[ShapeTextStyle, str, None] = None,
|
|
249
|
+
) -> None:
|
|
250
|
+
"""Draw a polygon on the canvas.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
xys: List of vertices [(x1, y1), ...(x_n, y_n)].
|
|
254
|
+
style (optional): Style of the polygon.
|
|
255
|
+
text (optional): Text shown at the center of the polygon.
|
|
256
|
+
textsize (optional): Font size of the text.
|
|
257
|
+
textstyle (optional): Style of the text.
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
None
|
|
261
|
+
"""
|
|
262
|
+
style, textstyle = ShapeUtil.format_styles(
|
|
263
|
+
style,
|
|
264
|
+
textstyle,
|
|
265
|
+
dtheme.polygonstyles.get,
|
|
266
|
+
dtheme.polygontextstyles.get,
|
|
267
|
+
)
|
|
268
|
+
validator.validate_shape_args(locals())
|
|
269
|
+
|
|
270
|
+
if textsize is not None:
|
|
271
|
+
textstyle.size = textsize
|
|
272
|
+
|
|
273
|
+
style.halign = None
|
|
274
|
+
style.valign = None
|
|
275
|
+
options = ShapeUtil.get_shape_options(style)
|
|
276
|
+
self._artists.append(Polygon(xy=xys, closed=True, **options))
|
|
277
|
+
|
|
278
|
+
if not text:
|
|
279
|
+
return
|
|
280
|
+
center, (_, _) = get_center_and_size(xys)
|
|
281
|
+
self._artists.append(
|
|
282
|
+
ShapeUtil.get_shape_text(
|
|
283
|
+
center,
|
|
284
|
+
text=text,
|
|
285
|
+
angle=0,
|
|
286
|
+
style=textstyle,
|
|
287
|
+
),
|
|
288
|
+
)
|
|
289
|
+
|
|
238
290
|
@error_handler
|
|
239
291
|
def shape( # noqa: C901
|
|
240
292
|
self,
|
|
@@ -266,6 +266,7 @@ line = canvas.line
|
|
|
266
266
|
line_curved = canvas.line_curved
|
|
267
267
|
line_bezier1 = canvas.line_bezier1
|
|
268
268
|
line_bezier2 = canvas.line_bezier2
|
|
269
|
+
line_arc = canvas.line_arc
|
|
269
270
|
lines = canvas.lines
|
|
270
271
|
lines_curved = canvas.lines_curved
|
|
271
272
|
lines_bezier = canvas.lines_bezier
|
|
@@ -275,10 +276,16 @@ rhombus = canvas.rhombus
|
|
|
275
276
|
trapezoid = canvas.trapezoid
|
|
276
277
|
parallelogram = canvas.parallelogram
|
|
277
278
|
triangle = canvas.triangle
|
|
278
|
-
arrow = canvas.arrow
|
|
279
279
|
star = canvas.star
|
|
280
280
|
chevron = canvas.chevron
|
|
281
281
|
|
|
282
|
+
# original arrow
|
|
283
|
+
arrow = canvas.arrow
|
|
284
|
+
arrow_polyline = canvas.arrow_polyline
|
|
285
|
+
arrow_arc = canvas.arrow_arc
|
|
286
|
+
arrow_l = canvas.arrow_l
|
|
287
|
+
arrow_u = canvas.arrow_u
|
|
288
|
+
|
|
282
289
|
# patches
|
|
283
290
|
arc = canvas.arc
|
|
284
291
|
circle = canvas.circle
|
|
@@ -20,7 +20,7 @@ import drawlib.v0_2.private.validators.args as validator
|
|
|
20
20
|
from drawlib.v0_2.private.core.model import LineStyle
|
|
21
21
|
from drawlib.v0_2.private.core.util import LineUtil
|
|
22
22
|
from drawlib.v0_2.private.core_canvas.base import CanvasBase
|
|
23
|
-
from drawlib.v0_2.private.util import error_handler
|
|
23
|
+
from drawlib.v0_2.private.util import error_handler, get_rotated_path_points
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
class CanvasLineFeature(CanvasBase):
|
|
@@ -191,6 +191,62 @@ class CanvasLineFeature(CanvasBase):
|
|
|
191
191
|
style=style,
|
|
192
192
|
)
|
|
193
193
|
|
|
194
|
+
@error_handler
|
|
195
|
+
def line_arc(
|
|
196
|
+
self,
|
|
197
|
+
xy: Tuple[float, float],
|
|
198
|
+
width: float,
|
|
199
|
+
height: float,
|
|
200
|
+
angle_start: float = 0,
|
|
201
|
+
angle_end: float = 180,
|
|
202
|
+
angle: float = 0,
|
|
203
|
+
arrowhead: Literal["", "->", "<-", "<->"] = "",
|
|
204
|
+
lwidth: Optional[float] = None,
|
|
205
|
+
style: Union[LineStyle, str, None] = None,
|
|
206
|
+
) -> None:
|
|
207
|
+
"""Draw arc line on ellipse.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
xy: Tuple[float, float]: The center point of the ellipse from which the arc is drawn.
|
|
211
|
+
width: float: The width of the ellipse.
|
|
212
|
+
height: float: The height of the ellipse
|
|
213
|
+
angle_start: float: The starting angle of the arc in degrees (default is 0).
|
|
214
|
+
angle_end: float: The ending angle of the arc in degrees (default is 180).
|
|
215
|
+
angle (float): The angle of ellipse.
|
|
216
|
+
arrowhead: Literal["", "->", "<-", "<->"]: Optional arrowhead style ("", "->", "<-", "<->").
|
|
217
|
+
lwidth: Optional[float]: Optional width of the line.
|
|
218
|
+
style: Union[LineStyle, str, None]: Optional line style.
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
None
|
|
222
|
+
"""
|
|
223
|
+
style = LineUtil.format_style(style)
|
|
224
|
+
# validator.validate_line_args(locals())
|
|
225
|
+
|
|
226
|
+
if angle_start > angle_end:
|
|
227
|
+
angle_start = -1 * (360 - angle_start)
|
|
228
|
+
path_points = LineArcHelper.get_ellipse_path_points(
|
|
229
|
+
xy,
|
|
230
|
+
width,
|
|
231
|
+
height,
|
|
232
|
+
angle_start,
|
|
233
|
+
angle_end,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
if angle != 0:
|
|
237
|
+
path_points = get_rotated_path_points(path_points, xy, angle) # type: ignore
|
|
238
|
+
|
|
239
|
+
start_point = path_points[0]
|
|
240
|
+
path_points = path_points[1:]
|
|
241
|
+
|
|
242
|
+
self.lines_bezier(
|
|
243
|
+
start_point, # type: ignore
|
|
244
|
+
path_points=path_points, # type: ignore
|
|
245
|
+
width=lwidth,
|
|
246
|
+
arrowhead=arrowhead,
|
|
247
|
+
style=style,
|
|
248
|
+
)
|
|
249
|
+
|
|
194
250
|
@error_handler
|
|
195
251
|
def lines(
|
|
196
252
|
self,
|
|
@@ -212,7 +268,7 @@ class CanvasLineFeature(CanvasBase):
|
|
|
212
268
|
"""
|
|
213
269
|
style = LineUtil.format_style(style)
|
|
214
270
|
validator.validate_line_args(locals())
|
|
215
|
-
|
|
271
|
+
xys = LineUtil.sanitize_xys(xys)
|
|
216
272
|
self.lines_bezier(
|
|
217
273
|
xy=xys[0],
|
|
218
274
|
path_points=xys[1:], # type: ignore
|
|
@@ -244,10 +300,12 @@ class CanvasLineFeature(CanvasBase):
|
|
|
244
300
|
"""
|
|
245
301
|
style = LineUtil.format_style(style)
|
|
246
302
|
validator.validate_line_args(locals())
|
|
303
|
+
|
|
247
304
|
if len(xys) == 2:
|
|
248
|
-
self.
|
|
305
|
+
self.line(xys[0], xys[1], width=width, arrowhead=arrowhead, style=style)
|
|
249
306
|
return
|
|
250
307
|
|
|
308
|
+
xys = LineUtil.sanitize_xys(xys)
|
|
251
309
|
path_points = []
|
|
252
310
|
last_i = len(xys) - 2
|
|
253
311
|
# last_xy = (0, 0)
|
|
@@ -332,6 +390,141 @@ class CanvasLineFeature(CanvasBase):
|
|
|
332
390
|
self._artists.append(FancyArrowPatch(path=path, **options))
|
|
333
391
|
|
|
334
392
|
|
|
393
|
+
class LineArcHelper:
|
|
394
|
+
"""Internal class"""
|
|
395
|
+
|
|
396
|
+
@classmethod
|
|
397
|
+
def get_point_on_ellipse(
|
|
398
|
+
cls,
|
|
399
|
+
xy: Tuple[float, float],
|
|
400
|
+
width: float,
|
|
401
|
+
height: float,
|
|
402
|
+
angle: float,
|
|
403
|
+
) -> Tuple[float, float]:
|
|
404
|
+
"""Internal function"""
|
|
405
|
+
x, y = xy
|
|
406
|
+
# Convert angle from degrees to radians
|
|
407
|
+
angle = math.radians(angle)
|
|
408
|
+
|
|
409
|
+
# Calculate the coordinates of the point
|
|
410
|
+
point_x = x + width / 2 * math.cos(angle)
|
|
411
|
+
point_y = y + height / 2 * math.sin(angle)
|
|
412
|
+
|
|
413
|
+
return point_x, point_y
|
|
414
|
+
|
|
415
|
+
@classmethod
|
|
416
|
+
def get_ellipse_path_points(
|
|
417
|
+
cls,
|
|
418
|
+
xy: Tuple[float, float],
|
|
419
|
+
width: float,
|
|
420
|
+
height: float,
|
|
421
|
+
angle_start: float,
|
|
422
|
+
angle_end: float,
|
|
423
|
+
) -> List[
|
|
424
|
+
Union[
|
|
425
|
+
Tuple[float, float],
|
|
426
|
+
Tuple[Tuple[float, float], Tuple[float, float], Tuple[float, float]],
|
|
427
|
+
]
|
|
428
|
+
]:
|
|
429
|
+
"""Internal function"""
|
|
430
|
+
diff = angle_end - angle_start
|
|
431
|
+
if abs(diff) > 270:
|
|
432
|
+
# having +2 for avoiding situation next_mid_angle == angle_end
|
|
433
|
+
step = int(diff / 3)
|
|
434
|
+
elif abs(diff) > 135:
|
|
435
|
+
step = int(diff / 2)
|
|
436
|
+
else:
|
|
437
|
+
p1, p2, p3, p4 = cls.bezier_ellipse_arc_approximation(
|
|
438
|
+
xy,
|
|
439
|
+
width,
|
|
440
|
+
height,
|
|
441
|
+
angle_start,
|
|
442
|
+
angle_end,
|
|
443
|
+
)
|
|
444
|
+
return [p1, (p2, p3, p4)]
|
|
445
|
+
if diff >= 0:
|
|
446
|
+
diff += 2
|
|
447
|
+
else:
|
|
448
|
+
diff -= 2
|
|
449
|
+
|
|
450
|
+
i = 0
|
|
451
|
+
start = None
|
|
452
|
+
path_points = []
|
|
453
|
+
while True:
|
|
454
|
+
last_mid_angle = angle_start + step * i
|
|
455
|
+
next_mid_angle = angle_start + step * (i + 1)
|
|
456
|
+
|
|
457
|
+
if angle_start < angle_end:
|
|
458
|
+
# anti clock wise
|
|
459
|
+
is_last = next_mid_angle > angle_end
|
|
460
|
+
else:
|
|
461
|
+
# clock wise
|
|
462
|
+
is_last = angle_end > next_mid_angle
|
|
463
|
+
|
|
464
|
+
if is_last:
|
|
465
|
+
p1, p2, p3, p4 = cls.bezier_ellipse_arc_approximation(
|
|
466
|
+
xy,
|
|
467
|
+
width,
|
|
468
|
+
height,
|
|
469
|
+
last_mid_angle,
|
|
470
|
+
angle_end,
|
|
471
|
+
)
|
|
472
|
+
if start is None:
|
|
473
|
+
start = p1
|
|
474
|
+
path_points.append((p2, p3, p4))
|
|
475
|
+
break
|
|
476
|
+
|
|
477
|
+
else:
|
|
478
|
+
p1, p2, p3, p4 = cls.bezier_ellipse_arc_approximation(
|
|
479
|
+
xy,
|
|
480
|
+
width,
|
|
481
|
+
height,
|
|
482
|
+
last_mid_angle,
|
|
483
|
+
next_mid_angle,
|
|
484
|
+
)
|
|
485
|
+
if start is None:
|
|
486
|
+
start = p1
|
|
487
|
+
path_points.append((p2, p3, p4))
|
|
488
|
+
|
|
489
|
+
i += 1
|
|
490
|
+
|
|
491
|
+
path_points.insert(0, start)
|
|
492
|
+
return path_points
|
|
493
|
+
|
|
494
|
+
@classmethod
|
|
495
|
+
def bezier_ellipse_arc_approximation(
|
|
496
|
+
cls,
|
|
497
|
+
xy: Tuple[float, float],
|
|
498
|
+
width: float,
|
|
499
|
+
height: float,
|
|
500
|
+
start_angle: float,
|
|
501
|
+
end_angle: float,
|
|
502
|
+
) -> Tuple[Tuple[float, float], Tuple[float, float], Tuple[float, float], Tuple[float, float]]:
|
|
503
|
+
"""Internal function"""
|
|
504
|
+
x, y = xy
|
|
505
|
+
|
|
506
|
+
# Convert angles from degrees to radians
|
|
507
|
+
start_angle = math.radians(start_angle)
|
|
508
|
+
end_angle = math.radians(end_angle)
|
|
509
|
+
|
|
510
|
+
# Calculate the points on the ellipse at the start and end angles
|
|
511
|
+
start_point = (x + width / 2 * math.cos(start_angle), y + height / 2 * math.sin(start_angle))
|
|
512
|
+
end_point = (x + width / 2 * math.cos(end_angle), y + height / 2 * math.sin(end_angle))
|
|
513
|
+
|
|
514
|
+
# Calculate the control points for the Bezier curve
|
|
515
|
+
t = (4 / 3) * math.tan((end_angle - start_angle) / 4)
|
|
516
|
+
control_point1 = (
|
|
517
|
+
start_point[0] - t * width / 2 * math.sin(start_angle),
|
|
518
|
+
start_point[1] + t * height / 2 * math.cos(start_angle),
|
|
519
|
+
)
|
|
520
|
+
control_point2 = (
|
|
521
|
+
end_point[0] + t * width / 2 * math.sin(end_angle),
|
|
522
|
+
end_point[1] - t * height / 2 * math.cos(end_angle),
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
return start_point, control_point1, control_point2, end_point
|
|
526
|
+
|
|
527
|
+
|
|
335
528
|
def _get_mid_points(
|
|
336
529
|
a: Tuple[float, float],
|
|
337
530
|
b: Tuple[float, float],
|