ggplot2-python 4.0.2.9000.post2__py3-none-any.whl → 4.0.2.9000.post4__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 +112 -10
- ggplot2_py/_compat.py +53 -6
- ggplot2_py/_defaults.py +169 -0
- ggplot2_py/_make_constructor.py +440 -0
- ggplot2_py/_utils.py +243 -16
- ggplot2_py/aes.py +56 -14
- ggplot2_py/annotation.py +21 -30
- ggplot2_py/facet.py +786 -219
- ggplot2_py/geom.py +54 -30
- ggplot2_py/guide.py +12 -5
- ggplot2_py/guide_axis.py +62 -47
- ggplot2_py/guide_colourbar.py +53 -7
- ggplot2_py/guide_legend.py +41 -33
- ggplot2_py/layer.py +17 -3
- ggplot2_py/plot.py +181 -42
- ggplot2_py/plot_render.py +502 -91
- ggplot2_py/protocols.py +106 -75
- ggplot2_py/save.py +8 -3
- ggplot2_py/scale.py +162 -4
- ggplot2_py/scales/__init__.py +25 -3
- ggplot2_py/stat.py +21 -4
- ggplot2_py/theme_defaults.py +13 -12
- ggplot2_py/theme_elements.py +48 -8
- {ggplot2_python-4.0.2.9000.post2.dist-info → ggplot2_python-4.0.2.9000.post4.dist-info}/METADATA +49 -8
- {ggplot2_python-4.0.2.9000.post2.dist-info → ggplot2_python-4.0.2.9000.post4.dist-info}/RECORD +27 -25
- {ggplot2_python-4.0.2.9000.post2.dist-info → ggplot2_python-4.0.2.9000.post4.dist-info}/WHEEL +0 -0
- {ggplot2_python-4.0.2.9000.post2.dist-info → ggplot2_python-4.0.2.9000.post4.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.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
|
|
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
|
-
"
|
|
801
|
-
"
|
|
802
|
-
"
|
|
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
|
-
"
|
|
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
|
# ---------------------------------------------------------------------------
|
ggplot2_py/_compat.py
CHANGED
|
@@ -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
|
|
108
|
+
"""Emit an informational message via Python's :mod:`warnings`.
|
|
107
109
|
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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
|
|
ggplot2_py/_defaults.py
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""Geom / stat default-aesthetic modifiers — port of R's
|
|
2
|
+
``geom-update-defaults.R``.
|
|
3
|
+
|
|
4
|
+
Lets users override ``Geom*.default_aes`` (and the analogous Stat
|
|
5
|
+
defaults) globally for the lifetime of a Python session, with a cache
|
|
6
|
+
that lets the user roll back via ``reset_geom_defaults`` /
|
|
7
|
+
``reset_stat_defaults``.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Any, Dict, Optional
|
|
12
|
+
|
|
13
|
+
from ggplot2_py.aes import rename_aes
|
|
14
|
+
from ggplot2_py.geom import Geom
|
|
15
|
+
from ggplot2_py.stat import Stat
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"update_geom_defaults",
|
|
19
|
+
"update_stat_defaults",
|
|
20
|
+
"reset_geom_defaults",
|
|
21
|
+
"reset_stat_defaults",
|
|
22
|
+
"get_geom_defaults",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# Cached pristine copies — populated lazily on first override so reset
|
|
27
|
+
# can restore the originals. R's equivalent: ``cache_defaults``.
|
|
28
|
+
_cache_geom: Dict[str, Any] = {}
|
|
29
|
+
_cache_stat: Dict[str, Any] = {}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _resolve(name_or_cls: Any, registry: Dict[str, Any], kind: str) -> Any:
|
|
33
|
+
"""Resolve a name or class to the registered Geom/Stat class.
|
|
34
|
+
|
|
35
|
+
Mirrors R ``validate_subclass``: accepts a string (snake-case
|
|
36
|
+
suffix), a Geom/Stat subclass, or a layer-constructor function.
|
|
37
|
+
"""
|
|
38
|
+
if isinstance(name_or_cls, str):
|
|
39
|
+
cls = registry.get(name_or_cls) or registry.get(name_or_cls.lower())
|
|
40
|
+
if cls is None:
|
|
41
|
+
raise ValueError(f"No registered {kind} named {name_or_cls!r}.")
|
|
42
|
+
return cls
|
|
43
|
+
if isinstance(name_or_cls, type):
|
|
44
|
+
return name_or_cls
|
|
45
|
+
# A bound layer constructor returns a Layer; in R the function is
|
|
46
|
+
# passed to validate_subclass which sniffs the geom/stat off it.
|
|
47
|
+
raise TypeError(
|
|
48
|
+
f"Expected a string name or {kind} class; got {type(name_or_cls).__name__}."
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _snake_key(cls: Any, prefix: str) -> str:
|
|
53
|
+
"""``GeomPoint -> "point"``, used as the cache key."""
|
|
54
|
+
name = cls.__name__
|
|
55
|
+
if name.startswith(prefix):
|
|
56
|
+
name = name[len(prefix):]
|
|
57
|
+
# CamelCase → snake_case
|
|
58
|
+
out = []
|
|
59
|
+
for i, c in enumerate(name):
|
|
60
|
+
if c.isupper() and i > 0:
|
|
61
|
+
out.append("_")
|
|
62
|
+
out.append(c.lower())
|
|
63
|
+
return "".join(out) or name
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _update(cls: Any, new: Any, cache: Dict[str, Any]) -> Any:
|
|
67
|
+
"""Common update path for both geom and stat defaults."""
|
|
68
|
+
key = _snake_key(cls, "Geom" if cache is _cache_geom else "Stat")
|
|
69
|
+
old = dict(cls.default_aes) if hasattr(cls.default_aes, "items") else dict(cls.default_aes or {})
|
|
70
|
+
|
|
71
|
+
if new is None:
|
|
72
|
+
# Reset path — restore from cache, drop entry.
|
|
73
|
+
cached = cache.pop(key, None)
|
|
74
|
+
if cached is not None:
|
|
75
|
+
cls.default_aes = type(cls.default_aes)(**cached) if hasattr(type(cls.default_aes), "__init__") else dict(cached)
|
|
76
|
+
return old
|
|
77
|
+
|
|
78
|
+
# First override: snapshot the pristine defaults so we can roll back.
|
|
79
|
+
if key not in cache:
|
|
80
|
+
cache[key] = old
|
|
81
|
+
|
|
82
|
+
new_dict = rename_aes_dict(new)
|
|
83
|
+
merged = {**old}
|
|
84
|
+
for k, v in new_dict.items():
|
|
85
|
+
merged[k] = v
|
|
86
|
+
# Replace in the same container type the class originally used.
|
|
87
|
+
cls.default_aes = type(cls.default_aes)(**merged) if hasattr(type(cls.default_aes), "__init__") else merged
|
|
88
|
+
return old
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def rename_aes_dict(new: Any) -> Dict[str, Any]:
|
|
92
|
+
"""Apply :func:`rename_aes` to a Mapping or plain dict."""
|
|
93
|
+
if hasattr(new, "items"):
|
|
94
|
+
items = list(new.items())
|
|
95
|
+
else:
|
|
96
|
+
items = list(new)
|
|
97
|
+
keys = [k for k, _ in items]
|
|
98
|
+
canonical = rename_aes({k: None for k in keys})
|
|
99
|
+
name_map = {k: c for k, c in zip(keys, list(canonical.keys()))}
|
|
100
|
+
return {name_map[k]: v for k, v in items}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def update_geom_defaults(geom: Any, new: Any) -> Any:
|
|
104
|
+
"""Modify a Geom's ``default_aes``. Pass ``None`` to roll back.
|
|
105
|
+
|
|
106
|
+
Mirrors R ``update_geom_defaults`` (geom-update-defaults.R:57-59).
|
|
107
|
+
"""
|
|
108
|
+
cls = _resolve(geom, Geom._registry, "geom")
|
|
109
|
+
return _update(cls, new, _cache_geom)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def update_stat_defaults(stat: Any, new: Any) -> Any:
|
|
113
|
+
"""Modify a Stat's ``default_aes``. Pass ``None`` to roll back.
|
|
114
|
+
|
|
115
|
+
Mirrors R ``update_stat_defaults`` (geom-update-defaults.R:63-65).
|
|
116
|
+
"""
|
|
117
|
+
cls = _resolve(stat, Stat._registry, "stat")
|
|
118
|
+
return _update(cls, new, _cache_stat)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def reset_geom_defaults() -> None:
|
|
122
|
+
"""Roll back every prior ``update_geom_defaults`` call.
|
|
123
|
+
|
|
124
|
+
Mirrors R ``reset_geom_defaults`` (geom-update-defaults.R:120).
|
|
125
|
+
"""
|
|
126
|
+
for key in list(_cache_geom.keys()):
|
|
127
|
+
cls = Geom._registry.get(key) or Geom._registry.get(key.lower())
|
|
128
|
+
if cls is not None:
|
|
129
|
+
_update(cls, None, _cache_geom)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def reset_stat_defaults() -> None:
|
|
133
|
+
"""Roll back every prior ``update_stat_defaults`` call.
|
|
134
|
+
|
|
135
|
+
Mirrors R ``reset_stat_defaults`` (geom-update-defaults.R:124).
|
|
136
|
+
"""
|
|
137
|
+
for key in list(_cache_stat.keys()):
|
|
138
|
+
cls = Stat._registry.get(key) or Stat._registry.get(key.lower())
|
|
139
|
+
if cls is not None:
|
|
140
|
+
_update(cls, None, _cache_stat)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def get_geom_defaults(geom: Any, theme: Optional[Any] = None) -> Dict[str, Any]:
|
|
144
|
+
"""Return the resolved default aesthetics for *geom*.
|
|
145
|
+
|
|
146
|
+
Mirrors R ``get_geom_defaults`` (geom-update-defaults.R:96-116).
|
|
147
|
+
Accepts a class, a registered name, or a layer-constructor
|
|
148
|
+
function. ``theme`` is honoured for ``FromTheme`` defaults.
|
|
149
|
+
"""
|
|
150
|
+
if callable(geom) and not isinstance(geom, type):
|
|
151
|
+
# Layer constructor (geom_point) — call to obtain the Layer
|
|
152
|
+
layer = geom()
|
|
153
|
+
cls = type(getattr(layer, "geom", None) or geom)
|
|
154
|
+
if hasattr(layer, "aes_params"):
|
|
155
|
+
base = dict(cls.default_aes) if hasattr(cls.default_aes, "items") else dict(cls.default_aes or {})
|
|
156
|
+
base.update(layer.aes_params or {})
|
|
157
|
+
return base
|
|
158
|
+
if isinstance(geom, str):
|
|
159
|
+
cls = _resolve(geom, Geom._registry, "geom")
|
|
160
|
+
elif isinstance(geom, type):
|
|
161
|
+
cls = geom
|
|
162
|
+
else:
|
|
163
|
+
cls = type(geom)
|
|
164
|
+
|
|
165
|
+
base = dict(cls.default_aes) if hasattr(cls.default_aes, "items") else dict(cls.default_aes or {})
|
|
166
|
+
if theme is not None:
|
|
167
|
+
from ggplot2_py.geom import _eval_from_theme
|
|
168
|
+
base = dict(_eval_from_theme(cls.default_aes, theme))
|
|
169
|
+
return base
|