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
plotnine/__init__.py CHANGED
@@ -198,6 +198,7 @@ from .scales import (
198
198
  scale_size_radius,
199
199
  scale_stroke,
200
200
  scale_stroke_continuous,
201
+ scale_stroke_identity,
201
202
  scale_x_continuous,
202
203
  scale_x_date,
203
204
  scale_x_datetime,
@@ -441,6 +442,7 @@ __all__ = (
441
442
  "scale_size_radius",
442
443
  "scale_stroke",
443
444
  "scale_stroke_continuous",
445
+ "scale_stroke_identity",
444
446
  "scale_x_continuous",
445
447
  "scale_x_date",
446
448
  "scale_x_datetime",
@@ -13,7 +13,7 @@ if TYPE_CHECKING:
13
13
  from matplotlib.figure import Figure
14
14
 
15
15
  from plotnine import ggplot
16
- from plotnine.plot_composition import Compose
16
+ from plotnine.composition import Compose
17
17
 
18
18
 
19
19
  class PlotnineLayoutEngine(LayoutEngine):
@@ -6,6 +6,8 @@ from typing import TYPE_CHECKING, cast
6
6
 
7
7
  from matplotlib.text import Text
8
8
 
9
+ from plotnine._mpl.patches import StripTextPatch
10
+ from plotnine._utils import ha_as_float, va_as_float
9
11
  from plotnine.exceptions import PlotnineError
10
12
 
11
13
  from ..utils import (
@@ -35,7 +37,11 @@ if TYPE_CHECKING:
35
37
  from plotnine._mpl.text import StripText
36
38
  from plotnine.iapi import legend_artists
37
39
  from plotnine.themes.elements import margin as Margin
38
- from plotnine.typing import StripPosition
40
+ from plotnine.typing import (
41
+ HorizontalJustification,
42
+ StripPosition,
43
+ VerticalJustification,
44
+ )
39
45
 
40
46
  from ._spaces import LayoutSpaces
41
47
 
@@ -299,9 +305,9 @@ class LayoutItems:
299
305
 
300
306
  return chain(major, minor)
301
307
 
302
- def strip_text_x_height(self, position: StripPosition) -> float:
308
+ def strip_text_x_extra_height(self, position: StripPosition) -> float:
303
309
  """
304
- Height taken up by the top strips
310
+ Height taken up by the top strips that is outside the panels
305
311
  """
306
312
  if not self.strip_text_x:
307
313
  return 0
@@ -311,11 +317,23 @@ class LayoutItems:
311
317
  for st in self.strip_text_x
312
318
  if st.patch.position == position
313
319
  ]
314
- return self.calc.max_height(artists)
315
320
 
316
- def strip_text_y_width(self, position: StripPosition) -> float:
321
+ heights = []
322
+
323
+ for a in artists:
324
+ info = (
325
+ a.text.draw_info
326
+ if isinstance(a, StripTextPatch)
327
+ else a.draw_info
328
+ )
329
+ h = self.calc.height(a)
330
+ heights.append(max(h + h * info.strip_align, 0))
331
+
332
+ return max(heights)
333
+
334
+ def strip_text_y_extra_width(self, position: StripPosition) -> float:
317
335
  """
318
- Width taken up by the right strips
336
+ Width taken up by the top strips that is outside the panels
319
337
  """
320
338
  if not self.strip_text_y:
321
339
  return 0
@@ -325,7 +343,19 @@ class LayoutItems:
325
343
  for st in self.strip_text_y
326
344
  if st.patch.position == position
327
345
  ]
328
- return self.calc.max_width(artists)
346
+
347
+ widths = []
348
+
349
+ for a in artists:
350
+ info = (
351
+ a.text.draw_info
352
+ if isinstance(a, StripTextPatch)
353
+ else a.draw_info
354
+ )
355
+ w = self.calc.width(a)
356
+ widths.append(max(w + w * info.strip_align, 0))
357
+
358
+ return max(widths)
329
359
 
330
360
  def axis_ticks_x_max_height_at(self, location: AxesLocation) -> float:
331
361
  """
@@ -489,6 +519,8 @@ class LayoutItems:
489
519
 
490
520
  self._adjust_axis_text_x(justify)
491
521
  self._adjust_axis_text_y(justify)
522
+ self._strip_text_x_background_equal_heights()
523
+ self._strip_text_y_background_equal_widths()
492
524
 
493
525
  def _adjust_axis_text_x(self, justify: TextJustifier):
494
526
  """
@@ -574,6 +606,36 @@ class LayoutItems:
574
606
  text, ha, -axis_text_col_width, 0, width=width
575
607
  )
576
608
 
