Skip to content

Commit ec56a0c

Browse files
committed
Lazily resolve colors for Patches.
This is consistent with Line2Ds and avoids complications with _original_{face,edge}color. test_patch_color_none was removed as it doesn't make much sense anymore (fixing it would just mean testing that mcolors.to_rgba is working as expected).
1 parent 4b1f956 commit ec56a0c

File tree

6 files changed

+79
-74
lines changed

6 files changed

+79
-74
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Lazy color resolution for patches
2+
`````````````````````````````````
3+
4+
Patches now lazily resolve colors and apply alpha at draw time, similarly
5+
to Line2Ds. In particular, this means that they will be affected by later
6+
modifications to :rc:`patch.facecolor` and :rc:`patch.edgecolor`, and that
7+
``patch.get_facecolor()`` and ``patch.get_edgecolor()`` no longer necessarily
8+
return RGBA tuples but rather whatever was passed in with the corresponding
9+
setters.
10+
11+
:rc:`hatch.color` is still resolved at artist creation time as there is no
12+
other way to set the hatch color.

lib/matplotlib/patches.py

Lines changed: 46 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ def __init__(self,
6868
if antialiased is None:
6969
antialiased = mpl.rcParams['patch.antialiased']
7070

71-
self._hatch_color = colors.to_rgba(mpl.rcParams['hatch.color'])
7271
self._fill = True # needed for set_facecolor call
7372
if color is not None:
7473
if edgecolor is not None or facecolor is not None:
@@ -87,6 +86,7 @@ def __init__(self,
8786
self.set_linewidth(linewidth)
8887
self.set_antialiased(antialiased)
8988
self.set_hatch(hatch)
89+
self._orig_hatch_color = colors.to_rgba(mpl.rcParams['hatch.color'])
9090
self.set_capstyle(capstyle)
9191
self.set_joinstyle(joinstyle)
9292
self._combined_transform = transforms.IdentityTransform()
@@ -115,7 +115,7 @@ def _process_radius(self, radius):
115115
if isinstance(self._picker, Number):
116116
_radius = self._picker
117117
else:
118-
if self.get_edgecolor()[3] == 0:
118+
if self._get_drawn_edgecolor()[3] == 0:
119119
_radius = 0
120120
else:
121121
_radius = self.get_linewidth()
@@ -170,7 +170,6 @@ def update_from(self, other):
170170
self._facecolor = other._facecolor
171171
self._fill = other._fill
172172
self._hatch = other._hatch
173-
self._hatch_color = other._hatch_color
174173
# copy the unscaled dash pattern
175174
self._us_dashes = other._us_dashes
176175
self.set_linewidth(other._linewidth) # also sets dash properties
@@ -221,13 +220,23 @@ def get_edgecolor(self):
221220
"""
222221
Return the edge color of the :class:`Patch`.
223222
"""
224-
return self._edgecolor
223+
ec = self._edgecolor
224+
if ec is None:
225+
if (mpl.rcParams['patch.force_edgecolor'] or
226+
not self._fill or self._edge_default):
227+
ec = mpl.rcParams['patch.edgecolor']
228+
else:
229+
ec = 'none'
230+
return ec
225231

226232
def get_facecolor(self):
227233
"""
228234
Return the face color of the :class:`Patch`.
229235
"""
230-
return self._facecolor
236+
fc = self._facecolor
237+
if fc is None:
238+
fc = mpl.rcParams['patch.facecolor']
239+
return fc
231240

232241
def get_linewidth(self):
233242
"""
@@ -254,21 +263,6 @@ def set_antialiased(self, aa):
254263
self._antialiased = aa
255264
self.stale = True
256265

257-
def _set_edgecolor(self, color):
258-
set_hatch_color = True
259-
if color is None:
260-
if (mpl.rcParams['patch.force_edgecolor'] or
261-
not self._fill or self._edge_default):
262-
color = mpl.rcParams['patch.edgecolor']
263-
else:
264-
color = 'none'
265-
set_hatch_color = False
266-
267-
self._edgecolor = colors.to_rgba(color, self._alpha)
268-
if set_hatch_color:
269-
self._hatch_color = self._edgecolor
270-
self.stale = True
271-
272266
def set_edgecolor(self, color):
273267
"""
274268
Set the patch edge color.
@@ -277,14 +271,7 @@ def set_edgecolor(self, color):
277271
----------
278272
color : color or None or 'auto'
279273
"""
280-
self._original_edgecolor = color
281-
self._set_edgecolor(color)
282-
283-
def _set_facecolor(self, color):
284-
if color is None:
285-
color = mpl.rcParams['patch.facecolor']
286-
alpha = self._alpha if self._fill else 0
287-
self._facecolor = colors.to_rgba(color, alpha)
274+
self._edgecolor = color
288275
self.stale = True
289276

290277
def set_facecolor(self, color):
@@ -295,8 +282,8 @@ def set_facecolor(self, color):
295282
----------
296283
color : color or None
297284
"""
298-
self._original_facecolor = color
299-
self._set_facecolor(color)
285+
self._facecolor = color
286+
self.stale = True
300287

301288
def set_color(self, c):
302289
"""
@@ -314,24 +301,6 @@ def set_color(self, c):
314301
self.set_facecolor(c)
315302
self.set_edgecolor(c)
316303

317-
def set_alpha(self, alpha):
318-
"""
319-
Set the alpha transparency of the patch.
320-
321-
Parameters
322-
----------
323-
alpha : float or None
324-
"""
325-
if alpha is not None:
326-
try:
327-
float(alpha)
328-
except TypeError:
329-
raise TypeError('alpha must be a float or None')
330-
artist.Artist.set_alpha(self, alpha)
331-
self._set_facecolor(self._original_facecolor)
332-
self._set_edgecolor(self._original_edgecolor)
333-
# stale is already True
334-
335304
def set_linewidth(self, w):
336305
"""
337306
Set the patch linewidth in points
@@ -393,8 +362,7 @@ def set_fill(self, b):
393362
b : bool
394363
"""
395364
self._fill = bool(b)
396-
self._set_facecolor(self._original_facecolor)
397-
self._set_edgecolor(self._original_edgecolor)
365+
self.set_edgecolor(self._edgecolor)
398366
self.stale = True
399367

400368
def get_fill(self):
@@ -479,6 +447,24 @@ def get_hatch(self):
479447
'Return the current hatching pattern'
480448
return self._hatch
481449

450+
def _get_drawn_edgecolor(self):
451+
return colors.to_rgba(self.get_edgecolor(), self._alpha)
452+
453+
def _get_drawn_facecolor(self):
454+
return colors.to_rgba(self.get_facecolor(),
455+
self._alpha if self._fill else 0)
456+
457+
def _get_drawn_hatchcolor(self):
458+
hc = self._edgecolor
459+
if hc is None:
460+
if (mpl.rcParams['patch.force_edgecolor'] or
461+
not self._fill or self._edge_default):
462+
hc = mpl.rcParams['patch.edgecolor']
463+
else:
464+
# Not affected by alpha.
465+
return self._orig_hatch_color
466+
return colors.to_rgba(hc, self._alpha)
467+
482468
@artist.allow_rasterization
483469
def draw(self, renderer):
484470
'Draw the :class:`Patch` to the given *renderer*.'
@@ -488,10 +474,11 @@ def draw(self, renderer):
488474
renderer.open_group('patch', self.get_gid())
489475
gc = renderer.new_gc()
490476

491-
gc.set_foreground(self._edgecolor, isRGBA=True)
477+
edgecolor = self._get_drawn_edgecolor()
478+
gc.set_foreground(edgecolor, isRGBA=True)
492479

493480
lw = self._linewidth
494-
if self._edgecolor[3] == 0:
481+
if edgecolor[3] == 0:
495482
lw = 0
496483
gc.set_linewidth(lw)
497484
gc.set_dashes(0, self._dashes)
@@ -503,7 +490,7 @@ def draw(self, renderer):
503490
gc.set_url(self._url)
504491
gc.set_snap(self.get_snap())
505492

506-
rgbFace = self._facecolor
493+
rgbFace = self._get_drawn_facecolor()
507494
if rgbFace[3] == 0:
508495
rgbFace = None # (some?) renderers expect this as no-fill signal
509496

@@ -512,7 +499,7 @@ def draw(self, renderer):
512499
if self._hatch:
513500
gc.set_hatch(self._hatch)
514501
try:
515-
gc.set_hatch_color(self._hatch_color)
502+
gc.set_hatch_color(self._get_drawn_hatchcolor())
516503
except AttributeError:
517504
# if we end up with a GC that does not have this method
518505
warnings.warn(
@@ -4259,10 +4246,11 @@ def draw(self, renderer):
42594246
renderer.open_group('patch', self.get_gid())
42604247
gc = renderer.new_gc()
42614248

4262-
gc.set_foreground(self._edgecolor, isRGBA=True)
4249+
edgecolor = self._get_drawn_edgecolor()
4250+
gc.set_foreground(edgecolor, isRGBA=True)
42634251

42644252
lw = self._linewidth
4265-
if self._edgecolor[3] == 0:
4253+
if edgecolor[3] == 0:
42664254
lw = 0
42674255
gc.set_linewidth(lw)
42684256
gc.set_dashes(self._dashoffset, self._dashes)
@@ -4272,7 +4260,7 @@ def draw(self, renderer):
42724260
gc.set_capstyle('round')
42734261
gc.set_snap(self.get_snap())
42744262

4275-
rgbFace = self._facecolor
4263+
rgbFace = self._get_drawn_facecolor()
42764264
if rgbFace[3] == 0:
42774265
rgbFace = None # (some?) renderers expect this as no-fill signal
42784266

@@ -4282,7 +4270,7 @@ def draw(self, renderer):
42824270
gc.set_hatch(self._hatch)
42834271
if self._hatch_color is not None:
42844272
try:
4285-
gc.set_hatch_color(self._hatch_color)
4273+
gc.set_hatch_color(self._get_drawn_hatchcolor())
42864274
except AttributeError:
42874275
# if we end up with a GC that does not have this method
42884276
warnings.warn("Your backend does not support setting the "

lib/matplotlib/tests/test_axes.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1461,16 +1461,16 @@ def test_bar_color_none_alpha():
14611461
ax = plt.gca()
14621462
rects = ax.bar([1, 2], [2, 4], alpha=0.3, color='none', edgecolor='r')
14631463
for rect in rects:
1464-
assert rect.get_facecolor() == (0, 0, 0, 0)
1465-
assert rect.get_edgecolor() == (1, 0, 0, 0.3)
1464+
assert rect._get_drawn_facecolor() == (0, 0, 0, 0)
1465+
assert rect._get_drawn_edgecolor() == (1, 0, 0, 0.3)
14661466

14671467

14681468
def test_bar_edgecolor_none_alpha():
14691469
ax = plt.gca()
14701470
rects = ax.bar([1, 2], [2, 4], alpha=0.3, color='r', edgecolor='none')
14711471
for rect in rects:
1472-
assert rect.get_facecolor() == (1, 0, 0, 0.3)
1473-
assert rect.get_edgecolor() == (0, 0, 0, 0)
1472+
assert rect._get_drawn_facecolor() == (1, 0, 0, 0.3)
1473+
assert rect._get_drawn_edgecolor() == (0, 0, 0, 0)
14741474

14751475

14761476
@image_comparison(baseline_images=['barh_tick_label'],
@@ -5659,7 +5659,8 @@ def test_bar_broadcast_args():
56595659
ax.bar(0, 1, bottom=range(4), width=1, orientation='horizontal')
56605660
# Check that edgecolor gets broadcasted.
56615661
rect1, rect2 = ax.bar([0, 1], [0, 1], edgecolor=(.1, .2, .3, .4))
5662-
assert rect1.get_edgecolor() == rect2.get_edgecolor() == (.1, .2, .3, .4)
5662+
assert rect1._get_drawn_edgecolor() == rect2._get_drawn_edgecolor() \
5663+
== (.1, .2, .3, .4)
56635664

56645665

56655666
def test_invalid_axis_limits():

lib/matplotlib/tests/test_legend.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99
from matplotlib.testing.decorators import image_comparison
1010
import matplotlib.pyplot as plt
1111
import matplotlib as mpl
12-
import matplotlib.transforms as mtransforms
1312
import matplotlib.collections as mcollections
14-
from matplotlib.legend_handler import HandlerTuple
13+
import matplotlib.colors as mcolors
1514
import matplotlib.legend as mlegend
15+
from matplotlib.legend_handler import HandlerTuple
16+
import matplotlib.transforms as mtransforms
1617
from matplotlib.cbook.deprecation import MatplotlibDeprecationWarning
1718

1819

@@ -543,3 +544,14 @@ def test_draggable():
543544
with pytest.warns(MatplotlibDeprecationWarning):
544545
legend.draggable()
545546
assert not legend.get_draggable()
547+
548+
549+
def test_alpha_handles():
550+
x, n, hh = plt.hist([1, 2, 3], alpha=0.25, label='data', color='red')
551+
legend = plt.legend()
552+
for lh in legend.legendHandles:
553+
lh.set_alpha(1.0)
554+
assert mcolors.to_rgb(lh.get_facecolor()) \
555+
== mcolors.to_rgb(hh[1].get_facecolor())
556+
assert mcolors.to_rgb(lh.get_edgecolor()) \
557+
== mcolors.to_rgb(hh[1].get_edgecolor())

lib/matplotlib/tests/test_patches.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -176,14 +176,6 @@ def test_patch_alpha_override():
176176
ax.set_ylim([-1, 2])
177177

178178

179-
@pytest.mark.style('default')
180-
def test_patch_color_none():
181-
# Make sure the alpha kwarg does not override 'none' facecolor.
182-
# Addresses issue #7478.
183-
c = plt.Circle((0, 0), 1, facecolor='none', alpha=1)
184-
assert c.get_facecolor()[0] == 0
185-
186-
187179
@image_comparison(baseline_images=['patch_custom_linestyle'],
188180
remove_text=True)
189181
def test_patch_custom_linestyle():

lib/matplotlib/tests/test_rcparams.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ def test_legend_colors(color_type, param_dict, target):
172172
_, ax = plt.subplots()
173173
ax.plot(range(3), label='test')
174174
leg = ax.legend()
175-
assert getattr(leg.legendPatch, get_func)() == target
175+
assert mcolors.same_color(getattr(leg.legendPatch, get_func)(), target)
176176

177177

178178
def test_mfc_rcparams():

0 commit comments

Comments
 (0)