drawlib 0.2.3.dev5__tar.gz → 0.2.3.dev6__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.

Potentially problematic release.


This version of drawlib might be problematic. Click here for more details.

Files changed (82) hide show
  1. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/PKG-INFO +1 -1
  2. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/__init__.py +1 -1
  3. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/core/util.py +53 -1
  4. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/core_canvas/line.py +4 -2
  5. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/core_canvas/original_arrow.py +3 -6
  6. drawlib-0.2.3.dev6/drawlib/v0_2/private/smartarts/boxlist.py +257 -0
  7. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/smartarts/gridlayout.py +26 -25
  8. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/smartarts/pyramid.py +34 -23
  9. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/pyproject.toml +1 -1
  10. drawlib-0.2.3.dev5/drawlib/v0_2/private/smartarts/boxlist.py +0 -178
  11. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/LICENSE.txt +0 -0
  12. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/__main__.py +0 -0
  13. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/apis.py +0 -0
  14. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/assets/__init__.py +0 -0
  15. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/assets/v0_2/__init__.py +0 -0
  16. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/assets/v0_2/fonticons/__init__.py +0 -0
  17. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/assets/v0_2/fonts/__init__.py +0 -0
  18. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/py.typed +0 -0
  19. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/__init__.py +0 -0
  20. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/__main__.py +0 -0
  21. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/apis.py +0 -0
  22. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/__init__.py +0 -0
  23. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/command.py +0 -0
  24. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/core/__init__.py +0 -0
  25. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/core/colors.py +0 -0
  26. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/core/dimage.py +0 -0
  27. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/core/fonts.py +0 -0
  28. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/core/model.py +0 -0
  29. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/core/model_system_default.py +0 -0
  30. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/core/theme.py +0 -0
  31. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/core/theme_officials.py +0 -0
  32. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/core/theme_style_caches.py +0 -0
  33. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/core_canvas/__init__.py +0 -0
  34. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/core_canvas/base.py +0 -0
  35. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/core_canvas/canvas.py +0 -0
  36. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/core_canvas/image.py +0 -0
  37. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/core_canvas/original_polygon.py +0 -0
  38. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/core_canvas/patches.py +0 -0
  39. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/core_canvas/text.py +0 -0
  40. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/download.py +0 -0
  41. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/dutil/__init__.py +0 -0
  42. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/dutil/dutil_canvas.py +0 -0
  43. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/dutil/dutil_color.py +0 -0
  44. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/dutil/dutil_script.py +0 -0
  45. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/dutil/settings.py +0 -0
  46. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/icons/__init__.py +0 -0
  47. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/icons/phosphor.py +0 -0
  48. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/icons/util.py +0 -0
  49. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/logging.py +0 -0
  50. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/smartarts/__init__.py +0 -0
  51. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/smartarts/boxtree.py +0 -0
  52. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/smartarts/bubblespeech.py +0 -0
  53. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/smartarts/bulletpoints.py +0 -0
  54. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/smartarts/dsart.py +0 -0
  55. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/smartarts/sourcecode.py +0 -0
  56. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/smartarts/table.py +0 -0
  57. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/smartarts/tree.py +0 -0
  58. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/umls/activity.py +0 -0
  59. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/umls/class_.py +0 -0
  60. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/umls/communication.py +0 -0
  61. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/umls/component.py +0 -0
  62. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/umls/deployment.py +0 -0
  63. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/umls/duml.py +0 -0
  64. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/umls/object.py +0 -0
  65. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/umls/package.py +0 -0
  66. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/umls/sequence.py +0 -0
  67. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/umls/state_machine.py +0 -0
  68. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/umls/usecase.py +0 -0
  69. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/util.py +0 -0
  70. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/validators/__init__.py +0 -0
  71. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/validators/args.py +0 -0
  72. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/validators/color.py +0 -0
  73. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/validators/coordinate.py +0 -0
  74. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/validators/icon.py +0 -0
  75. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/validators/image.py +0 -0
  76. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/validators/line.py +0 -0
  77. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/validators/shape.py +0 -0
  78. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/validators/smartarts.py +0 -0
  79. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/validators/style.py +0 -0
  80. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/validators/text.py +0 -0
  81. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/drawlib/v0_2/private/validators/types.py +0 -0
  82. {drawlib-0.2.3.dev5 → drawlib-0.2.3.dev6}/pypi.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: drawlib
3
- Version: 0.2.3.dev5
3
+ Version: 0.2.3.dev6
4
4
  Summary: Python drawing library. Illustration as Code.
5
5
  Home-page: https://www.drawlib.com
6
6
  Author: Yuichi Ito
@@ -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.3.dev5"
17
+ LIB_VERSION = "0.2.3.dev6"
18
18
 