609
+ def _strip_text_x_background_equal_heights(self):
610
+ """
611
+ Make the strip_text_x_backgrounds have equal heights
612
+
613
+ The smaller heights are expanded to match the largest height
614
+ """
615
+ if not self.strip_text_x:
616
+ return
617
+
618
+ heights = [self.calc.bbox(t.patch).height for t in self.strip_text_x]
619
+ max_height = max(heights)
620
+ relative_heights = [max_height / h for h in heights]
621
+ for text, scale in zip(self.strip_text_x, relative_heights):
622
+ text.patch.expand = scale
623
+
624
+ def _strip_text_y_background_equal_widths(self):
625
+ """
626
+ Make the strip_text_y_backgrounds have equal widths
627
+
628
+ The smaller widths are expanded to match the largest width
629
+ """
630
+ if not self.strip_text_y:
631
+ return
632
+
633
+ widths = [self.calc.bbox(t.patch).width for t in self.strip_text_y]
634
+ max_width = max(widths)
635
+ relative_widths = [max_width / w for w in widths]
636
+ for text, scale in zip(self.strip_text_y, relative_widths):
637
+ text.patch.expand = scale
638
+
577
639
 
578
640
  def _text_is_visible(text: Text) -> bool:
579
641
  """
@@ -596,7 +658,7 @@ class TextJustifier:
596
658
  def horizontally(
597
659
  self,
598
660
  text: Text,
599
- ha: str | float,
661
+ ha: HorizontalJustification | float,
600
662
  left: float,
601
663
  right: float,
602
664
  width: float | None = None,
@@ -604,8 +666,7 @@ class TextJustifier:
604
666
  """
605
667
  Horizontally Justify text between left and right
606
668
  """
607
- lookup = {"left": 0.0, "center": 0.5, "right": 1.0}
608
- rel = lookup.get(ha, ha) # pyright: ignore[reportCallIssue, reportArgumentType]
669
+ rel = ha_as_float(ha)
609
670
  if width is None:
610
671
  width = self.spaces.items.calc.width(text)
611
672
  x = rel_position(rel, width, left, right)
@@ -615,7 +676,7 @@ class TextJustifier:
615
676
  def vertically(
616
677
  self,
617
678
  text: Text,
618
- va: str | float,
679
+ va: VerticalJustification | float,
619
680
  bottom: float,
620
681
  top: float,
621
682
  height: float | None = None,
@@ -623,14 +684,7 @@ class TextJustifier:
623
684
  """
624
685
  Vertically Justify text between bottom and top
625
686
  """
626
- lookup = {
627
- "top": 1.0,
628
- "center": 0.5,
629
- "baseline": 0.5,
630
- "center_baseline": 0.5,
631
- "bottom": 0.0,
632
- }
633
- rel = lookup.get(va, va) # pyright: ignore[reportCallIssue, reportArgumentType]
687
+ rel = va_as_float(va)
634
688
 
635
689
  if height is None:
636
690
  height = self.spaces.items.calc.height(text)
@@ -638,13 +692,19 @@ class TextJustifier:
638
692
  text.set_y(y)
639
693
  text.set_verticalalignment("bottom")
640
694
 
641
- def horizontally_across_panel(self, text: Text, ha: str | float):
695
+ def horizontally_across_panel(
696
+ self, text: Text, ha: HorizontalJustification | float
697
+ ):
642
698
  """
643
699
  Horizontally Justify text accross the panel(s) width
644
700
  """
645
- self.horizontally(text, ha, self.spaces.l.left, self.spaces.r.right)
701
+ self.horizontally(
702
+ text, ha, self.spaces.l.panel_left, self.spaces.r.panel_right
703
+ )
646
704
 
647
- def horizontally_across_plot(self, text: Text, ha: str | float):
705
+ def horizontally_across_plot(
706
+ self, text: Text, ha: HorizontalJustification | float
707
+ ):
648
708
  """
649
709
  Horizontally Justify text across the plot's width
650
710
  """
@@ -652,13 +712,19 @@ class TextJustifier:
652
712
  text, ha, self.spaces.l.plot_left, self.spaces.r.plot_right
653
713
  )
654
714
 
655
- def vertically_along_panel(self, text: Text, va: str | float):
715
+ def vertically_along_panel(
716
+ self, text: Text, va: VerticalJustification | float
717
+ ):
656
718
  """
657
719
  Horizontally Justify text along the panel(s) height
658
720
  """
659
- self.vertically(text, va, self.spaces.b.bottom, self.spaces.t.top)
721
+ self.vertically(
722
+ text, va, self.spaces.b.panel_bottom, self.spaces.t.panel_top
723
+ )
660
724
 
661
- def vertically_along_plot(self, text: Text, va: str | float):
725
+ def vertically_along_plot(
726
+ self, text: Text, va: VerticalJustification | float
727
+ ):
662
728
  """
