plotnine 0.15.0a1__py3-none-any.whl → 0.15.0a2__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.
@@ -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,17 @@ 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
701
  self.horizontally(text, ha, self.spaces.l.left, self.spaces.r.right)
646
702
 
647
- def horizontally_across_plot(self, text: Text, ha: str | float):
703
+ def horizontally_across_plot(
704
+ self, text: Text, ha: HorizontalJustification | float
705
+ ):
648
706
  """
649
707
  Horizontally Justify text across the plot's width
650
708
  """
@@ -652,13 +710,17 @@ class TextJustifier:
652
710
  text, ha, self.spaces.l.plot_left, self.spaces.r.plot_right
653
711
  )
654
712
 
655
- def vertically_along_panel(self, text: Text, va: str | float):
713
+ def vertically_along_panel(
714
+ self, text: Text, va: VerticalJustification | float
715
+ ):
656
716
  """
657
717
  Horizontally Justify text along the panel(s) height
658
718
  """
659
719
  self.vertically(text, va, self.spaces.b.bottom, self.spaces.t.top)
660
720
 
661
- def vertically_along_plot(self, text: Text, va: str | float):
721
+ def vertically_along_plot(
722
+ self, text: Text, va: VerticalJustification | float
723
+ ):
662
724
  """
663
725
  Vertically Justify text along the plot's height
664
726
  """
@@ -433,7 +433,7 @@ class right_spaces(_side_spaces):
433
433
  margin_alignment: float = 0
434
434
  legend: float = 0
435
435
  legend_box_spacing: float = 0
436
- strip_text_y_width_right: float = 0
436
+ strip_text_y_extra_width: float = 0
437
437
 
438
438
  def _calculate(self):
439
439
  items = self.items
@@ -452,7 +452,7 @@ class right_spaces(_side_spaces):
452
452
  self.legend = self.legend_width
453
453
  self.legend_box_spacing = theme.getp("legend_box_spacing")
454
454
 
455
- self.strip_text_y_width_right = items.strip_text_y_width("right")
455
+ self.strip_text_y_extra_width = items.strip_text_y_extra_width("right")
456
456
 
457
457
  # Adjust plot_margin to make room for ylabels that protude well
458
458
  # beyond the axes
@@ -545,7 +545,7 @@ class top_spaces(_side_spaces):
545
545
  plot_subtitle_margin_bottom: float = 0
546
546
  legend: float = 0
547
547
  legend_box_spacing: float = 0
548
- strip_text_x_height_top: float = 0
548
+ strip_text_x_extra_height: float = 0
549
549
 
550
550
  def _calculate(self):
551
551
  items = self.items
@@ -578,7 +578,7 @@ class top_spaces(_side_spaces):
578
578
  self.legend = self.legend_height
579
579
  self.legend_box_spacing = theme.getp("legend_box_spacing") * F
580
580
 
581
- self.strip_text_x_height_top = items.strip_text_x_height("top")
581
+ self.strip_text_x_extra_height = items.strip_text_x_extra_height("top")
582
582
 
583
583
  # Adjust plot_margin to make room for ylabels that protude well
584
584
  # beyond the axes
@@ -1061,7 +1061,7 @@ class LayoutSpaces:
1061
1061
  # Only interested in the proportion of the strip that
1062
1062
  # does not overlap with the panel
1063
1063
  if strip_align_x > -1:
1064
- self.sh += self.t.strip_text_x_height_top * (1 + strip_align_x)
1064
+ self.sh += self.t.strip_text_x_extra_height * (1 + strip_align_x)
1065
1065
 
1066
1066
  if facet.free["x"]:
