Skip to content

Commit ec65514

Browse files
committed
MNT: Separate property cycle handling from _process_plot_var_args
By encapsulating the cycling into a dedicated class, we prepare for making cycling sharable between multiple _process_plot_var_args instances, which is the prerequisite for unifying the separate cyclers for lines and patches (matplotlib#29468) and sharing cyclers between multiple Axes (matplotlib#19479).
1 parent 5c3f2f6 commit ec65514

File tree

1 file changed

+43
-27
lines changed

1 file changed

+43
-27
lines changed

lib/matplotlib/axes/_base.py

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,42 @@ def _process_plot_format(fmt, *, ambiguous_fmt_datakey=False):
202202
return linestyle, marker, color
203203

204204

205+
class _PropCycle:
206+
"""
207+
A class that holds the property cycle information.
208+
209+
It expands the iterator-based Cycler into an explicit indexed list to support
210+
conditional advancing.
211+
"""
212+
def __init__(self, cycler):
213+
self._idx = 0
214+
self._cycler_items = [*cycler]
215+
216+
def get_next_color(self):
217+
"""Return the next color in the cycle."""
218+
entry = self._cycler_items[self._idx]
219+
if "color" in entry:
220+
self._idx = (self._idx + 1) % len(self._cycler_items) # Advance cycler.
221+
return entry["color"]
222+
else:
223+
return "k"
224+
225+
def getdefaults(self, kw, ignore=frozenset()):
226+
"""
227+
If some keys in the property cycle (excluding those in the set
228+
*ignore*) are absent or set to None in the dict *kw*, return a copy
229+
of the next entry in the property cycle, excluding keys in *ignore*.
230+
Otherwise, don't advance the property cycle, and return an empty dict.
231+
"""
232+
defaults = self._cycler_items[self._idx]
233+
if any(kw.get(k, None) is None for k in {*defaults} - ignore):
234+
self._idx = (self._idx + 1) % len(self._cycler_items) # Advance cycler.
235+
# Return a new dict to avoid exposing _cycler_items entries to mutation.
236+
return {k: v for k, v in defaults.items() if k not in ignore}
237+
else:
238+
return {}
239+
240+
205241
class _process_plot_var_args:
206242
"""
207243
Process variable length arguments to `~.Axes.plot`, to support ::
@@ -220,8 +256,7 @@ def __init__(self, output='Line2D'):
220256
self.set_prop_cycle(None)
221257

222258
def set_prop_cycle(self, cycler):
223-
self._idx = 0
224-
self._cycler_items = [*mpl._val_or_rc(cycler, 'axes.prop_cycle')]
259+
self._prop_cycle = _PropCycle(mpl._val_or_rc(cycler, 'axes.prop_cycle'))
225260

226261
def __call__(self, axes, *args, data=None, return_kwargs=False, **kwargs):
227262
axes._process_unit_info(kwargs=kwargs)
@@ -300,29 +335,10 @@ def __call__(self, axes, *args, data=None, return_kwargs=False, **kwargs):
300335

301336
def get_next_color(self):
302337
"""Return the next color in the cycle."""
303-
entry = self._cycler_items[self._idx]
304-
if "color" in entry:
305-
self._idx = (self._idx + 1) % len(self._cycler_items) # Advance cycler.
306-
return entry["color"]
307-
else:
308-
return "k"
309-
310-
def _getdefaults(self, kw, ignore=frozenset()):
311-
"""
312-
If some keys in the property cycle (excluding those in the set
313-
*ignore*) are absent or set to None in the dict *kw*, return a copy
314-
of the next entry in the property cycle, excluding keys in *ignore*.
315-
Otherwise, don't advance the property cycle, and return an empty dict.
316-
"""
317-
defaults = self._cycler_items[self._idx]
318-
if any(kw.get(k, None) is None for k in {*defaults} - ignore):
319-
self._idx = (self._idx + 1) % len(self._cycler_items) # Advance cycler.
320-
# Return a new dict to avoid exposing _cycler_items entries to mutation.
321-
return {k: v for k, v in defaults.items() if k not in ignore}
322-
else:
323-
return {}
338+
return self._prop_cycle.get_next_color()
324339

325-
def _setdefaults(self, defaults, kw):
340+
@staticmethod
341+
def _setdefaults(defaults, kw):
326342
"""
327343
Add to the dict *kw* the entries in the dict *default* that are absent
328344
or set to None in *kw*.
@@ -333,13 +349,13 @@ def _setdefaults(self, defaults, kw):
333349

334350
def _make_line(self, axes, x, y, kw, kwargs):
335351
kw = {**kw, **kwargs} # Don't modify the original kw.
336-
self._setdefaults(self._getdefaults(kw), kw)
352+
self._setdefaults(self._prop_cycle.getdefaults(kw), kw)
337353
seg = mlines.Line2D(x, y, **kw)
338354
return seg, kw
339355

340356
def _make_coordinates(self, axes, x, y, kw, kwargs):
341357
kw = {**kw, **kwargs} # Don't modify the original kw.
342-
self._setdefaults(self._getdefaults(kw), kw)
358+
self._setdefaults(self._prop_cycle.getdefaults(kw), kw)
343359
return (x, y), kw
344360

345361
def _make_polygon(self, axes, x, y, kw, kwargs):
@@ -367,7 +383,7 @@ def _make_polygon(self, axes, x, y, kw, kwargs):
367383
# for getting defaults for back-compat reasons.
368384
# Doing it with both seems to mess things up in
369385
# various places (probably due to logic bugs elsewhere).
370-
default_dict = self._getdefaults(kw, ignores)
386+
default_dict = self._prop_cycle.getdefaults(kw, ignores)
371387
self._setdefaults(default_dict, kw)
372388

373389
# Looks like we don't want "color" to be interpreted to

0 commit comments

Comments
 (0)