8000 Merge pull request #24849 from rcomer/stripey-line-collection · matplotlib/matplotlib@81f5f24 · GitHub
[go: up one dir, main page]

Skip to content

Commit 81f5f24

Browse files
authored
Merge pull request #24849 from rcomer/stripey-line-collection
Stripey `LineCollection`
2 parents 9ff38a8 + 3b4bd83 commit 81f5f24

File tree

3 files changed

+107
-8
lines changed

3 files changed

+107
-8
lines changed

lib/matplotlib/collections.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
line segments).
1010
"""
1111

12+
import itertools
1213
import math
1314
from numbers import Number
1415
import warnings
@@ -163,6 +164,9 @@ def __init__(self,
163164
# list of unbroadcast/scaled linewidths
164165
self._us_lw = [0]
165166
self._linewidths = [0]
167+
168+
self._gapcolor = None # Currently only used by LineCollection.
169+
166170
# Flags set by _set_mappable_flags: are colors from mapping an array?
167171
self._face_is_mapped = None
168172
self._edge_is_mapped = None
@@ -406,6 +410,17 @@ def draw(self, renderer):
406410
gc, paths[0], combined_transform.frozen(),
407411
mpath.Path(offsets), offset_trf, tuple(facecolors[0]))
408412
else:
413+
if self._gapcolor is not None:
414+
# First draw paths within the gaps.
415+
ipaths, ilinestyles = self._get_inverse_paths_linestyles()
416+
renderer.draw_path_collection(
417+
gc, transform.frozen(), ipaths,
418+
self.get_transforms(), offsets, offset_trf,
419+
[mcolors.to_rgba("none")], self._gapcolor,
420+
self._linewidths, ilinestyles,
421+
self._antialiaseds, self._urls,
422+
"screen")
423+
409424
renderer.draw_path_collection(
410425
gc, transform.frozen(), paths,
411426
self.get_transforms(), offsets, offset_trf,
@@ -1459,6 +1474,12 @@ def _get_default_edgecolor(self):
14591474
def _get_default_facecolor(self):
14601475
return 'none'
14611476

1477+
def set_alpha(self, alpha):
1478+
# docstring inherited
1479+
super().set_alpha(alpha)
1480+
if self._gapcolor is not None:
1481+
self.set_gapcolor(self._original_gapcolor)
1482+
14621483
def set_color(self, c):
14631484
"""
14641485
Set the edgecolor(s) of the LineCollection.
@@ -1479,6 +1500,53 @@ def get_color(self):
14791500

14801501
get_colors = get_color # for compatibility with old versions
14811502

1503+
def set_gapcolor(self, gapcolor):
1504+
"""
1505+
Set a color to fill the gaps in the dashed line style.
1506+
1507+
.. note::
1508+
1509+
Striped lines are created by drawing two interleaved dashed lines.
1510+
There can be overlaps between those two, which may result in
1511+
artifacts when using transparency.
1512+
1513+
This functionality is experimental and may change.
1514+
1515+
Parameters
1516+
----------
1517+
gapcolor : color or list of colors or None
1518+
The color with which to fill the gaps. If None, the gaps are
1519+
unfilled.
1520+
"""
1521+
self._original_gapcolor = gapcolor
1522+
self._set_gapcolor(gapcolor)
1523+
1524+
def _set_gapcolor(self, gapcolor):
1525+
if gapcolor is not None:
1526+
gapcolor = mcolors.to_rgba_array(gapcolor, self._alpha)
1527+
self._gapcolor = gapcolor
1528+
self.stale = True
1529+
1530+
def get_gapcolor(self):
1531+
return self._gapcolor
1532+
1533+
def _get_inverse_paths_linestyles(self):
1534+
"""
1535+
Returns the path and pattern for the gaps in the non-solid lines.
1536+
1537+
This path and pattern is the inverse of the path and pattern used to
1538+
construct the non-solid lines. For solid lines, we set the inverse path
1539+
to nans to prevent drawing an inverse line.
1540+
"""
1541+
path_patterns = [
1542+
(mpath.Path(np.full((1, 2), np.nan)), ls)
1543+
if ls == (0, None) else
1544+
(path, mlines._get_inverse_dash_pattern(*ls))
1545+
for (path, ls) in
1546+
zip(self._paths, itertools.cycle(self._linestyles))]
1547+
1548+
return zip(*path_patterns)
1549+
14821550

