diff --git a/doc/users/whats_new/validation_of_linestyle_rcparams.rst b/doc/users/whats_new/validation_of_linestyle_rcparams.rst new file mode 100644 index 000000000000..d8fd823593f2 --- /dev/null +++ b/doc/users/whats_new/validation_of_linestyle_rcparams.rst @@ -0,0 +1,31 @@ +Validation of line style rcParams +--------------------------------- + +Stricter validation +``````````````````` +The validation of rcParams that are related to line styles +(``lines.linestyle``, ``boxplot.*.linestyle``, ``grid.linestyle`` and +``contour.negative_linestyle``) now effectively checks that the values +are valid line styles. Strings like ``dashed`` or ``--`` are accepted, +as well as even-length sequences of on-off ink like ``[1, 1.65]``. In +this latter case, the offset value is handled internally and should *not* +be provided by the user. + +The validation is case-insensitive. + +Deprecation of the former validators for ``contour.negative_linestyle`` +``````````````````````````````````````````````````````````````````````` +The new validation scheme replaces the former one used for the +``contour.negative_linestyle`` rcParams, that was limited to ``solid`` +and ``dashed`` line styles. + +The former public validation functions ``validate_negative_linestyle`` +and ``validate_negative_linestyle_legacy`` will be deprecated in 2.1 and +may be removed in 2.3. There are no public functions to replace them. + +Examples of use +``````````````` +:: + + grid.linestyle : (1, 3) # loosely dotted grid lines + contour.negative_linestyle : dashdot # previously only solid or dashed diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index a5a248c156bd..cd43c354f715 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -25,7 +25,7 @@ import warnings import re -from matplotlib.cbook import mplDeprecation +from matplotlib.cbook import mplDeprecation, deprecated, ls_mapper from matplotlib.fontconfig_pattern import parse_fontconfig_pattern from matplotlib.colors import is_color_like @@ -530,20 +530,28 @@ def validate_ps_distiller(s): 'top', 'none']) validate_fillstylelist = _listify_validator(validate_fillstyle) -validate_negative_linestyle = ValidateInStrings('negative_linestyle', - ['solid', 'dashed'], - ignorecase=True) +_validate_negative_linestyle = ValidateInStrings('negative_linestyle', + ['solid', 'dashed'], + ignorecase=True) +@deprecated('2.1', + addendum=(" See 'validate_negative_linestyle_legacy' " + + "deprecation warning for more information.")) +def validate_negative_linestyle(s): + return _validate_negative_linestyle(s) + + +@deprecated('2.1', + addendum=(" The 'contour.negative_linestyle' rcParam now " + + "follows the same validation as the other rcParams " + + "that are related to line style.")) def validate_negative_linestyle_legacy(s): try: res = validate_negative_linestyle(s) return res except ValueError: dashes = validate_nseq_float(2)(s) - warnings.warn("Deprecated negative_linestyle specification; use " - "'solid' or 'dashed'", - mplDeprecation) return (0, dashes) # (offset, (solid, blank)) @@ -888,6 +896,39 @@ def validate_animation_writer_path(p): modules["matplotlib.animation"].writers.set_dirty() return p +# A validator dedicated to the named line styles, based on the items in +# ls_mapper, and a list of possible strings read from Line2D.set_linestyle +_validate_named_linestyle = ValidateInStrings('linestyle', + list(six.iterkeys(ls_mapper)) + + list(six.itervalues(ls_mapper)) + + ['None', 'none', ' ', ''], + ignorecase=True) + + +def _validate_linestyle(ls): + """ + A validator for all possible line styles, the named ones *and* + the on-off ink sequences. + """ + # Named line style, like u'--' or u'solid' + if isinstance(ls, six.text_type): + return _validate_named_linestyle(ls) + + # On-off ink (in points) sequence *of even length*. + # Offset is set to None. + try: + if len(ls) % 2 != 0: + # Expecting a sequence of even length + raise ValueError + return (None, validate_nseq_float()(ls)) + except (ValueError, TypeError): + # TypeError can be raised by wrong types passed to float() + # (called inside the instance of validate_nseq_float). + pass + + raise ValueError("linestyle must be a string or " + + "an even-length sequence of floats.") + # a map from key -> value, converter defaultParams = { @@ -912,7 +953,7 @@ def validate_animation_writer_path(p): # line props 'lines.linewidth': [1.5, validate_float], # line width in points - 'lines.linestyle': ['-', six.text_type], # solid line + 'lines.linestyle': ['-', _validate_linestyle], # solid line 'lines.color': ['C0', validate_color], # first color in color cycle 'lines.marker': ['None', six.text_type], # marker name 'lines.markeredgewidth': [1.0, validate_float], @@ -961,31 +1002,31 @@ def validate_animation_writer_path(p): 'boxplot.flierprops.markerfacecolor': ['none', validate_color_or_auto], 'boxplot.flierprops.markeredgecolor': ['k', validate_color], 'boxplot.flierprops.markersize': [6, validate_float], - 'boxplot.flierprops.linestyle': ['none', six.text_type], + 'boxplot.flierprops.linestyle': ['none', _validate_linestyle], 'boxplot.flierprops.linewidth': [1.0, validate_float], 'boxplot.boxprops.color': ['k', validate_color], 'boxplot.boxprops.linewidth': [1.0, validate_float], - 'boxplot.boxprops.linestyle': ['-', six.text_type], + 'boxplot.boxprops.linestyle': ['-', _validate_linestyle], 'boxplot.whiskerprops.color': ['k', validate_color], 'boxplot.whiskerprops.linewidth': [1.0, validate_float], - 'boxplot.whiskerprops.linestyle': ['-', six.text_type], + 'boxplot.whiskerprops.linestyle': ['-', _validate_linestyle], 'boxplot.capprops.color': ['k', validate_color], 'boxplot.capprops.linewidth': [1.0, validate_float], - 'boxplot.capprops.linestyle': ['-', six.text_type], + 'boxplot.capprops.linestyle': ['-', _validate_linestyle], 'boxplot.medianprops.color': ['C1', validate_color], 'boxplot.medianprops.linewidth': [1.0, validate_float], - 'boxplot.medianprops.linestyle': ['-', six.text_type], + 'boxplot.medianprops.linestyle': ['-', _validate_linestyle], 'boxplot.meanprops.color': ['C2', validate_color], 'boxplot.meanprops.marker': ['^', six.text_type], 'boxplot.meanprops.markerfacecolor': ['C2', validate_color], 'boxplot.meanprops.markeredgecolor': ['C2', validate_color], 'boxplot.meanprops.markersize': [6, validate_float], - 'boxplot.meanprops.linestyle': ['--', six.text_type], + 'boxplot.meanprops.linestyle': ['--', _validate_linestyle], 'boxplot.meanprops.linewidth': [1.0, validate_float], ## font props @@ -1051,8 +1092,7 @@ def validate_animation_writer_path(p): 'image.composite_image': [True, validate_bool], # contour props - 'contour.negative_linestyle': ['dashed', - validate_negative_linestyle_legacy], + 'contour.negative_linestyle': ['dashed', _validate_linestyle], 'contour.corner_mask': [True, validate_corner_mask], # errorbar props @@ -1215,7 +1255,7 @@ def validate_animation_writer_path(p): 'ytick.direction': ['out', six.text_type], # direction of yticks 'grid.color': ['#b0b0b0', validate_color], # grid color - 'grid.linestyle': ['-', six.text_type], # solid + 'grid.linestyle': ['-', _validate_linestyle], # solid 'grid.linewidth': [0.8, validate_float], # in points 'grid.alpha': [1.0, validate_float], diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index b376170ac31c..009e03e42d11 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -29,7 +29,8 @@ validate_nseq_float, validate_cycler, validate_hatch, - validate_hist_bins) + validate_hist_bins, + _validate_linestyle) mpl.rc('text', usetex=False) @@ -333,6 +334,26 @@ def generate_validator_testcases(valid): ), 'fail': (('aardvark', ValueError), ) + }, + {'validator': _validate_linestyle, # NB: case-insensitive + 'success': (('-', '-'), ('solid', 'solid'), + ('--', '--'), ('dashed', 'dashed'), + ('-.', '-.'), ('dashdot', 'dashdot'), + (':', ':'), ('dotted', 'dotted'), + ('', ''), (' ', ' '), + ('None', 'none'), ('none', 'none'), + ('DoTtEd', 'dotted'), + (['1.23', '4.56'], (None, [1.23, 4.56])), + ([1.23, 456], (None, [1.23, 456.0])), + ([1, 2, 3, 4], (None, [1.0, 2.0, 3.0, 4.0])), + ), + 'fail': (('aardvark', ValueError), # not a valid string + ((None, [1, 2]), ValueError), # (offset, dashes) is not OK + ((0, [1, 2]), ValueError), # idem + ((-1, [1, 2]), ValueError), # idem + ([1, 2, 3], ValueError), # not a sequence of even length + (1.23, ValueError) # not a sequence + ) } ) diff --git a/matplotlibrc.template b/matplotlibrc.template index e57dfd9ada2d..aaaf32e412c0 100644 --- a/matplotlibrc.template +++ b/matplotlibrc.template @@ -471,7 +471,7 @@ backend : $TEMPLATE_BACKEND # such as a PDF. ### CONTOUR PLOTS -#contour.negative_linestyle : dashed # dashed | solid +#contour.negative_linestyle : dashed # string or on-off ink sequence #contour.corner_mask : True # True | False | legacy ### ERRORBAR PLOTS