From f4e25aabd23ca3a1c2ec62b4ed2496b54c54afd2 Mon Sep 17 00:00:00 2001 From: Fabian Kloosterman Date: Sun, 14 Jan 2018 00:04:08 +0100 Subject: [PATCH 1/4] Added support for relative line width, marker size and marker edge width (e.g. 'x-large', 'xx-thin') and corresponding rcParams. Note: in rc, lines.linewidth and lines.markersize are taken as the reference for relative widths/sizes and cannot be relative themselves. Also, relative width for hatch.linewidth is not (yet) supported. --- lib/matplotlib/collections.py | 5 ++- lib/matplotlib/lines.py | 83 ++++++++++++++++++++++++++++++----- lib/matplotlib/patches.py | 16 ++++--- lib/matplotlib/rcsetup.py | 47 +++++++++++++------- 4 files changed, 117 insertions(+), 34 deletions(-) diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py index 4e15176e060a..cd0a0405a675 100644 --- a/lib/matplotlib/collections.py +++ b/lib/matplotlib/collections.py @@ -494,7 +494,7 @@ def set_linewidth(self, lw): or a sequence; if it is a sequence the patches will cycle through the sequence - ACCEPTS: float or sequence of floats + ACCEPTS: float, string, or sequence of floats/strings """ if lw is None: lw = mpl.rcParams['patch.linewidth'] @@ -503,6 +503,9 @@ def set_linewidth(self, lw): # get the un-scaled/broadcast lw self._us_lw = np.atleast_1d(np.asarray(lw)) + # convert line widths to points + self._us_lw = np.array([mlines.linewidth2points(x) for x in self._us_lw]) + # scale all of the dash patterns. self._linewidths, self._linestyles = self._bcast_lwls( self._us_lw, self._us_linestyles) diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 96c1a888e04f..fd2c35e6474c 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -73,6 +73,64 @@ def _scale_dashes(offset, dashes, lw): return scaled_offset, scaled_dashes +def _convert2points(val, default, lut): + """ + Convert relative size to points, using `default` as the base value and + `lut` as the look-up table for qualitative size names. + """ + if val is None: + return default + + try: + return default*lut[val] + except KeyError: + pts = float(val) + # do not use relative fraction as string, because when loading styles from file + # there is no way to distinguish between a proper float and its string equivalent + #if isinstance(val, six.string_types): + # pts *= default + return pts + +def _build_qualitative_scaling(labels, comparative=None, base=1.2): + + a, b = labels + if comparative is None: + ca, cb = a+"er", b+"er" + else: + ca, cb = comparative + + d = {None: 1., 'medium': 1., ca:base**-1, cb:base} + for k,m in enumerate(('', 'x-', 'xx-')): + d['{}{}'.format(m,a)] = base**(-k-1) + d['{}{}'.format(m,b)] = base**(k+1) + + return d + +linewidth_scaling = _build_qualitative_scaling(('thin', 'thick'), ('thinner', 'thicker'), 1.5) +markersize_scaling = _build_qualitative_scaling(('small', 'large'), ('smaller', 'larger'), 1.5) + +def linewidth2points(w, default=None): + """ + Convert a line width specification to points. + Line width can be either specified as float (absolute width in points), + a string representing a fraction of the default width in rcParams + or a string representing a relative qualitative width (e.g. 'x-thin') + """ + # the default value may be relative as well! (e.g. for markeredgewidth) + default = _convert2points(default, rcParams['lines.linewidth'], linewidth_scaling) + return _convert2points(w, default, linewidth_scaling) + +def markersize2points(s, default=None): + """ + Convert a marker size specification to points. + Marker size can be either specified as float (absolute size in points), + a string representing a fraction of the default size in rcParams + or a string representing a relative qualitative size (e.g. 'x-large') + """ + default = _convert2points(default, rcParams['lines.markersize'], markersize_scaling) + return _convert2points(s, default, markersize_scaling) + + def segment_hits(cx, cy, x, y, radius): """ Determine if any line segments are within radius of a @@ -1008,11 +1066,12 @@ def set_drawstyle(self, drawstyle): def set_linewidth(self, w): """ - Set the line width in points + Set the line width, either absolute width in points or width relative to rc default. - ACCEPTS: float value in points + ACCEPTS: [float value in points | fraction as string | None | 'xx-thin' | 'x-thin' | + 'thin' | 'thinner' | 'medium' | 'thicker' | 'thick' | 'x-thick' | 'xx-thick'] """ - w = float(w) + w = linewidth2points(w) if self._linewidth != w: self.stale = True @@ -1156,12 +1215,14 @@ def set_markeredgecolor(self, ec): def set_markeredgewidth(self, ew): """ - Set the marker edge width in points + Set the marker edge width, either absolute width in points or + width relative to rc default. - ACCEPTS: float value in points + ACCEPTS: [float value in points | fraction as string | None | 'xx-thin' | 'x-thin' | + 'thin' | 'thinner' | 'medium' | 'thicker' | 'thick' | 'x-thick' | 'xx-thick'] """ - if ew is None: - ew = rcParams['lines.markeredgewidth'] + ew = linewidth2points(ew, rcParams['lines.markeredgewidth']) + if self._markeredgewidth != ew: self.stale = True self._markeredgewidth = ew @@ -1192,11 +1253,13 @@ def set_markerfacecoloralt(self, fc): def set_markersize(self, sz): """ - Set the marker size in points + Set the line width, either absolute width in points or width relative to rc default. - ACCEPTS: float + ACCEPTS: [float value in points | fraction as string | None | 'xx-small' | 'x-small' | + 'small' | 'smaller' | 'medium' | 'larger' | 'large' | 'x-large' | 'xx-large'] """ - sz = float(sz) + sz = markersize2points(sz) + if self._markersize != sz: self.stale = True self._markersize = sz diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 6302fdf72078..0d72f7ab6c6e 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -351,16 +351,18 @@ def set_alpha(self, alpha): def set_linewidth(self, w): """ - Set the patch linewidth in points + Set the path line width, either absolute width in points or width relative to rc default. - ACCEPTS: float or None for default + ACCEPTS: [float value in points | fraction as string | None | 'xx-thin' | 'x-thin' | + 'thin' | 'thinner' | 'medium' | 'thicker' | 'thick' | 'x-thick' | 'xx-thick'] """ - if w is None: - w = mpl.rcParams['patch.linewidth'] - if w is None: - w = mpl.rcParams['axes.linewidth'] - self._linewidth = float(w) + default = mpl.rcParams['patch.linewidth'] + if default is None: + default = mpl.rcParams['axes.linewidth'] + + self._linewidth = mlines.linewidth2points(w, default) + # scale the dash pattern by the linewidth offset, ls = self._us_dashes self._dashoffset, self._dashes = mlines._scale_dashes( diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index eafc8d4eecf7..924bb31b3281 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -931,6 +931,21 @@ def _validate_linestyle(ls): raise ValueError("linestyle {!r} is not a valid on-off ink " "sequence.".format(ls)) +def _validate_linewidth(w): + try: + w=float(w) + except (ValueError, TypeError): + if w not in ['xx-thin','x-thin','thin','thinner','medium','thick','thicker','x-thick','xx-thick',None]: + raise ValueError("width {!r} is not a valid float or string.".format(w)) + return w + +def _validate_markersize(sz): + try: + sz=float(sz) + except (ValueError, TypeError): + if sz not in ['xx-small','x-small','small','smaller','medium','large','larger','x-large','xx-large',None]: + raise ValueError("marker size {!r} is not a valid float or string.".format(w)) + return sz # a map from key -> value, converter defaultParams = { @@ -959,7 +974,7 @@ def _validate_linestyle(ls): 'lines.linestyle': ['-', _validate_linestyle], # solid line 'lines.color': ['C0', validate_color], # first color in color cycle 'lines.marker': ['None', validate_string], # marker name - 'lines.markeredgewidth': [1.0, validate_float], + 'lines.markeredgewidth': [1.0, _validate_linewidth], # width in points, or relative to lines.linewidth 'lines.markersize': [6, validate_float], # markersize, in points 'lines.antialiased': [True, validate_bool], # antialiased (no jaggies) 'lines.dash_joinstyle': ['round', validate_joinstyle], @@ -976,7 +991,7 @@ def _validate_linestyle(ls): 'markers.fillstyle': ['full', validate_fillstyle], ## patch props - 'patch.linewidth': [1.0, validate_float], # line width in points + 'patch.linewidth': [1.0, _validate_linewidth], # line width in points, or relative to lines.linewidth 'patch.edgecolor': ['k', validate_color], 'patch.force_edgecolor' : [False, validate_bool], 'patch.facecolor': ['C0', validate_color], # first color in cycle @@ -1005,33 +1020,33 @@ def _validate_linestyle(ls): 'boxplot.flierprops.marker': ['o', validate_string], 'boxplot.flierprops.markerfacecolor': ['none', validate_color_or_auto], 'boxplot.flierprops.markeredgecolor': ['k', validate_color], - 'boxplot.flierprops.markersize': [6, validate_float], + 'boxplot.flierprops.markersize': [6, _validate_markersize], 'boxplot.flierprops.linestyle': ['none', _validate_linestyle], - 'boxplot.flierprops.linewidth': [1.0, validate_float], + 'boxplot.flierprops.linewidth': [1.0, _validate_linewidth], 'boxplot.boxprops.color': ['k', validate_color], - 'boxplot.boxprops.linewidth': [1.0, validate_float], + 'boxplot.boxprops.linewidth': [1.0, _validate_linewidth], 'boxplot.boxprops.linestyle': ['-', _validate_linestyle], 'boxplot.whiskerprops.color': ['k', validate_color], - 'boxplot.whiskerprops.linewidth': [1.0, validate_float], + 'boxplot.whiskerprops.linewidth': [1.0, _validate_linewidth], 'boxplot.whiskerprops.linestyle': ['-', _validate_linestyle], 'boxplot.capprops.color': ['k', validate_color], - 'boxplot.capprops.linewidth': [1.0, validate_float], + 'boxplot.capprops.linewidth': [1.0, _validate_linewidth], 'boxplot.capprops.linestyle': ['-', _validate_linestyle], 'boxplot.medianprops.color': ['C1', validate_color], - 'boxplot.medianprops.linewidth': [1.0, validate_float], + 'boxplot.medianprops.linewidth': [1.0, _validate_linewidth], 'boxplot.medianprops.linestyle': ['-', _validate_linestyle], 'boxplot.meanprops.color': ['C2', validate_color], 'boxplot.meanprops.marker': ['^', validate_string], 'boxplot.meanprops.markerfacecolor': ['C2', validate_color], 'boxplot.meanprops.markeredgecolor': ['C2', validate_color], - 'boxplot.meanprops.markersize': [6, validate_float], + 'boxplot.meanprops.markersize': [6, _validate_markersize], 'boxplot.meanprops.linestyle': ['--', _validate_linestyle], - 'boxplot.meanprops.linewidth': [1.0, validate_float], + 'boxplot.meanprops.linewidth': [1.0, _validate_linewidth], ## font props 'font.family': [['sans-serif'], validate_stringlist], # used by text object @@ -1107,7 +1122,7 @@ def _validate_linestyle(ls): 'axes.hold': [None, deprecate_axes_hold], 'axes.facecolor': ['w', validate_color], # background color; white 'axes.edgecolor': ['k', validate_color], # edge color; black - 'axes.linewidth': [0.8, validate_float], # edge linewidth + 'axes.linewidth': [0.8, _validate_linewidth], # edge linewidth 'axes.spines.left': [True, validate_bool], # Set visibility of axes 'axes.spines.right': [True, validate_bool], # 'spines', the lines @@ -1216,8 +1231,8 @@ def _validate_linestyle(ls): 'xtick.bottom': [True, validate_bool], # draw ticks on the bottom side 'xtick.major.size': [3.5, validate_float], # major xtick size in points 'xtick.minor.size': [2, validate_float], # minor xtick size in points - 'xtick.major.width': [0.8, validate_float], # major xtick width in points - 'xtick.minor.width': [0.6, validate_float], # minor xtick width in points + 'xtick.major.width': [0.8, _validate_linewidth], # major xtick width in points + 'xtick.minor.width': [0.6, _validate_linewidth], # minor xtick width in points 'xtick.major.pad': [3.5, validate_float], # distance to label in points 'xtick.minor.pad': [3.4, validate_float], # distance to label in points 'xtick.color': ['k', validate_color], # color of the xtick labels @@ -1236,8 +1251,8 @@ def _validate_linestyle(ls): 'ytick.right': [False, validate_bool], # draw ticks on the right side 'ytick.major.size': [3.5, validate_float], # major ytick size in points 'ytick.minor.size': [2, validate_float], # minor ytick size in points - 'ytick.major.width': [0.8, validate_float], # major ytick width in points - 'ytick.minor.width': [0.6, validate_float], # minor ytick width in points + 'ytick.major.width': [0.8, _validate_linewidth], # major ytick width in points + 'ytick.minor.width': [0.6, _validate_linewidth], # minor ytick width in points 'ytick.major.pad': [3.5, validate_float], # distance to label in points 'ytick.minor.pad': [3.4, validate_float], # distance to label in points 'ytick.color': ['k', validate_color], # color of the ytick labels @@ -1255,7 +1270,7 @@ def _validate_linestyle(ls): 'grid.color': ['#b0b0b0', validate_color], # grid color 'grid.linestyle': ['-', _validate_linestyle], # solid - 'grid.linewidth': [0.8, validate_float], # in points + 'grid.linewidth': [0.8, _validate_linewidth], # in points 'grid.alpha': [1.0, validate_float], From 77975e4cf989b902020030d2985f4a15df9f405f Mon Sep 17 00:00:00 2001 From: Fabian Kloosterman Date: Sun, 14 Jan 2018 01:29:57 +0100 Subject: [PATCH 2/4] Added test for setting relative line width, marker edge width and marker size. --- lib/matplotlib/tests/test_lines.py | 46 ++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/lib/matplotlib/tests/test_lines.py b/lib/matplotlib/tests/test_lines.py index e0b6258e9ba5..6483b5dbc3b4 100644 --- a/lib/matplotlib/tests/test_lines.py +++ b/lib/matplotlib/tests/test_lines.py @@ -184,3 +184,49 @@ def test_nan_is_sorted(): assert line._is_sorted(np.array([1, 2, 3])) assert line._is_sorted(np.array([1, np.nan, 3])) assert not line._is_sorted([3, 5] + [np.nan] * 100 + [0, 2]) + +def test_relative_sizes(): + line = mlines.Line2D([0,1],[0,1]) + + base = matplotlib.rcParams['lines.linewidth'] + + # use default line with from rcParams + line.set_linewidth(None) + assert line.get_linewidth() == base + + line.set_linewidth('auto') + assert line.get_linewidth() == base + + # set qualitative relative widths + for w in ('xx-thin', 'x-thin', 'thin', 'thinner', + 'medium', 'thick', 'thicker', 'x-thick', 'xx-thick'): + line.set_linewidth(w) + assert line.get_linewidth() == mlines.linewidth_scaling[w]*base + + line.set_markeredgewidth(w) + assert line.get_markeredgewidth() == mlines.linewidth_scaling[w]*base + + base = matplotlib.rcParams['lines.markersize'] + + # use default marker size + line.set_markersize(None) + assert line.get_markersize() == base + + line.set_markersize('auto') + assert line.get_markersize() == base + + # set qualitative relative size + for sz in ('xx-small', 'x-small', 'small', 'smaller', + 'medium', 'large', 'larger', 'x-large', 'xx-large'): + line.set_markersize(sz) + assert line.get_markersize() == mlines.markersize_scaling[sz]*base + + + with pytest.raises(ValueError): + line.set_linewidth('large') + + with pytest.raises(ValueError): + line.set_markeredgewidth('six') + + with pytest.raises(ValueError): + line.set_markersize('tall') From f229a8180aa531cc6d02a9b6a5bf746c80b31b40 Mon Sep 17 00:00:00 2001 From: Fabian Kloosterman Date: Sun, 14 Jan 2018 01:30:44 +0100 Subject: [PATCH 3/4] Separate default and reference value when performing size conversion. --- lib/matplotlib/lines.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index fd2c35e6474c..7b711babd96f 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -73,16 +73,17 @@ def _scale_dashes(offset, dashes, lw): return scaled_offset, scaled_dashes -def _convert2points(val, default, lut): +def _convert2points(val, default, reference, lut): """ - Convert relative size to points, using `default` as the base value and - `lut` as the look-up table for qualitative size names. + Convert relative size to points, using `reference` as the base value and + `lut` as the look-up table for qualitative size names. If `val` is None or 'auto', + then the `default` is returned """ - if val is None: + if val is None or val=='auto': return default try: - return default*lut[val] + return reference*lut[val] except KeyError: pts = float(val) # do not use relative fraction as string, because when loading styles from file @@ -99,7 +100,7 @@ def _build_qualitative_scaling(labels, comparative=None, base=1.2): else: ca, cb = comparative - d = {None: 1., 'medium': 1., ca:base**-1, cb:base} + d = {'medium': 1., ca:base**-1, cb:base} for k,m in enumerate(('', 'x-', 'xx-')): d['{}{}'.format(m,a)] = base**(-k-1) d['{}{}'.format(m,b)] = base**(k+1) @@ -117,8 +118,9 @@ def linewidth2points(w, default=None): or a string representing a relative qualitative width (e.g. 'x-thin') """ # the default value may be relative as well! (e.g. for markeredgewidth) - default = _convert2points(default, rcParams['lines.linewidth'], linewidth_scaling) - return _convert2points(w, default, linewidth_scaling) + default = _convert2points(default, rcParams['lines.linewidth'], + rcParams['lines.linewidth'], linewidth_scaling) + return _convert2points(w, default, rcParams['lines.linewidth'], linewidth_scaling) def markersize2points(s, default=None): """ @@ -127,8 +129,9 @@ def markersize2points(s, default=None): a string representing a fraction of the default size in rcParams or a string representing a relative qualitative size (e.g. 'x-large') """ - default = _convert2points(default, rcParams['lines.markersize'], markersize_scaling) - return _convert2points(s, default, markersize_scaling) + default = _convert2points(default, rcParams['lines.markersize'], + rcParams['lines.markersize'], markersize_scaling) + return _convert2points(s, default, rcParams['lines.markersize'], markersize_scaling) def segment_hits(cx, cy, x, y, radius): From f80c21e08bbba7b7e6907a6e557c210458acedf0 Mon Sep 17 00:00:00 2001 From: Fabian Kloosterman Date: Sun, 14 Jan 2018 16:50:50 +0100 Subject: [PATCH 4/4] Added support for '0.5x' and '120%' style relative widths/sizes Improved validators in rcsetup.py rcsetup.cycler now uses new line width/marker size validators Updated tests --- lib/matplotlib/cbook/__init__.py | 21 ++++++ lib/matplotlib/collections.py | 4 +- lib/matplotlib/lines.py | 97 +++++++++++++++++---------- lib/matplotlib/patches.py | 17 +++-- lib/matplotlib/rcsetup.py | 57 +++++++++++----- lib/matplotlib/tests/test_lines.py | 104 +++++++++++++++++++---------- 6 files changed, 200 insertions(+), 100 deletions(-) diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index 61e66f317fe8..2d6015e42c2b 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -2272,6 +2272,27 @@ def pts_to_midstep(x, *args): 'steps-mid': pts_to_midstep} +MEASUREMENT = re.compile( + r'''( # group match like scanf() token %e, %E, %f, %g + [-+]? # +/- or nothing for positive + (\d+(\.\d*)?|\.\d+) # match numbers: 1, 1., 1.1, .1 + ([eE][-+]?\d+)? # scientific notation: e(+/-)2 (*10^2) + ) + (\s*) # separator: white space or nothing + ( # unit of measure: like GB. also works for no units + \S*)''', re.VERBOSE) + + +def parse_measurement(value_sep_units): + measurement = re.match(MEASUREMENT, value_sep_units) + if measurement is None: + raise ValueError("Not a valid measurement value:" + " {!r}".format(value_sep_units)) + value = float(measurement.groups()[0]) + units = measurement.groups()[5] + return value, units + + def index_of(y): """ A helper function to get the index of an input to plot diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py index cd0a0405a675..65598520fc02 100644 --- a/lib/matplotlib/collections.py +++ b/lib/matplotlib/collections.py @@ -504,7 +504,8 @@ def set_linewidth(self, lw): self._us_lw = np.atleast_1d(np.asarray(lw)) # convert line widths to points - self._us_lw = np.array([mlines.linewidth2points(x) for x in self._us_lw]) + self._us_lw = np.array([mlines.linewidth2points(x) + for x in self._us_lw]) # scale all of the dash patterns. self._linewidths, self._linestyles = self._bcast_lwls( @@ -842,6 +843,7 @@ def update_from(self, other): # self.update_dict = other.update_dict # do we need to copy this? -JJL self.stale = True + # these are not available for the object inspector until after the # class is built so we define an initial set here for the init # function and they will be overridden after object defn diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 7b711babd96f..5f9cdf38b899 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -17,7 +17,7 @@ from .artist import Artist, allow_rasterization from .cbook import ( _to_unmasked_float_array, iterable, is_numlike, ls_mapper, ls_mapper_r, - STEP_LOOKUP_MAP) + STEP_LOOKUP_MAP, parse_measurement) from .markers import MarkerStyle from .path import Path from .transforms import Bbox, TransformedPath, IdentityTransform @@ -73,24 +73,30 @@ def _scale_dashes(offset, dashes, lw): return scaled_offset, scaled_dashes -def _convert2points(val, default, reference, lut): +def _convert2points(val, reference, lut): """ Convert relative size to points, using `reference` as the base value and - `lut` as the look-up table for qualitative size names. If `val` is None or 'auto', - then the `default` is returned + `lut` as the look-up table for qualitative size names. If `val` is None or + 'auto', then the `default` is returned. """ - if val is None or val=='auto': - return default try: - return reference*lut[val] + pts = reference*lut[val] except KeyError: - pts = float(val) - # do not use relative fraction as string, because when loading styles from file - # there is no way to distinguish between a proper float and its string equivalent - #if isinstance(val, six.string_types): - # pts *= default - return pts + try: + pts = float(val) + except (ValueError, TypeError): + pts, u = parse_measurement(val) + if u not in ['x', '%']: + raise ValueError('Unrecognized relative size value ' + '{!r}'.format(val)) + if u == '%': + pts /= 100. + + pts *= reference + + return pts + def _build_qualitative_scaling(labels, comparative=None, base=1.2): @@ -100,27 +106,30 @@ def _build_qualitative_scaling(labels, comparative=None, base=1.2): else: ca, cb = comparative - d = {'medium': 1., ca:base**-1, cb:base} - for k,m in enumerate(('', 'x-', 'xx-')): - d['{}{}'.format(m,a)] = base**(-k-1) - d['{}{}'.format(m,b)] = base**(k+1) + d = {'medium': 1., ca: base**-1, cb: base} + for k, m in enumerate(('', 'x-', 'xx-')): + d['{}{}'.format(m, a)] = base**(-k-1) + d['{}{}'.format(m, b)] = base**(k+1) return d -linewidth_scaling = _build_qualitative_scaling(('thin', 'thick'), ('thinner', 'thicker'), 1.5) -markersize_scaling = _build_qualitative_scaling(('small', 'large'), ('smaller', 'larger'), 1.5) -def linewidth2points(w, default=None): +linewidth_scaling = _build_qualitative_scaling( + ('thin', 'thick'), ('thinner', 'thicker'), 1.5) +markersize_scaling = _build_qualitative_scaling( + ('small', 'large'), ('smaller', 'larger'), 1.5) + + +def linewidth2points(w): """ Convert a line width specification to points. Line width can be either specified as float (absolute width in points), a string representing a fraction of the default width in rcParams or a string representing a relative qualitative width (e.g. 'x-thin') """ - # the default value may be relative as well! (e.g. for markeredgewidth) - default = _convert2points(default, rcParams['lines.linewidth'], - rcParams['lines.linewidth'], linewidth_scaling) - return _convert2points(w, default, rcParams['lines.linewidth'], linewidth_scaling) + reference = rcParams['lines.linewidth'] + return _convert2points(w, reference, linewidth_scaling) + def markersize2points(s, default=None): """ @@ -129,9 +138,8 @@ def markersize2points(s, default=None): a string representing a fraction of the default size in rcParams or a string representing a relative qualitative size (e.g. 'x-large') """ - default = _convert2points(default, rcParams['lines.markersize'], - rcParams['lines.markersize'], markersize_scaling) - return _convert2points(s, default, rcParams['lines.markersize'], markersize_scaling) + reference = rcParams['lines.markersize'] + return _convert2points(s, reference, markersize_scaling) def segment_hits(cx, cy, x, y, radius): @@ -1069,11 +1077,16 @@ def set_drawstyle(self, drawstyle): def set_linewidth(self, w): """ - Set the line width, either absolute width in points or width relative to rc default. + Set the line width, either absolute width in points or + width relative to rc default. - ACCEPTS: [float value in points | fraction as string | None | 'xx-thin' | 'x-thin' | - 'thin' | 'thinner' | 'medium' | 'thicker' | 'thick' | 'x-thick' | 'xx-thick'] + ACCEPTS: [float value in points | fraction as string | None | + 'xx-thin' | 'x-thin' | 'thin' | 'thinner' | 'medium' | + 'thicker' | 'thick' | 'x-thick' | 'xx-thick'] """ + if w is None: + w = rcParams['lines.linewidth'] + w = linewidth2points(w) if self._linewidth != w: @@ -1219,12 +1232,16 @@ def set_markeredgecolor(self, ec): def set_markeredgewidth(self, ew): """ Set the marker edge width, either absolute width in points or - width relative to rc default. + width relative to lines.linewidth rc default. - ACCEPTS: [float value in points | fraction as string | None | 'xx-thin' | 'x-thin' | - 'thin' | 'thinner' | 'medium' | 'thicker' | 'thick' | 'x-thick' | 'xx-thick'] + ACCEPTS: [float value in points | fraction as string | None | + 'xx-thin' | 'x-thin' | 'thin' | 'thinner' | 'medium' | + 'thicker' | 'thick' | 'x-thick' | 'xx-thick'] """ - ew = linewidth2points(ew, rcParams['lines.markeredgewidth']) + if ew is None: + ew = rcParams['lines.markeredgewidth'] + + ew = linewidth2points(ew) if self._markeredgewidth != ew: self.stale = True @@ -1256,11 +1273,17 @@ def set_markerfacecoloralt(self, fc): def set_markersize(self, sz): """ - Set the line width, either absolute width in points or width relative to rc default. + Set the line width, either absolute width in points or + width relative to rc default. - ACCEPTS: [float value in points | fraction as string | None | 'xx-small' | 'x-small' | - 'small' | 'smaller' | 'medium' | 'larger' | 'large' | 'x-large' | 'xx-large'] + ACCEPTS: [float value in points | fraction as string | None | + 'xx-small' | 'x-small' | 'small' | 'smaller' | + 'medium' | 'larger' | 'large' | 'x-large' | + 'xx-large'] """ + if sz is None: + sz = rcParams['lines.markersize'] + sz = markersize2points(sz) if self._markersize != sz: diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 0d72f7ab6c6e..2f82a5605f56 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -351,17 +351,20 @@ def set_alpha(self, alpha): def set_linewidth(self, w): """ - Set the path line width, either absolute width in points or width relative to rc default. + Set the path line width, either absolute width in points or + width relative to lines.linewidth rc default. - ACCEPTS: [float value in points | fraction as string | None | 'xx-thin' | 'x-thin' | - 'thin' | 'thinner' | 'medium' | 'thicker' | 'thick' | 'x-thick' | 'xx-thick'] + ACCEPTS: [float value in points | fraction as string | None | + 'xx-thin' | 'x-thin' | 'thin' | 'thinner' | 'medium' | + 'thicker' | 'thick' | 'x-thick' | 'xx-thick'] """ - default = mpl.rcParams['patch.linewidth'] - if default is None: - default = mpl.rcParams['axes.linewidth'] + if w is None: + w = mpl.rcParams['patch.linewidth'] + if w is None: + w = mpl.rcParams['axes.linewidth'] - self._linewidth = mlines.linewidth2points(w, default) + self._linewidth = mlines.linewidth2points(w) # scale the dash pattern by the linewidth offset, ls = self._us_dashes diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 924bb31b3281..23e029f831c0 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -675,10 +675,46 @@ def validate_hatch(s): validate_hatchlist = _listify_validator(validate_hatch) validate_dashlist = _listify_validator(validate_nseq_float(allow_none=True)) + +def _validate_linewidth(w): + try: + w = float(w) + except (ValueError, TypeError): + if w in ['xx-thin', 'x-thin', 'thin', 'thinner', 'medium', 'thick', + 'thicker', 'x-thick', 'xx-thick']: + return w + else: + val, u = cbook.parse_measurement(w) + if u not in ['x', '%']: + raise ValueError("value {!r} is not a valid absolute" + "or relative width.".format(w)) + + return w + + +def _validate_markersize(sz): + try: + sz = float(sz) + except (ValueError, TypeError): + if sz in ['xx-small', 'x-small', 'small', 'smaller', 'medium', + 'large', 'larger', 'x-large', 'xx-large']: + return sz + else: + val, u = cbook.parse_measurement(sz) + if u not in ['x', '%']: + raise ValueError("value {!r} is not a valid absolute" + "or relative size.".format(sz)) + + return sz + + +validate_linewidthlist = _listify_validator(_validate_linewidth) +validate_markersizelist = _listify_validator(_validate_markersize) + _prop_validators = { 'color': _listify_validator(validate_color_for_prop_cycle, allow_stringlist=True), - 'linewidth': validate_floatlist, + 'linewidth': validate_linewidthlist, 'linestyle': validate_stringlist, 'facecolor': validate_colorlist, 'edgecolor': validate_colorlist, @@ -686,8 +722,8 @@ def validate_hatch(s): 'capstyle': validate_capstylelist, 'fillstyle': validate_fillstylelist, 'markerfacecolor': validate_colorlist, - 'markersize': validate_floatlist, - 'markeredgewidth': validate_floatlist, + 'markersize': validate_markersizelist, + 'markeredgewidth': validate_linewidthlist, 'markeredgecolor': validate_colorlist, 'alpha': validate_floatlist, 'marker': validate_stringlist, @@ -931,21 +967,6 @@ def _validate_linestyle(ls): raise ValueError("linestyle {!r} is not a valid on-off ink " "sequence.".format(ls)) -def _validate_linewidth(w): - try: - w=float(w) - except (ValueError, TypeError): - if w not in ['xx-thin','x-thin','thin','thinner','medium','thick','thicker','x-thick','xx-thick',None]: - raise ValueError("width {!r} is not a valid float or string.".format(w)) - return w - -def _validate_markersize(sz): - try: - sz=float(sz) - except (ValueError, TypeError): - if sz not in ['xx-small','x-small','small','smaller','medium','large','larger','x-large','xx-large',None]: - raise ValueError("marker size {!r} is not a valid float or string.".format(w)) - return sz # a map from key -> value, converter defaultParams = { diff --git a/lib/matplotlib/tests/test_lines.py b/lib/matplotlib/tests/test_lines.py index 6483b5dbc3b4..da4e4c689fa6 100644 --- a/lib/matplotlib/tests/test_lines.py +++ b/lib/matplotlib/tests/test_lines.py @@ -185,48 +185,78 @@ def test_nan_is_sorted(): assert line._is_sorted(np.array([1, np.nan, 3])) assert not line._is_sorted([3, 5] + [np.nan] * 100 + [0, 2]) -def test_relative_sizes(): - line = mlines.Line2D([0,1],[0,1]) - - base = matplotlib.rcParams['lines.linewidth'] - - # use default line with from rcParams - line.set_linewidth(None) - assert line.get_linewidth() == base - - line.set_linewidth('auto') - assert line.get_linewidth() == base - - # set qualitative relative widths - for w in ('xx-thin', 'x-thin', 'thin', 'thinner', - 'medium', 'thick', 'thicker', 'x-thick', 'xx-thick'): - line.set_linewidth(w) - assert line.get_linewidth() == mlines.linewidth_scaling[w]*base - - line.set_markeredgewidth(w) - assert line.get_markeredgewidth() == mlines.linewidth_scaling[w]*base - - base = matplotlib.rcParams['lines.markersize'] - # use default marker size - line.set_markersize(None) - assert line.get_markersize() == base - - line.set_markersize('auto') - assert line.get_markersize() == base +def test_relative_sizes(): + line = mlines.Line2D([0, 1], [0, 1]) + + reference = 1. + with matplotlib.rc_context( + rc={'lines.linewidth': reference, + 'lines.markersize': reference, + 'lines.markeredgewidth': reference}): + + # use default line with from rcParams + line.set(linewidth=None, markeredgewidth=None, markersize=None) + assert line.get_linewidth() == reference + assert line.get_markeredgewidth() == reference + assert line.get_markersize() == reference + + line.set(linewidth='2x', markeredgewidth='0.5x', markersize='2e1x') + assert line.get_linewidth() == 2*reference + assert line.get_markeredgewidth() == 0.5*reference + assert line.get_markersize() == 2e1*reference + + line.set(linewidth='50%', markeredgewidth='2e2 %', markersize='250%') + assert line.get_linewidth() == 0.5*reference + assert line.get_markeredgewidth() == 2*reference + assert line.get_markersize() == 2.5*reference + + # set qualitative relative widths + for w in ('xx-thin', 'x-thin', 'thin', 'thinner', + 'medium', 'thick', 'thicker', 'x-thick', 'xx-thick'): + line.set(linewidth=w, markeredgewidth=w) + assert (line.get_linewidth() == + mlines.linewidth_scaling[w]*reference) + assert (line.get_markeredgewidth() == + mlines.linewidth_scaling[w]*reference) + + # set qualitative relative size + for sz in ('xx-small', 'x-small', 'small', 'smaller', + 'medium', 'large', 'larger', 'x-large', 'xx-large'): + line.set_markersize(sz) + assert (line.get_markersize() == + mlines.markersize_scaling[sz]*reference) + + with pytest.raises(ValueError): + line.set_linewidth('large') + + with pytest.raises(ValueError): + line.set_markeredgewidth('six') + + with pytest.raises(ValueError): + line.set_markersize('tall') + + +def test_relative_sizes_rc(): + + # only absolute width for lines.linewidth + matplotlib.rcParams['lines.linewidth'] = 1. + matplotlib.rcParams['lines.markersize'] = 1. - # set qualitative relative size - for sz in ('xx-small', 'x-small', 'small', 'smaller', - 'medium', 'large', 'larger', 'x-large', 'xx-large'): - line.set_markersize(sz) - assert line.get_markersize() == mlines.markersize_scaling[sz]*base + with pytest.raises(ValueError): + matplotlib.rcParams['lines.linewidth'] = '2x' - with pytest.raises(ValueError): - line.set_linewidth('large') + matplotlib.rcParams['lines.markersize'] = '100%' + + # relative width/size + matplotlib.rcParams['lines.markeredgewidth'] = '0.5x' + matplotlib.rcParams['xtick.major.width'] = '2x' + matplotlib.rcParams['ytick.minor.width'] = '50%' with pytest.raises(ValueError): - line.set_markeredgewidth('six') + matplotlib.rcParams['xtick.minor.width'] = 'big' with pytest.raises(ValueError): - line.set_markersize('tall') + matplotlib.rcParams['ytick.major.width'] = 'microscopic' + pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy