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,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from dataclasses import dataclass
3
4
  from typing import TYPE_CHECKING
4
5
 
5
6
  import pandas as pd
@@ -8,17 +9,40 @@ if TYPE_CHECKING:
8
9
  from typing_extensions import Self
9
10
 
10
11
  from plotnine import ggplot
12
+ from plotnine.composition import Compose
13
+
14
+
15
+ def reopen(fig):
16
+ """
17
+ Reopen an MPL figure that has been closed with plt.close
18
+
19
+ When drawing compositions, plot_composition_context will nest
20
+ plot_context. In this case, plot_context may close an MPL figure
21
+ that belongs to composition. A closed figure cannot be shown with
22
+ plt.show, so for compositions compose.show (if called) will do nothing.
23
+ """
24
+ from matplotlib._pylab_helpers import Gcf
25
+
26
+ Gcf.set_active(fig.canvas.manager)
27
+
28
+
29
+ def is_closed(fig) -> bool:
30
+ """
31
+ Return True if figure is closed
32
+ """
33
+ import matplotlib.pyplot as plt
34
+
35
+ return not plt.fignum_exists(fig.number)
11
36
 
12
37
 
13
38
  class plot_context:
14
39
  """
15
- Context to setup the environment within with the plot is built
40
+ Context within which the plot is built
16
41
 
17
42
  Parameters
18
43
  ----------
19
44
  plot :
20
45
  ggplot object to be built within the context.
21
- exits.
22
46
  show :
23
47
  Whether to show the plot.
24
48
  """
@@ -66,3 +90,55 @@ class plot_context:
66
90
 
67
91
  self.rc_context.__exit__(exc_type, exc_value, exc_traceback)
68
92
  self.pd_option_context.__exit__(exc_type, exc_value, exc_traceback)
93
+
94
+
95
+ @dataclass
96
+ class plot_composition_context:
97
+ """
98
+ Context within which a plot composition is built
99
+
100
+ Parameters
101
+ ----------
102
+ cmp :
103
+ composition object to be built within the context.
104
+ show :
105
+ Whether to show the plot.
106
+ """
107
+
108
+ cmp: Compose
109
+ show: bool
110
+
111
+ def __post_init__(self):
112
+ import matplotlib as mpl
113
+
114
+ # The dpi is needed when the figure is created, either as
115
+ # a parameter to plt.figure() or an rcParam.
116
+ # https://github.com/matplotlib/matplotlib/issues/24644
117
+ # When drawing the Composition, the dpi themeable is infective
118
+ # because it sets the rcParam after this figure is created.
119
+ rcParams = {"figure.dpi": self.cmp.last_plot.theme.getp("dpi")}
120
+ self._rc_context = mpl.rc_context(rcParams)
121
+
122
+ def __enter__(self) -> Self:
123
+ """
124
+ Enclose in matplolib & pandas environments
125
+ """
126
+ self._rc_context.__enter__()
127
+ return self
128
+
129
+ def __exit__(self, exc_type, exc_value, exc_traceback):
130
+ import matplotlib.pyplot as plt
131
+
132
+ if exc_type is None:
133
+ if self.show:
134
+ if is_closed(self.cmp.figure):
135
+ reopen(self.cmp.figure)
136
+ plt.show()
137
+ else:
138
+ plt.close(self.cmp.figure)
139
+ else:
140
+ # There is an exception, close any figure
141
+ if hasattr(self.cmp, "figure"):
142
+ plt.close(self.cmp.figure)
143
+
144
+ self._rc_context.__exit__(exc_type, exc_value, exc_traceback)
@@ -3,32 +3,24 @@ from __future__ import annotations
3
3
  from typing import TYPE_CHECKING
4
4
 
5
5
  if TYPE_CHECKING:
6
- from typing import Callable, Literal, TypeAlias
7
-
8
6
  from IPython.core.interactiveshell import InteractiveShell
9
7
 
10
- FigureFormat: TypeAlias = Literal[
11
- "png", "retina", "jpeg", "jpg", "svg", "pdf"
12
- ]
8
+ from ..typing import DisplayMetadata, FigureFormat, MimeBundle
13
9
 
14
10
 
15
- def get_ipython() -> "InteractiveShell":
11
+ def get_ipython() -> "None | InteractiveShell":
16
12
  """
17
13
  Return running IPython instance or None
18
14
  """
19
15
  try:
20
16
  from IPython.core.getipython import get_ipython as _get_ipython
21
- except ImportError as err:
22
- raise type(err)("IPython is has not been installed.") from err
23
-
24
- ip = _get_ipython()
25
- if ip is None:
26
- raise RuntimeError("Not running in a juptyer session.")
17
+ except ImportError:
18
+ return None
27
19
 
28
- return ip
20
+ return _get_ipython()
29
21
 
30
22
 
31
- def is_inline_backend():
23
+ def is_inline_backend() -> bool:
32
24
  """
33
25
  Return True if the inline_backend is on
34
26
 
@@ -36,47 +28,39 @@ def is_inline_backend():
36
28
  """
37
29
  import matplotlib as mpl
38
30
 
39
- return "matplotlib_inline.backend_inline" in mpl.get_backend()
31
+ backend = mpl.get_backend()
32
+ return backend in ("inline", "module://matplotlib_inline.backend_inline")
40
33
 
41
34
 
42
- def get_display_function(
43
- format: FigureFormat, figure_size_px: tuple[int, int]
44
- ) -> Callable[[bytes], None]:
35
+ def get_mimebundle(
36
+ b: bytes, format: FigureFormat, figure_size_px: tuple[int, int]
37
+ ) -> MimeBundle:
45
38
  """
46
- Return a function that will display the plot image
39
+ Return a the display MIME bundle from image data
40
+
41
+ Parameters
42
+ ----------
43
+ format :
44
+ The figure format
45
+ figure_size_px :
46
+ The figure size in pixels (width, height)
47
47
  """
48
- from IPython.display import (
49
- SVG,
50
- Image,
51
- display_jpeg,
52
- display_pdf,
53
- display_png,
54
- display_svg,
55
- )
56
-
57
- w, h = figure_size_px
58
-
59
- def png(b: bytes):
60
- display_png(Image(b, format="png", width=w, height=h))
61
-
62
- def retina(b: bytes):
63
- display_png(Image(b, format="png", retina=True))
64
-
65
- def jpeg(b: bytes):
66
- display_jpeg(Image(b, format="jpeg", width=w, height=h))
67
-
68
- def svg(b: bytes):
69
- display_svg(SVG(b))
70
-
71
- def pdf(b: bytes):
72
- display_pdf(b, raw=True)
73
48
 
74
49
  lookup = {
75
- "png": png,
76
- "retina": retina,
77
- "jpeg": jpeg,
78
- "jpg": jpeg,
79
- "svg": svg,
80
- "pdf": pdf,
50
+ "png": "image/png",
51
+ "retina": "image/png",
52
+ "jpeg": "image/jpeg",
53
+ "svg": "image/svg+xml",
54
+ "pdf": "application/pdf",
81
55
  }
82
- return lookup[format]
56
+ mimetype = lookup[format]
57
+
58
+ metadata: dict[str, DisplayMetadata] = {}
59
+ w, h = figure_size_px
60
+ if format in ("png", "jpeg"):
61
+ metadata = {mimetype: {"width": w, "height": h}}
62
+ elif format == "retina":
63
+ # `retina=True` in IPython.display.Image just halves width/height
64
+ metadata = {mimetype: {"width": w // 2, "height": h // 2}}
65
+
66
+ return {mimetype: b}, metadata
plotnine/_utils/quarto.py CHANGED
@@ -1,4 +1,6 @@
1
1
  import os
2
+ import sys
3
+ from functools import lru_cache
2
4
 
3
5
 
4
6
  def is_quarto_environment() -> bool:
@@ -31,3 +33,27 @@ def set_options_from_quarto():
31
33
  set_option("dpi", dpi)
32
34
  set_option("figure_size", figure_size)
33
35
  set_option("figure_format", figure_format)
36
+
37
+
38
+ # We do not expect the contents the file stored in the QUARTO_EXECUTE_INFO
39
+ # variable to change. We can can cache the output
40
+ @lru_cache()
41
+ def is_knitr_engine() -> bool:
42
+ """
43
+ Return True if knitr is executing the code
44
+ """
45
+
46
+ if filename := os.environ.get("QUARTO_EXECUTE_INFO"): # Quarto >= 1.8.21
47
+ import json
48
+ from pathlib import Path
49
+
50
+ try:
51
+ info = json.loads(Path(filename).read_text())
52
+ except FileNotFoundError:
53
+ # NOTE: Remove this branch some time after quarto 1.9 is released
54
+ # https://github.com/quarto-dev/quarto-cli/issues/13613
55
+ return "rpytools" in sys.modules
56
+ return info["format"]["execute"].get("engine") == "knitr"
57
+ else:
58
+ # NOTE: Remove this branch some time after quarto 1.9 is released
59
+ return "rpytools" in sys.modules
@@ -0,0 +1,115 @@
1
+ """
2
+ Functions/class to quickly create plots for development and testing
3
+ """
4
+
5
+ import pandas as pd
6
+
7
+ from plotnine import (
8
+ aes,
9
+ element_blank,
10
+ element_rect,
11
+ element_text,
12
+ geom_col,
13
+ geom_point,
14
+ ggplot,
15
+ labs,
16
+ theme,
17
+ )
18
+
19
+ __all__ = ("geom", "legend", "plot", "rotate", "tag")
20
+
21
+
22
+ class _Plot:
23
+ """
24
+ Create a plot
25
+ """
26
+
27
+ def __getattr__(self, color: str):
28
+ """
29
+ Create a blank plot with given background color
30
+ """
31
+ return (
32
+ ggplot()
33
+ + labs(
34
+ x="x-axis",
35
+ y="y-axis",
36
+ title=color.title(),
37
+ )
38
+ + theme(
39
+ figure_size=(8, 6),
40
+ text=element_text(color="black", size=11),
41
+ panel_background=element_rect(fill=color, size=1),
42
+ plot_background=element_rect(fill=color, alpha=0.2),
43
+ panel_border=element_rect(color="black"),
44
+ strip_background=element_rect(color="black"),
45
+ panel_grid=element_blank(),
46
+ legend_key_size=12,
47
+ )
48
+ )
49
+
50
+
51
+ class _Geom:
52
+ """
53
+ Create some simple geoms
54
+ """
55
+
56
+ data = pd.DataFrame(
57
+ {
58
+ "cat": ["a", "b", "c", "d"],
59
+ "cat2": ["r", "r", "s", "s"],
60
+ "value": [1, 2, 3, 4],
61
+ }
62
+ )
63
+
64
+ @property
65
+ def points(self):
66
+ return geom_point(aes("cat", "value", color="cat"), self.data, size=2)
67
+
68
+ @property
69
+ def cols(self):
70
+ return geom_col(aes("cat", "value", fill="cat"), self.data)
71
+
72
+
73
+ class _Legend:
74
+ """
75
+ Position Legends
76
+ """
77
+
78
+ @property
79
+ def left(self):
80
+ return theme(legend_position="left")
81
+
82
+ @property
83
+ def bottom(self):
84
+ return theme(legend_position="bottom")
85
+
86
+ @property
87
+ def right(self):
88
+ return theme(legend_position="right")
89
+
90
+ @property
91
+ def top(self):
92
+ return theme(legend_position="top")
93
+
94
+
95
+ class _Rotate:
96
+ """
97
+ Rotate a text themeable
98
+ """
99
+
100
+ def __getattr__(self, name):
101
+ angle = 0 if name[-2:] == "_y" else 90
102
+ return theme(**{name: element_text(angle=angle)}) # pyright: ignore[reportArgumentType]
103
+
104
+
105
+ def tag(s, position="topleft"):
106
+ """
107
+ Create a tag at a position
108
+ """
109
+ return [labs(tag=s), theme(plot_tag_position=position)]
110
+
111
+
112
+ plot = _Plot()
113
+ geom = _Geom()
114
+ legend = _Legend()
115
+ rotate = _Rotate()
@@ -0,0 +1,11 @@
1
+ from ._beside import Beside
2
+ from ._compose import Compose
3
+ from ._plot_spacer import plot_spacer
4
+ from ._stack import Stack
5
+
6
+ __all__ = (
7
+ "Compose",
8
+ "Stack",
9
+ "Beside",
10
+ "plot_spacer",
11
+ )
@@ -0,0 +1,55 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING
5
+
6
+ from ._compose import Compose
7
+
8
+ if TYPE_CHECKING:
9
+ from plotnine.ggplot import ggplot
10
+
11
+
12
+ @dataclass(repr=False)
13
+ class Beside(Compose):
14
+ """
15
+ Place plots or compositions side by side
16
+
17
+ **Usage**
18
+
19
+ plot | plot
20
+ plot | composition
21
+ composition | plot
22
+ composition | composition
23
+
24
+ Typically, you will use this class through the `|` operator.
25
+
26
+ See Also
27
+ --------
28
+ plotnine.composition.Stack : To arrange plots vertically
29
+ plotnine.composition.plot_spacer : To add a blank space between plots
30
+ plotnine.composition.Compose : For more on composing plots
31
+ """
32
+
33
+ @property
34
+ def nrow(self) -> int:
35
+ return 1
36
+
37
+ @property
38
+ def ncol(self) -> int:
39
+ return len(self)
40
+
41
+ def __or__(self, rhs: ggplot | Compose) -> Compose:
42
+ """
43
+ Add rhs as a column
44
+ """
45
+ # This is adjacent or i.e. (OR | rhs) so we collapse the
46
+ # operands into a single operation
47
+ return Beside([*self, rhs])
48
+
49
+ def __truediv__(self, rhs: ggplot | Compose) -> Compose:
50
+ """
51
+ Add rhs as a row
52
+ """
53
+ from ._stack import Stack
54
+
55
+ return Stack([self, rhs])