Skip to content

Commit 53a1fe6

Browse files
committed
Make Artist.set() apply properties in the order in which they are given.
... with a deprecation period (see changelog). - This is consistent with Artist.update, and we don't reorder separate calls to set_color and set_edgecolor either (we can't really to that, indeed). - The _prop_order mechanism was a slightly complex machinery for a single use case (applying "color" first). - Writing `p.set(edgecolor="r", color="g")` seems a bit twisted anyways. Also rewrote (and simplified) normalize kwargs to make it *also* maintain kwarg order, as that's necessary to make the warning emitted in the correct cases.
1 parent 4412114 commit 53a1fe6

File tree

3 files changed

+47
-45
lines changed

3 files changed

+47
-45
lines changed

doc/api/next_api_changes/deprecations.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,12 @@ variables is deprecated. Additional fonts may be registered using
173173
``matplotlib.compat``
174174
~~~~~~~~~~~~~~~~~~~~~
175175
This module is deprecated.
176+
177+
Reordering of parameters by `.Artist.set`
178+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
179+
In a future version, ``Artist.set`` will apply artist properties in the order
180+
in which they are given. This only affects the interaction between the
181+
*color*, *edgecolor*, *facecolor*, and, for `.Collection`\s, *alpha*
182+
properties: the *color* property now needs to be passed first in order not to
183+
override the other properties. This is consistent with e.g. `.Artist.update`,
184+
which did not reorder the properties passed to it.

lib/matplotlib/artist.py

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,6 @@ class Artist:
6262
"""
6363

6464
zorder = 0
65-
# order of precedence when bulk setting/updating properties
66-
# via update. The keys should be property names and the values
67-
# integers
68-
_prop_order = dict(color=-1)
6965

7066
def __init__(self):
7167
self._stale = True
@@ -1077,10 +1073,31 @@ def properties(self):
10771073
def set(self, **kwargs):
10781074
"""A property batch setter. Pass *kwargs* to set properties."""
10791075
kwargs = cbook.normalize_kwargs(kwargs, self)
1080-
props = OrderedDict(
1081-
sorted(kwargs.items(), reverse=True,
1082-
key=lambda x: (self._prop_order.get(x[0], 0), x[0])))
1083-
return self.update(props)
1076+
move_color_to_start = False
1077+
if "color" in kwargs:
1078+
keys = [*kwargs]
1079+
i_color = keys.index("color")
1080+
props = ["edgecolor", "facecolor"]
1081+
if any(tp.__module__ == "matplotlib.collections"
1082+
and tp.__name__ == "Collection"
1083+
for tp in type(self).__mro__):
1084+
props.append("alpha")
1085+
for other in props:
1086+
if other not in keys:
1087+
continue
1088+
i_other = keys.index(other)
1089+
if i_other < i_color:
1090+
move_color_to_start = True
1091+
cbook.warn_deprecated(
1092+
"3.3", message=f"You have passed the {other!r} kwarg "
1093+
"before the 'color' kwarg. Artist.set() currently "
1094+
"reorders the properties to apply 'color' first, but "
1095+
"this is deprecated since %(since)s and will be "
1096+
"removed %(removal)s; please pass 'color' first "
1097+
"instead.")
1098+
if move_color_to_start:
1099+
kwargs = {"color": kwargs.pop("color"), **kwargs}
1100+
return self.update(kwargs)
10841101

10851102
def findobj(self, match=None, include_self=True):
10861103
"""

lib/matplotlib/cbook/__init__.py

Lines changed: 13 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1738,43 +1738,19 @@ def normalize_kwargs(kw, alias_mapping=None, required=(), forbidden=(),
17381738
or isinstance(alias_mapping, Artist)):
17391739
alias_mapping = getattr(alias_mapping, "_alias_map", {})
17401740

1741-
# make a local so we can pop
1742-
kw = dict(kw)
1743-
# output dictionary
1744-
ret = dict()
1745-
1746-
# hit all alias mappings
1747-
for canonical, alias_list in alias_mapping.items():
1748-
1749-
# the alias lists are ordered from lowest to highest priority
1750-
# so we know to use the last value in this list
1751-
tmp = []
1752-
seen = []
1753-
for a in alias_list:
1754-
try:
1755-
tmp.append(kw.pop(a))
1756-
seen.append(a)
1757-
except KeyError:
1758-
pass
1759-
# if canonical is not in the alias_list assume highest priority
1760-
if canonical not in alias_list:
1761-
try:
1762-
tmp.append(kw.pop(canonical))
1763-
seen.append(canonical)
1764-
except KeyError:
1765-
pass
1766-
# if we found anything in this set of aliases put it in the return
1767-
# dict
1768-
if tmp:
1769-
ret[canonical] = tmp[-1]
1770-
if len(tmp) > 1:
1771-
raise TypeError("Got the following keyword arguments which "
1772-
"are aliases of one another: {}"
1773-
.format(", ".join(map(repr, seen))))
1774-
1775-
# at this point we know that all keys which are aliased are removed, update
1776-
# the return dictionary from the cleaned local copy of the input
1777-
ret.update(kw)
1741+
to_canonical = {alias: canonical
1742+
for canonical, alias_list in alias_mapping.items()
1743+
for alias in alias_list}
1744+
canonical_to_seen = {}
1745+
ret = {} # output dictionary
1746+
1747+
for k, v in kw.items():
1748+
canonical = to_canonical.get(k, k)
1749+
if canonical in canonical_to_seen:
1750+
raise TypeError(f"Got both {canonical_to_seen[canonical]!r} and "
1751+
f"{k!r}, which are aliases of one another")
1752+
canonical_to_seen[canonical] = k
1753+
ret[canonical] = v
17781754

17791755
fail_keys = [k for k in required if k not in ret]
17801756
if fail_keys:

0 commit comments

Comments
 (0)