rgrid-python 4.5.3.post1__py3-none-any.whl → 4.5.3.post3__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 +3 -3
- grid_py/_curve.py +22 -1
- grid_py/_draw.py +58 -15
- grid_py/_gpar.py +12 -1
- grid_py/_layout.py +9 -2
- grid_py/_renderer_base.py +127 -11
- grid_py/_size.py +36 -10
- grid_py/_state.py +42 -0
- grid_py/_units.py +14 -1
- grid_py/renderer.py +42 -20
- {rgrid_python-4.5.3.post1.dist-info → rgrid_python-4.5.3.post3.dist-info}/METADATA +1 -1
- {rgrid_python-4.5.3.post1.dist-info → rgrid_python-4.5.3.post3.dist-info}/RECORD +14 -14
- {rgrid_python-4.5.3.post1.dist-info → rgrid_python-4.5.3.post3.dist-info}/WHEEL +0 -0
- {rgrid_python-4.5.3.post1.dist-info → rgrid_python-4.5.3.post3.dist-info}/licenses/LICENSE +0 -0
grid_py/__init__.py
CHANGED
|
@@ -6,7 +6,7 @@ units, viewports, grobs (graphical objects), layouts, and rendering via
|
|
|
6
6
|
Cairo (pycairo).
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
__version__ = "4.5.3.
|
|
9
|
+
__version__ = "4.5.3.post3"
|
|
10
10
|
|
|
11
11
|
# --- Utilities ---
|
|
12
12
|
from grid_py._utils import depth, explode, grid_pretty, n2mfrow
|
|
@@ -25,7 +25,7 @@ from grid_py._units import (
|
|
|
25
25
|
)
|
|
26
26
|
|
|
27
27
|
# --- Graphical Parameters ---
|
|
28
|
-
from grid_py._gpar import Gpar, get_gpar
|
|
28
|
+
from grid_py._gpar import Gpar, get_gpar, gpar
|
|
29
29
|
|
|
30
30
|
# --- Arrow ---
|
|
31
31
|
from grid_py._arrow import Arrow, arrow
|
|
@@ -232,7 +232,7 @@ __all__ = [
|
|
|
232
232
|
"absolute_size",
|
|
233
233
|
"convert_unit", "convert_x", "convert_y", "convert_width", "convert_height",
|
|
234
234
|
# Gpar
|
|
235
|
-
"Gpar", "get_gpar",
|
|
235
|
+
"Gpar", "gpar", "get_gpar",
|
|
236
236
|
# Arrow
|
|
237
237
|
"Arrow", "arrow",
|
|
238
238
|
# Path
|
grid_py/_curve.py
CHANGED
|
@@ -1383,6 +1383,8 @@ def grid_curve(
|
|
|
1383
1383
|
def xspline_grob(
|
|
1384
1384
|
x: Optional[Any] = None,
|
|
1385
1385
|
y: Optional[Any] = None,
|
|
1386
|
+
id: Optional[Any] = None,
|
|
1387
|
+
id_lengths: Optional[Any] = None,
|
|
1386
1388
|
default_units: str = "npc",
|
|
1387
1389
|
shape: Union[float, Sequence[float]] = 0.0,
|
|
1388
1390
|
open_: bool = True,
|
|
@@ -1402,6 +1404,15 @@ def xspline_grob(
|
|
|
1402
1404
|
x, y : Unit, numeric, sequence, or None
|
|
1403
1405
|
Control-point coordinates. Defaults to ``Unit([0, 1], "npc")``
|
|
1404
1406
|
when ``None``.
|
|
1407
|
+
id : array-like of int or None
|
|
1408
|
+
Group label for each control point. Points sharing an ``id`` are
|
|
1409
|
+
rendered as one X-spline; the grob therefore renders one spline
|
|
1410
|
+
per unique ``id`` value. Mirrors R ``xsplineGrob(id=...)``.
|
|
1411
|
+
Mutually meaningful with ``id_lengths``: pass at most one.
|
|
1412
|
+
id_lengths : array-like of int or None
|
|
1413
|
+
Run-length encoding of ``id``: the n-th entry is the number of
|
|
1414
|
+
consecutive control points belonging to spline n. Mirrors R's
|
|
1415
|
+
``xsplineGrob(id.lengths=...)``.
|
|
1405
1416
|
default_units : str
|
|
1406
1417
|
Unit type for bare numerics.
|
|
1407
1418
|
shape : float or sequence of float
|
|
@@ -1439,9 +1450,16 @@ def xspline_grob(
|
|
|
1439
1450
|
if np.any((shape_arr < -1) | (shape_arr > 1)):
|
|
1440
1451
|
raise ValueError("all 'shape' values must be between -1 and 1")
|
|
1441
1452
|
|
|
1453
|
+
id_arr = None if id is None else np.asarray(id, dtype=np.int64)
|
|
1454
|
+
id_lengths_arr = (
|
|
1455
|
+
None if id_lengths is None else np.asarray(id_lengths, dtype=np.int64)
|
|
1456
|
+
)
|
|
1457
|
+
|
|
1442
1458
|
return Grob(
|
|
1443
1459
|
x=x,
|
|
1444
1460
|
y=y,
|
|
1461
|
+
id=id_arr,
|
|
1462
|
+
id_lengths=id_lengths_arr,
|
|
1445
1463
|
shape=shape_arr,
|
|
1446
1464
|
open_=bool(open_),
|
|
1447
1465
|
arrow=arrow,
|
|
@@ -1456,6 +1474,8 @@ def xspline_grob(
|
|
|
1456
1474
|
def grid_xspline(
|
|
1457
1475
|
x: Optional[Any] = None,
|
|
1458
1476
|
y: Optional[Any] = None,
|
|
1477
|
+
id: Optional[Any] = None,
|
|
1478
|
+
id_lengths: Optional[Any] = None,
|
|
1459
1479
|
default_units: str = "npc",
|
|
1460
1480
|
shape: Union[float, Sequence[float]] = 0.0,
|
|
1461
1481
|
open_: bool = True,
|
|
@@ -1497,7 +1517,8 @@ def grid_xspline(
|
|
|
1497
1517
|
The xspline grob.
|
|
1498
1518
|
"""
|
|
1499
1519
|
grob = xspline_grob(
|
|
1500
|
-
x=x, y=y,
|
|
1520
|
+
x=x, y=y, id=id, id_lengths=id_lengths,
|
|
1521
|
+
default_units=default_units,
|
|
1501
1522
|
shape=shape, open_=open_, arrow=arrow,
|
|
1502
1523
|
repEnds=repEnds, name=name, gp=gp, vp=vp,
|
|
1503
1524
|
)
|
grid_py/_draw.py
CHANGED
|
@@ -412,7 +412,15 @@ def _render_grob(
|
|
|
412
412
|
xs, ys = _calc_xspline_points(
|
|
413
413
|
x, y, shape=shape_arr, open_=open_, repEnds=rep_ends,
|
|
414
414
|
)
|
|
415
|
-
|
|
415
|
+
# R's ``xsplineGrob(open=FALSE)`` is a *filled closed* shape, not
|
|
416
|
+
# a stroked path; route through ``draw_polygon`` so ``gp$fill``
|
|
417
|
+
# actually paints (R/grid: drawDetails.xspline → C_xspline,
|
|
418
|
+
# filled when ``open == FALSE``). ``open=TRUE`` stays a stroked
|
|
419
|
+
# polyline.
|
|
420
|
+
if open_:
|
|
421
|
+
renderer.draw_polyline(xs, ys, id_=None, gp=gp)
|
|
422
|
+
else:
|
|
423
|
+
renderer.draw_polygon(xs, ys, gp=gp)
|
|
416
424
|
if arr is not None and len(xs) >= 2:
|
|
417
425
|
_draw_arrow_heads(xs, ys, arr, renderer, gp)
|
|
418
426
|
else:
|
|
@@ -436,12 +444,24 @@ def _render_grob(
|
|
|
436
444
|
out_id += 1
|
|
437
445
|
per_group.append((xs_g, ys_g))
|
|
438
446
|
if all_xs:
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
447
|
+
# Multi-id closed splines render as N separate filled
|
|
448
|
+
# subpolygons (R: xsplineGrob(id=..., open=FALSE)); use
|
|
449
|
+
# ``draw_path`` with the path_id array. Open splines stay
|
|
450
|
+
# multi-stroke polylines.
|
|
451
|
+
if open_:
|
|
452
|
+
renderer.draw_polyline(
|
|
453
|
+
np.asarray(all_xs, dtype=float),
|
|
454
|
+
np.asarray(all_ys, dtype=float),
|
|
455
|
+
id_=np.asarray(all_ids, dtype=int),
|
|
456
|
+
gp=gp,
|
|
457
|
+
)
|
|
458
|
+
else:
|
|
459
|
+
renderer.draw_path(
|
|
460
|
+
np.asarray(all_xs, dtype=float),
|
|
461
|
+
np.asarray(all_ys, dtype=float),
|
|
462
|
+
path_id=np.asarray(all_ids, dtype=int),
|
|
463
|
+
gp=gp,
|
|
464
|
+
)
|
|
445
465
|
if arr is not None:
|
|
446
466
|
for xs_g, ys_g in per_group:
|
|
447
467
|
if len(xs_g) >= 2:
|
|
@@ -564,15 +584,18 @@ def _render_grob(
|
|
|
564
584
|
if image is None:
|
|
565
585
|
image = getattr(grob, "image", None)
|
|
566
586
|
if image is not None:
|
|
567
|
-
# Apply justification (same as rect_grob)
|
|
568
587
|
hj, vj = _resolve_just(grob)
|
|
569
588
|
raw_x = renderer.resolve_x(getattr(grob, "x", 0.0), gp=gp)
|
|
570
589
|
raw_y = renderer.resolve_y(getattr(grob, "y", 0.0), gp=gp)
|
|
571
590
|
raw_w = renderer.resolve_w(getattr(grob, "width", 1.0), gp=gp)
|
|
572
591
|
raw_h = renderer.resolve_h(getattr(grob, "height", 1.0), gp=gp)
|
|
573
|
-
#
|
|
592
|
+
# `renderer.draw_raster` expects the *top-left* corner in y-down
|
|
593
|
+
# device pixels. `resolve_y` already returns y-down coords, so the
|
|
594
|
+
# y component of justification must be flipped relative to R's
|
|
595
|
+
# `justifyY(y, h, vjust) = y - h*vjust` (which assumes y-up NPC).
|
|
596
|
+
# Same correction `draw_rect` applies (renderer.py:815).
|
|
574
597
|
x0 = raw_x - raw_w * hj
|
|
575
|
-
y0 = raw_y - raw_h * vj
|
|
598
|
+
y0 = raw_y - raw_h * (1.0 - vj)
|
|
576
599
|
renderer.draw_raster(
|
|
577
600
|
image=image,
|
|
578
601
|
x=x0,
|
|
@@ -690,24 +713,44 @@ def _pop_grob_vp(vp: Any) -> None:
|
|
|
690
713
|
def _vp_depth(vp: Any) -> int:
|
|
691
714
|
"""Return the depth of a viewport (number of levels it adds).
|
|
692
715
|
|
|
716
|
+
Mirrors R's ``depth()`` generic (grid R/grid.R) for every viewport
|
|
717
|
+
container: VpStack pushes ``sum(depth(child))`` levels, VpList
|
|
718
|
+
pushes ``depth(last)``, VpTree pushes ``depth(parent) + depth(last
|
|
719
|
+
child)``, plain Viewport pushes 1, VpPath pushes ``n``.
|
|
720
|
+
|
|
721
|
+
Recognised types are routed through :func:`._viewport.depth` (the
|
|
722
|
+
canonical R-faithful dispatch); this is what guarantees
|
|
723
|
+
``_pop_grob_vp(grob.vp)`` pops the right count when a Gtable's
|
|
724
|
+
``make_context`` returns ``VpStack(orig_vp, layout_vp)``. The
|
|
725
|
+
prior implementation only checked ``hasattr(vp, "depth")`` —
|
|
726
|
+
VpStack/VpList don't expose a method, so it fell through to
|
|
727
|
+
``return 1`` and left every nested layout vp on the layout_stack
|
|
728
|
+
after the gtable rendered, breaking every grob drawn after.
|
|
729
|
+
|
|
730
|
+
Duck-typed objects with a ``.depth()`` method (used by tests and
|
|
731
|
+
user-defined viewport-like objects) are still honoured as a
|
|
732
|
+
fallback for unknown types.
|
|
733
|
+
|
|
693
734
|
Parameters
|
|
694
735
|
----------
|
|
695
736
|
vp : Any
|
|
696
|
-
A viewport, VpPath, VpStack, VpList, or
|
|
737
|
+
A viewport, VpPath, VpStack, VpList, VpTree, or any
|
|
738
|
+
depth-bearing duck-typed object.
|
|
697
739
|
|
|
698
740
|
Returns
|
|
699
741
|
-------
|
|
700
742
|
int
|
|
701
743
|
The depth.
|
|
702
744
|
"""
|
|
745
|
+
from ._viewport import (
|
|
746
|
+
Viewport, VpList, VpStack, VpTree, depth as _depth,
|
|
747
|
+
)
|
|
703
748
|
from ._path import VpPath
|
|
704
749
|
|
|
705
|
-
if isinstance(vp, VpPath):
|
|
706
|
-
|
|
707
|
-
return getattr(vp, "n", 1)
|
|
750
|
+
if isinstance(vp, (Viewport, VpList, VpStack, VpTree, VpPath)):
|
|
751
|
+
return _depth(vp)
|
|
708
752
|
if hasattr(vp, "depth"):
|
|
709
753
|
return vp.depth()
|
|
710
|
-
# Default single viewport depth
|
|
711
754
|
return 1
|
|
712
755
|
|
|
713
756
|
|
grid_py/_gpar.py
CHANGED
|
@@ -14,7 +14,7 @@ from typing import Any, Dict, List, Optional, Sequence, Union
|
|
|
14
14
|
|
|
15
15
|
import numpy as np
|
|
16
16
|
|
|
17
|
-
__all__ = ["Gpar", "get_gpar"]
|
|
17
|
+
__all__ = ["Gpar", "gpar", "get_gpar"]
|
|
18
18
|
|
|
19
19
|
# ---------------------------------------------------------------------------
|
|
20
20
|
# Constants
|
|
@@ -586,3 +586,14 @@ def get_gpar(names: Optional[Sequence[str]] = None) -> Gpar:
|
|
|
586
586
|
gp = object.__new__(Gpar)
|
|
587
587
|
gp._params = subset
|
|
588
588
|
return gp
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
def gpar(**kwargs: Any) -> Gpar:
|
|
592
|
+
"""Factory mirroring R ``grid::gpar(...)``.
|
|
593
|
+
|
|
594
|
+
R's ``gpar`` function constructs a ``gpar`` object from arbitrary
|
|
595
|
+
keyword arguments. ``Gpar(**kwargs)`` does the same, so this is a
|
|
596
|
+
thin alias kept for direct R-to-Python translation of code that
|
|
597
|
+
reads ``gpar(col="red")``.
|
|
598
|
+
"""
|
|
599
|
+
return Gpar(**kwargs)
|
grid_py/_layout.py
CHANGED
|
@@ -400,16 +400,23 @@ def _calc_layout_sizes(
|
|
|
400
400
|
reduced_h = max(reduced_h, 0.0)
|
|
401
401
|
|
|
402
402
|
# ---- Phase 2: allocate respected null units (layout.c:allocateRespected)
|
|
403
|
+
# R sums ALL relative widths/heights via totalWidth/totalHeight
|
|
404
|
+
# (layout.c:154-194) — not just the respected ones — when computing
|
|
405
|
+
# the aspect-ratio normalisation denominator. This is what lets a
|
|
406
|
+
# respect matrix that marks a single cell coexist with other null
|
|
407
|
+
# cells in the same dimension; restricting the sum to respected
|
|
408
|
+
# cells (the prior Python implementation) over-allocated the
|
|
409
|
+
# respected slot and collapsed everything else to 0.
|
|
403
410
|
if layout._valid_respect > 0 and (reduced_w > 0 or reduced_h > 0):
|
|
404
411
|
sum_w = sum(
|
|
405
412
|
float(widths._values[i])
|
|
406
413
|
for i in range(ncol)
|
|
407
|
-
if relative_w[i]
|
|
414
|
+
if relative_w[i]
|
|
408
415
|
)
|
|
409
416
|
sum_h = sum(
|
|
410
417
|
float(heights._values[j])
|
|
411
418
|
for j in range(nrow)
|
|
412
|
-
if relative_h[j]
|
|
419
|
+
if relative_h[j]
|
|
413
420
|
)
|
|
414
421
|
|
|
415
422
|
temp_w = reduced_w
|
grid_py/_renderer_base.py
CHANGED
|
@@ -249,9 +249,30 @@ class GridRenderer(ABC):
|
|
|
249
249
|
# Cell position in parent's NPC then inches
|
|
250
250
|
parent_w_dev = parent_vtr.width_cm / 2.54 * self._dev_units_per_inch
|
|
251
251
|
parent_h_dev = parent_vtr.height_cm / 2.54 * self._dev_units_per_inch
|
|
252
|
-
|
|
253
|
-
#
|
|
254
|
-
|
|
252
|
+
|
|
253
|
+
# Apply layout-level hjust / vjust (R: layout.c::subRegion
|
|
254
|
+
# lines 447-450). When the cells' total dimensions don't
|
|
255
|
+
# fill the parent vp (common for chrome-merge slices in
|
|
256
|
+
# patchwork), the leftover space is distributed per the
|
|
257
|
+
# layout's justification (default 0.5 → centred). The
|
|
258
|
+
# prior implementation always pinned cells to top-left
|
|
259
|
+
# (vjust=1, hjust=0), which placed merged chrome at the
|
|
260
|
+
# top of the parent panel cell rather than just outside
|
|
261
|
+
# it, breaking simplify_fixed renders.
|
|
262
|
+
hjust = float(grid.get("hjust", 0.0))
|
|
263
|
+
vjust = float(grid.get("vjust", 1.0))
|
|
264
|
+
total_w_dev = sum(col_widths)
|
|
265
|
+
total_h_dev = sum(row_heights)
|
|
266
|
+
hjust_offset_dev = hjust * (parent_w_dev - total_w_dev)
|
|
267
|
+
vjust_offset_dev = (1.0 - vjust) * (parent_h_dev - total_h_dev)
|
|
268
|
+
|
|
269
|
+
cell_x_in = (cell_x0_dev + hjust_offset_dev) / self._dev_units_per_inch
|
|
270
|
+
# Device y is top-down; convert to bottom-up inches.
|
|
271
|
+
# vjust_offset_dev shifts the block downward in device-y
|
|
272
|
+
# (i.e. upward in bottom-up coords) by the leftover * (1-vjust).
|
|
273
|
+
cell_y_in = parent_h_in - (
|
|
274
|
+
cell_y0_dev + cell_h_dev + vjust_offset_dev
|
|
275
|
+
) / self._dev_units_per_inch
|
|
255
276
|
|
|
256
277
|
# Build a simple translation transform for the cell
|
|
257
278
|
from ._vp_calc import translation, multiply
|
|
@@ -280,9 +301,12 @@ class GridRenderer(ABC):
|
|
|
280
301
|
if layout is not None:
|
|
281
302
|
w_dev = cell_w_dev
|
|
282
303
|
h_dev = cell_h_dev
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
304
|
+
# R: ``calcViewportLayout`` (layout.c:492-590) reads
|
|
305
|
+
# ``layoutRespect(layout)`` / ``layoutRespectMat(layout)``
|
|
306
|
+
# straight off the layout object — there is no caller-side
|
|
307
|
+
# respect argument. Mirror that: ``_calc_layout_sizes``
|
|
308
|
+
# consumes ``layout._valid_respect`` directly.
|
|
309
|
+
grid_info = self._compute_grid(layout, w_dev, h_dev)
|
|
286
310
|
self._layout_stack.append(grid_info)
|
|
287
311
|
self._layout_depth_stack.append(
|
|
288
312
|
len(self._vp_transform_stack))
|
|
@@ -294,8 +318,9 @@ class GridRenderer(ABC):
|
|
|
294
318
|
# Compute grid in device units for layout children.
|
|
295
319
|
w_dev = parent_vtr.width_cm / 2.54 * self._dev_units_per_inch
|
|
296
320
|
h_dev = parent_vtr.height_cm / 2.54 * self._dev_units_per_inch
|
|
297
|
-
|
|
298
|
-
|
|
321
|
+
# See R layout.c:492-590 — respect lives on the layout object;
|
|
322
|
+
# callers don't pass it down.
|
|
323
|
+
grid_info = self._compute_grid(layout, w_dev, h_dev)
|
|
299
324
|
|
|
300
325
|
# The layout viewport itself has the same transform as parent
|
|
301
326
|
# but we create a new VTR with the vp's xscale/yscale
|
|
@@ -388,14 +413,27 @@ class GridRenderer(ABC):
|
|
|
388
413
|
|
|
389
414
|
def _compute_grid(
|
|
390
415
|
self, layout: Any, parent_w: float, parent_h: float,
|
|
391
|
-
respect: bool = False,
|
|
392
416
|
) -> dict:
|
|
393
|
-
"""Compute row/column positions for a GridLayout within the parent.
|
|
417
|
+
"""Compute row/column positions for a GridLayout within the parent.
|
|
418
|
+
|
|
419
|
+
R reference: ``layout.c:calcViewportLayout`` (lines 492-590).
|
|
420
|
+
Respect (full or matrix-form) lives on the layout object itself —
|
|
421
|
+
``_calc_layout_sizes`` reads ``layout._valid_respect`` directly,
|
|
422
|
+
so this signature has no ``respect`` argument (matches R, where
|
|
423
|
+
there is no caller-side respect parameter either).
|
|
424
|
+
"""
|
|
394
425
|
from ._layout import _calc_layout_sizes, GridLayout
|
|
395
426
|
|
|
396
427
|
if isinstance(layout, GridLayout):
|
|
428
|
+
# Use device-units-per-inch (= dpi for raster ImageSurface, 72 for
|
|
429
|
+
# vector PDF/SVG/PS surfaces) so that absolute units (cm/mm/in/pt
|
|
430
|
+
# …) get converted to the SAME unit as ``parent_w``/``parent_h``.
|
|
431
|
+
# Passing ``self.dpi`` blindly would, for vector surfaces, scale
|
|
432
|
+
# absolute widths by dpi while the parent is measured in points,
|
|
433
|
+
# over-allocating fixed cells by ``dpi/72`` and shrinking the
|
|
434
|
+
# null-unit panel by the same factor.
|
|
397
435
|
col_widths, row_heights = _calc_layout_sizes(
|
|
398
|
-
layout, parent_w, parent_h, self.
|
|
436
|
+
layout, parent_w, parent_h, self._dev_units_per_inch,
|
|
399
437
|
)
|
|
400
438
|
else:
|
|
401
439
|
nrow = getattr(layout, "nrow", 1)
|
|
@@ -412,9 +450,24 @@ class GridRenderer(ABC):
|
|
|
412
450
|
col_starts = [sum(col_widths[:i]) for i in range(ncol)]
|
|
413
451
|
row_starts = [sum(row_heights[:i]) for i in range(nrow)]
|
|
414
452
|
|
|
453
|
+
# Layout justification (R: layout.c::subRegion lines 433-450).
|
|
454
|
+
# When the cells' total size is less than the parent vp, hjust /
|
|
455
|
+
# vjust shift the layout block within the parent (default 0.5 →
|
|
456
|
+
# centred). ``GridLayout`` carries this on ``_valid_just`` as
|
|
457
|
+
# (hjust, vjust); fall back to (0, 1) for non-GridLayout
|
|
458
|
+
# containers (matching the prior top-left alignment).
|
|
459
|
+
valid_just = getattr(layout, "_valid_just", None)
|
|
460
|
+
if valid_just is not None:
|
|
461
|
+
hjust = float(valid_just[0])
|
|
462
|
+
vjust = float(valid_just[1])
|
|
463
|
+
else:
|
|
464
|
+
hjust, vjust = 0.0, 1.0
|
|
465
|
+
|
|
415
466
|
return {
|
|
416
467
|
"col_starts": col_starts, "col_widths": col_widths,
|
|
417
468
|
"row_starts": row_starts, "row_heights": row_heights,
|
|
469
|
+
"parent_w": parent_w, "parent_h": parent_h,
|
|
470
|
+
"hjust": hjust, "vjust": vjust,
|
|
418
471
|
}
|
|
419
472
|
|
|
420
473
|
def _resolve_sizes(self, unit_obj: Any, n: int, total: float,
|
|
@@ -492,6 +545,28 @@ class GridRenderer(ABC):
|
|
|
492
545
|
# evaluateGrobUnit -- port of R unit.c:325-590 #
|
|
493
546
|
# ===================================================================== #
|
|
494
547
|
|
|
548
|
+
# Per-renderer cycle guard — populated with id(grob) for every grob
|
|
549
|
+
# currently being measured. ``_evaluate_grob_unit`` consults this
|
|
550
|
+
# before pushing the grob's vp; re-entry for the same grob means
|
|
551
|
+
# the vp itself references its own grob*Details (e.g.
|
|
552
|
+
# ``viewport(width = grobWidth(self))``), which would otherwise
|
|
553
|
+
# infinite-recurse through calcViewportTransform → grob_metric_fn
|
|
554
|
+
# → _evaluate_grob_unit → push vp → ...
|
|
555
|
+
#
|
|
556
|
+
# R bounds the same recursion implicitly because its
|
|
557
|
+
# widthDetails.gtable returns an absolute mm sum, and R's
|
|
558
|
+
# setviewport/calcViewportTransform doesn't re-trigger the metric
|
|
559
|
+
# path during the recursive pushViewport (verified empirically:
|
|
560
|
+
# preDraw.gtable runs exactly 3 times for one toplevel grid.draw —
|
|
561
|
+
# not unbounded). We mirror the bound explicitly.
|
|
562
|
+
@property
|
|
563
|
+
def _measuring_grobs(self) -> set:
|
|
564
|
+
s = getattr(self, "_measuring_grobs_set", None)
|
|
565
|
+
if s is None:
|
|
566
|
+
s = set()
|
|
567
|
+
self._measuring_grobs_set = s
|
|
568
|
+
return s
|
|
569
|
+
|
|
495
570
|
def _evaluate_grob_unit(
|
|
496
571
|
self,
|
|
497
572
|
grob: Any,
|
|
@@ -545,6 +620,46 @@ class GridRenderer(ABC):
|
|
|
545
620
|
if not isinstance(grob, Grob):
|
|
546
621
|
return 0.0
|
|
547
622
|
|
|
623
|
+
# --- Cycle break for self-referential grobwidth/grobheight ---
|
|
624
|
+
# When we're already measuring this grob and the grob's vp
|
|
625
|
+
# references its own width/height (e.g. patchwork's
|
|
626
|
+
# ``as_patch.GT`` builds ``viewport(width=grobWidth(grob))``),
|
|
627
|
+
# the recursive vp push would trigger _evaluate_grob_unit for
|
|
628
|
+
# the same grob again, etc. Resolve widthDetails/heightDetails
|
|
629
|
+
# directly without the recursive push — for non-context-
|
|
630
|
+
# dependent grobs (gtable widthDetails returns absolute_size of
|
|
631
|
+
# the widths sum) this gives the same answer as the full path,
|
|
632
|
+
# matching R's behaviour where preDraw.gtable runs a bounded
|
|
633
|
+
# number of times rather than unboundedly.
|
|
634
|
+
if id(grob) in self._measuring_grobs:
|
|
635
|
+
if unit_type == "grobwidth":
|
|
636
|
+
ru = width_details(grob)
|
|
637
|
+
axis = "x"
|
|
638
|
+
elif unit_type == "grobheight":
|
|
639
|
+
ru = height_details(grob)
|
|
640
|
+
axis = "y"
|
|
641
|
+
elif unit_type == "grobascent":
|
|
642
|
+
ru = ascent_details(grob)
|
|
643
|
+
axis = "y"
|
|
644
|
+
elif unit_type == "grobdescent":
|
|
645
|
+
ru = descent_details(grob)
|
|
646
|
+
axis = "y"
|
|
647
|
+
else:
|
|
648
|
+
return 0.0
|
|
649
|
+
if ru is None:
|
|
650
|
+
return 0.0
|
|
651
|
+
from ._units import Unit as _U
|
|
652
|
+
if not isinstance(ru, _U):
|
|
653
|
+
return 0.0
|
|
654
|
+
if len(ru) == 1 and ru._units[0] == "null":
|
|
655
|
+
return 0.0
|
|
656
|
+
return float(self._resolve_to_inches(ru, axis, True))
|
|
657
|
+
|
|
658
|
+
# Capture id BEFORE make_context() rebinds ``grob`` to the
|
|
659
|
+
# context-wrapped copy — we need to release the same id we added.
|
|
660
|
+
_measuring_id = id(grob)
|
|
661
|
+
self._measuring_grobs.add(_measuring_id)
|
|
662
|
+
|
|
548
663
|
# --- Save state (R unit.c:355-377) ---
|
|
549
664
|
saved_dl_on = state._dl_on
|
|
550
665
|
state.set_display_list_on(False)
|
|
@@ -620,6 +735,7 @@ class GridRenderer(ABC):
|
|
|
620
735
|
state.replace_gpar(saved_gpar)
|
|
621
736
|
state._current_grob = saved_current_grob
|
|
622
737
|
state.set_display_list_on(saved_dl_on)
|
|
738
|
+
self._measuring_grobs.discard(_measuring_id)
|
|
623
739
|
|
|
624
740
|
return result
|
|
625
741
|
|
grid_py/_size.py
CHANGED
|
@@ -232,13 +232,36 @@ def _resolve_grob_gp(grob: Any) -> "Optional[Gpar]":
|
|
|
232
232
|
def _text_bbox(grob: Any) -> tuple:
|
|
233
233
|
"""Compute (width, height) of the text bounding box in inches.
|
|
234
234
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
235
|
+
Ports R's ``heightDetails.text`` / ``widthDetails.text`` (grid's
|
|
236
|
+
``primitives.R:1430-1452``) — R's ``grobHeight`` on a text grob
|
|
237
|
+
returns the **ascent only** (glyph extent above baseline), never
|
|
238
|
+
the descent. ``grobDescent`` is a separate method (see
|
|
239
|
+
``descentDetails.text``) and is exposed independently so that
|
|
240
|
+
callers can add it when needed. ggplot2's ``titleGrob``
|
|
241
|
+
(margins.R:115-132) relies on this convention — it uses
|
|
242
|
+
``unit(1, "grobheight", grob) + y_descent`` to assemble the final
|
|
243
|
+
height — so any deviation here double-counts the descent.
|
|
244
|
+
|
|
245
|
+
Empirical verification (R 4.4 ``cairo_png`` at 150 dpi, default
|
|
246
|
+
Helvetica, fontsize 13.2 pt):
|
|
247
|
+
|
|
248
|
+
grobHeight(textGrob("A long title", fs=13.2)) = 3.293 mm
|
|
249
|
+
grobHeight(textGrob("gjpqy", fs=13.2)) = 3.293 mm
|
|
250
|
+
grobHeight(textGrob("y", fs=13.2)) = 3.293 mm
|
|
251
|
+
|
|
252
|
+
— i.e. the value is a font-level constant, independent of the
|
|
253
|
+
label content. Our cairo-backed ``calc_string_metric`` returns a
|
|
254
|
+
per-label ascent that varies slightly with glyph mix, which is the
|
|
255
|
+
closest we can get without implementing a full AFM font-metric
|
|
256
|
+
path; residual ≤ 0.3 mm discrepancy is a font-file difference
|
|
257
|
+
(AFM Helvetica vs. cairo's "Sans" fallback) and outside the
|
|
258
|
+
scope of this bbox function.
|
|
259
|
+
|
|
260
|
+
Height formula (port of R's ``GEStrHeight``):
|
|
261
|
+
|
|
262
|
+
width = max(per-line ink widths)
|
|
263
|
+
height = ascent(first line)
|
|
240
264
|
+ (n - 1) × cex × lineheight × fontsize × 1.2 / 72
|
|
241
|
-
— R's ``GEStrHeight``
|
|
242
265
|
|
|
243
266
|
The per-extra-line gap ``1.2 × fontsize / 72`` is R's device-level
|
|
244
267
|
``cra[1] × ipr[1] / default_ps`` collapsed for the standard cairo /
|
|
@@ -287,11 +310,14 @@ def _text_bbox(grob: Any) -> tuple:
|
|
|
287
310
|
n_lines = len(lines)
|
|
288
311
|
# Width: max per-line width (R's ``GEStrWidth`` walks each line).
|
|
289
312
|
w = max(calc_string_metric(ln, gp=gp)["width"] for ln in lines)
|
|
290
|
-
# Height:
|
|
291
|
-
#
|
|
292
|
-
#
|
|
313
|
+
# Height: ascent of the first line + per-line gap × (n - 1).
|
|
314
|
+
# R's ``heightDetails.text`` / ``GEStrHeight`` returns ASCENT
|
|
315
|
+
# only — never descent. Descent is a separate method
|
|
316
|
+
# (``descentDetails.text``). Including descent here would
|
|
317
|
+
# double-count it downstream in ggplot2 ``titleGrob``, which
|
|
318
|
+
# adds ``grobDescent`` manually to form the rendered height.
|
|
293
319
|
m0 = calc_string_metric(lines[0], gp=gp)
|
|
294
|
-
h = m0["ascent"] +
|
|
320
|
+
h = m0["ascent"] + (n_lines - 1) * inter_line_gap
|
|
295
321
|
|
|
296
322
|
if rot == 0.0:
|
|
297
323
|
# No rotation: bbox is just the text extent
|
grid_py/_state.py
CHANGED
|
@@ -224,6 +224,12 @@ class GridState:
|
|
|
224
224
|
# GSS_GROUPS: group registry for define/use (R grid.h:63, state.c:51)
|
|
225
225
|
# Maps group name → dict with keys: ref, xy, xyin, wh, r, etc.
|
|
226
226
|
self._groups: Dict[str, Any] = {}
|
|
227
|
+
# Per-push counter of redundant self-pushes (the same vp pushed
|
|
228
|
+
# again while already current). Each genuine push appends a 0;
|
|
229
|
+
# a redundant push increments the top entry; pops decrement
|
|
230
|
+
# before actually unwinding. Prevents the cyclic parent chain
|
|
231
|
+
# that would otherwise hang ``current_vp_path``.
|
|
232
|
+
self._redundant_push_count: List[int] = [0]
|
|
227
233
|
|
|
228
234
|
# ---- reset ------------------------------------------------------------
|
|
229
235
|
|
|
@@ -244,7 +250,27 @@ class GridState:
|
|
|
244
250
|
vp : Any
|
|
245
251
|
A viewport-like object. Must expose ``name``, ``parent``,
|
|
246
252
|
and ``children`` attributes (or dict keys).
|
|
253
|
+
|
|
254
|
+
Notes
|
|
255
|
+
-----
|
|
256
|
+
Pushing the same viewport object twice (e.g. when a Gtable's
|
|
257
|
+
``make_context`` builds ``VpStack(orig_vp, layout_vp)`` and
|
|
258
|
+
the iterating push hits ``orig_vp`` while ``orig_vp`` is
|
|
259
|
+
already the current vp — happens in ``_evaluate_grob_unit``'s
|
|
260
|
+
preDraw for self-referential ``viewport(width = grobWidth(self))``
|
|
261
|
+
setups) would otherwise set ``vp.parent = vp`` and produce a
|
|
262
|
+
cyclic parent chain that deadlocks ``current_vp_path``. The
|
|
263
|
+
Python port stores parent on the vp object itself (rather than
|
|
264
|
+
on a separate per-push record like R's grid does), so we
|
|
265
|
+
track redundant self-pushes on a counter stack and treat the
|
|
266
|
+
matching ``up_viewport`` / ``pop_viewport`` as no-ops so depth
|
|
267
|
+
stays balanced.
|
|
247
268
|
"""
|
|
269
|
+
if vp is self._current_vp:
|
|
270
|
+
self._redundant_push_count[-1] = (
|
|
271
|
+
self._redundant_push_count[-1] + 1
|
|
272
|
+
)
|
|
273
|
+
return
|
|
248
274
|
_vp_set_attr(vp, "parent", self._current_vp)
|
|
249
275
|
children = _vp_children(self._current_vp)
|
|
250
276
|
if children is None:
|
|
@@ -252,6 +278,7 @@ class GridState:
|
|
|
252
278
|
_vp_set_attr(self._current_vp, "children", children)
|
|
253
279
|
children.append(vp)
|
|
254
280
|
self._current_vp = vp
|
|
281
|
+
self._redundant_push_count.append(0)
|
|
255
282
|
|
|
256
283
|
def pop_viewport(self, n: int = 1) -> None:
|
|
257
284
|
"""Pop *n* viewports, navigating back toward the root.
|
|
@@ -272,8 +299,14 @@ class GridState:
|
|
|
272
299
|
if n == 0:
|
|
273
300
|
# Pop to root.
|
|
274
301
|
self._current_vp = self._vp_tree
|
|
302
|
+
self._redundant_push_count = [0]
|
|
275
303
|
return
|
|
276
304
|
for _ in range(n):
|
|
305
|
+
# First absorb any redundant self-pushes recorded for the
|
|
306
|
+
# current vp (see ``push_viewport`` notes).
|
|
307
|
+
if self._redundant_push_count and self._redundant_push_count[-1] > 0:
|
|
308
|
+
self._redundant_push_count[-1] -= 1
|
|
309
|
+
continue
|
|
277
310
|
parent = _vp_parent(self._current_vp)
|
|
278
311
|
if parent is None:
|
|
279
312
|
raise ValueError(
|
|
@@ -286,6 +319,8 @@ class GridState:
|
|
|
286
319
|
except ValueError:
|
|
287
320
|
pass
|
|
288
321
|
self._current_vp = parent
|
|
322
|
+
if len(self._redundant_push_count) > 1:
|
|
323
|
+
self._redundant_push_count.pop()
|
|
289
324
|
|
|
290
325
|
def up_viewport(self, n: int = 1) -> None:
|
|
291
326
|
"""Navigate up *n* levels without removing viewports from the tree.
|
|
@@ -305,14 +340,21 @@ class GridState:
|
|
|
305
340
|
raise ValueError(f"'n' must be non-negative, got {n}")
|
|
306
341
|
if n == 0:
|
|
307
342
|
self._current_vp = self._vp_tree
|
|
343
|
+
self._redundant_push_count = [0]
|
|
308
344
|
return
|
|
309
345
|
for _ in range(n):
|
|
346
|
+
# Absorb redundant self-pushes first.
|
|
347
|
+
if self._redundant_push_count and self._redundant_push_count[-1] > 0:
|
|
348
|
+
self._redundant_push_count[-1] -= 1
|
|
349
|
+
continue
|
|
310
350
|
parent = _vp_parent(self._current_vp)
|
|
311
351
|
if parent is None:
|
|
312
352
|
raise ValueError(
|
|
313
353
|
"Cannot navigate above the root viewport."
|
|
314
354
|
)
|
|
315
355
|
self._current_vp = parent
|
|
356
|
+
if len(self._redundant_push_count) > 1:
|
|
357
|
+
self._redundant_push_count.pop()
|
|
316
358
|
|
|
317
359
|
def down_viewport(self, name: str, strict: bool = False) -> int:
|
|
318
360
|
"""Navigate down to a named viewport (breadth-first search).
|
grid_py/_units.py
CHANGED
|
@@ -301,7 +301,20 @@ def _try_resolve_with_renderer(
|
|
|
301
301
|
state = get_state()
|
|
302
302
|
renderer = state.get_renderer()
|
|
303
303
|
|
|
304
|
-
if renderer is None
|
|
304
|
+
if renderer is None:
|
|
305
|
+
# R parity: `convertUnit` (unit.R:59-75) dispatches to `L_convert` in
|
|
306
|
+
# src/unit.c, which resolves context via `GEcurrentDevice()`. When no
|
|
307
|
+
# device is open, R's graphics system auto-opens its default device
|
|
308
|
+
# (PDF, 7×7 in) and converts against it. We replicate that here by
|
|
309
|
+
# lazily installing a default 7×7 in CairoRenderer the first time a
|
|
310
|
+
# context-dependent conversion is requested — matching R's observed
|
|
311
|
+
# behaviour: `convertHeight(unit(0.3,"npc"),"mm") == 53.34` (=0.3×7in
|
|
312
|
+
# in mm) without any prior `grid_newpage()` / `pdf()` call.
|
|
313
|
+
from .renderer import CairoRenderer
|
|
314
|
+
renderer = CairoRenderer(width=7.0, height=7.0)
|
|
315
|
+
state.init_device(renderer)
|
|
316
|
+
|
|
317
|
+
if not hasattr(renderer, "_resolve_to_inches_idx"):
|
|
305
318
|
return None
|
|
306
319
|
|
|
307
320
|
# Build a single-element Unit for the source
|
grid_py/renderer.py
CHANGED
|
@@ -141,6 +141,17 @@ class CairoRenderer(GridRenderer):
|
|
|
141
141
|
if filename is None:
|
|
142
142
|
raise ValueError("filename is required for SVG surface")
|
|
143
143
|
self._surface = cairo.SVGSurface(filename, width_pt, height_pt)
|
|
144
|
+
# Cairo's SVGSurface measures user-space in points; emit explicit
|
|
145
|
+
# ``pt`` units in the resulting <svg width="...pt" height="...pt">
|
|
146
|
+
# so SVG renderers (browsers, cairosvg, Inkscape) interpret the
|
|
147
|
+
# canvas at the intended physical size instead of treating the
|
|
148
|
+
# raw numbers as user-units (= pixels by SVG default).
|
|
149
|
+
try:
|
|
150
|
+
self._surface.set_document_unit(cairo.SVG_UNIT_PT)
|
|
151
|
+
except (AttributeError, TypeError):
|
|
152
|
+
# Older pycairo / cairo without set_document_unit — file is
|
|
153
|
+
# still well-formed, just unit-less.
|
|
154
|
+
pass
|
|
144
155
|
elif surface_type == "ps":
|
|
145
156
|
if filename is None:
|
|
146
157
|
raise ValueError("filename is required for PS surface")
|
|
@@ -322,6 +333,34 @@ class CairoRenderer(GridRenderer):
|
|
|
322
333
|
|
|
323
334
|
# ---- gpar application --------------------------------------------------
|
|
324
335
|
|
|
336
|
+
def _lwd_to_user(self, lwd_pt: float) -> float:
|
|
337
|
+
"""Convert R-grid lwd (points) to a Cairo user-space line width.
|
|
338
|
+
|
|
339
|
+
R's grid measures ``lwd`` in 1/72 inch (points) regardless of the
|
|
340
|
+
active viewport scale — a value of 1 should always produce a stroke
|
|
341
|
+
1pt wide on the output medium. Cairo's ``set_line_width`` takes a
|
|
342
|
+
user-space distance, so we have to:
|
|
343
|
+
|
|
344
|
+
1. Map points → device units. Raster ``ImageSurface`` has 1 user
|
|
345
|
+
unit = 1 pixel, so device units per point = ``dpi/72``. Vector
|
|
346
|
+
surfaces (PDF/SVG/PS) have 1 user unit = 1 pt, so the conversion
|
|
347
|
+
factor is 1.0. Equivalently, use ``_dev_units_per_inch / 72``.
|
|
348
|
+
2. Undo any active CTM scaling via ``device_to_user_distance`` so
|
|
349
|
+
that nested viewports (which scale the CTM) do not also scale
|
|
350
|
+
the stroke.
|
|
351
|
+
|
|
352
|
+
This is the analogue of the font-size handling in ``_set_font``.
|
|
353
|
+
"""
|
|
354
|
+
if self._surface_type == "image":
|
|
355
|
+
lw_dev = lwd_pt * self.dpi / 72.0
|
|
356
|
+
else:
|
|
357
|
+
lw_dev = lwd_pt
|
|
358
|
+
try:
|
|
359
|
+
ux, uy = self._ctx.device_to_user_distance(lw_dev, lw_dev)
|
|
360
|
+
return max(abs(ux), abs(uy))
|
|
361
|
+
except Exception:
|
|
362
|
+
return lw_dev
|
|
363
|
+
|
|
325
364
|
def _apply_stroke(self, gp: Optional[Gpar]) -> Tuple[float, float, float, float]:
|
|
326
365
|
"""Set stroke colour, line width, dash, caps, joins from Gpar.
|
|
327
366
|
|
|
@@ -331,7 +370,7 @@ class CairoRenderer(GridRenderer):
|
|
|
331
370
|
ctx = self._ctx
|
|
332
371
|
if gp is None:
|
|
333
372
|
ctx.set_source_rgba(0, 0, 0, 1)
|
|
334
|
-
ctx.set_line_width(1.0)
|
|
373
|
+
ctx.set_line_width(self._lwd_to_user(1.0))
|
|
335
374
|
return (0.0, 0.0, 0.0, 1.0)
|
|
336
375
|
|
|
337
376
|
col = gp.get("col", None)
|
|
@@ -367,24 +406,7 @@ class CairoRenderer(GridRenderer):
|
|
|
367
406
|
# R semantics: lwd=0 means invisible line
|
|
368
407
|
if lw <= 0:
|
|
369
408
|
return (0.0, 0.0, 0.0, 0.0)
|
|
370
|
-
|
|
371
|
-
# regardless of the current viewport's scale. Cairo's
|
|
372
|
-
# ``set_line_width`` takes a user-space distance, which the
|
|
373
|
-
# viewport CTM has scaled down to NPC-like units — so a value
|
|
374
|
-
# of 0.5 user-space becomes sub-pixel after ``scale(w, h)``.
|
|
375
|
-
# Convert ``lw`` from points → device pixels using the
|
|
376
|
-
# renderer's DPI, then back to user-space via
|
|
377
|
-
# ``device_to_user_distance`` so the stroke width stays at
|
|
378
|
-
# 0.5pt on the output device no matter how deep the
|
|
379
|
-
# viewport stack is (matches R grid's device-unit lwd).
|
|
380
|
-
dpi = getattr(self, "dpi", None) or getattr(self, "_dpi", 72.0) or 72.0
|
|
381
|
-
lw_px = lw * dpi / 72.0
|
|
382
|
-
try:
|
|
383
|
-
ux, uy = ctx.device_to_user_distance(lw_px, lw_px)
|
|
384
|
-
lw_user = max(abs(ux), abs(uy))
|
|
385
|
-
except Exception:
|
|
386
|
-
lw_user = lw
|
|
387
|
-
ctx.set_line_width(lw_user)
|
|
409
|
+
ctx.set_line_width(self._lwd_to_user(lw))
|
|
388
410
|
|
|
389
411
|
lty = gp.get("lty", None)
|
|
390
412
|
if lty is not None:
|
|
@@ -1197,7 +1219,7 @@ class CairoRenderer(GridRenderer):
|
|
|
1197
1219
|
"""
|
|
1198
1220
|
ctx.save()
|
|
1199
1221
|
ctx.new_path() # clear any residual path from prior draws
|
|
1200
|
-
ctx.set_line_width(lwd)
|
|
1222
|
+
ctx.set_line_width(self._lwd_to_user(lwd))
|
|
1201
1223
|
|
|
1202
1224
|
if pch_val <= 14:
|
|
1203
1225
|
# --- Group 0-14: stroke-only (use col for outline, no fill) ---
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rgrid-python
|
|
3
|
-
Version: 4.5.3.
|
|
3
|
+
Version: 4.5.3.post3
|
|
4
4
|
Summary: Python port of the R grid package (tracks R grid 4.5.3)
|
|
5
5
|
Project-URL: Homepage, https://github.com/Bio-Babel/grid_py
|
|
6
6
|
Project-URL: Repository, https://github.com/Bio-Babel/grid_py
|
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
grid_py/__init__.py,sha256=
|
|
1
|
+
grid_py/__init__.py,sha256=tSRqBr3SxkmX3f3x0tT6G-kMFeApA6cjrhD1CGiKWNs,10520
|
|
2
2
|
grid_py/_arrow.py,sha256=pgn4OCgF6TZY2yXl7ML-kt08DbwqlvT1ulApDcmRz4k,10867
|
|
3
3
|
grid_py/_clippath.py,sha256=p6fUAkcEc5Bg8chv-lIZwaOjEUbLK57duwjtIpDjAkw,4015
|
|
4
4
|
grid_py/_colour.py,sha256=WqaxGop-SM1LYZG4uMPmMX3Zjh6Y_ip0HcanP5wEij8,22872
|
|
5
5
|
grid_py/_coords.py,sha256=9cDD3MWHmw9TnT1I8QKHQA96SCG06OxRXNqYGFZsfwg,47210
|
|
6
|
-
grid_py/_curve.py,sha256=
|
|
6
|
+
grid_py/_curve.py,sha256=QnUkh9lWLd0OSm7h7QwS78JjzBAu9WHmRY4ZpZhccpY,54989
|
|
7
7
|
grid_py/_display_list.py,sha256=uMFAupSJaCgUCbLMv2-RDBQ-YLxCkAldypRqc7CnF3w,13622
|
|
8
|
-
grid_py/_draw.py,sha256=
|
|
8
|
+
grid_py/_draw.py,sha256=WcbxIX9GPEZBf5L8KRZpYiFNWyHVttGBrroKO4m6LUY,50953
|
|
9
9
|
grid_py/_edit.py,sha256=vQDZGBTTYy6DmlW33l94s1KJLrzPNym21NR4Od4qIFw,22263
|
|
10
10
|
grid_py/_font_metrics.py,sha256=XC_dgIN3eB72VPXIRFU-tynnS3wJl3bPugGHrwVzyeo,11086
|
|
11
|
-
grid_py/_gpar.py,sha256=
|
|
11
|
+
grid_py/_gpar.py,sha256=AW3PNlD7UWOozzZvY5Y3f7ZGTWCTRnOs2YqOvlFD1_A,19757
|
|
12
12
|
grid_py/_grab.py,sha256=XAPdYG2gyl9Px29xM_rByIPA2NwuvkbFaLcHGrqpaUQ,15423
|
|
13
13
|
grid_py/_grob.py,sha256=kHSxkPHWqHHyMVum7ueKS-ddDqFF2E-8tUQOM-azEXU,39992
|
|
14
14
|
grid_py/_group.py,sha256=jmPQPcy_lrVjurF8w5ovhCXvwKLBeGVkOwAvX0X0gA0,22828
|
|
15
15
|
grid_py/_highlevel.py,sha256=KVcFc0aUm6WBGyNKlYJfZ16qC4bRolng8ZKb86lSLpc,61803
|
|
16
16
|
grid_py/_just.py,sha256=lh9ar8lw6JRqZE5qHP4Wx7GHPIW0y7Wp0EgJ7vhFjek,10590
|
|
17
|
-
grid_py/_layout.py,sha256=
|
|
17
|
+
grid_py/_layout.py,sha256=opOEvxTO2gjwuLEt8sbKjp4yexjnx0Gik6d95yoL3aI,19721
|
|
18
18
|
grid_py/_ls.py,sha256=RhUGZJCZkcTUjUK97fft_G4D9KxuyBr3OMFhzC5IShc,25547
|
|
19
19
|
grid_py/_mask.py,sha256=d2Wm2z-RJnfEKHdAHcjteL7VubimcaK1_f9Us9MfrUk,4902
|
|
20
20
|
grid_py/_path.py,sha256=Tr5bNNcGwPpOsxWKqEp65MRri-KV8IqplUh2Z90sxnk,11421
|
|
21
21
|
grid_py/_patterns.py,sha256=PBwV2b3oJkbueTLCku6cxeZ1PThREjiPcA6lmrwH3pE,32691
|
|
22
22
|
grid_py/_primitives.py,sha256=dpX5_q5CS8p1vhuVzTa77MVbtaxigGBA5mmUzfFfHks,58861
|
|
23
|
-
grid_py/_renderer_base.py,sha256=
|
|
23
|
+
grid_py/_renderer_base.py,sha256=UD24cRzJlknMSSs2sl27XaDZ1e7XdIuhRDVhDxjiW-o,54770
|
|
24
24
|
grid_py/_scene_graph.py,sha256=mKhNEMkUolbWt4_CFgGhrGUudrYenxFNXaBp6GLN9_Y,7412
|
|
25
|
-
grid_py/_size.py,sha256=
|
|
26
|
-
grid_py/_state.py,sha256=
|
|
25
|
+
grid_py/_size.py,sha256=q9yYdhO7dcp-RtQzJIIsXssFH5oFbsm5_P6UxZlFKjg,43312
|
|
26
|
+
grid_py/_state.py,sha256=SsEzcicTmxWaGExDeLLNNVXT-G38aUFtgSuZTJEYBbw,22800
|
|
27
27
|
grid_py/_transforms.py,sha256=mQvs_Qm6icti66peLXTsjgmSmvIUCCphf_-D-tiE1O4,11675
|
|
28
28
|
grid_py/_typeset.py,sha256=J_PJ5YcOAOnnFkBfo8IW86utJdkdsrnGZi3MvnHc8io,11516
|
|
29
|
-
grid_py/_units.py,sha256=
|
|
29
|
+
grid_py/_units.py,sha256=bGrHV23Vr2A6lO15LJvcP7Qs1Yx_uQJLmyQ6bRB3yjc,61893
|
|
30
30
|
grid_py/_utils.py,sha256=QaWNkCF3BbPHyrqPPRxN7Ji2vB5ethNwVT3SJ1ad9Lc,8335
|
|
31
31
|
grid_py/_viewport.py,sha256=AOrYmv05Y4QJbsTksn86RLStfHdbOHERc2YDHdZAekE,48477
|
|
32
32
|
grid_py/_vp_calc.py,sha256=v0MJc-YmWohO5t7LHFopk5ogH-Sink_A_zNFV_1769w,32935
|
|
33
33
|
grid_py/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
|
-
grid_py/renderer.py,sha256=
|
|
34
|
+
grid_py/renderer.py,sha256=6L6Rb-2AfjmvaeQYhWQKlU1L1HYHkKAma03v6YdZcTk,62658
|
|
35
35
|
grid_py/renderer_web.py,sha256=pbOijES1eUUqkeAadXVyZPCBRIUIgzs3HIOgAElTYmo,27958
|
|
36
36
|
grid_py/resources/d3.v7.min.js,sha256=8glLv2FBs1lyLE_kVOtsSw8OQswQzHr5IfwVj864ZTk,279706
|
|
37
37
|
grid_py/resources/gridpy.css,sha256=tR5LF2rLvi_bGRWuH9CnmLQk-aG2f-jlZjQZqS7_4uY,1351
|
|
38
38
|
grid_py/resources/gridpy.js,sha256=l8KGgK-qZbJPVvrMAmLUu57RhpSAo_LMibs6rx3RnvA,28340
|
|
39
|
-
rgrid_python-4.5.3.
|
|
40
|
-
rgrid_python-4.5.3.
|
|
41
|
-
rgrid_python-4.5.3.
|
|
42
|
-
rgrid_python-4.5.3.
|
|
39
|
+
rgrid_python-4.5.3.post3.dist-info/METADATA,sha256=U2Lx9ccpNvIWTBozFmqItTD-AWHPY9Wq9zdeKQelK68,19910
|
|
40
|
+
rgrid_python-4.5.3.post3.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
41
|
+
rgrid_python-4.5.3.post3.dist-info/licenses/LICENSE,sha256=8zNQKZlkc4JOaW7br_a8aALg4baZwZkLKLTQx3kuHkc,48
|
|
42
|
+
rgrid_python-4.5.3.post3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|