ggplot2-python 4.0.2.9000.post2__tar.gz → 4.0.2.9000.post4__tar.gz

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.
Files changed (60) hide show
  1. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/PKG-INFO +49 -8
  2. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/README.md +48 -7
  3. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/__init__.py +112 -10
  4. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/_compat.py +53 -6
  5. ggplot2_python-4.0.2.9000.post4/ggplot2_py/_defaults.py +169 -0
  6. ggplot2_python-4.0.2.9000.post4/ggplot2_py/_make_constructor.py +440 -0
  7. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/_utils.py +243 -16
  8. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/aes.py +56 -14
  9. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/annotation.py +21 -30
  10. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/facet.py +786 -219
  11. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/geom.py +54 -30
  12. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/guide.py +12 -5
  13. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/guide_axis.py +62 -47
  14. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/guide_colourbar.py +53 -7
  15. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/guide_legend.py +41 -33
  16. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/layer.py +17 -3
  17. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/plot.py +181 -42
  18. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/plot_render.py +502 -91
  19. ggplot2_python-4.0.2.9000.post4/ggplot2_py/protocols.py +202 -0
  20. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/save.py +8 -3
  21. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/scale.py +162 -4
  22. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/scales/__init__.py +25 -3
  23. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/stat.py +21 -4
  24. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/theme_defaults.py +13 -12
  25. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/theme_elements.py +48 -8
  26. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/pyproject.toml +1 -1
  27. ggplot2_python-4.0.2.9000.post2/ggplot2_py/protocols.py +0 -171
  28. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/.gitattributes +0 -0
  29. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/.gitignore +0 -0
  30. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/LICENSE +0 -0
  31. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/_plugins.py +0 -0
  32. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/autoplot.py +0 -0
  33. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/coord.py +0 -0
  34. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/coords/__init__.py +0 -0
  35. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/datasets.py +0 -0
  36. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/draw_key.py +0 -0
  37. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/fortify.py +0 -0
  38. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/geoms/__init__.py +0 -0
  39. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/ggproto.py +0 -0
  40. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/guides/__init__.py +0 -0
  41. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/labeller.py +0 -0
  42. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/labels.py +0 -0
  43. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/layout.py +0 -0
  44. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/limits.py +0 -0
  45. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/position.py +0 -0
  46. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/py.typed +0 -0
  47. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/qplot.py +0 -0
  48. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/resources/diamonds.csv +0 -0
  49. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/resources/economics.csv +0 -0
  50. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/resources/economics_long.csv +0 -0
  51. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/resources/faithfuld.csv +0 -0
  52. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/resources/luv_colours.csv +0 -0
  53. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/resources/midwest.csv +0 -0
  54. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/resources/mpg.csv +0 -0
  55. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/resources/msleep.csv +0 -0
  56. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/resources/presidential.csv +0 -0
  57. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/resources/seals.csv +0 -0
  58. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/resources/txhousing.csv +0 -0
  59. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/stats/__init__.py +0 -0
  60. {ggplot2_python-4.0.2.9000.post2 → ggplot2_python-4.0.2.9000.post4}/ggplot2_py/theme.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ggplot2-python
3
- Version: 4.0.2.9000.post2
3
+ Version: 4.0.2.9000.post4
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
@@ -48,17 +48,17 @@ Requires-Dist: mkdocs-material; extra == 'docs'
48
48
  Requires-Dist: mkdocstrings[python]; extra == 'docs'
49
49
  Description-Content-Type: text/markdown
50
50
 
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>
51
+ # ggplot2-python <a href="https://github.com/Bio-Babel/ggplot2-python"><img src="assets/ggplot2_py_logo.png" align="right" height="138" alt="ggplot2-python logo" /></a>
52
52
 
