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

Skip to content
8000

Commit 16e72cf

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 3d5c6d5 commit 16e72cf

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
@@ -209,6 +209,38 @@ def _process_plot_format(fmt):
209209
return linestyle, marker, color
210210

211211

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

242274
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+
@pytest.mark.style("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