ggplot2-python 4.0.2.9000.post1__py3-none-any.whl → 4.0.2.9000.post3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.post1"
10
+ __version__ = "4.0.2.9000.post3"
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,
@@ -460,7 +466,9 @@ from ggplot2_py.coord import (
460
466
  CoordFixed,
461
467
  CoordFlip,
462
468
  CoordPolar,
469
+ CoordQuickmap,
463
470
  CoordRadial,
471
+ CoordSf,
464
472
  CoordTrans,
465
473
  CoordTransform,
466
474
  coord_cartesian,
@@ -468,11 +476,14 @@ from ggplot2_py.coord import (
468
476
  coord_fixed,
469
477
  coord_flip,
470
478
  coord_polar,
479
+ coord_quickmap,
471
480
  coord_radial,
481
+ coord_sf,
472
482
  coord_trans,
473
483
  coord_transform,
474
484
  coord_munch,
475
485
  is_coord,
486
+ sf_transform_xy,
476
487
  )
477
488
 
478
489
  # ---------------------------------------------------------------------------
@@ -487,7 +498,11 @@ from ggplot2_py.facet import (
487
498
  facet_grid,
488
499
  facet_wrap,
489
500
  is_facet,
501
+ wrap_dims,
502
+ max_height,
503
+ max_width,
490
504
  )
505
+ from ggplot2_py.layout import Layout
491
506
 
492
507
  # ---------------------------------------------------------------------------
493
508
  # Position adjustments
@@ -535,6 +550,8 @@ from ggplot2_py.guide import (
535
550
  guide_bins,
536
551
  guide_colourbar,
537
552
  guide_colorbar,
553
+ guide_old_colourbar,
554
+ guide_old_colorbar,
538
555
  guide_coloursteps,
539
556
  guide_colorsteps,
540
557
  guide_custom,
@@ -542,6 +559,14 @@ from ggplot2_py.guide import (
542
559
  guide_none,
543
560
  guides,
544
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,
545
570
  )
546
571
 
547
572
  # ---------------------------------------------------------------------------
@@ -560,6 +585,7 @@ from ggplot2_py.theme import (
560
585
  reset_theme_settings,
561
586
  update_theme,
562
587
  replace_theme,
588
+ from_theme,
563
589
  )
564
590
  from ggplot2_py.theme_elements import (
565
591
  Element,
@@ -575,6 +601,7 @@ from ggplot2_py.theme_elements import (
575
601
  el_def,
576
602
  merge_element,
577
603
  is_theme_element,
604
+ is_margin,
578
605
  Margin,
579
606
  margin,
580
607
  margin_auto,
@@ -596,6 +623,17 @@ from ggplot2_py.theme_defaults import (
596
623
  theme_classic,
597
624
  theme_void,
598
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,
599
637
  )
600
638
 
601
639
  # ---------------------------------------------------------------------------
@@ -637,13 +675,43 @@ from ggplot2_py.draw_key import (
637
675
  # Save, fortify, qplot
638
676
  # ---------------------------------------------------------------------------
639
677
  from ggplot2_py.save import ggsave, check_device
640
- from ggplot2_py.fortify import fortify
678
+ from ggplot2_py.fortify import (
679
+ fortify,
680
+ fortify_lm,
681
+ fortify_dispatch,
682
+ fortify_glht,
683
+ fortify_confint_glht,
684
+ fortify_summary_glht,
685
+ fortify_cld,
686
+ )
641
687
  from ggplot2_py.qplot import qplot, quickplot
688
+ from ggplot2_py.labeller import (
689
+ label_value,
690
+ label_both,
691
+ label_context,
692
+ label_parsed,
693
+ label_bquote,
694
+ label_wrap_gen,
695
+ as_labeller,
696
+ )
697
+ from ggplot2_py.autoplot import autoplot, autolayer
642
698
 
643
699
  # ---------------------------------------------------------------------------
644
700
  # Utility re-exports (matching R namespace)
645
701
  # ---------------------------------------------------------------------------
646
- 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
647
715
 
648
716
  # grid re-exports (matching R: importFrom(grid, unit, arrow))
649
717
  from grid_py import Unit as unit, arrow
@@ -714,6 +782,9 @@ __all__ = [
714
782
  "geom_sf", "geom_sf_label", "geom_sf_text",
715
783
  "geom_qq", "geom_qq_line",
716
784
  "is_geom",
785
+ "GeomMap", "GeomQuantile",
786
+ "GeomCustomAnn", "GeomLogticks", "GeomRasterAnn",
787
+ "translate_shape_string",
717
788
  # Stat classes
718
789
  "Stat", "StatIdentity", "StatBin", "StatCount", "StatDensity",
719
790
  "StatSmooth", "StatBoxplot", "StatSummary", "StatSummaryBin",
@@ -771,20 +842,51 @@ __all__ = [
771
842
  "scale_color_viridis_c", "scale_color_viridis_d",
772
843
  "scale_color_grey", "scale_color_identity", "scale_color_manual",
773
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",
774
847
  "scale_size", "scale_size_continuous", "scale_size_area",
775
- "scale_shape", "scale_shape_discrete",
776
- "scale_linetype", "scale_linetype_discrete",
777
- "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",
778
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",
779
878
  # Coords
780
879
  "Coord", "CoordCartesian", "CoordFixed", "CoordFlip",
781
- "CoordPolar", "CoordRadial", "CoordTrans", "CoordTransform",
880
+ "CoordPolar", "CoordQuickmap", "CoordRadial", "CoordTrans", "CoordTransform",
782
881
  "coord_cartesian", "coord_equal", "coord_fixed", "coord_flip",
783
- "coord_polar", "coord_radial", "coord_trans", "coord_transform",
784
- "coord_munch", "is_coord",
882
+ "coord_polar", "coord_quickmap", "coord_radial", "coord_sf", "coord_trans", "coord_transform",
883
+ "CoordSf",
884
+ "coord_munch", "is_coord", "sf_transform_xy",
785
885
  # Facets
786
886
  "Facet", "FacetNull", "FacetGrid", "FacetWrap",
787
- "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",
788
890
  # Positions
789
891
  "Position", "PositionIdentity", "PositionDodge", "PositionDodge2",
790
892
  "PositionJitter", "PositionJitterdodge", "PositionNudge",
@@ -797,22 +899,34 @@ __all__ = [
797
899
  "GuideAxisTheta", "GuideBins", "GuideColourbar", "GuideColoursteps",
798
900
  "GuideCustom", "GuideLegend", "GuideNone",
799
901
  "guide_axis", "guide_legend", "guide_colourbar", "guide_colorbar",
902
+ "guide_old_colourbar", "guide_old_colorbar",
800
903
  "guide_coloursteps", "guide_colorsteps", "guide_bins",
801
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",
802
909
  # Themes
803
910
  "theme", "is_theme", "complete_theme",
804
911
  "theme_get", "theme_set", "theme_update", "theme_replace",
805
912
  "set_theme", "get_theme", "reset_theme_settings",
913
+ "update_theme", "replace_theme", "from_theme",
806
914
  "Element", "element_blank", "element_line", "element_rect",
807
915
  "element_text", "element_point", "element_polygon", "element_geom",
808
916
  "element_grob", "element_render", "merge_element",
809
- "Margin", "margin", "Rel", "rel",
917
+ "el_def", "is_theme_element", "is_margin",
918
+ "Margin", "margin", "margin_auto", "margin_part", "Rel", "rel",
810
919
  "calc_element", "get_element_tree", "register_theme_elements",
811
920
  "theme_grey", "theme_gray", "theme_bw", "theme_linedraw",
812
921
  "theme_light", "theme_dark", "theme_minimal", "theme_classic",
813
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",
814
928
  # Labels, limits
815
- "labs", "xlab", "ylab", "ggtitle",
929
+ "labs", "xlab", "ylab", "ggtitle", "update_labels",
816
930
  "lims", "xlim", "ylim", "expand_limits",
817
931
  # Annotations
818
932
  "annotate", "annotation_custom", "annotation_raster", "annotation_logticks",
@@ -823,11 +937,29 @@ __all__ = [
823
937
  "draw_key_pointrange", "draw_key_smooth", "draw_key_text",
824
938
  "draw_key_abline", "draw_key_vline", "draw_key_timeseries", "draw_key_vpath",
825
939
  # Save, fortify, qplot
826
- "ggsave", "check_device", "fortify", "qplot", "quickplot",
940
+ "ggsave", "check_device",
941
+ "fortify", "fortify_lm", "fortify_dispatch",
942
+ "fortify_glht", "fortify_confint_glht", "fortify_summary_glht", "fortify_cld",
943
+ "qplot", "quickplot",
944
+ # Labellers
945
+ "label_value", "label_both", "label_context", "label_parsed",
946
+ "label_bquote", "label_wrap_gen", "as_labeller",
947
+ # autoplot / autolayer generics
948
+ "autoplot", "autolayer",
827
949
  # Utilities
828
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",
829
957
  "unit", "arrow", "alpha",
830
- "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",
831
963
  # Plugin discovery
832
964
  "discover_extensions", "list_extensions",
833
965
  ]
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 (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
 
@@ -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