diff --git a/doc/api/api_changes/2014-11-11_ELF_split_lsmapper.rst b/doc/api/api_changes/2014-11-11_ELF_split_lsmapper.rst new file mode 100644 index 000000000000..e32da0a0898f --- /dev/null +++ b/doc/api/api_changes/2014-11-11_ELF_split_lsmapper.rst @@ -0,0 +1,9 @@ +Split `matplotlib.cbook.ls_mapper` in two +````````````````````````````````````````` + +The `matplotlib.cbook.ls_mapper` dictionary is split into two now to +distinguish between qualified linestyle used by backends and +unqualified ones. `ls_mapper` now maps from the short symbols +(e.g. `"--"`) to qualified names (`"solid"`). The new ls_mapper_r is +the reversed mapping. + diff --git a/doc/users/whats_new/linestyles.rst b/doc/users/whats_new/linestyles.rst new file mode 100644 index 000000000000..53a273a7dfd0 --- /dev/null +++ b/doc/users/whats_new/linestyles.rst @@ -0,0 +1,8 @@ +Mostly unified linestyles for Lines, Patches and Collections +```````````````````````````````````````````````````````````` + +The handling of linestyles for Lines, Patches and Collections has been +unified. Now they all support defining linestyles with short symbols, +like `"--"`, as well as with full names, like `"dashed"`. Also the +definition using a dash pattern (`(0., [3., 3.])`) is supported for all +methods using Lines, Patches or Collections. diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index 814df14d9ab4..5c85ffdc59d6 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -2134,7 +2134,10 @@ def unmasked_index_ranges(mask, compressed=True): (':', 'dotted')] ls_mapper = dict(_linestyles) -ls_mapper.update([(ls[1], ls[0]) for ls in _linestyles]) +# The ls_mapper maps short codes for line style to their full name used +# by backends +# The reverse mapper is for mapping full names to short ones +ls_mapper_r = dict([(ls[1], ls[0]) for ls in _linestyles]) def align_iterators(func, *iterables): diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py index 8bffa9c11bfe..a37532f70c9b 100644 --- a/lib/matplotlib/collections.py +++ b/lib/matplotlib/collections.py @@ -482,16 +482,33 @@ def set_linestyle(self, ls): """ Set the linestyle(s) for the collection. - ACCEPTS: ['solid' | 'dashed', 'dashdot', 'dotted' | - (offset, on-off-dash-seq) ] + =========================== ================= + linestyle description + =========================== ================= + ``'-'`` or ``'solid'`` solid line + ``'--'`` or ``'dashed'`` dashed line + ``'-.'`` or ``'dash_dot'`` dash-dotted line + ``':'`` or ``'dotted'`` dotted line + =========================== ================= + + Alternatively a dash tuple of the following form can be provided:: + + (offset, onoffseq), + + where ``onoffseq`` is an even length tuple of on and off ink + in points. + + Parameters + ---------- + ls : { '-', '--', '-.', ':'} and more see description + The line style. """ try: dashd = backend_bases.GraphicsContextBase.dashd if cbook.is_string_like(ls): + ls = cbook.ls_mapper.get(ls, ls) if ls in dashd: dashes = [dashd[ls]] - elif ls in cbook.ls_mapper: - dashes = [dashd[cbook.ls_mapper[ls]]] else: raise ValueError() elif cbook.iterable(ls): @@ -499,10 +516,9 @@ def set_linestyle(self, ls): dashes = [] for x in ls: if cbook.is_string_like(x): + x = cbook.ls_mapper.get(x, x) if x in dashd: dashes.append(dashd[x]) - elif x in cbook.ls_mapper: - dashes.append(dashd[cbook.ls_mapper[x]]) else: raise ValueError() elif cbook.iterable(x) and len(x) == 2: @@ -511,7 +527,7 @@ def set_linestyle(self, ls): raise ValueError() except ValueError: if len(ls) == 2: - dashes = ls + dashes = [ls] else: raise ValueError() else: diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 3c0c39153118..4854ef045a51 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -16,7 +16,7 @@ from matplotlib import verbose from . import artist from .artist import Artist -from .cbook import iterable, is_string_like, is_numlike, ls_mapper +from .cbook import iterable, is_string_like, is_numlike, ls_mapper_r from .colors import colorConverter from .path import Path from .transforms import Bbox, TransformedPath, IdentityTransform @@ -921,55 +921,74 @@ def set_linewidth(self, w): """ self._linewidth = w - def set_linestyle(self, linestyle): + def set_linestyle(self, ls): """ - Set the linestyle of the line (also accepts drawstyles) + Set the linestyle of the line (also accepts drawstyles, + e.g., ``'steps--'``) - ================ ================= - linestyle description - ================ ================= - ``'-'`` solid - ``'--'`` dashed - ``'-.'`` dash_dot - ``':'`` dotted - ``'None'`` draw nothing - ``' '`` draw nothing - ``''`` draw nothing - ================ ================= + =========================== ================= + linestyle description + =========================== ================= + ``'-'`` or ``'solid'`` solid line + ``'--'`` or ``'dashed'`` dashed line + ``'-.'`` or ``'dash_dot'`` dash-dotted line + ``':'`` or ``'dotted'`` dotted line + ``'None'`` draw nothing + ``' '`` draw nothing + ``''`` draw nothing + =========================== ================= 'steps' is equivalent to 'steps-pre' and is maintained for backward-compatibility. + Alternatively a dash tuple of the following form can be provided:: + + (offset, onoffseq), + + where ``onoffseq`` is an even length tuple of on and off ink + in points. + .. seealso:: :meth:`set_drawstyle` To set the drawing style (stepping) of the plot. - ACCEPTS: [``'-'`` | ``'--'`` | ``'-.'`` | ``':'`` | ``'None'`` | - ``' '`` | ``''``] - - and any drawstyle in combination with a linestyle, e.g., ``'steps--'``. + Parameters + ---------- + 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._linestyle = "--" + return for ds in self.drawStyleKeys: # long names are first in the list - if linestyle.startswith(ds): + if ls.startswith(ds): self.set_drawstyle(ds) - if len(linestyle) > len(ds): - linestyle = linestyle[len(ds):] + if len(ls) > len(ds): + ls = ls[len(ds):] else: - linestyle = '-' + ls = '-' break - if linestyle not in self._lineStyles: - if linestyle in ls_mapper: - linestyle = ls_mapper[linestyle] - else: - verbose.report('Unrecognized line style %s, %s' % - (linestyle, type(linestyle))) - if linestyle in [' ', '']: - linestyle = 'None' - self._linestyle = linestyle + 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, " + "`{}`. See " + "docs of Line2D.set_linestyle for " + "valid values.").format(ls)) + + self._linestyle = ls @docstring.dedent_interpd def set_marker(self, marker): diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index da9f1c964ea1..d795dca1a480 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -344,10 +344,31 @@ def set_linestyle(self, ls): """ Set the patch linestyle - ACCEPTS: ['solid' | 'dashed' | 'dashdot' | 'dotted'] + =========================== ================= + linestyle description + =========================== ================= + ``'-'`` or ``'solid'`` solid line + ``'--'`` or ``'dashed'`` dashed line + ``'-.'`` or ``'dash_dot'`` dash-dotted line + ``':'`` or ``'dotted'`` dotted line + =========================== ================= + + Alternatively a dash tuple of the following form can be provided:: + + (offset, onoffseq), + + where ``onoffseq`` is an even length tuple of on and off ink + in points. + + Parameters + ---------- + ls : { '-', '--', '-.', ':'} and more see description + The line style. """ if ls is None: ls = "solid" + + ls = cbook.ls_mapper.get(ls, ls) self._linestyle = ls def set_ls(self, ls): diff --git a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_ls_dash.pdf b/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_ls_dash.pdf new file mode 100644 index 000000000000..3952787106ec Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_ls_dash.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_ls_dash.png b/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_ls_dash.png new file mode 100644 index 000000000000..3312b2be5bbc Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_ls_dash.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_ls_dash.svg b/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_ls_dash.svg new file mode 100644 index 000000000000..08c6ee903948 --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_collections/EventCollection_plot__set_ls_dash.svg @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_lines/line_dashes.pdf b/lib/matplotlib/tests/baseline_images/test_lines/line_dashes.pdf new file mode 100644 index 000000000000..ca440b3adecb Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_lines/line_dashes.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_lines/line_dashes.png b/lib/matplotlib/tests/baseline_images/test_lines/line_dashes.png new file mode 100644 index 000000000000..3d1e7195797c Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_lines/line_dashes.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_lines/line_dashes.svg b/lib/matplotlib/tests/baseline_images/test_lines/line_dashes.svg new file mode 100644 index 000000000000..d693b936214a --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_lines/line_dashes.svg @@ -0,0 +1,335 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/test_collections.py b/lib/matplotlib/tests/test_collections.py index fb0f23d97051..604591ca21cc 100644 --- a/lib/matplotlib/tests/test_collections.py +++ b/lib/matplotlib/tests/test_collections.py @@ -325,6 +325,19 @@ def test__EventCollection__set_linestyle(): splt.set_title('EventCollection: set_linestyle') +@image_comparison(baseline_images=['EventCollection_plot__set_ls_dash'], + remove_text=True) +def test__EventCollection__set_linestyle_single_dash(): + ''' + check to make sure set_linestyle accepts a single dash pattern + ''' + splt, coll, _ = generate_EventCollection_plot() + new_linestyle = (0, (6., 6.)) + coll.set_linestyle(new_linestyle) + assert_equal(coll.get_linestyle(), [(0, (6.0, 6.0))]) + splt.set_title('EventCollection: set_linestyle') + + @image_comparison(baseline_images=['EventCollection_plot__set_linewidth']) def test__EventCollection__set_linewidth(): ''' @@ -496,7 +509,7 @@ def test_polycollection_close(): vertsQuad * len(zpos), linewidth=0.25) poly.set_alpha(0.7) - ## need to have a z-value for *each* polygon = element! + # need to have a z-value for *each* polygon = element! zs = [] cs = [] for z, c in zip(zpos, colors): @@ -507,7 +520,7 @@ def test_polycollection_close(): ax.add_collection3d(poly, zs=zs, zdir='y') - ## axis limit settings: + # axis limit settings: ax.set_xlim3d(0, 4) ax.set_zlim3d(0, 3) ax.set_ylim3d(0, 4) @@ -578,6 +591,12 @@ class MouseEvent(object): assert_array_equal(indices['ind'], [0]) +@cleanup +def test_linestyle_single_dashes(): + plt.scatter([0, 1, 2], [0, 1, 2], linestyle=(0., [2., 2.])) + plt.draw() + + if __name__ == '__main__': import nose nose.runmodule(argv=['-s', '--with-doctest'], exit=False) diff --git a/lib/matplotlib/tests/test_lines.py b/lib/matplotlib/tests/test_lines.py index 55aa360af737..8176728824aa 100644 --- a/lib/matplotlib/tests/test_lines.py +++ b/lib/matplotlib/tests/test_lines.py @@ -6,12 +6,14 @@ import six -from nose.tools import assert_true +import nose +from nose.tools import assert_true, assert_raises from timeit import repeat import numpy as np import matplotlib as mpl import matplotlib.pyplot as plt from matplotlib.testing.decorators import cleanup, image_comparison +import sys @cleanup @@ -71,6 +73,14 @@ def test_set_line_coll_dash(): assert True +@image_comparison(baseline_images=['line_dashes'], remove_text=True) +def test_line_dashes(): + fig = plt.figure() + ax = fig.add_subplot(1, 1, 1) + + ax.plot(range(10), linestyle=(0, (3, 3)), lw=5) + + @cleanup def test_line_colors(): fig = plt.figure() @@ -84,6 +94,29 @@ def test_line_colors(): assert True +@cleanup +def test_linestyle_variants(): + fig = plt.figure() + ax = fig.add_subplot(1, 1, 1) + for ls in ["-", "solid", "--", "dashed", + "-.", "dashdot", ":", "dotted"]: + ax.plot(range(10), linestyle=ls) + + fig.canvas.draw() + assert True + + +@cleanup +def test_valid_linestyles(): + if sys.version_info[:2] < (2, 7): + raise nose.SkipTest("assert_raises as context manager " + "not supported with Python < 2.7") + + line = mpl.lines.Line2D([], []) + with assert_raises(ValueError): + line.set_linestyle('aardvark') + + @image_comparison(baseline_images=['line_collection_dashes'], remove_text=True) def test_set_line_coll_dash_image(): fig = plt.figure() @@ -101,5 +134,4 @@ def test_nan_is_sorted(): if __name__ == '__main__': - import nose nose.runmodule(argv=['-s', '--with-doctest'], exit=False) diff --git a/lib/matplotlib/tests/test_patches.py b/lib/matplotlib/tests/test_patches.py index 1c8549d29255..44856642b282 100644 --- a/lib/matplotlib/tests/test_patches.py +++ b/lib/matplotlib/tests/test_patches.py @@ -13,7 +13,7 @@ from matplotlib.patches import Polygon from matplotlib.patches import Rectangle -from matplotlib.testing.decorators import image_comparison +from matplotlib.testing.decorators import image_comparison, cleanup import matplotlib.pyplot as plt import matplotlib.patches as mpatches import matplotlib.collections as mcollections @@ -203,6 +203,36 @@ def test_patch_custom_linestyle(): ax.set_ylim([-1, 2]) +@cleanup +def test_patch_linestyle_accents(): + #: Test if linestyle can also be specified with short menoics + #: like "--" + #: c.f. Gihub issue #2136 + star = mpath.Path.unit_regular_star(6) + circle = mpath.Path.unit_circle() + # concatenate the star with an internal cutout of the circle + verts = np.concatenate([circle.vertices, star.vertices[::-1]]) + codes = np.concatenate([circle.codes, star.codes]) + + linestyles = ["-", "--", "-.", ":", + "solid", "dashed", "dashdot", "dotted"] + + fig = plt.figure() + ax = fig.add_subplot(1, 1, 1) + for i, ls in enumerate(linestyles): + star = mpath.Path(verts + i, codes) + patch = mpatches.PathPatch(star, + linewidth=3, linestyle=ls, + facecolor=(1, 0, 0), + edgecolor=(0, 0, 1)) + ax.add_patch(patch) + + ax.set_xlim([-1, i + 1]) + ax.set_ylim([-1, i + 1]) + fig.canvas.draw() + assert True + + def test_wedge_movement(): param_dict = {'center': ((0, 0), (1, 1), 'set_center'), 'r': (5, 8, 'set_radius'),