663
729
  Vertically Justify text along the plot's height
664
730
  """
@@ -723,7 +789,7 @@ def set_legends_position(legends: legend_artists, spaces: LayoutSpaces):
723
789
  if legends.right:
724
790
  y = rel_position(
725
791
  legends.right.justification,
726
- spaces.r._legend_height,
792
+ spaces.r.legend_height,
727
793
  params.bottom,
728
794
  params.top,
729
795
  )
@@ -733,7 +799,7 @@ def set_legends_position(legends: legend_artists, spaces: LayoutSpaces):
733
799
  if legends.left:
734
800
  y = rel_position(
735
801
  legends.left.justification,
736
- spaces.l._legend_height,
802
+ spaces.l.legend_height,
737
803
  params.bottom,
738
804
  params.top,
739
805
  )
@@ -743,7 +809,7 @@ def set_legends_position(legends: legend_artists, spaces: LayoutSpaces):
743
809
  if legends.top:
744
810
  x = rel_position(
745
811
  legends.top.justification,
746
- spaces.t._legend_width,
812
+ spaces.t.legend_width,
747
813
  params.left,
748
814
  params.right,
749
815
  )
@@ -753,7 +819,7 @@ def set_legends_position(legends: legend_artists, spaces: LayoutSpaces):
753
819
  if legends.bottom:
754
820
  x = rel_position(
755
821
  legends.bottom.justification,
756
- spaces.b._legend_width,
822
+ spaces.b.legend_width,
757
823
  params.left,
758
824
  params.right,
759
825
  )
@@ -830,7 +896,10 @@ def set_plot_tag_position(tag: Text, spaces: LayoutSpaces):
830
896
 
831
897
  def set_plot_tag_position_in_margin(tag: Text, spaces: LayoutSpaces):
832
898
  """
833
- Place the tag in the margin around the plot
899
+ Place the tag in an inner margin around the plot
900
+
901
+ The panel_margin remains outside the tag. For compositions, the
902
+ tag is placed and within the tag_alignment space.
834
903
  """
835
904
  position: TagPosition = spaces.plot.theme.getp("plot_tag_position")
836
905
  if not isinstance(position, str):
@@ -840,18 +909,34 @@ def set_plot_tag_position_in_margin(tag: Text, spaces: LayoutSpaces):
840
909
  )
841
910
 
842
911
  tag.set_position(spaces.to_figure_space((0.5, 0.5)))
843
- if "top" in position:
844
- tag.set_y(spaces.t.y2("plot_tag"))
845
- tag.set_verticalalignment("top")
846
- if "bottom" in position:
847
- tag.set_y(spaces.b.y1("plot_tag"))
848
- tag.set_verticalalignment("bottom")
849
- if "left" in position:
850
- tag.set_x(spaces.l.x1("plot_tag"))
912
+ ha = spaces.plot.theme.get_ha("plot_tag")
913
+ va = spaces.plot.theme.get_va("plot_tag")
914
+ if "left" in position: # left, topleft, bottomleft
915
+ space = spaces.l.tag_alignment
916
+ x = spaces.l.x1("plot_tag") - (1 - ha) * space
917
+ tag.set_x(x)
851
918
  tag.set_horizontalalignment("left")
852
- if "right" in position:
853
- tag.set_x(spaces.r.x2("plot_tag"))
854
- tag.set_horizontalalignment("right")
919
+ if "right" in position: # right, topright, bottomright
920
+ space = spaces.r.tag_alignment
921
+ x = spaces.r.x1("plot_tag") + ha * space
922
+ tag.set_x(x)
923
+ tag.set_horizontalalignment("left")
924
+ if "bottom" in position: # bottom, bottomleft, bottomright
925
+ space = spaces.b.tag_alignment
926
+ y = spaces.b.y1("plot_tag") + (1 - va) * space
927
+ tag.set_y(y)
928
+ tag.set_verticalalignment("bottom")
929
+ if "top" in position: # top, topleft, topright
930
+ space = spaces.t.tag_alignment
931
+ y = spaces.t.y1("plot_tag") + va * space
932
+ tag.set_y(y)
933
+ tag.set_verticalalignment("bottom")
934
+
935
+ justify = TextJustifier(spaces)
936
+ if position in ("left", "right"):
937
+ justify.vertically_along_plot(tag, va)
938
+ elif position in ("top", "bottom"):
939
+ justify.horizontally_across_plot(tag, ha)
855
940
 
856
941
 
857
942
  def _plot_tag_margin_adjustment(