Skip to content

Commit ab4081d

Browse files
committed
add hatchcolors param for collections
1 parent 218a42b commit ab4081d

15 files changed

+240
-49
lines changed

lib/matplotlib/backend_bases.py

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ def draw_markers(self, gc, marker_path, marker_trans, path,
206206
def draw_path_collection(self, gc, master_transform, paths, all_transforms,
207207
offsets, offset_trans, facecolors, edgecolors,
208208
linewidths, linestyles, antialiaseds, urls,
209-
offset_position):
209+
offset_position, hatchcolors=None):
210210
"""
211211
Draw a collection of *paths*.
212212
@@ -215,8 +215,8 @@ def draw_path_collection(self, gc, master_transform, paths, all_transforms,
215215
*master_transform*. They are then translated by the corresponding
216216
entry in *offsets*, which has been first transformed by *offset_trans*.
217217
218-
*facecolors*, *edgecolors*, *linewidths*, *linestyles*, and
219-
*antialiased* are lists that set the corresponding properties.
218+
*facecolors*, *edgecolors*, *linewidths*, *linestyles*, *antialiased*
219+
and *hatchcolors* are lists that set the corresponding properties.
220220
221221
*offset_position* is unused now, but the argument is kept for
222222
backwards compatibility.
@@ -233,10 +233,13 @@ def draw_path_collection(self, gc, master_transform, paths, all_transforms,
233233
path_ids = self._iter_collection_raw_paths(master_transform,
234234
paths, all_transforms)
235235

236+
if hatchcolors is None:
237+
hatchcolors = []
238+
236239
for xo, yo, path_id, gc0, rgbFace in self._iter_collection(
237240
gc, list(path_ids), offsets, offset_trans,
238241
facecolors, edgecolors, linewidths, linestyles,
239-
antialiaseds, urls, offset_position):
242+
antialiaseds, urls, offset_position, hatchcolors):
240243
path, transform = path_id
241244
# Only apply another translation if we have an offset, else we
242245
# reuse the initial transform.
@@ -250,7 +253,7 @@ def draw_path_collection(self, gc, master_transform, paths, all_transforms,
250253

251254
def draw_quad_mesh(self, gc, master_transform, meshWidth, meshHeight,
252255
coordinates, offsets, offsetTrans, facecolors,
253-
antialiased, edgecolors):
256+
antialiased, edgecolors, hatchcolors=None):
254257
"""
255258
Draw a quadmesh.
256259
@@ -263,11 +266,13 @@ def draw_quad_mesh(self, gc, master_transform, meshWidth, meshHeight,
263266

264267
if edgecolors is None:
265268
edgecolors = facecolors
269+
if hatchcolors is None:
270+
hatchcolors = []
266271
linewidths = np.array([gc.get_linewidth()], float)
267272

268273
return self.draw_path_collection(
269274
gc, master_transform, paths, [], offsets, offsetTrans, facecolors,
270-
edgecolors, linewidths, [], [antialiased], [None], 'screen')
275+
edgecolors, linewidths, [], [antialiased], [None], 'screen', hatchcolors)
271276

272277
def draw_gouraud_triangles(self, gc, triangles_array, colors_array,
273278
transform):
@@ -335,7 +340,7 @@ def _iter_collection_uses_per_path(self, paths, all_transforms,
335340

336341
def _iter_collection(self, gc, path_ids, offsets, offset_trans, facecolors,
337342
edgecolors, linewidths, linestyles,
338-
antialiaseds, urls, offset_position):
343+
antialiaseds, urls, offset_position, hatchcolors=None):
339344
"""
340345
Helper method (along with `_iter_collection_raw_paths`) to implement
341346
`draw_path_collection` in a memory-efficient manner.
@@ -358,16 +363,20 @@ def _iter_collection(self, gc, path_ids, offsets, offset_trans, facecolors,
358363
*path_ids*; *gc* is a graphics context and *rgbFace* is a color to
359364
use for filling the path.
360365
"""
366+
if hatchcolors is None:
367+
hatchcolors = []
368+
361369
Npaths = len(path_ids)
362370
Noffsets = len(offsets)
363371
N = max(Npaths, Noffsets)
364372
Nfacecolors = len(facecolors)
365373
Nedgecolors = len(edgecolors)
374+
Nhatchcolors = len(hatchcolors)
366375
Nlinewidths = len(linewidths)
367376
Nlinestyles = len(linestyles)
368377
Nurls = len(urls)
369378

370-
if (Nfacecolors == 0 and Nedgecolors == 0) or Npaths == 0:
379+
if (Nfacecolors == 0 and Nedgecolors == 0 and Nhatchcolors == 0) or Npaths == 0:
371380
return
372381