19
19
  # please list active main committers (1+ commits per month)
20
20
  AUTHORS: Final[List[str]] = [
@@ -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],
@@ -268,7 +268,7 @@ class CanvasLineFeature(CanvasBase):
268
268
  """
269
269
  style = LineUtil.format_style(style)
270
270
  validator.validate_line_args(locals())
271
-
271
+ xys = LineUtil.sanitize_xys(xys)
272
272
  self.lines_bezier(
273
273
  xy=xys[0],
274
274
  path_points=xys[1:], # type: ignore
@@ -300,10 +300,12 @@ class CanvasLineFeature(CanvasBase):
300
300
  """
301
301
  style = LineUtil.format_style(style)
302
302
  validator.validate_line_args(locals())
303
+
303
304
  if len(xys) == 2:
304
- self.lines(xys, width=width, arrowhead=arrowhead, style=style)
305
+ self.line(xys[0], xys[1], width=width, arrowhead=arrowhead, style=style)
305
306
  return
306
307
 
308
+ xys = LineUtil.sanitize_xys(xys)
307
309
  path_points = []
308
310
  last_i = len(xys) - 2
309
311
  # last_xy = (0, 0)
@@ -19,7 +19,7 @@ from matplotlib.path import Path
19
19
  import drawlib.v0_2.private.validators.args as validator
20
20
  from drawlib.v0_2.private.core.model import ShapeStyle, ShapeTextStyle
21
21
  from drawlib.v0_2.private.core.theme import dtheme
22
- from drawlib.v0_2.private.core.util import ShapeUtil
22
+ from drawlib.v0_2.private.core.util import LineUtil, ShapeUtil
23
23
  from drawlib.v0_2.private.core_canvas.base import CanvasBase
24
24
  from drawlib.v0_2.private.core_canvas.line import LineArcHelper