1067
1067
  self.sh += self.items.axis_text_x_max_height_at(
plotnine/_mpl/patches.py CHANGED
@@ -4,12 +4,11 @@ from typing import TYPE_CHECKING
4
4
 
5
5
  from matplotlib import artist
6
6
  from matplotlib.patches import FancyBboxPatch, Rectangle
7
- from matplotlib.text import _get_textbox # type: ignore
8
- from matplotlib.transforms import Affine2D
7
+ from matplotlib.transforms import Bbox
9
8
 
10
- if TYPE_CHECKING:
11
- from matplotlib.backend_bases import RendererBase
9
+ from plotnine._mpl.utils import rel_position
12
10
 
11
+ if TYPE_CHECKING:
13
12
  from plotnine.typing import StripPosition
14
13
 
15
14
  from .text import StripText
@@ -26,51 +25,88 @@ class StripTextPatch(FancyBboxPatch):
26
25
  Strip text background box
27
26
  """
28
27
 
29
- # The text artists that is wrapped by this box
30
28
  text: StripText
29
+ """
30
+ The text artists that is wrapped by this box
31
+ """
32
+
31
33
  position: StripPosition
32
- _update = False
34
+ """
35
+ The position of the strip_text associated with this patch
36
+ """
33
37
 
34
- def __init__(self, text: StripText):
35
- boxstyle = f"square, pad={text.draw_info.strip_text_margin}"
38
+ expand: float = 1
39
+ """
40
+ Factor by which to expand the thickness of this patch.
36
41
 
42
+ This value is used by the layout manager to increase the breadth
43
+ of the narrower strip_backgrounds.
44
+ """
45
+
46
+ def __init__(self, text: StripText):
37
47
  super().__init__(
38
- (0, 0), 1, 1, boxstyle=boxstyle, clip_on=False, zorder=2.2
48
+ # The position, width and height are determine in
49
+ # .get_window_extent.
50
+ (0, 0),
51
+ width=1,
52
+ height=1,
53
+ boxstyle="square, pad=0",
54
+ clip_on=False,
55
+ zorder=2.2,
39
56
  )
40
57
 
41
58
  self.text = text
42
59
  self.position = text.draw_info.position
43
60
 
44
- def update_position_size(self, renderer: RendererBase):
61
+ def get_window_extent(self, renderer=None):
45
62
  """
46
- Update the location and the size of the bbox.
63
+ Location & dimensions of the box in display coordinates
47
64
  """
48
- if self._update:
49
- return
50
-
51
- text = self.text
52
- posx, posy = text.get_transform().transform(text.get_position())
53
- x, y, w, h = _get_textbox(text, renderer)
54
-
55
- self.set_bounds(0.0, 0.0, w, h)
56
- self.set_transform(
57
- Affine2D()
58
- .rotate_deg(text.get_rotation())
59
- .translate(posx + x, posy + y)
60
- )
61
- fontsize_in_pixel = renderer.points_to_pixels(
62
- text.get_size() # type: ignore
63
- )
64
- self.set_mutation_scale(fontsize_in_pixel) # type: ignore
65
- self._update = True
65
+ info = self.text.draw_info
66
+ m = info.margin
67
+
68
+ # bboxes in display space
69
+ text_bbox = self.text.get_window_extent(renderer)
70
+ ax_bbox = info.ax.bbox.frozen()
71
+
72
+ # line height in display space
73
+ line_height = self.text._line_height(renderer)
74
+
75
+ # Convert the bottom left coordinates of the patch from
76
+ # transAxes to display space. We are not justifying the patch
77
+ # within the axes so we use 0 for the lengths, this gives us
78
+ # a patch that starts at the edge of the axes and not one that
79
+ # ends at the edge
80
+ x0 = rel_position(info.bg_x, 0, ax_bbox.x0, ax_bbox.x1)
81
+ y0 = rel_position(info.bg_y, 0, ax_bbox.y0, ax_bbox.y1)
82
+
83
+ # info.bg_width and info.bg_height are in axes space
84
+ # so they are a scaling factor
85
+ if info.position == "top":
86
+ width = ax_bbox.width * info.bg_width
87
+ height = text_bbox.height + ((m.b + m.t) * line_height)
88
+ height *= self.expand
89
+ y0 += height * info.strip_align
90
+ else:
91
+ height = ax_bbox.height * info.bg_height
92
+ width = text_bbox.width + ((m.l + m.r) * line_height)
93
+ width *= self.expand
94
+ x0 += width * info.strip_align
95
+
96
+ return Bbox.from_bounds(x0, y0, width, height)
66
97
 
67
- def get_window_extent(self, renderer=None):
98
+ @artist.allow_rasterization
99
+ def draw(self, renderer):
68
100
  """
69
- Location & dimensions of the box
101
+ Draw patch
70
102
  """
71
- if renderer:
72
- self.update_position_size(renderer)
73
- return super().get_window_extent(renderer)
103
+ # The geometry of the patch is determined by its rectangular bounds,
104
+ # this is also its "window_extent". As the extent value is in
105
+ # display units, we don't need a transform.
106
+ bbox = self.get_window_extent(renderer)
107
+ self.set_bounds(bbox.bounds)
108
+ self.set_transform(None)
109
+ return super().draw(renderer)
74
110
 
75
111
 
76
112
  class InsideStrokedRectangle(Rectangle):
plotnine/_mpl/text.py CHANGED
@@ -1,9 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from functools import lru_cache
3
4
  from typing import TYPE_CHECKING
4
5
 
6
+ from matplotlib import artist
5
7
  from matplotlib.text import Text
6
8
 
9
+ from plotnine._utils import ha_as_float, va_as_float
10
+
7
11
  from .patches import StripTextPatch
8
12
  from .utils import bbox_in_axes_space, rel_position
9
13
 
@@ -11,7 +15,6 @@ if TYPE_CHECKING:
11
15
  from matplotlib.backend_bases import RendererBase
12
16
 
13
17
  from plotnine.iapi import strip_draw_info
14
- from plotnine.typing import HorizontalJustification, VerticalJustification
15
18
 
16
19
 
17
20
  class StripText(Text):
@@ -28,86 +31,170 @@ class StripText(Text):
28
31
  "transform": info.ax.transAxes,
29
32
  "clip_on": False,
30
33
  "zorder": 3.3,
34
+ # Since the text can be rotated, it is simpler to anchor it at
35
+ # the center, align it then do the rotation. Vertically,
36
+ # center_baseline places the text in the visual center, but
37
+ # only if it is one line. For multiline text, we are better
38
+ # off with plain center.
39
+ "ha": "center",
40
+ "va": "center_baseline" if info.is_oneline else "center",
41
+ "rotation_mode": "anchor",
31
42
  }
32
43
 
33
- super().__init__(
34
- info.x,
35
- info.y,
36
- info.label,
37
- **kwargs,
38
- )
44
+ super().__init__(0, 0, info.label, **kwargs)
39
45
  self.draw_info = info
40
46
  self.patch = StripTextPatch(self)
41
47
 
42
- # TODO: Move these _justify methods to the layout manager
43
- # We need to first make sure that the patch has the final size during
44
- # layout computation. Right now, the final size is calculated during
45
- # draw (in these justify methods)
46
- def _justify_horizontally(self, renderer):
48
+ # self.set_horizontalalignment("center")
49
+ # self.set_verticalalignment(
50
+ # "center_baseline" if info.is_oneline else "center"
51
+ # )
52
+ # self.set_rotation_mode("anchor")
53
+
54
+ # TODO: This should really be part of the unit conversions in the
55
+ # margin class.
56
+ @lru_cache(2)
57
+ def _line_height(self, renderer) -> float:
58
+ """
59
+ The line height in display space of the text on the canvas
60
+ """
61
+ # Text string, (width, height), x, y
62
+ parts: list[tuple[str, tuple[float, float], float, float]]
63
+
64
+ try:
65
+ # matplotlib.Text._get_layout is a private API and we cannot
66
+ # tell how using it may fail in the future.
67
+ _, parts, _ = self._get_layout(renderer) # pyright: ignore[reportAttributeAccessIssue]
68
+ except Exception:
69
+ from warnings import warn
70
+
71
+ from plotnine.exceptions import PlotnineWarning
72
+
73
+ # The canvas height is nearly always bigger than the stated
74
+ # fontsize. 1.36 is a good multiplication factor obtained by
75
+ # some rough exploration
76
+ f = 1.36
77
+ size = self.get_fontsize()
78
+ height = round(size * f) if isinstance(size, int) else 14
79
+ warn(
80
+ f"Could not calculate line height for {self.get_text()}. "
81
+ "Using an estimate, please let us know about this at "
82
+ "https://github.com/has2k1/plotnine/issues",
83
+ PlotnineWarning,
84
+ )
85
+ else:
86
+ # The the text has multiple lines, we use the maximum height
87
+ # of anyone single line.
88
+ height = max([p[1][1] for p in parts])
89
+
90
+ return height
91
+
92
+ def _set_position(self, renderer):
93
+ """
94
+ Set the postion of the text within the strip_background
95
+ """
96
+ # We have to two premises that depend on each other:
97
+ #
98
+ # 1. The breadth of the strip_background grows to accomodate
99
+ # the strip_text.
100
+ # 2. The strip_text is justified within the strip_background.
101
+ #
102
+ # From these we note that the strip_background does not need the
103
+ # position of the strip_text, but it needs its size. Therefore
104
+ # we implement StripTextPatch.get_window_extent can use
105
+ # StripText.get_window_extent, peeking only at the size.
106
+ #
107
+ # And we implement StripText._set_position_* to use
108
+ # StripTextPatch.get_window_extent and make the calculations in
109
+ # both methods independent.
110
+ if self.draw_info.position == "top":
111
+ self._set_position_top(renderer)
112
+ else: # "right"
113
+ self._set_position_right(renderer)
114
+
115
+ def _set_position_top(self, renderer):
47
116
  """
48
- Justify the text along the strip_background
117
+ Set position of the text within the top strip_background
49
118
  """
50
119
  info = self.draw_info
51
- lookup: dict[HorizontalJustification, float] = {
52
- "left": 0.0,
53
- "center": 0.5,
54
- "right": 1.0,
55
- }
56
- rel = lookup.get(info.ha, 0.5) if isinstance(info.ha, str) else info.ha
57
- patch_bbox = bbox_in_axes_space(self.patch, info.ax, renderer)
58
- text_bbox = bbox_in_axes_space(self, info.ax, renderer)
59
- l, b, w, h = info.x, info.y, info.box_width, patch_bbox.height
60
- b = b + patch_bbox.height * info.strip_align
61
- x = rel_position(rel, text_bbox.width, patch_bbox.x0, patch_bbox.x1)
62
- y = b + h / 2
63
- self.set_horizontalalignment("left")
64
- self.patch.set_bounds(l, b, w, h)
120
+ ha, va, ax, m = info.ha, info.va, info.ax, info.margin
121
+
122
+ rel_x, rel_y = ha_as_float(ha), va_as_float(va)
123
+ patch_bbox = bbox_in_axes_space(self.patch, ax, renderer)
124
+ text_bbox = bbox_in_axes_space(self, ax, renderer)
125
+
126
+ # line_height and margins in axes space
127
+ line_height = self._line_height(renderer) / ax.bbox.height
128
+
129
+ x = (
130
+ # Justify horizontally within the strip_background
131
+ rel_position(
132
+ rel_x,
133
+ text_bbox.width + (line_height * (m.l + m.r)),
134
+ patch_bbox.x0,
135
+ patch_bbox.x1,
136
+ )
137
+ + (m.l * line_height)
138
+ + text_bbox.width / 2
139
+ )
140
+ # Setting the y position based on the bounding box is wrong
141
+ y = (
142
+ rel_position(
143
+ rel_y,
144
+ text_bbox.height,
145
+ patch_bbox.y0 + m.b * line_height,
146
+ patch_bbox.y1 - m.t * line_height,
147
+ )
148
+ + text_bbox.height / 2
149
+ )
65
150
  self.set_position((x, y))
66
151
 
67
- def _justify_vertically(self, renderer):
152
+ def _set_position_right(self, renderer):
68
153
  """
69
- Justify the text along the strip_background
154
+ Set position of the text within the bottom strip_background
70
155
  """
71
- # Note that the strip text & background and horizontal but
72
- # rotated to appear vertical. So we really are still justifying
73
- # horizontally.
74
156
  info = self.draw_info
75
- lookup: dict[VerticalJustification, float] = {
76
- "bottom": 0.0,
77
- "center": 0.5,
78
- "top": 1.0,
79
- }
80
- rel = lookup.get(info.va, 0.5) if isinstance(info.va, str) else info.va
81
- patch_bbox = bbox_in_axes_space(self.patch, info.ax, renderer)
82
- text_bbox = bbox_in_axes_space(self, info.ax, renderer)
83
- l, b, w, h = info.x, info.y, patch_bbox.width, info.box_height
84
- l = l + patch_bbox.width * info.strip_align
85
- x = l + w / 2
86
- y = rel_position(rel, text_bbox.height, patch_bbox.y0, patch_bbox.y1)
87
- self.set_horizontalalignment("right") # 90CW right means bottom
88
- self.patch.set_bounds(l, b, w, h)
89
- self.set_position((x, y))
157
+ ha, va, ax, m = info.ha, info.va, info.ax, info.margin
90
158
 
91
- def draw(self, renderer: RendererBase):
92
- if not self.get_visible():
93
- return
159
+ # bboxes in axes space
160
+ patch_bbox = bbox_in_axes_space(self.patch, ax, renderer)
161
+ text_bbox = bbox_in_axes_space(self, ax, renderer)
94
162
 
95
- # expand strip_text patch to contain the text
96
- self.patch.update_position_size(renderer)
163
+ # line_height in axes space
164
+ line_height = self._line_height(renderer) / ax.bbox.width
97
165
 
98
- # Align patch across the edge of the panel
99
- if self.draw_info.position == "top":
100
- self._justify_horizontally(renderer)
101
- else: # "right"
102
- self._justify_vertically(renderer)
166
+ rel_x, rel_y = ha_as_float(ha), va_as_float(va)
103
167
 
104
- self.patch.set_transform(self.draw_info.ax.transAxes)
105
- self.patch.set_mutation_scale(0)
168
+ x = (
169
+ rel_position(
170
+ rel_x,
171
+ text_bbox.width,
172
+ patch_bbox.x0 + m.l * line_height,
173
+ patch_bbox.x1 - m.r * line_height,
174
+ )
175
+ + text_bbox.width / 2
176
+ )
177
+ y = (
178
+ # Justify vertically within the strip_background
179
+ rel_position(
180
+ rel_y,
181
+ text_bbox.height + ((m.b + m.t) * line_height),
182
+ patch_bbox.y0,
183
+ patch_bbox.y1,
184
+ )
185
+ + (m.b * line_height)
186
+ + text_bbox.height / 2
187
+ )
188
+ self.set_position((x, y))
106
189
 
107
- # Put text in center of patch
108
- self.set_rotation_mode("anchor")
109
- self.set_verticalalignment("center_baseline")
190
+ @artist.allow_rasterization
191
+ def draw(self, renderer: RendererBase):
192
+ """
193
+ Draw text along with the patch
194
+ """
195
+ if not self.get_visible():
196
+ return
110
197
 
111
- # Draw spatch
198
+ self._set_position(renderer)
112
199
  self.patch.draw(renderer)
113
200
  return super().draw(renderer)
plotnine/_mpl/utils.py CHANGED
@@ -139,7 +139,7 @@ def draw_bbox(bbox, figure, color="black", **kwargs):
139
139
  width=bbox.width,
140
140
  height=bbox.height,
141
141
  edgecolor=color,
142
- fill=False,
142
+ fill="facecolor" in kwargs,
143
143
  clip_on=False,
144
144
  **kwargs,
145
145
  )