8000 Merge pull request #6710 from tacaswell/fix_refactor_dash_scaling · matplotlib/matplotlib@b8698ab · GitHub
[go: up one dir, main page]

Skip to content

Commit b8698ab

Browse files
authored
Merge pull request #6710 from tacaswell/fix_refactor_dash_scaling
FIX: refactor dash scaling
2 parents 1c08c16 + 3ef3f5b commit b8698ab

File tree

6 files changed

+179
-82
lines changed

6 files changed

+179
-82
lines changed

lib/matplotlib/backend_bases.py

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -871,16 +871,7 @@ def get_dashes(self):
871871
872872
Default value is None
873873
"""
874-
if rcParams['_internal.classic_mode']:
875-
return self._dashes
876-
else:
877-
scale = max(1.0, self.get_linewidth())
878-
offset, dashes = self._dashes
879-
if offset is not None:
880-
offset = offset * scale
881-
if dashes is not None:
882-
dashes = [x * scale for x in dashes]
883-
return offset, dashes
874+
return self._dashes
884875

885876
def get_forced_alpha(self):
886877
"""
@@ -1062,10 +1053,7 @@ def set_linestyle(self, style):
10621053
`lines.dotted_pattern`. One may also specify customized dash
10631054
styles by providing a tuple of (offset, dash pairs).
10641055
"""
1065-
offset, dashes = lines.get_dash_pattern(style)
1066-
10671056
self._linestyle = style
1068-
self.set_dashes(offset, dashes)
10691057

10701058
def set_url(self, url):
10711059
"""

lib/matplotlib/collections.py

Lines changed: 75 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,16 @@
1313

1414
import six
1515
from six.moves import zip
16+
try:
17+
from math import gcd
18+
except ImportError:
19+
# LPy workaround
20+
from fractions import gcd
1621
import warnings
22+
1723
import numpy as np
1824
import numpy.ma as ma
25+
1926
import matplotlib as mpl
2027
import matplotlib.cbook as cbook
2128
import matplotlib.colors as mcolors
@@ -24,13 +31,11 @@
2431
import matplotlib.transforms as transforms
2532
import matplotlib.artist as artist
2633
from matplotlib.artist import allow_rasterization
27-
import matplotlib.backend_bases as backend_bases
2834
import matplotlib.path as mpath
2935
from matplotlib import _path
3036
import matplotlib.mlab as mlab
3137
import matplotlib.lines as mlines
3238

33-
3439
CIRCLE_AREA_FACTOR = 1.0 / np.sqrt(np.pi)
3540

3641

