plotnine 0.15.0.dev2__py3-none-any.whl → 0.15.1__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 (140) 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 +128 -83
  4. plotnine/_mpl/layout_manager/_layout_tree.py +761 -310
  5. plotnine/_mpl/layout_manager/_spaces.py +320 -103
  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 +21 -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 +11 -2
  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 +8 -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 +28 -8
  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 +85 -0
  77. plotnine/mapping/aes.py +91 -72
  78. plotnine/mapping/evaluation.py +7 -65
  79. plotnine/scales/__init__.py +2 -0
  80. plotnine/scales/limits.py +7 -7
  81. plotnine/scales/scale.py +3 -3
  82. plotnine/scales/scale_color.py +82 -18
  83. plotnine/scales/scale_continuous.py +6 -4
  84. plotnine/scales/scale_datetime.py +28 -14
  85. plotnine/scales/scale_discrete.py +1 -1
  86. plotnine/scales/scale_identity.py +21 -2
  87. plotnine/scales/scale_manual.py +8 -2
  88. plotnine/scales/scale_xy.py +2 -2
  89. plotnine/stats/binning.py +4 -1
  90. plotnine/stats/smoothers.py +23 -36
  91. plotnine/stats/stat.py +20 -32
  92. plotnine/stats/stat_bin.py +6 -5
  93. plotnine/stats/stat_bin_2d.py +11 -9
  94. plotnine/stats/stat_bindot.py +13 -16
  95. plotnine/stats/stat_boxplot.py +6 -6
  96. plotnine/stats/stat_count.py +6 -9
  97. plotnine/stats/stat_density.py +7 -10
  98. plotnine/stats/stat_density_2d.py +12 -8
  99. plotnine/stats/stat_ecdf.py +7 -6
  100. plotnine/stats/stat_ellipse.py +9 -6
  101. plotnine/stats/stat_function.py +10 -8
  102. plotnine/stats/stat_hull.py +6 -3
  103. plotnine/stats/stat_identity.py +5 -2
  104. plotnine/stats/stat_pointdensity.py +5 -7
  105. plotnine/stats/stat_qq.py +46 -20
  106. plotnine/stats/stat_qq_line.py +16 -11
  107. plotnine/stats/stat_quantile.py +15 -9
  108. plotnine/stats/stat_sina.py +45 -14
  109. plotnine/stats/stat_smooth.py +8 -10
  110. plotnine/stats/stat_sum.py +5 -2
  111. plotnine/stats/stat_summary.py +7 -10
  112. plotnine/stats/stat_summary_bin.py +11 -14
  113. plotnine/stats/stat_unique.py +5 -2
  114. plotnine/stats/stat_ydensity.py +8 -11
  115. plotnine/themes/elements/__init__.py +2 -1
  116. plotnine/themes/elements/element_line.py +17 -9
  117. plotnine/themes/elements/margin.py +64 -1
  118. plotnine/themes/theme.py +9 -1
  119. plotnine/themes/theme_538.py +0 -1
  120. plotnine/themes/theme_bw.py +0 -1
  121. plotnine/themes/theme_dark.py +0 -1
  122. plotnine/themes/theme_gray.py +6 -5
  123. plotnine/themes/theme_light.py +1 -1
  124. plotnine/themes/theme_matplotlib.py +5 -5
  125. plotnine/themes/theme_seaborn.py +7 -4
  126. plotnine/themes/theme_void.py +9 -8
  127. plotnine/themes/theme_xkcd.py +0 -1
  128. plotnine/themes/themeable.py +110 -32
  129. plotnine/typing.py +17 -6
  130. plotnine/watermark.py +3 -3
  131. {plotnine-0.15.0.dev2.dist-info → plotnine-0.15.1.dist-info}/METADATA +13 -6
  132. plotnine-0.15.1.dist-info/RECORD +221 -0
  133. {plotnine-0.15.0.dev2.dist-info → plotnine-0.15.1.dist-info}/WHEEL +1 -1
  134. plotnine/plot_composition/__init__.py +0 -10
  135. plotnine/plot_composition/_compose.py +0 -436
  136. plotnine/plot_composition/_spacer.py +0 -32
  137. plotnine-0.15.0.dev2.dist-info/RECORD +0 -214
  138. /plotnine/{plot_composition → composition}/_plotspec.py +0 -0
  139. {plotnine-0.15.0.dev2.dist-info → plotnine-0.15.1.dist-info}/licenses/LICENSE +0 -0
  140. {plotnine-0.15.0.dev2.dist-info → plotnine-0.15.1.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,43 +305,9 @@ class LayoutItems:
299
305
 
300
306
  return chain(major, minor)
301
307
 
302
- def axis_text_x_margin(self, ax: Axes) -> Iterator[float]:
303
- """
304
- Return XTicks paddings
305
- """
306
- # In plotnine tick padding are specified as a margin to the
307
- # the axis_text.
308
- major, minor = [], []
309
- if not self._is_blank("axis_text_x"):
310
- h = self.plot.figure.bbox.height
311
- major = [
312
- (t.get_pad() or 0) / h for t in ax.xaxis.get_major_ticks()
313
- ]
314
- minor = [
315
- (t.get_pad() or 0) / h for t in ax.xaxis.get_minor_ticks()
316
- ]
317
- return chain(major, minor)
318
-
319
- def axis_text_y_margin(self, ax: Axes) -> Iterator[float]:
320
- """
321
- Return YTicks paddings
322
- """
323
- # In plotnine tick padding are specified as a margin to the
324
- # the axis_text.
325
- major, minor = [], []
326
- if not self._is_blank("axis_text_y"):
327
- w = self.plot.figure.bbox.width
328
- major = [
329
- (t.get_pad() or 0) / w for t in ax.yaxis.get_major_ticks()
330
- ]
331
- minor = [
332
- (t.get_pad() or 0) / w for t in ax.yaxis.get_minor_ticks()
333
- ]
334
- return chain(major, minor)
335
-
336
- def strip_text_x_height(self, position: StripPosition) -> float:
308
+ def strip_text_x_extra_height(self, position: StripPosition) -> float:
337
309
  """
338
- Height taken up by the top strips
310
+ Height taken up by the top strips that is outside the panels
339
311
  """
340
312
  if not self.strip_text_x:
341
313
  return 0
@@ -345,11 +317,23 @@ class LayoutItems:
345
317
  for st in self.strip_text_x
346
318
  if st.patch.position == position
347
319
  ]
