ggplot2-python 4.0.2.9000__py3-none-any.whl → 4.0.2.9000.post1__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.
- ggplot2_py/__init__.py +1 -1
- ggplot2_py/plot_render.py +147 -12
- ggplot2_py/theme_elements.py +14 -4
- {ggplot2_python-4.0.2.9000.dist-info → ggplot2_python-4.0.2.9000.post1.dist-info}/METADATA +3 -1
- {ggplot2_python-4.0.2.9000.dist-info → ggplot2_python-4.0.2.9000.post1.dist-info}/RECORD +7 -7
- {ggplot2_python-4.0.2.9000.dist-info → ggplot2_python-4.0.2.9000.post1.dist-info}/WHEEL +0 -0
- {ggplot2_python-4.0.2.9000.dist-info → ggplot2_python-4.0.2.9000.post1.dist-info}/licenses/LICENSE +0 -0
ggplot2_py/__init__.py
CHANGED
|
@@ -7,7 +7,7 @@ approach to creating statistical visualizations.
|
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
-
__version__ = "4.0.2.9000"
|
|
10
|
+
__version__ = "4.0.2.9000.post1"
|
|
11
11
|
__r_commit__ = "c02c05a"
|
|
12
12
|
|
|
13
13
|
# ---------------------------------------------------------------------------
|
ggplot2_py/plot_render.py
CHANGED
|
@@ -55,6 +55,130 @@ def _legend_label_width_cm(labels: List[Any], fontsize: float = 6.0) -> float:
|
|
|
55
55
|
return max(max_w, 0.3) # minimum width 0.3 cm
|
|
56
56
|
|
|
57
57
|
|
|
58
|
+
# ---------------------------------------------------------------------------
|
|
59
|
+
# Layer-to-guide filtering (ports of R's matched_aes / include_layer_in_guide,
|
|
60
|
+
# ``ggplot2/R/guides-.R:871-912``).
|
|
61
|
+
#
|
|
62
|
+
# R's GuideLegend$process_layers only forwards layers whose aesthetic
|
|
63
|
+
# mapping actually maps one of the guide's aesthetics, unless the user
|
|
64
|
+
# explicitly set ``show.legend=TRUE``. Without this filter, a legend
|
|
65
|
+
# picks up ``draw_key`` from any convenient layer (e.g. a backbone
|
|
66
|
+
# ``geom_segment`` with a fixed black colour) and renders black path
|
|
67
|
+
# glyphs instead of the colour-scale dots it should show.
|
|
68
|
+
# ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
_AES_SYNONYMS: Dict[str, str] = {"color": "colour"}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _canon_aes(name: str) -> str:
|
|
74
|
+
return _AES_SYNONYMS.get(name, name)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _aes_key_set(obj: Any) -> set:
|
|
78
|
+
"""Return the canonicalised set of aesthetic names in a mapping-like obj."""
|
|
79
|
+
if obj is None:
|
|
80
|
+
return set()
|
|
81
|
+
try:
|
|
82
|
+
keys = obj.keys() if hasattr(obj, "keys") else list(obj)
|
|
83
|
+
except Exception:
|
|
84
|
+
return set()
|
|
85
|
+
return {_canon_aes(str(k)) for k in keys}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _matched_aes(layer: Any, guide_aes: set) -> set:
|
|
89
|
+
"""Port of R's ``matched_aes`` (``guides-.R:871-880``).
|
|
90
|
+
|
|
91
|
+
Returns the canonical aesthetic names that are *mapped* by this
|
|
92
|
+
layer's ``aes()`` and also part of the guide's key columns, excluding
|
|
93
|
+
aesthetics that are fixed (``aes_params``/``computed_geom_params``).
|
|
94
|
+
"""
|
|
95
|
+
mapping_keys = _aes_key_set(getattr(layer, "computed_mapping", None)
|
|
96
|
+
or getattr(layer, "mapping", None))
|
|
97
|
+
stat = getattr(layer, "stat", None)
|
|
98
|
+
stat_default = _aes_key_set(getattr(stat, "default_aes", None))
|
|
99
|
+
all_names = mapping_keys | stat_default
|
|
100
|
+
|
|
101
|
+
geom = getattr(layer, "geom", None)
|
|
102
|
+
geom_required = set()
|
|
103
|
+
geom_default = set()
|
|
104
|
+
if geom is not None:
|
|
105
|
+
req = getattr(geom, "required_aes", None)
|
|
106
|
+
if req is not None:
|
|
107
|
+
geom_required = {_canon_aes(str(a)) for a in req}
|
|
108
|
+
geom_default = _aes_key_set(getattr(geom, "default_aes", None))
|
|
109
|
+
geom_names = geom_required | geom_default
|
|
110
|
+
# R's rename_size shim: size-renaming geoms contribute to size
|
|
111
|
+
# legends even without mapping "size" explicitly.
|
|
112
|
+
if geom is not None and getattr(geom, "rename_size", False):
|
|
113
|
+
if "size" in all_names and "linewidth" not in all_names:
|
|
114
|
+
geom_names = geom_names | {"size"}
|
|
115
|
+
|
|
116
|
+
matched = (all_names & geom_names) & {_canon_aes(a) for a in guide_aes}
|
|
117
|
+
matched -= _aes_key_set(getattr(layer, "computed_geom_params", None))
|
|
118
|
+
matched -= _aes_key_set(getattr(layer, "aes_params", None))
|
|
119
|
+
return matched
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _include_layer_in_guide(layer: Any, matched: set) -> bool:
|
|
123
|
+
"""Port of R's ``include_layer_in_guide`` (``guides-.R:885-912``)."""
|
|
124
|
+
show = getattr(layer, "show_legend", None)
|
|
125
|
+
# Non-logical values: R warns and treats as FALSE. Python accepts
|
|
126
|
+
# None (= NA) and bool; anything else is coerced to False.
|
|
127
|
+
if show is not None and not isinstance(show, (bool, np.bool_)):
|
|
128
|
+
# Named-dict form (``show.legend=c(colour=TRUE)``) — uncommon in
|
|
129
|
+
# ggplot2_py but supported for completeness.
|
|
130
|
+
if isinstance(show, dict):
|
|
131
|
+
if not matched:
|
|
132
|
+
return False
|
|
133
|
+
picks = {_canon_aes(k): v for k, v in show.items()}
|
|
134
|
+
vals = [picks[a] for a in matched if a in picks and picks[a] is not None]
|
|
135
|
+
return len(vals) == 0 or any(vals)
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
if matched:
|
|
139
|
+
# Layer maps at least one of the guide's aesthetics:
|
|
140
|
+
# include unless show.legend is explicitly FALSE.
|
|
141
|
+
if show is None:
|
|
142
|
+
return True
|
|
143
|
+
return bool(show)
|
|
144
|
+
# Layer does not map any guide aesthetic: include only if show.legend
|
|
145
|
+
# is explicitly TRUE.
|
|
146
|
+
return show is True
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _resolve_draw_key_for_entry(
|
|
150
|
+
entry: Dict[str, Any], layers: Any,
|
|
151
|
+
) -> tuple[Any, List[Any]]:
|
|
152
|
+
"""Pick the ``draw_key`` and layer subset for a single legend entry.
|
|
153
|
+
|
|
154
|
+
Mirrors R's ``GuideLegend$process_layers`` filtering combined with
|
|
155
|
+
``get_layer_key``'s first-layer-wins behaviour for glyph selection.
|
|
156
|
+
Returns ``(draw_key_fn, included_layers)`` — the included layer
|
|
157
|
+
list is forwarded to ``build_legend_decor`` so ``aes_params`` /
|
|
158
|
+
``default_aes`` resolution also uses only qualifying layers.
|
|
159
|
+
"""
|
|
160
|
+
from ggplot2_py.draw_key import draw_key_point as _draw_key_point
|
|
161
|
+
|
|
162
|
+
guide_aes = {_canon_aes(a) for a in (entry.get("aes_mapped") or {}).keys()}
|
|
163
|
+
if not guide_aes:
|
|
164
|
+
guide_aes = {_canon_aes(str(entry.get("aesthetic", "")))}
|
|
165
|
+
|
|
166
|
+
included: List[Any] = []
|
|
167
|
+
if layers:
|
|
168
|
+
for layer in layers:
|
|
169
|
+
matched = _matched_aes(layer, guide_aes)
|
|
170
|
+
if _include_layer_in_guide(layer, matched):
|
|
171
|
+
included.append(layer)
|
|
172
|
+
|
|
173
|
+
draw_key_fn = _draw_key_point
|
|
174
|
+
for layer in included:
|
|
175
|
+
geom = getattr(layer, "geom", None)
|
|
176
|
+
if geom is not None and hasattr(geom, "draw_key"):
|
|
177
|
+
draw_key_fn = geom.draw_key
|
|
178
|
+
break
|
|
179
|
+
return draw_key_fn, included
|
|
180
|
+
|
|
181
|
+
|
|
58
182
|
@singledispatch
|
|
59
183
|
def ggplot_gtable(data: Any) -> Any:
|
|
60
184
|
"""Convert a built ggplot to a gtable for rendering.
|
|
@@ -362,16 +486,11 @@ def _table_add_legends(
|
|
|
362
486
|
PADDING_CM = 0.15 # R: legend.margin default padding
|
|
363
487
|
|
|
364
488
|
# ------------------------------------------------------------------
|
|
365
|
-
# 4.
|
|
489
|
+
# 4. ``draw_key`` is now resolved per-entry inside the loop below
|
|
490
|
+
# (R's ``GuideLegend$process_layers`` filters layers against each
|
|
491
|
+
# guide's aesthetics via ``matched_aes`` / ``include_layer_in_guide``,
|
|
492
|
+
# ``guide-legend.R:219-231``). See ``_resolve_draw_key_for_entry``.
|
|
366
493
|
# ------------------------------------------------------------------
|
|
367
|
-
from ggplot2_py.draw_key import draw_key_point as _draw_key_point
|
|
368
|
-
draw_key_fn = _draw_key_point
|
|
369
|
-
if layers:
|
|
370
|
-
for layer in layers:
|
|
371
|
-
geom = getattr(layer, "geom", None)
|
|
372
|
-
if geom is not None and hasattr(geom, "draw_key"):
|
|
373
|
-
draw_key_fn = geom.draw_key
|
|
374
|
-
break
|
|
375
494
|
|
|
376
495
|
# ------------------------------------------------------------------
|
|
377
496
|
# 5. Build each guide as an independent Gtable
|
|
@@ -537,11 +656,27 @@ def _table_add_legends(
|
|
|
537
656
|
continue
|
|
538
657
|
|
|
539
658
|
# --- Legend path: discrete scales ---
|
|
540
|
-
|
|
541
|
-
ncol =
|
|
659
|
+
# Mirror R ``GuideLegend$setup_params`` (``guide-legend.R:286-298``):
|
|
660
|
+
# vertical direction defaults to ``ncol = ceiling(n_breaks / 20)``,
|
|
661
|
+
# then ``nrow = ceiling(n_breaks / ncol)``. Previously hardcoded
|
|
662
|
+
# ``ncol = 1`` caused any legend with more than 20 entries to pile
|
|
663
|
+
# all wrapped entries into a single physical column (multi-column
|
|
664
|
+
# positions were computed by ``arrange_legend_layout`` but only
|
|
665
|
+
# one column width was allocated in the gtable), producing
|
|
666
|
+
# overlapping key + label glyphs per row.
|
|
667
|
+
ncol = max(1, math.ceil(n_breaks / 20))
|
|
668
|
+
nrow = max(1, math.ceil(n_breaks / ncol))
|
|
669
|
+
|
|
670
|
+
# Per-entry draw_key: mirror R's ``matched_aes`` /
|
|
671
|
+
# ``include_layer_in_guide`` so ``geom_segment`` / ``geom_path``
|
|
672
|
+
# layers that don't map the guide's aesthetic can't hijack the
|
|
673
|
+
# legend key glyph.
|
|
674
|
+
entry_draw_key_fn, entry_layers = _resolve_draw_key_for_entry(
|
|
675
|
+
entry, layers,
|
|
676
|
+
)
|
|
542
677
|
|
|
543
678
|
decor = build_legend_decor(
|
|
544
|
-
entry,
|
|
679
|
+
entry, entry_draw_key_fn, entry_layers,
|
|
545
680
|
key_width_cm=KEY_W_CM, key_height_cm=KEY_H_CM,
|
|
546
681
|
theme=theme,
|
|
547
682
|
)
|
ggplot2_py/theme_elements.py
CHANGED
|
@@ -1425,16 +1425,26 @@ class _TitleGrob(GTree):
|
|
|
1425
1425
|
self._title_widths = widths
|
|
1426
1426
|
self._title_heights = heights
|
|
1427
1427
|
|
|
1428
|
-
# R: widthDetails.titleGrob
|
|
1428
|
+
# R: widthDetails.titleGrob <- function(x) sum(x$widths)
|
|
1429
|
+
# (``ggplot2/R/margins.R:199-201``). R's ``sum()`` on a unit vector
|
|
1430
|
+
# dispatches to ``Summary.unit`` which wraps the full vector in a
|
|
1431
|
+
# *single-element* L_SUM compound (``grid/R/unit.R:300-347``). That
|
|
1432
|
+
# is what ``evaluateGrobUnit`` expects at ``grid/src/unit.c:535``,
|
|
1433
|
+
# where it reads ``transformWidthtoINCHES(unitx, 0, ...)``. Using
|
|
1434
|
+
# Python's builtin ``sum`` here would leave a multi-element unit
|
|
1435
|
+
# unchanged (because ``0 + Unit`` short-circuits to identity),
|
|
1436
|
+
# which makes grid_py read only the left-margin element and
|
|
1437
|
+
# underestimate the grob's width.
|
|
1429
1438
|
def width_details(self) -> Any:
|
|
1430
1439
|
if self._title_widths is not None:
|
|
1431
|
-
|
|
1440
|
+
from grid_py import unit_summary_sum
|
|
1441
|
+
return unit_summary_sum(self._title_widths)
|
|
1432
1442
|
return Unit(0, "cm")
|
|
1433
1443
|
|
|
1434
|
-
# R: heightDetails.titleGrob → sum(x$heights)
|
|
1435
1444
|
def height_details(self) -> Any:
|
|
1436
1445
|
if self._title_heights is not None:
|
|
1437
|
-
|
|
1446
|
+
from grid_py import unit_summary_sum
|
|
1447
|
+
return unit_summary_sum(self._title_heights)
|
|
1438
1448
|
return Unit(0, "cm")
|
|
1439
1449
|
|
|
1440
1450
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ggplot2-python
|
|
3
|
-
Version: 4.0.2.9000
|
|
3
|
+
Version: 4.0.2.9000.post1
|
|
4
4
|
Summary: Python port of the R ggplot2 package (tracks R ggplot2 4.0.2.9000)
|
|
5
5
|
Project-URL: Homepage, https://github.com/Bio-Babel/ggplot2-python
|
|
6
6
|
Project-URL: Repository, https://github.com/Bio-Babel/ggplot2-python
|
|
@@ -50,6 +50,8 @@ Description-Content-Type: text/markdown
|
|
|
50
50
|
|
|
51
51
|
# ggplot2_py <a href="https://github.com/R2pyBioinformatics/ggplot2_py"><img src="assets/ggplot2_py_logo.png" align="right" height="138" alt="ggplot2_py logo" /></a>
|
|
52
52
|
|
|
53
|
+
[](https://pypi.org/project/ggplot2-python/)
|
|
54
|
+
|
|
53
55
|
AI-assisted Python port of the R **ggplot2** package — Create Elegant Data Visualisations Using the Grammar of Graphics.
|
|
54
56
|
|
|
55
57
|
## Overview
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
ggplot2_py/__init__.py,sha256=
|
|
1
|
+
ggplot2_py/__init__.py,sha256=Ru2gTGufNQhiqAruJJkR8DjJmXBQERgYpAE93AlQD0Q,24417
|
|
2
2
|
ggplot2_py/_compat.py,sha256=sLzzhq0ii2eSx1xMRaHemNnNqUd6FfT3wRierE1EXCg,10670
|
|
3
3
|
ggplot2_py/_plugins.py,sha256=TLsSEfN5L5VyWtsHaV_fms3TjQezUDUWWEj8ctVxK88,4323
|
|
4
4
|
ggplot2_py/_utils.py,sha256=Qyh2ORUgLlT_rgJ9IaJpzej6G7BIK13nzBR1hF5Zjtw,13612
|
|
@@ -21,7 +21,7 @@ ggplot2_py/layer.py,sha256=de6GyIir0L7GLgGQ3e4w3VTKOEwW5h34zmZvlmCg5ZA,29346
|
|
|
21
21
|
ggplot2_py/layout.py,sha256=QkaGP3anrQYS8vIFMlsz3jKmblHUTYGSevCXIMGlDns,25341
|
|
22
22
|
ggplot2_py/limits.py,sha256=e1WezeXnqPUKn6aaNgyDkLkF1JbxustqfQorKrmjPHI,8219
|
|
23
23
|
ggplot2_py/plot.py,sha256=tMdrEosF9la9rK_QxcRGjFMWvcO7xBg8eG2_kMZy_fM,41102
|
|
24
|
-
ggplot2_py/plot_render.py,sha256=
|
|
24
|
+
ggplot2_py/plot_render.py,sha256=JIatuPPdBjb6ujKjagTWOYlOzwzPw2zEw3WGGQ0Tesg,35105
|
|
25
25
|
ggplot2_py/position.py,sha256=weKCXpnDtif0NZ9cZqOEs4J96GJhMxUZiBFhZgt0IFM,34339
|
|
26
26
|
ggplot2_py/protocols.py,sha256=7d34T7v7NLg3N1bSEPmmPFrV-5BZvnzBsbpiqnDatZA,5353
|
|
27
27
|
ggplot2_py/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -31,7 +31,7 @@ ggplot2_py/scale.py,sha256=ufYdBhtEv-_KlXYFTzX6iHjS5fd0DTmR1VpE02gSLb8,82254
|
|
|
31
31
|
ggplot2_py/stat.py,sha256=airtZLlQMoOAMnrn62DoQSMHzAMf8zVEQBQdldl8iWQ,167192
|
|
32
32
|
ggplot2_py/theme.py,sha256=23cbJ64wkfFyw4g474yBFzjFBLtKp3vv61iDdsUA64I,13546
|
|
33
33
|
ggplot2_py/theme_defaults.py,sha256=iA_kJBjjzLpsG7vHzEL5ma7t_9INiy2e6XXtZCfWH68,36311
|
|
34
|
-
ggplot2_py/theme_elements.py,sha256=
|
|
34
|
+
ggplot2_py/theme_elements.py,sha256=Q2z8JZOTj--ntcwY7kPa4b2uri7rR-QoPgK9Rux4E94,64255
|
|
35
35
|
ggplot2_py/coords/__init__.py,sha256=EB2z4ocd6jRlf2f95-_F8xD4aLvf2QFMmqos2PUixiw,834
|
|
36
36
|
ggplot2_py/geoms/__init__.py,sha256=iUCIceJbLSD4TjOtBLCn-8BK2jHVyIJBVYfjTt9BdIA,293
|
|
37
37
|
ggplot2_py/guides/__init__.py,sha256=YPw0bfL6l-xpduIJuwPcq4KVtnJVgOqLcYHzxTh-Fbc,204
|
|
@@ -48,7 +48,7 @@ ggplot2_py/resources/seals.csv,sha256=VEuSEBLz3Fj14eBdM6hp6M6GpH2lqN5ekT_d2Mk1rK
|
|
|
48
48
|
ggplot2_py/resources/txhousing.csv,sha256=RdHoH5W9bud_DzsefIc8yNOFaxMl6IDDKMiP68aoIoY,523993
|
|
49
49
|
ggplot2_py/scales/__init__.py,sha256=n1NLGW0cOeFxOYkj_yS2rvec4p9N1ZJBsugPG2TOHgY,100013
|
|
50
50
|
ggplot2_py/stats/__init__.py,sha256=X_WSwq3jS2iJPGy7jEu_tXSQvVrIvC-5pm4ag2qTLVI,222
|
|
51
|
-
ggplot2_python-4.0.2.9000.dist-info/METADATA,sha256=
|
|
52
|
-
ggplot2_python-4.0.2.9000.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
53
|
-
ggplot2_python-4.0.2.9000.dist-info/licenses/LICENSE,sha256=E0GJjw07cYRmJYj_8UZKbhevWQ-Swd3nVR6qBddvz9c,39
|
|
54
|
-
ggplot2_python-4.0.2.9000.dist-info/RECORD,,
|
|
51
|
+
ggplot2_python-4.0.2.9000.post1.dist-info/METADATA,sha256=sx0qj7GIFOoFlxb50W4MvJS1BcryyG4cDjjfSeToufw,8336
|
|
52
|
+
ggplot2_python-4.0.2.9000.post1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
53
|
+
ggplot2_python-4.0.2.9000.post1.dist-info/licenses/LICENSE,sha256=E0GJjw07cYRmJYj_8UZKbhevWQ-Swd3nVR6qBddvz9c,39
|
|
54
|
+
ggplot2_python-4.0.2.9000.post1.dist-info/RECORD,,
|
|
File without changes
|
{ggplot2_python-4.0.2.9000.dist-info → ggplot2_python-4.0.2.9000.post1.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|