25
25
  from drawlib.v0_2.private.util import (
@@ -205,6 +205,7 @@ class CanvasOriginalArrowFeature(CanvasBase):
205
205
  new_point = (a[0] + scaled_vector[0], a[1] + scaled_vector[1])
206
206
  return new_point
207
207
 
208
+ xys = LineUtil.sanitize_xys(xys)
208
209
  if head in {"<-", "<->"}:
209
210
  xys_start = point_on_line(xys[0], xys[1], head_length)
210
211
  else:
@@ -568,9 +569,6 @@ class ArrowPolylineHelper:
568
569
  curve.append((x, y))
569
570
  return curve
570
571
 
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
572
  self._r = r
575
573
  if r == 0:
576
574
  self._original_points = xys[:]
@@ -600,7 +598,7 @@ class ArrowPolylineHelper:
600
598
  points.extend(get_points(bezier_points))
601
599
  bezier_start = p2
602
600
 
603
- self._original_points = remove_consecutive_duplicates(points)
601
+ self._original_points = points
604
602
 
605
603
  def get_parallel_points(self, distance: float) -> List[Tuple[float, float]]:
606
604
  """Internal function"""
@@ -619,7 +617,6 @@ class ArrowPolylineHelper:
619
617
  p0, p1 = self._original_points[i - 1], self._original_points[i]
620
618
  dx, dy = p1[0] - p0[0], p1[1] - p0[1]
621
619
  length = get_distance(p0, p1)
622
- print(p0, p1, length)
623
620
  nx, ny = -dy / length, dx / length
624
621
  px0, py0 = p0[0] + distance * nx, p0[1] + distance * ny
625
622
  px1, py1 = p1[0] + distance * nx, p1[1] + distance * ny
@@ -0,0 +1,257 @@
1
+ # Copyright (c) 2024 Yuichi Ito (yuichi@yuichi.com)
2
+ #
3
+ # This software is licensed under the Apache License, Version 2.0.
4
+ # For more information, please visit: https://github.com/yuichi110/drawlib
5
+ #
6
+ # This software is provided "as is", without warranty of any kind,
7
+ # express or implied, including but not limited to the warranties of
8
+ # merchantability, fitness for a particular purpose and noninfringement.
9
+
10
+
11
+ """BoxList implementation module."""
12
+
13
+ import dataclasses
14
+ from typing import Any, List, Literal, Optional, Tuple, Union
15
+
16
+ from drawlib.v0_2.private.core.colors import Colors
17
+ from drawlib.v0_2.private.core.model import ShapeStyle, ShapeTextStyle
18
+ from drawlib.v0_2.private.core.theme import dtheme
19
+ from drawlib.v0_2.private.core_canvas.canvas import rectangle
20
+ from drawlib.v0_2.private.util import error_handler
21
+
22
+
23
+ class BoxList:
24
+ """A class to draw a list of boxes with text, supporting highlighting of certain boxes.
25
+
26
+ Args:
27
+ box_style (Union[str, ShapeStyle, None]): The style for the boxes.
28
+ text_style (Union[str, ShapeTextStyle, None]): The style for the text inside the boxes.
29
+ box_highlight_style (Union[str, ShapeStyle, None]): The style for the highlighted boxes.
30
+ text_highlight_style (Union[str, ShapeTextStyle, None]): The style for the text inside the highlighted boxes.
31
+ """
32
+
33
+ @error_handler
34
+ def __init__(
35
+ self,
36
+ default_box_style: Union[str, ShapeStyle, None] = None,
37
+ default_text_style: Union[str, ShapeTextStyle, None] = None,
38
+ ) -> None:
39
+ """Initialize BoxList.
40
+
41
+ Args:
42
+ default_box_style (Union[str, ShapeStyle, None]): The style for the boxes.
43
+ default_text_style (Union[str, ShapeTextStyle, None]): The style for the text inside the boxes.
44
+ box_highlight_style (Union[str, ShapeStyle, None]): The style for the highlighted boxes.
45
+ text_highlight_style (Union[str, ShapeTextStyle, None]):
46
+ The style for the text inside the highlighted boxes.
47
+ """
48
+ if isinstance(default_box_style, str):
49
+ default_box_style = dtheme.rectanglestyles.get(default_box_style)
50
+ if default_box_style is None:
51
+ default_box_style = dtheme.rectanglestyles.get()
52
+ default_box_style.halign = "center"
53
+ default_box_style.valign = "center"
54
+ self._default_box_style = default_box_style
55
+
56
+ if isinstance(default_text_style, str):
57
+ default_text_style = dtheme.rectangletextstyles.get(default_text_style)
58
+ if default_text_style is None:
59
+ default_text_style = dtheme.rectangletextstyles.get()
60
+ self._default_text_style = default_text_style
61
+
62
+ self._list: List[_Item] = []
63
+
64
+ @error_handler
65
+ def append(
66
+ self,
67
+ text: str,
68
+ box_style: Union[str, ShapeStyle, None] = None,
69
+ text_style: Union[str, ShapeTextStyle, None] = None,
70
+ ) -> None:
71
+ """
72
+ Appends a new box with text to the BoxList.
73
+
74
+ Args:
75
+ text (str): The text to be displayed inside the box.
76
+ box_style (Union[str, ShapeStyle, None], optional):
77
+ The style for the box. Can be a style name, a ShapeStyle object, or None.
78
+ If None, the default box style is used.
79
+ text_style (Union[str, ShapeTextStyle, None], optional):
80
+ The style for the text inside the box. Can be a style name, a ShapeTextStyle object, or None.
81
+ If None, the default text style is used.
82
+ """
83
+ self.extend([text], box_style=box_style, text_style=text_style)
84
+
85
+ @error_handler
86
+ def insert(
87
+ self,
88
+ index: int,
89
+ text: str,
90
+ box_style: Union[str, ShapeStyle, None] = None,
91
+ text_style: Union[str, ShapeTextStyle, None] = None,
92
+ ) -> None:
93
+ """
94
+ Inserts a new box with text at a specified position in the BoxList.
95
+
96
+ Args:
97
+ index (int): The position at which to insert the new box.
98
+ text (str): The text to be displayed inside the box.
99
+ box_style (Union[str, ShapeStyle, None], optional):
100
+ The style for the box. Can be a style name, a ShapeStyle object, or None.
101
+ If None, the default box style is used.
102
+ text_style (Union[str, ShapeTextStyle, None], optional):
103
+ The style for the text inside the box. Can be a style name, a ShapeTextStyle object, or None.
104
+ If None, the default text style is used.
105
+ """
106
+ is_custom_style = box_style is not None or text_style is not None
107
+
108
+ if isinstance(box_style, str):
109
+ box_style = dtheme.rectanglestyles.get(box_style)
110
+ elif box_style is None:
111
+ box_style = self._default_box_style
112
+
113
+ if isinstance(text_style, str):
114
+ text_style = dtheme.rectangletextstyles.get(text_style)
115
+ elif text_style is None:
116
+ text_style = self._default_text_style
117
+
118
+ item = _Item(
119
+ text=text,
120
+ box_style=box_style,
121
+ text_style=text_style,
122
+ is_custom_style=is_custom_style,
123
+ )
124
+ self._list.insert(index, item)
125
+
126
+ @error_handler
127
+ def extend(
128
+ self,
129
+ texts: List[str],
130
+ box_style: Union[str, ShapeStyle, None] = None,
131
+ text_style: Union[str, ShapeTextStyle, None] = None,
132
+ ) -> None:
133
+ """
134
+ Extends the BoxList by appending multiple boxes with text.
135
+
136
+ Args:
137
+ texts (List[str]): A list of texts to be displayed inside the boxes.
138
+ box_style (Union[str, ShapeStyle, None], optional):
139
+ The style for the boxes. Can be a style name, a ShapeStyle object, or None.
140
+ If None, the default box style is used.
141
+ text_style (Union[str, ShapeTextStyle, None], optional):
142
+ The style for the text inside the boxes. Can be a style name, a ShapeTextStyle object, or None.
143
+ If None, the default text style is used.
144
+ """
145
+ is_custom_style = box_style is not None or text_style is not None
146
+
147
+ if isinstance(box_style, str):
148
+ box_style = dtheme.rectanglestyles.get(box_style)
149
+ elif box_style is None:
150
+ box_style = self._default_box_style
151
+
152
+ if isinstance(text_style, str):
153
+ text_style = dtheme.rectangletextstyles.get(text_style)
154
+ elif text_style is None:
155
+ text_style = self._default_text_style
156
+
157
+ for text in texts:
158
+ item = _Item(
159
+ text=text,
160
+ box_style=box_style,
161
+ text_style=text_style,
162
+ is_custom_style=is_custom_style,
163
+ )
164
+ self._list.append(item)
165
+
166
+ @error_handler
167
+ def draw(
168
+ self,
169
+ xy: Tuple[float, float],
170
+ box_width: float,
171
+ box_height: float,
172
+ align: Literal["left", "right", "bottom", "top"] = "left",
173
+ ) -> None:
174
+ """Draws a list of boxes at the specified location.
175
+
176
+ Args:
177
+ xy (Tuple[float, float]): The starting point (x, y) to draw the list of boxes.
178
+ box_width (float): The width of each box.
179
+ box_height (float): The height of each box.
180
+ items (List[Any]): The list of items to be displayed inside the boxes.
181
+ highlight_indexs (Optional[List[int]]):
182
+ The list of indices of the boxes to be highlighted.
183
+ length (Optional[int]): The total number of boxes to be drawn.
184
+ align (Literal["left", "right", "bottom", "top"]):
185
+ The alignment of the boxes relative to the starting point.
186
+
187
+ """
188
+ for index, item in enumerate(self._list):
189
+ if item.is_custom_style:
190
+ continue
191
+
192
+ self._draw_cell(
193
+ start_xy=xy,
194
+ index=index,
195
+ text=item.text,
196
+ box_width=box_width,
197
+ box_height=box_height,
198
+ box_style=item.box_style,
199
+ text_style=item.text_style,
200
+ align=align,
201
+ )
202
+
203
+ for index, item in enumerate(self._list):
204
+ if not item.is_custom_style:
205
+ continue
206
+
207
+ self._draw_cell(
208
+ start_xy=xy,
209
+ index=index,
210
+ text=item.text,
211
+ box_width=box_width,
212
+ box_height=box_height,
213
+ box_style=item.box_style,
214
+ text_style=item.text_style,
215
+ align=align,
216
+ )
217
+
218
+ @staticmethod
219
+ def _draw_cell(
220
+ start_xy: Tuple[float, float],
221
+ index: int,
222
+ text: str,
223
+ box_width: float,
224
+ box_height: float,
225
+ box_style: ShapeStyle,
226
+ text_style: ShapeTextStyle,
227
+ align: Literal["left", "right", "bottom", "top"],
228
+ ) -> None:
229
+ if align == "left":
230
+ x = start_xy[0] + box_width * (index + 0.5)
231
+ y = start_xy[1] + box_height / 2
232
+ elif align == "right":
233
+ x = start_xy[0] - box_width * (index + 0.5)
234
+ y = start_xy[1] + box_height / 2
235
+ elif align == "bottom":
236
+ x = start_xy[0] + box_width / 2
237
+ y = start_xy[1] + box_height * (index + 0.5)
238
+ else:
239
+ x = start_xy[0] + box_width / 2
240
+ y = start_xy[1] - box_height * (index + 0.5)
241
+
242
+ rectangle(
243
+ xy=(x, y),
244
+ width=box_width,
245
+ height=box_height,
246
+ style=box_style,
247
+ text=text,
248
+ textstyle=text_style,
249
+ )
250
+
251
+
252
+ @dataclasses.dataclass
253
+ class _Item:
254
+ text: str
255
+ box_style: ShapeStyle
256
+ text_style: ShapeTextStyle
257
+ is_custom_style: bool
@@ -49,8 +49,9 @@ class GridLayout:
49
49
  @error_handler
50
50
  def add( # noqa: C901
51
51
  self,
52
- column_range: Tuple[int, int],
53
- row_range: Tuple[int, int],
52
+ position: Tuple[int, int],
53
+ width: int,
54
+ height: int,
54
55
  r: Optional[int] = None,
55
56
  style: Union[str, ShapeStyle, None] = None,
56
57
  text: str = "",
@@ -62,8 +63,9 @@ class GridLayout:
62
63
  Add an item to the grid layout.
63
64
 
64
65
  Args:
65
- column_range (Tuple[int, int]): The range of columns the item will span.
66
- row_range (Tuple[int, int]): The range of rows the item will span.
66
+ position (Tuple[int, int]): Cell start (column, row) point.
67
+ width (int): How many column cells.
68
+ height (int): How many row cells.
67
69
  r (Optional[int], optional): The radius of the item. Default is None, which uses the default radius.
68
70
  style (Union[str, ShapeStyle, None], optional):
69
71
  The style of the item.
@@ -81,25 +83,24 @@ class GridLayout:
81
83
  Raises:
82
84
  ValueError: If the column or row ranges are invalid or out of bounds.
83
85
  """
84
- # validate column
85
- c1, c2 = column_range
86
- if c1 > c2:
87
- column_range = (c2, c1)
88
- c1, c2 = c2, c1
89
- if c1 < 0:
90
- raise ValueError("Column range must be between 0 ~ last-index.")
91
- if c2 > self._num_column:
92
- raise ValueError("Column range must be between 0 ~ last-index.")
93
-
94
- # validate row
95
- r1, r2 = row_range
96
- if r1 > r2:
97
- row_range = (r2, r1)
98
- r1, r2 = r2, r1
99
- if r1 < 0:
100
- raise ValueError("Row range must be between 0 ~ last-index.")
101
- if r2 > self._num_column:
102
- raise ValueError("Row range must be between 0 ~ last-index.")
86
+ # validate
87
+ if width < 1:
88
+ raise ValueError("Grid cell must have 1+ columns")
89
+ if height < 1:
90
+ raise ValueError("Grid cell must have 1+ rows")
91
+
92
+ column_start, row_start = position
93
+ if not 0 <= column_start < self._num_column:
94
+ raise ValueError("Grid cell's column start position must be between 0 ~ last-column.")
95
+ if not 0 <= row_start < self._num_row:
96
+ raise ValueError("Grid cell's row start position must be between 0 ~ last-column.")
97
+
98
+ column_end = column_start + width - 1
99
+ row_end = row_start + height - 1
100
+ if column_end >= self._num_column:
101
+ raise ValueError("Grid cell's column start position must be between 0 ~ last-column.")
102
+ if row_end >= self._num_row:
103
+ raise ValueError("Grid cell's row start position must be between 0 ~ last-column.")
103
104
 
104
105
  # string style to Style class
105
106
  if isinstance(style, str):
@@ -137,8 +138,8 @@ class GridLayout:
137
138
 
138
139
  # push
139
140
  item = _GridLayoutItem(
140
- column_range=column_range,
141
- row_range=row_range,
141
+ column_range=(column_start, column_end),
142
+ row_range=(row_start, row_end),
142
143
  r=r,
143
144
  text=text,
144
145
  style=style,
@@ -19,6 +19,13 @@ from drawlib.v0_2.private.core_canvas.canvas import trapezoid, triangle
19
19
  from drawlib.v0_2.private.util import error_handler
20
20
 
21
21
 
22
+ @dataclasses.dataclass
23
+ class _PyramidItem:
24
+ style: ShapeStyle
25
+ text: str
26
+ textstyle: ShapeTextStyle
27
+
28
+
22
29
  class Pyramid:
23
30
  """Class for rendering smart art pyramid."""
24
31
 
@@ -116,6 +123,7 @@ class Pyramid:
116
123
  height: float,
117
124
  margin: float,
118
125
  align: Literal["bottom", "top", "left", "right"] = "bottom",
126
+ order: Literal["vertex_to_base", "base_to_vertex"] = "vertex_to_base",
119
127
  ) -> None:
120
128
  """Draw smart art pyramid.
121
129
 
@@ -125,6 +133,7 @@ class Pyramid:
125
133
  height (float): The heifht of the pyramid.
126
134
  margin (float): The margin between pyramid items.
127
135
  align (str): Alignment of a pyramid.
136
+ order (str): Item order. "vertex -> base" or "base -> vertex". default is "vertex -> base".
128
137
  """
129
138
  if len(self._items) == 0:
130
139
  raise ValueError("Number of pyramid item is 0.")
@@ -139,6 +148,7 @@ class Pyramid:
139
148
  item_heights=item_heights,
140
149
  margins=margins,
141
150
  align=align,
151
+ order=order,
142
152
  )
143
153
 
144
154
  @error_handler
@@ -149,6 +159,7 @@ class Pyramid:
149
159
  item_heights: List[float],
150
160
  margins: List[float],
151
161
  align: Literal["bottom", "top", "left", "right"] = "bottom",
162
+ order: Literal["vertex_to_base", "base_to_vertex"] = "vertex_to_base",
152
163
  ) -> None:
153
164
  """Draw smart art pyramid with flexible pyramid item heights.
154
165
 
@@ -158,6 +169,7 @@ class Pyramid:
158
169
  item_heights (float): The height of the each pyramid items.
159
170
  margins (float): The margin between pyramid items.
160
171
  align (str): Alignment of a pyramid.
172
+ order (str): Item order. "vertex -> base" or "base -> vertex". default is "vertex -> base".
161
173
 
162
174
  Raises:
163
175
  ValueError: If the lengths of column_widths, column_margins, row_heights, or row_margins are incorrect.
@@ -172,35 +184,38 @@ class Pyramid:
172
184
  if len(self._items) != len(margins) + 1:
173
185
  raise ValueError('Number of pyramid item and arg "margins" length does not match.')
174
186
 
187
+ items = self._items[::-1] if order == "vertex_to_base" else self._items[::]
188
+
175
189
  if align == "bottom":
176
- self._draw_flexible_bottom(xy, width, item_heights, margins)
190
+ self._draw_flexible_bottom(xy, width, item_heights, margins, items)
177
191
  elif align == "top":
178
- self._draw_flexible_top(xy, width, item_heights, margins)
192
+ self._draw_flexible_top(xy, width, item_heights, margins, items)
179
193
  elif align == "left":
180
- self._draw_flexible_left(xy, width, item_heights, margins)
194
+ self._draw_flexible_left(xy, width, item_heights, margins, items)
181
195
  elif align == "right":
182
- self._draw_flexible_right(xy, width, item_heights, margins)
196
+ self._draw_flexible_right(xy, width, item_heights, margins, items)
183
197
  else:
184
198
  raise ValueError("Drawlib internal error.")
185
199
 
200
+ @staticmethod
186
201
  def _draw_flexible_bottom(
187
- self,
188
202
  xy: Tuple[float, float],
189
203
  width: float,
190
204
  item_heights: List[float],
191
205
  margins: List[float],
206
+ items: List[_PyramidItem],
192
207
  ) -> None:
193
208
  x = xy[0] + width / 2
194
209
  height = sum(item_heights) + sum(margins)
195
210
  current_height = 0
196
- for i, item in enumerate(self._items):
211
+ for i, item in enumerate(items):
197
212
  text = item.text
198
213
  style = item.style
199
214
  style.halign = "center"
200
215
  style.valign = "bottom"
201
216
  textstyle = item.textstyle
202
217
 
203
- is_last = i == len(self._items) - 1
218
+ is_last = i == len(items) - 1
204
219
  if is_last:
205
220
  ratio = (height - current_height) / height
206
221
  item_width = ratio * width
@@ -231,24 +246,25 @@ class Pyramid:
231
246
  )
232
247
  current_height += item_height + margins[i]
233
248
 
249
+ @staticmethod
234
250
  def _draw_flexible_top(
235
- self,
236
251
  xy: Tuple[float, float],
237
252
  width: float,
238
253
  item_heights: List[float],
239
254
  margins: List[float],
255
+ items: List[_PyramidItem],
240
256
  ) -> None:
241
257
  x = xy[0] + width / 2
242
258
  height = sum(item_heights) + sum(margins)
243
259
  current_height = 0
244
- for i, item in enumerate(self._items):
260
+ for i, item in enumerate(items):
245
261
  text = item.text
246
262
  style = item.style
247
263
  style.halign = "center"
248
264
  style.valign = "bottom"
249
265
  textstyle = item.textstyle
250
266
 
251
- is_last = i == len(self._items) - 1
267
+ is_last = i == len(items) - 1
252
268
  if is_last:
253
269
  ratio = (height - current_height) / height
254
270
  item_width = ratio * width
@@ -284,17 +300,18 @@ class Pyramid:
284
300
  )