348
- return self.calc.max_height(artists)
349
320
 
350
- 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:
351
335
  """
352
- Width taken up by the right strips
336
+ Width taken up by the top strips that is outside the panels
353
337
  """
354
338
  if not self.strip_text_y:
355
339
  return 0
@@ -359,7 +343,19 @@ class LayoutItems:
359
343
  for st in self.strip_text_y
360
344
  if st.patch.position == position
361
345
  ]
362
- 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)
363
359
 
364
360
  def axis_ticks_x_max_height_at(self, location: AxesLocation) -> float:
365
361
  """
@@ -377,10 +373,7 @@ class LayoutItems:
377
373
  Return maximum height[figure space] of x tick labels
378
374
  """
379
375
  heights = [
380
- self.calc.tight_height(label) + pad
381
- for label, pad in zip(
382
- self.axis_text_x(ax), self.axis_text_x_margin(ax)
383
- )
376
+ self.calc.tight_height(label) for label in self.axis_text_x(ax)
384
377
  ]
385
378
  return max(heights) if len(heights) else 0
386
379
 
@@ -410,10 +403,7 @@ class LayoutItems:
410
403
  Return maximum width[figure space] of y tick labels
411
404
  """
412
405
  widths = [
413
- self.calc.tight_width(label) + pad
414
- for label, pad in zip(
415
- self.axis_text_y(ax), self.axis_text_y_margin(ax)
416
- )
406
+ self.calc.tight_width(label) for label in self.axis_text_y(ax)
417
407
  ]
418
408
  return max(widths) if len(widths) else 0
419
409
 
@@ -529,6 +519,8 @@ class LayoutItems:
529
519
 
530
520
  self._adjust_axis_text_x(justify)
531
521
  self._adjust_axis_text_y(justify)
522
+ self._strip_text_x_background_equal_heights()
523
+ self._strip_text_y_background_equal_widths()
532
524
 
533
525
  def _adjust_axis_text_x(self, justify: TextJustifier):
534
526
  """
@@ -614,6 +606,36 @@ class LayoutItems:
614
606
  text, ha, -axis_text_col_width, 0, width=width
615
607
  )
616
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
+
617
639
 
618
640
  def _text_is_visible(text: Text) -> bool:
619
641
  """
@@ -636,7 +658,7 @@ class TextJustifier:
636
658
  def horizontally(
637
659
  self,
638
660
  text: Text,
639
- ha: str | float,
661
+ ha: HorizontalJustification | float,
640
662
  left: float,
641
663
  right: float,
642
664
  width: float | None = None,
@@ -644,8 +666,7 @@ class TextJustifier:
644
666
  """
645
667
  Horizontally Justify text between left and right
646
668
  """
647
- lookup = {"left": 0.0, "center": 0.5, "right": 1.0}
648
- rel = lookup.get(ha, ha) # pyright: ignore[reportCallIssue, reportArgumentType]
669
+ rel = ha_as_float(ha)
649
670
  if width is None:
650
671
  width = self.spaces.items.calc.width(text)
651
672
  x = rel_position(rel, width, left, right)
@@ -655,7 +676,7 @@ class TextJustifier:
655
676
  def vertically(
656
677
  self,
657
678
  text: Text,
658
- va: str | float,
679
+ va: VerticalJustification | float,
659
680
  bottom: float,
660
681
  top: float,
661
682
  height: float | None = None,
@@ -663,14 +684,7 @@ class TextJustifier:
663
684
  """
