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/_layout.py
ADDED
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
"""Layout system for grid_py -- Python port of R's ``grid::grid.layout``.
|
|
2
|
+
|
|
3
|
+
This module provides the :class:`GridLayout` class and associated accessor
|
|
4
|
+
functions that mirror R's ``grid.layout()`` constructor and its companion
|
|
5
|
+
helper functions (``layout.nrow``, ``layout.ncol``, etc.).
|
|
6
|
+
|
|
7
|
+
A layout partitions a rectangular region into a grid of rows and columns
|
|
8
|
+
whose sizes may be expressed in any unit supported by the grid unit system.
|
|
9
|
+
The *respect* mechanism allows certain cells to maintain their aspect ratio
|
|
10
|
+
when the viewport is resized.
|
|
11
|
+
|
|
12
|
+
References
|
|
13
|
+
----------
|
|
14
|
+
R source: ``src/library/grid/R/layout.R``
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from typing import Any, Dict, Optional, Sequence, Tuple, Union
|
|
20
|
+
|
|
21
|
+
import numpy as np
|
|
22
|
+
|
|
23
|
+
from ._just import valid_just
|
|
24
|
+
from ._units import Unit, is_unit
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"GridLayout",
|
|
28
|
+
"layout_nrow",
|
|
29
|
+
"layout_ncol",
|
|
30
|
+
"layout_widths",
|
|
31
|
+
"layout_heights",
|
|
32
|
+
"layout_respect",
|
|
33
|
+
"layout_region",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class GridLayout:
|
|
38
|
+
"""A grid layout specification.
|
|
39
|
+
|
|
40
|
+
Divides a rectangular region into *nrow* rows and *ncol* columns whose
|
|
41
|
+
dimensions are given by *widths* and *heights* (as :class:`Unit` objects).
|
|
42
|
+
|
|
43
|
+
Parameters
|
|
44
|
+
----------
|
|
45
|
+
nrow : int
|
|
46
|
+
Number of rows.
|
|
47
|
+
ncol : int
|
|
48
|
+
Number of columns.
|
|
49
|
+
widths : Unit or None
|
|
50
|
+
Column widths. If *None* (the default), each column receives equal
|
|
51
|
+
``"null"`` space (``Unit([1]*ncol, "null")``).
|
|
52
|
+
heights : Unit or None
|
|
53
|
+
Row heights. If *None* (the default), each row receives equal
|
|
54
|
+
``"null"`` space (``Unit([1]*nrow, "null")``).
|
|
55
|
+
default_units : str
|
|
56
|
+
Unit type applied to *widths* / *heights* when they are supplied as
|
|
57
|
+
plain numeric values rather than :class:`Unit` objects.
|
|
58
|
+
respect : bool or numpy.ndarray
|
|
59
|
+
If ``False`` (the default), no aspect-ratio constraints are applied.
|
|
60
|
+
If ``True``, all null-unit cells are respected. An *nrow* x *ncol*
|
|
61
|
+
integer matrix (``numpy.ndarray``) selects individual cells to respect.
|
|
62
|
+
just : str or sequence of str
|
|
63
|
+
Justification of the layout within its parent viewport. Accepted
|
|
64
|
+
values follow :func:`._just.valid_just` conventions (e.g.
|
|
65
|
+
``"centre"``, ``"left"``, ``["right", "top"]``).
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
__slots__ = (
|
|
69
|
+
"_nrow",
|
|
70
|
+
"_ncol",
|
|
71
|
+
"_widths",
|
|
72
|
+
"_heights",
|
|
73
|
+
"_respect",
|
|
74
|
+
"_valid_respect",
|
|
75
|
+
"_respect_mat",
|
|
76
|
+
"_just",
|
|
77
|
+
"_valid_just",
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
def __init__(
|
|
81
|
+
self,
|
|
82
|
+
nrow: int = 1,
|
|
83
|
+
ncol: int = 1,
|
|
84
|
+
widths: Optional[Unit] = None,
|
|
85
|
+
heights: Optional[Unit] = None,
|
|
86
|
+
default_units: str = "null",
|
|
87
|
+
respect: Union[bool, np.ndarray] = False,
|
|
88
|
+
just: Union[str, Sequence[str]] = "centre",
|
|
89
|
+
) -> None:
|
|
90
|
+
self._nrow: int = int(nrow)
|
|
91
|
+
self._ncol: int = int(ncol)
|
|
92
|
+
|
|
93
|
+
# -- widths ----------------------------------------------------------
|
|
94
|
+
if widths is None:
|
|
95
|
+
self._widths: Unit = Unit([1.0] * self._ncol, "null")
|
|
96
|
+
elif is_unit(widths):
|
|
97
|
+
self._widths = widths
|
|
98
|
+
else:
|
|
99
|
+
self._widths = Unit(widths, default_units)
|
|
100
|
+
|
|
101
|
+
# -- heights ---------------------------------------------------------
|
|
102
|
+
if heights is None:
|
|
103
|
+
self._heights: Unit = Unit([1.0] * self._nrow, "null")
|
|
104
|
+
elif is_unit(heights):
|
|
105
|
+
self._heights = heights
|
|
106
|
+
else:
|
|
107
|
+
self._heights = Unit(heights, default_units)
|
|
108
|
+
|
|
109
|
+
# -- respect ---------------------------------------------------------
|
|
110
|
+
if isinstance(respect, np.ndarray):
|
|
111
|
+
respect_arr = np.asarray(respect, dtype=np.int32)
|
|
112
|
+
if respect_arr.shape != (self._nrow, self._ncol):
|
|
113
|
+
raise ValueError(
|
|
114
|
+
"'respect' must be logical or an 'nrow' by 'ncol' matrix; "
|
|
115
|
+
f"got shape {respect_arr.shape}, expected "
|
|
116
|
+
f"({self._nrow}, {self._ncol})"
|
|
117
|
+
)
|
|
118
|
+
self._respect_mat: np.ndarray = respect_arr
|
|
119
|
+
# R stores integer 2 to signal "matrix mode"
|
|
120
|
+
self._respect: Union[bool, np.ndarray] = respect
|
|
121
|
+
self._valid_respect: int = 2
|
|
122
|
+
elif respect:
|
|
123
|
+
self._respect_mat = np.zeros(
|
|
124
|
+
(self._nrow, self._ncol), dtype=np.int32
|
|
125
|
+
)
|
|
126
|
+
self._respect = True
|
|
127
|
+
self._valid_respect = 1
|
|
128
|
+
else:
|
|
129
|
+
self._respect_mat = np.zeros(
|
|
130
|
+
(self._nrow, self._ncol), dtype=np.int32
|
|
131
|
+
)
|
|
132
|
+
self._respect = False
|
|
133
|
+
self._valid_respect = 0
|
|
134
|
+
|
|
135
|
+
# -- justification ---------------------------------------------------
|
|
136
|
+
self._just = just
|
|
137
|
+
self._valid_just: Tuple[float, float] = valid_just(just)
|
|
138
|
+
|
|
139
|
+
# --------------------------------------------------------------------- #
|
|
140
|
+
# Properties #
|
|
141
|
+
# --------------------------------------------------------------------- #
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def nrow(self) -> int:
|
|
145
|
+
"""Number of rows in the layout."""
|
|
146
|
+
return self._nrow
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def ncol(self) -> int:
|
|
150
|
+
"""Number of columns in the layout."""
|
|
151
|
+
return self._ncol
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def widths(self) -> Unit:
|
|
155
|
+
"""Column widths as a :class:`Unit`."""
|
|
156
|
+
return self._widths
|
|
157
|
+
|
|
158
|
+
@property
|
|
159
|
+
def heights(self) -> Unit:
|
|
160
|
+
"""Row heights as a :class:`Unit`."""
|
|
161
|
+
return self._heights
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def respect(self) -> Union[bool, np.ndarray]:
|
|
165
|
+
"""Respect specification.
|
|
166
|
+
|
|
167
|
+
Returns ``False`` (no respect), ``True`` (full respect), or an
|
|
168
|
+
*nrow* x *ncol* integer matrix indicating per-cell respect.
|
|
169
|
+
"""
|
|
170
|
+
if self._valid_respect == 0:
|
|
171
|
+
return False
|
|
172
|
+
if self._valid_respect == 1:
|
|
173
|
+
return True
|
|
174
|
+
return self._respect_mat
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
def respect_mat(self) -> np.ndarray:
|
|
178
|
+
"""The *nrow* x *ncol* integer matrix of per-cell respect flags."""
|
|
179
|
+
return self._respect_mat
|
|
180
|
+
|
|
181
|
+
@property
|
|
182
|
+
def dim(self) -> Tuple[int, int]:
|
|
183
|
+
"""Layout dimensions as ``(nrow, ncol)``."""
|
|
184
|
+
return (self._nrow, self._ncol)
|
|
185
|
+
|
|
186
|
+
# --------------------------------------------------------------------- #
|
|
187
|
+
# Dunder methods #
|
|
188
|
+
# --------------------------------------------------------------------- #
|
|
189
|
+
|
|
190
|
+
def __repr__(self) -> str:
|
|
191
|
+
return (
|
|
192
|
+
f"GridLayout(nrow={self._nrow}, ncol={self._ncol}, "
|
|
193
|
+
f"widths={self._widths!r}, heights={self._heights!r}, "
|
|
194
|
+
f"respect={self._respect!r}, just={self._just!r})"
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
# ======================================================================= #
|
|
199
|
+
# Module-level accessor functions #
|
|
200
|
+
# ======================================================================= #
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def layout_nrow(layout: GridLayout) -> int:
|
|
204
|
+
"""Return the number of rows in *layout*.
|
|
205
|
+
|
|
206
|
+
Parameters
|
|
207
|
+
----------
|
|
208
|
+
layout : GridLayout
|
|
209
|
+
A grid layout object.
|
|
210
|
+
|
|
211
|
+
Returns
|
|
212
|
+
-------
|
|
213
|
+
int
|
|
214
|
+
Number of rows.
|
|
215
|
+
"""
|
|
216
|
+
return layout.nrow
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def layout_ncol(layout: GridLayout) -> int:
|
|
220
|
+
"""Return the number of columns in *layout*.
|
|
221
|
+
|
|
222
|
+
Parameters
|
|
223
|
+
----------
|
|
224
|
+
layout : GridLayout
|
|
225
|
+
A grid layout object.
|
|
226
|
+
|
|
227
|
+
Returns
|
|
228
|
+
-------
|
|
229
|
+
int
|
|
230
|
+
Number of columns.
|
|
231
|
+
"""
|
|
232
|
+
return layout.ncol
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def layout_widths(layout: GridLayout) -> Unit:
|
|
236
|
+
"""Return the column widths of *layout*.
|
|
237
|
+
|
|
238
|
+
Parameters
|
|
239
|
+
----------
|
|
240
|
+
layout : GridLayout
|
|
241
|
+
A grid layout object.
|
|
242
|
+
|
|
243
|
+
Returns
|
|
244
|
+
-------
|
|
245
|
+
Unit
|
|
246
|
+
Column widths.
|
|
247
|
+
"""
|
|
248
|
+
return layout.widths
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def layout_heights(layout: GridLayout) -> Unit:
|
|
252
|
+
"""Return the row heights of *layout*.
|
|
253
|
+
|
|
254
|
+
Parameters
|
|
255
|
+
----------
|
|
256
|
+
layout : GridLayout
|
|
257
|
+
A grid layout object.
|
|
258
|
+
|
|
259
|
+
Returns
|
|
260
|
+
-------
|
|
261
|
+
Unit
|
|
262
|
+
Row heights.
|
|
263
|
+
"""
|
|
264
|
+
return layout.heights
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def layout_respect(layout: GridLayout) -> Union[bool, np.ndarray]:
|
|
268
|
+
"""Return the respect specification of *layout*.
|
|
269
|
+
|
|
270
|
+
Parameters
|
|
271
|
+
----------
|
|
272
|
+
layout : GridLayout
|
|
273
|
+
A grid layout object.
|
|
274
|
+
|
|
275
|
+
Returns
|
|
276
|
+
-------
|
|
277
|
+
bool or numpy.ndarray
|
|
278
|
+
``False`` for no respect, ``True`` for full respect, or an
|
|
279
|
+
*nrow* x *ncol* integer matrix for per-cell respect.
|
|
280
|
+
"""
|
|
281
|
+
return layout.respect
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
# ======================================================================= #
|
|
285
|
+
# Three-phase layout algorithm (mirrors R layout.c:calcViewportLayout) #
|
|
286
|
+
# ======================================================================= #
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _col_respected(col: int, layout: GridLayout) -> bool:
|
|
290
|
+
"""Check if column *col* (0-based) has any respected cell.
|
|
291
|
+
|
|
292
|
+
Mirrors R ``layout.c:colRespected`` (lines 121-133).
|
|
293
|
+
"""
|
|
294
|
+
if layout._valid_respect == 1:
|
|
295
|
+
return True
|
|
296
|
+
if layout._valid_respect == 2:
|
|
297
|
+
return bool(np.any(layout._respect_mat[:, col] != 0))
|
|
298
|
+
return False
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _row_respected(row: int, layout: GridLayout) -> bool:
|
|
302
|
+
"""Check if row *row* (0-based) has any respected cell.
|
|
303
|
+
|
|
304
|
+
Mirrors R ``layout.c:rowRespected`` (lines 135-147).
|
|
305
|
+
"""
|
|
306
|
+
if layout._valid_respect == 1:
|
|
307
|
+
return True
|
|
308
|
+
if layout._valid_respect == 2:
|
|
309
|
+
return bool(np.any(layout._respect_mat[row, :] != 0))
|
|
310
|
+
return False
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def _calc_layout_sizes(
|
|
314
|
+
layout: GridLayout,
|
|
315
|
+
parent_w_px: float,
|
|
316
|
+
parent_h_px: float,
|
|
317
|
+
dpi: float = 150.0,
|
|
318
|
+
) -> Tuple[list, list]:
|
|
319
|
+
"""Three-phase layout negotiation algorithm.
|
|
320
|
+
|
|
321
|
+
Mirrors R ``layout.c:calcViewportLayout`` (lines 492-591).
|
|
322
|
+
|
|
323
|
+
Phase 1: Allocate absolute (non-null) units to device pixels.
|
|
324
|
+
Phase 2: Allocate respected null units with unified aspect-ratio scale.
|
|
325
|
+
Phase 3: Distribute remaining space among unrespected null units.
|
|
326
|
+
|
|
327
|
+
Parameters
|
|
328
|
+
----------
|
|
329
|
+
layout : GridLayout
|
|
330
|
+
The layout specification.
|
|
331
|
+
parent_w_px, parent_h_px : float
|
|
332
|
+
Available space in device pixels.
|
|
333
|
+
dpi : float
|
|
334
|
+
Device resolution in dots per inch.
|
|
335
|
+
|
|
336
|
+
Returns
|
|
337
|
+
-------
|
|
338
|
+
(col_widths, row_heights) : (list[float], list[float])
|
|
339
|
+
Allocated sizes in device pixels.
|
|
340
|
+
"""
|
|
341
|
+
from ._units import _INCHES_PER
|
|
342
|
+
|
|
343
|
+
ncol = layout.nrow if False else layout.ncol # kept explicit for clarity
|
|
344
|
+
nrow = layout.nrow
|
|
345
|
+
ncol = layout.ncol
|
|
346
|
+
widths = layout.widths
|
|
347
|
+
heights = layout.heights
|
|
348
|
+
|
|
349
|
+
col_sizes = [0.0] * ncol
|
|
350
|
+
row_sizes = [0.0] * nrow
|
|
351
|
+
relative_w = [False] * ncol
|
|
352
|
+
relative_h = [False] * nrow
|
|
353
|
+
|
|
354
|
+
reduced_w = parent_w_px
|
|
355
|
+
reduced_h = parent_h_px
|
|
356
|
+
|
|
357
|
+
# ---- Phase 1: allocate absolute units (layout.c:allocateKnownWidths) --
|
|
358
|
+
# R resolves ALL non-null units to absolute device pixels in this phase.
|
|
359
|
+
# Compound units (sum/min/max), contextual units (lines/char/snpc),
|
|
360
|
+
# and string/grob metric units must be resolved via the renderer.
|
|
361
|
+
from ._state import get_state as _get_state
|
|
362
|
+
_renderer = _get_state().get_renderer()
|
|
363
|
+
|
|
364
|
+
def _resolve_unit_to_px(unit_obj, idx, axis, parent_px):
|
|
365
|
+
"""Resolve a single unit element to device pixels."""
|
|
366
|
+
utype = unit_obj._units[idx] if idx < len(unit_obj._units) else "null"
|
|
367
|
+
val = float(unit_obj._values[idx])
|
|
368
|
+
if utype == "null":
|
|
369
|
+
return None # null → flex
|
|
370
|
+
if utype == "npc":
|
|
371
|
+
return val * parent_px
|
|
372
|
+
if utype in _INCHES_PER:
|
|
373
|
+
return val * _INCHES_PER[utype] * dpi
|
|
374
|
+
# Compound or contextual unit: resolve via renderer
|
|
375
|
+
if _renderer is not None:
|
|
376
|
+
from ._units import Unit
|
|
377
|
+
elem = Unit(val, utype,
|
|
378
|
+
data=unit_obj._data[idx] if unit_obj._data else None)
|
|
379
|
+
inches = _renderer._resolve_to_inches(elem, axis, True)
|
|
380
|
+
return inches * dpi
|
|
381
|
+
return None # no renderer → treat as null
|
|
382
|
+
|
|
383
|
+
for i in range(ncol):
|
|
384
|
+
px = _resolve_unit_to_px(widths, i, "x", parent_w_px)
|
|
385
|
+
if px is None:
|
|
386
|
+
relative_w[i] = True
|
|
387
|
+
else:
|
|
388
|
+
col_sizes[i] = px
|
|
389
|
+
reduced_w -= px
|
|
390
|
+
|
|
391
|
+
for j in range(nrow):
|
|
392
|
+
px = _resolve_unit_to_px(heights, j, "y", parent_h_px)
|
|
393
|
+
if px is None:
|
|
394
|
+
relative_h[j] = True
|
|
395
|
+
else:
|
|
396
|
+
row_sizes[j] = px
|
|
397
|
+
reduced_h -= px
|
|
398
|
+
|
|
399
|
+
reduced_w = max(reduced_w, 0.0)
|
|
400
|
+
reduced_h = max(reduced_h, 0.0)
|
|
401
|
+
|
|
402
|
+
# ---- Phase 2: allocate respected null units (layout.c:allocateRespected)
|
|
403
|
+
if layout._valid_respect > 0 and (reduced_w > 0 or reduced_h > 0):
|
|
404
|
+
sum_w = sum(
|
|
405
|
+
float(widths._values[i])
|
|
406
|
+
for i in range(ncol)
|
|
407
|
+
if relative_w[i] and _col_respected(i, layout)
|
|
408
|
+
)
|
|
409
|
+
sum_h = sum(
|
|
410
|
+
float(heights._values[j])
|
|
411
|
+
for j in range(nrow)
|
|
412
|
+
if relative_h[j] and _row_respected(j, layout)
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
temp_w = reduced_w
|
|
416
|
+
temp_h = reduced_h
|
|
417
|
+
|
|
418
|
+
if sum_w > 0 or sum_h > 0:
|
|
419
|
+
# Determine limiting dimension (layout.c:221)
|
|
420
|
+
if temp_h * sum_w > sum_h * temp_w:
|
|
421
|
+
denom, mult = sum_w, temp_w
|
|
422
|
+
else:
|
|
423
|
+
denom, mult = sum_h, temp_h
|
|
424
|
+
|
|
425
|
+
for i in range(ncol):
|
|
426
|
+
if relative_w[i] and _col_respected(i, layout):
|
|
427
|
+
# Special case: sumHeight==0 (layout.c:240-243)
|
|
428
|
+
d, m = denom, mult
|
|
429
|
+
if sum_h == 0:
|
|
430
|
+
d, m = sum_w, temp_w
|
|
431
|
+
if d > 0:
|
|
432
|
+
col_sizes[i] = float(widths._values[i]) / d * m
|
|
433
|
+
reduced_w -= col_sizes[i]
|
|
434
|
+
|
|
435
|
+
for j in range(nrow):
|
|
436
|
+
if relative_h[j] and _row_respected(j, layout):
|
|
437
|
+
d, m = denom, mult
|
|
438
|
+
if sum_w == 0:
|
|
439
|
+
d, m = sum_h, temp_h
|
|
440
|
+
if d > 0:
|
|
441
|
+
row_sizes[j] = float(heights._values[j]) / d * m
|
|
442
|
+
reduced_h -= row_sizes[j]
|
|
443
|
+
else:
|
|
444
|
+
# No respect or no remaining space: respected nulls get 0
|
|
445
|
+
for i in range(ncol):
|
|
446
|
+
if relative_w[i] and _col_respected(i, layout):
|
|
447
|
+
col_sizes[i] = 0.0
|
|
448
|
+
for j in range(nrow):
|
|
449
|
+
if relative_h[j] and _row_respected(j, layout):
|
|
450
|
+
row_sizes[j] = 0.0
|
|
451
|
+
|
|
452
|
+
reduced_w = max(reduced_w, 0.0)
|
|
453
|
+
reduced_h = max(reduced_h, 0.0)
|
|
454
|
+
|
|
455
|
+
# ---- Phase 3: allocate unrespected null units (layout.c:allocateRemaining)
|
|
456
|
+
sum_unresp_w = sum(
|
|
457
|
+
float(widths._values[i])
|
|
458
|
+
for i in range(ncol)
|
|
459
|
+
if relative_w[i] and not _col_respected(i, layout)
|
|
460
|
+
)
|
|
461
|
+
if sum_unresp_w > 0:
|
|
462
|
+
for i in range(ncol):
|
|
463
|
+
if relative_w[i] and not _col_respected(i, layout):
|
|
464
|
+
col_sizes[i] = reduced_w * float(widths._values[i]) / sum_unresp_w
|
|
465
|
+
else:
|
|
466
|
+
for i in range(ncol):
|
|
467
|
+
if relative_w[i] and not _col_respected(i, layout):
|
|
468
|
+
col_sizes[i] = 0.0
|
|
469
|
+
|
|
470
|
+
sum_unresp_h = sum(
|
|
471
|
+
float(heights._values[j])
|
|
472
|
+
for j in range(nrow)
|
|
473
|
+
if relative_h[j] and not _row_respected(j, layout)
|
|
474
|
+
)
|
|
475
|
+
if sum_unresp_h > 0:
|
|
476
|
+
for j in range(nrow):
|
|
477
|
+
if relative_h[j] and not _row_respected(j, layout):
|
|
478
|
+
row_sizes[j] = reduced_h * float(heights._values[j]) / sum_unresp_h
|
|
479
|
+
else:
|
|
480
|
+
for j in range(nrow):
|
|
481
|
+
if relative_h[j] and not _row_respected(j, layout):
|
|
482
|
+
row_sizes[j] = 0.0
|
|
483
|
+
|
|
484
|
+
return col_sizes, row_sizes
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def layout_region(
|
|
488
|
+
layout: GridLayout,
|
|
489
|
+
row: Union[int, Sequence[int]],
|
|
490
|
+
col: Union[int, Sequence[int]],
|
|
491
|
+
parent_w_px: Optional[float] = None,
|
|
492
|
+
parent_h_px: Optional[float] = None,
|
|
493
|
+
dpi: float = 150.0,
|
|
494
|
+
) -> Dict[str, Unit]:
|
|
495
|
+
"""Compute the region occupied by a range of layout cells.
|
|
496
|
+
|
|
497
|
+
Mirrors R's ``layoutRegion`` function. Row and column indices are
|
|
498
|
+
**1-based** (following R convention). A single integer selects one
|
|
499
|
+
row/column; a two-element sequence ``[start, end]`` selects an inclusive
|
|
500
|
+
range.
|
|
501
|
+
|
|
502
|
+
When *parent_w_px* and *parent_h_px* are provided, the full three-phase
|
|
503
|
+
layout negotiation algorithm is used (matching R's ``calcViewportLayout``
|
|
504
|
+
in ``layout.c``), correctly handling mixed units (``cm`` + ``null`` +
|
|
505
|
+
``npc``), the ``respect`` parameter, and per-cell respect matrices.
|
|
506
|
+
|
|
507
|
+
When these parameters are omitted, a simplified proportional allocation
|
|
508
|
+
is used (backward compatible; only correct for uniform ``null`` units).
|
|
509
|
+
|
|
510
|
+
Parameters
|
|
511
|
+
----------
|
|
512
|
+
layout : GridLayout
|
|
513
|
+
A grid layout object.
|
|
514
|
+
row : int or sequence of int
|
|
515
|
+
1-based row index or ``[start, end]`` range (inclusive).
|
|
516
|
+
col : int or sequence of int
|
|
517
|
+
1-based column index or ``[start, end]`` range (inclusive).
|
|
518
|
+
parent_w_px : float or None
|
|
519
|
+
Parent viewport width in device pixels. When provided, enables the
|
|
520
|
+
full layout algorithm.
|
|
521
|
+
parent_h_px : float or None
|
|
522
|
+
Parent viewport height in device pixels.
|
|
523
|
+
dpi : float
|
|
524
|
+
Device resolution (dots per inch).
|
|
525
|
+
|
|
526
|
+
Returns
|
|
527
|
+
-------
|
|
528
|
+
dict
|
|
529
|
+
Dictionary with keys ``"left"``, ``"bottom"``, ``"width"``,
|
|
530
|
+
``"height"``, each containing a :class:`Unit` in ``"npc"`` units.
|
|
531
|
+
"""
|
|
532
|
+
# Normalise to (start, end) -- 1-based inclusive
|
|
533
|
+
if isinstance(row, (int, np.integer)):
|
|
534
|
+
row_start, row_end = int(row), int(row)
|
|
535
|
+
else:
|
|
536
|
+
row_seq = list(row)
|
|
537
|
+
row_start = int(row_seq[0])
|
|
538
|
+
row_end = int(row_seq[-1]) if len(row_seq) > 1 else row_start
|
|
539
|
+
|
|
540
|
+
if isinstance(col, (int, np.integer)):
|
|
541
|
+
col_start, col_end = int(col), int(col)
|
|
542
|
+
else:
|
|
543
|
+
col_seq = list(col)
|
|
544
|
+
col_start = int(col_seq[0])
|
|
545
|
+
col_end = int(col_seq[-1]) if len(col_seq) > 1 else col_start
|
|
546
|
+
|
|
547
|
+
# Convert to 0-based indices
|
|
548
|
+
r0 = row_start - 1
|
|
549
|
+
r1 = row_end # exclusive upper bound
|
|
550
|
+
c0 = col_start - 1
|
|
551
|
+
c1 = col_end
|
|
552
|
+
|
|
553
|
+
# -- Full three-phase algorithm when parent dimensions available --------
|
|
554
|
+
if parent_w_px is not None and parent_h_px is not None:
|
|
555
|
+
col_widths, row_heights = _calc_layout_sizes(
|
|
556
|
+
layout, parent_w_px, parent_h_px, dpi,
|
|
557
|
+
)
|
|
558
|
+
total_w = sum(col_widths) or 1.0
|
|
559
|
+
total_h = sum(row_heights) or 1.0
|
|
560
|
+
|
|
561
|
+
left_frac = sum(col_widths[:c0]) / total_w
|
|
562
|
+
width_frac = sum(col_widths[c0:c1]) / total_w
|
|
563
|
+
top_frac = sum(row_heights[:r0]) / total_h
|
|
564
|
+
height_frac = sum(row_heights[r0:r1]) / total_h
|
|
565
|
+
bottom_frac = 1.0 - top_frac - height_frac
|
|
566
|
+
|
|
567
|
+
return {
|
|
568
|
+
"left": Unit(left_frac, "npc"),
|
|
569
|
+
"bottom": Unit(bottom_frac, "npc"),
|
|
570
|
+
"width": Unit(width_frac, "npc"),
|
|
571
|
+
"height": Unit(height_frac, "npc"),
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
# -- Simplified proportional fallback (null units only) ----------------
|
|
575
|
+
w_vals = np.asarray(layout.widths._values, dtype=np.float64)
|
|
576
|
+
h_vals = np.asarray(layout.heights._values, dtype=np.float64)
|
|
577
|
+
|
|
578
|
+
total_w = w_vals.sum() or 1.0
|
|
579
|
+
total_h = h_vals.sum() or 1.0
|
|
580
|
+
|
|
581
|
+
left_frac = w_vals[:c0].sum() / total_w
|
|
582
|
+
width_frac = w_vals[c0:c1].sum() / total_w
|
|
583
|
+
|
|
584
|
+
top_frac = h_vals[:r0].sum() / total_h
|
|
585
|
+
height_frac = h_vals[r0:r1].sum() / total_h
|
|
586
|
+
bottom_frac = 1.0 - top_frac - height_frac
|
|
587
|
+
|
|
588
|
+
return {
|
|
589
|
+
"left": Unit(left_frac, "npc"),
|
|
590
|
+
"bottom": Unit(bottom_frac, "npc"),
|
|
591
|
+
"width": Unit(width_frac, "npc"),
|
|
592
|
+
"height": Unit(height_frac, "npc"),
|
|
593
|
+
}
|