373382
gc0 = self.new_gc()
@@ -382,6 +391,7 @@ def cycle_or_default(seq, default=None):
382391
toffsets = cycle_or_default(offset_trans.transform(offsets), (0, 0))
383392
fcs = cycle_or_default(facecolors)
384393
ecs = cycle_or_default(edgecolors)
394+
hcs = cycle_or_default(hatchcolors)
385395
lws = cycle_or_default(linewidths)
386396
lss = cycle_or_default(linestyles)
387397
aas = cycle_or_default(antialiaseds)
@@ -390,8 +400,8 @@ def cycle_or_default(seq, default=None):
390400
if Nedgecolors == 0:
391401
gc0.set_linewidth(0.0)
392402

393-
for pathid, (xo, yo), fc, ec, lw, ls, aa, url in itertools.islice(
394-
zip(pathids, toffsets, fcs, ecs, lws, lss, aas, urls), N):
403+
for pathid, (xo, yo), fc, ec, hc, lw, ls, aa, url in itertools.islice(
404+
zip(pathids, toffsets, fcs, ecs, hcs, lws, lss, aas, urls), N):
395405
if not (np.isfinite(xo) and np.isfinite(yo)):
396406
continue
397407
if Nedgecolors:
@@ -403,6 +413,8 @@ def cycle_or_default(seq, default=None):
403413
gc0.set_linewidth(0)
404414
else:
405415
gc0.set_foreground(ec)
416+
if Nhatchcolors:
417+
gc0.set_hatch_color(hc)
406418
if fc is not None and len(fc) == 4 and fc[3] == 0:
407419
fc = None
408420
gc0.set_antialiased(aa)
@@ -690,7 +702,7 @@ def __init__(self):
690702
self._linewidth = 1
691703
self._rgb = (0.0, 0.0, 0.0, 1.0)
692704
self._hatch = None
693-
self._hatch_color = colors.to_rgba(rcParams['hatch.color'])
705+
self._hatch_color = None
694706
self._hatch_linewidth = rcParams['hatch.linewidth']
695707
self._url = None
696708
self._gid = None

lib/matplotlib/backend_bases.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ class RendererBase:
6363
antialiaseds: bool | Sequence[bool],
6464
urls: str | Sequence[str],
6565
offset_position: Any,
66+
hatchcolors: ColorType | Sequence[ColorType] | None = None,
6667
) -> None: ...
6768
def draw_quad_mesh(
6869
self,
@@ -76,6 +77,7 @@ class RendererBase:
7677
facecolors: Sequence[ColorType],
7778
antialiased: bool,
7879
edgecolors: Sequence[ColorType] | ColorType | None,
80+
hatchcolors: Sequence[ColorType] | ColorType | None = None,
7981
) -> None: ...
8082
def draw_gouraud_triangles(
8183
self,

lib/matplotlib/backends/backend_pdf.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2027,14 +2027,17 @@ def draw_path(self, gc, path, transform, rgbFace=None):
20272027
def draw_path_collection(self, gc, master_transform, paths, all_transforms,
20282028
offsets, offset_trans, facecolors, edgecolors,
20292029
linewidths, linestyles, antialiaseds, urls,
2030-
offset_position):
2030+
offset_position, hatchcolors=None):
20312031
# We can only reuse the objects if the presence of fill and
20322032
# stroke (and the amount of alpha for each) is the same for
20332033
# all of them
20342034
can_do_optimization = True
20352035
facecolors = np.asarray(facecolors)
20362036
edgecolors = np.asarray(edgecolors)
20372037

2038+
if hatchcolors is None:
2039+
hatchcolors = []
2040+
20382041
if not len(facecolors):
20392042
filled = False
20402043
can_do_optimization = not gc.get_hatch()
@@ -2069,7 +2072,7 @@ def draw_path_collection(self, gc, master_transform, paths, all_transforms,
20692072
self, gc, master_transform, paths, all_transforms,
20702073
offsets, offset_trans, facecolors, edgecolors,
20712074
linewidths, linestyles, antialiaseds, urls,
2072-
offset_position)
2075+
offset_position, hatchcolors)
20732076

20742077
padding = np.max(linewidths)
20752078
path_codes = []
@@ -2085,7 +2088,7 @@ def draw_path_collection(self, gc, master_transform, paths, all_transforms,
20852088
for xo, yo, path_id, gc0, rgbFace in self._iter_collection(
20862089
gc, path_codes, offsets, offset_trans,
20872090
facecolors, edgecolors, linewidths, linestyles,
2088-
antialiaseds, urls, offset_position):
2091+
antialiaseds, urls, offset_position, hatchcolors):
20892092

20902093
self.check_gc(gc0, rgbFace)
20912094
dx, dy = xo - lastx, yo - lasty