285
301
  current_height += item_height + margins[i]
286
302
 
303
+ @staticmethod
287
304
  def _draw_flexible_left(
288
- self,
289
305
  xy: Tuple[float, float],
290
306
  width: float,
291
307
  item_heights: List[float],
292
308
  margins: List[float],
309
+ items: List[_PyramidItem],
293
310
  ) -> None:
294
311
  y = xy[1] + width / 2
295
312
  height = sum(item_heights) + sum(margins)
296
313
  current_height = 0
297
- for i, item in enumerate(self._items):
314
+ for i, item in enumerate(items):
298
315
  text = item.text
299
316
  style = item.style
300
317
  style.halign = "center"
@@ -304,7 +321,7 @@ class Pyramid:
304
321
  if textstyle.angle is None:
305
322
  textstyle.angle = 0
306
323
 
307
- is_last = i == len(self._items) - 1
324
+ is_last = i == len(items) - 1
308
325
  if is_last:
309
326
  ratio = (height - current_height) / height
310
327
  item_width = ratio * width
@@ -339,17 +356,18 @@ class Pyramid:
339
356
  )
340
357
  current_height += item_height + margins[i]
341
358
 
359
+ @staticmethod
342
360
  def _draw_flexible_right(
343
- self,
344
361
  xy: Tuple[float, float],
345
362
  width: float,
346
363
  item_heights: List[float],
347
364
  margins: List[float],
365
+ items: List[_PyramidItem],
348
366
  ) -> None:
