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/_group.py ADDED
@@ -0,0 +1,798 @@
1
+ """Group, define, and use grob system for grid_py.
2
+
3
+ Port of R's ``grid/R/group.R``. This module provides grob classes and
4
+ factory functions for compositing groups:
5
+
6
+ * :class:`GroupGrob` -- a grob that groups its children with a compositing
7
+ operator (``"over"``, ``"source"``, ``"xor"``, etc.).
8
+ * :class:`DefineGrob` -- a grob for deferred definition (define once, use
9
+ later via :class:`UseGrob`).
10
+ * :class:`UseGrob` -- a grob that references a previously defined group and
11
+ optionally applies an affine transform.
12
+
13
+ Factory functions mirror the R API:
14
+
15
+ * :func:`group_grob` / :func:`grid_group`
16
+ * :func:`define_grob` / :func:`grid_define`
17
+ * :func:`use_grob` / :func:`grid_use`
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import warnings
23
+ from typing import Any, Optional, Union
24
+
25
+ import numpy as np
26
+ from numpy.typing import NDArray
27
+
28
+ from ._gpar import Gpar
29
+ from ._grob import Grob, GList, GTree
30
+ from ._transforms import (
31
+ viewport_transform,
32
+ )
33
+
34
+ __all__ = [
35
+ # Classes
36
+ "GroupGrob",
37
+ "DefineGrob",
38
+ "UseGrob",
39
+ # Factory / convenience functions
40
+ "group_grob",
41
+ "grid_group",
42
+ "define_grob",
43
+ "grid_define",
44
+ "use_grob",
45
+ "grid_use",
46
+ # Constants
47
+ "COMPOSITING_OPERATORS",
48
+ ]
49
+
50
+ # ---------------------------------------------------------------------------
51
+ # Valid compositing operators (mirrors R's .opIndex validation)
52
+ # ---------------------------------------------------------------------------
53
+
54
+ COMPOSITING_OPERATORS: tuple[str, ...] = (
55
+ "clear",
56
+ "source",
57
+ "over",
58
+ "in",
59
+ "out",
60
+ "atop",
61
+ "dest",
62
+ "dest.over",
63
+ "dest.in",
64
+ "dest.out",
65
+ "dest.atop",
66
+ "xor",
67
+ "add",
68
+ "saturate",
69
+ )
70
+
71
+
72
+ def _validate_op(op: str) -> str:
73
+ """Validate a compositing operator string.
74
+
75
+ Parameters
76
+ ----------
77
+ op : str
78
+ Compositing operator name.
79
+
80
+ Returns
81
+ -------
82
+ str
83
+ The validated (lower-cased) operator.
84
+
85
+ Raises
86
+ ------
87
+ ValueError
88
+ If *op* is not a recognised compositing operator.
89
+ """
90
+ op_lower = op.lower()
91
+ if op_lower not in COMPOSITING_OPERATORS:
92
+ raise ValueError(
93
+ f"Invalid compositing operator {op!r}. "
94
+ f"Must be one of {COMPOSITING_OPERATORS!r}"
95
+ )
96
+ return op_lower
97
+
98
+
99
+ def _validate_transform(transform: Optional[NDArray[np.float64]]) -> None:
100
+ """Validate that *transform* is a legal 3x3 affine matrix.
101
+
102
+ The bottom-right element must be 1 and the right column (indices
103
+ ``[0, 2]`` and ``[1, 2]``) must be 0, matching R's convention.
104
+
105
+ Parameters
106
+ ----------
107
+ transform : ndarray or None
108
+ A 3x3 numeric matrix, or ``None``.
109
+
110
+ Raises
111
+ ------
112
+ ValueError
113
+ If the matrix does not satisfy the constraints.
114
+ TypeError
115
+ If *transform* is not a numpy array.
116
+ """
117
+ if transform is None:
118
+ return
119
+ if not isinstance(transform, np.ndarray):
120
+ raise TypeError(
121
+ f"'transform' must be a numpy ndarray, got {type(transform).__name__}"
122
+ )
123
+ if transform.shape != (3, 3):
124
+ raise ValueError(
125
+ f"'transform' must be a 3x3 matrix, got shape {transform.shape}"
126
+ )
127
+ if not np.issubdtype(transform.dtype, np.number):
128
+ raise ValueError("'transform' must contain numeric values")
129
+ if transform[0, 2] != 0 or transform[1, 2] != 0 or transform[2, 2] != 1:
130
+ raise ValueError(
131
+ "Invalid transform: requires transform[0,2]==0, "
132
+ "transform[1,2]==0, and transform[2,2]==1"
133
+ )
134
+
135
+
136
+ # ============================================================================
137
+ # GroupGrob
138
+ # ============================================================================
139
+
140
+
141
+ class GroupGrob(GTree):
142
+ """A grob that groups its children with a compositing operator.
143
+
144
+ This is the Python equivalent of R's ``GridGroup`` S3 class produced by
145
+ ``groupGrob()``. When drawn, the *src* grob is composited onto the
146
+ optional *dst* grob using the specified Porter-Duff compositing
147
+ *op* (defaulting to ``"over"``).
148
+
149
+ Parameters
150
+ ----------
151
+ src : Grob or None
152
+ Source grob to composite.
153
+ op : str
154
+ Compositing operator (default ``"over"``). Must be one of
155
+ :data:`COMPOSITING_OPERATORS`.
156
+ dst : Grob or None
157
+ Destination grob. When ``None`` the destination is transparent.
158
+ name : str or None
159
+ Unique grob name. Auto-generated when ``None``.
160
+ gp : Gpar or None
161
+ Graphical parameters.
162
+ vp : object or None
163
+ Viewport.
164
+
165
+ Raises
166
+ ------
167
+ TypeError
168
+ If *src* is not a :class:`Grob` (when provided) or *dst* is neither
169
+ a :class:`Grob` nor ``None``.
170
+ ValueError
171
+ If *op* is not a recognised compositing operator.
172
+
173
+ Examples
174
+ --------
175
+ >>> from grid_py._grob import Grob
176
+ >>> src = Grob(name="circle1")
177
+ >>> g = GroupGrob(src=src, op="xor")
178
+ >>> g.op
179
+ 'xor'
180
+ """
181
+
182
+ _grid_class: str = "GridGroup" # type: ignore[assignment]
183
+
184
+ def __init__(
185
+ self,
186
+ src: Optional[Grob] = None,
187
+ op: str = "over",
188
+ dst: Optional[Grob] = None,
189
+ name: Optional[str] = None,
190
+ gp: Optional[Gpar] = None,
191
+ vp: Optional[Any] = None,
192
+ ) -> None:
193
+ self.src: Optional[Grob] = src
194
+ self.op: str = _validate_op(op)
195
+ self.dst: Optional[Grob] = dst
196
+ # Build a children GList from src and dst for the GTree machinery
197
+ children = self._build_children()
198
+ super().__init__(
199
+ children=children,
200
+ name=name,
201
+ gp=gp,
202
+ vp=vp,
203
+ _grid_class="GridGroup",
204
+ )
205
+
206
+ # -- helpers -----------------------------------------------------------
207
+
208
+ def _build_children(self) -> Optional[GList]:
209
+ """Build a :class:`GList` from *src* and *dst*."""
210
+ parts: list[Grob] = []
211
+ if self.src is not None:
212
+ parts.append(self.src)
213
+ if self.dst is not None:
214
+ parts.append(self.dst)
215
+ return GList(*parts) if parts else None
216
+
217
+ # -- validation --------------------------------------------------------
218
+
219
+ def valid_details(self) -> None:
220
+ """Validate GroupGrob-specific slots.
221
+
222
+ Raises
223
+ ------
224
+ TypeError
225
+ If *src* or *dst* have incorrect types.
226
+ ValueError
227
+ If *op* is invalid.
228
+ """
229
+ if hasattr(self, "src"):
230
+ if self.src is not None and not isinstance(self.src, Grob):
231
+ raise TypeError("Invalid source: must be a Grob or None")
232
+ if hasattr(self, "dst"):
233
+ if self.dst is not None and not isinstance(self.dst, Grob):
234
+ raise TypeError("Invalid destination: must be a Grob or None")
235
+ if hasattr(self, "op"):
236
+ self.op = _validate_op(self.op)
237
+
238
+ # -- drawing -----------------------------------------------------------
239
+
240
+ def draw_details(self, recording: bool = True) -> None:
241
+ """Draw the composited group.
242
+
243
+ Port of R ``drawDetails.GridGroup`` (group.R:261-270):
244
+ 1. finaliseGroup(x) → source/destination closures
245
+ 2. .defineGroup(src, op, dst) → ref
246
+ 3. recordGroup(x, ref)
247
+ 4. .useGroup(ref, NULL)
248
+ """
249
+ _draw_group_grob(self, use_immediately=True)
250
+
251
+ # -- repr --------------------------------------------------------------
252
+
253
+ def __repr__(self) -> str:
254
+ return (
255
+ f"GroupGrob[{self.name}](op={self.op!r}, "
256
+ f"src={self.src!r}, dst={self.dst!r})"
257
+ )
258
+
259
+
260
+ # ============================================================================
261
+ # DefineGrob
262
+ # ============================================================================
263
+
264
+
265
+ class DefineGrob(GTree):
266
+ """A grob for deferred group definition.
267
+
268
+ This is the Python equivalent of R's ``GridDefine`` S3 class produced by
269
+ ``defineGrob()``. The group is defined (but not drawn) so that it can
270
+ later be referenced by :class:`UseGrob`.
271
+
272
+ Parameters
273
+ ----------
274
+ src : Grob
275
+ Source grob to define.
276
+ op : str
277
+ Compositing operator (default ``"over"``).
278
+ dst : Grob or None
279
+ Destination grob (default ``None``).
280
+ name : str or None
281
+ Unique grob name. Auto-generated when ``None``.
282
+ gp : Gpar or None
283
+ Graphical parameters.
284
+ vp : object or None
285
+ Viewport.
286
+
287
+ Raises
288
+ ------
289
+ TypeError
290
+ If *src* is not a :class:`Grob`.
291
+ ValueError
292
+ If *op* is not a recognised compositing operator.
293
+
294
+ Examples
295
+ --------
296
+ >>> from grid_py._grob import Grob
297
+ >>> src = Grob(name="rect1")
298
+ >>> d = DefineGrob(src=src)
299
+ >>> d.src.name
300
+ 'rect1'
301
+ """
302
+
303
+ _grid_class: str = "GridDefine" # type: ignore[assignment]
304
+
305
+ def __init__(
306
+ self,
307
+ src: Grob = None, # type: ignore[assignment]
308
+ op: str = "over",
309
+ dst: Optional[Grob] = None,
310
+ name: Optional[str] = None,
311
+ gp: Optional[Gpar] = None,
312
+ vp: Optional[Any] = None,
313
+ ) -> None:
314
+ self.src: Grob = src # type: ignore[assignment]
315
+ self.op: str = _validate_op(op)
316
+ self.dst: Optional[Grob] = dst
317
+ # Build children
318
+ parts: list[Grob] = []
319
+ if self.src is not None:
320
+ parts.append(self.src)
321
+ if self.dst is not None:
322
+ parts.append(self.dst)
323
+ children = GList(*parts) if parts else None
324
+ super().__init__(
325
+ children=children,
326
+ name=name,
327
+ gp=gp,
328
+ vp=vp,
329
+ _grid_class="GridDefine",
330
+ )
331
+
332
+ # -- validation --------------------------------------------------------
333
+
334
+ def valid_details(self) -> None:
335
+ """Validate DefineGrob-specific slots.
336
+
337
+ Raises
338
+ ------
339
+ TypeError
340
+ If *src* is not a :class:`Grob`.
341
+ """
342
+ if hasattr(self, "src") and self.src is not None:
343
+ if not isinstance(self.src, Grob):
344
+ raise TypeError("Invalid source: must be a Grob")
345
+ if hasattr(self, "dst"):
346
+ if self.dst is not None and not isinstance(self.dst, Grob):
347
+ raise TypeError("Invalid destination: must be a Grob or None")
348
+ if hasattr(self, "op"):
349
+ self.op = _validate_op(self.op)
350
+
351
+ # -- drawing -----------------------------------------------------------
352
+
353
+ def draw_details(self, recording: bool = True) -> None:
354
+ """Define the group without drawing.
355
+
356
+ Port of R ``drawDetails.GridDefine`` (group.R:300-304):
357
+ 1. finaliseGroup(x) → source/destination closures
358
+ 2. .defineGroup(src, op, dst) → ref
359
+ 3. recordGroup(x, ref) — store for later UseGrob
360
+ No visible output is produced.
361
+ """
362
+ _draw_group_grob(self, use_immediately=False)
363
+
364
+ # -- repr --------------------------------------------------------------
365
+
366
+ def __repr__(self) -> str:
367
+ return (
368
+ f"DefineGrob[{self.name}](src={self.src!r}, "
369
+ f"op={self.op!r}, dst={self.dst!r})"
370
+ )
371
+
372
+
373
+ # ============================================================================
374
+ # UseGrob
375
+ # ============================================================================
376
+
377
+
378
+ class UseGrob(Grob):
379
+ """A grob that references a previously defined group.
380
+
381
+ This is the Python equivalent of R's ``GridUse`` S3 class produced by
382
+ ``useGrob()``. It draws a group that was previously registered via
383
+ :class:`DefineGrob`, optionally applying an affine *transform*.
384
+
385
+ Parameters
386
+ ----------
387
+ group : str
388
+ Name of the previously defined group to use.
389
+ transform : ndarray or None
390
+ A 3x3 affine transformation matrix (NumPy array, dtype float64).
391
+ When ``None``, the default viewport transform is used.
392
+ name : str or None
393
+ Unique grob name. Auto-generated when ``None``.
394
+ gp : Gpar or None
395
+ Graphical parameters.
396
+ vp : object or None
397
+ Viewport.
398
+
399
+ Raises
400
+ ------
401
+ TypeError
402
+ If *group* is not a string or *transform* is not a numpy array.
403
+ ValueError
404
+ If *transform* does not satisfy the affine-matrix constraints.
405
+
406
+ Examples
407
+ --------
408
+ >>> u = UseGrob(group="rect1")
409
+ >>> u.group
410
+ 'rect1'
411
+ """
412
+
413
+ _grid_class: str = "GridUse" # type: ignore[assignment]
414
+
415
+ def __init__(
416
+ self,
417
+ group: str = "",
418
+ transform: Optional[NDArray[np.float64]] = None,
419
+ name: Optional[str] = None,
420
+ gp: Optional[Gpar] = None,
421
+ vp: Optional[Any] = None,
422
+ ) -> None:
423
+ self.group: str = str(group)
424
+ self.transform: Optional[NDArray[np.float64]] = transform
425
+ _validate_transform(self.transform)
426
+ super().__init__(
427
+ name=name,
428
+ gp=gp,
429
+ vp=vp,
430
+ _grid_class="GridUse",
431
+ )
432
+
433
+ # -- validation --------------------------------------------------------
434
+
435
+ def valid_details(self) -> None:
436
+ """Validate UseGrob-specific slots.
437
+
438
+ Raises
439
+ ------
440
+ TypeError
441
+ If *group* is not a string.
442
+ ValueError
443
+ If *transform* is invalid.
444
+ """
445
+ if hasattr(self, "group"):
446
+ if not isinstance(self.group, str):
447
+ raise TypeError("'group' must be a string")
448
+ if hasattr(self, "transform"):
449
+ _validate_transform(self.transform)
450
+
451
+ # -- drawing -----------------------------------------------------------
452
+
453
+ def draw_details(self, recording: bool = True) -> None:
454
+ """Draw the referenced group with the optional transform.
455
+
456
+ Port of R ``drawDetails.GridUse`` (group.R:330-347):
457
+ 1. lookupGroup(x$group) → group metadata
458
+ 2. Compute transform via x$transform(group, device=TRUE)
459
+ 3. Validate 3x3 affine matrix
460
+ 4. .useGroup(group$ref, transform)
461
+ """
462
+ from ._state import get_state
463
+
464
+ state = get_state()
465
+ group_data = state.lookup_group(self.group)
466
+
467
+ if group_data is None:
468
+ warnings.warn(f"Unknown group: {self.group}")
469
+ return
470
+
471
+ ref = group_data.get("ref")
472
+ if ref is None:
473
+ warnings.warn(f"Group '{self.group}' has no ref")
474
+ return
475
+
476
+ # Compute transform (R group.R:335)
477
+ transform = self.transform
478
+ if callable(transform):
479
+ # R passes a function: x$transform(group, device=TRUE)
480
+ transform = transform(group_data, device=True)
481
+
482
+ # Validate transform (R group.R:336-344)
483
+ if transform is not None:
484
+ m = np.asarray(transform, dtype=float)
485
+ if m.shape != (3, 3):
486
+ warnings.warn("Invalid transform (nothing drawn)")
487
+ return
488
+ if m[0, 2] != 0 or m[1, 2] != 0 or m[2, 2] != 1:
489
+ warnings.warn("Invalid transform (nothing drawn)")
490
+ return
491
+
492
+ renderer = state.get_renderer()
493
+ if renderer is not None and hasattr(renderer, "use_group"):
494
+ renderer.use_group(ref, transform)
495
+
496
+ # -- repr --------------------------------------------------------------
497
+
498
+ def __repr__(self) -> str:
499
+ return (
500
+ f"UseGrob[{self.name}](group={self.group!r}, "
501
+ f"transform={'set' if self.transform is not None else 'None'})"
502
+ )
503
+
504
+
505
+ # ============================================================================
506
+ # Internal: shared group drawing logic
507
+ # ============================================================================
508
+
509
+
510
+ def _draw_group_grob(grob: Union[GroupGrob, DefineGrob],
511
+ use_immediately: bool) -> None:
512
+ """Shared logic for GroupGrob.draw_details and DefineGrob.draw_details.
513
+
514
+ Port of R's ``drawDetails.GridGroup`` (group.R:261-270) and
515
+ ``drawDetails.GridDefine`` (group.R:300-304).
516
+
517
+ 1. Build source/destination draw closures (``finaliseGroup``, group.R:9-58)
518
+ 2. Call renderer.define_group(src, op, dst) → ref
519
+ 3. Record group in state for later UseGrob access
520
+ 4. If *use_immediately*, call renderer.use_group(ref, None)
521
+ """
522
+ from ._state import get_state
523
+ from ._draw import grid_draw
524
+
525
+ state = get_state()
526
+ renderer = state.get_renderer()
527
+
528
+ if renderer is None:
529
+ return
530
+
531
+ src_grob = getattr(grob, "src", None)
532
+ dst_grob = getattr(grob, "dst", None)
533
+ op = getattr(grob, "op", "over")
534
+
535
+ # Build source closure (R group.R:10-38)
536
+ # R pushes a viewport with mask="none" to ensure clean group context.
537
+ # We simply draw the source grob.
538
+ def source_fn():
539
+ if src_grob is not None:
540
+ grid_draw(src_grob, recording=False)
541
+
542
+ # Build destination closure (R group.R:40-56)
543
+ dst_fn = None
544
+ if dst_grob is not None:
545
+ def dst_fn():
546
+ grid_draw(dst_grob, recording=False)
547
+
548
+ # Define group on renderer (R group.R:263/302)
549
+ ref = renderer.define_group(source_fn, op, dst_fn)
550
+
551
+ # Record group for later UseGrob access (R group.R:265/303)
552
+ # Port of recordGroup (group.R:65-104)
553
+ from ._units import convert_x, convert_y, Unit
554
+ group_data = {
555
+ "ref": ref,
556
+ "name": grob.name,
557
+ }
558
+ # Store viewport location/size for viewportTransform
559
+ try:
560
+ vtr = renderer._vp_transform_stack[-1]
561
+ group_data["wh"] = (vtr.width_cm / 2.54, vtr.height_cm / 2.54)
562
+ group_data["r"] = vtr.rotation_angle
563
+ group_data["transform"] = vtr.transform.copy()
564
+ except Exception:
565
+ group_data["wh"] = (1.0, 1.0)
566
+ group_data["r"] = 0.0
567
+
568
+ state.record_group(grob.name, group_data)
569
+
570
+ if ref is None:
571
+ warnings.warn("Group definition failed")
572
+ return
573
+
574
+ # Use immediately for GroupGrob (R group.R:269)
575
+ if use_immediately:
576
+ renderer.use_group(ref, None)
577
+
578
+
579
+ # ============================================================================
580
+ # Factory functions
581
+ # ============================================================================
582
+
583
+
584
+ def group_grob(
585
+ src: Optional[Grob] = None,
586
+ op: str = "over",
587
+ dst: Optional[Grob] = None,
588
+ name: Optional[str] = None,
589
+ gp: Optional[Gpar] = None,
590
+ vp: Optional[Any] = None,
591
+ ) -> GroupGrob:
592
+ """Create a :class:`GroupGrob`.
593
+
594
+ This is the functional equivalent of R's ``groupGrob()``.
595
+
596
+ Parameters
597
+ ----------
598
+ src : Grob or None
599
+ Source grob.
600
+ op : str
601
+ Compositing operator (default ``"over"``).
602
+ dst : Grob or None
603
+ Destination grob.
604
+ name : str or None
605
+ Grob name.
606
+ gp : Gpar or None
607
+ Graphical parameters.
608
+ vp : object or None
609
+ Viewport.
610
+
611
+ Returns
612
+ -------
613
+ GroupGrob
614
+ """
615
+ return GroupGrob(src=src, op=op, dst=dst, name=name, gp=gp, vp=vp)
616
+
617
+
618
+ def grid_group(
619
+ src: Optional[Grob] = None,
620
+ op: str = "over",
621
+ dst: Optional[Grob] = None,
622
+ name: Optional[str] = None,
623
+ gp: Optional[Gpar] = None,
624
+ vp: Optional[Any] = None,
625
+ draw: bool = True,
626
+ ) -> GroupGrob:
627
+ """Create and optionally draw a :class:`GroupGrob`.
628
+
629
+ This is the functional equivalent of R's ``grid.group()``.
630
+
631
+ Parameters
632
+ ----------
633
+ src : Grob or None
634
+ Source grob.
635
+ op : str
636
+ Compositing operator (default ``"over"``).
637
+ dst : Grob or None
638
+ Destination grob.
639
+ name : str or None
640
+ Grob name.
641
+ gp : Gpar or None
642
+ Graphical parameters.
643
+ vp : object or None
644
+ Viewport.
645
+ draw : bool
646
+ If ``True`` (default), the grob is drawn immediately via
647
+ ``draw_details``.
648
+
649
+ Returns
650
+ -------
651
+ GroupGrob
652
+ """
653
+ grb = GroupGrob(src=src, op=op, dst=dst, name=name, gp=gp, vp=vp)
654
+ if draw:
655
+ grb.draw_details()
656
+ return grb
657
+
658
+
659
+ def define_grob(
660
+ src: Grob,
661
+ op: str = "over",
662
+ dst: Optional[Grob] = None,
663
+ name: Optional[str] = None,
664
+ gp: Optional[Gpar] = None,
665
+ vp: Optional[Any] = None,
666
+ ) -> DefineGrob:
667
+ """Create a :class:`DefineGrob`.
668
+
669
+ This is the functional equivalent of R's ``defineGrob()``.
670
+
671
+ Parameters
672
+ ----------
673
+ src : Grob
674
+ Source grob.
675
+ op : str
676
+ Compositing operator (default ``"over"``).
677
+ dst : Grob or None
678
+ Destination grob.
679
+ name : str or None
680
+ Grob name.
681
+ gp : Gpar or None
682
+ Graphical parameters.
683
+ vp : object or None
684
+ Viewport.
685
+
686
+ Returns
687
+ -------
688
+ DefineGrob
689
+ """
690
+ return DefineGrob(src=src, op=op, dst=dst, name=name, gp=gp, vp=vp)
691
+
692
+
693
+ def grid_define(
694
+ src: Grob,
695
+ op: str = "over",
696
+ dst: Optional[Grob] = None,
697
+ name: Optional[str] = None,
698
+ gp: Optional[Gpar] = None,
699
+ vp: Optional[Any] = None,
700
+ draw: bool = True,
701
+ ) -> DefineGrob:
702
+ """Create and optionally draw a :class:`DefineGrob`.
703
+
704
+ This is the functional equivalent of R's ``grid.define()``.
705
+
706
+ Parameters
707
+ ----------
708
+ src : Grob
709
+ Source grob.
710
+ op : str
711
+ Compositing operator (default ``"over"``).
712
+ dst : Grob or None
713
+ Destination grob.
714
+ name : str or None
715
+ Grob name.
716
+ gp : Gpar or None
717
+ Graphical parameters.
718
+ vp : object or None
719
+ Viewport.
720
+ draw : bool
721
+ If ``True`` (default), the grob is drawn (defined) immediately.
722
+
723
+ Returns
724
+ -------
725
+ DefineGrob
726
+ """
727
+ grb = DefineGrob(src=src, op=op, dst=dst, name=name, gp=gp, vp=vp)
728
+ if draw:
729
+ grb.draw_details()
730
+ return grb
731
+
732
+
733
+ def use_grob(
734
+ group: str,
735
+ transform: Optional[NDArray[np.float64]] = None,
736
+ name: Optional[str] = None,
737
+ gp: Optional[Gpar] = None,
738
+ vp: Optional[Any] = None,
739
+ ) -> UseGrob:
740
+ """Create a :class:`UseGrob`.
741
+
742
+ This is the functional equivalent of R's ``useGrob()``.
743
+
744
+ Parameters
745
+ ----------
746
+ group : str
747
+ Name of the previously defined group.
748
+ transform : ndarray or None
749
+ 3x3 affine transformation matrix.
750
+ name : str or None
751
+ Grob name.
752
+ gp : Gpar or None
753
+ Graphical parameters.
754
+ vp : object or None
755
+ Viewport.
756
+
757
+ Returns
758
+ -------
759
+ UseGrob
760
+ """
761
+ return UseGrob(group=group, transform=transform, name=name, gp=gp, vp=vp)
762
+
763
+
764
+ def grid_use(
765
+ group: str,
766
+ transform: Optional[NDArray[np.float64]] = None,
767
+ name: Optional[str] = None,
768
+ gp: Optional[Gpar] = None,
769
+ vp: Optional[Any] = None,
770
+ draw: bool = True,
771
+ ) -> UseGrob:
772
+ """Create and optionally draw a :class:`UseGrob`.
773
+
774
+ This is the functional equivalent of R's ``grid.use()``.
775
+
776
+ Parameters
777
+ ----------
778
+ group : str
779
+ Name of the previously defined group.
780
+ transform : ndarray or None
781
+ 3x3 affine transformation matrix.
782
+ name : str or None
783
+ Grob name.
784
+ gp : Gpar or None
785
+ Graphical parameters.
786
+ vp : object or None
787
+ Viewport.
788
+ draw : bool
789
+ If ``True`` (default), the grob is drawn immediately.
790
+
791
+ Returns
792
+ -------
793
+ UseGrob
794
+ """
795
+ grb = UseGrob(group=group, transform=transform, name=name, gp=gp, vp=vp)
796
+ if draw:
797
+ grb.draw_details()
798
+ return grb