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/_edit.py
ADDED
|
@@ -0,0 +1,756 @@
|
|
|
1
|
+
"""Display-list edit operations for grid_py (port of R's grid edit/get/set/add/remove).
|
|
2
|
+
|
|
3
|
+
This module provides the ``grid_*`` family of functions that operate on the
|
|
4
|
+
current display list, mirroring the R functions ``grid.edit``, ``grid.get``,
|
|
5
|
+
``grid.set``, ``grid.add``, ``grid.remove``, and their ``grep``-defaulting
|
|
6
|
+
convenience aliases ``grid.gedit``, ``grid.gget``, ``grid.gremove``.
|
|
7
|
+
|
|
8
|
+
These functions locate grobs on the display list by :class:`~._path.GPath`
|
|
9
|
+
and apply modifications (edit attributes, replace, add children, or remove
|
|
10
|
+
grobs) with optional regex matching.
|
|
11
|
+
|
|
12
|
+
References
|
|
13
|
+
----------
|
|
14
|
+
R source: ``src/library/grid/R/edit.R``, ``src/library/grid/R/grob.R``
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import copy
|
|
20
|
+
import re
|
|
21
|
+
import warnings
|
|
22
|
+
from typing import Any, Dict, List, Optional, Union
|
|
23
|
+
|
|
24
|
+
from ._grob import (
|
|
25
|
+
GEdit,
|
|
26
|
+
GEditList,
|
|
27
|
+
GList,
|
|
28
|
+
GTree,
|
|
29
|
+
Grob,
|
|
30
|
+
apply_edit,
|
|
31
|
+
apply_edits,
|
|
32
|
+
edit_grob,
|
|
33
|
+
is_grob,
|
|
34
|
+
)
|
|
35
|
+
from ._path import GPath
|
|
36
|
+
from ._display_list import DisplayList, DLDrawGrob
|
|
37
|
+
from ._state import get_state
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
"grid_edit",
|
|
41
|
+
"grid_get",
|
|
42
|
+
"grid_set",
|
|
43
|
+
"grid_add",
|
|
44
|
+
"grid_remove",
|
|
45
|
+
"grid_gedit",
|
|
46
|
+
"grid_gget",
|
|
47
|
+
"grid_gremove",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# ---------------------------------------------------------------------------
|
|
52
|
+
# Internal helpers
|
|
53
|
+
# ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _ensure_gpath(gpath: Union[str, GPath]) -> GPath:
|
|
57
|
+
"""Convert *gpath* to a :class:`GPath` if it is a string.
|
|
58
|
+
|
|
59
|
+
Parameters
|
|
60
|
+
----------
|
|
61
|
+
gpath : str or GPath
|
|
62
|
+
A grob path specification.
|
|
63
|
+
|
|
64
|
+
Returns
|
|
65
|
+
-------
|
|
66
|
+
GPath
|
|
67
|
+
|
|
68
|
+
Raises
|
|
69
|
+
------
|
|
70
|
+
TypeError
|
|
71
|
+
If *gpath* is neither a string nor a :class:`GPath`.
|
|
72
|
+
"""
|
|
73
|
+
if isinstance(gpath, str):
|
|
74
|
+
return GPath(gpath)
|
|
75
|
+
if isinstance(gpath, GPath):
|
|
76
|
+
return gpath
|
|
77
|
+
raise TypeError(f"invalid gPath: expected str or GPath, got {type(gpath).__name__}")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _name_match(pattern: str, candidates: List[str], grep: bool) -> Optional[int]:
|
|
81
|
+
"""Return the index (0-based) of the first matching candidate, or ``None``.
|
|
82
|
+
|
|
83
|
+
Parameters
|
|
84
|
+
----------
|
|
85
|
+
pattern : str
|
|
86
|
+
The name or regex pattern to match.
|
|
87
|
+
candidates : list[str]
|
|
88
|
+
The candidate names to search.
|
|
89
|
+
grep : bool
|
|
90
|
+
If ``True``, use regex matching; otherwise, exact matching.
|
|
91
|
+
|
|
92
|
+
Returns
|
|
93
|
+
-------
|
|
94
|
+
int or None
|
|
95
|
+
The 0-based index of the first match, or ``None`` if no match.
|
|
96
|
+
"""
|
|
97
|
+
for i, name in enumerate(candidates):
|
|
98
|
+
if grep:
|
|
99
|
+
if re.search(pattern, name):
|
|
100
|
+
return i
|
|
101
|
+
else:
|
|
102
|
+
if pattern == name:
|
|
103
|
+
return i
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _find_dl_grobs(
|
|
108
|
+
dl: DisplayList,
|
|
109
|
+
gpath: GPath,
|
|
110
|
+
strict: bool,
|
|
111
|
+
grep: Union[bool, List[bool]],
|
|
112
|
+
global_: bool,
|
|
113
|
+
) -> List[tuple]:
|
|
114
|
+
"""Find grob(s) on the display list matching *gpath*.
|
|
115
|
+
|
|
116
|
+
Parameters
|
|
117
|
+
----------
|
|
118
|
+
dl : DisplayList
|
|
119
|
+
The display list to search.
|
|
120
|
+
gpath : GPath
|
|
121
|
+
Path identifying the target grob(s).
|
|
122
|
+
strict : bool
|
|
123
|
+
If ``True``, the path must match exactly from the root of each grob.
|
|
124
|
+
grep : bool or list[bool]
|
|
125
|
+
Whether to use regex matching at each path level.
|
|
126
|
+
global_ : bool
|
|
127
|
+
If ``True``, find all matches; otherwise, stop at first match.
|
|
128
|
+
|
|
129
|
+
Returns
|
|
130
|
+
-------
|
|
131
|
+
list[tuple]
|
|
132
|
+
Each tuple is ``(dl_index, grob_reference)`` for a matching grob.
|
|
133
|
+
"""
|
|
134
|
+
if isinstance(grep, bool):
|
|
135
|
+
grep_flags = [grep] * gpath.n
|
|
136
|
+
else:
|
|
137
|
+
grep_flags = list(grep)
|
|
138
|
+
while len(grep_flags) < gpath.n:
|
|
139
|
+
grep_flags.append(grep_flags[-1] if grep_flags else False)
|
|
140
|
+
|
|
141
|
+
matches: list[tuple] = []
|
|
142
|
+
|
|
143
|
+
for idx, item in enumerate(dl):
|
|
144
|
+
if not isinstance(item, DLDrawGrob) or item.grob is None:
|
|
145
|
+
continue
|
|
146
|
+
grob = item.grob
|
|
147
|
+
found = _match_grob_path(grob, gpath, strict, grep_flags)
|
|
148
|
+
if found is not None:
|
|
149
|
+
matches.append((idx, found))
|
|
150
|
+
if not global_:
|
|
151
|
+
break
|
|
152
|
+
|
|
153
|
+
return matches
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _match_grob_path(
|
|
157
|
+
grob: Any,
|
|
158
|
+
gpath: GPath,
|
|
159
|
+
strict: bool,
|
|
160
|
+
grep_flags: List[bool],
|
|
161
|
+
) -> Optional[Any]:
|
|
162
|
+
"""Attempt to match *gpath* against *grob* and its children.
|
|
163
|
+
|
|
164
|
+
Parameters
|
|
165
|
+
----------
|
|
166
|
+
grob : Grob
|
|
167
|
+
The grob to match against.
|
|
168
|
+
gpath : GPath
|
|
169
|
+
The target path.
|
|
170
|
+
strict : bool
|
|
171
|
+
Require exact depth matching.
|
|
172
|
+
grep_flags : list[bool]
|
|
173
|
+
Per-level regex flags.
|
|
174
|
+
|
|
175
|
+
Returns
|
|
176
|
+
-------
|
|
177
|
+
Grob or None
|
|
178
|
+
The matched grob, or ``None`` if no match.
|
|
179
|
+
"""
|
|
180
|
+
if not is_grob(grob):
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
components = gpath.components
|
|
184
|
+
|
|
185
|
+
# Single-level path: match grob name directly
|
|
186
|
+
if len(components) == 1:
|
|
187
|
+
pattern = components[0]
|
|
188
|
+
use_grep = grep_flags[0]
|
|
189
|
+
if use_grep:
|
|
190
|
+
if re.search(pattern, grob.name):
|
|
191
|
+
return grob
|
|
192
|
+
else:
|
|
193
|
+
if pattern == grob.name:
|
|
194
|
+
return grob
|
|
195
|
+
# If not strict, search children recursively
|
|
196
|
+
if not strict and isinstance(grob, GTree):
|
|
197
|
+
for child_name in grob._children_order:
|
|
198
|
+
child = grob._children[child_name]
|
|
199
|
+
result = _match_grob_path(child, gpath, strict, grep_flags)
|
|
200
|
+
if result is not None:
|
|
201
|
+
return result
|
|
202
|
+
return None
|
|
203
|
+
|
|
204
|
+
# Multi-level path: first component must match this grob (or a descendant if not strict)
|
|
205
|
+
pattern = components[0]
|
|
206
|
+
use_grep = grep_flags[0]
|
|
207
|
+
rest_path = GPath(*components[1:])
|
|
208
|
+
rest_flags = grep_flags[1:]
|
|
209
|
+
|
|
210
|
+
matched_here = False
|
|
211
|
+
if use_grep:
|
|
212
|
+
matched_here = bool(re.search(pattern, grob.name))
|
|
213
|
+
else:
|
|
214
|
+
matched_here = pattern == grob.name
|
|
215
|
+
|
|
216
|
+
if matched_here and isinstance(grob, GTree):
|
|
217
|
+
for child_name in grob._children_order:
|
|
218
|
+
child = grob._children[child_name]
|
|
219
|
+
result = _match_grob_path(child, rest_path, strict, rest_flags)
|
|
220
|
+
if result is not None:
|
|
221
|
+
return result
|
|
222
|
+
|
|
223
|
+
# If not strict, skip this level and try children with the full path
|
|
224
|
+
if not strict and isinstance(grob, GTree):
|
|
225
|
+
for child_name in grob._children_order:
|
|
226
|
+
child = grob._children[child_name]
|
|
227
|
+
result = _match_grob_path(child, gpath, strict, grep_flags)
|
|
228
|
+
if result is not None:
|
|
229
|
+
return result
|
|
230
|
+
|
|
231
|
+
return None
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _redraw_display_list() -> None:
|
|
235
|
+
"""Replay the display list to refresh the output.
|
|
236
|
+
|
|
237
|
+
Calls :meth:`DisplayList.replay` on the current state's display list.
|
|
238
|
+
"""
|
|
239
|
+
state = get_state()
|
|
240
|
+
dl = state.display_list
|
|
241
|
+
if hasattr(dl, "replay"):
|
|
242
|
+
dl.replay(state)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
# ---------------------------------------------------------------------------
|
|
246
|
+
# Public API -- grid.edit / grid.gedit
|
|
247
|
+
# ---------------------------------------------------------------------------
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def grid_edit(
|
|
251
|
+
gPath: Union[str, GPath],
|
|
252
|
+
*,
|
|
253
|
+
strict: bool = False,
|
|
254
|
+
grep: bool = False,
|
|
255
|
+
global_: bool = False,
|
|
256
|
+
allDevices: bool = False,
|
|
257
|
+
redraw: bool = True,
|
|
258
|
+
**kwargs: Any,
|
|
259
|
+
) -> None:
|
|
260
|
+
"""Edit grob(s) on the display list matching *gPath*.
|
|
261
|
+
|
|
262
|
+
Locates grobs whose names match *gPath* on the current display list and
|
|
263
|
+
applies the attribute changes specified in *kwargs*. This is the Python
|
|
264
|
+
equivalent of R's ``grid.edit()``.
|
|
265
|
+
|
|
266
|
+
Parameters
|
|
267
|
+
----------
|
|
268
|
+
gPath : str or GPath
|
|
269
|
+
A grob-path specification identifying the grob(s) to edit.
|
|
270
|
+
strict : bool, optional
|
|
271
|
+
If ``True``, the path must match exactly from the root.
|
|
272
|
+
grep : bool, optional
|
|
273
|
+
If ``True``, path components are treated as regex patterns.
|
|
274
|
+
global_ : bool, optional
|
|
275
|
+
If ``True``, edit *all* matching grobs; otherwise only the first.
|
|
276
|
+
allDevices : bool, optional
|
|
277
|
+
If ``True``, apply the edit to all open devices. Not yet
|
|
278
|
+
implemented -- raises :class:`NotImplementedError`.
|
|
279
|
+
redraw : bool, optional
|
|
280
|
+
If ``True`` (default), redraw after the edit.
|
|
281
|
+
**kwargs
|
|
282
|
+
Attribute name-value pairs to apply to the matched grob(s).
|
|
283
|
+
|
|
284
|
+
Raises
|
|
285
|
+
------
|
|
286
|
+
NotImplementedError
|
|
287
|
+
If *allDevices* is ``True``.
|
|
288
|
+
TypeError
|
|
289
|
+
If *gPath* is invalid.
|
|
290
|
+
"""
|
|
291
|
+
if allDevices:
|
|
292
|
+
raise NotImplementedError("allDevices is not yet implemented")
|
|
293
|
+
|
|
294
|
+
gpath = _ensure_gpath(gPath)
|
|
295
|
+
|
|
296
|
+
if not isinstance(grep, bool):
|
|
297
|
+
raise TypeError("invalid 'grep' value")
|
|
298
|
+
grep_flags = [grep] * gpath.n
|
|
299
|
+
|
|
300
|
+
state = get_state()
|
|
301
|
+
dl = state.display_list
|
|
302
|
+
|
|
303
|
+
matches = _find_dl_grobs(dl, gpath, strict, grep_flags, global_)
|
|
304
|
+
for dl_idx, matched_grob in matches:
|
|
305
|
+
for key, value in kwargs.items():
|
|
306
|
+
if hasattr(matched_grob, key) or hasattr(matched_grob, f"_{key}"):
|
|
307
|
+
setattr(matched_grob, key, value)
|
|
308
|
+
elif key == "gp":
|
|
309
|
+
matched_grob._gp = value
|
|
310
|
+
elif key == "name":
|
|
311
|
+
matched_grob._name = str(value) if value is not None else None
|
|
312
|
+
elif key == "vp":
|
|
313
|
+
matched_grob._vp = Grob._check_vp(value)
|
|
314
|
+
else:
|
|
315
|
+
warnings.warn(f"slot '{key}' not found on grob", stacklevel=2)
|
|
316
|
+
|
|
317
|
+
if redraw and matches:
|
|
318
|
+
_redraw_display_list()
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def grid_gedit(
|
|
322
|
+
gPath: Union[str, GPath],
|
|
323
|
+
*,
|
|
324
|
+
strict: bool = False,
|
|
325
|
+
global_: bool = True,
|
|
326
|
+
allDevices: bool = False,
|
|
327
|
+
redraw: bool = True,
|
|
328
|
+
**kwargs: Any,
|
|
329
|
+
) -> None:
|
|
330
|
+
"""Edit grobs on the display list with ``grep=True`` (convenience alias).
|
|
331
|
+
|
|
332
|
+
This is the Python equivalent of R's ``grid.gedit()``, which is
|
|
333
|
+
``grid.edit()`` with ``grep=True`` and ``global_=True`` as defaults.
|
|
334
|
+
|
|
335
|
+
Parameters
|
|
336
|
+
----------
|
|
337
|
+
gPath : str or GPath
|
|
338
|
+
A grob-path specification (regex matching enabled).
|
|
339
|
+
strict : bool, optional
|
|
340
|
+
If ``True``, require exact depth matching.
|
|
341
|
+
global_ : bool, optional
|
|
342
|
+
If ``True`` (default), edit all matching grobs.
|
|
343
|
+
allDevices : bool, optional
|
|
344
|
+
Not yet implemented.
|
|
345
|
+
redraw : bool, optional
|
|
346
|
+
If ``True`` (default), redraw after the edit.
|
|
347
|
+
**kwargs
|
|
348
|
+
Attribute name-value pairs to apply.
|
|
349
|
+
"""
|
|
350
|
+
grid_edit(
|
|
351
|
+
gPath,
|
|
352
|
+
strict=strict,
|
|
353
|
+
grep=True,
|
|
354
|
+
global_=global_,
|
|
355
|
+
allDevices=allDevices,
|
|
356
|
+
redraw=redraw,
|
|
357
|
+
**kwargs,
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
# ---------------------------------------------------------------------------
|
|
362
|
+
# Public API -- grid.get / grid.gget
|
|
363
|
+
# ---------------------------------------------------------------------------
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def grid_get(
|
|
367
|
+
gPath: Union[str, GPath],
|
|
368
|
+
strict: bool = False,
|
|
369
|
+
grep: bool = False,
|
|
370
|
+
global_: bool = False,
|
|
371
|
+
allDevices: bool = False,
|
|
372
|
+
) -> Union[Grob, List[Grob], None]:
|
|
373
|
+
"""Get grob(s) from the display list matching *gPath*.
|
|
374
|
+
|
|
375
|
+
Returns the grob(s) whose names match *gPath*. This is the Python
|
|
376
|
+
equivalent of R's ``grid.get()``.
|
|
377
|
+
|
|
378
|
+
Parameters
|
|
379
|
+
----------
|
|
380
|
+
gPath : str or GPath
|
|
381
|
+
A grob-path specification.
|
|
382
|
+
strict : bool, optional
|
|
383
|
+
If ``True``, require exact depth matching.
|
|
384
|
+
grep : bool, optional
|
|
385
|
+
If ``True``, use regex matching on path components.
|
|
386
|
+
global_ : bool, optional
|
|
387
|
+
If ``True``, return a list of all matching grobs; otherwise,
|
|
388
|
+
return the first match (or ``None``).
|
|
389
|
+
allDevices : bool, optional
|
|
390
|
+
Not yet implemented.
|
|
391
|
+
|
|
392
|
+
Returns
|
|
393
|
+
-------
|
|
394
|
+
Grob or list[Grob] or None
|
|
395
|
+
The matched grob(s). If *global_* is ``False``, a single
|
|
396
|
+
:class:`~._grob.Grob` or ``None``. If *global_* is ``True``,
|
|
397
|
+
a list (possibly empty).
|
|
398
|
+
|
|
399
|
+
Raises
|
|
400
|
+
------
|
|
401
|
+
NotImplementedError
|
|
402
|
+
If *allDevices* is ``True``.
|
|
403
|
+
TypeError
|
|
404
|
+
If *gPath* is invalid or *grep* is not boolean.
|
|
405
|
+
"""
|
|
406
|
+
if allDevices:
|
|
407
|
+
raise NotImplementedError("allDevices is not yet implemented")
|
|
408
|
+
|
|
409
|
+
gpath = _ensure_gpath(gPath)
|
|
410
|
+
|
|
411
|
+
if not isinstance(grep, bool):
|
|
412
|
+
raise TypeError("invalid 'grep' value")
|
|
413
|
+
grep_flags = [grep] * gpath.n
|
|
414
|
+
|
|
415
|
+
state = get_state()
|
|
416
|
+
dl = state.display_list
|
|
417
|
+
|
|
418
|
+
matches = _find_dl_grobs(dl, gpath, strict, grep_flags, global_)
|
|
419
|
+
|
|
420
|
+
if global_:
|
|
421
|
+
return [copy.deepcopy(m[1]) for m in matches]
|
|
422
|
+
if matches:
|
|
423
|
+
return copy.deepcopy(matches[0][1])
|
|
424
|
+
return None
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def grid_gget(
|
|
428
|
+
gPath: Union[str, GPath],
|
|
429
|
+
strict: bool = False,
|
|
430
|
+
allDevices: bool = False,
|
|
431
|
+
) -> Union[Grob, List[Grob], None]:
|
|
432
|
+
"""Get grobs from the display list with ``grep=True`` (convenience alias).
|
|
433
|
+
|
|
434
|
+
This is the Python equivalent of R's ``grid.gget()``, which is
|
|
435
|
+
``grid.get()`` with ``grep=True`` and ``global_=True`` as defaults.
|
|
436
|
+
|
|
437
|
+
Parameters
|
|
438
|
+
----------
|
|
439
|
+
gPath : str or GPath
|
|
440
|
+
A grob-path specification (regex matching enabled).
|
|
441
|
+
strict : bool, optional
|
|
442
|
+
If ``True``, require exact depth matching.
|
|
443
|
+
allDevices : bool, optional
|
|
444
|
+
Not yet implemented.
|
|
445
|
+
|
|
446
|
+
Returns
|
|
447
|
+
-------
|
|
448
|
+
Grob or list[Grob] or None
|
|
449
|
+
The matched grob(s).
|
|
450
|
+
"""
|
|
451
|
+
return grid_get(
|
|
452
|
+
gPath,
|
|
453
|
+
strict=strict,
|
|
454
|
+
grep=True,
|
|
455
|
+
global_=True,
|
|
456
|
+
allDevices=allDevices,
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
# ---------------------------------------------------------------------------
|
|
461
|
+
# Public API -- grid.set
|
|
462
|
+
# ---------------------------------------------------------------------------
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def grid_set(
|
|
466
|
+
gPath: Union[str, GPath],
|
|
467
|
+
newGrob: Grob,
|
|
468
|
+
strict: bool = False,
|
|
469
|
+
grep: bool = False,
|
|
470
|
+
redraw: bool = True,
|
|
471
|
+
) -> None:
|
|
472
|
+
"""Set (replace) a grob on the display list.
|
|
473
|
+
|
|
474
|
+
Locates the first grob matching *gPath* on the display list and replaces
|
|
475
|
+
it with *newGrob*. This is the Python equivalent of R's ``grid.set()``.
|
|
476
|
+
|
|
477
|
+
Parameters
|
|
478
|
+
----------
|
|
479
|
+
gPath : str or GPath
|
|
480
|
+
A grob-path specification identifying the grob to replace.
|
|
481
|
+
newGrob : Grob
|
|
482
|
+
The replacement grob.
|
|
483
|
+
strict : bool, optional
|
|
484
|
+
If ``True``, require exact depth matching.
|
|
485
|
+
grep : bool, optional
|
|
486
|
+
If ``True``, use regex matching on path components.
|
|
487
|
+
redraw : bool, optional
|
|
488
|
+
If ``True`` (default), redraw after the replacement.
|
|
489
|
+
|
|
490
|
+
Raises
|
|
491
|
+
------
|
|
492
|
+
TypeError
|
|
493
|
+
If *gPath* is invalid or *grep* is not boolean.
|
|
494
|
+
ValueError
|
|
495
|
+
If *gPath* does not match any grob on the display list.
|
|
496
|
+
"""
|
|
497
|
+
gpath = _ensure_gpath(gPath)
|
|
498
|
+
|
|
499
|
+
if not isinstance(grep, bool):
|
|
500
|
+
raise TypeError("invalid 'grep' value")
|
|
501
|
+
grep_flags = [grep] * gpath.n
|
|
502
|
+
|
|
503
|
+
state = get_state()
|
|
504
|
+
dl = state.display_list
|
|
505
|
+
|
|
506
|
+
matches = _find_dl_grobs(dl, gpath, strict, grep_flags, False)
|
|
507
|
+
if not matches:
|
|
508
|
+
raise ValueError("gPath does not specify a valid child")
|
|
509
|
+
|
|
510
|
+
dl_idx, _old = matches[0]
|
|
511
|
+
# Replace the grob in the display list item
|
|
512
|
+
item = dl[dl_idx]
|
|
513
|
+
if isinstance(item, DLDrawGrob):
|
|
514
|
+
item.grob = newGrob
|
|
515
|
+
item.params["grob"] = newGrob
|
|
516
|
+
|
|
517
|
+
if redraw:
|
|
518
|
+
_redraw_display_list()
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
# ---------------------------------------------------------------------------
|
|
522
|
+
# Public API -- grid.add
|
|
523
|
+
# ---------------------------------------------------------------------------
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def grid_add(
|
|
527
|
+
grob: Grob,
|
|
528
|
+
gPath: Optional[Union[str, GPath]] = None,
|
|
529
|
+
strict: bool = False,
|
|
530
|
+
grep: bool = False,
|
|
531
|
+
global_: bool = False,
|
|
532
|
+
allDevices: bool = False,
|
|
533
|
+
redraw: bool = True,
|
|
534
|
+
) -> None:
|
|
535
|
+
"""Add a grob to a gTree on the display list.
|
|
536
|
+
|
|
537
|
+
If *gPath* is ``None``, *grob* is appended as a new top-level entry on
|
|
538
|
+
the display list. Otherwise, the grob is added as a child of the
|
|
539
|
+
gTree identified by *gPath*. This is the Python equivalent of R's
|
|
540
|
+
``grid.add()``.
|
|
541
|
+
|
|
542
|
+
Parameters
|
|
543
|
+
----------
|
|
544
|
+
grob : Grob
|
|
545
|
+
The grob to add.
|
|
546
|
+
gPath : str, GPath, or None, optional
|
|
547
|
+
Path to the parent gTree. ``None`` adds to the top level.
|
|
548
|
+
strict : bool, optional
|
|
549
|
+
If ``True``, require exact depth matching.
|
|
550
|
+
grep : bool, optional
|
|
551
|
+
If ``True``, use regex matching on path components.
|
|
552
|
+
global_ : bool, optional
|
|
553
|
+
If ``True``, add to all matching gTrees; otherwise only the first.
|
|
554
|
+
allDevices : bool, optional
|
|
555
|
+
Not yet implemented.
|
|
556
|
+
redraw : bool, optional
|
|
557
|
+
If ``True`` (default), redraw after the addition.
|
|
558
|
+
|
|
559
|
+
Raises
|
|
560
|
+
------
|
|
561
|
+
NotImplementedError
|
|
562
|
+
If *allDevices* is ``True``.
|
|
563
|
+
TypeError
|
|
564
|
+
If *gPath* is invalid or *grep* is not boolean.
|
|
565
|
+
"""
|
|
566
|
+
if allDevices:
|
|
567
|
+
raise NotImplementedError("allDevices is not yet implemented")
|
|
568
|
+
|
|
569
|
+
state = get_state()
|
|
570
|
+
dl = state.display_list
|
|
571
|
+
|
|
572
|
+
if gPath is None:
|
|
573
|
+
# Add as a new top-level entry
|
|
574
|
+
dl.record(DLDrawGrob(grob=grob))
|
|
575
|
+
else:
|
|
576
|
+
gpath = _ensure_gpath(gPath)
|
|
577
|
+
if not isinstance(grep, bool):
|
|
578
|
+
raise TypeError("invalid 'grep' value")
|
|
579
|
+
grep_flags = [grep] * gpath.n
|
|
580
|
+
|
|
581
|
+
matches = _find_dl_grobs(dl, gpath, strict, grep_flags, global_)
|
|
582
|
+
for _dl_idx, matched_grob in matches:
|
|
583
|
+
if isinstance(matched_grob, GTree):
|
|
584
|
+
matched_grob.add_child(grob)
|
|
585
|
+
else:
|
|
586
|
+
warnings.warn(
|
|
587
|
+
f"gPath matched a non-gTree grob '{matched_grob.name}'; "
|
|
588
|
+
"cannot add child",
|
|
589
|
+
stacklevel=2,
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
if redraw:
|
|
593
|
+
_redraw_display_list()
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
# ---------------------------------------------------------------------------
|
|
597
|
+
# Public API -- grid.remove / grid.gremove
|
|
598
|
+
# ---------------------------------------------------------------------------
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
def grid_remove(
|
|
602
|
+
gPath: Union[str, GPath],
|
|
603
|
+
warn: bool = True,
|
|
604
|
+
strict: bool = False,
|
|
605
|
+
grep: bool = False,
|
|
606
|
+
global_: bool = False,
|
|
607
|
+
allDevices: bool = False,
|
|
608
|
+
redraw: bool = True,
|
|
609
|
+
) -> None:
|
|
610
|
+
"""Remove grob(s) from the display list.
|
|
611
|
+
|
|
612
|
+
Locates grobs matching *gPath* and removes them. For single-component
|
|
613
|
+
paths the grob is removed directly from the display list. For
|
|
614
|
+
multi-component paths the leaf grob is removed from its parent gTree.
|
|
615
|
+
This is the Python equivalent of R's ``grid.remove()``.
|
|
616
|
+
|
|
617
|
+
Parameters
|
|
618
|
+
----------
|
|
619
|
+
gPath : str or GPath
|
|
620
|
+
Path identifying the grob(s) to remove.
|
|
621
|
+
warn : bool, optional
|
|
622
|
+
If ``True`` (default), warn when the path does not match.
|
|
623
|
+
strict : bool, optional
|
|
624
|
+
If ``True``, require exact depth matching.
|
|
625
|
+
grep : bool, optional
|
|
626
|
+
If ``True``, use regex matching on path components.
|
|
627
|
+
global_ : bool, optional
|
|
628
|
+
If ``True``, remove all matching grobs; otherwise only the first.
|
|
629
|
+
allDevices : bool, optional
|
|
630
|
+
Not yet implemented.
|
|
631
|
+
redraw : bool, optional
|
|
632
|
+
If ``True`` (default), redraw after the removal.
|
|
633
|
+
|
|
634
|
+
Raises
|
|
635
|
+
------
|
|
636
|
+
NotImplementedError
|
|
637
|
+
If *allDevices* is ``True``.
|
|
638
|
+
TypeError
|
|
639
|
+
If *gPath* is invalid or *grep* is not boolean.
|
|
640
|
+
"""
|
|
641
|
+
if allDevices:
|
|
642
|
+
raise NotImplementedError("allDevices is not yet implemented")
|
|
643
|
+
|
|
644
|
+
gpath = _ensure_gpath(gPath)
|
|
645
|
+
|
|
646
|
+
if not isinstance(grep, bool):
|
|
647
|
+
raise TypeError("invalid 'grep' value")
|
|
648
|
+
grep_flags = [grep] * gpath.n
|
|
649
|
+
|
|
650
|
+
state = get_state()
|
|
651
|
+
dl = state.display_list
|
|
652
|
+
|
|
653
|
+
if gpath.n == 1:
|
|
654
|
+
# Remove directly from the display list
|
|
655
|
+
pattern = gpath.name
|
|
656
|
+
use_grep = grep_flags[0]
|
|
657
|
+
removed_any = False
|
|
658
|
+
items = dl.get_items()
|
|
659
|
+
indices_to_remove: list[int] = []
|
|
660
|
+
|
|
661
|
+
for idx, item in enumerate(items):
|
|
662
|
+
if not isinstance(item, DLDrawGrob) or item.grob is None:
|
|
663
|
+
continue
|
|
664
|
+
matched = False
|
|
665
|
+
if use_grep:
|
|
666
|
+
matched = bool(re.search(pattern, item.grob.name))
|
|
667
|
+
else:
|
|
668
|
+
matched = pattern == item.grob.name
|
|
669
|
+
if matched:
|
|
670
|
+
indices_to_remove.append(idx)
|
|
671
|
+
removed_any = True
|
|
672
|
+
if not global_:
|
|
673
|
+
break
|
|
674
|
+
|
|
675
|
+
# Remove in reverse order to preserve indices
|
|
676
|
+
for idx in reversed(indices_to_remove):
|
|
677
|
+
dl._items.pop(idx)
|
|
678
|
+
|
|
679
|
+
if not removed_any and warn:
|
|
680
|
+
warnings.warn(
|
|
681
|
+
f"gPath ({gpath}) not found on the display list",
|
|
682
|
+
stacklevel=2,
|
|
683
|
+
)
|
|
684
|
+
else:
|
|
685
|
+
# Multi-level: remove leaf from parent gTree
|
|
686
|
+
parent_path = GPath(*gpath.components[:-1])
|
|
687
|
+
leaf_name = gpath.name
|
|
688
|
+
leaf_grep = grep_flags[-1]
|
|
689
|
+
parent_grep_flags = grep_flags[:-1]
|
|
690
|
+
|
|
691
|
+
matches = _find_dl_grobs(dl, parent_path, strict, parent_grep_flags, global_)
|
|
692
|
+
removed_any = False
|
|
693
|
+
|
|
694
|
+
for _dl_idx, parent_grob in matches:
|
|
695
|
+
if not isinstance(parent_grob, GTree):
|
|
696
|
+
continue
|
|
697
|
+
# Find matching child(ren) to remove
|
|
698
|
+
names_to_remove: list[str] = []
|
|
699
|
+
for child_name in parent_grob._children_order:
|
|
700
|
+
if leaf_grep:
|
|
701
|
+
if re.search(leaf_name, child_name):
|
|
702
|
+
names_to_remove.append(child_name)
|
|
703
|
+
else:
|
|
704
|
+
if leaf_name == child_name:
|
|
705
|
+
names_to_remove.append(child_name)
|
|
706
|
+
if not global_ and names_to_remove:
|
|
707
|
+
break
|
|
708
|
+
|
|
709
|
+
for name in names_to_remove:
|
|
710
|
+
parent_grob.remove_child(name)
|
|
711
|
+
removed_any = True
|
|
712
|
+
|
|
713
|
+
if not removed_any and warn:
|
|
714
|
+
warnings.warn(
|
|
715
|
+
f"gPath ({gpath}) not found",
|
|
716
|
+
stacklevel=2,
|
|
717
|
+
)
|
|
718
|
+
|
|
719
|
+
if redraw:
|
|
720
|
+
_redraw_display_list()
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
def grid_gremove(
|
|
724
|
+
gPath: Union[str, GPath],
|
|
725
|
+
warn: bool = True,
|
|
726
|
+
strict: bool = False,
|
|
727
|
+
allDevices: bool = False,
|
|
728
|
+
redraw: bool = True,
|
|
729
|
+
) -> None:
|
|
730
|
+
"""Remove grobs with ``grep=True`` (convenience alias).
|
|
731
|
+
|
|
732
|
+
This is the Python equivalent of R's ``grid.gremove()``, which is
|
|
733
|
+
``grid.remove()`` with ``grep=True`` and ``global_=True`` as defaults.
|
|
734
|
+
|
|
735
|
+
Parameters
|
|
736
|
+
----------
|
|
737
|
+
gPath : str or GPath
|
|
738
|
+
Path identifying the grob(s) to remove (regex matching enabled).
|
|
739
|
+
warn : bool, optional
|
|
740
|
+
If ``True`` (default), warn when the path does not match.
|
|
741
|
+
strict : bool, optional
|
|
742
|
+
If ``True``, require exact depth matching.
|
|
743
|
+
allDevices : bool, optional
|
|
744
|
+
Not yet implemented.
|
|
745
|
+
redraw : bool, optional
|
|
746
|
+
If ``True`` (default), redraw after the removal.
|
|
747
|
+
"""
|
|
748
|
+
grid_remove(
|
|
749
|
+
gPath,
|
|
750
|
+
warn=warn,
|
|
751
|
+
strict=strict,
|
|
752
|
+
grep=True,
|
|
753
|
+
global_=True,
|
|
754
|
+
allDevices=allDevices,
|
|
755
|
+
redraw=redraw,
|
|
756
|
+
)
|