349
367
  y = xy[1] + width / 2
350
368
  height = sum(item_heights) + sum(margins)
351
369
  current_height = 0
352
- for i, item in enumerate(self._items):
370
+ for i, item in enumerate(items):
353
371
  text = item.text
354
372
  style = item.style
355
373
  style.halign = "center"
@@ -358,7 +376,7 @@ class Pyramid:
358
376
  if textstyle.angle is None:
359
377
  textstyle.angle = 0
360
378
 
361
- is_last = i == len(self._items) - 1
379
+ is_last = i == len(items) - 1
362
380
  if is_last:
363
381
  ratio = (height - current_height) / height
364
382
  item_width = ratio * width
@@ -392,10 +410,3 @@ class Pyramid:
392
410
  angle=90,
393
411
  )
394
412
  current_height += item_height + margins[i]
395
-
396
-
397
- @dataclasses.dataclass
398
- class _PyramidItem:
399
- style: ShapeStyle
400
- text: str
401
- textstyle: ShapeTextStyle
@@ -7,7 +7,7 @@ build-backend = "poetry.core.masonry.api"
7
7
 
8
8
  [tool.poetry]
9
9
  name = "drawlib"
10
- version = "0.2.3.dev5"
10
+ version = "0.2.3.dev6"
11
11
  description = "Python drawing library. Illustration as Code."