53
53
  [![PyPI](https://img.shields.io/pypi/v/ggplot2-python)](https://pypi.org/project/ggplot2-python/)
54
54
 
55
- AI-assisted Python port of the R **ggplot2** package — Create Elegant Data Visualisations Using the Grammar of Graphics.
55
+ AI-assisted port of the python **ggplot2** package — Create Elegant Data Visualisations Using the Grammar of Graphics.
56
56
 
57
57
  ## Overview
58
58
 
59
- ggplot2_py implements the grammar of graphics in Python, faithfully porting R's ggplot2 using pandas DataFrames as the data container and a Cairo-based rendering backend. It supports 47 geoms, 32 stats, faceting, coordinate systems, themes, guides, and 130+ scales.
59
+ ggplot2-python implements the grammar of graphics in Python, faithfully porting R's ggplot2 using pandas DataFrames as the data container and a Cairo-based rendering backend. It supports 47 geoms, 32 stats, faceting, coordinate systems, themes, guides, and 130+ scales.
60
60
 
61
- Beyond a direct port, ggplot2_py adds **Python-exclusive features** that extend the Grammar of Graphics with Python-native idioms while preserving full orthogonality of GOG components.
61
+ Beyond a direct port, ggplot2-python adds **Python-exclusive features** that extend the Grammar of Graphics with Python-native idioms while preserving full orthogonality of GOG components.
62
62
 
63
63
  ## Python-Exclusive Features
64
64
 
@@ -73,6 +73,7 @@ These capabilities have no R equivalent and leverage Python-specific language fe
73
73
  | **Auto-registration** | `__init_subclass__` | `class GeomStar(Geom): ...` auto-registers; no manual wiring needed |
74
74
  | **Protocol contracts** | `typing.Protocol` | `isinstance(my_geom, GeomProtocol)` — structural type checking for extensions |
75
75
  | **Scoped defaults** | `contextvars.ContextVar` | `with ggplot_defaults(theme=theme_minimal()): ...` — thread-safe scoped defaults |
76
+ | **Functional composition** | `sum` / `reduce` over `__add__` | `sum(parts, start=ggplot(data))` — compose plots without the `+` operator, useful for programmatic plot construction |
76
77
 
77
78
  ## Installation
78
79
 
@@ -85,7 +86,7 @@ For a local development:
85
86
 
86
87
  ```bash
87
88
  git clone https://github.com/Bio-Babel/ggplot2-python.git
88
- cd ggplot2_py
89
+ cd ggplot2-python
89
90
  pip install -e ".[dev]"
90
91
  ```
91
92
 
@@ -145,6 +146,46 @@ with ggplot_defaults(theme=theme_minimal()):
145
146
  # Outside: no defaults
146
147
  ```
147
148
 
149
+ ### Functional composition with `sum()` / `reduce()` (Python-exclusive)
150
+
151
+ The `+` operator is the canonical ggplot2 syntax. Because `GGPlot.__add__` is defined and every component family is registered with
152
+ the `update_ggplot` singledispatch generic, **Python's iterable-composition
153
+ idioms also work directly** — useful for programmatic plot building, list
154
+ comprehensions, or just function-style code:
155
+
156
+ ```python
157
+ # 1) `sum(parts, start=ggplot(data))` — the canonical function-style form
158
+ def fnplot(data, *parts):
159
+ return sum(parts, start=ggplot(data))
160
+
161
+ fnplot(
162
+ mpg,
163
+ aes(x="displ", y="hwy", colour="class"),
164
+ geom_point(),
165
+ geom_smooth(method="lm"),
166
+ facet_wrap("drv"),
167
+ theme_minimal(),
168
+ )
169
+
170
+ # 2) `sum` over an iterable, no helper needed:
171
+ sum(
172
+ [aes(x="displ", y="hwy"), geom_point(), theme_minimal()],
173
+ start=ggplot(mpg),
174
+ )
175
+
176
+ # 3) `functools.reduce` — the canonical Python composition operator:
177
+ from functools import reduce
178
+ from operator import add
179
+ reduce(add, [aes(x="displ", y="hwy"), geom_point(), theme_minimal()], ggplot(mpg))
180
+
181
+ # 4) List on the RHS of `+` — recursive add via the list-dispatch:
182
+ ggplot(mpg, aes("displ", "hwy")) + [geom_point(), geom_smooth(), theme_minimal()]
183
+ ```
184
+
185
+ > One caveat: Python's built-in `sum` has the signature
186
+ > `sum(iterable, /, start=0)` — it accepts *one* iterable plus an optional
187
+ > `start`, **not** variadic arguments. `sum(a, b, c, d)` raises `TypeError`;
188
+
148
189
  ## Tutorials
149
190
 
150
191
  ### User Tutorials
@@ -160,11 +201,11 @@ with ggplot_defaults(theme=theme_minimal()):
160
201
  - [Build Hooks](tutorials/build_hooks.ipynb) — intercepting the 16-stage build pipeline
161
202
 
162
203
  ### Developer Guide
163
- - [Developer Guide: Extending ggplot2_py](tutorials/developer_guide.ipynb) — comprehensive guide covering ggproto system, custom Stat/Geom creation, Protocol contracts, singledispatch, hooks, auto-registration, context manager, and packaging
204
+ - [Developer Guide: Extending ggplot2-python](tutorials/developer_guide.ipynb) — comprehensive guide covering ggproto system, custom Stat/Geom creation, Protocol contracts, singledispatch, hooks, auto-registration, context manager, and packaging
164
205
 
165
206
  ## Extension Architecture
166
207
 
167
- ggplot2_py is designed as an **extensible platform**. The following table summarises all extension points:
208
+ ggplot2-python is designed as an **extensible platform**. The following table summarises all extension points:
168
209
 
169
210
  | Extension point | Mechanism | How to use |
170
211
  |----------------|-----------|-----------|
@@ -1,14 +1,14 @@
1
- # 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>
1
+ # ggplot2-python <a href="https://github.com/Bio-Babel/ggplot2-python"><img src="assets/ggplot2_py_logo.png" align="right" height="138" alt="ggplot2-python logo" /></a>
2
2
 
3
3
  [![PyPI](https://img.shields.io/pypi/v/ggplot2-python)](https://pypi.org/project/ggplot2-python/)
4
4
 
5
- AI-assisted Python port of the R **ggplot2** package — Create Elegant Data Visualisations Using the Grammar of Graphics.
5
+ AI-assisted port of the python **ggplot2** package — Create Elegant Data Visualisations Using the Grammar of Graphics.
6
6
 
7
7
  ## Overview
8
8
 
9
- ggplot2_py implements the grammar of graphics in Python, faithfully porting R's ggplot2 using pandas DataFrames as the data container and a Cairo-based rendering backend. It supports 47 geoms, 32 stats, faceting, coordinate systems, themes, guides, and 130+ scales.
9
+ ggplot2-python implements the grammar of graphics in Python, faithfully porting R's ggplot2 using pandas DataFrames as the data container and a Cairo-based rendering backend. It supports 47 geoms, 32 stats, faceting, coordinate systems, themes, guides, and 130+ scales.
10
10
 
11
- Beyond a direct port, ggplot2_py adds **Python-exclusive features** that extend the Grammar of Graphics with Python-native idioms while preserving full orthogonality of GOG components.
11
+ Beyond a direct port, ggplot2-python adds **Python-exclusive features** that extend the Grammar of Graphics with Python-native idioms while preserving full orthogonality of GOG components.
12
12
 
13
13
  ## Python-Exclusive Features
14
14
 
@@ -23,6 +23,7 @@ These capabilities have no R equivalent and leverage Python-specific language fe
23
23
  | **Auto-registration** | `__init_subclass__` | `class GeomStar(Geom): ...` auto-registers; no manual wiring needed |
24
24
  | **Protocol contracts** | `typing.Protocol` | `isinstance(my_geom, GeomProtocol)` — structural type checking for extensions |
25
25
  | **Scoped defaults** | `contextvars.ContextVar` | `with ggplot_defaults(theme=theme_minimal()): ...` — thread-safe scoped defaults |
26
+ | **Functional composition** | `sum` / `reduce` over `__add__` | `sum(parts, start=ggplot(data))` — compose plots without the `+` operator, useful for programmatic plot construction |
26
27
 
27
28
  ## Installation
28
29
 
@@ -35,7 +36,7 @@ For a local development:
35
36
 
36
37
  ```bash
37
38
  git clone https://github.com/Bio-Babel/ggplot2-python.git
38
- cd ggplot2_py
39
+ cd ggplot2-python
39
40
  pip install -e ".[dev]"
40
41
  ```
41
42
 
@@ -95,6 +96,46 @@ with ggplot_defaults(theme=theme_minimal()):
95
96
  # Outside: no defaults
96
97
  ```
97
98
 
99
+ ### Functional composition with `sum()` / `reduce()` (Python-exclusive)
100
+
101
+ The `+` operator is the canonical ggplot2 syntax. Because `GGPlot.__add__` is defined and every component family is registered with
102
+ the `update_ggplot` singledispatch generic, **Python's iterable-composition
103
+ idioms also work directly** — useful for programmatic plot building, list
104
+ comprehensions, or just function-style code:
105
+
106
+ ```python
107
+ # 1) `sum(parts, start=ggplot(data))` — the canonical function-style form
108
+ def fnplot(data, *parts):
109
+ return sum(parts, start=ggplot(data))
110
+
111
+ fnplot(
112
+ mpg,
113
+ aes(x="displ", y="hwy", colour="class"),
114
+ geom_point(),
115
+ geom_smooth(method="lm"),
116
+ facet_wrap("drv"),
117
+ theme_minimal(),
118
+ )
119
+
120
+ # 2) `sum` over an iterable, no helper needed:
121
+ sum(
122
+ [aes(x="displ", y="hwy"), geom_point(), theme_minimal()],
123
+ start=ggplot(mpg),
124
+ )
125
+
126
+ # 3) `functools.reduce` — the canonical Python composition operator:
127
+ from functools import reduce
128
+ from operator import add
129
+ reduce(add, [aes(x="displ", y="hwy"), geom_point(), theme_minimal()], ggplot(mpg))
130
+
131
+ # 4) List on the RHS of `+` — recursive add via the list-dispatch:
132
+ ggplot(mpg, aes("displ", "hwy")) + [geom_point(), geom_smooth(), theme_minimal()]
133
+ ```
134
+
135
+ > One caveat: Python's built-in `sum` has the signature
136
+ > `sum(iterable, /, start=0)` — it accepts *one* iterable plus an optional
137
+ > `start`, **not** variadic arguments. `sum(a, b, c, d)` raises `TypeError`;
138
+
98
139
  ## Tutorials
99
140
 
100
141
  ### User Tutorials
@@ -110,11 +151,11 @@ with ggplot_defaults(theme=theme_minimal()):
110
151
  - [Build Hooks](tutorials/build_hooks.ipynb) — intercepting the 16-stage build pipeline
111
152
 
112
153
  ### Developer Guide
113
- - [Developer Guide: Extending ggplot2_py](tutorials/developer_guide.ipynb) — comprehensive guide covering ggproto system, custom Stat/Geom creation, Protocol contracts, singledispatch, hooks, auto-registration, context manager, and packaging
154
+ - [Developer Guide: Extending ggplot2-python](tutorials/developer_guide.ipynb) — comprehensive guide covering ggproto system, custom Stat/Geom creation, Protocol contracts, singledispatch, hooks, auto-registration, context manager, and packaging
114
155
 
115
156
  ## Extension Architecture
116
157
 
117
- ggplot2_py is designed as an **extensible platform**. The following table summarises all extension points:
158
+ ggplot2-python is designed as an **extensible platform**. The following table summarises all extension points:
118
159
 
119
160
  | Extension point | Mechanism | How to use |
120
161
  |----------------|-----------|-----------|
@@ -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.post1"
10
+ __version__ = "4.0.2.9000.post4"
11
11
  __r_commit__ = "c02c05a"
12
12
 
13
13
  # ---------------------------------------------------------------------------
@@ -153,6 +153,10 @@ from ggplot2_py.geom import (
153
153
  GeomQuantile,
154
154
  GeomJitter,
155
155
  GeomSf,
156
+ GeomCustomAnn,
157
+ GeomLogticks,
158
+ GeomRasterAnn,
159
+ translate_shape_string,
156
160
  geom_point,
157
161
  geom_path,
158
162
  geom_line,
@@ -420,12 +424,14 @@ from ggplot2_py.scales import (
420
424
  scale_size_datetime,
421
425
  scale_radius,
422
426
  scale_shape,
427
+ scale_shape_continuous,
423
428
  scale_shape_discrete,
424
429
  scale_shape_binned,
425
430
  scale_shape_identity,
426
431
  scale_shape_manual,
427
432
  scale_shape_ordinal,
428
433
  scale_linetype,
434
+ scale_linetype_continuous,
429
435
  scale_linetype_discrete,
430
436
  scale_linetype_binned,
431
437
  scale_linetype_identity,
@@ -492,7 +498,11 @@ from ggplot2_py.facet import (
492
498
  facet_grid,
493
499
  facet_wrap,
494
500
  is_facet,
501
+ wrap_dims,
502
+ max_height,
503
+ max_width,
495
504
  )
505
+ from ggplot2_py.layout import Layout
496
506
 
497
507
  # ---------------------------------------------------------------------------
498
508
  # Position adjustments
@@ -549,6 +559,14 @@ from ggplot2_py.guide import (
549
559
  guide_none,
550
560
  guides,
551
561
  is_guide,
562
+ is_guides,
563
+ new_guide,
564
+ old_guide,
565
+ guide_geom,
566
+ guide_train,
567
+ guide_merge,
568
+ guide_transform,
569
+ guide_gengrob,
552
570
  )
553
571
 
554
572
  # ---------------------------------------------------------------------------
@@ -567,6 +585,7 @@ from ggplot2_py.theme import (
567
585
  reset_theme_settings,
568
586
  update_theme,
569
587
  replace_theme,
588
+ from_theme,
570
589
  )
571
590
  from ggplot2_py.theme_elements import (
572
591
  Element,
@@ -582,6 +601,7 @@ from ggplot2_py.theme_elements import (
582
601
  el_def,
583
602
  merge_element,
584
603
  is_theme_element,
604
+ is_margin,
585
605
  Margin,
586
606
  margin,
587
607
  margin_auto,
@@ -603,6 +623,17 @@ from ggplot2_py.theme_defaults import (
603
623
  theme_classic,
604
624
  theme_void,
605
625
  theme_test,
626
+ theme_sub_axis,
627
+ theme_sub_axis_x,
628
+ theme_sub_axis_y,
629
+ theme_sub_axis_top,
630
+ theme_sub_axis_bottom,
631
+ theme_sub_axis_left,
632
+ theme_sub_axis_right,
633
+ theme_sub_legend,
634
+ theme_sub_panel,
635
+ theme_sub_plot,
636
+ theme_sub_strip,
606
637
  )
607
638
 
608
639
  # ---------------------------------------------------------------------------
@@ -668,7 +699,19 @@ from ggplot2_py.autoplot import autoplot, autolayer
668
699
  # ---------------------------------------------------------------------------
669
700
  # Utility re-exports (matching R namespace)
670
701
  # ---------------------------------------------------------------------------
671
- from ggplot2_py._utils import resolution, remove_missing
702
+ from ggplot2_py._utils import (
703
+ resolution, remove_missing,
704
+ cut_interval, cut_number, cut_width,
705
+ transform_position, fill_alpha, pattern_alpha,
706
+ )
707
+ from ggplot2_py._defaults import (
708
+ update_geom_defaults, update_stat_defaults,
709
+ reset_geom_defaults, reset_stat_defaults,
710
+ get_geom_defaults,
711
+ )
712
+ from ggplot2_py._make_constructor import make_constructor
713
+ # zeroGrob alias for R parity (ggplot2 imports it from grid)
714
+ from grid_py import null_grob as zeroGrob
672
715
 
673
716
  # grid re-exports (matching R: importFrom(grid, unit, arrow))
674
717
  from grid_py import Unit as unit, arrow
@@ -739,6 +782,9 @@ __all__ = [
739
782
  "geom_sf", "geom_sf_label", "geom_sf_text",
740
783
  "geom_qq", "geom_qq_line",
741
784
  "is_geom",
785
+ "GeomMap", "GeomQuantile",
786
+ "GeomCustomAnn", "GeomLogticks", "GeomRasterAnn",
787
+ "translate_shape_string",
742
788
  # Stat classes
743
789
  "Stat", "StatIdentity", "StatBin", "StatCount", "StatDensity",
744
790
  "StatSmooth", "StatBoxplot", "StatSummary", "StatSummaryBin",
@@ -796,21 +842,51 @@ __all__ = [
796
842
  "scale_color_viridis_c", "scale_color_viridis_d",
797
843
  "scale_color_grey", "scale_color_identity", "scale_color_manual",
798
844
  "scale_alpha", "scale_alpha_continuous", "scale_alpha_discrete",
845
+ "scale_alpha_binned", "scale_alpha_identity", "scale_alpha_manual",
846
+ "scale_alpha_ordinal", "scale_alpha_date", "scale_alpha_datetime",
799
847
  "scale_size", "scale_size_continuous", "scale_size_area",
800
- "scale_shape", "scale_shape_discrete",
801
- "scale_linetype", "scale_linetype_discrete",
802
- "scale_linewidth", "scale_linewidth_continuous",
848
+ "scale_size_binned", "scale_size_binned_area",
849
+ "scale_size_discrete", "scale_size_identity", "scale_size_manual",
850
+ "scale_size_ordinal", "scale_size_date", "scale_size_datetime",
851
+ "scale_radius",
852
+ "scale_shape", "scale_shape_continuous", "scale_shape_discrete",
853
+ "scale_shape_binned", "scale_shape_identity", "scale_shape_manual",
854
+ "scale_shape_ordinal",
855
+ "scale_linetype", "scale_linetype_continuous", "scale_linetype_discrete",
856
+ "scale_linetype_binned", "scale_linetype_identity", "scale_linetype_manual",
857
+ "scale_linewidth", "scale_linewidth_continuous", "scale_linewidth_discrete",
858
+ "scale_linewidth_binned", "scale_linewidth_identity", "scale_linewidth_manual",
859
+ "scale_linewidth_ordinal",
860
+ "scale_linewidth_date", "scale_linewidth_datetime",
803
861
  "scale_stroke", "scale_stroke_continuous",
862
+ # Binned / stepped / fermenter / distiller / ordinal / date colour & fill scales
863
+ "scale_colour_binned", "scale_colour_distiller", "scale_colour_fermenter",
864
+ "scale_colour_ordinal", "scale_colour_steps", "scale_colour_steps2",
865
+ "scale_colour_stepsn", "scale_colour_viridis_b",
866
+ "scale_colour_date", "scale_colour_datetime",
867
+ "scale_color_binned", "scale_color_distiller", "scale_color_fermenter",
868
+ "scale_color_ordinal", "scale_color_steps", "scale_color_steps2",
869
+ "scale_color_stepsn", "scale_color_viridis_b",
870
+ "scale_color_date", "scale_color_datetime",
871
+ "scale_fill_binned", "scale_fill_distiller", "scale_fill_fermenter",
872
+ "scale_fill_ordinal", "scale_fill_steps", "scale_fill_steps2",
873
+ "scale_fill_stepsn", "scale_fill_viridis_b",
874
+ "scale_fill_date", "scale_fill_datetime",
875
+ # Time / identity / manual variants
876
+ "scale_x_time", "scale_y_time",
877
+ "scale_continuous_identity", "scale_discrete_identity", "scale_discrete_manual",
804
878
  # Coords
805
879
  "Coord", "CoordCartesian", "CoordFixed", "CoordFlip",
806
880
  "CoordPolar", "CoordQuickmap", "CoordRadial", "CoordTrans", "CoordTransform",
807
881
  "coord_cartesian", "coord_equal", "coord_fixed", "coord_flip",
808
882
  "coord_polar", "coord_quickmap", "coord_radial", "coord_sf", "coord_trans", "coord_transform",
809
883
  "CoordSf",
810
- "coord_munch", "is_coord",
884
+ "coord_munch", "is_coord", "sf_transform_xy",
811
885
  # Facets
812
886
  "Facet", "FacetNull", "FacetGrid", "FacetWrap",
813
- "facet_null", "facet_grid", "facet_wrap", "is_facet",
887
+ "facet_null", "facet_grid", "facet_wrap", "is_facet", "wrap_dims",
888
+ "max_height", "max_width",
889
+ "Layout",
814
890
  # Positions
815
891
  "Position", "PositionIdentity", "PositionDodge", "PositionDodge2",
816
892
  "PositionJitter", "PositionJitterdodge", "PositionNudge",
@@ -826,20 +902,31 @@ __all__ = [
826
902
  "guide_old_colourbar", "guide_old_colorbar",
827
903
  "guide_coloursteps", "guide_colorsteps", "guide_bins",
828
904
  "guide_custom", "guide_none", "guides", "is_guide",
905
+ "guide_axis_logticks", "guide_axis_stack", "guide_axis_theta",
906
+ "is_guides", "new_guide", "old_guide",
907
+ "guide_geom", "guide_train", "guide_merge", "guide_transform",
908
+ "guide_gengrob",
829
909
  # Themes
830
910
  "theme", "is_theme", "complete_theme",
831
911
  "theme_get", "theme_set", "theme_update", "theme_replace",
832
912
  "set_theme", "get_theme", "reset_theme_settings",
913
+ "update_theme", "replace_theme", "from_theme",
833
914
  "Element", "element_blank", "element_line", "element_rect",
834
915
  "element_text", "element_point", "element_polygon", "element_geom",
835
916
  "element_grob", "element_render", "merge_element",
836
- "Margin", "margin", "Rel", "rel",
917
+ "el_def", "is_theme_element", "is_margin",
918
+ "Margin", "margin", "margin_auto", "margin_part", "Rel", "rel",
837
919
  "calc_element", "get_element_tree", "register_theme_elements",
838
920
  "theme_grey", "theme_gray", "theme_bw", "theme_linedraw",
839
921
  "theme_light", "theme_dark", "theme_minimal", "theme_classic",
840
922
  "theme_void", "theme_test",
923
+ "theme_sub_axis", "theme_sub_axis_x", "theme_sub_axis_y",
924
+ "theme_sub_axis_top", "theme_sub_axis_bottom",
925
+ "theme_sub_axis_left", "theme_sub_axis_right",
926
+ "theme_sub_legend", "theme_sub_panel", "theme_sub_plot",
927
+ "theme_sub_strip",
841
928
  # Labels, limits
842
- "labs", "xlab", "ylab", "ggtitle",
929
+ "labs", "xlab", "ylab", "ggtitle", "update_labels",
843
930
  "lims", "xlim", "ylim", "expand_limits",
844
931
  # Annotations
845
932
  "annotate", "annotation_custom", "annotation_raster", "annotation_logticks",
@@ -861,10 +948,25 @@ __all__ = [
861
948
  "autoplot", "autolayer",
862
949
  # Utilities
863
950
  "resolution", "remove_missing",
951
+ "cut_interval", "cut_number", "cut_width",
952
+ "transform_position", "fill_alpha", "pattern_alpha",
953
+ "update_geom_defaults", "update_stat_defaults",
954
+ "reset_geom_defaults", "reset_stat_defaults",
955
+ "get_geom_defaults",
956
+ "make_constructor",
864
957
  "unit", "arrow", "alpha",
865
- "PT", "STROKE",
958
+ "PT", "STROKE", "zeroGrob",
959
+ # Submodule namespaces and remaining R-exported helpers already
960
+ # defined at top level (just need to surface for ``import *``).
961
+ "stat", "labeller",
962
+ "derive", "flip_data", "flipped_names", "has_flipped_aes",
866
963
  # Plugin discovery
867
964
  "discover_extensions", "list_extensions",
965
+ # Python-exclusive extension surface (README quickstart uses
966
+ # ``from ggplot2_py import *``; these would otherwise be invisible)
967
+ "ggplot_defaults", "BuildStage",
968
+ "GeomProtocol", "StatProtocol", "ScaleProtocol",
969
+ "CoordProtocol", "FacetProtocol", "PositionProtocol",
868
970
  ]
869
971
 
870
972
  # ---------------------------------------------------------------------------
@@ -32,6 +32,8 @@ __all__ = [
32
32
  "Waiver",
33
33
  "is_waiver",
34
34
  "waiver",
35
+ "NA",
36
+ "is_na",
35
37
  "caller_arg",
36
38
  ]
37
39
 
@@ -103,22 +105,24 @@ def cli_inform(
103
105
  call: Optional[str] = None,
104
106
  **kwargs: Any,
105
107
  ) -> None:
106
- """Emit an informational message (no-op by default).
108
+ """Emit an informational message via Python's :mod:`warnings`.
107
109
 
108
- In interactive sessions this could print; in batch mode it stays silent.
109
- Override by monkey-patching if verbose output is desired.
110
+ Mirrors R ``cli::cli_inform`` / ``rlang::inform`` which always write
111
+ to stderr regardless of session type. Routing through ``warnings``
112
+ lets pytest, ``warnings.catch_warnings``, and user filters capture
113
+ or silence the message — matching the way R lets users wrap
114
+ ``suppressMessages({...})`` around an expression.
110
115
 
111
116
  Parameters
112
117
  ----------
113
118
  message : str
114
119
  Informational message.
115
120
  call : str, optional
116
- Name of the calling function.
121
+ Name of the calling function (unused; matches R signature).
117
122
  **kwargs : Any
118
123
  Substitution values for placeholders in *message*.
119
124
  """
120
- # Intentionally silent – mirrors rlang::inform() in non-interactive R.
121
- pass
125
+ warnings.warn(message, UserWarning, stacklevel=2)
122
126
 
123
127
 
124
128
  # ---------------------------------------------------------------------------
@@ -436,6 +440,49 @@ def waiver() -> Waiver:
436
440
  return Waiver()
437
441
 
438
442
 
443
+ # ---------------------------------------------------------------------------
444
+ # NA sentinel (R parity)
445
+ # ---------------------------------------------------------------------------
446
+ #
447
+ # R distinguishes ``NULL`` (unspecified, inherits from parent) from ``NA``
448
+ # (explicitly absent, must NOT inherit). Python collapses both onto
449
+ # ``None`` by default, which silently breaks theme inheritance — e.g.
450
+ # ``element_rect(fill="grey92", colour=NA)`` should keep the missing
451
+ # border on merge, but Python would replace ``None`` with the parent
452
+ # ``rect`` element's ``"black"``. This sentinel preserves R's NA
453
+ # semantics through ``combine_elements``.
454
+
455
+ class _NA:
456
+ """Sentinel for R's ``NA`` — explicitly absent, never inherited."""
457
+
458
+ _instance: Optional["_NA"] = None
459
+
460
+ def __new__(cls) -> "_NA":
461
+ if cls._instance is None:
462
+ cls._instance = super().__new__(cls)
463
+ return cls._instance
464
+
465
+ def __repr__(self) -> str:
466
+ return "NA"
467
+
468
+ def __bool__(self) -> bool:
469
+ return False
470
+
471
+ def __eq__(self, other: Any) -> bool:
472
+ return isinstance(other, _NA)
473
+
474
+ def __hash__(self) -> int:
475
+ return 0
476
+
477
+
478
+ NA = _NA()
479
+
480
+
481
+ def is_na(x: Any) -> bool:
482
+ """Return True iff *x* is the singleton :data:`NA` sentinel."""
483
+ return isinstance(x, _NA)
484
+
485
+
439
486
  def is_waiver(x: Any) -> bool:
440
487
  """Check whether *x* is a ``Waiver`` sentinel.
441
488