diff --git a/doc/users/next_whats_new/striped_lines.rst b/doc/users/next_whats_new/striped_lines.rst new file mode 100644 index 000000000000..dff1205a5db1 --- /dev/null +++ b/doc/users/next_whats_new/striped_lines.rst @@ -0,0 +1,19 @@ +Striped lines (experimental) +---------------------------- + +New *gapcolor* parameter enables the creation of striped lines. + +.. plot:: + :include-source: true + + import matplotlib.pyplot as plt + import numpy as np + + x = np.linspace(1., 3., 10) + y = x**3 + + fig, ax = plt.subplots() + ax.plot(x, y, linestyle='--', color='orange', gapcolor='blue', + linewidth=3, label='a striped line') + ax.legend() + plt.show() diff --git a/examples/lines_bars_and_markers/line_demo_dash_control.py b/examples/lines_bars_and_markers/line_demo_dash_control.py index 78043dfed2ff..9d02981597c0 100644 --- a/examples/lines_bars_and_markers/line_demo_dash_control.py +++ b/examples/lines_bars_and_markers/line_demo_dash_control.py @@ -17,6 +17,11 @@ :doc:`property_cycle ` by passing a list of dash sequences using the keyword *dashes* to the cycler. This is not shown within this example. + +Other attributes of the dash may also be set either with the relevant method +(`~.Line2D.set_dash_capstyle`, `~.Line2D.set_dash_joinstyle`, +`~.Line2D.set_gapcolor`) or by passing the property through a plotting +function. """ import numpy as np import matplotlib.pyplot as plt @@ -24,14 +29,21 @@ x = np.linspace(0, 10, 500) y = np.sin(x) +plt.rc('lines', linewidth=2.5) fig, ax = plt.subplots() -# Using set_dashes() to modify dashing of an existing line -line1, = ax.plot(x, y, label='Using set_dashes()') -line1.set_dashes([2, 2, 10, 2]) # 2pt line, 2pt break, 10pt line, 2pt break +# Using set_dashes() and set_capstyle() to modify dashing of an existing line. +line1, = ax.plot(x, y, label='Using set_dashes() and set_dash_capstyle()') +line1.set_dashes([2, 2, 10, 2]) # 2pt line, 2pt break, 10pt line, 2pt break. +line1.set_dash_capstyle('round') -# Using plot(..., dashes=...) to set the dashing when creating a line +# Using plot(..., dashes=...) to set the dashing when creating a line. line2, = ax.plot(x, y - 0.2, dashes=[6, 2], label='Using the dashes parameter') -ax.legend() +# Using plot(..., dashes=..., gapcolor=...) to set the dashing and +# alternating color when creating a line. +line3, = ax.plot(x, y - 0.4, dashes=[4, 4], gapcolor='tab:pink', + label='Using the dashes and gapcolor parameters') + +ax.legend(handlelength=4) plt.show() diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 226787c2b6a8..40ed46fd8f40 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -272,6 +272,7 @@ def __init__(self, xdata, ydata, linewidth=None, # all Nones default to rc linestyle=None, color=None, + gapcolor=None, marker=None, markersize=None, markeredgewidth=None, @@ -364,6 +365,9 @@ def __init__(self, xdata, ydata, else: self._marker = marker + self._gapcolor = None + self.set_gapcolor(gapcolor) + self._markevery = None self._markersize = None self._antialiased = None @@ -754,9 +758,6 @@ def draw(self, renderer): self._set_gc_clip(gc) gc.set_url(self.get_url()) - lc_rgba = mcolors.to_rgba(self._color, self._alpha) - gc.set_foreground(lc_rgba, isRGBA=True) - gc.set_antialiased(self._antialiased) gc.set_linewidth(self._linewidth) @@ -772,6 +773,26 @@ def draw(self, renderer): if self.get_sketch_params() is not None: gc.set_sketch_params(*self.get_sketch_params()) + # We first draw a path within the gaps if needed. + if self.is_dashed() and self._gapcolor is not None: + lc_rgba = mcolors.to_rgba(self._gapcolor, self._alpha) + gc.set_foreground(lc_rgba, isRGBA=True) + + # Define the inverse pattern by moving the last gap to the + # start of the sequence. + dashes = self._dash_pattern[1] + gaps = dashes[-1:] + dashes[:-1] + # Set the offset so that this new first segment is skipped + # (see backend_bases.GraphicsContextBase.set_dashes for + # offset definition). + offset_gaps = self._dash_pattern[0] + dashes[-1] + + gc.set_dashes(offset_gaps, gaps) + renderer.draw_path(gc, tpath, affine.frozen()) + + lc_rgba = mcolors.to_rgba(self._color, self._alpha) + gc.set_foreground(lc_rgba, isRGBA=True) + gc.set_dashes(*self._dash_pattern) renderer.draw_path(gc, tpath, affine.frozen()) gc.restore() @@ -876,6 +897,14 @@ def get_drawstyle(self): """ return self._drawstyle + def get_gapcolor(self): + """ + Return the line gapcolor. + + See also `~.Line2D.set_gapcolor`. + """ + return self._gapcolor + def get_linestyle(self): """ Return the linestyle. @@ -1066,6 +1095,29 @@ def set_drawstyle(self, drawstyle): self._invalidx = True self._drawstyle = drawstyle + def set_gapcolor(self, gapcolor): + """ + Set a color to fill the gaps in the dashed line style. + + .. note:: + + Striped lines are created by drawing two interleaved dashed lines. + There can be overlaps between those two, which may result in + artifacts when using transparency. + + This functionality is experimental and may change. + + Parameters + ---------- + gapcolor : color or None + The color with which to fill the gaps. If None, the gaps are + unfilled. + """ + if gapcolor is not None: + mcolors._check_color_like(color=gapcolor) + self._gapcolor = gapcolor + self.stale = True + def set_linewidth(self, w): """ Set the line width in points. @@ -1247,6 +1299,9 @@ def set_dashes(self, seq): For example, (5, 2, 1, 2) describes a sequence of 5 point and 1 point dashes separated by 2 point spaces. + See also `~.Line2D.set_gapcolor`, which allows those spaces to be + filled with a color. + Parameters ---------- seq : sequence of floats (on/off ink in points) or (None, None) @@ -1264,6 +1319,7 @@ def update_from(self, other): self._linestyle = other._linestyle self._linewidth = other._linewidth self._color = other._color + self._gapcolor = other._gapcolor self._markersize = other._markersize self._markerfacecolor = other._markerfacecolor self._markerfacecoloralt = other._markerfacecoloralt diff --git a/lib/matplotlib/tests/baseline_images/test_lines/striped_line.png b/lib/matplotlib/tests/baseline_images/test_lines/striped_line.png new file mode 100644 index 000000000000..957129cb981f Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_lines/striped_line.png differ diff --git a/lib/matplotlib/tests/test_lines.py b/lib/matplotlib/tests/test_lines.py index e302ddf5e494..fcb28f6696fc 100644 --- a/lib/matplotlib/tests/test_lines.py +++ b/lib/matplotlib/tests/test_lines.py @@ -304,6 +304,17 @@ def test_marker_as_markerstyle(): assert_array_equal(line3.get_marker().vertices, triangle1.vertices) +@image_comparison(['striped_line.png'], remove_text=True, style='mpl20') +def test_striped_lines(): + rng = np.random.default_rng(19680801) + _, ax = plt.subplots() + ax.plot(rng.uniform(size=12), color='orange', gapcolor='blue', + linestyle='--', lw=5, label=' ') + ax.plot(rng.uniform(size=12), color='red', gapcolor='black', + linestyle=(0, (2, 5, 4, 2)), lw=5, label=' ', alpha=0.5) + ax.legend(handlelength=5) + + @check_figures_equal() def test_odd_dashes(fig_test, fig_ref): fig_test.add_subplot().plot([1, 2], dashes=[1, 2, 3])