plotnine 0.15.0.dev3__py3-none-any.whl → 0.15.2__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.
Files changed (139) hide show
  1. plotnine/__init__.py +2 -0
  2. plotnine/_mpl/layout_manager/_engine.py +1 -1
  3. plotnine/_mpl/layout_manager/_layout_items.py +126 -41
  4. plotnine/_mpl/layout_manager/_layout_tree.py +712 -314
  5. plotnine/_mpl/layout_manager/_spaces.py +305 -101
  6. plotnine/_mpl/patches.py +70 -34
  7. plotnine/_mpl/text.py +144 -63
  8. plotnine/_mpl/utils.py +1 -1
  9. plotnine/_utils/__init__.py +50 -107
  10. plotnine/_utils/context.py +78 -2
  11. plotnine/_utils/ipython.py +35 -51
  12. plotnine/_utils/quarto.py +26 -0
  13. plotnine/_utils/yippie.py +115 -0
  14. plotnine/composition/__init__.py +11 -0
  15. plotnine/composition/_beside.py +55 -0
  16. plotnine/composition/_compose.py +471 -0
  17. plotnine/composition/_plot_spacer.py +60 -0
  18. plotnine/composition/_stack.py +55 -0
  19. plotnine/coords/coord.py +3 -3
  20. plotnine/data/__init__.py +31 -0
  21. plotnine/data/anscombe-quartet.csv +45 -0
  22. plotnine/doctools.py +4 -4
  23. plotnine/facets/facet.py +4 -4
  24. plotnine/facets/strips.py +17 -28
  25. plotnine/geoms/annotate.py +13 -13
  26. plotnine/geoms/annotation_logticks.py +7 -8
  27. plotnine/geoms/annotation_stripes.py +6 -6
  28. plotnine/geoms/geom.py +60 -27
  29. plotnine/geoms/geom_abline.py +3 -2
  30. plotnine/geoms/geom_area.py +2 -2
  31. plotnine/geoms/geom_bar.py +1 -0
  32. plotnine/geoms/geom_bin_2d.py +6 -2
  33. plotnine/geoms/geom_blank.py +0 -3
  34. plotnine/geoms/geom_boxplot.py +8 -4
  35. plotnine/geoms/geom_col.py +2 -2
  36. plotnine/geoms/geom_count.py +6 -2
  37. plotnine/geoms/geom_crossbar.py +3 -3
  38. plotnine/geoms/geom_density_2d.py +6 -2
  39. plotnine/geoms/geom_dotplot.py +2 -2
  40. plotnine/geoms/geom_errorbar.py +2 -2
  41. plotnine/geoms/geom_errorbarh.py +2 -2
  42. plotnine/geoms/geom_histogram.py +1 -1
  43. plotnine/geoms/geom_hline.py +3 -2
  44. plotnine/geoms/geom_linerange.py +2 -2
  45. plotnine/geoms/geom_map.py +5 -5
  46. plotnine/geoms/geom_path.py +11 -12
  47. plotnine/geoms/geom_point.py +4 -5
  48. plotnine/geoms/geom_pointdensity.py +4 -0
  49. plotnine/geoms/geom_pointrange.py +3 -5
  50. plotnine/geoms/geom_polygon.py +2 -3
  51. plotnine/geoms/geom_qq.py +4 -0
  52. plotnine/geoms/geom_qq_line.py +4 -0
  53. plotnine/geoms/geom_quantile.py +4 -0
  54. plotnine/geoms/geom_raster.py +4 -5
  55. plotnine/geoms/geom_rect.py +3 -4
  56. plotnine/geoms/geom_ribbon.py +7 -7
  57. plotnine/geoms/geom_rug.py +1 -1
  58. plotnine/geoms/geom_segment.py +2 -2
  59. plotnine/geoms/geom_sina.py +3 -3
  60. plotnine/geoms/geom_smooth.py +7 -3
  61. plotnine/geoms/geom_step.py +2 -2
  62. plotnine/geoms/geom_text.py +2 -3
  63. plotnine/geoms/geom_violin.py +8 -5
  64. plotnine/geoms/geom_vline.py +3 -2
  65. plotnine/ggplot.py +64 -85
  66. plotnine/guides/guide.py +7 -10
  67. plotnine/guides/guide_colorbar.py +3 -3
  68. plotnine/guides/guide_legend.py +3 -3
  69. plotnine/guides/guides.py +6 -6
  70. plotnine/helpers.py +49 -0
  71. plotnine/iapi.py +28 -5
  72. plotnine/labels.py +3 -3
  73. plotnine/layer.py +36 -19
  74. plotnine/mapping/_atomic.py +178 -0
  75. plotnine/mapping/_env.py +13 -2
  76. plotnine/mapping/_eval_environment.py +1 -1
  77. plotnine/mapping/aes.py +85 -49
  78. plotnine/scales/__init__.py +2 -0
  79. plotnine/scales/limits.py +7 -7
  80. plotnine/scales/scale.py +3 -3
  81. plotnine/scales/scale_color.py +82 -18
  82. plotnine/scales/scale_continuous.py +6 -4
  83. plotnine/scales/scale_datetime.py +28 -14
  84. plotnine/scales/scale_discrete.py +1 -1
  85. plotnine/scales/scale_identity.py +21 -2
  86. plotnine/scales/scale_manual.py +8 -2
  87. plotnine/scales/scale_xy.py +2 -2
  88. plotnine/stats/binning.py +4 -1
  89. plotnine/stats/smoothers.py +23 -36
  90. plotnine/stats/stat.py +20 -32
  91. plotnine/stats/stat_bin.py +6 -5
  92. plotnine/stats/stat_bin_2d.py +11 -9
  93. plotnine/stats/stat_bindot.py +13 -16
  94. plotnine/stats/stat_boxplot.py +6 -6
  95. plotnine/stats/stat_count.py +6 -9
  96. plotnine/stats/stat_density.py +7 -10
  97. plotnine/stats/stat_density_2d.py +12 -8
  98. plotnine/stats/stat_ecdf.py +7 -6
  99. plotnine/stats/stat_ellipse.py +9 -6
  100. plotnine/stats/stat_function.py +10 -8
  101. plotnine/stats/stat_hull.py +6 -3
  102. plotnine/stats/stat_identity.py +5 -2
  103. plotnine/stats/stat_pointdensity.py +5 -7
  104. plotnine/stats/stat_qq.py +46 -20
  105. plotnine/stats/stat_qq_line.py +16 -11
  106. plotnine/stats/stat_quantile.py +15 -9
  107. plotnine/stats/stat_sina.py +13 -15
  108. plotnine/stats/stat_smooth.py +8 -10
  109. plotnine/stats/stat_sum.py +5 -2
  110. plotnine/stats/stat_summary.py +7 -10
  111. plotnine/stats/stat_summary_bin.py +11 -14
  112. plotnine/stats/stat_unique.py +5 -2
  113. plotnine/stats/stat_ydensity.py +8 -11
  114. plotnine/themes/elements/__init__.py +2 -1
  115. plotnine/themes/elements/element_line.py +17 -9
  116. plotnine/themes/elements/margin.py +64 -1
  117. plotnine/themes/theme.py +9 -1
  118. plotnine/themes/theme_538.py +0 -1
  119. plotnine/themes/theme_bw.py +0 -1
  120. plotnine/themes/theme_dark.py +0 -1
  121. plotnine/themes/theme_gray.py +6 -5
  122. plotnine/themes/theme_light.py +1 -1
  123. plotnine/themes/theme_matplotlib.py +5 -5
  124. plotnine/themes/theme_seaborn.py +7 -4
  125. plotnine/themes/theme_void.py +9 -8
  126. plotnine/themes/theme_xkcd.py +0 -1
  127. plotnine/themes/themeable.py +109 -31
  128. plotnine/typing.py +17 -6
  129. plotnine/watermark.py +3 -3
  130. {plotnine-0.15.0.dev3.dist-info → plotnine-0.15.2.dist-info}/METADATA +13 -6
  131. plotnine-0.15.2.dist-info/RECORD +221 -0
  132. {plotnine-0.15.0.dev3.dist-info → plotnine-0.15.2.dist-info}/WHEEL +1 -1
  133. plotnine/plot_composition/__init__.py +0 -10
  134. plotnine/plot_composition/_compose.py +0 -436
  135. plotnine/plot_composition/_spacer.py +0 -32
  136. plotnine-0.15.0.dev3.dist-info/RECORD +0 -215
  137. /plotnine/{plot_composition → composition}/_plotspec.py +0 -0
  138. {plotnine-0.15.0.dev3.dist-info → plotnine-0.15.2.dist-info}/licenses/LICENSE +0 -0
  139. {plotnine-0.15.0.dev3.dist-info → plotnine-0.15.2.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,5 @@
1
1
  """
2
- Theme elements used to decorate the graph.
2
+ Margin
3
3
  """
4
4
 
5
5
  from __future__ import annotations
@@ -17,17 +17,55 @@ if TYPE_CHECKING:
17
17
 
18
18
  @dataclass
19
19
  class margin:
20
+ """
21
+ Margin
22
+ """
23
+
20
24
  t: float = 0
25
+ """
26
+ Top margin
27
+ """
28
+
21
29
  r: float = 0
30
+ """
31
+ Right margin
32
+ """
33
+
22
34
  b: float = 0
35
+ """
36
+ Bottom margin
37
+ """
38
+
23
39
  l: float = 0
40
+ """
41
+ Left Margin
42
+ """
43
+
24
44
  unit: Literal["pt", "in", "lines", "fig"] = "pt"
45
+ """
46
+ The units (coordinate space) of the values
47
+ """
25
48
 
26
49
  # These are set by the themeable when it is applied
27
50
  fontsize: float = field(init=False, default=0)
51
+ """
52
+ Font size of text that this margin applies to
53
+ """
54
+
28
55
  figure_size: tuple[float, float] = field(init=False, default=(0, 0))
56
+ """
57
+ Size of the figure in inches
58
+ """
29
59
 
30
60
  def setup(self, theme: theme, themeable_name: str):
61
+ """
62
+ Setup the margin to be used in the layout
63
+
64
+ For the margin's values to be useful, we need to be able to
65
+ convert them to different units as is required. Here we get
66
+ all the parameters that we shall need to do the conversions.
67
+ """
68
+ self.themeable_name = themeable_name
31
69
  self.fontsize = theme.getp((themeable_name, "size"), 11)
32
70
  self.figure_size = theme.getp("figure_size")
33
71
 
@@ -35,6 +73,8 @@ class margin:
35
73
  def pt(self) -> margin:
36
74
  """
37
75
  Return margin in points
76
+
77
+ These are the units of the display coordinate system
38
78
  """
39
79
  return self.to("pt")
40
80
 
@@ -42,6 +82,8 @@ class margin:
42
82
  def inch(self) -> margin:
43
83
  """
44
84
  Return margin in inches
85
+
86
+ These are the units of the figure-inches coordinate system
45
87
  """
46
88
  return self.to("in")
47
89
 
@@ -56,6 +98,8 @@ class margin:
56
98
  def fig(self) -> margin:
57
99
  """
58
100
  Return margin in figure units
101
+
102
+ These are the units of the figure coordinate system
59
103
  """
60
104
  return self.to("fig")
61
105
 
@@ -102,3 +146,22 @@ class margin:
102
146
  }
