8000 Update path simplification to handle anti-parallel vectors · matplotlib/matplotlib@5c5d7ec · GitHub
[go: up one dir, main page]

Skip to content

Commit 5c5d7ec

Browse files
committed
Update path simplification to handle anti-parallel vectors
1 parent 0d0b1c1 commit 5c5d7ec

File tree

5 files changed

+378
-50
lines changed

5 files changed

+378
-50
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
Path simplification updates
2+
---------------------------
3+
4+
Line simplification controlled by the ``path.simplify`` and
5+
``path.simplify_threshold`` parameters has been improved. You should
6+
notice better rendering performance when plotting large amounts of
7+
data (as long as the above parameters are set accordingly). Only the
8+
line segment portion of paths will be simplified -- if you are also
9+
drawing markers and experiencing problems with rendering speed, you
10+
should consider using the ``markevery`` option to ``plot``.
11+
See the :ref:`performance` section in the usage tutorial for more
12+
information.
13+
14+
The simplification works by iteratively merging line segments
15+
into a single vector until the next line segment's perpendicular
16+
distance to the vector (measured in display-coordinate space)
17+
is greater than the ``path.simplify_threshold`` parameter. Thus, higher
18+
values of ``path.simplify_threshold`` result in quicker rendering times.
19+
If you are plotting just to explore data and not for publication quality,
20+
pixel perfect plots, then a value of ``1.0`` can be safely used. If you
21+
want to make sure your plot reflects your data *exactly*, then you should
22+
set ``path.simplify`` to false and/or ``path.simplify_threshold`` to ``0``.
23+
Matplotlib currently defaults to a conservative value of ``1/9``, smaller
24+
values are unlikely to cause any visible differences in your plots.

lib/matplotlib/path.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -206,12 +206,13 @@ def _fast_from_codes_and_verts(cls, verts, codes, internals=None):
206206
return pth
207207

208208
def _update_values(self):
209+
self._simplify_threshold = rcParams['path.simplify_threshold']
209210
self._should_simplify = (
211+
self._simplify_threshold > 0 and
210212
rcParams['path.simplify'] and
211-
(len(self._vertices) >= 128 and
212-
(self._codes is None or np.all(self._codes <= Path.LINETO)))
213+
len(self._vertices) >= 128 and
214+
(self._codes is None or np.all(self._codes <= Path.LINETO))
213215
)
214-
self._simplify_threshold = rcParams['path.simplify_threshold']
215216
self._has_nonfinite = not np.isfinite(self._vertices).all()
216217

217218
@property

lib/matplotlib/tests/test_simplification.py

Lines changed: 136 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import io
55

66
import numpy as np
7+
from numpy.testing import assert_array_almost_equal, assert_array_equal
8+
79
import pytest
810

911
from matplotlib.testing.decorators import image_comparison
@@ -49,33 +51,158 @@ def test_diamond():
4951

5052
def test_noise():
5153
np.random.seed(0)
52-
x = np.random.uniform(size=(5000,)) * 50
54+
x = np.random.uniform(size=(50000,)) * 50
5355

5456
fig, ax = plt.subplots()
5557
p1 = ax.plot(x, solid_joinstyle='round', linewidth=2.0)
5658

