8000 Shareable property cyclers. · matplotlib/matplotlib@9eec1e5 · GitHub
[go: up one dir, main page]

Skip to content
8000

Commit 9eec1e5

Browse files
committed
Shareable property cyclers.
Allow a single property cycler to be reused across multiple axes, with plots on any axes advancing the same joint iterator. e.g. ```python from matplotlib import pyplot as plt from matplotlib.axes._base import to_shareable_cycler shared = to_shareable_cycler(plt.rcParams["axes.prop_cycle"]) fig, axs = plt.subplots(2, sharex=True) axs[0].set_prop_cycle(shared) axs[1].set_prop_cycle(shared) axs[0].plot([0, 1]) axs[1].plot([0, 1]) # Check that fill uses a separate iterator. axs[1].fill([2, 3, 4], [0, 1, 0]) axs[0].fill([2, 3, 4], [0, 1, 0]) # Check that the second axes goes back to the first color, even though it # didn't use it on the first cycle. for i in range(10): axs[1].plot([5+i, 5+i], [0, 1]) plt.show() ```
1 parent 21663b5 commit 9eec1e5

File tree

2 files changed

+62
-1
lines changed

2 files changed

+62
-1
lines changed

lib/matplotlib/axes/_base.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,38 @@ def _process_plot_format(fmt, *, ambiguous_fmt_datakey=False):
208208
return linestyle, marker, color
209209

210210

211+
def to_shareable_cycler(cycler):
212+
"""
213+
Return a copy of *cycler*, that can be shared across multiple Axes.
214+
215+
Plotting on any Axes will advance the joint property iterators of all Axes.
216+
"""
217+
from cycler import Cycler
218+
new = Cycler(cycler)
219+
# This doesn't carry forward any previous shares, but that can easily be
220+
# changed by checking first whether the dict exists.
221+
new._cycling_iterators_cache = {}
222+
return new
223+
224+
225+
def _get_cycling_iterator(cycler, name):
226+
"""
227+
Return an `itertools.cycle()` over this *cycler*, intended for plotting
228+
command *name*.
229+
230+
If this cycler is shareable (per `to_shareable_cycler`), then multiple
231+
invocations with the same *cycler* and *name* will return the same
232+
`itertools.cycle` instance (*name* only serves as a key for this purpose).
233+
If this cycler is not shareable, then a new `itertools.cycle` instance is
234+
returned every time.
235+
"""
236+
if not hasattr(cycler, "_cycling_iterators_cache"):
237+
return itertools.cycle(cycler)
238+
if name not in cycler._cycling_iterators_cache:
239+
cycler._cycling_iterators_cache[name] = itertools.cycle(cycler)
240+
return cycler._cycling_iterators_cache[name]
241+
242+
211243
class _process_plot_var_args:
212244
"""
213245
Process variable length arguments to `~.Axes.plot`, to support ::
@@ -235,7 +267,7 @@ def __setstate__(self, state):
235267
def set_prop_cycle(self, cycler):
236268
if cycler is None:
237269
cycler = mpl.rcParams['axes.prop_cycle']
238-
self.prop_cycler = itertools.cycle(cycler)
270+
self.prop_cycler = _get_cycling_iterator(cycler, self.command)
239271
self._prop_keys = cycler.keys # This should make a copy
240272

241273
def __call__(self, *args, data=None, **kwargs):

lib/matplotlib/tests/test_cycles.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,32 @@ def test_invalid_input_forms():
158158
ax.set_prop_cycle(cycler(foobar=[1, 2]))
159159
with pytest.raises(ValueError):
160160
ax.set_prop_cycle(cycler(color='rgb', c='cmy'))
161+
162+
163+
@mpl.style.context("default")
164+
def test_cycler_sharing():
165+
from matplotlib.axes._base import to_shareable_cycler
166+
167+
shared = to_shareable_cycler(plt.rcParams["axes.prop_cycle"])
168+
fig, axs = plt.subplots(2)
169+
axs[0].set_prop_cycle(shared)
170+
axs[1].set_prop_cycle(shared)
171+
172+
axs[0].plot([0, 1])
173+
axs[1].plot([0, 1])
174+
# Check that fill uses a separate iterator.
175+
axs[1].fill([2, 3, 4], [0, 1, 0])
176+
axs[0].fill([2, 3, 4], [0, 1, 0])
177+
# Check that the second axes goes back to the first color, even though it
178+
# didn't use it on the first cycle.
179+
for i in range(10):
180+
axs[1].plot([5+i, 5+i], [0, 1])
181+
182+
assert mpl.colors.same_color([l.get_color() for l in axs[0].lines],
183+
["C0"])
184+
assert mpl.colors.same_color([l.get_color() for l in axs[1].lines],
185+
[f"C{i}" for i in "12345678901"])
186+
assert mpl.colors.same_color([p.get_facecolor() for p in axs[0].patches],
187+
["C1"])
188+
assert mpl.colors.same_color([p.get_facecolor() for p in axs[1].patches],
189+
["C0"])

0 commit comments

Comments
 (0)
0