Skip to content

Commit 6a67e88

Browse files
committed
Robust fix
1 parent bd4cd7f commit 6a67e88

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+222
-145
lines changed

ultraplot/axes/plot.py

Lines changed: 41 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -2477,52 +2477,43 @@ 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+
cycle_kw = cycle_kw or {}
2481+
cycle_manually = cycle_manually or {}
2482+
cycle_kw.setdefault("N", ncycle)
2483+
2484+
# Match-case for cycle resolution
2485+
match cycle:
2486+
case None if not cycle_kw:
2487+
resolved_cycle = None
2488+
case True:
2489+
resolved_cycle = constructor.Cycle(rc["axes.prop_cycle"])
2490+
case str() if cycle.lower() == "none":
2491+
resolved_cycle = None
2492+
case str() | int():
2493+
resolved_cycle = constructor.Cycle(cycle, **cycle_kw)
2494+
case _:
2495+
resolved_cycle = None
2496+
2497+
# Ignore cycle for single-column plotting
2498+
resolved_cycle = None if ncycle == 1 else resolved_cycle
2499+
2500+
# Return or apply cycle
2501+
if return_cycle:
2502+
return resolved_cycle, kwargs
24802503

2481-
# Create/update cycler only if needed
2482-
2483-
if cycle is not None or cycle_kw or not hasattr(self, "_current_cycler"):
2484-
cycle_kw = cycle_kw or {}
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:
2504-
self.set_prop_cycle(cycle)
2505-
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)
2504+
if resolved_cycle and resolved_cycle != self._active_cycle:
2505+
self.set_prop_cycle(resolved_cycle)
25232506

2524-
if return_cycle:
2525-
return cycle, kwargs
2507+
# Apply manual cycle properties
2508+
if cycle_manually:
2509+
current_prop = self._get_lines._cycler_items[self._get_lines._idx]
2510+
self._get_lines._idx = (self._get_lines._idx + 1) % len(
2511+
self._get_lines._cycler_items
2512+
)
2513+
for prop, key in cycle_manually.items():
2514+
if kwargs.get(key) is None and prop in current_prop:
2515+
value = current_prop[prop]
2516+
kwargs[key] = pcolors.to_hex(value) if key == "c" else value
25262517
return kwargs
25272518

25282519
def _parse_level_lim(
@@ -3458,8 +3449,6 @@ def _apply_scatter(self, xs, ys, ss, cc, *, vert=True, **kwargs):
34583449
ys, kw = inputs._dist_reduce(ys, **kw)
34593450
ss, kw = self._parse_markersize(ss, **kw) # parse 's'
34603451

3461-
3462-
34633452
# Only parse color if explicitly provided
34643453
infer_rgb = True
34653454
if cc is not None:
@@ -3480,15 +3469,16 @@ def _apply_scatter(self, xs, ys, ss, cc, *, vert=True, **kwargs):
34803469
infer_rgb=infer_rgb,
34813470
**kw,
34823471
)
3483-
# Move _parse_cycle before _parse_color
3472+
# Create the cycler object by manually cycling and sanitzing the inputs
34843473
kw = self._parse_cycle(
3485-
xs.shape[1] if xs.ndim > 1 else 1, cycle_manually=cycle_manually, **kw
3486-
)
3474+
xs.shape[1] if xs.ndim > 1 else 1, cycle_manually=cycle_manually, **kw
3475+
)
34873476

