drawlib 0.2.3.dev4__py3-none-any.whl → 0.2.3.dev6__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 CHANGED
@@ -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.dev4"
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)
@@ -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
@@ -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:
@@ -219,8 +220,8 @@ class CanvasOriginalArrowFeature(CanvasBase):
219
220
  new_xys.insert(0, xys_start)
220
221
  new_xys.append(xys_end)
221
222
  aph = ArrowPolylineHelper(new_xys, r, 100)
222
- parallel_xys1 = aph.get_parallel_curve_points(tail_width / 2)
223
- parallel_xys2 = aph.get_parallel_curve_points(tail_width / -2)
223
+ parallel_xys1 = aph.get_parallel_points(tail_width / 2)
224
+ parallel_xys2 = aph.get_parallel_points(tail_width / -2)
224
225
  parallel_xys2.reverse()
225
226
 
226
227
  if head in {"<-", "<->"}:
@@ -568,6 +569,11 @@ class ArrowPolylineHelper:
568
569
  curve.append((x, y))
569
570
  return curve
570
571
 
572
+ self._r = r
573
+ if r == 0:
574
+ self._original_points = xys[:]
575
+ return
576
+
571
577
  points = []
572
578
  bezier_start: Tuple[float, float] = (0, 0)
573
579
  last_i = len(xys) - 2
@@ -592,24 +598,121 @@ class ArrowPolylineHelper:
592
598
  points.extend(get_points(bezier_points))
593
599
  bezier_start = p2
594
600
 
595
- self._original_curve_points = points
601
+ self._original_points = points
602
+
603
+ def get_parallel_points(self, distance: float) -> List[Tuple[float, float]]:
604
+ """Internal function"""
605
+ if self._r == 0:
606
+ return self._get_parallel_straight_points(distance)
607
+ return self._get_parallel_curved_points(distance)
596
608
 
597
- def get_parallel_curve_points(self, distance: float) -> List[Tuple[float, float]]:
609
+ def _get_parallel_curved_points(self, distance: float) -> List[Tuple[float, float]]:
598
610
  """Internal function"""
599
611
 
600
612
  def get_distance(p1: Tuple[float, float], p2: Tuple[float, float]) -> float:
601
613
  return math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2)
602
614
 
603
615
  parallel_curve_points = []
604
- for i in range(1, len(self._original_curve_points)):
605
- p0, p1 = self._original_curve_points[i - 1], self._original_curve_points[i]
616
+ for i in range(1, len(self._original_points)):
617
+ p0, p1 = self._original_points[i - 1], self._original_points[i]
606
618
  dx, dy = p1[0] - p0[0], p1[1] - p0[1]
607
619
  length = get_distance(p0, p1)
608
620
  nx, ny = -dy / length, dx / length
609
621
  px0, py0 = p0[0] + distance * nx, p0[1] + distance * ny
610
622
  px1, py1 = p1[0] + distance * nx, p1[1] + distance * ny
611
623
  parallel_curve_points.append((px0, py0))
612
- if i == len(self._original_curve_points) - 1:
624
+ if i == len(self._original_points) - 1:
613
625
  parallel_curve_points.append((px1, py1))
614
626
 
615
627
  return parallel_curve_points