5759
path = p1[0].get_path()
5860
transform = p1[0].get_transform()
5961
path = transform.transform_path(path)
60-
simplified = list(path.iter_segments(simplify=(800, 600)))
62+
simplified = path.cleaned(simplify=True)
63+
64+
assert simplified.vertices.size == 25512
65+
66+
67+
def test_antiparallel_simplification():
68+
def _get_simplified(x, y):
69+
fig, ax = plt.subplots()
70+
p1 = ax.plot(x, y)
71+
72+
path = p1[0].get_path()
73+
transform = p1[0].get_transform()
74+
path = transform.transform_path(path)
75+
simplified = path.cleaned(simplify=True)
76+
simplified = transform.inverted().transform_path(simplified)
77+
78+
return simplified
79+
80+
# test ending on a maximum
81+
x = [0, 0, 0, 0, 0, 1]
82+
y = [.5, 1, -1, 1, 2, .5]
83+
84+
simplified = _get_simplified(x, y)
85+
86+
assert_array_almost_equal([[0., 0.5],
87+
[0., -1.],
88+
[0., 2.],
89+
[1., 0.5]],
90+
simplified.vertices[:-2, :])
91+
92+
# test ending on a minimum
93+
x = [0, 0, 0, 0, 0, 1]
94+
y = [.5, 1, -1, 1, -2, .5]
95+
96+
simplified = _get_simplified(x, y)
97+
98+
assert_array_almost_equal([[0., 0.5],
99+
[0., 1.],
100+
[0., -2.],
101+
[1., 0.5]],
102+
simplified.vertices[:-2, :])
103+
104+
# test ending in between
105+
x = [0, 0, 0, 0, 0, 1]
106+
y = [.5, 1, -1, 1, 0, .5]
107+
108+
simplified = _get_simplified(x, y)
109+
110+
assert_array_almost_equal([[0., 0.5],
111+
[0., 1.],
112+
[0., -1.],
113+
[0., 0.],
114+
[1., 0.5]],
115+
simplified.vertices[:-2, :])
116+
117+
# test no anti-parallel ending at max
118+
x = [0, 0, 0, 0, 0, 1]
119+
y = [.5, 1, 2, 1, 3, .5]
120+
121+
simplified = _get_simplified(x, y)
122+
123+
assert_array_almost_equal([[0., 0.5],
124+
[0., 3.],
125+
[1., 0.5]],
126+
simplified.vertices[:-2, :])
127+
128+
# test no anti-parallel ending in middle
129+
x = [0, 0, 0, 0, 0, 1]
130+
y = [.5, 1, 2, 1, 1, .5]
131+
132+
simplified = _get_simplified(x, y)
133+
134+
assert_array_almost_equal([[0., 0.5],
135+
[0., 2.],
136+
[0., 1.],
137+
[1., 0.5]],
138+
simplified.vertices[:-2, :])
139+
140+
141+
# Only consider angles in 0 <= angle <= pi/2, otherwise
142+
# using min/max will get the expected results out of order:
143+
# min/max for simplification code depends on original vector,
144+
# and if angle is outside above range then simplification
145+
# min/max will be opposite from actual min/max.
146+
@pytest.mark.parametrize('angle', [0, np.pi/4, np.pi/3, np.pi/2])
147+
@pytest.mark.parametrize('offset', [0, .5])
148+
def test_angled_antiparallel(angle, offset):
149+
scale = 5
150+
np.random.seed(19680801)
151+
# get 15 random offsets
152+
# TODO: guarantee offset > 0 results in some offsets < 0
153+
vert_offsets = (np.random.rand(15) - offset) * scale
154+
# always start at 0 so rotation makes sense
155+
vert_offsets[0] = 0
156+
# always take the first step the same direction
157+
vert_offsets[1] = 1
158+
# compute points along a diagonal line
159+
x = np.sin(angle) * vert_offsets
160+
y = np.cos(angle) * vert_offsets
161+
162+
# will check these later
163+
x_max = x[1:].max()
164+
x_min = x[1:].min()
165+
166+
y_max = y[1:].max()
167+
y_min = y[1:].min()
168+
169+
if offset > 0:
170+
p_expected = Path([[0, 0],
171+
[x_max, y_max],
172+
[x_min, y_min],
173+
[x[-1], y[-1]],
174+
[0, 0]],
175+
codes=[1, 2, 2, 2, 0])
176+
177+
else:
178+
p_expected = Path([[0, 0],
179+
[x_max, y_max],
180+
[x[-1], y[-1]],
181+
[0, 0]],
182+
codes=[1, 2, 2, 0])
183+
184+
p = Path(np.vstack([x, y]).T)
185+
p2 = p.cleaned(simplify=True)
61186

62-
assert len(simplified) == 3884
187+
assert_array_almost_equal(p_expected.vertices,
188+
p2.vertices)
189+
assert_array_equal(p_expected.codes, p2.codes)
63190

64191

65192
def test_sine_plus_noise():
66193
np.random.seed(0)
67-
x = (np.sin(np.linspace(0, np.pi * 2.0, 1000)) +
68-
np.random.uniform(size=(1000,)) * 0.01)
194+
x = (np.sin(np.linspace(0, np.pi * 2.0, 50000)) +
195+
np.random.uniform(size=(50000,)) * 0.01)
69196

70197
fig, ax = plt.subplots()
71198
p1 = ax.plot(x, solid_joinstyle='round', linewidth=2.0)
72199

73200
path = p1[0].get_path()
74201
transform = p1[0].get_transform()
75202
path = transform.transform_path(path)
76-
simplified = list(path.iter_segments(simplify=(800, 600)))
203+
simplified = path.cleaned(simplify=True)
77204

78-
assert len(simplified) == 876
205+
assert simplified.vertices.size == 25240
79206

80207

81208
@image_comparison(baseline_images=['simplify_curve'], remove_text=True)
@@ -110,9 +237,9 @@ def test_fft_peaks():
110237
path = p1[0].get_path()
111238
transform = p1[0].get_transform()
112239
path = transform.transform_path(path)
113-
simplified = list(path.iter_segments(simplify=(800, 600)))
240+
simplified = path.cleaned(simplify=True)
114241

115-
assert len(simplified) == 20
242+
assert simplified.vertices.size == 36
116243

117244

118245
def test_start_with_moveto():

0 commit comments

Comments
 (0)
0