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.
- h2o_lightwave/__init__.py +30 -0
- h2o_lightwave/core.py +508 -0
- h2o_lightwave/graphics.py +841 -0
- h2o_lightwave/py.typed +0 -0
- h2o_lightwave/routing.py +249 -0
- h2o_lightwave/server.py +109 -0
- h2o_lightwave/types.py +14098 -0
- h2o_lightwave/ui.py +4978 -0
- h2o_lightwave/ui_ext.py +52 -0
- h2o_lightwave/version.py +1 -0
- h2o_lightwave-1.7.6.dist-info/METADATA +172 -0
- h2o_lightwave-1.7.6.dist-info/RECORD +14 -0
- h2o_lightwave-1.7.6.dist-info/WHEEL +4 -0
- h2o_lightwave-1.7.6.dist-info/licenses/LICENSE +1 -0
|
@@ -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)
|