Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit c11175d

Browse filesBrowse files
Impaler343r3kstetimhoffm
authored
Separates edgecolor from hatchcolor (#28104)
* separated hatchcolor from edgecolor in patches * fixed logic for inherit in collections * added a gallery example * made suggested changes * reverted use of `inherit` * fixed the logic for inheriting from edgecolor * fixed docs and enhanced tests * Apply suggestions from code review Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> * made suggested changes * minor fix * use 'edge' instead of 'inherit' * fix rcsetup * enhanced docs and added note for follow-up PR * Made suggested changes * made suggested changes * enhanced tests and minor fix in docs * minor changes to docs * Changed wording and alt text * Minor Fix --------- Co-authored-by: anTon <138380708+r3kste@users.noreply.github.com> Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>
1 parent c2d502d commit c11175d
Copy full SHA for c11175d

File tree

11 files changed

+225
-11
lines changed
Filter options

11 files changed

+225
-11
lines changed
+59Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
Separated ``hatchcolor`` from ``edgecolor``
2+
-------------------------------------------
3+
4+
When the *hatchcolor* parameter is specified, it will be used for the hatch.
5+
If it is not specified, it will fall back to using :rc:`hatch.color`.
6+
The special value 'edge' uses the patch edgecolor, with a fallback to
7+
:rc:`patch.edgecolor` if the patch edgecolor is 'none'.
8+
Previously, hatch colors were the same as edge colors, with a fallback to
9+
:rc:`hatch.color` if the patch did not have an edge color.
10+
11+
.. plot::
12+
:include-source: true
13+
:alt: Four Rectangle patches, each displaying the color of hatches in different specifications of edgecolor and hatchcolor. Top left has hatchcolor='black' representing the default value when both hatchcolor and edgecolor are not set, top right has edgecolor='blue' and hatchcolor='black' which remains when the edgecolor is set again, bottom left has edgecolor='red' and hatchcolor='orange' on explicit specification and bottom right has edgecolor='green' and hatchcolor='green' when the hatchcolor is not set.
14+
15+
import matplotlib as mpl
16+
import matplotlib.pyplot as plt
17+
from matplotlib.patches import Rectangle
18+
19+
fig, ax = plt.subplots()
20+
21+
# In this case, hatchcolor is orange
22+
patch1 = Rectangle((0.1, 0.1), 0.3, 0.3, edgecolor='red', linewidth=2,
23+
hatch='//', hatchcolor='orange')
24+
ax.add_patch(patch1)
25+
26+
# When hatchcolor is not specified, it matches edgecolor
27+
# In this case, hatchcolor is green
28+
patch2 = Rectangle((0.6, 0.1), 0.3, 0.3, edgecolor='green', linewidth=2,
29+
hatch='//', facecolor='none')
30+
ax.add_patch(patch2)
31+
32+
# If both hatchcolor and edgecolor are not specified
33+
# it will default to the 'patch.edgecolor' rcParam, which is black by default
34+
# In this case, hatchcolor is black
35+
patch3 = Rectangle((0.1, 0.6), 0.3, 0.3, hatch='//')
36+
ax.add_patch(patch3)
37+
38+
# When using `hatch.color` in the `rcParams`
39+
# edgecolor will now not overwrite hatchcolor
40+
# In this case, hatchcolor is black
41+
with plt.rc_context({'hatch.color': 'black'}):
42+
patch4 = Rectangle((0.6, 0.6), 0.3, 0.3, edgecolor='blue', linewidth=2,
43+
hatch='//', facecolor='none')
44+
45+
# hatchcolor is black (it uses the `hatch.color` rcParam value)
46+
patch4.set_edgecolor('blue')
47+
# hatchcolor is still black (here, it does not update when edgecolor changes)
48+
ax.add_patch(patch4)
49+
50+
ax.annotate("hatchcolor = 'orange'",
51+
xy=(.5, 1.03), xycoords=patch1, ha='center', va='bottom')
52+
ax.annotate("hatch color unspecified\nedgecolor='green'",
53+
xy=(.5, 1.03), xycoords=patch2, ha='center', va='bottom')
54+
ax.annotate("hatch color unspecified\nusing patch.edgecolor",
55+
xy=(.5, 1.03), xycoords=patch3, ha='center', va='bottom')
56+
ax.annotate("hatch.color='black'",
57+
xy=(.5, 1.03), xycoords=patch4, ha='center', va='bottom')
58+
59+
plt.show()
+43Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""
2+
================
3+
Patch hatchcolor
4+
================
5+
6+
This example shows how to use the *hatchcolor* parameter to set the color of
7+
the hatch. The *hatchcolor* parameter is available for `~.patches.Patch`,
8+
child classes of Patch, and methods that pass through to Patch.
9+
"""
10+
11+
import matplotlib.pyplot as plt
12+
import numpy as np
13+
14+
from matplotlib.patches import Rectangle
15+
16+
fig, (ax1, ax2) = plt.subplots(1, 2)
17+
18+
# Rectangle with red hatch color and black edge color
19+
ax1.add_patch(Rectangle((0.1, 0.5), 0.8, 0.3, hatch=".", hatchcolor='red',
20+
edgecolor='black', lw=2))
21+
# If hatchcolor is not passed, the hatch will match the edge color
22+
ax1.add_patch(Rectangle((0.1, 0.1), 0.8, 0.3, hatch='x', edgecolor='orange', lw=2))
23+
24+
x = np.arange(1, 5)
25+
y = np.arange(1, 5)
26+
27+
ax2.bar(x, y, facecolor='none', edgecolor='red', hatch='//', hatchcolor='blue')
28+
ax2.set_xlim(0, 5)
29+
ax2.set_ylim(0, 5)
30+
31+
plt.show()
32+
33+
# %%
34+
#
35+
# .. admonition:: References
36+
#
37+
# The use of the following functions, methods, classes and modules is shown
38+
# in this example:
39+
#
40+
# - `matplotlib.patches`
41+
# - `matplotlib.patches.Polygon`
42+
# - `matplotlib.axes.Axes.add_patch`
43+
# - `matplotlib.axes.Axes.bar` / `matplotlib.pyplot.bar`

‎lib/matplotlib/backend_bases.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backend_bases.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -690,7 +690,7 @@ def __init__(self):
690690
self._linewidth = 1
691691
self._rgb = (0.0, 0.0, 0.0, 1.0)
692692
self._hatch = None
693-
self._hatch_color = colors.to_rgba(rcParams['hatch.color'])
693+
self._hatch_color = None
694694
self._hatch_linewidth = rcParams['hatch.linewidth']
695695
self._url = None
696696
self._gid = None

‎lib/matplotlib/collections.py

Copy file name to clipboardExpand all lines: lib/matplotlib/collections.py
+7-1Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,13 @@ def __init__(self, *,
174174
self._face_is_mapped = None
175175
self._edge_is_mapped = None
176176
self._mapped_colors = None # calculated in update_scalarmappable
177-
self._hatch_color = mcolors.to_rgba(mpl.rcParams['hatch.color'])
177+
178+
# Temporary logic to set hatchcolor. This eager resolution is temporary
179+
# and will be replaced by a proper mechanism in a follow-up PR.
180+
hatch_color = mpl.rcParams['hatch.color']
181+
if hatch_color == 'edge':
182+
hatch_color = mpl.rcParams['patch.edgecolor']
183+
self._hatch_color = mcolors.to_rgba(hatch_color)
178184
self._hatch_linewidth = mpl.rcParams['hatch.linewidth']
179185
self.set_facecolor(facecolors)
180186
self.set_edgecolor(edgecolors)

‎lib/matplotlib/mpl-data/matplotlibrc

Copy file name to clipboardExpand all lines: lib/matplotlib/mpl-data/matplotlibrc
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@
162162
## ***************************************************************************
163163
## * HATCHES *
164164
## ***************************************************************************
165-
#hatch.color: black
165+
#hatch.color: edge
166166
#hatch.linewidth: 1.0
167167

168168

‎lib/matplotlib/mpl-data/stylelib/_classic_test_patch.mplstyle

Copy file name to clipboardExpand all lines: lib/matplotlib/mpl-data/stylelib/_classic_test_patch.mplstyle
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@
44
text.kerning_factor : 6
55

66
ytick.alignment: center_baseline
7+
8+
hatch.color: edge

‎lib/matplotlib/patches.py

Copy file name to clipboardExpand all lines: lib/matplotlib/patches.py
+37-7Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ def __init__(self, *,
5656
fill=True,
5757
capstyle=None,
5858
joinstyle=None,
59+
hatchcolor=None,
5960
**kwargs):
6061
"""
6162
The following kwarg properties are supported
@@ -71,7 +72,6 @@ def __init__(self, *,
7172
if joinstyle is None:
7273
joinstyle = JoinStyle.miter
7374

74-
self._hatch_color = colors.to_rgba(mpl.rcParams['hatch.color'])
7575
self._hatch_linewidth = mpl.rcParams['hatch.linewidth']
7676
self._fill = bool(fill) # needed for set_facecolor call
7777
if color is not None:
@@ -82,6 +82,7 @@ def __init__(self, *,
8282
self.set_color(color)
8383
else:
8484
self.set_edgecolor(edgecolor)
85+
self.set_hatchcolor(hatchcolor)
8586
self.set_facecolor(facecolor)
8687

8788
self._linewidth = 0
@@ -291,6 +292,7 @@ def update_from(self, other):
291292
self._fill = other._fill
292293
self._hatch = other._hatch
293294
self._hatch_color = other._hatch_color
295+
self._original_hatchcolor = other._original_hatchcolor
294296
self._unscaled_dash_pattern = other._unscaled_dash_pattern
295297
self.set_linewidth(other._linewidth) # also sets scaled dashes
296298
self.set_transform(other.get_data_transform())
@@ -338,6 +340,14 @@ def get_facecolor(self):
338340
"""Return the face color."""
339341
return self._facecolor
340342

343+
def get_hatchcolor(self):
344+
"""Return the hatch color."""
345+
if self._hatch_color == 'edge':
346+
if self._edgecolor[3] == 0: # fully transparent
347+
return colors.to_rgba(mpl.rcParams['patch.edgecolor'])
348+
return self.get_edgecolor()
349+
return self._hatch_color
350+
341351
def get_linewidth(self):
342352
"""Return the line width in points."""
343353
return self._linewidth
@@ -358,18 +368,14 @@ def set_antialiased(self, aa):
358368
self.stale = True
359369

360370
def _set_edgecolor(self, color):
361-
set_hatch_color = True
362371
if color is None:
363372
if (mpl.rcParams['patch.force_edgecolor'] or
364373
not self._fill or self._edge_default):
365374
color = mpl.rcParams['patch.edgecolor']
366375
else:
367376
color = 'none'
368-
set_hatch_color = False
369377

370378
self._edgecolor = colors.to_rgba(color, self._alpha)
371-
if set_hatch_color:
372-
self._hatch_color = self._edgecolor
373379
self.stale = True
374380

375381
def set_edgecolor(self, color):
@@ -413,14 +419,37 @@ def set_color(self, c):
413419
Patch.set_facecolor, Patch.set_edgecolor
414420
For setting the edge or face color individually.
415421
"""
416-
self.set_facecolor(c)
417422
self.set_edgecolor(c)
423+
self.set_hatchcolor(c)
424+
self.set_facecolor(c)
425+
426+
def _set_hatchcolor(self, color):
427+
color = mpl._val_or_rc(color, 'hatch.color')
428+
if color == 'edge':
429+
self._hatch_color = 'edge'
430+
else:
431+
self._hatch_color = colors.to_rgba(color, self._alpha)
432+
self.stale = True
433+
434+
def set_hatchcolor(self, color):
435+
"""
436+
Set the patch hatch color.
437+
438+
Parameters
439+
----------
440+
color : :mpltype:`color` or 'edge' or None
441+
"""
442+
if cbook._str_equal(color, 'edge'):
443+
color = 'edge'
444+
self._original_hatchcolor = color
445+
self._set_hatchcolor(color)
418446

419447
def set_alpha(self, alpha):
420448
# docstring inherited
421449
super().set_alpha(alpha)
422450
self._set_facecolor(self._original_facecolor)
423451
self._set_edgecolor(self._original_edgecolor)
452+
self._set_hatchcolor(self._original_hatchcolor)
424453
# stale is already True
425454

426455
def set_linewidth(self, w):
@@ -482,6 +511,7 @@ def set_fill(self, b):
482511
self._fill = bool(b)
483512
self._set_facecolor(self._original_facecolor)
484513
self._set_edgecolor(self._original_edgecolor)
514+
self._set_hatchcolor(self._original_hatchcolor)
485515
self.stale = True
486516

487517
def get_fill(self):
@@ -608,7 +638,7 @@ def _draw_paths_with_artist_properties(
608638

609639
if self._hatch:
610640
gc.set_hatch(self._hatch)
611-
gc.set_hatch_color(self._hatch_color)
641+
gc.set_hatch_color(self.get_hatchcolor())
612642
gc.set_hatch_linewidth(self._hatch_linewidth)
613643

614644
if self.get_sketch_params() is not None:

‎lib/matplotlib/patches.pyi

Copy file name to clipboardExpand all lines: lib/matplotlib/patches.pyi
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class Patch(artist.Artist):
2525
fill: bool = ...,
2626
capstyle: CapStyleType | None = ...,
2727
joinstyle: JoinStyleType | None = ...,
28+
hatchcolor: ColorType | None = ...,
2829
**kwargs,
2930
) -> None: ...
3031
def get_verts(self) -> ArrayLike: ...
@@ -42,12 +43,14 @@ class Patch(artist.Artist):
4243
def get_antialiased(self) -> bool: ...
4344
def get_edgecolor(self) -> ColorType: ...
4445
def get_facecolor(self) -> ColorType: ...
46+
def get_hatchcolor(self) -> ColorType: ...
4547
def get_linewidth(self) -> float: ...
4648
def get_linestyle(self) -> LineStyleType: ...
4749
def set_antialiased(self, aa: bool | None) -> None: ...
4850
def set_edgecolor(self, color: ColorType | None) -> None: ...
4951
def set_facecolor(self, color: ColorType | None) -> None: ...
5052
def set_color(self, c: ColorType | None) -> None: ...
53+
def set_hatchcolor(self, color: ColorType | Literal["edge"] | None) -> None: ...
5154
def set_alpha(self, alpha: float | None) -> None: ...
5255
def set_linewidth(self, w: float | None) -> None: ...
5356
def set_linestyle(self, ls: LineStyleType | None) -> None: ...

‎lib/matplotlib/rcsetup.py

Copy file name to clipboardExpand all lines: lib/matplotlib/rcsetup.py
+7-1Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,12 @@ def validate_color_or_auto(s):
301301
return validate_color(s)
302302

303303

304+
def _validate_color_or_edge(s):
305+
if cbook._str_equal(s, 'edge'):
306+
return s
307+
return validate_color(s)
308+
309+
304310
def validate_color_for_prop_cycle(s):
305311
# N-th color cycle syntax can't go into the color cycle.
306312
if isinstance(s, str) and re.match("^C[0-9]$", s):
@@ -950,7 +956,7 @@ def _convert_validator_spec(key, conv):
950956
"patch.antialiased": validate_bool, # antialiased (no jaggies)
951957

952958
## hatch props
953-
"hatch.color": validate_color,
959+
"hatch.color": _validate_color_or_edge,
954960
"hatch.linewidth": validate_float,
955961

956962
## Histogram properties

‎lib/matplotlib/rcsetup.pyi

Copy file name to clipboardExpand all lines: lib/matplotlib/rcsetup.pyi
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ _auto_backend_sentinel: object
4848
def validate_backend(s: Any) -> str: ...
4949
def validate_color_or_inherit(s: Any) -> Literal["inherit"] | ColorType: ...
5050
def validate_color_or_auto(s: Any) -> ColorType | Literal["auto"]: ...
51+
def _validate_color_or_edge(s: Any) -> ColorType | Literal["edge"]: ...
5152
def validate_color_for_prop_cycle(s: Any) -> ColorType: ...
5253
def validate_color(s: Any) -> ColorType: ...
5354
def validate_colorlist(s: Any) -> list[ColorType]: ...

‎lib/matplotlib/tests/test_patches.py

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_patches.py
+64Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -999,3 +999,67 @@ def test_set_and_get_hatch_linewidth(fig_test, fig_ref):
999999

10001000
assert ax_ref.patches[0].get_hatch_linewidth() == lw
10011001
assert ax_test.patches[0].get_hatch_linewidth() == lw
1002+
1003+
1004+
def test_patch_hatchcolor_inherit_logic():
1005+
with mpl.rc_context({'hatch.color': 'edge'}):
1006+
# Test for when edgecolor and hatchcolor is set
1007+
rect = Rectangle((0, 0), 1, 1, hatch='//', ec='red',
1008+
hatchcolor='yellow')
1009+
assert mcolors.same_color(rect.get_edgecolor(), 'red')
1010+
assert mcolors.same_color(rect.get_hatchcolor(), 'yellow')
1011+
1012+
# Test for explicitly setting edgecolor and then hatchcolor
1013+
rect = Rectangle((0, 0), 1, 1, hatch='//')
1014+
rect.set_edgecolor('orange')
1015+
assert mcolors.same_color(rect.get_hatchcolor(), 'orange')
1016+
rect.set_hatchcolor('cyan')
1017+
assert mcolors.same_color(rect.get_hatchcolor(), 'cyan')
1018+
1019+
# Test for explicitly setting hatchcolor and then edgecolor
1020+
rect = Rectangle((0, 0), 1, 1, hatch='//')
1021+
rect.set_hatchcolor('purple')
1022+
assert mcolors.same_color(rect.get_hatchcolor(), 'purple')
1023+
rect.set_edgecolor('green')
1024+
assert mcolors.same_color(rect.get_hatchcolor(), 'purple')
1025+
1026+
1027+
def test_patch_hatchcolor_fallback_logic():
1028+
# Test for when hatchcolor parameter is passed
1029+
rect = Rectangle((0, 0), 1, 1, hatch='//', hatchcolor='green')
1030+
assert mcolors.same_color(rect.get_hatchcolor(), 'green')
1031+
1032+
# Test that hatchcolor parameter takes precedence over rcParam
1033+
# When edgecolor is not set
1034+
with mpl.rc_context({'hatch.color': 'blue'}):
1035+
rect = Rectangle((0, 0), 1, 1, hatch='//', hatchcolor='green')
1036+
assert mcolors.same_color(rect.get_hatchcolor(), 'green')
1037+
# When edgecolor is set
1038+
with mpl.rc_context({'hatch.color': 'yellow'}):
1039+
rect = Rectangle((0, 0), 1, 1, hatch='//', hatchcolor='green', edgecolor='red')
1040+
assert mcolors.same_color(rect.get_hatchcolor(), 'green')
1041+
1042+
# Test that hatchcolor is not overridden by edgecolor when
1043+
# hatchcolor parameter is not passed and hatch.color rcParam is set to a color
1044+
# When edgecolor is not set
1045+
with mpl.rc_context({'hatch.color': 'blue'}):
1046+
rect = Rectangle((0, 0), 1, 1, hatch='//')
1047+
assert mcolors.same_color(rect.get_hatchcolor(), 'blue')
1048+
# When edgecolor is set
1049+
with mpl.rc_context({'hatch.color': 'blue'}):
1050+
rect = Rectangle((0, 0), 1, 1, hatch='//', edgecolor='red')
1051+
assert mcolors.same_color(rect.get_hatchcolor(), 'blue')
1052+
1053+
# Test that hatchcolor matches edgecolor when
1054+
# hatchcolor parameter is not passed and hatch.color rcParam is set to 'edge'
1055+
with mpl.rc_context({'hatch.color': 'edge'}):
1056+
rect = Rectangle((0, 0), 1, 1, hatch='//', edgecolor='red')
1057+
assert mcolors.same_color(rect.get_hatchcolor(), 'red')
1058+
# hatchcolor parameter is set to 'edge'
1059+
rect = Rectangle((0, 0), 1, 1, hatch='//', hatchcolor='edge', edgecolor='orange')
1060+
assert mcolors.same_color(rect.get_hatchcolor(), 'orange')
1061+
1062+
# Test for default hatchcolor when hatchcolor parameter is not passed and
1063+
# hatch.color rcParam is set to 'edge' and edgecolor is not set
1064+
rect = Rectangle((0, 0), 1, 1, hatch='//')
1065+
assert mcolors.same_color(rect.get_hatchcolor(), mpl.rcParams['patch.edgecolor'])

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.