drawlib 0.2.3.dev2__py3-none-any.whl → 0.2.3.dev4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of drawlib might be problematic. Click here for more details.
- drawlib/__init__.py +1 -1
- drawlib/v0_2/apis.py +5 -0
- drawlib/v0_2/private/core/model.py +19 -1
- drawlib/v0_2/private/core/util.py +6 -0
- drawlib/v0_2/private/core_canvas/base.py +53 -1
- drawlib/v0_2/private/core_canvas/canvas.py +8 -1
- drawlib/v0_2/private/core_canvas/line.py +192 -1
- drawlib/v0_2/private/core_canvas/original_arrow.py +477 -1
- drawlib/v0_2/private/core_canvas/patches.py +19 -69
- drawlib/v0_2/private/smartarts/boxlist.py +178 -0
- drawlib/v0_2/private/smartarts/bubblespeech.py +16 -16
- drawlib/v0_2/private/smartarts/bulletpoints.py +188 -0
- drawlib/v0_2/private/smartarts/dsart.py +7 -0
- drawlib/v0_2/private/smartarts/gridlayout.py +47 -26
- drawlib/v0_2/private/smartarts/pyramid.py +229 -17
- drawlib/v0_2/private/smartarts/table.py +683 -0
- drawlib/v0_2/private/smartarts/tree.py +10 -5
- drawlib/v0_2/private/util.py +116 -1
- drawlib/v0_2/private/validators/args.py +26 -11
- {drawlib-0.2.3.dev2.dist-info → drawlib-0.2.3.dev4.dist-info}/METADATA +1 -1
- {drawlib-0.2.3.dev2.dist-info → drawlib-0.2.3.dev4.dist-info}/RECORD +24 -21
- {drawlib-0.2.3.dev2.dist-info → drawlib-0.2.3.dev4.dist-info}/LICENSE.txt +0 -0
- {drawlib-0.2.3.dev2.dist-info → drawlib-0.2.3.dev4.dist-info}/WHEEL +0 -0
- {drawlib-0.2.3.dev2.dist-info → drawlib-0.2.3.dev4.dist-info}/entry_points.txt +0 -0
|
@@ -10,17 +10,24 @@
|
|
|
10
10
|
|
|
11
11
|
"""Canvas's original arrow feature implementation module."""
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
import math
|
|
14
|
+
from typing import List, Literal, Optional, Tuple, Union
|
|
15
|
+
|
|
16
|
+
from matplotlib.patches import PathPatch
|
|
17
|
+
from matplotlib.path import Path
|
|
14
18
|
|
|
15
19
|
import drawlib.v0_2.private.validators.args as validator
|
|
16
20
|
from drawlib.v0_2.private.core.model import ShapeStyle, ShapeTextStyle
|
|
17
21
|
from drawlib.v0_2.private.core.theme import dtheme
|
|
18
22
|
from drawlib.v0_2.private.core.util import ShapeUtil
|
|
19
23
|
from drawlib.v0_2.private.core_canvas.base import CanvasBase
|
|
24
|
+
from drawlib.v0_2.private.core_canvas.line import LineArcHelper
|
|
20
25
|
from drawlib.v0_2.private.util import (
|
|
21
26
|
error_handler,
|
|
22
27
|
get_angle,
|
|
23
28
|
get_distance,
|
|
29
|
+
get_rotated_path_points,
|
|
30
|
+
get_rotated_points,
|
|
24
31
|
)
|
|
25
32
|
|
|
26
33
|
|
|
@@ -137,3 +144,472 @@ class CanvasOriginalArrowFeature(CanvasBase):
|
|
|
137
144
|
textsize=textsize,
|
|
138
145
|
textstyle=textstyle,
|
|
139
146
|
)
|
|
147
|
+
|
|
148
|
+
@error_handler
|
|
149
|
+
def arrow_polyline(
|
|
150
|
+
self,
|
|
151
|
+
xys: List[Tuple[float, float]],
|
|
152
|
+
tail_width: float,
|
|
153
|
+
head_width: float,
|
|
154
|
+
head_length: float,
|
|
155
|
+
head: Literal[
|
|
156
|
+
"->",
|
|
157
|
+
"<-",
|
|
158
|
+
"<->",
|
|
159
|
+
] = "->",
|
|
160
|
+
r: float = 0,
|
|
161
|
+
style: Union[ShapeStyle, str, None] = None,
|
|
162
|
+
) -> None:
|
|
163
|
+
"""Draw single and double-headed arrow.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
xys: List[Tuple[float, float]]: Arrow points.
|
|
167
|
+
tail_width: float: Width of the arrow tail.
|
|
168
|
+
head_width: float: Width of the arrow head.
|
|
169
|
+
head_length: float: Length of the arrow head.
|
|
170
|
+
head: Literal["->", "<-", "<->"]: Arrow head style ("->", "<-", "<->").
|
|
171
|
+
r (float, optional): Radius for rounded connections (default is 0.0).
|
|
172
|
+
style: Union[ShapeStyle, str, None]: Optional style of the arrow.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
None
|
|
176
|
+
"""
|
|
177
|
+
# matplotlib FancyArrow, FancyArrowPatch seems not good
|
|
178
|
+
# for implement this function.
|
|
179
|
+
# Calculate arrow points pass it to shape().
|
|
180
|
+
|
|
181
|
+
style, _ = ShapeUtil.format_styles(
|
|
182
|
+
style,
|
|
183
|
+
None,
|
|
184
|
+
dtheme.arrowstyles.get,
|
|
185
|
+
dtheme.arrowtextstyles.get,
|
|
186
|
+
)
|
|
187
|
+
validator.validate_shape_args(locals())
|
|
188
|
+
|
|
189
|
+
def point_on_line(a: Tuple[float, float], b: Tuple[float, float], n: float) -> Tuple[float, float]:
|
|
190
|
+
ab = (b[0] - a[0], b[1] - a[1])
|
|
191
|
+
ab_length = math.sqrt(ab[0] ** 2 + ab[1] ** 2)
|
|
192
|
+
ab_unit = (ab[0] / ab_length, ab[1] / ab_length)
|
|
193
|
+
scaled_vector = (ab_unit[0] * n, ab_unit[1] * n)
|
|
194
|
+
new_point = (a[0] + scaled_vector[0], a[1] + scaled_vector[1])
|
|
195
|
+
return new_point
|
|
196
|
+
|
|
197
|
+
def point_parallel_to_line(
|
|
198
|
+
a: Tuple[float, float], b: Tuple[float, float], distance: float
|
|
199
|
+
) -> Tuple[float, float]:
|
|
200
|
+
ab = (b[0] - a[0], b[1] - a[1])
|
|
201
|
+
ab_length = math.sqrt(ab[0] ** 2 + ab[1] ** 2)
|
|
202
|
+
ab_unit = (ab[0] / ab_length, ab[1] / ab_length)
|
|
203
|
+
ab_perpendicular = (-ab_unit[1], ab_unit[0])
|
|
204
|
+
scaled_vector = (ab_perpendicular[0] * distance, ab_perpendicular[1] * distance)
|
|
205
|
+
new_point = (a[0] + scaled_vector[0], a[1] + scaled_vector[1])
|
|
206
|
+
return new_point
|
|
207
|
+
|
|
208
|
+
if head in {"<-", "<->"}:
|
|
209
|
+
xys_start = point_on_line(xys[0], xys[1], head_length)
|
|
210
|
+
else:
|
|
211
|
+
xys_start = xys[0]
|
|
212
|
+
|
|
213
|
+
if head in {"->", "<->"}:
|
|
214
|
+
xys_end = point_on_line(xys[-1], xys[-2], head_length)
|
|
215
|
+
else:
|
|
216
|
+
xys_end = xys[-1]
|
|
217
|
+
|
|
218
|
+
new_xys = xys[1:-1]
|
|
219
|
+
new_xys.insert(0, xys_start)
|
|
220
|
+
new_xys.append(xys_end)
|
|
221
|
+
aph = ArrowPolylineHelper(new_xys, r, 100)
|
|
222
|
+
parallel_xys1 = aph.get_parallel_curve_points(tail_width / 2)
|
|
223
|
+
parallel_xys2 = aph.get_parallel_curve_points(tail_width / -2)
|
|
224
|
+
parallel_xys2.reverse()
|
|
225
|
+
|
|
226
|
+
if head in {"<-", "<->"}:
|
|
227
|
+
ahp1 = point_parallel_to_line(xys_start, xys[1], head_width / 2)
|
|
228
|
+
ahp2 = point_parallel_to_line(xys_start, xys[1], head_width / -2)
|
|
229
|
+
parallel_xys1.insert(0, xys[0])
|
|
230
|
+
parallel_xys1.insert(1, ahp1)
|
|
231
|
+
parallel_xys2.append(ahp2)
|
|
232
|
+
|
|
233
|
+
if head in {"->", "<->"}:
|
|
234
|
+
ahp3 = point_parallel_to_line(xys_end, xys[-2], head_width / -2)
|
|
235
|
+
ahp4 = point_parallel_to_line(xys_end, xys[-2], head_width / 2)
|
|
236
|
+
parallel_xys1.append(ahp3)
|
|
237
|
+
parallel_xys1.append(xys[-1])
|
|
238
|
+
parallel_xys1.append(ahp4)
|
|
239
|
+
|
|
240
|
+
parallel_xys1.extend(parallel_xys2)
|
|
241
|
+
self.polygon(xys=parallel_xys1, style=style, text="", textstyle=None)
|
|
242
|
+
|
|
243
|
+
@error_handler
|
|
244
|
+
def arrow_arc(
|
|
245
|
+
self,
|
|
246
|
+
xy: Tuple[float, float],
|
|
247
|
+
width: float,
|
|
248
|
+
height: float,
|
|
249
|
+
tail_width: float,
|
|
250
|
+
head_width: float,
|
|
251
|
+
head_angle: float = 10,
|
|
252
|
+
head: Literal["->", "<-", "<->"] = "->",
|
|
253
|
+
angle_start: float = 0,
|
|
254
|
+
angle_end: float = 180,
|
|
255
|
+
angle: float = 0,
|
|
256
|
+
style: Union[ShapeStyle, str, None] = None,
|
|
257
|
+
) -> None:
|
|
258
|
+
"""Draw arc arrow.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
xy: Tuple[float, float]: The center point of the circle from which the arc arrow is drawn.
|
|
262
|
+
width: float: The width of the ellipse.
|
|
263
|
+
height: float: The height of the ellipse
|
|
264
|
+
tail_width: float: The width of the tail of the arrow.
|
|
265
|
+
head_width: float: The width of the head of the arrow.
|
|
266
|
+
head_angle: float: The angle of the arrowhead in degrees (default is 10).
|
|
267
|
+
head: Literal["->", "<-", "<->"]: The arrowhead style ("->", "<-", "<->").
|
|
268
|
+
angle_start: float: The starting angle of the arc in degrees (default is 0).
|
|
269
|
+
angle_end: float: The ending angle of the arc in degrees (default is 180).
|
|
270
|
+
angle (float): The angle of ellipse.
|
|
271
|
+
style: Union[ShapeStyle, str, None]: Optional shape style.
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
None
|
|
275
|
+
"""
|
|
276
|
+
style, _ = ShapeUtil.format_styles(
|
|
277
|
+
style,
|
|
278
|
+
None,
|
|
279
|
+
dtheme.arrowstyles.get,
|
|
280
|
+
dtheme.arrowtextstyles.get,
|
|
281
|
+
)
|
|
282
|
+
validator.validate_shape_args(locals())
|
|
283
|
+
|
|
284
|
+
width_int = width - tail_width
|
|
285
|
+
width_ext = width + tail_width
|
|
286
|
+
height_int = height - tail_width
|
|
287
|
+
height_ext = height + tail_width
|
|
288
|
+
|
|
289
|
+
if head in {"<-", "<->"}:
|
|
290
|
+
angle_start2 = angle_start + head_angle
|
|
291
|
+
angle_start2 %= 360
|
|
292
|
+
else:
|
|
293
|
+
angle_start2 = angle_start
|
|
294
|
+
|
|
295
|
+
if head in {"->", "<->"}:
|
|
296
|
+
angle_end2 = angle_end - head_angle
|
|
297
|
+
angle_end2 %= 360
|
|
298
|
+
else:
|
|
299
|
+
angle_end2 = angle_end
|
|
300
|
+
|
|
301
|
+
if angle_start2 > angle_end2:
|
|
302
|
+
pp1_fa = -1 * (360 - angle_start2)
|
|
303
|
+
pp1_ta = angle_end2
|
|
304
|
+
pp2_fa = angle_end2
|
|
305
|
+
pp2_ta = pp1_fa
|
|
306
|
+
else:
|
|
307
|
+
pp1_fa = angle_start2
|
|
308
|
+
pp1_ta = angle_end2
|
|
309
|
+
pp2_fa = angle_end2
|
|
310
|
+
pp2_ta = angle_start2
|
|
311
|
+
|
|
312
|
+
path_points1 = LineArcHelper.get_ellipse_path_points(
|
|
313
|
+
xy,
|
|
314
|
+
width_int,
|
|
315
|
+
height_int,
|
|
316
|
+
pp1_fa,
|
|
317
|
+
pp1_ta,
|
|
318
|
+
)
|
|
319
|
+
path_points2 = LineArcHelper.get_ellipse_path_points(
|
|
320
|
+
xy,
|
|
321
|
+
width_ext,
|
|
322
|
+
height_ext,
|
|
323
|
+
pp2_fa,
|
|
324
|
+
pp2_ta,
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
if angle_start > angle_end:
|
|
328
|
+
angle_start = -1 * (360 - angle_start)
|
|
329
|
+
|
|
330
|
+
if head in {"<-", "<->"}:
|
|
331
|
+
path_points1.insert(
|
|
332
|
+
0,
|
|
333
|
+
LineArcHelper.get_point_on_ellipse(
|
|
334
|
+
xy,
|
|
335
|
+
width,
|
|
336
|
+
height,
|
|
337
|
+
angle_start,
|
|
338
|
+
),
|
|
339
|
+
)
|
|
340
|
+
path_points1.insert(
|
|
341
|
+
1,
|
|
342
|
+
LineArcHelper.get_point_on_ellipse(
|
|
343
|
+
xy,
|
|
344
|
+
width - head_width,
|
|
345
|
+
height - head_width,
|
|
346
|
+
angle_start2,
|
|
347
|
+
),
|
|
348
|
+
)
|
|
349
|
+
path_points2.append(
|
|
350
|
+
LineArcHelper.get_point_on_ellipse(
|
|
351
|
+
xy,
|
|
352
|
+
width + head_width,
|
|
353
|
+
height + head_width,
|
|
354
|
+
angle_start2,
|
|
355
|
+
)
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
if head in {"->", "<->"}:
|
|
359
|
+
path_points1.append(
|
|
360
|
+
LineArcHelper.get_point_on_ellipse(
|
|
361
|
+
xy,
|
|
362
|
+
width - head_width,
|
|
363
|
+
height - head_width,
|
|
364
|
+
angle_end2,
|
|
365
|
+
)
|
|
366
|
+
)
|
|
367
|
+
path_points1.append(
|
|
368
|
+
LineArcHelper.get_point_on_ellipse(
|
|
369
|
+
xy,
|
|
370
|
+
width,
|
|
371
|
+
height,
|
|
372
|
+
angle_end,
|
|
373
|
+
)
|
|
374
|
+
)
|
|
375
|
+
path_points2.insert(
|
|
376
|
+
0,
|
|
377
|
+
LineArcHelper.get_point_on_ellipse(
|
|
378
|
+
xy,
|
|
379
|
+
width + head_width,
|
|
380
|
+
height + head_width,
|
|
381
|
+
angle_end2,
|
|
382
|
+
),
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
path_points1.extend(path_points2)
|
|
386
|
+
|
|
387
|
+
if angle != 0:
|
|
388
|
+
path_points1 = get_rotated_path_points(path_points1, xy, angle) # type: ignore
|
|
389
|
+
|
|
390
|
+
# create Path
|
|
391
|
+
vertices = [path_points1[0]]
|
|
392
|
+
codes = [Path.MOVETO]
|
|
393
|
+
for p in path_points1[1:]:
|
|
394
|
+
length = len(p)
|
|
395
|
+
if length not in {2, 3}:
|
|
396
|
+
raise ValueError()
|
|
397
|
+
|
|
398
|
+
if not isinstance(p[0], tuple):
|
|
399
|
+
vertices.append(p)
|
|
400
|
+
codes.append(Path.LINETO)
|
|
401
|
+
|
|
402
|
+
elif length == 2:
|
|
403
|
+
vertices.extend([p[0], p[1]]) # type: ignore
|
|
404
|
+
codes.extend([Path.CURVE3] * 2)
|
|
405
|
+
|
|
406
|
+
else:
|
|
407
|
+
vertices.extend([p[0], p[1], p[2]]) # type: ignore
|
|
408
|
+
codes.extend([Path.CURVE4] * 3)
|
|
409
|
+
|
|
410
|
+
vertices.append(path_points1[0])
|
|
411
|
+
codes.append(Path.CLOSEPOLY)
|
|
412
|
+
path = Path(vertices=vertices, codes=codes)
|
|
413
|
+
|
|
414
|
+
# create PathPatch
|
|
415
|
+
|
|
416
|
+
options = ShapeUtil.get_shape_options(style)
|
|
417
|
+
self._artists.append(PathPatch(path=path, **options))
|
|
418
|
+
|
|
419
|
+
@error_handler
|
|
420
|
+
def arrow_l(
|
|
421
|
+
self,
|
|
422
|
+
xy: Tuple[float, float],
|
|
423
|
+
width: float,
|
|
424
|
+
height: float,
|
|
425
|
+
tail_width: float,
|
|
426
|
+
head_width: float,
|
|
427
|
+
head_length: float,
|
|
428
|
+
head: Literal["->", "<-", "<->"] = "->",
|
|
429
|
+
r: float = 0,
|
|
430
|
+
angle: float = 0,
|
|
431
|
+
style: Union[ShapeStyle, str, None] = None,
|
|
432
|
+
) -> None:
|
|
433
|
+
"""Draw "L" shape arrow.
|
|
434
|
+
|
|
435
|
+
Args:
|
|
436
|
+
xy: Tuple[float, float]: The center point of L arrow.
|
|
437
|
+
width: float: The width L arrow.
|
|
438
|
+
height: float: The height of L arrow.
|
|
439
|
+
tail_width: float: The width of the tail of the arrow.
|
|
440
|
+
head_width: float: The width of the head of the arrow.
|
|
441
|
+
head_length: float: Length of the arrow head.
|
|
442
|
+
head: Literal["->", "<-", "<->"]: Arrow head style ("->", "<-", "<->").
|
|
443
|
+
r (float, optional): Radius for rounded connections (default is 0.0).
|
|
444
|
+
angle: float: rotate angle.
|
|
445
|
+
style: Union[ShapeStyle, str, None]: Optional shape style.
|
|
446
|
+
|
|
447
|
+
Returns:
|
|
448
|
+
None
|
|
449
|
+
"""
|
|
450
|
+
style, _ = ShapeUtil.format_styles(
|
|
451
|
+
style,
|
|
452
|
+
None,
|
|
453
|
+
dtheme.arrowstyles.get,
|
|
454
|
+
dtheme.arrowtextstyles.get,
|
|
455
|
+
)
|
|
456
|
+
validator.validate_shape_args(locals())
|
|
457
|
+
|
|
458
|
+
p1 = (xy[0] - width / 2, xy[1] + height / 2)
|
|
459
|
+
p2 = (xy[0] - width / 2, xy[1] - height / 2)
|
|
460
|
+
p3 = (xy[0] + width / 2, xy[1] - height / 2)
|
|
461
|
+
|
|
462
|
+
xys = [p1, p2, p3]
|
|
463
|
+
if angle != 0:
|
|
464
|
+
xys = get_rotated_points(xys=xys, center=xy, angle=angle)
|
|
465
|
+
|
|
466
|
+
self.arrow_polyline(
|
|
467
|
+
xys=xys,
|
|
468
|
+
tail_width=tail_width,
|
|
469
|
+
head_width=head_width,
|
|
470
|
+
head_length=head_length,
|
|
471
|
+
head=head,
|
|
472
|
+
r=r,
|
|
473
|
+
style=style,
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
@error_handler
|
|
477
|
+
def arrow_u(
|
|
478
|
+
self,
|
|
479
|
+
xy: Tuple[float, float],
|
|
480
|
+
width: float,
|
|
481
|
+
height: float,
|
|
482
|
+
tail_width: float,
|
|
483
|
+
head_width: float,
|
|
484
|
+
head_length: float,
|
|
485
|
+
head: Literal["->", "<-", "<->"] = "->",
|
|
486
|
+
r: float = 0,
|
|
487
|
+
angle: float = 0,
|
|
488
|
+
style: Union[ShapeStyle, str, None] = None,
|
|
489
|
+
) -> None:
|
|
490
|
+
"""Draw "U" shape arrow.
|
|
491
|
+
|
|
492
|
+
Args:
|
|
493
|
+
xy: Tuple[float, float]: The center point U arrow.
|
|
494
|
+
width: float: The width of the U arrow.
|
|
495
|
+
height: float: The height of the U arrow.
|
|
496
|
+
tail_width: float: The width of the tail of the arrow.
|
|
497
|
+
head_width: float: The width of the head of the arrow.
|
|
498
|
+
head_length: float: Length of the arrow head.
|
|
499
|
+
head: Literal["->", "<-", "<->"]: Arrow head style ("->", "<-", "<->").
|
|
500
|
+
r (float, optional): Radius for rounded connections (default is 0.0).
|
|
501
|
+
angle: float: rotate angle.
|
|
502
|
+
style: Union[ShapeStyle, str, None]: Optional shape style.
|
|
503
|
+
|
|
504
|
+
Returns:
|
|
505
|
+
None
|
|
506
|
+
"""
|
|
507
|
+
style, _ = ShapeUtil.format_styles(
|
|
508
|
+
style,
|
|
509
|
+
None,
|
|
510
|
+
dtheme.arrowstyles.get,
|
|
511
|
+
dtheme.arrowtextstyles.get,
|
|
512
|
+
)
|
|
513
|
+
validator.validate_shape_args(locals())
|
|
514
|
+
|
|
515
|
+
p1 = (xy[0] - width / 2, xy[1] + height / 2)
|
|
516
|
+
p2 = (xy[0] - width / 2, xy[1] - height / 2)
|
|
517
|
+
p3 = (xy[0] + width / 2, xy[1] - height / 2)
|
|
518
|
+
p4 = (xy[0] + width / 2, xy[1] + height / 2)
|
|
519
|
+
|
|
520
|
+
xys = [p1, p2, p3, p4]
|
|
521
|
+
if angle != 0:
|
|
522
|
+
xys = get_rotated_points(xys=xys, center=xy, angle=angle)
|
|
523
|
+
|
|
524
|
+
self.arrow_polyline(
|
|
525
|
+
xys=xys,
|
|
526
|
+
tail_width=tail_width,
|
|
527
|
+
head_width=head_width,
|
|
528
|
+
head_length=head_length,
|
|
529
|
+
head=head,
|
|
530
|
+
r=r,
|
|
531
|
+
style=style,
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
class ArrowPolylineHelper:
|
|
536
|
+
"""Internal class"""
|
|
537
|
+
|
|
538
|
+
def __init__(
|
|
539
|
+
self,
|
|
540
|
+
xys: List[Tuple[float, float]],
|
|
541
|
+
r: float,
|
|
542
|
+
num_points: int = 100,
|
|
543
|
+
) -> None:
|
|
544
|
+
"""Internal function"""
|
|
545
|
+
|
|
546
|
+
def get_mid_points(
|
|
547
|
+
a: Tuple[float, float],
|
|
548
|
+
b: Tuple[float, float],
|
|
549
|
+
) -> Tuple[Tuple[float, float], Tuple[float, float]]:
|
|
550
|
+
ab = [b[0] - a[0], b[1] - a[1]]
|
|
551
|
+
ab_distance = math.sqrt(ab[0] ** 2 + ab[1] ** 2)
|
|
552
|
+
ab_unit = [ab[0] / ab_distance, ab[1] / ab_distance]
|
|
553
|
+
c = (a[0] + r * ab_unit[0], a[1] + r * ab_unit[1])
|
|
554
|
+
d = (b[0] - r * ab_unit[0], b[1] - r * ab_unit[1])
|
|
555
|
+
return c, d
|
|
556
|
+
|
|
557
|
+
def bernstein_poly(i: int, n: int, t: float) -> float:
|
|
558
|
+
return math.comb(n, i) * (t**i) * ((1 - t) ** (n - i))
|
|
559
|
+
|
|
560
|
+
def get_points(
|
|
561
|
+
bezier_points: List[Tuple[float, float]],
|
|
562
|
+
) -> List[Tuple[float, float]]:
|
|
563
|
+
n = len(bezier_points) - 1
|
|
564
|
+
curve = []
|
|
565
|
+
for t in [i / (num_points - 1) for i in range(num_points)]:
|
|
566
|
+
x = sum(bernstein_poly(i, n, t) * bezier_points[i][0] for i in range(n + 1))
|
|
567
|
+
y = sum(bernstein_poly(i, n, t) * bezier_points[i][1] for i in range(n + 1))
|
|
568
|
+
curve.append((x, y))
|
|
569
|
+
return curve
|
|
570
|
+
|
|
571
|
+
points = []
|
|
572
|
+
bezier_start: Tuple[float, float] = (0, 0)
|
|
573
|
+
last_i = len(xys) - 2
|
|
574
|
+
# last_xy = (0, 0)
|
|
575
|
+
for i in range(len(xys)):
|
|
576
|
+
# first straight line
|
|
577
|
+
if i == 0:
|
|
578
|
+
_, bezier_start = get_mid_points(xys[0], xys[1])
|
|
579
|
+
points.append(xys[0])
|
|
580
|
+
continue
|
|
581
|
+
|
|
582
|
+
# last straight line
|
|
583
|
+
if i == last_i:
|
|
584
|
+
p1, _ = get_mid_points(xys[i], xys[i + 1])
|
|
585
|
+
bezier_points = [bezier_start, xys[i], p1]
|
|
586
|
+
points.extend(get_points(bezier_points))
|
|
587
|
+
points.append(xys[i + 1])
|
|
588
|
+
break
|
|
589
|
+
|
|
590
|
+
p1, p2 = get_mid_points(xys[i], xys[i + 1])
|
|
591
|
+
bezier_points = [bezier_start, xys[i], p1]
|
|
592
|
+
points.extend(get_points(bezier_points))
|
|
593
|
+
bezier_start = p2
|
|
594
|
+
|
|
595
|
+
self._original_curve_points = points
|
|
596
|
+
|
|
597
|
+
def get_parallel_curve_points(self, distance: float) -> List[Tuple[float, float]]:
|
|
598
|
+
"""Internal function"""
|
|
599
|
+
|
|
600
|
+
def get_distance(p1: Tuple[float, float], p2: Tuple[float, float]) -> float:
|
|
601
|
+
return math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2)
|
|
602
|
+
|
|
603
|
+
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]
|
|
606
|
+
dx, dy = p1[0] - p0[0], p1[1] - p0[1]
|
|
607
|
+
length = get_distance(p0, p1)
|
|
608
|
+
nx, ny = -dy / length, dx / length
|
|
609
|
+
px0, py0 = p0[0] + distance * nx, p0[1] + distance * ny
|
|
610
|
+
px1, py1 = p1[0] + distance * nx, p1[1] + distance * ny
|
|
611
|
+
parallel_curve_points.append((px0, py0))
|
|
612
|
+
if i == len(self._original_curve_points) - 1:
|
|
613
|
+
parallel_curve_points.append((px1, py1))
|
|
614
|
+
|
|
615
|
+
return parallel_curve_points
|
|
@@ -16,7 +16,6 @@ from matplotlib.patches import (
|
|
|
16
16
|
Arc,
|
|
17
17
|
Circle,
|
|
18
18
|
Ellipse,
|
|
19
|
-
Polygon,
|
|
20
19
|
RegularPolygon,
|
|
21
20
|
Wedge,
|
|
22
21
|
)
|
|
@@ -26,7 +25,7 @@ from drawlib.v0_2.private.core.model import ShapeStyle, ShapeTextStyle
|
|
|
26
25
|
from drawlib.v0_2.private.core.theme import dtheme
|
|
27
26
|
from drawlib.v0_2.private.core.util import ShapeUtil
|
|
28
27
|
from drawlib.v0_2.private.core_canvas.base import CanvasBase
|
|
29
|
-
from drawlib.v0_2.private.util import error_handler
|
|
28
|
+
from drawlib.v0_2.private.util import error_handler
|
|
30
29
|
|
|
31
30
|
|
|
32
31
|
class CanvasPatchesFeature(CanvasBase):
|
|
@@ -46,8 +45,8 @@ class CanvasPatchesFeature(CanvasBase):
|
|
|
46
45
|
xy: Tuple[float, float],
|
|
47
46
|
width: float,
|
|
48
47
|
height: float,
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
angle_start: Union[int, float] = 0.0,
|
|
49
|
+
angle_end: Union[int, float] = 360.0,
|
|
51
50
|
angle: Union[int, float] = 0.0,
|
|
52
51
|
style: Union[ShapeStyle, str, None] = None,
|
|
53
52
|
text: str = "",
|
|
@@ -60,8 +59,8 @@ class CanvasPatchesFeature(CanvasBase):
|
|
|
60
59
|
xy: Center of the arc.
|
|
61
60
|
width: Width of the arc.
|
|
62
61
|
height: Height of the arc.
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
angle_start (optional): Starting angle of the arc (default is 0.0).
|
|
63
|
+
angle_end (optional): Ending angle of the arc (default is 360.0).
|
|
65
64
|
angle (optional): Rotation angle of the arc.
|
|
66
65
|
style (optional): Style of the arc.
|
|
67
66
|
text (optional): Text shown at the center of the arc.
|
|
@@ -90,8 +89,8 @@ class CanvasPatchesFeature(CanvasBase):
|
|
|
90
89
|
width=width,
|
|
91
90
|
height=height,
|
|
92
91
|
angle=angle,
|
|
93
|
-
theta1=
|
|
94
|
-
theta2=
|
|
92
|
+
theta1=angle_start,
|
|
93
|
+
theta2=angle_end,
|
|
95
94
|
**options,
|
|
96
95
|
)
|
|
97
96
|
)
|
|
@@ -227,55 +226,6 @@ class CanvasPatchesFeature(CanvasBase):
|
|
|
227
226
|
),
|
|
228
227
|
)
|
|
229
228
|
|
|
230
|
-
@error_handler
|
|
231
|
-
def polygon(
|
|
232
|
-
self,
|
|
233
|
-
xys: List[Tuple[float, float]],
|
|
234
|
-
style: Union[ShapeStyle, str, None] = None,
|
|
235
|
-
text: str = "",
|
|
236
|
-
textsize: Optional[float] = None,
|
|
237
|
-
textstyle: Union[ShapeTextStyle, str, None] = None,
|
|
238
|
-
) -> None:
|
|
239
|
-
"""Draw a polygon on the canvas.
|
|
240
|
-
|
|
241
|
-
Args:
|
|
242
|
-
xys: List of vertices [(x1, y1), ...(x_n, y_n)].
|
|
243
|
-
style (optional): Style of the polygon.
|
|
244
|
-
text (optional): Text shown at the center of the polygon.
|
|
245
|
-
textsize (optional): Font size of the text.
|
|
246
|
-
textstyle (optional): Style of the text.
|
|
247
|
-
|
|
248
|
-
Returns:
|
|
249
|
-
None
|
|
250
|
-
"""
|
|
251
|
-
style, textstyle = ShapeUtil.format_styles(
|
|
252
|
-
style,
|
|
253
|
-
textstyle,
|
|
254
|
-
dtheme.polygonstyles.get,
|
|
255
|
-
dtheme.polygontextstyles.get,
|
|
256
|
-
)
|
|
257
|
-
validator.validate_shape_args(locals())
|
|
258
|
-
|
|
259
|
-
if textsize is not None:
|
|
260
|
-
textstyle.size = textsize
|
|
261
|
-
|
|
262
|
-
style.halign = None
|
|
263
|
-
style.valign = None
|
|
264
|
-
options = ShapeUtil.get_shape_options(style)
|
|
265
|
-
self._artists.append(Polygon(xy=xys, closed=True, **options))
|
|
266
|
-
|
|
267
|
-
if not text:
|
|
268
|
-
return
|
|
269
|
-
center, (_, _) = get_center_and_size(xys)
|
|
270
|
-
self._artists.append(
|
|
271
|
-
ShapeUtil.get_shape_text(
|
|
272
|
-
center,
|
|
273
|
-
text=text,
|
|
274
|
-
angle=0,
|
|
275
|
-
style=textstyle,
|
|
276
|
-
),
|
|
277
|
-
)
|
|
278
|
-
|
|
279
229
|
@error_handler
|
|
280
230
|
def regularpolygon(
|
|
281
231
|
self,
|
|
@@ -345,8 +295,8 @@ class CanvasPatchesFeature(CanvasBase):
|
|
|
345
295
|
xy: Tuple[float, float],
|
|
346
296
|
radius: float,
|
|
347
297
|
width: Optional[float] = None,
|
|
348
|
-
|
|
349
|
-
|
|
298
|
+
angle_start: float = 0,
|
|
299
|
+
angle_end: float = 360,
|
|
350
300
|
angle: Union[int, float] = 0.0,
|
|
351
301
|
style: Union[ShapeStyle, str, None] = None,
|
|
352
302
|
text: str = "",
|
|
@@ -360,8 +310,8 @@ class CanvasPatchesFeature(CanvasBase):
|
|
|
360
310
|
radius: Radius of the wedge.
|
|
361
311
|
width (optional): Length from outer to inner circumference.
|
|
362
312
|
Default is same as radius.
|
|
363
|
-
|
|
364
|
-
|
|
313
|
+
angle_start (optional): Starting angle of the wedge (default is 0).
|
|
314
|
+
angle_end (optional): Ending angle of the wedge (default is 360).
|
|
365
315
|
angle (optional): Rotation angle of the wedge.
|
|
366
316
|
style (optional): Style of the wedge.
|
|
367
317
|
text (optional): Text shown at the center of the wedge.
|
|
@@ -391,8 +341,8 @@ class CanvasPatchesFeature(CanvasBase):
|
|
|
391
341
|
center=xy,
|
|
392
342
|
r=radius,
|
|
393
343
|
width=width, # None makes no hole
|
|
394
|
-
theta1=
|
|
395
|
-
theta2=
|
|
344
|
+
theta1=angle_start + angle,
|
|
345
|
+
theta2=angle_end + angle,
|
|
396
346
|
**options,
|
|
397
347
|
)
|
|
398
348
|
)
|
|
@@ -459,8 +409,8 @@ class CanvasPatchesFeature(CanvasBase):
|
|
|
459
409
|
self,
|
|
460
410
|
xy: Tuple[float, float],
|
|
461
411
|
radius: float,
|
|
462
|
-
|
|
463
|
-
|
|
412
|
+
angle_start: float = 0,
|
|
413
|
+
angle_end: float = 180,
|
|
464
414
|
angle: Union[int, float] = 0.0,
|
|
465
415
|
style: Union[ShapeStyle, str, None] = None,
|
|
466
416
|
text: str = "",
|
|
@@ -472,8 +422,8 @@ class CanvasPatchesFeature(CanvasBase):
|
|
|
472
422
|
Args:
|
|
473
423
|
xy: Center of the fan.
|
|
474
424
|
radius: Radius of the fan.
|
|
475
|
-
|
|
476
|
-
|
|
425
|
+
angle_start (optional): Starting angle of the fan (default is 0).
|
|
426
|
+
angle_end (optional): Ending angle of the fan (default is 180).
|
|
477
427
|
angle (optional): Rotation angle of the fan.
|
|
478
428
|
style (optional): Style of the fan.
|
|
479
429
|
text (optional): Text shown at the center of the fan.
|
|
@@ -495,8 +445,8 @@ class CanvasPatchesFeature(CanvasBase):
|
|
|
495
445
|
xy=xy,
|
|
496
446
|
radius=radius,
|
|
497
447
|
width=None,
|
|
498
|
-
|
|
499
|
-
|
|
448
|
+
angle_start=angle_start,
|
|
449
|
+
angle_end=angle_end,
|
|
500
450
|
angle=angle,
|
|
501
451
|
style=style,
|
|
502
452
|
text=text,
|