628
+
629
+ def _get_parallel_straight_points(self, distance: float) -> List[Tuple[float, float]]:
630
+ """Internal function"""
631
+
632
+ def get_parallel_line_xys(
633
+ xy1: Tuple[float, float],
634
+ xy2: Tuple[float, float],
635
+ ) -> Tuple[Tuple[float, float], Tuple[float, float]]:
636
+ """Internal function"""
637
+ x1, y1 = xy1
638
+ x2, y2 = xy2
639
+
640
+ # calc vector
641
+ vx = x2 - x1
642
+ vy = y2 - y1
643
+ length = math.sqrt(vx**2 + vy**2)
644
+ dx = -vy / length * distance
645
+ dy = vx / length * distance
646
+
647
+ # add vector
648
+ x1_prime = x1 + dx
649
+ y1_prime = y1 + dy
650
+ x2_prime = x2 + dx
651
+ y2_prime = y2 + dy
652
+
653
+ return (x1_prime, y1_prime), (x2_prime, y2_prime)
654
+
655
+ def find_lines_intersection(
656
+ xy1: Tuple[float, float],
657
+ xy2: Tuple[float, float],
658
+ xy3: Tuple[float, float],
659
+ xy4: Tuple[float, float],
660
+ ) -> Tuple[float, float]:
661
+ """Internal function"""
662
+ x1, y1 = xy1
663
+ x2, y2 = xy2
664
+ x3, y3 = xy3
665
+ x4, y4 = xy4
666
+
667
+ # Check if lines are vertical
668
+ if x1 == x2 and x3 == x4:
669
+ raise ValueError(f'line "{xy1}" -> "{xy2}" and "{xy3}" => "{xy4}" are parallel.')
670
+
671
+ # Handle vertical lines
672
+ if x1 == x2:
673
+ m2 = (y4 - y3) / (x4 - x3)
674
+ b2 = y3 - m2 * x3
675
+ x = x1
676
+ y = m2 * x + b2
677
+ return (x, y)
678
+
679
+ if x3 == x4:
680
+ m1 = (y2 - y1) / (x2 - x1)
681
+ b1 = y1 - m1 * x1
682
+ x = x3
683
+ y = m1 * x + b1
684
+ return (x, y)
685
+
686
+ m1 = (y2 - y1) / (x2 - x1)
687
+ b1 = y1 - m1 * x1
688
+ m2 = (y4 - y3) / (x4 - x3)
689
+ b2 = y3 - m2 * x3
690
+
691
+ if m1 == m2:
692
+ raise ValueError(f'line "{xy1}" -> "{xy2}" and "{xy3}" => "{xy4}" are parallel.')
693
+
694
+ x = (b2 - b1) / (m1 - m2)
695
+ y = m1 * x + b1
696
+
697
+ return (x, y)
698
+
699
+ xys = self._original_points
700
+ parallel_lines: List[Tuple[Tuple[float, float], Tuple[float, float]]] = []
701
+ for i in range(len(xys) - 1):
702
+ xy1, xy2 = get_parallel_line_xys(xys[i], xys[i + 1])
703
+ parallel_lines.append((xy1, xy2))
704
+
705
+ points: List[Tuple[float, float]] = []
706
+ for i in range(len(parallel_lines) - 1):
707
+ xy1, xy2 = parallel_lines[i]
708
+ xy3, xy4 = parallel_lines[i + 1]
709
+ xy = find_lines_intersection(xy1, xy2, xy3, xy4)
710
+ points.append(xy)
711
+
712
+ first_point = parallel_lines[0][0]
713
+ last_point = parallel_lines[len(parallel_lines) - 1][1]
714
+
715
+ points.insert(0, first_point)
716
+ points.append(last_point)
717
+
718
+ return points
@@ -10,6 +10,7 @@
10
10
 
11
11
  """BoxList implementation module."""
12
12
 
13
+ import dataclasses
13
14
  from typing import Any, List, Literal, Optional, Tuple, Union
14
15
 
15
16
  from drawlib.v0_2.private.core.colors import Colors
@@ -32,47 +33,135 @@ class BoxList:
32
33
  @error_handler
33
34
  def __init__(
34
35
  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,
36
+ default_box_style: Union[str, ShapeStyle, None] = None,
37
+ default_text_style: Union[str, ShapeTextStyle, None] = None,
39
38
  ) -> None:
40
39
  """Initialize BoxList.
41
40
 
42
41
  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.
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.
45
44
  box_highlight_style (Union[str, ShapeStyle, None]): The style for the highlighted boxes.
46
45
  text_highlight_style (Union[str, ShapeTextStyle, None]):
47
46
  The style for the text inside the highlighted boxes.
48
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
+
49
147
  if isinstance(box_style, str):
50
148
  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
149
+ elif box_style is None:
150
+ box_style = self._default_box_style
56
151
 
57
152
  if isinstance(text_style, str):
58
153
  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
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)
76
165
 
77
166
  @error_handler
78
167
  def draw(
@@ -80,9 +169,6 @@ class BoxList:
80
169
  xy: Tuple[float, float],
81
170
  box_width: float,
82
171
  box_height: float,
83
- items: List[Any],
84
- highlight_indexs: Optional[List[int]] = None,
85
- length: Optional[int] = None,
86
172
  align: Literal["left", "right", "bottom", "top"] = "left",
87
173
  ) -> None:
88
174
  """Draws a list of boxes at the specified location.
@@ -99,48 +185,33 @@ class BoxList:
99
185
  The alignment of the boxes relative to the starting point.
100
186
 
101
187
  """
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 = ""
188
+ for index, item in enumerate(self._list):
189
+ if item.is_custom_style:
190
+ continue
118
191
 
119
192
  self._draw_cell(
120
193
  start_xy=xy,
121
194
  index=index,
122
- text=text,
195
+ text=item.text,
123
196
  box_width=box_width,
124
197
  box_height=box_height,
125
- box_style=self._box_style,
126
- text_style=self._text_style,
198
+ box_style=item.box_style,
199
+ text_style=item.text_style,
127
200
  align=align,
128
201
  )
129
202
 
130
- for index in highlight_indexs:
131
- if index <= len(items) - 1:
132
- text = str(items[index])
133
- else:
134
- text = ""
203
+ for index, item in enumerate(self._list):
204
+ if not item.is_custom_style:
205
+ continue
135
206
 
136
207
  self._draw_cell(
137
208
  start_xy=xy,
138
209
  index=index,
139
- text=text,
210
+ text=item.text,
140
211
  box_width=box_width,
141
212
  box_height=box_height,
142
- box_style=self._box_highlight_style,
143
- text_style=self._text_highlight_style,
213
+ box_style=item.box_style,
214
+ text_style=item.text_style,
144
215
  align=align,
145
216
  )
146
217
 
@@ -176,3 +247,11 @@ class BoxList:
176
247
  text=text,
177
248
  textstyle=text_style,
178
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: drawlib
3
- Version: 0.2.3.dev4
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
@@ -1,4 +1,4 @@
1
- drawlib/__init__.py,sha256=jXsDFzb9eWZYUjfkP43y1J-umimTxB5iXTEx201e0Ow,2678
1
+ drawlib/__init__.py,sha256=3zKmWLrNLpYIMX7EUahkWxwJPVgY8oa17IDaeSxcCQc,2678
2
2
  drawlib/__main__.py,sha256=UDsH6DiEIWBEwlClbYI10aWYBw_gOmaNCB0TSImWZ-c,604
3
3
  drawlib/apis.py,sha256=Q1Uz4U6IK63ZtjaV_UTWPZq7xDpxTz99qHfpGsL8SnI,531
4
4
  drawlib/assets/__init__.py,sha256=bYT4EWquLr0oyEziAuixlD9M4EPnMY6PyhknOxJjxqY,437
@@ -20,13 +20,13 @@ drawlib/v0_2/private/core/model_system_default.py,sha256=oK9WpeQVX_baJJmjwz4L23G
20
20
  drawlib/v0_2/private/core/theme.py,sha256=QcuaCw615fRN0C753gjfay9cqCbHILERqBIDNpO1whQ,47118
21
21
  drawlib/v0_2/private/core/theme_officials.py,sha256=AeZdENE-B8kSTcDAnWLKU-b77PssjwqFa_D-42HWNjg,19832
22
22
  drawlib/v0_2/private/core/theme_style_caches.py,sha256=k22Jrm0nsZITg_Mv0qGyv5pMkivLOb-s-svnWqqm8xY,186385
23
- drawlib/v0_2/private/core/util.py,sha256=FaOlxKnQPcJT2oHiy06ZtVq0ZNO3tppAL-8xvW9Un4c,28437
23
+ drawlib/v0_2/private/core/util.py,sha256=uf_VgDJ7HKwK1RuF-kIklwVVJnHXIR_SDoWeFy9jHiQ,30314
24
24
  drawlib/v0_2/private/core_canvas/__init__.py,sha256=6HANaWCLm6GjIU7cZGAOdUCrvIxbtQx3KTkQWK-nEhU,455
25
25
  drawlib/v0_2/private/core_canvas/base.py,sha256=FipdPyX9M-JWL9FQpAT1r8hoo9o1wyCS1QGx3kINwLQ,20603
26
26
  drawlib/v0_2/private/core_canvas/canvas.py,sha256=haDITKf-Wz2zA2zDC_NmrdH-QsSagukQ7LT7xEB2crY,9594
27
27
  drawlib/v0_2/private/core_canvas/image.py,sha256=1KoLmCTbr4VoTZi0rI_e1_HIYOmpGklygeq5ACgwVus,6958
28
- drawlib/v0_2/private/core_canvas/line.py,sha256=qp5IcQUTfLyH1hi4JOKa-h_8TVWRWCEthfSSEEErMgQ,17862
29
- drawlib/v0_2/private/core_canvas/original_arrow.py,sha256=vTnL7krRxEtplF4CGF5pYnwI0T_YtuNaQ_5bI3zmvCU,21231
28
+ drawlib/v0_2/private/core_canvas/line.py,sha256=7TN4pRDWzaanGYQaox7HywIPQuUx4CfOnwvM7mN2e7I,17954
29
+ drawlib/v0_2/private/core_canvas/original_arrow.py,sha256=13MqxSEcWR9tbYmcrSdF8AMdjm8rkP6-q42Olk3jfKc,24466
30
30
  drawlib/v0_2/private/core_canvas/original_polygon.py,sha256=uvBbfr72J3QEw9efxHdd5YXl1IKZhEA82qzEXdEK7Kc,14546
31
31
  drawlib/v0_2/private/core_canvas/patches.py,sha256=uDRZMYqMnkh37lXVLGwTUmsb7XuNjNMNZBjP3Cgi5Eg,14211
32
32
  drawlib/v0_2/private/core_canvas/text.py,sha256=bqdiqptdXeV8HVjp0Fx2pl2eA0q29XHYtWcK4WSyCw8,3520
@@ -41,13 +41,13 @@ drawlib/v0_2/private/icons/phosphor.py,sha256=4xwcmDvFB6raCZZFQdrU2eR7l5QhYz9Bg_
41
41
  drawlib/v0_2/private/icons/util.py,sha256=9ZH67tRO1diuzwhENVki6exgNFJbKa1WDICYsxDbaWs,2367
42
42
  drawlib/v0_2/private/logging.py,sha256=tZT62wFfPbwY2JBQVtwoS2qf7HBvCjfB-oXdATEnupc,802
43
43
  drawlib/v0_2/private/smartarts/__init__.py,sha256=ii_xf3fXWPvP4SqSdcLO0gIbx643oRbVch58KlaYAnA,445
44
- drawlib/v0_2/private/smartarts/boxlist.py,sha256=ymY5yAWhev3JpQi493_8mkTydYaqNMjohj7kpoMFnLw,6714
44
+ drawlib/v0_2/private/smartarts/boxlist.py,sha256=fPBauEoZAMO-7o_z6eZ32oJ-h0Gp0dL5dZPfW0jy14g,9679
45
45
  drawlib/v0_2/private/smartarts/boxtree.py,sha256=f6EXkPMEkU8v4vvBxA2Bu2_vV6nJ1nIxppuj9UPhf2E,4655
46
46
  drawlib/v0_2/private/smartarts/bubblespeech.py,sha256=2ynQt0W5A0EGRpE1MX-1DQnd01NzM4zIjEUGunEWnIE,4219
47
47
  drawlib/v0_2/private/smartarts/bulletpoints.py,sha256=2wOa5O7pq1qlrdagPaiA9t3UrSCKYAZ5HuVGgrG04u4,6039
48
48
  drawlib/v0_2/private/smartarts/dsart.py,sha256=3wKpD4x_ZqZFG3QJAtD3NhKL7_lDlwJ3tY08sfUF02o,1245
49
- drawlib/v0_2/private/smartarts/gridlayout.py,sha256=5gJGNa5uILFOl7DDPB-GBE-MrzaLq74aMFbDnJI4ZRc,11472
50
- drawlib/v0_2/private/smartarts/pyramid.py,sha256=bVCkIo6w-9qDqRfOXShvOozxOzI6WlxR_dQMc4l5Q48,14314
49
+ drawlib/v0_2/private/smartarts/gridlayout.py,sha256=YHHk1XVxH2Shg1-rjfNaSw6gPMx_njVJBaauDabxHLQ,11709
50
+ drawlib/v0_2/private/smartarts/pyramid.py,sha256=k2zIFQkrLGzLuhS5X0LwiV48wEsny75mVUQmhdWuFAo,14926
51
51
  drawlib/v0_2/private/smartarts/sourcecode.py,sha256=VkPrmxNeYR_E0kyfFsezAKJE7dVNIdrIJUsq348iqyE,8922
52
52
  drawlib/v0_2/private/smartarts/table.py,sha256=Fyji-xUt8JnZQPzbjt0GgibVB0qcr4zA2VgqDXddPmw,25407
53
53
  drawlib/v0_2/private/smartarts/tree.py,sha256=BStMQ77yO26RufYwfAbc5H4PEhCynkv9QKD5DbDasUg,11230
@@ -75,8 +75,8 @@ drawlib/v0_2/private/validators/smartarts.py,sha256=fOAlC0syvZVk-GCLQgaPhhfWr3gc
75
75
  drawlib/v0_2/private/validators/style.py,sha256=R7aLlaVfzdo33L7GLQ3WVfzzD_EsgV2-Y7ERcbFq7QI,3308
76
76
  drawlib/v0_2/private/validators/text.py,sha256=ELT87nekr9hMx7m-wpVTku0QPxL3qCIX4cxI5RbFoIc,1126
77
77
  drawlib/v0_2/private/validators/types.py,sha256=KtiIgivuaFyFaxIGeqr5N8xpzqIk9soiacSzcFDdjSA,4155
78
- drawlib-0.2.3.dev4.dist-info/LICENSE.txt,sha256=h_ZiliCgEDqIYpxmUPmqGGP87VBcUHil1S4wUI48ZKg,11344
79
- drawlib-0.2.3.dev4.dist-info/METADATA,sha256=keuYahbx9v68956lsqVpKMBmY3l0G6rHq28VVvphFkc,4007
80
- drawlib-0.2.3.dev4.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
81
- drawlib-0.2.3.dev4.dist-info/entry_points.txt,sha256=Lp6mbrRMP-g6c7P6Iz21CEjceLITCWrma3b0ncP30oQ,49
82
- drawlib-0.2.3.dev4.dist-info/RECORD,,
78
+ drawlib-0.2.3.dev6.dist-info/LICENSE.txt,sha256=h_ZiliCgEDqIYpxmUPmqGGP87VBcUHil1S4wUI48ZKg,11344
79
+ drawlib-0.2.3.dev6.dist-info/METADATA,sha256=1EFfoSpotEcmmxshxHgnuVm81AISwoSMxnc4emfZxxg,4007
80
+ drawlib-0.2.3.dev6.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
81
+ drawlib-0.2.3.dev6.dist-info/entry_points.txt,sha256=Lp6mbrRMP-g6c7P6Iz21CEjceLITCWrma3b0ncP30oQ,49
82
+ drawlib-0.2.3.dev6.dist-info/RECORD,,