664
685
  Vertically Justify text between bottom and top
665
686
  """
666
- lookup = {
667
- "top": 1.0,
668
- "center": 0.5,
669
- "baseline": 0.5,
670
- "center_baseline": 0.5,
671
- "bottom": 0.0,
672
- }
673
- rel = lookup.get(va, va) # pyright: ignore[reportCallIssue, reportArgumentType]
687
+ rel = va_as_float(va)
674
688
 
675
689
  if height is None:
676
690
  height = self.spaces.items.calc.height(text)
@@ -678,13 +692,19 @@ class TextJustifier:
678
692
  text.set_y(y)
679
693
  text.set_verticalalignment("bottom")
680
694
 
681
- def horizontally_across_panel(self, text: Text, ha: str | float):
695
+ def horizontally_across_panel(
696
+ self, text: Text, ha: HorizontalJustification | float
697
+ ):
682
698
  """
683
699
  Horizontally Justify text accross the panel(s) width
684
700
  """
685
- 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
+ )
686
704
 
687
- def horizontally_across_plot(self, text: Text, ha: str | float):
705
+ def horizontally_across_plot(
706
+ self, text: Text, ha: HorizontalJustification | float
707
+ ):
688
708
  """
689
709
  Horizontally Justify text across the plot's width
690
710
  """
@@ -692,13 +712,19 @@ class TextJustifier:
692
712
  text, ha, self.spaces.l.plot_left, self.spaces.r.plot_right
693
713
  )
694
714
 
695
- def vertically_along_panel(self, text: Text, va: str | float):
715
+ def vertically_along_panel(
716
+ self, text: Text, va: VerticalJustification | float
717
+ ):
696
718
  """
697
719
  Horizontally Justify text along the panel(s) height
698
720
  """
699
- 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
+ )
700
724
 
701
- def vertically_along_plot(self, text: Text, va: str | float):
725
+ def vertically_along_plot(
726
+ self, text: Text, va: VerticalJustification | float
727
+ ):
702
728
  """
703
729
  Vertically Justify text along the plot's height
704
730
  """
@@ -763,7 +789,7 @@ def set_legends_position(legends: legend_artists, spaces: LayoutSpaces):
763
789
  if legends.right:
764
790
  y = rel_position(
765
791
  legends.right.justification,
766
- spaces.r._legend_height,
792
+ spaces.r.legend_height,
767
793
  params.bottom,
768
794
  params.top,
769
795
  )
@@ -773,7 +799,7 @@ def set_legends_position(legends: legend_artists, spaces: LayoutSpaces):
773
799
  if legends.left:
774
800
  y = rel_position(
775
801
  legends.left.justification,
776
- spaces.l._legend_height,
802
+ spaces.l.legend_height,
777
803
  params.bottom,
778
804
  params.top,
779
805
  )
@@ -783,7 +809,7 @@ def set_legends_position(legends: legend_artists, spaces: LayoutSpaces):
783
809
  if legends.top:
784
810
  x = rel_position(
785
811
  legends.top.justification,
786
- spaces.t._legend_width,
812
+ spaces.t.legend_width,
787
813
  params.left,
788
814
  params.right,
789
815
  )
@@ -793,7 +819,7 @@ def set_legends_position(legends: legend_artists, spaces: LayoutSpaces):
793
819
  if legends.bottom:
794
820
  x = rel_position(
795
821
  legends.bottom.justification,
796
- spaces.b._legend_width,
822
+ spaces.b.legend_width,
797
823
  params.left,
798
824
  params.right,
799
825
  )
@@ -870,7 +896,10 @@ def set_plot_tag_position(tag: Text, spaces: LayoutSpaces):
870
896
 
871
897
  def set_plot_tag_position_in_margin(tag: Text, spaces: LayoutSpaces):
872
898
  """
873
- 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.
874
903
  """
875
904
  position: TagPosition = spaces.plot.theme.getp("plot_tag_position")
876
905
  if not isinstance(position, str):
@@ -880,18 +909,34 @@ def set_plot_tag_position_in_margin(tag: Text, spaces: LayoutSpaces):
880
909
  )
881
910
 
882
911
  tag.set_position(spaces.to_figure_space((0.5, 0.5)))
883
- if "top" in position:
884
- tag.set_y(spaces.t.y2("plot_tag"))
885
- tag.set_verticalalignment("top")
886
- if "bottom" in position:
887
- tag.set_y(spaces.b.y1("plot_tag"))
888
- tag.set_verticalalignment("bottom")
889
- if "left" in position:
890
- 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)
918
+ tag.set_horizontalalignment("left")
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)
891
923
  tag.set_horizontalalignment("left")
892
- if "right" in position:
893
- tag.set_x(spaces.r.x2("plot_tag"))
894
- tag.set_horizontalalignment("right")
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)
895
940
 
896
941
 
897
942
  def _plot_tag_margin_adjustment(