Skip to content

relative line widths, marker sizes and marker edge widths #10244

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions lib/matplotlib/cbook/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion lib/matplotlib/collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -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']
Expand All @@ -503,6 +503,10 @@ 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)
Expand Down Expand Up @@ -839,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
Expand Down
107 changes: 98 additions & 9 deletions lib/matplotlib/lines.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -73,6 +73,75 @@ def _scale_dashes(offset, dashes, lw):
return scaled_offset, scaled_dashes


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.
"""

try:
pts = reference*lut[val]
except KeyError:
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):

a, b = labels
if comparative is None:
ca, cb = a+"er", b+"er"
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)

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):
"""
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')
"""
reference = rcParams['lines.linewidth']
return _convert2points(w, reference, 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')
"""
reference = rcParams['lines.markersize']
return _convert2points(s, reference, markersize_scaling)


def segment_hits(cx, cy, x, y, radius):
"""
Determine if any line segments are within radius of a
Expand Down Expand Up @@ -1008,11 +1077,17 @@ 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)
if w is None:
w = rcParams['lines.linewidth']

w = linewidth2points(w)

if self._linewidth != w:
self.stale = True
Expand Down Expand Up @@ -1156,12 +1231,18 @@ 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 lines.linewidth 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)

if self._markeredgewidth != ew:
self.stale = True
self._markeredgewidth = ew
Expand Down Expand Up @@ -1192,11 +1273,19 @@ 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)
if sz is None:
sz = rcParams['lines.markersize']

sz = markersize2points(sz)

if self._markersize != sz:
self.stale = True
self._markersize = sz
Expand Down
11 changes: 8 additions & 3 deletions lib/matplotlib/patches.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,16 +351,21 @@ 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 lines.linewidth 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)
self._linewidth = mlines.linewidth2points(w)

# scale the dash pattern by the linewidth
offset, ls = self._us_dashes
self._dashoffset, self._dashes = mlines._scale_dashes(
Expand Down
74 changes: 55 additions & 19 deletions lib/matplotlib/rcsetup.py
Original file line number Diff line number Diff line change
Expand Up @@ -675,19 +675,55 @@ 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,
'joinstyle': validate_joinstylelist,
'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,
Expand Down Expand Up @@ -959,7 +995,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],
Expand All @@ -976,7 +1012,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
Expand Down Expand Up @@ -1005,33 +1041,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
Expand Down Expand Up @@ -1107,7 +1143,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
Expand Down Expand Up @@ -1216,8 +1252,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
Expand All @@ -1236,8 +1272,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
Expand All @@ -1255,7 +1291,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],


Expand Down
Loading