h2o-lightwave 1.7.6__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.
@@ -0,0 +1,841 @@
1
+ import json
2
+ import math
3
+ from typing import Union, Optional, List
4
+ from .core import pack, data as _data, Data, Ref, Expando, expando_to_dict
5
+
6
+
7
+ # TODO add formal parameters for shape functions, including presentation attributes:
8
+ # https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/Presentation
9
+
10
+ def stage(**kwargs) -> str:
11
+ """
12
+ Create a stage. A stage holds static graphics elements that are rendered as part of the background (behind the scene).
13
+ The return value must be assigned to the `stage` property of a `h2o_wave.types.GraphicsCard`.
14
+
15
+ Args:
16
+ kwargs: Graphical elements to render as part of the stage.
17
+ Returns:
18
+ Packed data.
19
+ """
20
+ return pack([expando_to_dict(v) for v in kwargs.values()])
21
+
22
+
23
+ def scene(**kwargs) -> Data:
24
+ """
25
+ Create a scene. A scene holds graphic elements whose attributes need to be changed dynamically (causing a re-render).
26
+ The return value must be assigned to the `scene` property of a `h2o_wave.types.GraphicsCard`.
27
+
28
+ Args:
29
+ kwargs: Graphical elements to render as part of the scene.
30
+ Returns:
31
+ A `h2o_wave.core.Data` instance.
32
+ """
33
+ return _data(fields='d o', rows={k: [json.dumps(expando_to_dict(v)), ''] for k, v in kwargs.items()})
34
+
35
+
36
+ def draw(element: Ref, **kwargs) -> Ref:
37
+ """
38
+ Schedule a redraw of the specified graphical element using the provided attributes.
39
+
40
+ Args:
41
+ element: A reference to a graphical element.
42
+ kwargs: Attributes to use while performing a redraw.
43
+ Returns:
44
+ The element reference, without change.
45
+ """
46
+ element['o'] = json.dumps(kwargs)
47
+ return element
48
+
49
+
50
+ def reset(element: Ref) -> Ref:
51
+ """
52
+ Schedule a redraw of the specified graphical element using its original attributes.
53
+ Calling this function clears any changes performed using the `h2o_wave.graphics.draw` function.
54
+
55
+ Args:
56
+ element: A reference to a graphical element.
57
+ Returns:
58
+ The element reference, without change.
59
+ """
60
+ element['o'] = ''
61
+ return element
62
+
63
+
64
+ def _el(t: str, d: dict) -> Expando:
65
+ d['_t'] = t
66
+ return Expando(d)
67
+
68
+
69
+ _element_types = dict(
70
+ a='arc',
71
+ c='circle',
72
+ e='ellipse',
73
+ i='image',
74
+ l='line',
75
+ p='path',
76
+ pg='polygon',
77
+ pl='polyline',
78
+ s='spline',
79
+ r='rect',
80
+ t='text',
81
+ )
82
+
83
+
84
+ def type_of(element: Expando) -> Optional[str]:
85
+ """
86
+ Get the type of the graphical element.
87
+
88
+ Args:
89
+ element: A graphical element.
90
+ Returns:
91
+ A string indicating the type of the element, e.g. 'circle', 'line', etc.
92
+ """
93
+ return _element_types.get(element['_t'], None)
94
+
95
+
96
+ def arc(r1: float, r2: float, a1: float, a2: float, **kwargs) -> Expando:
97
+ """
98
+ Draw circular or annular sector, as in a pie or donut chart, centered at (0, 0).
99
+
100
+ Args:
101
+ r1: inner radius.
102
+ r2: outer radius.
103
+ a1: start angle, in degrees.
104
+ a2: end angle, in degrees.
105
+ kwargs: Attributes to use for the initial render. SVG attributes, snake-cased.
106
+ Returns:
107
+ Data for the graphical element.
108
+ """
109
+ return _el('a', dict(r1=r1, r2=r2, a1=a1, a2=a2, **kwargs))
110
+
111
+
112
+ def circle(**kwargs) -> Expando:
113
+ """
114
+ Draw a circle.
115
+ See https://developer.mozilla.org/en-US/docs/Web/SVG/Element/circle
116
+
117
+ Args:
118
+ kwargs: Attributes to use for the initial render. SVG attributes, snake-cased.
119
+ Returns:
120
+ Data for the graphical element.
121
+ """
122
+ return _el('c', kwargs)
123
+
124
+
125
+ def ellipse(**kwargs) -> Expando:
126
+ """
127
+ Draw an ellipse.
128
+ See https://developer.mozilla.org/en-US/docs/Web/SVG/Element/ellipse
129
+
130
+ Args:
131
+ kwargs: Attributes to use for the initial render. SVG attributes, snake-cased.
132
+ Returns:
133
+ Data for the graphical element.
134
+ """
135
+ return _el('e', kwargs)
136
+
137
+
138
+ def image(**kwargs) -> Expando:
139
+ """
140
+ Draw an image.
141
+ See https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image
142
+
143
+ Args:
144
+ kwargs: Attributes to use for the initial render. SVG attributes, snake-cased.
145
+ Returns:
146
+ Data for the graphical element.
147
+ """
148
+ return _el('i', kwargs)
149
+
150
+
151
+ def line(**kwargs) -> Expando:
152
+ """
153
+ Draw a line.
154
+ See https://developer.mozilla.org/en-US/docs/Web/SVG/Element/line
155
+
156
+ Args:
157
+ kwargs: Attributes to use for the initial render. SVG attributes, snake-cased.
158
+ Returns:
159
+ Data for the graphical element.
160
+ """
161
+ return _el('l', kwargs)
162
+
163
+
164
+ def path(**kwargs) -> Expando:
165
+ """
166
+ Draw a path.
167
+ See https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path
168
+
169
+ Args:
170
+ kwargs: Attributes to use for the initial render. SVG attributes, snake-cased.
171
+ Returns:
172
+ Data for the graphical element.
173
+ """
174
+ return _el('p', kwargs)
175
+
176
+
177
+ def polygon(**kwargs) -> Expando:
178
+ """
179
+ Draw a polygon.
180
+ See https://developer.mozilla.org/en-US/docs/Web/SVG/Element/polygon
181
+
182
+ Args:
183
+ kwargs: Attributes to use for the initial render. SVG attributes, snake-cased.
184
+ Returns:
185
+ Data for the graphical element.
186
+ """
187
+ return _el('pg', kwargs)
188
+
189
+
190
+ def polyline(**kwargs) -> Expando:
191
+ """
192
+ Draw a polyline.
193
+ See https://developer.mozilla.org/en-US/docs/Web/SVG/Element/polyline
194
+
195
+ Args:
196
+ kwargs: Attributes to use for the initial render. SVG attributes, snake-cased.
197
+ Returns:
198
+ Data for the graphical element.
199
+ """
200
+ return _el('pl', kwargs)
201
+
202
+
203
+ Floats = Optional[List[Optional[float]]]
204
+
205
+
206
+ def _str(fs: Floats) -> Optional[str]:
207
+ if fs is None:
208
+ return None
209
+ return ' '.join(['' if f is None else str(round(f, 2)) for f in fs])
210
+
211
+
212
+ def spline(x: Floats = None, y: Floats = None,
213
+ x0: Floats = None, y0: Floats = None,
214
+ curve: Optional[str] = None, radial: Optional[bool] = None, **kwargs) -> Expando:
215
+ """
216
+ Draw a spline.
217
+
218
+ If x, y are specified, draws a regular spline.
219
+
220
+ If x, y, y0 are specified, draws a horizontal area spline. Sets baseline to zero if y0 is an empty list.
221
+
222
+ If x, x0, y are specified, draws a vertical area spline. Sets baseline to zero if x0 is an empty list
223
+
224
+ Missing information is rendered as gaps in the spline.
225
+
226
+ Args:
227
+ x: x-coordinates.
228
+ y: y-coordinates.
229
+ x0: base x-coordinates.
230
+ y0: base y-coordinates.
231
+ curve: Interpolation. One of basis, basis-closed, basis-open, cardinal, cardinal-closed, cardinal-open, smooth, smooth-closed, smooth-open, linear, linear-closed, monotone-x, monotone-y, natural, step, step-after, step-before. Defaults to linear.
232
+ radial: Whether (x, y) should be treated as (angle,radius) or (x0, x, y0, y) should be treated as (start-angle, end-angle, inner-radius, outer-radius).
233
+ kwargs: Attributes to use for the initial render. SVG attributes, snake-cased.
234
+ Returns:
235
+ Data for the graphical element.
236
+ """
237
+ attrs = dict(x=_str(x), y=_str(y), x0=_str(x0), y0=_str(y0), curve=curve, radial=radial)
238
+ return _el('s', dict(**{k: v for k, v in attrs.items() if v is not None}, **kwargs))
239
+
240
+
241
+ def rect(**kwargs) -> Expando:
242
+ """
243
+ Draw a rectangle.
244
+ See https://developer.mozilla.org/en-US/docs/Web/SVG/Element/rect
245
+
246
+ Args:
247
+ kwargs: Attributes to use for the initial render. SVG attributes, snake-cased.
248
+ Returns:
249
+ Data for the graphical element.
250
+ """
251
+ return _el('r', kwargs)
252
+
253
+
254
+ def text(text: str, **kwargs) -> Expando:
255
+ """
256
+ Draw text.
257
+ See https://developer.mozilla.org/en-US/docs/Web/SVG/Element/text
258
+
259
+ Args:
260
+ text: The text content.
261
+ kwargs: Attributes to use for the initial render. SVG attributes, snake-cased.
262
+ Returns:
263
+ Data for the graphical element.
264
+ """
265
+ return _el('t', dict(text=text, **kwargs))
266
+
267
+
268
+ class Path:
269
+ """
270
+ A convenience class for drawing SVG paths.
271
+ """
272
+
273
+ def __init__(self):
274
+ self.__d = []
275
+
276
+ def _d(self, command: str, *args) -> 'Path':
277
+ self.__d.append(command)
278
+ for arg in args:
279
+ self.__d.append(str(round(arg, 2) if isinstance(arg, float) else arg))
280
+ return self
281
+
282
+ def d(self) -> str:
283
+ """
284
+ Serialize this path's commands into SVG path data.
285
+
286
+ Returns:
287
+ The ``d`` attribute for a SVG path.
288
+ """
289
+ return ' '.join(self.__d)
290
+
291
+ def path(self, **kwargs) -> Expando:
292
+ """
293
+ A SVG path element representing the commands in this ``Path`` instance.
294
+ Same as calling ``h2o_wave.graphics.path(d=path.d())``
295
+
296
+ Args:
297
+ kwargs: Additional attributes for the SVG path element.
298
+ Returns:
299
+ A SVG path element.
300
+ """
301
+ return path(d=self.d(), **kwargs)
302
+
303
+ def M(self, x: float, y: float) -> 'Path':
304
+ """
305
+ Start a new sub-path at the given (x,y) coordinates.
306
+ In absolute coordinates.
307
+
308
+ See https://www.w3.org/TR/SVG/paths.html#PathDataMovetoCommands
309
+
310
+ Args:
311
+ x: x-coordinate
312
+ y: y-coordinate
313
+ Returns:
314
+ The current ``Path`` instance.
315
+ """
316
+ return self._d('M', x, y)
317
+
318
+ def m(self, x: float, y: float) -> 'Path':
319
+ """
320
+ Start a new sub-path at the given (x,y) coordinates.
321
+ In relative coordinates.
322
+
323
+ See https://www.w3.org/TR/SVG/paths.html#PathDataMovetoCommands
324
+
325
+ Args:
326
+ x: x-coordinate
327
+ y: y-coordinate
328
+ Returns:
329
+ The current ``Path`` instance.
330
+ """
331
+ return self._d('m', x, y)
332
+
333
+ def Z(self) -> 'Path':
334
+ """
335
+ Close the current subpath by connecting it back to the current subpath's initial point.
336
+
337
+ See https://www.w3.org/TR/SVG/paths.html#PathDataClosePathCommand
338
+
339
+ Returns:
340
+ The current ``Path`` instance.
341
+ """
342
+ return self._d('Z')
343
+
344
+ def z(self) -> 'Path':
345
+ """
346
+ Close the current subpath by connecting it back to the current subpath's initial point.
347
+
348
+ See https://www.w3.org/TR/SVG/paths.html#PathDataClosePathCommand
349
+
350
+ Returns:
351
+ The current ``Path`` instance.
352
+ """
353
+ return self._d('z')
354
+
355
+ def L(self, x: float, y: float) -> 'Path':
356
+ """
357
+ Draw a line from the current point to the given (x,y) coordinate which becomes the new current point.
358
+ In absolute coordinates.
359
+
360
+ See https://www.w3.org/TR/SVG/paths.html#PathDataLinetoCommands
361
+
362
+ Args:
363
+ x: x-coordinate
364
+ y: y-coordinate
365
+ Returns:
366
+ The current ``Path`` instance.
367
+ """
368
+ return self._d('L', x, y)
369
+
370
+ def l(self, x: float, y: float) -> 'Path':
371
+ """
372
+ Draw a line from the current point to the given (x,y) coordinate which becomes the new current point.
373
+ In relative coordinates.
374
+
375
+ See https://www.w3.org/TR/SVG/paths.html#PathDataLinetoCommands
376
+
377
+ Args:
378
+ x: x-coordinate
379
+ y: y-coordinate
380
+ Returns:
381
+ The current ``Path`` instance.
382
+ """
383
+ return self._d('l', x, y)
384
+
385
+ def H(self, x: float) -> 'Path':
386
+ """
387
+ Draws a horizontal line from the current point.
388
+ In absolute coordinates.
389
+
390
+ See https://www.w3.org/TR/SVG/paths.html#PathDataLinetoCommands
391
+
392
+ Args:
393
+ x: x-coordinate
394
+ Returns:
395
+ The current ``Path`` instance.
396
+ """
397
+ return self._d('H', x)
398
+
399
+ def h(self, x: float) -> 'Path':
400
+ """
401
+ Draws a horizontal line from the current point.
402
+ In relative coordinates.
403
+
404
+ See https://www.w3.org/TR/SVG/paths.html#PathDataLinetoCommands
405
+
406
+ Args:
407
+ x: x-coordinate
408
+ Returns:
409
+ The current ``Path`` instance.
410
+ """
411
+ return self._d('h', x)
412
+
413
+ def V(self, y: float) -> 'Path':
414
+ """
415
+ Draws a vertical line from the current point.
416
+ In absolute coordinates.
417
+
418
+ See https://www.w3.org/TR/SVG/paths.html#PathDataLinetoCommands
419
+
420
+ Args:
421
+ y: y-coordinate
422
+ Returns:
423
+ The current ``Path`` instance.
424
+ """
425
+ return self._d('V', y)
426
+
427
+ def v(self, y: float) -> 'Path':
428
+ """
429
+ Draws a vertical line from the current point.
430
+ In relative coordinates.
431
+
432
+ See https://www.w3.org/TR/SVG/paths.html#PathDataLinetoCommands
433
+
434
+ Args:
435
+ y: y-coordinate
436
+ Returns:
437
+ The current ``Path`` instance.
438
+ """
439
+ return self._d('v', y)
440
+
441
+ def C(self, x1: float, y1: float, x2: float, y2: float, x: float, y: float) -> 'Path':
442
+ """
443
+ Draws a cubic Bézier curve from the current point to (x,y) using (x1,y1) as the control point at the beginning
444
+ of the curve and (x2,y2) as the control point at the end of the curve.
445
+ In absolute coordinates.
446
+
447
+ See https://www.w3.org/TR/SVG/paths.html#PathDataCubicBezierCommands
448
+
449
+ Args:
450
+ x1: x-coordinate of first control point
451
+ y1: y-coordinate of first control point
452
+ x2: x-coordinate of second control point
453
+ y2: y-coordinate of second control point
454
+ x: x-coordinate
455
+ y: y-coordinate
456
+ Returns:
457
+ The current ``Path`` instance.
458
+ """
459
+ return self._d('C', x1, y1, x2, y2, x, y)
460
+
461
+ def c(self, x1: float, y1: float, x2: float, y2: float, x: float, y: float) -> 'Path':
462
+ """
463
+ Draws a cubic Bézier curve from the current point to (x,y) using (x1,y1) as the control point at the beginning
464
+ of the curve and (x2,y2) as the control point at the end of the curve.
465
+ In relative coordinates.
466
+
467
+ See https://www.w3.org/TR/SVG/paths.html#PathDataCubicBezierCommands
468
+
469
+ Args:
470
+ x1: x-coordinate of first control point
471
+ y1: y-coordinate of first control point
472
+ x2: x-coordinate of second control point
473
+ y2: y-coordinate of second control point
474
+ x: x-coordinate
475
+ y: y-coordinate
476
+ Returns:
477
+ The current ``Path`` instance.
478
+ """
479
+ return self._d('c', x1, y1, x2, y2, x, y)
480
+
481
+ def S(self, x2: float, y2: float, x: float, y: float) -> 'Path':
482
+ """
483
+ Draws a cubic Bézier curve from the current point to (x,y). The first control point is assumed to be the
484
+ reflection of the second control point on the previous command relative to the current point.
485
+ (x2,y2) is the second control point (i.e., the control point at the end of the curve).
486
+ In absolute coordinates.
487
+
488
+ See https://www.w3.org/TR/SVG/paths.html#PathDataCubicBezierCommands
489
+
490
+ Args:
491
+ x2: x-coordinate of second control point
492
+ y2: y-coordinate of second control point
493
+ x: x-coordinate
494
+ y: y-coordinate
495
+ Returns:
496
+ The current ``Path`` instance.
497
+ """
498
+ return self._d('S', x2, y2, x, y)
499
+
500
+ def s(self, x2: float, y2: float, x: float, y: float) -> 'Path':
501
+ """
502
+ Draws a cubic Bézier curve from the current point to (x,y). The first control point is assumed to be the
503
+ reflection of the second control point on the previous command relative to the current point.
504
+ (x2,y2) is the second control point (i.e., the control point at the end of the curve).
505
+ In relative coordinates.
506
+
507
+ See https://www.w3.org/TR/SVG/paths.html#PathDataCubicBezierCommands
508
+
509
+ Args:
510
+ x2: x-coordinate of second control point
511
+ y2: y-coordinate of second control point
512
+ x: x-coordinate
513
+ y: y-coordinate
514
+ Returns:
515
+ The current ``Path`` instance.
516
+ """
517
+ return self._d('s', x2, y2, x, y)
518
+
519
+ def Q(self, x1: float, y1: float, x: float, y: float) -> 'Path':
520
+ """
521
+ Draws a quadratic Bézier curve from the current point to (x,y) using (x1,y1) as the control point.
522
+ In absolute coordinates.
523
+
524
+ See https://www.w3.org/TR/SVG/paths.html#PathDataQuadraticBezierCommands
525
+
526
+ Args:
527
+ x1: x-coordinate of first control point
528
+ y1: y-coordinate of first control point
529
+ x: x-coordinate
530
+ y: y-coordinate
531
+ Returns:
532
+ The current ``Path`` instance.
533
+ """
534
+ return self._d('Q', x1, y1, x, y)
535
+
536
+ def q(self, x1: float, y1: float, x: float, y: float) -> 'Path':
537
+ """
538
+ Draws a quadratic Bézier curve from the current point to (x,y) using (x1,y1) as the control point.
539
+ In relative coordinates.
540
+
541
+ See https://www.w3.org/TR/SVG/paths.html#PathDataQuadraticBezierCommands
542
+
543
+ Args:
544
+ x1: x-coordinate of first control point
545
+ y1: y-coordinate of first control point
546
+ x: x-coordinate
547
+ y: y-coordinate
548
+ Returns:
549
+ The current ``Path`` instance.
550
+ """
551
+ return self._d('q', x1, y1, x, y)
552
+
553
+ def T(self, x: float, y: float) -> 'Path':
554
+ """
555
+ Draws a quadratic Bézier curve from the current point to (x,y). The control point is assumed to be the
556
+ reflection of the control point on the previous command relative to the current point.
557
+ In absolute coordinates.
558
+
559
+ See https://www.w3.org/TR/SVG/paths.html#PathDataQuadraticBezierCommands
560
+
561
+ Args:
562
+ x: x-coordinate
563
+ y: y-coordinate
564
+ Returns:
565
+ The current ``Path`` instance.
566
+ """
567
+ return self._d('T', x, y)
568
+
569
+ def t(self, x: float, y: float) -> 'Path':
570
+ """
571
+ Draws a quadratic Bézier curve from the current point to (x,y). The control point is assumed to be the
572
+ reflection of the control point on the previous command relative to the current point.
573
+ In relative coordinates.
574
+
575
+ See https://www.w3.org/TR/SVG/paths.html#PathDataQuadraticBezierCommands
576
+
577
+ Args:
578
+ x: x-coordinate
579
+ y: y-coordinate
580
+ Returns:
581
+ The current ``Path`` instance.
582
+ """
583
+ return self._d('t', x, y)
584
+
585
+ def A(self, rx: float, ry: float, x_axis_rotation: float, large_arc: bool, sweep: bool, x: float,
586
+ y: float) -> 'Path':
587
+ """
588
+ Draws an elliptical arc from the current point to (x, y). The size and orientation of the ellipse are defined
589
+ by two radii (rx, ry) and an ``x_axis_rotation``, which indicates how the ellipse as a whole is rotated,
590
+ in degrees, relative to the current coordinate system. The center (cx, cy) of the ellipse is calculated
591
+ automatically to satisfy the constraints imposed by the other parameters. ``large_arc`` and ``sweep_flag``
592
+ contribute to the automatic calculations and help determine how the arc is drawn.
593
+ In absolute coordinates.
594
+
595
+ See https://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
596
+
597
+ Args:
598
+ rx: x-radius
599
+ ry: y-radius
600
+ x_axis_rotation: Rotation in degrees.
601
+ large_arc: Determines if the arc should be greater than or less than 180 degrees.
602
+ sweep: Determines if the arc should begin moving at positive angles or negative ones.
603
+ x: x-coordinate
604
+ y: y-coordinate
605
+ Returns:
606
+ The current ``Path`` instance.
607
+ """
608
+ return self._d('A', rx, ry, x_axis_rotation, 1 if large_arc else 0, 1 if sweep else 0, x, y)
609
+
610
+ def a(self, rx: float, ry: float, x_axis_rotation: float, large_arc: bool, sweep: bool, x: float,
611
+ y: float) -> 'Path':
612
+ """
613
+ Draws an elliptical arc from the current point to (x, y). The size and orientation of the ellipse are defined
614
+ by two radii (rx, ry) and an ``x_axis_rotation``, which indicates how the ellipse as a whole is rotated,
615
+ in degrees, relative to the current coordinate system. The center (cx, cy) of the ellipse is calculated
616
+ automatically to satisfy the constraints imposed by the other parameters. ``large_arc`` and ``sweep_flag``
617
+ contribute to the automatic calculations and help determine how the arc is drawn.
618
+ In relative coordinates.
619
+
620
+ See https://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
621
+
622
+ Args:
623
+ rx: x-radius
624
+ ry: y-radius
625
+ x_axis_rotation: Rotation in degrees.
626
+ large_arc: Determines if the arc should be greater than or less than 180 degrees.
627
+ sweep: Determines if the arc should begin moving at positive angles or negative ones.
628
+ x: x-coordinate
629
+ y: y-coordinate
630
+ Returns:
631
+ The current ``Path`` instance.
632
+ """
633
+ return self._d('a', rx, ry, x_axis_rotation, 1 if large_arc else 0, 1 if sweep else 0, x, y)
634
+
635
+
636
+ def p() -> Path:
637
+ """
638
+ Create a new `h2o_wave.graphics.Path`.
639
+
640
+ Returns:
641
+ A new `h2o_wave.graphics.Path`.
642
+ """
643
+ return Path()
644
+
645
+
646
+ class _Vec(object):
647
+ __slots__ = ('x', 'y')
648
+
649
+ def __init__(self, x: Union[int, float], y: Union[int, float]):
650
+ self.x = float(x)
651
+ self.y = float(y)
652
+
653
+ def __neg__(self) -> '_Vec': return _Vec(-self.x, -self.y)
654
+
655
+ def __add__(self, v: '_Vec') -> '_Vec': return _Vec(self.x + v.x, self.y + v.y)
656
+
657
+ def __sub__(self, v: '_Vec') -> '_Vec': return _Vec(self.x - v.x, self.y - v.y)
658
+
659
+ def __mul__(self, v: Union['_Vec', int, float]) -> Union['_Vec', float]:
660
+ if isinstance(v, _Vec):
661
+ return self.x * v.x + self.y * v.y # dot product
662
+ return _Vec(self.x * v, self.y * v)
663
+
664
+ def __rmul__(self, v: Union['_Vec', int, float]) -> Union['_Vec', float]: return self.__mul__(v)
665
+
666
+ def __div__(self, d: Union[int, float]): return _Vec(self.x / d, self.y / d)
667
+
668
+ def __abs__(self) -> float: return (self.x ** 2 + self.y ** 2) ** 0.5
669
+
670
+ def rotate(self, a: Union[int, float]):
671
+ p = _Vec(-self.y, self.x) # perpendicular
672
+ c = math.cos(a)
673
+ s = math.sin(a)
674
+ return _Vec(self.x * c + p.x * s, self.y * c + p.y * s)
675
+
676
+
677
+ class Turtle:
678
+ """
679
+ A Logo-like Turtle implementation for generating SVG paths.
680
+ This is not a complete Turtle implementation. Contains a useful subset relevant to generating paths without
681
+ using trigonometry or mental gymnastics.
682
+ """
683
+
684
+ def __init__(self, x=0.0, y=0.0, degrees=0.0):
685
+ """
686
+ Create a Turtle.
687
+
688
+ Args:
689
+ x: initial position x
690
+ y: initial position y
691
+ degrees: initial angle in degrees
692
+ """
693
+ self._p = _Vec(x, y) # position vector
694
+ a = math.radians(degrees)
695
+ self._a = _Vec(math.cos(a), math.sin(a)) # orientation vector
696
+ self._pd = False # pen down?
697
+ self._path = Path()
698
+
699
+ def _draw(self) -> 'Turtle':
700
+ if self._pd:
701
+ self._path.L(self._p.x, self._p.y)
702
+ else:
703
+ self._path.M(self._p.x, self._p.y)
704
+ return self
705
+
706
+ def _move(self, d: float) -> 'Turtle':
707
+ self._p = self._p + self._a * d
708
+ return self._draw()
709
+
710
+ def _rotate(self, a: float) -> 'Turtle':
711
+ self._a = self._a.rotate(math.radians(a))
712
+ return self
713
+
714
+ def f(self, distance: float) -> 'Turtle':
715
+ """
716
+ Move forward.
717
+
718
+ Args:
719
+ distance: Distance to move by.
720
+ Returns:
721
+ The current turtle instance.
722
+ """
723
+ return self._move(distance)
724
+
725
+ def b(self, distance: float) -> 'Turtle':
726
+ """
727
+ Move backward.
728
+
729
+ Args:
730
+ distance: Distance to move by.
731
+ Returns:
732
+ The current turtle instance.
733
+ """
734
+ return self._move(-distance)
735
+
736
+ def l(self, degrees: float) -> 'Turtle':
737
+ """
738
+ Turn left.
739
+
740
+ Args:
741
+ degrees: Angle in degrees.
742
+ Returns:
743
+ The current turtle instance.
744
+ """
745
+ return self._rotate(-degrees)
746
+
747
+ def r(self, degrees: float) -> 'Turtle':
748
+ """
749
+ Turn right.
750
+
751
+ Args:
752
+ degrees: Angle in degrees.
753
+ Returns:
754
+ The current turtle instance.
755
+ """
756
+ return self._rotate(degrees)
757
+
758
+ def pu(self, close: bool) -> 'Turtle':
759
+ """
760
+ Pen up.
761
+
762
+ Args:
763
+ close: Whether to close the current subpath.
764
+ Returns:
765
+ The current turtle instance.
766
+ """
767
+ if close:
768
+ self._path.Z()
769
+
770
+ self._pd = False
771
+ return self
772
+
773
+ def pd(self) -> 'Turtle':
774
+ """
775
+ Pen down.
776
+
777
+ Returns:
778
+ The current turtle instance.
779
+ """
780
+ self._pd = True
781
+ return self
782
+
783
+ def p(self, x: float = 0.0, y: float = 0.0) -> 'Turtle':
784
+ """
785
+ Set the turtle's position.
786
+
787
+ Args:
788
+ x: x-coordinate
789
+ y: y-coordinate
790
+ Returns:
791
+ The current turtle instance.
792
+ """
793
+ self._p = _Vec(x, y)
794
+ return self._draw()
795
+
796
+ def a(self, degrees: float = 0) -> 'Turtle':
797
+ """
798
+ Set the turtle's orientation.
799
+
800
+ Args:
801
+ degrees: angle in degrees
802
+ Returns:
803
+ The current turtle instance.
804
+ """
805
+ a = math.radians(degrees)
806
+ self._a = _Vec(math.cos(a), math.sin(a))
807
+ return self
808
+
809
+ def d(self) -> str:
810
+ """
811
+ Serialize this turtle's movements into SVG path data.
812
+
813
+ Returns:
814
+ The ``d`` attribute for a SVG path.
815
+ """
816
+ return self._path.d()
817
+
818
+ def path(self, **kwargs) -> Expando:
819
+ """
820
+ Create a SVG path element that represents this turtle's movements.
821
+
822
+ Args:
823
+ kwargs: Additional attributes for the SVG path element.
824
+ Returns:
825
+ A SVG path element.
826
+ """
827
+ return self._path.path(**kwargs)
828
+
829
+
830
+ def turtle(x=0.0, y=0.0, degrees=0.0) -> Turtle:
831
+ """
832
+ Create a new `h2o_wave.graphics.Turtle`.
833
+
834
+ Args:
835
+ x: initial position x
836
+ y: initial position y
837
+ degrees: initial angle in degrees
838
+ Returns:
839
+ A new `h2o_wave.graphics.Turtle`.
840
+ """
841
+ return Turtle(x, y, degrees)