14831551
class EventCollection(LineCollection):
14841552
"""

lib/matplotlib/lines.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,18 @@ def _get_dash_pattern(style):
6060
return offset, dashes
6161

6262

63+
def _get_inverse_dash_pattern(offset, dashes):
64+
"""Return the inverse of the given dash pattern, for filling the gaps."""
65+
# Define the inverse pattern by moving the last gap to the start of the
66+
# sequence.
67+
gaps = dashes[-1:] + dashes[:-1]
68+
# Set the offset so that this new first segment is skipped
69+
# (see backend_bases.GraphicsContextBase.set_dashes for offset definition).
70+
offset_gaps = offset + dashes[-1]
71+
72+
return offset_gaps, gaps
73+
74+
6375
def _scale_dashes(offset, dashes, lw):
6476
if not mpl.rcParams['lines.scale_dashes']:
6577
return offset, dashes
@@ -780,14 +792,8 @@ def draw(self, renderer):
780792
lc_rgba = mcolors.to_rgba(self._gapcolor, self._alpha)
781793
gc.set_foreground(lc_rgba, isRGBA=True)
782794

783-
# Define the inverse pattern by moving the last gap to the
784-
# start of the sequence.
785-
dashes = self._dash_pattern[1]
786-
gaps = dashes[-1:] + dashes[:-1]
787-
# Set the offset so that this new first segment is skipped
788-
# (see backend_bases.GraphicsContextBase.set_dashes for
789-
# offset definition).
790-
offset_gaps = self._dash_pattern[0] + dashes[-1]
795+
offset_gaps, gaps = _get_inverse_dash_pattern(
796+
*self._dash_pattern)
791797

792798
gc.set_dashes(offset_gaps, gaps)
793799
renderer.draw_path(gc, tpath, affine.frozen())

lib/matplotlib/tests/test_collections.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from datetime import datetime
22
import io
3+
import itertools
34
import re
45
from types import SimpleNamespace
56

@@ -1191,3 +1192,27 @@ def test_check_offsets_dtype():
11911192
unmasked_offsets = np.column_stack([x, y])
11921193
scat.set_offsets(unmasked_offsets)
11931194
assert isinstance(scat.get_offsets(), type(unmasked_offsets))
1195+
1196+
1197+
@pytest.mark.parametrize('gapcolor', ['orange', ['r', 'k']])
1198+
@check_figures_equal(extensions=['png'])
1199+
@mpl.rc_context({'lines.linewidth': 20})
1200+
def test_striped_lines(fig_test, fig_ref, gapcolor):
1201+
ax_test = fig_test.add_subplot(111)
1202+
ax_ref = fig_ref.add_subplot(111)
1203+
1204+
for ax in [ax_test, ax_ref]:
1205+
ax.set_xlim(0, 6)
1206+
ax.set_ylim(0, 1)
1207+
1208+
x = range(1, 6)
1209+
linestyles = [':', '-', '--']
1210+
1211+
ax_test.vlines(x, 0, 1, linestyle=linestyles, gapcolor=gapcolor, alpha=0.5)
1212+
1213+
if isinstance(gapcolor, str):
1214+
gapcolor = [gapcolor]
1215+
1216+
for x, gcol, ls in zip(x, itertools.cycle(gapcolor),
1217+
itertools.cycle(linestyles)):
1218+
ax_ref.axvline(x, 0, 1, linestyle=ls, gapcolor=gcol, alpha=0.5)

0 commit comments

Comments
 (0)
0