rgrid-python 4.5.3.post2__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 +1 -1
- grid_py/_curve.py +22 -1
- grid_py/_draw.py +33 -10
- grid_py/_renderer_base.py +8 -1
- grid_py/renderer.py +42 -20
- {rgrid_python-4.5.3.post2.dist-info → rgrid_python-4.5.3.post3.dist-info}/METADATA +1 -1
- {rgrid_python-4.5.3.post2.dist-info → rgrid_python-4.5.3.post3.dist-info}/RECORD +9 -9
- {rgrid_python-4.5.3.post2.dist-info → rgrid_python-4.5.3.post3.dist-info}/WHEEL +0 -0
- {rgrid_python-4.5.3.post2.dist-info → rgrid_python-4.5.3.post3.dist-info}/licenses/LICENSE +0 -0
grid_py/__init__.py
CHANGED
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,
|
grid_py/_renderer_base.py
CHANGED
|
@@ -425,8 +425,15 @@ class GridRenderer(ABC):
|
|
|
425
425
|
from ._layout import _calc_layout_sizes, GridLayout
|
|
426
426
|
|
|
427
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.
|
|
428
435
|
col_widths, row_heights = _calc_layout_sizes(
|
|
429
|
-
layout, parent_w, parent_h, self.
|
|
436
|
+
layout, parent_w, parent_h, self._dev_units_per_inch,
|
|
430
437
|
)
|
|
431
438
|
else:
|
|
432
439
|
nrow = getattr(layout, "nrow", 1)
|
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,11 +1,11 @@
|
|
|
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
11
|
grid_py/_gpar.py,sha256=AW3PNlD7UWOozzZvY5Y3f7ZGTWCTRnOs2YqOvlFD1_A,19757
|
|
@@ -20,7 +20,7 @@ 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
25
|
grid_py/_size.py,sha256=q9yYdhO7dcp-RtQzJIIsXssFH5oFbsm5_P6UxZlFKjg,43312
|
|
26
26
|
grid_py/_state.py,sha256=SsEzcicTmxWaGExDeLLNNVXT-G38aUFtgSuZTJEYBbw,22800
|
|
@@ -31,12 +31,12 @@ 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
|