Skip to content

Commit f574cf2

Browse files
committed
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).
1 parent 38476fd commit f574cf2

File tree

1 file changed

+45
-68
lines changed

1 file changed

+45
-68
lines changed

lib/matplotlib/patches.py

+45-68
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import contextlib
12
import functools
23
import math
34
from numbers import Number
@@ -479,11 +480,15 @@ def get_hatch(self):
479480
'Return the current hatching pattern'
480481
return self._hatch
481482

482-
@artist.allow_rasterization
483-
def draw(self, renderer):
484-
'Draw the :class:`Patch` to the given *renderer*.'
485-
if not self.get_visible():
486-
return
483+
@contextlib.contextmanager
484+
def _prepare_path_drawer(self, renderer):
485+
"""
486+
``draw()`` helper factored out for sharing with `FancyArrowPatch`.
487+
488+
Yields a callable that can be called as a bound ``draw_path`` method
489+
except that the first argument (the ``GraphicsContext``) has already
490+
been suitably filled in.
491+
"""
487492

488493
renderer.open_group('patch', self.get_gid())
489494
gc = renderer.new_gc()
@@ -521,21 +526,37 @@ def draw(self, renderer):
521526
if self.get_sketch_params() is not None:
522527
gc.set_sketch_params(*self.get_sketch_params())
523528

524-
path = self.get_path()
525-
transform = self.get_transform()
526-
tpath = transform.transform_path_non_affine(path)
527-
affine = transform.get_affine()
528-
529529
if self.get_path_effects():
530530
from matplotlib.patheffects import PathEffectRenderer
531531
renderer = PathEffectRenderer(self.get_path_effects(), renderer)
532532

533-
renderer.draw_path(gc, tpath, affine, rgbFace)
533+
# In `with _prepare_path_drawer(renderer) as draw_path: draw_path(...)`
534+
# (in the implementations of `draw()` below), calls to `draw_path(...)`
535+
# will occur as if they took place here with `gc` inserted as
536+
# additional first argument.
537+
yield functools.partial(renderer.draw_path, gc)
534538

535539
gc.restore()
536540
renderer.close_group('patch')
537541
self.stale = False
538542

543+
@artist.allow_rasterization
544+
def draw(self, renderer):
545+
'Draw the :class:`Patch` to the given *renderer*.'
546+
if not self.get_visible():
547+
return
548+
549+
with self._prepare_path_drawer(renderer) as draw_path:
550+
path = self.get_path()
551+
transform = self.get_transform()
552+
tpath = transform.transform_path_non_affine(path)
553+
affine = transform.get_affine()
554+
draw_path(tpath, affine,
555+
# Work around a bug in the PDF and SVG renderers, which
556+
# do not draw the hatches if the facecolor is fully
557+
# transparent, but do if it is None.
558+
self._facecolor if self._facecolor[3] else None)
559+
539560
def get_path(self):
540561
"""
541562
Return the path of this patch
@@ -4256,67 +4277,23 @@ def draw(self, renderer):
42564277
if not self.get_visible():
42574278
return
42584279

4259-
renderer.open_group('patch', self.get_gid())
4260-
gc = renderer.new_gc()
4261-
4262-
gc.set_foreground(self._edgecolor, isRGBA=True)
4263-
4264-
lw = self._linewidth
4265-
if self._edgecolor[3] == 0:
4266-
lw = 0
4267-
gc.set_linewidth(lw)
4268-
gc.set_dashes(self._dashoffset, self._dashes)
4269-
4270-
gc.set_antialiased(self._antialiased)
4271-
self._set_gc_clip(gc)
4272-
gc.set_capstyle('round')
4273-
gc.set_snap(self.get_snap())
4274-
4275-
rgbFace = self._facecolor
4276-
if rgbFace[3] == 0:
4277-
rgbFace = None # (some?) renderers expect this as no-fill signal
4278-
4279-
gc.set_alpha(self._alpha)
4280-
4281-
if self._hatch:
4282-
gc.set_hatch(self._hatch)
4283-
if self._hatch_color is not None:
4284-
try:
4285-
gc.set_hatch_color(self._hatch_color)
4286-
except AttributeError:
4287-
# if we end up with a GC that does not have this method
4288-
warnings.warn("Your backend does not support setting the "
4289-
"hatch color.")
4290-
4291-
if self.get_sketch_params() is not None:
4292-
gc.set_sketch_params(*self.get_sketch_params())
4280+
# FancyArrowPatch have traditionally forced the capstyle and joinstyle.
4281+
with cbook._setattr_cm(self, _capstyle='round', _joinstyle='round'), \
4282+
self._prepare_path_drawer(renderer) as draw_path:
42934283

4294-
# FIXME : dpi_cor is for the dpi-dependecy of the
4295-
# linewidth. There could be room for improvement.
4296-
#
4297-
# dpi_cor = renderer.points_to_pixels(1.)
4298-
self.set_dpi_cor(renderer.points_to_pixels(1.))
4299-
path, fillable = self.get_path_in_displaycoord()
4284+
# FIXME : dpi_cor is for the dpi-dependecy of the linewidth. There
4285+
# could be room for improvement.
4286+
self.set_dpi_cor(renderer.points_to_pixels(1.))
4287+
path, fillable = self.get_path_in_displaycoord()
43004288

4301-
if not cbook.iterable(fillable):
4302-
path = [path]
4303-
fillable = [fillable]
4289+
if not cbook.iterable(fillable):
4290+
path = [path]
4291+
fillable = [fillable]
43044292

4305-
affine = transforms.IdentityTransform()
4293+
affine = transforms.IdentityTransform()
43064294

4307-
if self.get_path_effects():
4308-
from matplotlib.patheffects import PathEffectRenderer
4309-
renderer = PathEffectRenderer(self.get_path_effects(), renderer)
4310-
4311-
for p, f in zip(path, fillable):
4312-
if f:
4313-
renderer.draw_path(gc, p, affine, rgbFace)
4314-
else:
4315-
renderer.draw_path(gc, p, affine, None)
4316-
4317-
gc.restore()
4318-
renderer.close_group('patch')
4319-
self.stale = False
4295+
for p, f in zip(path, fillable):
4296+
draw_path(p, affine, self._facecolor if f else None)
43204297

43214298

43224299
class ConnectionPatch(FancyArrowPatch):

0 commit comments

Comments
 (0)