diff --git a/plotnine/_mpl/patches.py b/plotnine/_mpl/patches.py index 245c8a5d3..9367da7f1 100644 --- a/plotnine/_mpl/patches.py +++ b/plotnine/_mpl/patches.py @@ -111,16 +111,41 @@ def draw(self, renderer): class InsideStrokedRectangle(Rectangle): """ - A rectangle whose stroked is fully contained within it + A rectangle whose stroke is fully contained within it """ @artist.allow_rasterization def draw(self, renderer): """ - Draw with the bounds of the rectangle adjusted to contain the stroke + Draw stroking at 2·lw and clipping to the rectangle's own boundary + + The outer half of the stroke is removed by the clip, leaving exactly + `lw` of stroke fully inside the rectangle, with the visible outer edge + of the stroke on the original boundary. This is transform-agnostic + (works under data, axes, identity, and DrawingArea transforms) and + idempotent across redraws. """ - x, y = self.xy - w, h = self.get_width(), self.get_height() lw = self.get_linewidth() - self.set_bounds((x + lw / 2), (y + lw / 2), (w - lw), (h - lw)) - super().draw(renderer) + if lw <= 0: + return super().draw(renderer) + + saved_clip_path = self.get_clip_path() + saved_clip_on = self.get_clip_on() + + # Dash lengths track the linewidth: matplotlib stores the dash + # pattern as multipliers and scales them whenever set_linewidth is + # called. Doubling lw below would therefore stretch the dashes 2x. + # Snapshot the pattern at the user's lw now and write it back after + # the doubling so the dashes render at their requested length. + original_dash_pattern = self._dash_pattern + try: + self.set_linewidth(2 * lw) + self._dash_pattern = original_dash_pattern + self.set_clip_path(self.get_path(), self.get_transform()) + self.set_clip_on(True) + self.set_snap(False) + super().draw(renderer) + finally: + self.set_linewidth(lw) + self.set_clip_path(saved_clip_path) + self.set_clip_on(saved_clip_on) diff --git a/tests/baseline_images/test_theme/theme_light.png b/tests/baseline_images/test_theme/theme_light.png index 8efe51b2a..a170595d6 100644 Binary files a/tests/baseline_images/test_theme/theme_light.png and b/tests/baseline_images/test_theme/theme_light.png differ diff --git a/tests/baseline_images/test_theme/theme_linedraw.png b/tests/baseline_images/test_theme/theme_linedraw.png index 6d5c8bd5e..325f1ed3b 100644 Binary files a/tests/baseline_images/test_theme/theme_linedraw.png and b/tests/baseline_images/test_theme/theme_linedraw.png differ