Skip to content

Commit

Permalink
FIX: do dash scaling early
Browse files Browse the repository at this point in the history
When scaling the dash pattern by the linewidth do the scaling at artist
creation / value set time rather than at draw time.

closes #6592
closes #6588
closes #6590
Closes #6693
closes #5430
  • Loading branch information
tacaswell committed Jul 12, 2016
1 parent b23460b commit 3ef3f5b
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 81 deletions.
14 changes: 1 addition & 13 deletions lib/matplotlib/backend_bases.py
Expand Up @@ -871,16 +871,7 @@ def get_dashes(self):
Default value is None
"""
if rcParams['_internal.classic_mode']:
return self._dashes
else:
scale = max(1.0, self.get_linewidth())
offset, dashes = self._dashes
if offset is not None:
offset = offset * scale
if dashes is not None:
dashes = [x * scale for x in dashes]
return offset, dashes
return self._dashes

def get_forced_alpha(self):
"""
Expand Down Expand Up @@ -1062,10 +1053,7 @@ def set_linestyle(self, style):
`lines.dotted_pattern`. One may also specify customized dash
styles by providing a tuple of (offset, dash pairs).
"""
offset, dashes = lines.get_dash_pattern(style)

self._linestyle = style
self.set_dashes(offset, dashes)

def set_url(self, url):
"""
Expand Down
99 changes: 75 additions & 24 deletions lib/matplotlib/collections.py
Expand Up @@ -13,9 +13,16 @@

import six
from six.moves import zip
try:
from math import gcd
except ImportError:
# LPy workaround
from fractions import gcd
import warnings

import numpy as np
import numpy.ma as ma

import matplotlib as mpl
import matplotlib.cbook as cbook
import matplotlib.colors as mcolors
Expand All @@ -24,13 +31,11 @@
import matplotlib.transforms as transforms
import matplotlib.artist as artist
from matplotlib.artist import allow_rasterization
import matplotlib.backend_bases as backend_bases
import matplotlib.path as mpath
from matplotlib import _path
import matplotlib.mlab as mlab
import matplotlib.lines as mlines


CIRCLE_AREA_FACTOR = 1.0 / np.sqrt(np.pi)