34883477
guide_kw = _pop_params(kw, self._update_guide)
34893478
objs = []
34903479
for _, n, x, y, s, c, kw in self._iter_arg_cols(xs, ys, ss, cc, **kw):
3491-
# Don't set 'c' explicitly unless it was provided
3480+
# Cycle s and c as they are in cycle_manually
3481+
# Note: they could be None
34923482
kw["s"], kw["c"] = s, c
34933483
kw = self._parse_cycle(n, cycle_manually=cycle_manually, **kw)
34943484
*eb, kw = self._add_error_bars(x, y, vert=vert, default_barstds=True, **kw)
@@ -4030,6 +4020,7 @@ def _apply_violinplot(
40304020
bodies = artists.pop("bodies", ()) # should be no other entries
40314021
if bodies:
40324022
bodies = cbook.silent_list(type(bodies[0]).__name__, bodies)
4023+
40334024
for i, body in enumerate(bodies):
40344025
body.set_alpha(1.0) # change default to 1.0
40354026
if fillcolor[i] is not None:

ultraplot/constructor.py

Lines changed: 92 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,13 @@
3030
from . import ticker as pticker
3131
from .config import rc
3232
from .internals import ic # noqa: F401
33-
from .internals import _not_none, _pop_props, _version_cartopy, _version_mpl, warnings
33+
from .internals import (
34+
_not_none,
35+
_pop_props,
36+
_version_cartopy,
37+
_version_mpl,
38+
warnings,
39+
)
3440
from .utils import get_colors, to_hex, to_rgba
3541

3642
try:
@@ -235,7 +241,12 @@
235241
"height": 15000e3,
236242
},
237243
"tmerc": {"lon_0": 0, "lat_0": 0, "width": 10000e3, "height": 10000e3},
238-
"merc": {"llcrnrlat": -80, "urcrnrlat": 84, "llcrnrlon": -180, "urcrnrlon": 180},
244+
"merc": {
245+
"llcrnrlat": -80,
246+
"urcrnrlat": 84,
247+
"llcrnrlon": -180,
248+
"urcrnrlon": 180,
249+
},
239250
"omerc": {
240251
"lat_0": 0,
241252
"lon_0": 0,
@@ -763,93 +774,87 @@ def _pop_modification(key):
763774

764775

765776
class Cycle(cycler.Cycler):
777+
"""
778+
Generate and merge `~cycler.Cycler` instances in a variety of ways. The new generated class can be used to internally map keywords to the properties of the `~cycler.Cycler` instance. It is used by various plot functions to cycle through colors, linestyles, markers, etc.
779+
780+
Parameters
781+
----------
782+
*args : colormap-spec or cycle-spec, optional
783+
Positional arguments control the *colors* in the `~cycler.Cycler`
784+
object. If zero arguments are passed, the single color ``'black'``
785+
is used. If more than one argument is passed, the resulting cycles
786+
are merged. Arguments are interpreted as follows:
787+
788+
* If a `~cycler.Cycler`, nothing more is done.
789+
* If a sequence of RGB tuples or color strings, these colors are used.
790+
* If a `~ultraplot.colors.DiscreteColormap`, colors from the ``colors``
791+
attribute are used.
792+
* If a string cycle name, that `~ultraplot.colors.DiscreteColormap`
793+
is looked up and its ``colors`` are used.
794+
* In all other cases, the argument is passed to `Colormap`, and
795+
colors from the resulting `~ultraplot.colors.ContinuousColormap`
796+
are used. See the `samples` argument.
797+
798+
If the last positional argument is numeric, it is used for the
799+
`samples` keyword argument.
800+
N
801+
Shorthand for `samples`.
802+
samples : float or sequence of float, optional
803+
For `~ultraplot.colors.DiscreteColormap`\\ s, this is the number of
804+
colors to select. For example, ``Cycle('538', 4)`` returns the first 4
805+
colors of the ``'538'`` color cycle.
806+
For `~ultraplot.colors.ContinuousColormap`\\ s, this is either a
807+
sequence of sample coordinates used to draw colors from the colormap, or
808+
an integer number of colors to draw. If the latter, the sample coordinates
809+
are ``np.linspace(0, 1, samples)``. For example, ``Cycle('Reds', 5)``
810+
divides the ``'Reds'`` colormap into five evenly spaced colors.
811+
812+
Other parameters
813+
----------------
814+
c, color, colors : sequence of color-spec, optional
815+
A sequence of colors passed as keyword arguments. This is equivalent
816+
to passing a sequence of colors as the first positional argument and is
817+
included for consistency with `~matplotlib.axes.Axes.set_prop_cycle`.
818+
If positional arguments were passed, the colors in this list are
819+
appended to the colors resulting from the positional arguments.
820+
lw, ls, d, a, m, ms, mew, mec, mfc
821+
Shorthands for the below keywords.
822+
linewidth, linestyle, dashes, alpha, marker, markersize, markeredgewidth, \
823+
markeredgecolor, markerfacecolor : object or sequence of object, optional
824+
Lists of `~matplotlib.lines.Line2D` properties that can be added to the
825+
`~cycler.Cycler` instance. If the input was already a `~cycler.Cycler`,
826+
these are added or appended to the existing cycle keys. If the lists have
827+
unequal length, they are repeated to their least common multiple (unlike
828+
`~cycler.cycler`, which throws an error in this case). For more info
829+
on cyclers see `~matplotlib.axes.Axes.set_prop_cycle`. Also see
830+
the `line style reference \
831+
<https://matplotlib.org/2.2.5/gallery/lines_bars_and_markers/line_styles_reference.html>`__,
832+
the `marker reference \
833+
<https://matplotlib.org/stable/gallery/lines_bars_and_markers/marker_reference.html>`__,
834+
and the `custom dashes reference \
835+
<https://matplotlib.org/stable/gallery/lines_bars_and_markers/line_demo_dash_control.html>`__.
836+
linewidths, linestyles, dashes, alphas, markers, markersizes, markeredgewidths, \
837+
markeredgecolors, markerfacecolors
838+
Aliases for the above keywords.
839+
**kwargs
840+
If the input is not already a `~cycler.Cycler` instance, these are passed
841+
to `Colormap` and used to build the `~ultraplot.colors.DiscreteColormap`
842+
from which the cycler will draw its colors.
843+
844+
See also
845+
--------
846+
cycler.cycler
847+
cycler.Cycler
848+
matplotlib.axes.Axes.set_prop_cycle
849+
ultraplot.constructor.Colormap
850+
ultraplot.constructor.Norm
851+
ultraplot.utils.get_colors
852+
"""
853+
766854
def __init__(self, *args, N=None, samples=None, name=None, **kwargs):
767-
"""
768-
Generate and merge `~cycler.Cycler` instances in a variety of ways.
769-
770-
Parameters
771-
----------
772-
*args : colormap-spec or cycle-spec, optional
773-
Positional arguments control the *colors* in the `~cycler.Cycler`
774-
object. If zero arguments are passed, the single color ``'black'``
775-
is used. If more than one argument is passed, the resulting cycles
776-
are merged. Arguments are interpreted as follows:
777-
778-
* If a `~cycler.Cycler`, nothing more is done.
779-
* If a sequence of RGB tuples or color strings, these colors are used.
780-
* If a `~ultraplot.colors.DiscreteColormap`, colors from the ``colors``
781-
attribute are used.
782-
* If a string cycle name, that `~ultraplot.colors.DiscreteColormap`
783-
is looked up and its ``colors`` are used.
784-
* In all other cases, the argument is passed to `Colormap`, and
785-
colors from the resulting `~ultraplot.colors.ContinuousColormap`
786-
are used. See the `samples` argument.
787-
788-
If the last positional argument is numeric, it is used for the
789-
`samples` keyword argument.
790-
N
791-
Shorthand for `samples`.
792-
samples : float or sequence of float, optional
793-
For `~ultraplot.colors.DiscreteColormap`\\ s, this is the number of
794-
colors to select. For example, ``Cycle('538', 4)`` returns the first 4
795-
colors of the ``'538'`` color cycle.
796-
For `~ultraplot.colors.ContinuousColormap`\\ s, this is either a
797-
sequence of sample coordinates used to draw colors from the colormap, or
798-
an integer number of colors to draw. If the latter, the sample coordinates
799-
are ``np.linspace(0, 1, samples)``. For example, ``Cycle('Reds', 5)``
800-
divides the ``'Reds'`` colormap into five evenly spaced colors.
801-
802-
Other parameters
803-
----------------
804-
c, color, colors : sequence of color-spec, optional
805-
A sequence of colors passed as keyword arguments. This is equivalent
806-
to passing a sequence of colors as the first positional argument and is
807-
included for consistency with `~matplotlib.axes.Axes.set_prop_cycle`.
808-
If positional arguments were passed, the colors in this list are
809-
appended to the colors resulting from the positional arguments.
810-
lw, ls, d, a, m, ms, mew, mec, mfc
811-
Shorthands for the below keywords.
812-
linewidth, linestyle, dashes, alpha, marker, markersize, markeredgewidth, \
813-
markeredgecolor, markerfacecolor : object or sequence of object, optional
814-
Lists of `~matplotlib.lines.Line2D` properties that can be added to the
815-
`~cycler.Cycler` instance. If the input was already a `~cycler.Cycler`,
816-
these are added or appended to the existing cycle keys. If the lists have
817-
unequal length, they are repeated to their least common multiple (unlike
818-
`~cycler.cycler`, which throws an error in this case). For more info
819-
on cyclers see `~matplotlib.axes.Axes.set_prop_cycle`. Also see
820-
the `line style reference \
821-
<https://matplotlib.org/2.2.5/gallery/lines_bars_and_markers/line_styles_reference.html>`__,
822-
the `marker reference \
823-
<https://matplotlib.org/stable/gallery/lines_bars_and_markers/marker_reference.html>`__,
824-
and the `custom dashes reference \
825-
<https://matplotlib.org/stable/gallery/lines_bars_and_markers/line_demo_dash_control.html>`__.
826-
linewidths, linestyles, dashes, alphas, markers, markersizes, markeredgewidths, \
827-
markeredgecolors, markerfacecolors
828-
Aliases for the above keywords.
829-
**kwargs
830-
If the input is not already a `~cycler.Cycler` instance, these are passed
831-
to `Colormap` and used to build the `~ultraplot.colors.DiscreteColormap`
832-
from which the cycler will draw its colors.
833-
834-
Returns
835-
-------
836-
cycler.Cycler
837-
A `~cycler.Cycler` instance that can be passed
838-
to `~matplotlib.axes.Axes.set_prop_cycle`.
839-
840-
See also
841-
--------
842-
cycler.cycler
843-
cycler.Cycler
844-
matplotlib.axes.Axes.set_prop_cycle
845-
ultraplot.constructor.Colormap
846-
ultraplot.constructor.Norm
847-
ultraplot.utils.get_colors
848-
"""
849855
self.name = "_no_name" # default value
850856
cycler_props = self._parse_basic_properties(kwargs)
851857
samples = _not_none(samples=samples, N=N) # trigger Colormap default
852-
853858
if not args:
854859
self._handle_empty_args(cycler_props, kwargs)
855860
elif self._is_all_cyclers(args):
@@ -876,7 +881,7 @@ def _parse_basic_properties(self, kwargs):
876881

877882
def _handle_empty_args(self, props, kwargs):
878883
"""Handle case when no positional arguments are provided."""
879-
props.setdefault("color", ["black"])
884+
props.setdefault("color", "black")
880885
if kwargs:
881886
warnings._warn_ultraplot(f"Ignoring Cycle() keyword arg(s) {kwargs}.")
882887
self._build_cycler(())
@@ -921,11 +926,10 @@ def _build_cycler(self, dicts):
921926
for dict_ in dicts:
922927
for key, value in dict_.items():
923928
props.setdefault(key, []).extend(value)
924-
925929
# Build cycler with matching property lengths
926930
# Ensure at least a default color property exists
927931
if not props:
928-
props = {'color': ['black']}
932+
props = {"color": ["black"]}
929933

930934
# Build cycler with matching property lengths
931935
lengths = [len(value) for value in props.values()]
-20 Bytes
-887 Bytes
-4.58 KB
-52.7 KB
Binary file not shown.
-171 Bytes
-34.7 KB
-7.5 KB
-5.08 KB

0 commit comments

Comments
 (0)