rgrid-python 4.5.3__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.
- grid_py/__init__.py +340 -0
- grid_py/_arrow.py +331 -0
- grid_py/_clippath.py +170 -0
- grid_py/_colour.py +815 -0
- grid_py/_coords.py +1534 -0
- grid_py/_curve.py +1668 -0
- grid_py/_display_list.py +507 -0
- grid_py/_draw.py +1397 -0
- grid_py/_edit.py +756 -0
- grid_py/_font_metrics.py +319 -0
- grid_py/_gpar.py +572 -0
- grid_py/_grab.py +501 -0
- grid_py/_grob.py +1377 -0
- grid_py/_group.py +798 -0
- grid_py/_highlevel.py +2176 -0
- grid_py/_just.py +361 -0
- grid_py/_layout.py +593 -0
- grid_py/_ls.py +895 -0
- grid_py/_mask.py +196 -0
- grid_py/_path.py +414 -0
- grid_py/_patterns.py +1049 -0
- grid_py/_primitives.py +2198 -0
- grid_py/_renderer_base.py +1184 -0
- grid_py/_scene_graph.py +248 -0
- grid_py/_size.py +1352 -0
- grid_py/_state.py +683 -0
- grid_py/_transforms.py +448 -0
- grid_py/_typeset.py +384 -0
- grid_py/_units.py +1924 -0
- grid_py/_utils.py +310 -0
- grid_py/_viewport.py +1649 -0
- grid_py/_vp_calc.py +970 -0
- grid_py/py.typed +0 -0
- grid_py/renderer.py +1762 -0
- grid_py/renderer_web.py +764 -0
- grid_py/resources/d3.v7.min.js +2 -0
- grid_py/resources/gridpy.css +80 -0
- grid_py/resources/gridpy.js +813 -0
- rgrid_python-4.5.3.dist-info/METADATA +489 -0
- rgrid_python-4.5.3.dist-info/RECORD +42 -0
- rgrid_python-4.5.3.dist-info/WHEEL +4 -0
- rgrid_python-4.5.3.dist-info/licenses/LICENSE +3 -0
grid_py/__init__.py
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
"""
|
|
2
|
+
grid_py — Python port of the R grid graphics package.
|
|
3
|
+
|
|
4
|
+
Provides a complete reimplementation of R's grid graphics system including
|
|
5
|
+
units, viewports, grobs (graphical objects), layouts, and rendering via
|
|
6
|
+
Cairo (pycairo).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
__version__ = "4.5.3"
|
|
10
|
+
|
|
11
|
+
# --- Utilities ---
|
|
12
|
+
from grid_py._utils import depth, explode, grid_pretty, n2mfrow
|
|
13
|
+
|
|
14
|
+
# --- Justification ---
|
|
15
|
+
from grid_py._just import valid_just, resolve_hjust, resolve_vjust, resolve_raster_size
|
|
16
|
+
|
|
17
|
+
# --- Units ---
|
|
18
|
+
from grid_py._units import (
|
|
19
|
+
Unit, is_unit, unit_type, unit_c, unit_length,
|
|
20
|
+
unit_pmax, unit_pmin, unit_psum, unit_rep,
|
|
21
|
+
string_width, string_height, string_ascent, string_descent,
|
|
22
|
+
absolute_size,
|
|
23
|
+
convert_unit, convert_x, convert_y, convert_width, convert_height,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# --- Graphical Parameters ---
|
|
27
|
+
from grid_py._gpar import Gpar, get_gpar
|
|
28
|
+
|
|
29
|
+
# --- Arrow ---
|
|
30
|
+
from grid_py._arrow import Arrow, arrow
|
|
31
|
+
|
|
32
|
+
# --- Paths ---
|
|
33
|
+
from grid_py._path import GPath, VpPath, GridPath, as_path, is_closed, PATH_SEP
|
|
34
|
+
|
|
35
|
+
# --- Layout ---
|
|
36
|
+
from grid_py._layout import (
|
|
37
|
+
GridLayout, layout_region,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# --- Patterns, Masks, Clip Paths ---
|
|
41
|
+
from grid_py._patterns import (
|
|
42
|
+
LinearGradient, RadialGradient, Pattern,
|
|
43
|
+
linear_gradient, radial_gradient, pattern,
|
|
44
|
+
)
|
|
45
|
+
from grid_py._mask import GridMask, as_mask, is_mask
|
|
46
|
+
from grid_py._clippath import GridClipPath, as_clip_path, is_clip_path
|
|
47
|
+
|
|
48
|
+
# --- Viewport ---
|
|
49
|
+
from grid_py._viewport import (
|
|
50
|
+
Viewport, VpList, VpStack, VpTree,
|
|
51
|
+
push_viewport, pop_viewport, down_viewport, up_viewport, seek_viewport,
|
|
52
|
+
current_viewport, current_vp_path, current_vp_tree,
|
|
53
|
+
current_transform, current_rotation, current_parent,
|
|
54
|
+
data_viewport, plot_viewport, edit_viewport, show_viewport,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# --- State ---
|
|
58
|
+
from grid_py._state import GridState, get_state
|
|
59
|
+
|
|
60
|
+
# --- Display List ---
|
|
61
|
+
from grid_py._display_list import DisplayList
|
|
62
|
+
|
|
63
|
+
# --- Transforms ---
|
|
64
|
+
from grid_py._transforms import (
|
|
65
|
+
group_translate, group_rotate, group_scale, group_shear, group_flip,
|
|
66
|
+
defn_translate, defn_rotate, defn_scale,
|
|
67
|
+
use_translate, use_rotate, use_scale,
|
|
68
|
+
viewport_translate, viewport_rotate, viewport_scale, viewport_transform,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# --- Grob ---
|
|
72
|
+
from grid_py._grob import (
|
|
73
|
+
Grob, GTree, GList, GEdit, GEditList,
|
|
74
|
+
grob_tree, grob_name, is_grob,
|
|
75
|
+
get_grob, set_grob, add_grob, remove_grob, edit_grob,
|
|
76
|
+
force_grob, set_children, reorder_grob,
|
|
77
|
+
apply_edit, apply_edits,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# --- Primitives ---
|
|
81
|
+
from grid_py._primitives import (
|
|
82
|
+
move_to_grob, grid_move_to,
|
|
83
|
+
line_to_grob, grid_line_to,
|
|
84
|
+
lines_grob, grid_lines,
|
|
85
|
+
polyline_grob, grid_polyline,
|
|
86
|
+
segments_grob, grid_segments,
|
|
87
|
+
arrows_grob, grid_arrows,
|
|
88
|
+
points_grob, grid_points,
|
|
89
|
+
rect_grob, grid_rect,
|
|
90
|
+
roundrect_grob, grid_roundrect,
|
|
91
|
+
circle_grob, grid_circle,
|
|
92
|
+
polygon_grob, grid_polygon,
|
|
93
|
+
path_grob, grid_path,
|
|
94
|
+
text_grob, grid_text,
|
|
95
|
+
raster_grob, grid_raster,
|
|
96
|
+
clip_grob, grid_clip,
|
|
97
|
+
null_grob, grid_null,
|
|
98
|
+
function_grob, grid_function,
|
|
99
|
+
as_path,
|
|
100
|
+
stroke_grob, grid_stroke,
|
|
101
|
+
fill_grob, grid_fill,
|
|
102
|
+
fill_stroke_grob, grid_fill_stroke,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# --- Coordinates ---
|
|
106
|
+
from grid_py._coords import (
|
|
107
|
+
GridCoords, GridGrobCoords, GridGTreeCoords,
|
|
108
|
+
grob_coords, grob_points,
|
|
109
|
+
grid_coords, grid_grob_coords, grid_gtree_coords,
|
|
110
|
+
empty_coords, empty_grob_coords, empty_gtree_coords,
|
|
111
|
+
is_empty_coords,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# --- Curves ---
|
|
115
|
+
from grid_py._curve import (
|
|
116
|
+
curve_grob, grid_curve,
|
|
117
|
+
xspline_grob, grid_xspline,
|
|
118
|
+
bezier_grob, grid_bezier,
|
|
119
|
+
xspline_points, bezier_points,
|
|
120
|
+
arc_curvature,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# --- Groups ---
|
|
124
|
+
from grid_py._group import (
|
|
125
|
+
GroupGrob, DefineGrob, UseGrob,
|
|
126
|
+
group_grob, grid_group,
|
|
127
|
+
define_grob, grid_define,
|
|
128
|
+
use_grob, grid_use,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# --- Renderer ---
|
|
132
|
+
from grid_py._renderer_base import GridRenderer
|
|
133
|
+
from grid_py.renderer import CairoRenderer
|
|
134
|
+
from grid_py.renderer_web import WebRenderer
|
|
135
|
+
|
|
136
|
+
# --- Drawing ---
|
|
137
|
+
from grid_py._draw import (
|
|
138
|
+
grid_draw, grid_newpage, grid_refresh,
|
|
139
|
+
grid_record, record_grob,
|
|
140
|
+
grid_delay, delay_grob,
|
|
141
|
+
grid_dl_apply, grid_locator,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# --- Edit (display list) ---
|
|
145
|
+
from grid_py._edit import (
|
|
146
|
+
grid_edit, grid_get, grid_set, grid_add, grid_remove,
|
|
147
|
+
grid_gedit, grid_gget, grid_gremove,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# --- Listing & Search ---
|
|
151
|
+
from grid_py._ls import (
|
|
152
|
+
grid_ls, grid_grep,
|
|
153
|
+
nested_listing, path_listing, grob_path_listing,
|
|
154
|
+
show_grob, get_names, child_names,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# --- Grab ---
|
|
158
|
+
from grid_py._grab import (
|
|
159
|
+
grid_grab, grid_grab_expr,
|
|
160
|
+
grid_force, grid_revert,
|
|
161
|
+
grid_cap, grid_reorder,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# --- High-level ---
|
|
165
|
+
from grid_py._highlevel import (
|
|
166
|
+
grid_grill, grid_show_layout, grid_show_viewport,
|
|
167
|
+
grid_abline, grid_plot_and_legend, layout_torture,
|
|
168
|
+
frame_grob, grid_frame, pack_grob, grid_pack, place_grob, grid_place,
|
|
169
|
+
xaxis_grob, grid_xaxis, yaxis_grob, grid_yaxis,
|
|
170
|
+
legend_grob, grid_legend,
|
|
171
|
+
grid_multipanel, grid_panel, grid_strip,
|
|
172
|
+
grid_top_level_vp,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# --- Size / Metrics ---
|
|
176
|
+
from grid_py._size import (
|
|
177
|
+
calc_string_metric,
|
|
178
|
+
width_details, height_details, ascent_details, descent_details,
|
|
179
|
+
grob_width, grob_height, grob_x, grob_y,
|
|
180
|
+
grob_ascent, grob_descent,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
# --- Typeset ---
|
|
184
|
+
from grid_py._typeset import glyph_grob, grid_glyph
|
|
185
|
+
|
|
186
|
+
# --- Deprecated aliases (R compatibility) ---
|
|
187
|
+
convert_native = convert_unit
|
|
188
|
+
grid_convert = convert_unit
|
|
189
|
+
grid_convert_x = convert_x
|
|
190
|
+
grid_convert_y = convert_y
|
|
191
|
+
grid_convert_width = convert_width
|
|
192
|
+
grid_convert_height = convert_height
|
|
193
|
+
from grid_py._units import device_loc, device_dim
|
|
194
|
+
|
|
195
|
+
# R names grid.collection and grid.copy were undocumented stubs
|
|
196
|
+
grid_collection = grid_draw
|
|
197
|
+
grid_copy = grid_draw
|
|
198
|
+
|
|
199
|
+
# grid.display.list / engine.display.list
|
|
200
|
+
def grid_display_list(on: bool = True) -> bool:
|
|
201
|
+
"""Enable or disable the display list.
|
|
202
|
+
|
|
203
|
+
Parameters
|
|
204
|
+
----------
|
|
205
|
+
on : bool
|
|
206
|
+
Whether to enable recording.
|
|
207
|
+
|
|
208
|
+
Returns
|
|
209
|
+
-------
|
|
210
|
+
bool
|
|
211
|
+
Previous state.
|
|
212
|
+
"""
|
|
213
|
+
state = get_state()
|
|
214
|
+
prev = state._dl_on
|
|
215
|
+
state._dl_on = on
|
|
216
|
+
return prev
|
|
217
|
+
|
|
218
|
+
engine_display_list = grid_display_list
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
__all__ = [
|
|
222
|
+
# Utils
|
|
223
|
+
"depth", "explode", "grid_pretty", "n2mfrow",
|
|
224
|
+
# Just
|
|
225
|
+
"valid_just", "resolve_hjust", "resolve_vjust", "resolve_raster_size",
|
|
226
|
+
# Units
|
|
227
|
+
"Unit", "is_unit", "unit_type", "unit_c", "unit_length",
|
|
228
|
+
"unit_pmax", "unit_pmin", "unit_psum", "unit_rep",
|
|
229
|
+
"string_width", "string_height", "string_ascent", "string_descent",
|
|
230
|
+
"absolute_size",
|
|
231
|
+
"convert_unit", "convert_x", "convert_y", "convert_width", "convert_height",
|
|
232
|
+
# Gpar
|
|
233
|
+
"Gpar", "get_gpar",
|
|
234
|
+
# Arrow
|
|
235
|
+
"Arrow", "arrow",
|
|
236
|
+
# Path
|
|
237
|
+
"GPath", "VpPath", "GridPath", "as_path", "is_closed", "PATH_SEP",
|
|
238
|
+
# Layout
|
|
239
|
+
"GridLayout", "layout_region",
|
|
240
|
+
# Patterns
|
|
241
|
+
"LinearGradient", "RadialGradient", "Pattern",
|
|
242
|
+
"linear_gradient", "radial_gradient", "pattern",
|
|
243
|
+
# Mask
|
|
244
|
+
"GridMask", "as_mask", "is_mask",
|
|
245
|
+
# Clip path
|
|
246
|
+
"GridClipPath", "as_clip_path", "is_clip_path",
|
|
247
|
+
# Viewport
|
|
248
|
+
"Viewport", "VpList", "VpStack", "VpTree",
|
|
249
|
+
"push_viewport", "pop_viewport", "down_viewport", "up_viewport", "seek_viewport",
|
|
250
|
+
"current_viewport", "current_vp_path", "current_vp_tree",
|
|
251
|
+
"current_transform", "current_rotation", "current_parent",
|
|
252
|
+
"data_viewport", "plot_viewport", "edit_viewport", "show_viewport",
|
|
253
|
+
# State
|
|
254
|
+
"GridState", "get_state",
|
|
255
|
+
# Display list
|
|
256
|
+
"DisplayList",
|
|
257
|
+
# Transforms
|
|
258
|
+
"group_translate", "group_rotate", "group_scale", "group_shear", "group_flip",
|
|
259
|
+
"defn_translate", "defn_rotate", "defn_scale",
|
|
260
|
+
"use_translate", "use_rotate", "use_scale",
|
|
261
|
+
"viewport_translate", "viewport_rotate", "viewport_scale", "viewport_transform",
|
|
262
|
+
# Grob
|
|
263
|
+
"Grob", "GTree", "GList", "GEdit", "GEditList",
|
|
264
|
+
"grob_tree", "grob_name", "is_grob",
|
|
265
|
+
"get_grob", "set_grob", "add_grob", "remove_grob", "edit_grob",
|
|
266
|
+
"force_grob", "set_children", "reorder_grob",
|
|
267
|
+
"apply_edit", "apply_edits",
|
|
268
|
+
# Primitives
|
|
269
|
+
"move_to_grob", "grid_move_to",
|
|
270
|
+
"line_to_grob", "grid_line_to",
|
|
271
|
+
"lines_grob", "grid_lines",
|
|
272
|
+
"polyline_grob", "grid_polyline",
|
|
273
|
+
"segments_grob", "grid_segments",
|
|
274
|
+
"arrows_grob", "grid_arrows",
|
|
275
|
+
"points_grob", "grid_points",
|
|
276
|
+
"rect_grob", "grid_rect",
|
|
277
|
+
"roundrect_grob", "grid_roundrect",
|
|
278
|
+
"circle_grob", "grid_circle",
|
|
279
|
+
"polygon_grob", "grid_polygon",
|
|
280
|
+
"path_grob", "grid_path",
|
|
281
|
+
"text_grob", "grid_text",
|
|
282
|
+
"raster_grob", "grid_raster",
|
|
283
|
+
"clip_grob", "grid_clip",
|
|
284
|
+
"null_grob", "grid_null",
|
|
285
|
+
"function_grob", "grid_function",
|
|
286
|
+
# Coords
|
|
287
|
+
"GridCoords", "GridGrobCoords", "GridGTreeCoords",
|
|
288
|
+
"grob_coords", "grob_points",
|
|
289
|
+
"grid_coords", "grid_grob_coords", "grid_gtree_coords",
|
|
290
|
+
"empty_coords", "empty_grob_coords", "empty_gtree_coords",
|
|
291
|
+
"is_empty_coords",
|
|
292
|
+
# Curves
|
|
293
|
+
"curve_grob", "grid_curve",
|
|
294
|
+
"xspline_grob", "grid_xspline",
|
|
295
|
+
"bezier_grob", "grid_bezier",
|
|
296
|
+
"xspline_points", "bezier_points",
|
|
297
|
+
"arc_curvature",
|
|
298
|
+
# Groups
|
|
299
|
+
"GroupGrob", "DefineGrob", "UseGrob",
|
|
300
|
+
"group_grob", "grid_group",
|
|
301
|
+
"define_grob", "grid_define",
|
|
302
|
+
"use_grob", "grid_use",
|
|
303
|
+
# Draw
|
|
304
|
+
"grid_draw", "grid_newpage", "grid_refresh",
|
|
305
|
+
"grid_record", "record_grob",
|
|
306
|
+
"grid_delay", "delay_grob",
|
|
307
|
+
"grid_dl_apply", "grid_locator",
|
|
308
|
+
# Edit
|
|
309
|
+
"grid_edit", "grid_get", "grid_set", "grid_add", "grid_remove",
|
|
310
|
+
"grid_gedit", "grid_gget", "grid_gremove",
|
|
311
|
+
# LS
|
|
312
|
+
"grid_ls", "grid_grep",
|
|
313
|
+
"nested_listing", "path_listing", "grob_path_listing",
|
|
314
|
+
"show_grob", "get_names", "child_names",
|
|
315
|
+
# Grab
|
|
316
|
+
"grid_grab", "grid_grab_expr",
|
|
317
|
+
"grid_force", "grid_revert",
|
|
318
|
+
"grid_cap", "grid_reorder",
|
|
319
|
+
# High-level
|
|
320
|
+
"grid_grill", "grid_show_layout", "grid_show_viewport",
|
|
321
|
+
"grid_abline", "grid_plot_and_legend", "layout_torture",
|
|
322
|
+
"frame_grob", "grid_frame", "pack_grob", "grid_pack", "place_grob", "grid_place",
|
|
323
|
+
"xaxis_grob", "grid_xaxis", "yaxis_grob", "grid_yaxis",
|
|
324
|
+
"legend_grob", "grid_legend",
|
|
325
|
+
"grid_multipanel", "grid_panel", "grid_strip",
|
|
326
|
+
"grid_top_level_vp",
|
|
327
|
+
# Size
|
|
328
|
+
"calc_string_metric",
|
|
329
|
+
"grob_width", "grob_height", "grob_x", "grob_y",
|
|
330
|
+
"grob_ascent", "grob_descent",
|
|
331
|
+
# Typeset
|
|
332
|
+
"glyph_grob", "grid_glyph",
|
|
333
|
+
# Deprecated
|
|
334
|
+
"convert_native", "grid_convert",
|
|
335
|
+
"grid_convert_x", "grid_convert_y",
|
|
336
|
+
"grid_convert_width", "grid_convert_height",
|
|
337
|
+
"device_loc", "device_dim",
|
|
338
|
+
"grid_collection", "grid_copy",
|
|
339
|
+
"grid_display_list", "engine_display_list",
|
|
340
|
+
]
|
grid_py/_arrow.py
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Arrow head specification for grid_py -- Python port of R's ``grid::arrow()``.
|
|
3
|
+
|
|
4
|
+
This module provides the :class:`Arrow` class and the :func:`arrow` factory
|
|
5
|
+
function, which describe the arrow heads that can be attached to line-based
|
|
6
|
+
grobs (segments, lines, curves, etc.).
|
|
7
|
+
|
|
8
|
+
The implementation mirrors the behaviour of R's ``arrow()`` constructor,
|
|
9
|
+
``length.arrow``, ``rep.arrow``, and ``[.arrow`` method defined in
|
|
10
|
+
``src/library/grid/R/primitives.R``.
|
|
11
|
+
|
|
12
|
+
Examples
|
|
13
|
+
--------
|
|
14
|
+
>>> from grid_py._arrow import arrow
|
|
15
|
+
>>> a = arrow()
|
|
16
|
+
>>> a
|
|
17
|
+
Arrow(angle=[30.0], length=Unit([0.25], 'inches'), ends=[2], type=[1])
|
|
18
|
+
>>> len(a)
|
|
19
|
+
1
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from typing import List, Optional, Sequence, Union
|
|
25
|
+
|
|
26
|
+
import numpy as np
|
|
27
|
+
|
|
28
|
+
from ._units import Unit, unit_rep
|
|
29
|
+
|
|
30
|
+
__all__ = ["Arrow", "arrow"]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _recycle_unit(u: Unit, n: int) -> Unit:
|
|
34
|
+
"""Recycle a Unit to length *n* using indexing with modular indices."""
|
|
35
|
+
lu = len(u)
|
|
36
|
+
if lu == n:
|
|
37
|
+
return u
|
|
38
|
+
indices = [i % lu for i in range(n)]
|
|
39
|
+
return u[indices]
|
|
40
|
+
|
|
41
|
+
# Valid string values for *ends* and *type*, following R's match() semantics.
|
|
42
|
+
_VALID_ENDS = ("first", "last", "both")
|
|
43
|
+
_VALID_TYPES = ("open", "closed")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class Arrow:
|
|
47
|
+
"""Description of an arrow head to attach to a line-based grob.
|
|
48
|
+
|
|
49
|
+
Parameters
|
|
50
|
+
----------
|
|
51
|
+
angle : float or Sequence[float]
|
|
52
|
+
Angle of the arrow head in degrees (the angle between the shaft and
|
|
53
|
+
each edge of the arrow head). Scalar or vector.
|
|
54
|
+
length : Unit, optional
|
|
55
|
+
Length of the arrow head measured along the edges. Must be a
|
|
56
|
+
:class:`Unit` object. Defaults to ``Unit(0.25, "inches")``.
|
|
57
|
+
ends : {"first", "last", "both"} or Sequence[str]
|
|
58
|
+
Which end(s) of the line should receive an arrow head. Encoded
|
|
59
|
+
internally as integers: ``1`` = first, ``2`` = last, ``3`` = both,
|
|
60
|
+
matching R's ``match()`` convention.
|
|
61
|
+
type : {"open", "closed"} or Sequence[str]
|
|
62
|
+
Whether the arrow head is open or closed. Encoded internally as
|
|
63
|
+
integers: ``1`` = open, ``2`` = closed.
|
|
64
|
+
|
|
65
|
+
Raises
|
|
66
|
+
------
|
|
67
|
+
TypeError
|
|
68
|
+
If *length* is not a :class:`Unit` object.
|
|
69
|
+
ValueError
|
|
70
|
+
If *ends* or *type* contains invalid values.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
# ------------------------------------------------------------------
|
|
74
|
+
# Construction
|
|
75
|
+
# ------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
def __init__(
|
|
78
|
+
self,
|
|
79
|
+
angle: Union[float, int, Sequence[float]] = 30,
|
|
80
|
+
length: Optional[Unit] = None,
|
|
81
|
+
ends: Union[str, Sequence[str]] = "last",
|
|
82
|
+
type: Union[str, Sequence[str]] = "open", # noqa: A002
|
|
83
|
+
) -> None:
|
|
84
|
+
# --- angle --------------------------------------------------------
|
|
85
|
+
if isinstance(angle, (int, float, np.integer, np.floating)):
|
|
86
|
+
self._angle: np.ndarray = np.asarray([float(angle)], dtype=np.float64)
|
|
87
|
+
else:
|
|
88
|
+
self._angle = np.asarray(angle, dtype=np.float64).ravel()
|
|
89
|
+
|
|
90
|
+
# --- length -------------------------------------------------------
|
|
91
|
+
if length is None:
|
|
92
|
+
length = Unit(0.25, "inches")
|
|
93
|
+
if not isinstance(length, Unit):
|
|
94
|
+
raise TypeError("'length' must be a Unit object")
|
|
95
|
+
self._length: Unit = length
|
|
96
|
+
|
|
97
|
+
# --- ends ---------------------------------------------------------
|
|
98
|
+
self._ends: np.ndarray = self._encode_match(ends, _VALID_ENDS, "ends")
|
|
99
|
+
|
|
100
|
+
# --- type ---------------------------------------------------------
|
|
101
|
+
self._type: np.ndarray = self._encode_match(type, _VALID_TYPES, "type")
|
|
102
|
+
|
|
103
|
+
# ------------------------------------------------------------------
|
|
104
|
+
# Helpers
|
|
105
|
+
# ------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
@staticmethod
|
|
108
|
+
def _encode_match(
|
|
109
|
+
value: Union[str, Sequence[str]],
|
|
110
|
+
valid: tuple,
|
|
111
|
+
label: str,
|
|
112
|
+
) -> np.ndarray:
|
|
113
|
+
"""Encode string(s) to 1-based integer codes, like R's ``match()``.
|
|
114
|
+
|
|
115
|
+
Parameters
|
|
116
|
+
----------
|
|
117
|
+
value : str or Sequence[str]
|
|
118
|
+
Input value(s).
|
|
119
|
+
valid : tuple of str
|
|
120
|
+
Allowed string values.
|
|
121
|
+
label : str
|
|
122
|
+
Name used in error messages.
|
|
123
|
+
|
|
124
|
+
Returns
|
|
125
|
+
-------
|
|
126
|
+
np.ndarray
|
|
127
|
+
1-based integer codes corresponding to *value*.
|
|
128
|
+
|
|
129
|
+
Raises
|
|
130
|
+
------
|
|
131
|
+
ValueError
|
|
132
|
+
If any element of *value* is not in *valid*.
|
|
133
|
+
"""
|
|
134
|
+
if isinstance(value, str):
|
|
135
|
+
value = [value]
|
|
136
|
+
codes: List[int] = []
|
|
137
|
+
for v in value:
|
|
138
|
+
if v not in valid:
|
|
139
|
+
raise ValueError(
|
|
140
|
+
f"invalid '{label}' argument: {v!r}; "
|
|
141
|
+
f"must be one of {valid}"
|
|
142
|
+
)
|
|
143
|
+
codes.append(valid.index(v) + 1)
|
|
144
|
+
arr = np.asarray(codes, dtype=np.int64)
|
|
145
|
+
if arr.size == 0:
|
|
146
|
+
raise ValueError(f"'{label}' must have length > 0")
|
|
147
|
+
return arr
|
|
148
|
+
|
|
149
|
+
# ------------------------------------------------------------------
|
|
150
|
+
# Properties (read-only access to internal data)
|
|
151
|
+
# ------------------------------------------------------------------
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def angle(self) -> np.ndarray:
|
|
155
|
+
"""Arrow-head angle(s) in degrees."""
|
|
156
|
+
return self._angle
|
|
157
|
+
|
|
158
|
+
@property
|
|
159
|
+
def length(self) -> Unit:
|
|
160
|
+
"""Arrow-head length as a :class:`Unit`."""
|
|
161
|
+
return self._length
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def ends(self) -> np.ndarray:
|
|
165
|
+
"""Integer code(s) for which end gets an arrow (1=first, 2=last, 3=both)."""
|
|
166
|
+
return self._ends
|
|
167
|
+
|
|
168
|
+
@property
|
|
169
|
+
def type(self) -> np.ndarray:
|
|
170
|
+
"""Integer code(s) for arrow type (1=open, 2=closed)."""
|
|
171
|
+
return self._type
|
|
172
|
+
|
|
173
|
+
# ------------------------------------------------------------------
|
|
174
|
+
# length (len) -- mirrors R's length.arrow
|
|
175
|
+
# ------------------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
def __len__(self) -> int:
|
|
178
|
+
"""Return the effective vector length of this Arrow.
|
|
179
|
+
|
|
180
|
+
Follows R's ``length.arrow`` which returns the maximum of the
|
|
181
|
+
lengths of all component vectors (angle, length, ends, type).
|
|
182
|
+
|
|
183
|
+
Returns
|
|
184
|
+
-------
|
|
185
|
+
int
|
|
186
|
+
Effective length.
|
|
187
|
+
"""
|
|
188
|
+
return int(
|
|
189
|
+
max(
|
|
190
|
+
len(self._angle),
|
|
191
|
+
len(self._length),
|
|
192
|
+
len(self._ends),
|
|
193
|
+
len(self._type),
|
|
194
|
+
)
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
# ------------------------------------------------------------------
|
|
198
|
+
# Subscript -- mirrors R's `[.arrow`
|
|
199
|
+
# ------------------------------------------------------------------
|
|
200
|
+
|
|
201
|
+
def __getitem__(self, index: Union[int, slice, Sequence[int]]) -> "Arrow":
|
|
202
|
+
"""Subset an Arrow, recycling components to a common length first.
|
|
203
|
+
|
|
204
|
+
Parameters
|
|
205
|
+
----------
|
|
206
|
+
index : int, slice, or Sequence[int]
|
|
207
|
+
Index(es) to select.
|
|
208
|
+
|
|
209
|
+
Returns
|
|
210
|
+
-------
|
|
211
|
+
Arrow
|
|
212
|
+
A new :class:`Arrow` with the selected elements.
|
|
213
|
+
"""
|
|
214
|
+
maxn = len(self)
|
|
215
|
+
|
|
216
|
+
# Recycle each component to *maxn*.
|
|
217
|
+
angle = np.resize(self._angle, maxn)
|
|
218
|
+
length = _recycle_unit(self._length, maxn)
|
|
219
|
+
ends = np.resize(self._ends, maxn)
|
|
220
|
+
type_ = np.resize(self._type, maxn)
|
|
221
|
+
|
|
222
|
+
# Apply the index.
|
|
223
|
+
new = object.__new__(Arrow)
|
|
224
|
+
new._angle = np.atleast_1d(angle[index])
|
|
225
|
+
new._length = length[index]
|
|
226
|
+
new._ends = np.atleast_1d(ends[index])
|
|
227
|
+
new._type = np.atleast_1d(type_[index])
|
|
228
|
+
return new
|
|
229
|
+
|
|
230
|
+
# ------------------------------------------------------------------
|
|
231
|
+
# rep -- mirrors R's rep.arrow
|
|
232
|
+
# ------------------------------------------------------------------
|
|
233
|
+
|
|
234
|
+
def rep(self, times: int = 1, length_out: Optional[int] = None) -> "Arrow":
|
|
235
|
+
"""Repeat the Arrow, recycling components to a common length first.
|
|
236
|
+
|
|
237
|
+
Parameters
|
|
238
|
+
----------
|
|
239
|
+
times : int, optional
|
|
240
|
+
Number of times to repeat (default ``1``).
|
|
241
|
+
length_out : int, optional
|
|
242
|
+
Desired length of the result. If given, *times* is ignored and
|
|
243
|
+
the output is truncated or recycled to this length.
|
|
244
|
+
|
|
245
|
+
Returns
|
|
246
|
+
-------
|
|
247
|
+
Arrow
|
|
248
|
+
A new :class:`Arrow` with repeated elements.
|
|
249
|
+
"""
|
|
250
|
+
maxn = len(self)
|
|
251
|
+
|
|
252
|
+
# First recycle components to the common length.
|
|
253
|
+
angle = np.resize(self._angle, maxn)
|
|
254
|
+
ends = np.resize(self._ends, maxn)
|
|
255
|
+
type_ = np.resize(self._type, maxn)
|
|
256
|
+
length = _recycle_unit(self._length, maxn)
|
|
257
|
+
|
|
258
|
+
# Then tile by *times*.
|
|
259
|
+
angle = np.tile(angle, times)
|
|
260
|
+
ends = np.tile(ends, times)
|
|
261
|
+
type_ = np.tile(type_, times)
|
|
262
|
+
length = unit_rep(length, times)
|
|
263
|
+
|
|
264
|
+
# Trim / recycle to *length_out* if requested.
|
|
265
|
+
if length_out is not None:
|
|
266
|
+
angle = np.resize(angle, length_out)
|
|
267
|
+
ends = np.resize(ends, length_out)
|
|
268
|
+
type_ = np.resize(type_, length_out)
|
|
269
|
+
length = _recycle_unit(length, length_out)
|
|
270
|
+
|
|
271
|
+
new = object.__new__(Arrow)
|
|
272
|
+
new._angle = angle
|
|
273
|
+
new._length = length
|
|
274
|
+
new._ends = ends
|
|
275
|
+
new._type = type_
|
|
276
|
+
return new
|
|
277
|
+
|
|
278
|
+
# ------------------------------------------------------------------
|
|
279
|
+
# repr
|
|
280
|
+
# ------------------------------------------------------------------
|
|
281
|
+
|
|
282
|
+
def __repr__(self) -> str:
|
|
283
|
+
return (
|
|
284
|
+
f"Arrow(angle={self._angle.tolist()}, "
|
|
285
|
+
f"length={self._length!r}, "
|
|
286
|
+
f"ends={self._ends.tolist()}, "
|
|
287
|
+
f"type={self._type.tolist()})"
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
# ----------------------------------------------------------------------
|
|
292
|
+
# Factory function
|
|
293
|
+
# ----------------------------------------------------------------------
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def arrow(
|
|
297
|
+
angle: Union[float, int, Sequence[float]] = 30,
|
|
298
|
+
length: Optional[Unit] = None,
|
|
299
|
+
ends: Union[str, Sequence[str]] = "last",
|
|
300
|
+
type: Union[str, Sequence[str]] = "open", # noqa: A002
|
|
301
|
+
) -> Arrow:
|
|
302
|
+
"""Create an Arrow specification.
|
|
303
|
+
|
|
304
|
+
This is the main user-facing factory function, equivalent to R's
|
|
305
|
+
``grid::arrow()``.
|
|
306
|
+
|
|
307
|
+
Parameters
|
|
308
|
+
----------
|
|
309
|
+
angle : float or Sequence[float], optional
|
|
310
|
+
Angle of the arrow head in degrees (default ``30``).
|
|
311
|
+
length : Unit, optional
|
|
312
|
+
Length of the arrow head edges. Defaults to
|
|
313
|
+
``Unit(0.25, "inches")``.
|
|
314
|
+
ends : {"first", "last", "both"} or Sequence[str], optional
|
|
315
|
+
Which end(s) of the line receive an arrow head (default ``"last"``).
|
|
316
|
+
type : {"open", "closed"} or Sequence[str], optional
|
|
317
|
+
Arrow head style (default ``"open"``).
|
|
318
|
+
|
|
319
|
+
Returns
|
|
320
|
+
-------
|
|
321
|
+
Arrow
|
|
322
|
+
A new :class:`Arrow` instance.
|
|
323
|
+
|
|
324
|
+
Examples
|
|
325
|
+
--------
|
|
326
|
+
>>> from grid_py._arrow import arrow
|
|
327
|
+
>>> a = arrow(angle=45, type="closed")
|
|
328
|
+
>>> a
|
|
329
|
+
Arrow(angle=[45.0], length=Unit([0.25], 'inches'), ends=[2], type=[2])
|
|
330
|
+
"""
|
|
331
|
+
return Arrow(angle=angle, length=length, ends=ends, type=type)
|