lib/matplotlib/backends/backend_ps.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -675,7 +675,9 @@ def draw_markers(
675675
def draw_path_collection(self, gc, master_transform, paths, all_transforms,
676676
offsets, offset_trans, facecolors, edgecolors,
677677
linewidths, linestyles, antialiaseds, urls,
678-
offset_position):
678+
offset_position, hatchcolors=None):
679+
if hatchcolors is None:
680+
hatchcolors = []
679681
# Is the optimization worth it? Rough calculation:
680682
# cost of emitting a path in-line is
681683
# (len_path + 2) * uses_per_path
@@ -691,7 +693,7 @@ def draw_path_collection(self, gc, master_transform, paths, all_transforms,
691693
self, gc, master_transform, paths, all_transforms,
692694
offsets, offset_trans, facecolors, edgecolors,
693695
linewidths, linestyles, antialiaseds, urls,
694-
offset_position)
696+
offset_position, hatchcolors)
695697

696698
path_codes = []
697699
for i, (path, transform) in enumerate(self._iter_collection_raw_paths(
@@ -710,7 +712,7 @@ def draw_path_collection(self, gc, master_transform, paths, all_transforms,
710712
for xo, yo, path_id, gc0, rgbFace in self._iter_collection(
711713
gc, path_codes, offsets, offset_trans,
712714
facecolors, edgecolors, linewidths, linestyles,
713-
antialiaseds, urls, offset_position):
715+
antialiaseds, urls, offset_position, hatchcolors):
714716
ps = f"{xo:g} {yo:g} {path_id}"
715717
self._draw_ps(ps, gc0, rgbFace)
716718

lib/matplotlib/backends/backend_svg.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -735,7 +735,9 @@ def draw_markers(
735735
def draw_path_collection(self, gc, master_transform, paths, all_transforms,
736736
offsets, offset_trans, facecolors, edgecolors,
737737
linewidths, linestyles, antialiaseds, urls,
738-
offset_position):
738+
offset_position, hatchcolors=None):
739+
if hatchcolors is None:
740+
hatchcolors = []
739741
# Is the optimization worth it? Rough calculation:
740742
# cost of emitting a path in-line is
741743
# (len_path + 5) * uses_per_path
@@ -751,7 +753,7 @@ def draw_path_collection(self, gc, master_transform, paths, all_transforms,
751753
gc, master_transform, paths, all_transforms,
752754
offsets, offset_trans, facecolors, edgecolors,
753755
linewidths, linestyles, antialiaseds, urls,
754-
offset_position)
756+
offset_position, hatchcolors)
755757

756758
writer = self.writer
757759
path_codes = []
@@ -769,7 +771,7 @@ def draw_path_collection(self, gc, master_transform, paths, all_transforms,
769771
for xo, yo, path_id, gc0, rgbFace in self._iter_collection(
770772
gc, path_codes, offsets, offset_trans,
771773
facecolors, edgecolors, linewidths, linestyles,
772-
antialiaseds, urls, offset_position):
774+
antialiaseds, urls, offset_position, hatchcolors):
773775
url = gc0.get_url()
774776
if url is not None:
775777
writer.start('a', attrib={'xlink:href': url})

lib/matplotlib/collections.py

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ class Collection(mcolorizer.ColorizingArtist):
7979
def __init__(self, *,
8080
edgecolors=None,
8181
facecolors=None,
82+
hatchcolors=None,
8283
linewidths=None,
8384
linestyles='solid',
8485
capstyle=None,
@@ -169,12 +170,13 @@ def __init__(self, *,
169170
self._linewidths = [0]
170171

171172
self._gapcolor = None # Currently only used by LineCollection.
173+
self._original_edgecolor = None
174+
self._original_hatchcolor = None
172175

173176
# Flags set by _set_mappable_flags: are colors from mapping an array?
174177
self._face_is_mapped = None
175178
self._edge_is_mapped = None
176179
self._mapped_colors = None # calculated in update_scalarmappable
177-
self._hatch_color = mcolors.to_rgba(mpl.rcParams['hatch.color'])
178180
self.set_facecolor(facecolors)
179181
self.set_edgecolor(edgecolors)
180182
self.set_linewidth(linewidths)
@@ -183,6 +185,7 @@ def __init__(self, *,
183185
self.set_pickradius(pickradius)
184186
self.set_urls(urls)
185187
self.set_hatch(hatch)
188+
self.set_hatchcolor(hatchcolors)
186189
self.set_zorder(zorder)
187190

188191
if capstyle:
@@ -364,7 +367,6 @@ def draw(self, renderer):
364367

365368
if self._hatch:
366369
gc.set_hatch(self._hatch)
367-
gc.set_hatch_color(self._hatch_color)
368370

369371
if self.get_sketch_params() is not None:
370372
gc.set_sketch_params(*self.get_sketch_params())
@@ -423,15 +425,16 @@ def draw(self, renderer):
423425
[mcolors.to_rgba("none")], self._gapcolor,
424426
self._linewidths, ilinestyles,
425427
self._antialiaseds, self._urls,
426-
"screen")
428+
"screen", self.get_hatchcolor())
427429

428430
renderer.draw_path_collection(
429431
gc, transform.frozen(), paths,
430432
self.get_transforms(), offsets, offset_trf,
431433
self.get_facecolor(), self.get_edgecolor(),
432434
self._linewidths, self._linestyles,
433435
self._antialiaseds, self._urls,
434-
"screen") # offset_position, kept for backcompat.
436+
"screen", # offset_position, kept for backcompat.
437+
self.get_hatchcolor())
435438

436439
gc.restore()
437440
renderer.close_group(self.__class__.__name__)
@@ -798,23 +801,31 @@ def _get_default_edgecolor(self):
798801
# This may be overridden in a subclass.
799802
return mpl.rcParams['patch.edgecolor']
800803

804+
def get_hatchcolor(self):
805+
return self._hatchcolors
806+
801807
def _set_edgecolor(self, c):
802-
set_hatch_color = True
803808
if c is None:
804809
if (mpl.rcParams['patch.force_edgecolor']
805810
or self._edge_default
806811
or cbook._str_equal(self._original_facecolor, 'none')):
807812
c = self._get_default_edgecolor()
808813
else:
809814
c = 'none'
810-
set_hatch_color = False
811815
if cbook._str_lower_equal(c, 'face'):
812816
self._edgecolors = 'face'
817+
if self._original_hatchcolor is None:
818+
self._hatchcolors = mpl.colors.to_rgba_array(
819+
mpl.rcParams['patch.edgecolor'], self._alpha)
813820
self.stale = True
814821
return
815822
self._edgecolors = mcolors.to_rgba_array(c, self._alpha)
816-
if set_hatch_color and len(self._edgecolors):
817-
self._hatch_color = tuple(self._edgecolors[0])
823+
if self._original_hatchcolor is None:
824+
if isinstance(c, str) and c == 'none':
825+
self._hatchcolors = mpl.colors.to_rgba_array(
826+
mpl.rcParams['patch.edgecolor'], self._alpha)
827+
else:
828+
self._hatchcolors = self._edgecolors
818829
self.stale = True
819830

820831
def set_edgecolor(self, c):
@@ -835,6 +846,33 @@ def set_edgecolor(self, c):
835846
self._original_edgecolor = c
836847
self._set_edgecolor(c)
837848

849+
def _set_hatchcolor(self, c):
850+
c = mpl._val_or_rc(c, 'hatch.color')
851+
if c == 'inherit':
852+
c = self._original_edgecolor
853+
if c is None or (isinstance(c, str) and c in ('none', 'face')):
854+
c = mpl.rcParams['patch.edgecolor']
855+
else:
856+
self._original_hatchcolor = c
857+
858+
self._hatchcolors = mcolors.to_rgba_array(c, self._alpha)
859+
self.stale = True
860+
861+
def set_hatchcolor(self, c):
862+
"""
863+
Set the hatchcolor(s) of the collection.
864+
865+
Parameters
866+
----------
867+
c : :mpltype:`color` or list of :mpltype:`color` or 'inherit'
868+
The collection hatchcolor(s). If a sequence, the patches cycle
869+
through it.
870+
"""
871+
if isinstance(c, str) and c.lower() == 'inherit':
872+
c = c.lower()
873+
self._original_hatchcolor = c
874+
self._set_hatchcolor(c)
875+
838876
def set_alpha(self, alpha):
839877
"""
840878
Set the transparency of the collection.
@@ -952,6 +990,7 @@ def update_from(self, other):
952990
self._us_linestyles = other._us_linestyles
953991
self._pickradius = other._pickradius
954992
self._hatch = other._hatch
993+
self._hatchcolors = other._hatchcolors
955994

956995
# update_from for scalarmappable
957996
self._A = other._A
@@ -2450,7 +2489,8 @@ def draw(self, renderer):
24502489
coordinates, offsets, offset_trf,
24512490
# Backends expect flattened rgba arrays (n*m, 4) for fc and ec
24522491
self.get_facecolor().reshape((-1, 4)),
2453-
self._antialiased, self.get_edgecolors().reshape((-1, 4)))
2492+
self._antialiased, self.get_edgecolors().reshape((-1, 4)),
2493+
self.get_hatchcolor().reshape((-1, 4)))
24542494
gc.restore()
24552495
renderer.close_group(self.__class__.__name__)
24562496
self.stale = False

0 commit comments

Comments
 (0)