From 5c564d4007aaa02aaf671df1503e5e0aca61ed47 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Mon, 23 Jul 2018 00:59:20 +0200 Subject: [PATCH] Factor out common code between Patch.draw and FancyArrowPatch.draw. The code setting up the renderer and gc are nearly identical, so we can factor it out as a helper contextmanager. The only difference is that FancyArrowPatch then calls draw_path multiple times with various arguments. Note that the refactoring exposed the fact that FancyArrowPatches are (incorrectly, IMO) ignoring joinstyles and capstyles (this is necessary to make image test pass, but we probably will want to generate new images with the "correct" (non-ignoring) behavior at some point). --- lib/matplotlib/patches.py | 122 ++++++++++++++++---------------------- 1 file changed, 51 insertions(+), 71 deletions(-) diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 06247e6d4156..ea54d84b95b3 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -1,3 +1,4 @@ +import contextlib import functools import math from numbers import Number @@ -481,11 +482,16 @@ def get_hatch(self): 'Return the current hatching pattern' return self._hatch - @artist.allow_rasterization - def draw(self, renderer): - 'Draw the :class:`Patch` to the given *renderer*.' - if not self.get_visible(): - return + @contextlib.contextmanager + def _bind_draw_path_function(self, renderer): + """ + ``draw()`` helper factored out for sharing with `FancyArrowPatch`. + + Yields a callable ``dp`` such that calling ``dp(*args, **kwargs)`` is + equivalent to calling ``renderer1.draw_path(gc, *args, **kwargs)`` + where ``renderer1`` and ``gc`` have been suitably set from ``renderer`` + and the artist's properties. + """ renderer.open_group('patch', self.get_gid()) gc = renderer.new_gc() @@ -496,7 +502,7 @@ def draw(self, renderer): if self._edgecolor[3] == 0: lw = 0 gc.set_linewidth(lw) - gc.set_dashes(0, self._dashes) + gc.set_dashes(self._dashoffset, self._dashes) gc.set_capstyle(self._capstyle) gc.set_joinstyle(self._joinstyle) @@ -525,21 +531,39 @@ def draw(self, renderer): if self.get_sketch_params() is not None: gc.set_sketch_params(*self.get_sketch_params()) - path = self.get_path() - transform = self.get_transform() - tpath = transform.transform_path_non_affine(path) - affine = transform.get_affine() - if self.get_path_effects(): from matplotlib.patheffects import PathEffectRenderer renderer = PathEffectRenderer(self.get_path_effects(), renderer) - renderer.draw_path(gc, tpath, affine, rgbFace) + # In `with _bind_draw_path_function(renderer) as draw_path: ...` + # (in the implementations of `draw()` below), calls to `draw_path(...)` + # will occur as if they took place here with `gc` inserted as + # additional first argument. + yield functools.partial(renderer.draw_path, gc) gc.restore() renderer.close_group('patch') self.stale = False + @artist.allow_rasterization + def draw(self, renderer): + 'Draw the :class:`Patch` to the given *renderer*.' + if not self.get_visible(): + return + + # Patch has traditionally ignored the dashoffset. + with cbook._setattr_cm(self, _dashoffset=0), \ + self._bind_draw_path_function(renderer) as draw_path: + path = self.get_path() + transform = self.get_transform() + tpath = transform.transform_path_non_affine(path) + affine = transform.get_affine() + draw_path(tpath, affine, + # Work around a bug in the PDF and SVG renderers, which + # do not draw the hatches if the facecolor is fully + # transparent, but do if it is None. + self._facecolor if self._facecolor[3] else None) + def get_path(self): """ Return the path of this patch @@ -4263,69 +4287,25 @@ def draw(self, renderer): if not self.get_visible(): return - renderer.open_group('patch', self.get_gid()) - gc = renderer.new_gc() - - gc.set_foreground(self._edgecolor, isRGBA=True) - - lw = self._linewidth - if self._edgecolor[3] == 0: - lw = 0 - gc.set_linewidth(lw) - gc.set_dashes(self._dashoffset, self._dashes) - - gc.set_antialiased(self._antialiased) - self._set_gc_clip(gc) - gc.set_capstyle('round') - gc.set_snap(self.get_snap()) + # FancyArrowPatch has traditionally forced the capstyle and joinstyle. + with cbook._setattr_cm(self, _capstyle='round', _joinstyle='round'), \ + self._bind_draw_path_function(renderer) as draw_path: - rgbFace = self._facecolor - if rgbFace[3] == 0: - rgbFace = None # (some?) renderers expect this as no-fill signal + # FIXME : dpi_cor is for the dpi-dependecy of the linewidth. There + # could be room for improvement. + self.set_dpi_cor(renderer.points_to_pixels(1.)) + path, fillable = self.get_path_in_displaycoord() - gc.set_alpha(self._alpha) + if not cbook.iterable(fillable): + path = [path] + fillable = [fillable] - if self._hatch: - gc.set_hatch(self._hatch) - if self._hatch_color is not None: - try: - gc.set_hatch_color(self._hatch_color) - except AttributeError: - # if we end up with a GC that does not have this method - cbook.warn_deprecated( - "3.1", "Your backend does not support setting the " - "hatch color; such backends will become unsupported " - "in Matplotlib 3.3.") + affine = transforms.IdentityTransform() - if self.get_sketch_params() is not None: - gc.set_sketch_params(*self.get_sketch_params()) - - # FIXME : dpi_cor is for the dpi-dependecy of the - # linewidth. There could be room for improvement. - # - # dpi_cor = renderer.points_to_pixels(1.) - self.set_dpi_cor(renderer.points_to_pixels(1.)) - path, fillable = self.get_path_in_displaycoord() - - if not cbook.iterable(fillable): - path = [path] - fillable = [fillable] - - affine = transforms.IdentityTransform() - - if self.get_path_effects(): - from matplotlib.patheffects import PathEffectRenderer - renderer = PathEffectRenderer(self.get_path_effects(), renderer) - - for p, f in zip(path, fillable): - if f: - renderer.draw_path(gc, p, affine, rgbFace) - else: - renderer.draw_path(gc, p, affine, None) - - gc.restore() - renderer.close_group('patch') - self.stale = False + for p, f in zip(path, fillable): + draw_path( + p, affine, + self._facecolor if f and self._facecolor[3] else None) class ConnectionPatch(FancyArrowPatch):