diff --git a/doc/users/next_whats_new/markevery_prop_cycle.rst b/doc/users/next_whats_new/markevery_prop_cycle.rst new file mode 100644 index 000000000000..05e647cbfa80 --- /dev/null +++ b/doc/users/next_whats_new/markevery_prop_cycle.rst @@ -0,0 +1,7 @@ +Implemented support for axes.prop_cycle property markevery in rcParams +---------------------------------------------------------------------- + +The Matplotlib ``rcParams`` settings object now supports configuration +of the attribute `axes.prop_cycle` with cyclers using the `markevery` +Line2D object property. An example of this feature is provided at +`~matplotlib/examples/lines_bars_and_markers/markevery_prop_cycle.py` \ No newline at end of file diff --git a/examples/lines_bars_and_markers/markevery_prop_cycle.py b/examples/lines_bars_and_markers/markevery_prop_cycle.py new file mode 100644 index 000000000000..e680ab2a0d98 --- /dev/null +++ b/examples/lines_bars_and_markers/markevery_prop_cycle.py @@ -0,0 +1,67 @@ +""" +================================================================= +Implemented support for prop_cycle property markevery in rcParams +================================================================= + +This example demonstrates a working solution to issue #8576, providing full +support of the markevery property for axes.prop_cycle assignments through +rcParams. Makes use of the same list of markevery cases from +https://matplotlib.org/examples/pylab_examples/markevery_demo.html + +Renders a plot with shifted-sine curves along each column with +a unique markevery value for each sine curve. +""" +from cycler import cycler +import numpy as np +import matplotlib as mpl +import matplotlib.pyplot as plt + +# Define a list of markevery cases and color cases to plot +cases = [None, + 8, + (30, 8), + [16, 24, 30], + [0, -1], + slice(100, 200, 3), + 0.1, + 0.3, + 1.5, + (0.0, 0.1), + (0.45, 0.1)] + +colors = ['#1f77b4', + '#ff7f0e', + '#2ca02c', + '#d62728', + '#9467bd', + '#8c564b', + '#e377c2', + '#7f7f7f', + '#bcbd22', + '#17becf', + '#1a55FF'] + +# Create two different cyclers to use with axes.prop_cycle +markevery_cycler = cycler(markevery=cases) +color_cycler = cycler('color', colors) + +# Configure rcParams axes.prop_cycle with custom cycler +custom_cycler = color_cycler + markevery_cycler +mpl.rcParams['axes.prop_cycle'] = custom_cycler + +# Create data points and offsets +x = np.linspace(0, 2 * np.pi) +offsets = np.linspace(0, 2 * np.pi, 11, endpoint=False) +yy = np.transpose([np.sin(x + phi) for phi in offsets]) + +# Set the plot curve with markers and a title +fig = plt.figure() +ax = fig.add_axes([0.1, 0.1, 0.6, 0.75]) + +for i in range(len(cases)): + ax.plot(yy[:, i], marker='o', label=str(cases[i])) + ax.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.) + +plt.title('Support for axes.prop_cycle cycler with markevery') + +plt.show() diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index a4c86779ba4e..99e445863877 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -534,7 +534,54 @@ def validate_ps_distiller(s): _validate_negative_linestyle = ValidateInStrings('negative_linestyle', ['solid', 'dashed'], ignorecase=True) +def validate_markevery(s): + """ + Validate the markevery property of a Line2D object. + + Parameters + ---------- + s : None, int, float, slice, length-2 tuple of ints, + length-2 tuple of floats, list of ints + + Returns + ------- + s : None, int, float, slice, length-2 tuple of ints, + length-2 tuple of floats, list of ints + + """ + # Validate s against type slice + if isinstance(s, slice): + return s + # Validate s against type tuple + if isinstance(s, tuple): + tupMaxLength = 2 + tupType = type(s[0]) + if len(s) != tupMaxLength: + raise TypeError("'markevery' tuple must have a length of " + "%d" % (tupMaxLength)) + if tupType is int and not all(isinstance(e, int) for e in s): + raise TypeError("'markevery' tuple with first element of " + "type int must have all elements of type " + "int") + if tupType is float and not all(isinstance(e, float) for e in s): + raise TypeError("'markevery' tuple with first element of " + "type float must have all elements of type " + "float") + if tupType is not float and tupType is not int: + raise TypeError("'markevery' tuple contains an invalid type") + # Validate s against type list + elif isinstance(s, list): + if not all(isinstance(e, int) for e in s): + raise TypeError("'markevery' list must have all elements of " + "type int") + # Validate s against type float int and None + elif not isinstance(s, (float, int)): + if s is not None: + raise TypeError("'markevery' is of an invalid type") + + return s +validate_markeverylist = _listify_validator(validate_markevery) validate_legend_loc = ValidateInStrings( 'legend_loc', @@ -676,6 +723,7 @@ def validate_hatch(s): 'markersize': validate_floatlist, 'markeredgewidth': validate_floatlist, 'markeredgecolor': validate_colorlist, + 'markevery': validate_markeverylist, 'alpha': validate_floatlist, 'marker': validate_stringlist, 'hatch': validate_hatchlist, diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index a4fd6fd0e96a..51ea501474e2 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -23,6 +23,7 @@ validate_cycler, validate_hatch, validate_hist_bins, + validate_markevery, _validate_linestyle) @@ -326,6 +327,35 @@ def generate_validator_testcases(valid): ), 'fail': (('aardvark', ValueError), ) + }, + {'validator': validate_markevery, + 'success': ((None, None), + (1, 1), + (0.1, 0.1), + ((1, 1), (1, 1)), + ((0.1, 0.1), (0.1, 0.1)), + ([1, 2, 3], [1, 2, 3]), + (slice(2), slice(None, 2, None)), + (slice(1, 2, 3), slice(1, 2, 3)) + ), + 'fail': (((1, 2, 3), TypeError), + ([1, 2, 0.3], TypeError), + (['a', 2, 3], TypeError), + ([1, 2, 'a'], TypeError), + ((0.1, 0.2, 0.3), TypeError), + ((0.1, 2, 3), TypeError), + ((1, 0.2, 0.3), TypeError), + ((1, 0.1), TypeError), + ((0.1, 1), TypeError), + (('abc'), TypeError), + ((1, 'a'), TypeError), + ((0.1, 'b'), TypeError), + (('a', 1), TypeError), + (('a', 0.1), TypeError), + ('abc', TypeError), + ('a', TypeError), + (object(), TypeError) + ) } )