Expand Down Expand Up @@ -117,6 +122,14 @@ def __init__(self,
"""
artist.Artist.__init__(self)
cm.ScalarMappable.__init__(self, norm, cmap)
# list of un-scaled dash patterns
# this is needed scaling the dash pattern by linewidth
self._us_linestyles = [(None, None)]
# list of dash patterns
self._linestyles = [(None, None)]
# list of unbroadcast/scaled linewidths
self._us_lw = [0]
self._linewidths = [0]

self.set_edgecolor(edgecolors)
self.set_facecolor(facecolors)
Expand Down Expand Up @@ -313,7 +326,7 @@ def draw(self, renderer):
if do_single_path_optimization:
gc.set_foreground(tuple(edgecolors[0]))
gc.set_linewidth(self._linewidths[0])
gc.set_linestyle(self._linestyles[0])
gc.set_dashes(*self._linestyles[0])
gc.set_antialiased(self._antialiaseds[0])
gc.set_url(self._urls[0])
renderer.draw_markers(
Expand Down Expand Up @@ -489,8 +502,12 @@ def set_linewidth(self, lw):
lw = mpl.rcParams['lines.linewidth']
else:
lw = 0
# get the un-scaled/broadcast lw
self._us_lw = self._get_value(lw)

self._linewidths = self._get_value(lw)
# scale all of the dash patterns.
self._linewidths, self._linestyles = self._bcast_lwls(
self._us_lw, self._us_linestyles)
self.stale = True

def set_linewidths(self, lw):
Expand Down Expand Up @@ -534,29 +551,63 @@ def set_linestyle(self, ls):
try:
if cbook.is_string_like(ls) and cbook.is_hashable(ls):
ls = cbook.ls_mapper.get(ls, ls)
dashes = [mlines.get_dash_pattern(ls)]
elif cbook.iterable(ls):
dashes = [mlines._get_dash_pattern(ls)]
else:
try:
dashes = []
for x in ls:
if cbook.is_string_like(x):
x = cbook.ls_mapper.get(x, x)
dashes.append(mlines.get_dash_pattern(x))
elif cbook.iterable(x) and len(x) == 2:
dashes.append(x)
else:
raise ValueError()
dashes = [mlines._get_dash_pattern(ls)]
except ValueError:
if len(ls) == 2:
dashes = [ls]
else:
raise ValueError()
else:
raise ValueError()
dashes = [mlines._get_dash_pattern(x) for x in ls]

except ValueError:
raise ValueError('Do not know how to convert %s to dashes' % ls)
self._linestyles = dashes
self.stale = True
raise ValueError(
'Do not know how to convert {!r} to dashes'.format(ls))

# get the list of raw 'unscaled' dash patterns
self._us_linestyles = dashes

# broadcast and scale the lw and dash patterns
self._linewidths, self._linestyles = self._bcast_lwls(
self._us_lw, self._us_linestyles)

@staticmethod
def _bcast_lwls(linewidths, dashes):
'''Internal helper function to broadcast + scale ls/lw
In the collection drawing code the linewidth and linestyle are
cycled through as circular buffers (via v[i % len(v)]). Thus,
if we are going to scale the dash pattern at set time (not
draw time) we need to do the broadcasting now and expand both
lists to be the same length.
Parameters
----------
linewidths : list
line widths of collection
dashes : list
dash specification (offset, (dash pattern tuple))
Returns
-------
linewidths, dashes : list
Will be the same length, dashes are scaled by paired linewidth
'''
if mpl.rcParams['_internal.classic_mode']:
return linewidths, dashes
# make sure they are the same length so we can zip them
if len(dashes) != len(linewidths):
l_dashes = len(dashes)
l_lw = len(linewidths)
GCD = gcd(l_dashes, l_lw)
dashes = list(dashes) * (l_lw // GCD)
linewidths = list(linewidths) * (l_dashes // GCD)

# scale the dash patters
dashes = [mlines._scale_dashes(o, d, lw)
for (o, d), lw in zip(dashes, linewidths)]

return linewidths, dashes

def set_linestyles(self, ls):
"""alias for set_linestyle"""
Expand Down
110 changes: 71 additions & 39 deletions lib/matplotlib/lines.py
Expand Up @@ -13,11 +13,11 @@

import numpy as np
from numpy import ma
from matplotlib import verbose
from . import artist, colors as mcolors
from .artist import Artist
from .cbook import (iterable, is_string_like, is_numlike, ls_mapper_r,
pts_to_prestep, pts_to_poststep, pts_to_midstep)
pts_to_prestep, pts_to_poststep, pts_to_midstep, ls_mapper,
is_hashable)

from .path import Path
from .transforms import Bbox, TransformedPath, IdentityTransform
Expand All @@ -36,24 +36,49 @@
from matplotlib import _path


def get_dash_pattern(style):
"""
Given a dash pattern name from 'solid', 'dashed', 'dashdot' or
'dotted', returns the (offset, dashes) pattern.
def _get_dash_pattern(style):
"""Convert linestyle -> dash pattern
"""
if style == 'solid':
# go from short hand -> full strings
if is_string_like(style) and is_hashable(style):
style = ls_mapper.get(style, style)
# un-dashed styles
if style in ['solid', 'None']:
offset, dashes = None, None
# dashed styles
elif style in ['dashed', 'dashdot', 'dotted']:
offset = 0
dashes = tuple(rcParams['lines.{}_pattern'.format(style)])
#
elif isinstance(style, tuple):
offset, dashes = style
else:
raise ValueError('Unrecognized linestyle: %s' % str(style))

# normalize offset to be positive and shorter than the dash cycle
if dashes is not None and offset is not None:
dsum = sum(dashes)
if dsum:
offset %= dsum

return offset, dashes


def _scale_dashes(offset, dashes, lw):
if rcParams['_internal.classic_mode']:
return offset, dashes
scale = max(1.0, lw)
scaled_offset = scaled_dashes = None
if offset is not None:
scaled_offset = offset * scale
if dashes is not None:
scaled_dashes = [x * scale if x is not None else None
for x in dashes]

return scaled_offset, scaled_dashes


def segment_hits(cx, cy, x, y, radius):
"""
Determine if any line segments are within radius of a
Expand Down Expand Up @@ -360,10 +385,15 @@ def __init__(self, xdata, ydata,

self._linestyles = None
self._drawstyle = None
self._linewidth = None
self._linewidth = linewidth

# scaled dash + offset
self._dashSeq = None
self._dashOffset = 0
# unscaled dash + offset
# this is needed scaling the dash pattern by linewidth
self._us_dashSeq = None
self._us_dashOffset = 0

self.set_linestyle(linestyle)
self.set_drawstyle(drawstyle)
Expand Down Expand Up @@ -1013,9 +1043,13 @@ def set_linewidth(self, w):
ACCEPTS: float value in points
"""
w = float(w)

if self._linewidth != w:
self.stale = True
self._linewidth = w
# rescale the dashes + offset
self._dashOffset, self._dashSeq = _scale_dashes(
self._us_dashOffset, self._us_dashSeq, self._linewidth)

def _split_drawstyle_linestyle(self, ls):
'''Split drawstyle from linestyle string
Expand Down Expand Up @@ -1093,31 +1127,31 @@ def set_linestyle(self, ls):
ls : { ``'-'``, ``'--'``, ``'-.'``, ``':'``} and more see description
The line style.
"""
if not is_string_like(ls):
if len(ls) != 2:
raise ValueError()

self.set_dashes(ls[1])
self._dashOffset = ls[0]
self._linestyle = "--"
return
ds, ls = self._split_drawstyle_linestyle(ls)
if ds is not None:
self.set_drawstyle(ds)

if ls in [' ', '', 'none']:
ls = 'None'

if ls not in self._lineStyles:
try:
ls = ls_mapper_r[ls]
except KeyError:
raise ValueError(("You passed in an invalid linestyle, "
"`{0}`. See "
"docs of Line2D.set_linestyle for "
"valid values.").format(ls))
if is_string_like(ls):
ds, ls = self._split_drawstyle_linestyle(ls)
if ds is not None:
self.set_drawstyle(ds)

if ls in [' ', '', 'none']:
ls = 'None'

if ls not in self._lineStyles:
try:
ls = ls_mapper_r[ls]
except KeyError:
raise ValueError(("You passed in an invalid linestyle, "
"`{0}`. See "
"docs of Line2D.set_linestyle for "
"valid values.").format(ls))
self._linestyle = ls
else:
self._linestyle = '--'

self._linestyle = ls
# get the unscaled dashes
self._us_dashOffset, self._us_dashSeq = _get_dash_pattern(ls)
# compute the linewidth scaled dashes
self._dashOffset, self._dashSeq = _scale_dashes(
self._us_dashOffset, self._us_dashSeq, self._linewidth)

@docstring.dedent_interpd
def set_marker(self, marker):
Expand Down Expand Up @@ -1227,10 +1261,7 @@ def set_dashes(self, seq):
if seq == (None, None) or len(seq) == 0:
self.set_linestyle('-')
else:
self.set_linestyle('--')
if self._dashSeq != seq:
self.stale = True
self._dashSeq = seq # TODO: offset ignored for now
self.set_linestyle((0, seq))

def _draw_lines(self, renderer, gc, path, trans):
self._lineFunc(renderer, gc, path, trans)
Expand Down Expand Up @@ -1258,21 +1289,22 @@ def _draw_steps_mid(self, renderer, gc, path, trans):

def _draw_solid(self, renderer, gc, path, trans):
gc.set_linestyle('solid')
gc.set_dashes(self._dashOffset, self._dashSeq)
renderer.draw_path(gc, path, trans)

def _draw_dashed(self, renderer, gc, path, trans):
gc.set_linestyle('dashed')
if self._dashSeq is not None:
gc.set_dashes(self._dashOffset, self._dashSeq)

gc.set_dashes(self._dashOffset, self._dashSeq)
renderer.draw_path(gc, path, trans)

def _draw_dash_dot(self, renderer, gc, path, trans):
gc.set_linestyle('dashdot')
gc.set_dashes(self._dashOffset, self._dashSeq)
renderer.draw_path(gc, path, trans)

def _draw_dotted(self, renderer, gc, path, trans):
gc.set_linestyle('dotted')
gc.set_dashes(self._dashOffset, self._dashSeq)
renderer.draw_path(gc, path, trans)

def update_from(self, other):
Expand Down

0 comments on commit 3ef3f5b

Please sign in to comment.