@@ -117,6 +122,14 @@ def __init__(self,
117122
"""
118123
artist.Artist.__init__(self)
119124
cm.ScalarMappable.__init__(self, norm, cmap)
125+
# list of un-scaled dash patterns
126+
# this is needed scaling the dash pattern by linewidth
127+
self._us_linestyles = [(None, None)]
128+
# list of dash patterns
129+
self._linestyles = [(None, None)]
130+
# list of unbroadcast/scaled linewidths
131+
self._us_lw = [0]
132+
self._linewidths = [0]
120133

121134
self.set_edgecolor(edgecolors)
122135
self.set_facecolor(facecolors)
@@ -313,7 +326,7 @@ def draw(self, renderer):
313326
if do_single_path_optimization:
314327
gc.set_foreground(tuple(edgecolors[0]))
315328
gc.set_linewidth(self._linewidths[0])
316-
gc.set_linestyle(self._linestyles[0])
329+
gc.set_dashes(*self._linestyles[0])
317330
gc.set_antialiased(self._antialiaseds[0])
318331
gc.set_url(self._urls[0])
319332
renderer.draw_markers(
@@ -489,8 +502,12 @@ def set_linewidth(self, lw):
489502
lw = mpl.rcParams['lines.linewidth']
490503
else:
491504
lw = 0
505+
# get the un-scaled/broadcast lw
506+
self._us_lw = self._get_value(lw)
492507

493-
self._linewidths = self._get_value(lw)
508+
# scale all of the dash patterns.
509+
self._linewidths, self._linestyles = self._bcast_lwls(
510+
self._us_lw, self._us_linestyles)
494511
self.stale = True
495512

496513
def set_linewidths(self, lw):
@@ -534,29 +551,63 @@ def set_linestyle(self, ls):
534551
try:
535552
if cbook.is_string_like(ls) and cbook.is_hashable(ls):
536553
ls = cbook.ls_mapper.get(ls, ls)
537-
dashes = [mlines.get_dash_pattern(ls)]
538-
elif cbook.iterable(ls):
554+
dashes = [mlines._get_dash_pattern(ls)]
555+
else:
539556
try:
540-
dashes = []
541-
for x in ls:
542-
if cbook.is_string_like(x):
543-
x = cbook.ls_mapper.get(x, x)
544-
dashes.append(mlines.get_dash_pattern(x))
545-
elif cbook.iterable(x) and len(x) == 2:
546-
dashes.append(x)
547-
else:
548-
raise ValueError()
557+
dashes = [mlines._get_dash_pattern(ls)]
549558
except ValueError:
550-
if len(ls) == 2:
551-
dashes = [ls]
552-
else:
553-
raise ValueError()
554-
else:
555-
raise ValueError()
559+
dashes = [mlines._get_dash_pattern(x) for x in ls]
560+
556561
except ValueError:
557-
raise ValueError('Do not know how to convert %s to dashes' % ls)
558-
self._linestyles = dashes
559-
self.stale = True
562+
raise ValueError(
563+
'Do not know how to convert {!r} to dashes'.format(ls))
564+
565+
# get the list of raw 'unscaled' dash patterns
566+
self._us_linestyles = dashes
567+
568+
# broadcast and scale the lw and dash patterns
569+
self._linewidths, self._linestyles = self._bcast_lwls(
570+
self._us_lw, self._us_linestyles)
571+
572+
@staticmethod
573+
def _bcast_lwls(linewidths, dashes):
574+
'''Intern 10000 al helper function to broadcast + scale ls/lw
575+
576+
In the collection drawing code the linewidth and linestyle are
577+
cycled through as circular buffers (via v[i % len(v)]). Thus,
578+
if we are going to scale the dash pattern at set time (not
579+
draw time) we need to do the broadcasting now and expand both
580+
lists to be the same length.
581+
582+
Parameters
583+
----------
584+
linewidths : list
585+
line widths of collection
586+
587+
dashes : list
588+
dash specification (offset, (dash pattern tuple))
589+
590+
Returns
591+
-------
592+
linewidths, dashes : list
593+
Will be the same length, dashes are scaled by paired linewidth
594+
595+
'''
596+
if mpl.rcParams['_internal.classic_mode']:
597+
return linewidths, dashes
598+
# make sure they are the same length so we can zip them
599+
if len(dashes) != len(linewidths):
600+
l_dashes = len(dashes)
601+
l_lw = len(linewidths)
602+
GCD = gcd(l_dashes, l_lw)
603+
dashes = list(dashes) * (l_lw // GCD)
604+
linewidths = list(linewidths) * (l_dashes // GCD)
605+
606+
# scale the dash patters
607+
dashes = [mlines._scale_dashes(o, d, lw)
608+
for (o, d), lw in zip(dashes, linewidths)]
609+
610+
return linewidths, dashes
560611

561612
def set_linestyles(self, ls):
562613
"""alias for set_linestyle"""

lib/matplotlib/lines.py

Lines changed: 71 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@
1313

1414
import numpy as np
1515
from numpy import ma
16-
from matplotlib import verbose
1716
from . import artist, colors as mcolors
1817
from .artist import Artist
1918
from .cbook import (iterable, is_string_like, is_numlike, ls_mapper_r,
20-
pts_to_prestep, pts_to_poststep, pts_to_midstep)
19+
pts_to_prestep, pts_to_poststep, pts_to_midstep, ls_mapper,
20+
is_hashable)
2121

2222
from .path import Path
2323
from .transforms import Bbox, TransformedPath, IdentityTransform
@@ -36,24 +36,49 @@
3636
from matplotlib import _path
3737

3838

39-
def get_dash_pattern(style):
40-
"""
41-
Given a dash pattern name from 'solid', 'dashed', 'dashdot' or
42-
'dotted', returns the (offset, dashes) pattern.
39+
def _get_dash_pattern(style):
40+
"""Convert linestyle -> dash pattern
41+
4342
"""
44-
if style == 'solid':
43+
# go from short hand -> full strings
44+
if is_string_like(style) and is_hashable(style):
45+
style = ls_mapper.get(style, style)
46+
# un-dashed styles
47+
if style in ['solid', 'None']:
4548
offset, dashes = None, None
49+
# dashed styles
4650
elif style in ['dashed', 'dashdot', 'dotted']:
4751
offset = 0
4852
dashes = tuple(rcParams['lines.{}_pattern'.format(style)])
53+
#
4954
elif isinstance(style, tuple):
5055
offset, dashes = style
5156
else:
5257
raise ValueError('Unrecognized linestyle: %s' % str(style))
5358

59+
# normalize offset to be positive and shorter than the dash cycle
60+
if dashes is not None and offset is not None:
61+
dsum = sum(dashes)
62+
if dsum:
63+
offset %= dsum
64+
5465
return offset, dashes
5566

5667

68+
def _scale_dashes(offset, dashes, lw):
69+
if rcParams['_internal.classic_mode']:
70+
return offset, dashes
71+
scale = max(1.0, lw)
72+
scaled_offset = scaled_dashes = None
73+
if offset is not None:
74+
scaled_offset = offset * scale
75+
if dashes is not None:
76+
scaled_dashes = [x * scale if x is not None else None
77+
for x in dashes]
78+
79+
return scaled_offset, scaled_dashes
80+
81+
5782
def segment_hits(cx, cy, x, y, radius):
5883
"""
5984
Determine if any line segments are within radius of a
@@ -360,10 +385,15 @@ def __init__(self, xdata, ydata,
360385

361386
self._linestyles = None
362387
self._drawstyle = None
363-
self._linewidth = None
388+
self._linewidth = linewidth
364389

390+
# scaled dash + offset
365391
self._dashSeq = None
366392
self._dashOffset = 0
393+
# unscaled dash + offset
394+
# this is needed scaling the dash pattern by linewidth
395+
self._us_dashSeq = None
396+
self._us_dashOffset = 0
367397

368398
self.set_linestyle(linestyle)
369399
self.set_drawstyle(drawstyle)
@@ -1013,9 +1043,13 @@ def set_linewidth(self, w):
10131043
ACCEPTS: float value in points
10141044
"""
10151045
w = float(w)
1046+
10161047
if self._linewidth != w:
10171048
self.stale = True
10181049
self._linewidth = w
1050+
# rescale the dashes + offset
1051+
self._dashOffset, self._dashSeq = _scale_dashes(
1052+
self._us_dashOffset, self._us_dashSeq, self._linewidth)
10191053

10201054
def _split_drawstyle_linestyle(self, ls):
10211055
'''Split drawstyle from linestyle string
@@ -1093,31 +1127,31 @@ def set_linestyle(self, ls):
10931127
ls : { ``'-'``, ``'--'``, ``'-.'``, ``':'``} and more see description
10941128
The line style.
10951129
"""
1096-
if not is_string_like(ls):
1097-
if len(ls) != 2:
1098-
raise ValueError()
1099-
1100-
self.set_dashes(ls[1])
1101-
self._dashOffset = ls[0]
1102-
self._linestyle = "--"
1103-
return
1104-
ds, ls = self._split_drawstyle_linestyle(ls)
1105-
if ds is not None:
1106-
self.set_drawstyle(ds)
1107-
1108-
if ls in [' ', '', 'none']:
1109-
ls = 'None'
1110-
1111-
if ls not in self._lineStyles:
1112-
try:
1113-
ls = ls_mapper_r[ls]
1114-
except KeyError:
1115-
raise ValueError(("You passed in an invalid linestyle, "
1116-
"`{0}`. See "
1117-
"docs of Line2D.set_linestyle for "
1118-
"valid values.").format(ls))
1130+
if is_string_like(ls):
1131+
ds, ls = self._split_drawstyle_linestyle(ls)
1132+
if ds is not None:
1133+
self.set_drawstyle(ds)
1134+
1135+
if ls in [' ', '', 'none']:
1136+
ls = 'None'
1137+
1138+
if ls not in self._lineStyles:
1139+
try:
1140+
ls = ls_mapper_r[ls]
1141+
except KeyError:
1142+
raise ValueError(("You passed in an invalid linestyle, "
1143+
"`{0}`. See "
1144+
"docs of Line2D.set_linestyle for "
1145+
"valid values.").format(ls))
1146+
self._linestyle = ls
1147+
else:
1148+
self._linestyle = '--'
11191149

1120-
self._linestyle = ls
1150+
# get the unscaled dashes
1151+
self._us_dashOffset, self._us_dashSeq = _get_dash_pattern(ls)
1152+
# compute the linewidth scaled dashes
1153+
self._dashOffset, self._dashSeq = _scale_dashes(
1154+
self._us_dashOffset, self._us_dashSeq, self._linewidth)
11211155

11221156
@docstring.dedent_interpd
11231157
def set_marker(self, marker):
@@ -1227,10 +1261,7 @@ def set_dashes(self, seq):
12271261
if seq == (None, None) or len(seq) == 0:
12281262
self.set_linestyle('-')
12291263
else:
1230-
self.set_linestyle('--')
1231-
if self._dashSeq != seq:
1232-
self.stale = True
1233-
self._dashSeq = seq # TODO: offset ignored for now
1264+
self.set_linestyle((0, seq))
12341265

12351266
def _draw_lines(self, renderer, gc, path, trans):
12361267
self._lineFunc(renderer, gc, path, trans)
@@ -1258,21 +1289,22 @@ def _draw_steps_mid(self, renderer, gc, path, trans):
12581289

12591290
def _draw_solid(self, renderer, gc, path, trans):
12601291
gc.set_linestyle('solid')
1292+
gc.set_dashes(self._dashOffset, self._dashSeq)
12611293
renderer.draw_path(gc, path, trans)
12621294

12631295
def _draw_dashed(self, renderer, gc, path, trans):
12641296
gc.set_linestyle('dashed')
1265-
if self._dashSeq is not None:
1266-
gc.set_dashes(self._dashOffset, self._dashSeq)
1267-
1297+
gc.set_dashes(self._dashOffset, self._dashSeq)
12681298
renderer.draw_path(gc, path, trans)
12691299

12701300
def _draw_dash_dot(self, renderer, gc, path, trans):
12711301
gc.set_linestyle('dashdot')
1302+
gc.set_dashes(self._dashOffset, self._dashSeq)
12721303
renderer.draw_path(gc, path, trans)
12731304

12741305
def _draw_dotted(self, renderer, gc, path, trans):
12751306
gc.set_linestyle('dotted')
1307+
gc.set_dashes(self._dashOffset, self._dashSeq)
12761308
renderer.draw_path(gc, path, trans)
12771309

12781310
def update_from(self, other):

0 commit comments

Comments
 (0)
0