12
12
  homepage = "https://www.drawlib.com"
13
13
  repository = "https://github.com/yuichi110/drawlib"
@@ -1,178 +0,0 @@
1
- # Copyright (c) 2024 Yuichi Ito (yuichi@yuichi.com)
2
- #
3
- # This software is licensed under the Apache License, Version 2.0.
4
- # For more information, please visit: https://github.com/yuichi110/drawlib
5
- #
6
- # This software is provided "as is", without warranty of any kind,
7
- # express or implied, including but not limited to the warranties of
8
- # merchantability, fitness for a particular purpose and noninfringement.
9
-
10
-
11
- """BoxList implementation module."""
12
-
13
- from typing import Any, List, Literal, Optional, Tuple, Union
14
-
15
- from drawlib.v0_2.private.core.colors import Colors
16
- from drawlib.v0_2.private.core.model import ShapeStyle, ShapeTextStyle
17
- from drawlib.v0_2.private.core.theme import dtheme
18
- from drawlib.v0_2.private.core_canvas.canvas import rectangle
19
- from drawlib.v0_2.private.util import error_handler
20
-
21
-
22
- class BoxList:
23
- """A class to draw a list of boxes with text, supporting highlighting of certain boxes.
24
-
25
- Args:
26
- box_style (Union[str, ShapeStyle, None]): The style for the boxes.
27
- text_style (Union[str, ShapeTextStyle, None]): The style for the text inside the boxes.
28
- box_highlight_style (Union[str, ShapeStyle, None]): The style for the highlighted boxes.
29
- text_highlight_style (Union[str, ShapeTextStyle, None]): The style for the text inside the highlighted boxes.
30
- """
31
-
32
- @error_handler
33
- def __init__(
34
- self,
35
- box_style: Union[str, ShapeStyle, None] = None,
36
- text_style: Union[str, ShapeTextStyle, None] = None,
37
- box_highlight_style: Union[str, ShapeStyle, None] = None,
38
- text_highlight_style: Union[str, ShapeTextStyle, None] = None,
39
- ) -> None:
40
- """Initialize BoxList.
41
-
42
- Args:
43
- box_style (Union[str, ShapeStyle, None]): The style for the boxes.
44
- text_style (Union[str, ShapeTextStyle, None]): The style for the text inside the boxes.
45
- box_highlight_style (Union[str, ShapeStyle, None]): The style for the highlighted boxes.
46
- text_highlight_style (Union[str, ShapeTextStyle, None]):
47
- The style for the text inside the highlighted boxes.
48
- """
49
- if isinstance(box_style, str):
50
- box_style = dtheme.rectanglestyles.get(box_style)
51
- if box_style is None:
52
- box_style = dtheme.rectanglestyles.get()
53
- box_style.halign = "center"
54
- box_style.valign = "center"
55
- self._box_style = box_style
56
-
57
- if isinstance(text_style, str):
58
- text_style = dtheme.rectangletextstyles.get(text_style)
59
- if text_style is None:
60
- text_style = dtheme.rectangletextstyles.get()
61
- self._text_style = text_style
62
-
63
- if isinstance(box_highlight_style, str):
64
- box_highlight_style = dtheme.rectanglestyles.get(box_highlight_style)
65
- if box_highlight_style is None:
66
- box_highlight_style = box_style
67
- box_highlight_style.halign = "center"
68
- box_highlight_style.valign = "center"
69
- self._box_highlight_style = box_highlight_style
70
-
71
- if isinstance(text_highlight_style, str):
72
- text_highlight_style = dtheme.rectangletextstyles.get(text_highlight_style)
73
- if text_highlight_style is None:
74
- text_highlight_style = text_style
75
- self._text_highlight_style = text_highlight_style
76
-
77
- @error_handler
78
- def draw(
79
- self,
80
- xy: Tuple[float, float],
81
- box_width: float,
82
- box_height: float,
83
- items: List[Any],
84
- highlight_indexs: Optional[List[int]] = None,
85
- length: Optional[int] = None,
86
- align: Literal["left", "right", "bottom", "top"] = "left",
87
- ) -> None:
88
- """Draws a list of boxes at the specified location.
89
-
90
- Args:
91
- xy (Tuple[float, float]): The starting point (x, y) to draw the list of boxes.
92
- box_width (float): The width of each box.
93
- box_height (float): The height of each box.
94
- items (List[Any]): The list of items to be displayed inside the boxes.
95
- highlight_indexs (Optional[List[int]]):
96
- The list of indices of the boxes to be highlighted.
97
- length (Optional[int]): The total number of boxes to be drawn.
98
- align (Literal["left", "right", "bottom", "top"]):
99
- The alignment of the boxes relative to the starting point.
100
-
101
- """
102
- if highlight_indexs is None:
103
- highlight_indexs = []
104
-
105
- if length is not None:
106
- indexs = range(length)
107
- else:
108
- indexs = range(len(items))
109
-
110
- non_highlight_indexs = [item for item in indexs if item not in highlight_indexs]
111
- highlight_indexs = [item for item in indexs if item in highlight_indexs]
112
-
113
- for index in non_highlight_indexs:
114
- if index <= len(items) - 1:
115
- text = str(items[index])
116
- else:
117
- text = ""
118
-
119
- self._draw_cell(
120
- start_xy=xy,
121
- index=index,
122
- text=text,
123
- box_width=box_width,
124
- box_height=box_height,
125
- box_style=self._box_style,
126
- text_style=self._text_style,
127
- align=align,
128
- )
129
-
130
- for index in highlight_indexs:
131
- if index <= len(items) - 1:
132
- text = str(items[index])
133
- else:
134
- text = ""
135
-
136
- self._draw_cell(
137
- start_xy=xy,
138
- index=index,
139
- text=text,
140
- box_width=box_width,
141
- box_height=box_height,
142
- box_style=self._box_highlight_style,
143
- text_style=self._text_highlight_style,
144
- align=align,
145
- )
146
-
147
- @staticmethod
148
- def _draw_cell(
149
- start_xy: Tuple[float, float],
150
- index: int,
151
- text: str,
152
- box_width: float,
153
- box_height: float,
154
- box_style: ShapeStyle,
155
- text_style: ShapeTextStyle,
156
- align: Literal["left", "right", "bottom", "top"],
157
- ) -> None:
158
- if align == "left":
159
- x = start_xy[0] + box_width * (index + 0.5)
160
- y = start_xy[1] + box_height / 2
161
- elif align == "right":
162
- x = start_xy[0] - box_width * (index + 0.5)
163
- y = start_xy[1] + box_height / 2
164
- elif align == "bottom":
165
- x = start_xy[0] + box_width / 2
166
- y = start_xy[1] + box_height * (index + 0.5)
167
- else:
168
- x = start_xy[0] + box_width / 2
169
- y = start_xy[1] - box_height * (index + 0.5)
170
-
171
- rectangle(
172
- xy=(x, y),
173
- width=box_width,
174
- height=box_height,
175
- style=box_style,
176
- text=text,
177
- textstyle=text_style,
178
- )
File without changes
File without changes