Skip to content

Commit bd4cd7f

Browse files
committed
more robust prop cycling
1 parent 8b5a604 commit bd4cd7f

File tree

2 files changed

+210
-167
lines changed

2 files changed

+210
-167
lines changed

ultraplot/axes/plot.py

Lines changed: 47 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2477,52 +2477,53 @@ def _parse_cycle(
24772477
Whether to simply return the property cycle or apply it. The cycle is
24782478
only applied (and therefore reset) if it differs from the current one.
24792479
"""
2480-
# Create the property cycler and update it if necessary
2481-
# NOTE: Matplotlib Cycler() objects have built-in __eq__ operator
2482-
# so really easy to check if the cycler has changed!
2483-
if cycle is not None or cycle_kw:
2480+
2481+
# Create/update cycler only if needed
2482+
2483+
if cycle is not None or cycle_kw or not hasattr(self, "_current_cycler"):
24842484
cycle_kw = cycle_kw or {}
2485-
if ncycle != 1: # ignore for column-by-column plotting commands
2486-
cycle_kw.setdefault("N", ncycle) # if None then filled in Colormap()
2487-
if isinstance(cycle, str) and cycle.lower() == "none":
2488-
cycle = False
2489-
if not cycle:
2490-
args = ()
2491-
elif cycle is True: # consistency with 'False' ('reactivate' the cycler)
2492-
args = (rc["axes.prop_cycle"],)
2493-
else:
2494-
args = (cycle,)
2495-
cycle = constructor.Cycle(*args, **cycle_kw)
2496-
with warnings.catch_warnings(): # hide 'elementwise-comparison failed'
2497-
warnings.simplefilter("ignore", FutureWarning)
2498-
if return_cycle:
2499-
pass
2500-
elif cycle != self._active_cycle:
2485+
if ncycle != 1:
2486+
cycle_kw.setdefault("N", ncycle)
2487+
# Convert string or list to Cycle object
2488+
if isinstance(cycle, (str, list)):
2489+
cycle = constructor.Cycle(cycle, **cycle_kw)
2490+
elif cycle is True:
2491+
cycle = constructor.Cycle(rc["axes.prop_cycle"], **cycle_kw)
2492+
elif cycle is False:
2493+
cycle = None
2494+
elif cycle is not None and not isinstance(cycle, constructor.Cycle):
2495+
cycle = constructor.Cycle(cycle, **cycle_kw)
2496+
2497+
if not hasattr(self, "_current_cycler"):
2498+
self._current_cycler = cycle
2499+
2500+
# Update the current cycler if it changed
2501+
if cycle != self._current_cycler:
2502+
self._current_cycle = cycle
2503+
if not return_cycle and self._current_cycler != self._active_cycle:
25012504
self.set_prop_cycle(cycle)
25022505

2503-
# Manually extract and apply settings to outgoing keyword arguments
2504-
# if native matplotlib function does not include desired properties
2505-
cycle_manually = cycle_manually or {}
2506-
parser = self._get_lines # the _process_plot_var_args instance
2507-
props = {} # which keys to apply from property cycler
2508-
for prop, key in cycle_manually.items():
2509-
if kwargs.get(key, None) is None and any(
2510-
prop in item for item in parser._cycler_items
2511-
):
2512-
props[prop] = key
2513-
if props:
2514-
dict_ = parser._cycler_items[parser._idx]
2515-
parser._idx = (parser._idx + 1) % len(parser._cycler_items)
2516-
for prop, key in props.items():
2517-
value = dict_[prop]
2518-
if key == "c": # special case: scatter() color must be converted to hex
2519-
value = pcolors.to_hex(value)
2520-
kwargs[key] = value
2506+
2507+
# Use existing cycler if none specified
2508+
if cycle is None:
2509+
cycle = self._current_cycler
2510+
2511+
# Get next set of properties
2512+
if cycle is not None:
2513+
props = cycle.get_next()
2514+
if cycle_manually:
2515+
mapped_props = {}
2516+
for prop, value in props.items():
2517+
if mapped_key := cycle_manually.get(prop):
2518+
mapped_props[mapped_key] = value
2519+
for prop in props:
2520+
if prop in cycle_manually:
2521+
kwargs.pop(prop, None)
2522+
kwargs.update(mapped_props)
25212523

25222524
if return_cycle:
2523-
return cycle, kwargs # needed for stem() to apply in a context()
2524-
else:
2525-
return kwargs
2525+
return cycle, kwargs
2526+
return kwargs
25262527

25272528
def _parse_level_lim(
25282529
self,
@@ -3457,8 +3458,7 @@ def _apply_scatter(self, xs, ys, ss, cc, *, vert=True, **kwargs):
34573458
ys, kw = inputs._dist_reduce(ys, **kw)
34583459
ss, kw = self._parse_markersize(ss, **kw) # parse 's'
34593460

3460-
# Move _parse_cycle before _parse_color
3461-
kw = self._parse_cycle(xs.shape[1] if xs.ndim > 1 else 1, **kw)
3461+
34623462

34633463
# Only parse color if explicitly provided
34643464
infer_rgb = True
@@ -3480,6 +3480,10 @@ def _apply_scatter(self, xs, ys, ss, cc, *, vert=True, **kwargs):
34803480
infer_rgb=infer_rgb,
34813481
**kw,
34823482
)
3483+
# Move _parse_cycle before _parse_color
3484+
kw = self._parse_cycle(
3485+
xs.shape[1] if xs.ndim > 1 else 1, cycle_manually=cycle_manually, **kw
3486+
)
34833487

34843488
guide_kw = _pop_params(kw, self._update_guide)
34853489
objs = []

0 commit comments

Comments
 (0)