103
147
 
104
148
  return functions[conversion](value)
149
+
150
+
151
+ def margin_auto(
152
+ t: float = 0.0,
153
+ r: float | None = None,
154
+ b: float | None = None,
155
+ l: float | None = None,
156
+ unit: Literal["pt", "in", "lines", "fig"] = "pt",
157
+ ) -> margin:
158
+ """
159
+ Create margin with minimal arguments
160
+ """
161
+ if r is None:
162
+ r = t
163
+ if b is None:
164
+ b = t
165
+ if l is None:
166
+ l = r
167
+ return margin(t, r, b, l, unit)
plotnine/themes/theme.py CHANGED
@@ -72,7 +72,7 @@ class theme:
72
72
  ```
73
73
 
74
74
  will only modify the x-axis text.
75
- kwargs: dict
75
+ kwargs: Any
76
76
  kwargs are `themeables`. The themeables are elements that are
77
77
  subclasses of `themeable`. Many themeables are defined using
78
78
  theme elements i.e
@@ -276,6 +276,14 @@ class theme:
276
276
  """
277
277
  return self.themeables.getp((name, "margin"))
278
278
 
279
+ @cached_property
280
+ def get_ha(self):
281
+ return self.themeables.get_ha
282
+
283
+ @cached_property
284
+ def get_va(self):
285
+ return self.themeables.get_va
286
+
279
287
  def apply(self):
280
288
  """
281
289
  Apply this theme, then apply additional modifications in order.
@@ -23,7 +23,6 @@ class theme_538(theme_gray):
23
23
  axis_ticks=element_blank(),
24
24
  title=element_text(color="#3C3C3C"),
25
25
  legend_background=element_rect(fill="none"),
26
- legend_key=element_rect(fill="#E0E0E0"),
27
26
  panel_background=element_rect(fill=bgcolor),
28
27
  panel_border=element_blank(),
29
28
  panel_grid_major=element_line(color="#D5D5D5"),
@@ -20,7 +20,6 @@ class theme_bw(theme_gray):
20
20
  super().__init__(base_size, base_family)
21
21
  self += theme(
22
22
  axis_text=element_text(size=0.8 * base_size),
23
- legend_key=element_rect(fill="none", color="#CCCCCC"),
24
23
  panel_background=element_rect(fill="white"),
25
24
  panel_border=element_rect(fill="none", color="#7f7f7f"),
26
25
  panel_grid_major=element_line(color="#E5E5E5"),
@@ -24,7 +24,6 @@ class theme_dark(theme_gray):
24
24
  self += theme(
25
25
  axis_ticks=element_line(color="#666666", size=0.5),
26
26
  axis_ticks_minor=element_blank(),
27
- legend_key=element_rect(fill="#7F7F7F", color="#666666", size=0.5),
28
27
  panel_background=element_rect(fill="#7F7F7F", color="none"),
29
28
  panel_grid_major=element_line(color="#666666", size=0.5),
30
29
  panel_grid_minor=element_line(color="#737373", size=0.25),
@@ -6,6 +6,7 @@ from .elements import (
6
6
  element_rect,
7
7
  element_text,
8
8
  margin,
9
+ margin_auto,
9
10
  )
10
11
  from .theme import theme
11
12
 
@@ -27,6 +28,7 @@ class theme_gray(theme):
27
28
 
28
29
  def __init__(self, base_size=11, base_family=None):
29
30
  base_family = base_family or get_option("base_family")
31
+ half_line = base_size / 2
30
32
  quarter_line = base_size / 4
31
33
  fifth_line = base_size / 5
32
34
  eighth_line = base_size / 8
@@ -81,7 +83,6 @@ class theme_gray(theme):
81
83
  legend_frame=element_blank(),
82
84
  legend_key_spacing_x=6,
83
85
  legend_key_spacing_y=2,
84
- legend_key=element_rect(fill="#F2F2F2", colour="none"),
85
86
  legend_key_size=base_size * 0.8 * 1.8,
86
87
  legend_ticks_length=0.2,
87
88
  legend_margin=0, # points
@@ -89,13 +90,13 @@ class theme_gray(theme):
89
90
  legend_spacing=10, # points
90
91
  legend_text=element_text(
91
92
  size=base_size * 0.8,
92
- margin=margin(m / 1.5, m / 1.5, m / 1.5, m / 1.5, "fig"),
93
+ margin=margin_auto(m / 1.5, unit="fig"),
93
94
  ),
94
95
  legend_ticks=element_line(color="#CCCCCC", size=1),
95
96
  legend_title=element_text(
96
97
  margin=margin(t=m, l=m * 2, b=m / 2, r=m * 2, unit="fig")
97
98
  ),
98
- panel_background=element_rect(fill="#EBEBEB"),
99
+ panel_background=element_rect(fill="#EBEBEB", color="none"),
99
100
  panel_border=element_blank(),
100
101
  panel_grid_major=element_line(color="white", size=1),
101
102
  panel_grid_minor=element_line(color="white", size=0.5),
@@ -136,8 +137,8 @@ class theme_gray(theme):
136
137
  strip_text=element_text(
137
138
  color="#1A1A1A",
138
139
  size=base_size * 0.8,
139
- linespacing=1.0,
140
- margin=margin(1 / 3, 1 / 3, 1 / 3, 1 / 3, "lines"),
140
+ linespacing=1.5,
141
+ margin=margin_auto(half_line * 0.8),
141
142
  ),
142
143
  strip_text_y=element_text(rotation=-90),
143
144
  complete=True,
@@ -24,7 +24,7 @@ class theme_light(theme_gray):
24
24
  self += theme(
25
25
  axis_ticks=element_line(color="#B3B3B3", size=0.5),
26
26
  axis_ticks_minor=element_blank(),
27
- legend_key=element_rect(fill="white", color="#7F7F7F", size=0.72),
27
+ legend_key=element_rect(color="#7F7F7F", size=0.72),
28
28
  panel_background=element_rect(fill="white"),
29
29
  panel_border=element_rect(fill="none", color="#B3B3B3", size=1),
30
30
  panel_grid_major=element_line(color="#D9D9D9", size=0.5),
@@ -5,6 +5,7 @@ from .elements import (
5
5
  element_rect,
6
6
  element_text,
7
7
  margin,
8
+ margin_auto,
8
9
  )
9
10
  from .theme import theme
10
11
 
@@ -33,6 +34,7 @@ class theme_matplotlib(theme):
33
34
  m = get_option("base_margin")
34
35
  base_size = mpl.rcParams.get("font.size", 11)
35
36
  linewidth = mpl.rcParams.get("grid.linewidth", 0.8)
37
+ half_line = base_size / 2
36
38
 
37
39
  super().__init__(
38
40
  line=element_line(size=linewidth),
@@ -63,15 +65,12 @@ class theme_matplotlib(theme):
63
65
  legend_key_spacing_x=6,
64
66
  legend_key_spacing_y=2,
65
67
  legend_frame=element_rect(color="black"),
66
- legend_key=element_blank(),
67
68
  legend_key_size=16,
68
69
  legend_ticks_length=0.2,
69
70
  legend_margin=0,
70
71
  legend_position="right",
71
72
  legend_spacing=10,
72
- legend_text=element_text(
73
- margin=margin(m / 2, m / 2, m / 2, m / 2, "fig")
74
- ),
73
+ legend_text=element_text(margin=margin_auto(m / 2, unit="fig")),
75
74
  legend_ticks=element_line(color="black"),
76
75
  legend_title=element_text(
77
76
  ha="left",
@@ -112,7 +111,8 @@ class theme_matplotlib(theme):
112
111
  fill="#D9D9D9", color="black", size=linewidth
113
112
  ),
114
113
  strip_text=element_text(
115
- margin=margin(1 / 3, 1 / 3, 1 / 3, 1 / 3, "lines"),
114
+ linespacing=1.5,
115
+ margin=margin_auto(half_line * 0.8),
116
116
  ),
117
117
  strip_text_y=element_text(rotation=-90),
118
118
  complete=True,
@@ -5,6 +5,7 @@ from .elements import (
5
5
  element_rect,
6
6
  element_text,
7
7
  margin,
8
+ margin_auto,
8
9
  )
9
10
  from .theme import theme
10
11
 
@@ -20,7 +21,7 @@ class theme_seaborn(theme):
20
21
 
21
22
  Parameters
22
23
  ----------
23
- style: "whitegrid", "darkgrid", "nogrid", "ticks"
24
+ style: "white", "dark", "whitegrid", "darkgrid", "ticks"
24
25
  Style of axis background.
25
26
  context: "notebook", "talk", "paper", "poster"]``
26
27
  Intended context for resulting figures.
@@ -83,13 +84,15 @@ class theme_seaborn(theme):
83
84
  legend_position="right",
84
85
  legend_spacing=10, # points
85
86
  legend_text=element_text(
86
- margin=margin(m / 1.5, m / 1.5, m / 1.5, m / 1.5, "fig")
87
+ size=base_size * 0.8,
88
+ margin=margin_auto(m / 1.5, unit="fig"),
87
89
  ),
88
90
  legend_ticks=element_line(color="#CCCCCC", size=1),
89
91
  legend_title=element_text(
90
92
  margin=margin(t=m, l=m * 2, b=m / 2, r=m * 2, unit="fig")
91
93
  ),
92
94
  panel_spacing=m,
95
+ panel_background=element_rect(fill=rcparams["axes.facecolor"]),
93
96
  plot_caption=element_text(
94
97
  size=base_size * 0.8,
95
98
  ha="right",
@@ -123,8 +126,8 @@ class theme_seaborn(theme):
123
126
  strip_background=element_rect(color="none", fill="#D1CDDF"),
124
127
  strip_text=element_text(
125
128
  size=base_size * 0.8,
126
- linespacing=1.0,
127
- margin=margin(1 / 3, 1 / 3, 1 / 3, 1 / 3, "lines"),
129
+ linespacing=1.5,
130
+ margin=margin_auto(half_line * 0.8),
128
131
  ),
129
132
  strip_text_y=element_text(rotation=-90),
130
133
  complete=True,
@@ -1,5 +1,11 @@
1
1
  from ..options import get_option
2
- from .elements import element_blank, element_line, element_text, margin
2
+ from .elements import (
3
+ element_blank,
4
+ element_line,
5
+ element_text,
6
+ margin,
7
+ margin_auto,
8
+ )
3
9
  from .theme import theme
4
10
 
5
11
 
@@ -54,7 +60,7 @@ class theme_void(theme):
54
60
  legend_spacing=10,
55
61
  legend_text=element_text(
56
62
  size=base_size * 0.8,
57
- margin=margin(m / 1.5, m / 1.5, m / 1.5, m / 1.5, "fig"),
63
+ margin=margin_auto(m / 1.5, unit="fig"),
58
64
  ),
59
65
  legend_ticks=element_line(color="#CCCCCC", size=1),
60
66
  legend_title=element_text(
@@ -91,11 +97,6 @@ class theme_void(theme):
91
97
  plot_tag_location="margin",
92
98
  plot_tag_position="topleft",
93
99
  strip_align=0,
94
- strip_text=element_text(
95
- color="#1A1A1A",
96
- size=base_size * 0.8,
97
- linespacing=1.0,
98
- margin=margin(1 / 3, 1 / 3, 1 / 3, 1 / 3, "lines"),
99
- ),
100
+ strip_text=element_text(size=base_size * 0.8),
100
101
  complete=True,
101
102
  )
@@ -50,7 +50,6 @@ class theme_xkcd(theme_gray):
50
50
  legend_background=element_rect(color="black"),
51
51
  legend_box_margin=2,
52
52
  legend_margin=5,
53
- legend_key=element_rect(fill="none"),
54
53
  panel_border=element_rect(color="black", size=1),
55
54
  panel_grid=element_blank(),
56
55
  panel_background=element_rect(fill="white"),
@@ -17,7 +17,7 @@ from warnings import warn
17
17
 
18
18
  import numpy as np
19
19
 
20
- from .._utils import to_rgba
20
+ from .._utils import has_alpha_channel, to_rgba
21
21
  from .._utils.registry import RegistryHierarchyMeta
22
22
  from ..exceptions import PlotnineError, deprecated_themeable_name
23
23
  from .elements import element_blank
@@ -377,6 +377,36 @@ class Themeables(dict[str, themeable]):
377
377
 
378
378
  return default
379
379
 
380
+ def get_ha(self, name: str) -> float:
381
+ """
382
+ Get the horizontal alignement of themeable as a float
383
+
384
+ The themeable should be and element_text
385
+ """
386
+ lookup = {"left": 0.0, "center": 0.5, "right": 1.0}
387
+ ha: str | float = self.getp((name, "ha"), "center")
388
+ if isinstance(ha, str):
389
+ ha = lookup[ha]
390
+ return ha
391
+
392
+ def get_va(self, name) -> float:
393
+ """
394
+ Get the vertical alignement of themeable as a float
395
+
396
+ The themeable should be and element_text
397
+ """
398
+ lookup = {
399
+ "bottom": 0.0,
400
+ "center": 0.5,
401
+ "baseline": 0.5,
402
+ "center_baseline": 0.5,
403
+ "top": 1.0,
404
+ }
405
+ va: str | float = self.getp((name, "va"), "center")
406
+ if isinstance(va, str):
407
+ va = lookup[va]
408
+ return va
409
+
380
410
  def property(self, name: str, key: str = "value") -> Any:
381
411
  """
382
412
  Get the value a specific themeable(s) property
@@ -474,6 +504,25 @@ class MixinSequenceOfValues(themeable):
474
504
  a.set(**{name: value})
475
505
 
476
506
 
507
+ def blend_alpha(
508
+ properties: dict[str, Any], key: str = "color"
509
+ ) -> dict[str, Any]:
510
+ """
511
+ Blend color with alpha
512
+
513
+ When setting color property values of matplotlib objects,
514
+ for a color with an alpha channel, we don't want the alpha
515
+ property if any to have any effect on that color.
516
+ """
517
+ if (color := properties.get(key)) is not None:
518
+ if "alpha" in properties:
519
+ properties[key] = to_rgba(color, properties["alpha"])
520
+ properties["alpha"] = None
521
+ elif has_alpha_channel(color):
522
+ properties["alpha"] = None
523
+ return properties
524
+
525
+
477
526
  # element_text themeables
478
527
 
479
528
 
@@ -729,18 +778,29 @@ class plot_tag(themeable):
729
778
 
730
779
  Notes
731
780
  -----
732
- The `ha` & `va` of element_text will only have an effect if the
733
- have no effect if the
734
- [](:class:`~plotnine.themes.themeable.plot_tag_position`)
735
- is given as x-y coordinates.
781
+ The `ha` & `va` of element_text have no effect in some cases. e.g.
782
+ if [](:class:`~plotnine.themes.themeable.plot_tag_position`) is "margin"
783
+ and the tag is at the top it cannot be vertically aligned.
784
+
785
+ Also `ha` & `va` can be floats if it makes sense to justify the tag
786
+ over a span. e.g. along the panel or plot, or when aligning with
787
+ other tags in a composition.
736
788
  """
737
789
 
738
790
  _omit = ["margin"]
739
791
 
740
792
  def apply_figure(self, figure: Figure, targets: ThemeTargets):
741
793
  super().apply_figure(figure, targets)
794
+ props = self.properties
795
+
796
+ if "va" in props and not isinstance(props["va"], str):
797
+ del props["va"]
798
+
799
+ if "ha" in props and not isinstance(props["ha"], str):
800
+ del props["ha"]
801
+
742
802
  if text := targets.plot_tag:
743
- text.set(**self.properties)
803
+ text.set(**props)
744
804
 
745
805
  def blank_figure(self, figure: Figure, targets: ThemeTargets):
746
806
  super().blank_figure(figure, targets)
@@ -811,7 +871,7 @@ class strip_text_x(MixinSequenceOfValues):
811
871
  theme_element : element_text
812
872
  """
813
873
 
814
- _omit = ["margin", "ha"]
874
+ _omit = ["margin", "ha", "va"]
815
875
 
816
876
  def apply_figure(self, figure: Figure, targets: ThemeTargets):
817
877
  super().apply_figure(figure, targets)
@@ -834,7 +894,7 @@ class strip_text_y(MixinSequenceOfValues):
834
894
  theme_element : element_text
835
895
  """
836
896
 
837
- _omit = ["margin", "va"]
897
+ _omit = ["margin", "ha", "va"]
838
898
 
839
899
  def apply_figure(self, figure: Figure, targets: ThemeTargets):
840
900
  super().apply_figure(figure, targets)
@@ -894,13 +954,28 @@ class axis_text_x(MixinSequenceOfValues):
894
954
 
895
955
  def apply_ax(self, ax: Axes):
896
956
  super().apply_ax(ax)
897
- self.set(ax.get_xticklabels())
957
+
958
+ # TODO: Remove this code when the minimum matplotlib >= 3.10.0,
959
+ # and use the commented one below it
960
+ import matplotlib as mpl
961
+ from packaging import version
962
+
963
+ vinstalled = version.parse(mpl.__version__)
964
+ v310 = version.parse("3.10.0")
965
+ name = "labelbottom" if vinstalled >= v310 else "labelleft"
966
+ if not ax.xaxis.get_tick_params()[name]:
967
+ return
968
+
969
+ # if not ax.xaxis.get_tick_params()["labelbottom"]:
970
+ # return
971
+
972
+ labels = [t.label1 for t in ax.xaxis.get_major_ticks()]
973
+ self.set(labels)
898
974
 
899
975
  def blank_ax(self, ax: Axes):
900
976
  super().blank_ax(ax)
901
- ax.xaxis.set_tick_params(
902
- which="both", labelbottom=False, labeltop=False
903
- )
977
+ for t in ax.xaxis.get_major_ticks():
978
+ t.label1.set_visible(False)
904
979
 
905
980
 
906
981
  class axis_text_y(MixinSequenceOfValues):
@@ -927,13 +1002,17 @@ class axis_text_y(MixinSequenceOfValues):
927
1002
 
928
1003
  def apply_ax(self, ax: Axes):
929
1004
  super().apply_ax(ax)
930
- self.set(ax.get_yticklabels())
1005
+
1006
+ if not ax.yaxis.get_tick_params()["labelleft"]:
1007
+ return
1008
+
1009
+ labels = [t.label1 for t in ax.yaxis.get_major_ticks()]
1010
+ self.set(labels)
931
1011
 
932
1012
  def blank_ax(self, ax: Axes):
933
1013
  super().blank_ax(ax)
934
- ax.yaxis.set_tick_params(
935
- which="both", labelleft=False, labelright=False
936
- )
1014
+ for t in ax.yaxis.get_major_ticks():
1015
+ t.label1.set_visible(False)
937
1016
 
938
1017
 
939
1018
  class axis_text(axis_text_x, axis_text_y):
@@ -1306,7 +1385,7 @@ class panel_grid_major_x(themeable):
1306
1385
 
1307
1386
  def apply_ax(self, ax: Axes):
1308
1387
  super().apply_ax(ax)
1309
- ax.xaxis.grid(which="major", **self.properties)
1388
+ ax.xaxis.grid(which="major", **blend_alpha(self.properties))
1310
1389
 
1311
1390
  def blank_ax(self, ax: Axes):
1312
1391
  super().blank_ax(ax)
@@ -1324,7 +1403,7 @@ class panel_grid_major_y(themeable):
1324
1403
 
1325
1404
  def apply_ax(self, ax: Axes):
1326
1405
  super().apply_ax(ax)
1327
- ax.yaxis.grid(which="major", **self.properties)
1406
+ ax.yaxis.grid(which="major", **blend_alpha(self.properties))
1328
1407
 
1329
1408
  def blank_ax(self, ax: Axes):
1330
1409
  super().blank_ax(ax)
@@ -1448,8 +1527,13 @@ class legend_key(MixinSequenceOfValues):
1448
1527
  def apply_figure(self, figure: Figure, targets: ThemeTargets):
1449
1528
  super().apply_figure(figure, targets)
1450
1529
  properties = self.properties
1530
+ edgecolor = properties.get("edgecolor", None)
1531
+
1532
+ if isinstance(self, rect) and edgecolor:
1533
+ del properties["edgecolor"]
1534
+
1451
1535
  # Prevent invisible strokes from having any effect
1452
- if properties.get("edgecolor") in ("none", "None"):
1536
+ if edgecolor in ("none", "None"):
1453
1537
  properties["linewidth"] = 0
1454
1538
 
1455
1539
  rects = [da.patch for da in targets.legend_key]
@@ -1533,7 +1617,7 @@ class legend_box_background(themeable):
1533
1617
  """
1534
1618
 
1535
1619
 
1536
- class panel_background(themeable):
1620
+ class panel_background(legend_key):
1537
1621
  """
1538
1622
  Panel background
1539
1623
 
@@ -1542,13 +1626,12 @@ class panel_background(themeable):
1542
1626
  theme_element : element_rect
1543
1627
  """
1544
1628
 
1629
+ def apply_figure(self, figure: Figure, targets: ThemeTargets):
1630
+ super().apply_figure(figure, targets)
1631
+
1545
1632
  def apply_ax(self, ax: Axes):
1546
1633
  super().apply_ax(ax)
1547
- d = self.properties
1548
- if "facecolor" in d and "alpha" in d:
1549
- d["facecolor"] = to_rgba(d["facecolor"], d["alpha"])
1550
- del d["alpha"]
1551
-
1634
+ d = blend_alpha(self.properties, "facecolor")
1552
1635
  d["edgecolor"] = "none"
1553
1636
  d["linewidth"] = 0
1554
1637
  ax.patch.set(**d)
@@ -1574,16 +1657,12 @@ class panel_border(MixinSequenceOfValues):
1574
1657
  if not (rects := targets.panel_border):
1575
1658
  return
1576
1659
 
1577
- d = self.properties
1660
+ d = blend_alpha(self.properties, "edgecolor")
1578
1661
 
1579
1662
  with suppress(KeyError):
1580
1663
  if d["edgecolor"] == "none" or d["size"] == 0:
1581
1664
  return
1582
1665
 
1583
- if "edgecolor" in d and "alpha" in d:
1584
- d["edgecolor"] = to_rgba(d["edgecolor"], d["alpha"])
1585
- del d["alpha"]
1586
-
1587
1666
  self.set(rects, d)
1588
1667
 
1589
1668
  def blank_figure(self, figure: Figure, targets: ThemeTargets):
@@ -1663,7 +1742,6 @@ class strip_background(strip_background_x, strip_background_y):
1663
1742
 
1664
1743
 
1665
1744
  class rect(
1666
- legend_key,
1667
1745
  legend_frame,
1668
1746
  legend_background,
1669
1747
  panel_background,
plotnine/typing.py CHANGED
@@ -1,14 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
- import sys
4
- from datetime import datetime, timedelta
5
3
  from typing import (
6
- TYPE_CHECKING,
7
4
  Any,
8
5
  Callable,
9
6
  Literal,
7
+ NotRequired,
10
8
  Protocol,
11
9
  Sequence,
10
+ TypedDict,
12
11
  TypeVar,
13
12
  )
14
13
 
@@ -80,7 +79,7 @@ FacetSpaceRatios: TypeAlias = dict[Literal["x", "y"], Sequence[float]]
80
79
 
81
80
  StripPosition: TypeAlias = Literal["top", "right"]
82
81
 
83
- ## Scales
82
+ # Scales
84
83
 
85
84
  # Name names of scaled aesthetics
86
85
  ScaledAestheticsName: TypeAlias = Literal[
@@ -110,11 +109,11 @@ ScaledAestheticsName: TypeAlias = Literal[
110
109
  "upper",
111
110
  ]
112
111
 
113
- ## Coords
112
+ # Coords
114
113
  CoordRange: TypeAlias = tuple[float, float]
115
114
 
116
115
  # Guide
117
- SidePosition: TypeAlias = Literal["left", "right", "top", "bottom"]
116
+ Side: TypeAlias = Literal["left", "right", "top", "bottom"]
118
117
  LegendPosition: TypeAlias = (
119
118
  Literal["left", "right", "top", "bottom", "inside"] | tuple[float, float]
120
119
  )
@@ -142,3 +141,15 @@ class PTransform(Protocol):
142
141
  """
143
142
 
144
143
  def __call__(self, x: TFloatArrayLike) -> TFloatArrayLike: ...
144
+
145
+
146
+ class DisplayMetadata(TypedDict):
147
+ """
148
+ Metadata for the IPython output
149
+ """
150
+
151
+ width: NotRequired[int]
152
+ height: NotRequired[int]
153
+
154
+
155
+ MimeBundle: TypeAlias = tuple[dict[str, bytes], dict[str, DisplayMetadata]]
plotnine/watermark.py CHANGED
@@ -50,12 +50,12 @@ class watermark:
50
50
  kwargs["zorder"] = 99.9
51
51
  self.kwargs = kwargs
52
52
 
53
- def __radd__(self, plot: p9.ggplot) -> p9.ggplot:
53
+ def __radd__(self, other: p9.ggplot) -> p9.ggplot:
54
54
  """
55
55
  Add watermark to ggplot object
56
56
  """
57
- plot.watermarks.append(self)
58
- return plot
57
+ other.watermarks.append(self)
58
+ return other
59
59
 
60
60
  def draw(self, figure: matplotlib.figure.Figure):
61
61
  """