8000 Clarify color priorities in collections · matplotlib/matplotlib@b639e0f · GitHub
[go: up one dir, main page]

Skip to content

Commit b639e0f

Browse files
committed
Clarify color priorities in collections
1 parent e768849 commit b639e0f

File tree

7 files changed

+251
-99
lines changed

7 files changed

+251
-99
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
Collection color specification and mapping
2+
------------------------------------------
3+
4+
Reworking the handling of color mapping and the keyword arguments for facecolor
5+
and edgecolor has resulted in three behavior changes:
6+
7+
1. Color mapping can be turned off by calling ``Collection.set_array(None)``.
8+
Previously, this would have no effect.
9+
2. When a mappable array is set, with ``facecolor='none'`` and
10+
``edgecolor='face'``, both the faces and the edges are left uncolored.
11+
Previously the edges would be color-mapped.
12+
3. When a mappable array is set, with ``facecolor='none'`` and
13+
``edgecolor='red'``, the edges are red. This addresses Issue #1302.
14+
Previously the edges would be color-mapped.

lib/matplotlib/axes/_axes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6154,7 +6154,7 @@ def pcolormesh(self, *args, alpha=None, norm=None, cmap=None, vmin=None,
61546154
if shading is None:
61556155
shading = rcParams['pcolor.shading']
61566156
shading = shading.lower()
6157-
kwargs.setdefault('edgecolors', 'None')
6157+
kwargs.setdefault('edgecolors', 'none')
61586158

61596159
X, Y, C, shading = self._pcolorargs('pcolormesh', *args,
61606160
shading=shading, kwargs=kwargs)

lib/matplotlib/cm.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ def set_array(self, A):
366366
367367
Parameters
368368
----------
369-
A : ndarray
369+
A : ndarray or None
370370
"""
371371
self._A = A
372372
self._update_dict['array'] = True

lib/matplotlib/collections.py

Lines changed: 134 additions & 90 deletions
Original file line numberDiff line numberDiff line change
2020
import warnings
2121

2222

23+
# "color" is excluded; it is a compound setter, and its docstring differs
24+
# in LineCollection.
2325
@cbook._define_aliases({
2426
"antialiased": ["antialiaseds", "aa"],
2527
"edgecolor": ["edgecolors", "ec"],
@@ -168,8 +170,10 @@ def __init__(self,
168170
# list of unbroadcast/scaled linewidths
169171
self._us_lw = [0]
170172
self._linewidths = [0]
171-
self._is_filled = True # May be modified by set_facecolor().
172-
173+
# Flags: do colors come from mapping an array?
174+
self._face_is_mapped = None
175+
self._edge_is_mapped = None
176+
self._mapped_colors = None # Calculated in update_scalarmappable
173177
self._hatch_color = mcolors.to_rgba(mpl.rcParams['hatch.color'])
174178
self.set_facecolor(facecolors)
175179
self.set_edgecolor(edgecolors)
@@ -586,6 +590,10 @@ def get_offset_position(self):
586590
"""
587591
return self._offset_position
588592

593+
def _get_default_linewidth(self):
594+
# This may be overridden in a subclass.
595+
return mpl.rcParams['patch.linewidth'] # validated as float
596+
589597
def set_linewidth(self, lw):
590598
"""
591599
Set the linewidth(s) for the collection. *lw* can be a scalar
@@ -597,9 +605,7 @@ def set_linewidth(self, lw):
597605
lw : float or list of floats
598606
"""
599607
if lw is None:
600-
lw = mpl.rcParams['patch.linewidth']
601-
if lw is None:
602-
lw = mpl.rcParams['lines.linewidth']
608+
lw = self._get_default_linewidth()
603609
# get the un-scaled/broadcast lw
604610
self._us_lw = np.atleast_1d(np.asarray(lw))
605611

@@ -750,16 +756,14 @@ def set_color(self, c):
750756
self.set_facecolor(c)
751757
self.set_edgecolor(c)
752758

759+
def _get_default_facecolor(self):
760+
# This may be overridden in a subclass.
761+
return mpl.rcParams['patch.facecolor']
762+
753763
def _set_facecolor(self, c):
754764
if c is None:
755-
c = mpl.rcParams['patch.facecolor']
765+
c = self._get_default_facecolor()
756766

757-
self._is_filled = True
758-
try:
759-
if c.lower() == 'none':
760-
self._is_filled = False
761-
except AttributeError:
762-
pass
763767
self._facecolors = mcolors.to_rgba_array(c, self._alpha)
764768
self.stale = True
765769

@@ -775,6 +779,8 @@ def set_facecolor(self, c):
775779
----------
776780
c : color or list of colors
777781
"""
782+
if isinstance(c, str) and c.lower() in ("none", "face"):
783+
c = c.lower()
778784
self._original_facecolor = c
779785
self._set_facecolor(c)
780786

@@ -787,29 +793,24 @@ def get_edgecolor(self):
787793
else:
788794
return self._edgecolors
789795

796+
def _get_default_edgecolor(self):
797+
# This may be overridden in a subclass.
798+
return mpl.rcParams['patch.edgecolor']
799+
790800
def _set_edgecolor(self, c):
791801
set_hatch_color = True
792802
if c is None:
793-
if (mpl.rcParams['patch.force_edgecolor'] or
794-
not self._is_filled or self._edge_default):
795-
c = mpl.rcParams['patch.edgecolor']
803+
if (mpl.rcParams['patch.force_edgecolor']
804+
or self._edge_default
805+
or cbook._str_equal(self._original_facecolor, 'none')):
806+
c = self._get_default_edgecolor()
796807
else:
797808
c = 'none'
798809
set_hatch_color = False
799-
800-
self._is_stroked = True
801-
try:
802-
if c.lower() == 'none':
803-
self._is_stroked = False
804-
except AttributeError:
805-
pass
806-
807-
try:
808-
if c.lower() == 'face': # Special case: lookup in "get" method.
809-
self._edgecolors = 'face'
810-
return
811-
except AttributeError:
812-
pass
810+
if cbook._str_lower_equal(c, 'face'):
811+
self._edgecolors = 'face'
812+
self.stale = True
813+
return
813814
self._edgecolors = mcolors.to_rgba_array(c, self._alpha)
814815
if set_hatch_color and len(self._edgecolors):
815816
self._hatch_color = tuple(self._edgecolors[0])
@@ -825,6 +826,11 @@ def set_edgecolor(self, c):
825826
The collection edgecolor(s). If a sequence, the patches cycle
826827
through it. If 'face', match the facecolor.
827828
"""
829+
# We pass through a default value for use in LineCollection.
830+
# This allows us to maintain None as the default indicator in
831+
# _original_edgecolor.
832+
if isinstance(c, str) and c.lower() in ("none", "face"):
833+
c = c.lower()
828834
self._original_edgecolor = c
829835
self._set_edgecolor(c)
830836

@@ -853,36 +859,78 @@ def get_linewidth(self):
853859
def get_linestyle(self):
854860
return self._linestyles
855861

862+
def _set_mappable_flags(self):
863+
"""
864+
Determine whether edges and/or faces are color-mapped.
865+
866+
This is a helper for update_scalarmappable.
867+
It sets Boolean flags '_edge_is_mapped' and '_face_is_mapped'.
868+
869+
Returns
870+
-------
871+
mapping_change: bool
872+
True if either flag is True, or if a flag has changed.
873+
"""
874+
edge0 = self._edge_is_mapped
875+
face0 = self._face_is_mapped
876+
self._edge_is_mapped = False
877+
self._face_is_mapped = False
878+
if self._A is not None:
879+
if not cbook._str_equal(self._original_facecolor, 'none'):
880+
self._face_is_mapped = True
881+
if cbook._str_equal(self._original_edgecolor, 'face'):
882+
self._edge_is_mapped = True
883+
else:
884+
if self._original_edgecolor is None:
885+
self._edge_is_mapped = True
886+
887+
mapped = self._face_is_mapped or self._edge_is_mapped
888+
changed = (edge0 is None or face0 is None
889+
or self._edge_is_mapped != edge0
890+
or self._face_is_mapped != face0)
891+
return mapped or changed
892+
856893
def update_scalarmappable(self):
857-
"""Update colors from the scalar mappable array, if it is not None."""
858-
if self._A is None:
859-
return
860-
# QuadMesh can map 2d arrays (but pcolormesh supplies 1d array)
861-
if self._A.ndim > 1 and not isinstance(self, QuadMesh):
862-
raise ValueError('Collections can only map rank 1 arrays')
863-
if not self._check_update("array"):
894+
"""
895+
Update colors from the scalar mappable array, if any.
896+
897+
Assign colors to edges and faces based on the array and/or
898+
colors that were directly set, as appropriate.
899+
"""
900+
if not self._set_mappable_flags():
864901
return
865-
if np.iterable(self._alpha):
866-
if self._alpha.size != self._A.size:
867-
raise ValueError(f'Data array shape, {self._A.shape} '
868-
'is incompatible with alpha array shape, '
869-
f'{self._alpha.shape}. '
870-
'This can occur with the deprecated '
871-
'behavior of the "flat" shading option, '
872-
'in which a row and/or column of the data '
873-
'array is dropped.')
874-
# pcolormesh, scatter, maybe others flatten their _A
875-
self._alpha = self._alpha.reshape(self._A.shape)
876-
877-
if self._is_filled:
878-
self._facecolors = self.to_rgba(self._A, self._alpha)
879-
elif self._is_stroked:
880-
self._edgecolors = self.to_rgba(self._A, self._alpha)
902+
# Allow possibility to call 'self.set_array(None)'.
903+
if self._check_update("array") and self._A is not None:
904+
# QuadMesh can map 2d arrays (but pcolormesh supplies 1d array)
905+
if self._A.ndim > 1 and not isinstance(self, QuadMesh):
906+
raise ValueError('Collections can only map rank 1 arrays')
907+
if np.iterable(self._alpha):
908+
if self._alpha.size != self._A.size:
909+
raise ValueError(
910+
f'Data array shape, {self._A.shape} '
911+
'is incompatible with alpha array shape, '
912+
f'{self._alpha.shape}. '
913+
'This can occur with the deprecated '
914+
'behavior of the "flat" shading option, '
915+
'in which a row and/or column of the data '
916+
'array is dropped.')
917+
# pcolormesh, scatter, maybe others flatten their _A
918+
self._alpha = self._alpha.reshape(self._A.shape)
919+
self._mapped_colors = self.to_rgba(self._A, self._alpha)
920+
921+
if self._face_is_mapped:
922+
self._facecolors = self._mapped_colors
923+
else:
924+
self._set_facecolor(self._original_facecolor)
925+
if self._edge_is_mapped:
926+
self._edgecolors = self._mapped_colors
927+
else:
928+
self._set_edgecolor(self._original_edgecolor)
881929
self.stale = True
882930

883931
def get_fill(self):
884-
"""Return whether fill is set."""
885-
return self._is_filled
932+
"""Return whether face is colored."""
933+
return not cbook._str_lower_equal(self._original_facecolor, "none")
886934

887935
def update_from(self, other):
888936
"""Copy properties from other to self."""
@@ -1350,18 +1398,9 @@ class LineCollection(Collection):
13501398

13511399
_edge_default = True
13521400

1353-
def __init__(self, segments, # Can be None.
1354-
linewidths=None,
1355-
colors=None,
1356-
antialiaseds=None,
1357-
linestyles='solid',
1358-
offsets=None,
1359-
transOffset=None,
1360-
norm=None,
1361-
cmap=None,
1362-
pickradius=5,
1363-
zorder=2,
1364-
facecolors='none',
1401+
def __init__(self, segments, # Can be None.
1402+
*args, # Deprecated.
1403+
zorder=2, # Collection.zorder is 1
13651404
**kwargs
13661405
):
13671406
"""
@@ -1394,29 +1433,25 @@ def __init__(self, segments, # Can be None.
13941433
`~.path.Path.CLOSEPOLY`.
13951434
13961435
**kwargs
1397-
Forwareded to `.Collection`.
1436+
Forwarded to `.Collection`.
13981437
"""
1399-
if colors is None:
1400-
colors = mpl.rcParams['lines.color']
1401-
if linewidths is None:
1402-
linewidths = (mpl.rcParams['lines.linewidth'],)
1403-
if antialiaseds is None:
1404-
antialiaseds = (mpl.rcParams['lines.antialiased'],)
1405-
1406-
colors = mcolors.to_rgba_array(colors)
1438+
argnames = ["linewidths", "colors", "antialiaseds", "linestyles",
1439+
"offsets", "transOffset", "norm", "cmap", "pickradius",
1440+
"zorder", "facecolors"]
1441+
if args:
1442+
argkw = {name: val for name, val in zip(argnames, args)}
1443+
kwargs.update(argkw)
1444+
cbook.warn_deprecated(
1445+
"3.4", message="Since %(since)s, passing LineCollection "
1446+
"arguments other than the first, 'segments', as positional "
1447+
"arguments is deprecated, and they will become keyword-only "
1448+
"arguments %(removal)s."
1449+
)
1450+
# Unfortunately, mplot3d needs this explicit setting of 'facecolors'.
1451+
kwargs.setdefault('facecolors', 'none')
14071452
super().__init__(
1408-
edgecolors=colors,
1409-
facecolors=facecolors,
1410-
linewidths=linewidths,
1411-
linestyles=linestyles,
1412-
antialiaseds=antialiaseds,
1413-
offsets=offsets,
1414-
transOffset=transOffset,
1415-
norm=norm,
1416-
cmap=cmap,
14171453
zorder=zorder,
14181454
**kwargs)
1419-
14201455
self.set_segments(segments)
14211456

14221457
def set_segments(self, segments):
@@ -1468,19 +1503,29 @@ def _add_offsets(self, segs):
14681503
segs[i] = segs[i] + offsets[io:io + 1]
14691504
return segs
14701505

1506+
def _get_default_linewidth(self):
1507+
return mpl.rcParams['lines.linewidth']
1508+
1509+
def _get_default_edgecolor(self):
1510+
return mpl.rcParams['lines.color']
1511+
1512+
def _get_default_facecolor(self):
1513+
return 'none'
1514+
14711515
def set_color(self, c):
14721516
"""
1473-
Set the color(s) of the LineCollection.
1517+
Set the edgecolor(s) of the LineCollection.
14741518
14751519
Parameters
14761520
----------
14771521
c : color or list of colors
1478-
Single color (all patches have same color), or a
1479-
sequence of rgba tuples; if it is a sequence the patches will
1522+
Single color (all lines have same color), or a
1523+
sequence of rgba tuples; if it is a sequence the lines will
14801524
cycle through the sequence.
14811525
"""
14821526
self.set_edgecolor(c)
1483-
self.stale = True
1527+
1528+
set_colors = set_color
14841529

14851530
def get_color(self):
14861531
return self._edgecolors
@@ -1855,7 +1900,6 @@ def __init__(self, triangulation, **kwargs):
18551900
super().__init__(**kwargs)
18561901
self._triangulation = triangulation
18571902
self._shading = 'gouraud'
1858-
self._is_filled = True
18591903

18601904
self._bbox = transforms.Bbox.unit()
18611905

lib/matplotlib/patches.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4209,9 +4209,6 @@ def get_path_in_displaycoord(self):
42094209
self.get_linewidth() * dpi_cor,
42104210
self.get_mutation_aspect())
42114211

4212-
# if not fillable:
4213-
# self._fill = False
4214-
42154212
return _path, fillable
42164213

42174214
def draw(self, renderer):

0 commit comments

Comments
 (0)
0