Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit c33557d

Browse files
authored
Merge pull request #22707 from aragilar/no_broken_streamlines
Proposed ENH: Allow user to turn off breaking of streamlines in streamplot (rebased)
2 parents 6d42cf7 + d190205 commit c33557d

File tree

6 files changed

+75
-20
lines changed

6 files changed

+75
-20
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
Add option to plt.streamplot to not break streamlines
2+
-----------------------------------------------------
3+
4+
It is now possible to specify that streamplots have continuous, unbroken
5+
streamlines. Previously streamlines would end to limit the number of lines
6+
within a single grid cell. See the difference between the plots below:
7+
8+
.. plot::
9+
10+
import matplotlib.pyplot as plt
11+
import numpy as np
12+
13+
w = 3
14+
Y, X = np.mgrid[-w:w:100j, -w:w:100j]
15+
U = -1 - X**2 + Y
16+
V = 1 + X - Y**2
17+
speed = np.sqrt(U**2 + V**2)
18+
19+
fig, (ax0, ax1) = plt.subplots(1, 2, sharex=True)
20+
21+
ax0.streamplot(X, Y, U, V, broken_streamlines=True)
22+
ax0.set_title('broken_streamlines=True')
23+
24+
ax1.streamplot(X, Y, U, V, broken_streamlines=False)
25+
ax1.set_title('broken_streamlines=False')

examples/images_contours_and_fields/plot_streamplot.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
* Varying the line width along a streamline.
1212
* Controlling the starting points of streamlines.
1313
* Streamlines skipping masked regions and NaN values.
14+
* Unbroken streamlines even when exceeding the limit of lines within a single
15+
grid cell.
1416
"""
1517
import numpy as np
1618
import matplotlib.pyplot as plt
@@ -61,13 +63,17 @@
6163
U[:20, :20] = np.nan
6264
U = np.ma.array(U, mask=mask)
6365

64-
ax4 = fig.add_subplot(gs[2:, :])
66+
ax4 = fig.add_subplot(gs[2, 0])
6567
ax4.streamplot(X, Y, U, V, color='r')
6668
ax4.set_title('Streamplot with Masking')
6769

6870
ax4.imshow(~mask, extent=(-w, w, -w, w), alpha=0.5, cmap='gray', aspect='auto')
6971
ax4.set_aspect('equal')
7072

73+
ax5 = fig.add_subplot(gs[2, 1])
74+
ax5.streamplot(X, Y, U, V, broken_streamlines=False)
75+
ax5.set_title('Streamplot with with unbroken streamlines')
76+
7177
plt.tight_layout()
7278
plt.show()
7379
#############################################################################

lib/matplotlib/pyplot.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2914,14 +2914,16 @@ def streamplot(
29142914
x, y, u, v, density=1, linewidth=None, color=None, cmap=None,
29152915
norm=None, arrowsize=1, arrowstyle='-|>', minlength=0.1,
29162916
transform=None, zorder=None, start_points=None, maxlength=4.0,
2917-
integration_direction='both', *, data=None):
2917+
integration_direction='both', broken_streamlines=True, *,
2918+
data=None):
29182919
__ret = gca().streamplot(
29192920
x, y, u, v, density=density, linewidth=linewidth, color=color,
29202921
cmap=cmap, norm=norm, arrowsize=arrowsize,
29212922
arrowstyle=arrowstyle, minlength=minlength,
29222923
transform=transform, zorder=zorder, start_points=start_points,
29232924
maxlength=maxlength,
29242925
integration_direction=integration_direction,
2926+
broken_streamlines=broken_streamlines,
29252927
**({"data": data} if data is not None else {}))
29262928
sci(__ret.lines)
29272929
return __ret

lib/matplotlib/streamplot.py

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None,
1919
cmap=None, norm=None, arrowsize=1, arrowstyle='-|>',
2020
minlength=0.1, transform=None, zorder=None, start_points=None,
21-
maxlength=4.0, integration_direction='both'):
21+
maxlength=4.0, integration_direction='both',
22+
broken_streamlines=True):
2223
"""
2324
Draw streamlines of a vector flow.
2425
@@ -70,6 +71,10 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None,
7071
Integrate the streamline in forward, backward or both directions.
7172
data : indexable object, optional
7273
DATA_PARAMETER_PLACEHOLDER
74+
broken_streamlines : boolean, default: True
75+
If False, forces streamlines to continue until they
76+
leave the plot domain. If True, they may be terminated if they
77+
come too close to another streamline.
7378
7479
Returns
7580
-------
@@ -149,7 +154,7 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None,
149154
for xm, ym in _gen_starting_points(mask.shape):
150155
if mask[ym, xm] == 0:
151156
xg, yg = dmap.mask2grid(xm, ym)
152-
t = integrate(xg, yg)
157+
t = integrate(xg, yg, broken_streamlines)
153158
if t is not None:
154159
trajectories.append(t)
155160
else:
@@ -177,7 +182,7 @@ def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None,
177182
xg = np.clip(xg, 0, grid.nx - 1)
178183
yg = np.clip(yg, 0, grid.ny - 1)
179184

180-
t = integrate(xg, yg)
185+
t = integrate(xg, yg, broken_streamlines)
181186
if t is not None:
182187
trajectories.append(t)
183188

@@ -294,19 +299,19 @@ def data2grid(self, xd, yd):
294299
def grid2data(self, xg, yg):
295300
return xg / self.x_data2grid, yg / self.y_data2grid
296301

297-
def start_trajectory(self, xg, yg):
302+
def start_trajectory(self, xg, yg, broken_streamlines=True):
298303
xm, ym = self.grid2mask(xg, yg)
299-
self.mask._start_trajectory(xm, ym)
304+
self.mask._start_trajectory(xm, ym, broken_streamlines)
300305

301306
def reset_start_point(self, xg, yg):
302307
xm, ym = self.grid2mask(xg, yg)
303308
self.mask._current_xy = (xm, ym)
304309

305-
def update_trajectory(self, xg, yg):
310+
def update_trajectory(self, xg, yg, broken_streamlines=True):
306311
if not self.grid.within_grid(xg, yg):
307312
raise InvalidIndexError
308313
xm, ym = self.grid2mask(xg, yg)
309-
self.mask._update_trajectory(xm, ym)
314+
self.mask._update_trajectory(xm, ym, broken_streamlines)
310315

311316
def undo_trajectory(self):
312317
self.mask._undo_trajectory()
@@ -396,17 +401,17 @@ def __init__(self, density):
396401
def __getitem__(self, args):
397402
return self._mask[args]
398403

399-
def _start_trajectory(self, xm, ym):
404+
def _start_trajectory(self, xm, ym, broken_streamlines=True):
400405
"""Start recording streamline trajectory"""
401406
self._traj = []
402-
self._update_trajectory(xm, ym)
407+
self._update_trajectory(xm, ym, broken_streamlines)
403408

404409
def _undo_trajectory(self):
405410
"""Remove current trajectory from mask"""
406411
for t in self._traj:
407412
self._mask[t] = 0
408413

409-
def _update_trajectory(self, xm, ym):
414+
def _update_trajectory(self, xm, ym, broken_streamlines=True):
410415
"""
411416
Update current trajectory position in mask.
412417
@@ -418,7 +423,10 @@ def _update_trajectory(self, xm, ym):
418423
self._mask[ym, xm] = 1
419424
self._current_xy = (xm, ym)
420425
else:
421-
raise InvalidIndexError
426+
if broken_streamlines:
427+
raise InvalidIndexError
428+
else:
429+
pass
422430

423431

424432
class InvalidIndexError(Exception):
@@ -457,9 +465,9 @@ def backward_time(xi, yi):
457465
dxi, dyi = forward_time(xi, yi)
458466
return -dxi, -dyi
459467

460-
def integrate(x0, y0):
468+
def integrate(x0, y0, broken_streamlines=True):
461469
"""
462-
Return (N, 2) grid-coordinates of trajectory based on starting point.
470+
Return x, y grid-coordinates of trajectory based on starting point.
463471
464472
Integrate both forward and backward in time from starting point in
465473
grid coordinates.
@@ -472,17 +480,19 @@ def integrate(x0, y0):
472480
stotal, xy_traj = 0., []
473481

474482
try:
475-
dmap.start_trajectory(x0, y0)
483+
dmap.start_trajectory(x0, y0, broken_streamlines)
476484
except InvalidIndexError:
477485
return None
478486
if integration_direction in ['both', 'backward']:
479-
s, xyt = _integrate_rk12(x0, y0, dmap, backward_time, maxlength)
487+
s, xyt = _integrate_rk12(x0, y0, dmap, backward_time, maxlength,
488+
broken_streamlines)
480489
stotal += s
481490
xy_traj += xyt[::-1]
482491

483492
if integration_direction in ['both', 'forward']:
484493
dmap.reset_start_point(x0, y0)
485-
s, xyt = _integrate_rk12(x0, y0, dmap, forward_time, maxlength)
494+
s, xyt = _integrate_rk12(x0, y0, dmap, forward_time, maxlength,
495+
broken_streamlines)
486496
stotal += s
487497
xy_traj += xyt[1:]
488498

@@ -508,7 +518,7 @@ class OutOfBounds(IndexError):
508518
pass
509519

510520

511-
def _integrate_rk12(x0, y0, dmap, f, maxlength):
521+
def _integrate_rk12(x0, y0, dmap, f, maxlength, broken_streamlines=True):
512522
"""
513523
2nd-order Runge-Kutta algorithm with adaptive step size.
514524
@@ -588,7 +598,7 @@ def _integrate_rk12(x0, y0, dmap, f, maxlength):
588598
xi += dx2
589599
yi += dy2
590600
try:
591-
dmap.update_trajectory(xi, yi)
601+
dmap.update_trajectory(xi, yi, broken_streamlines)
592602
except InvalidIndexError:
593603
break
594604
if stotal + ds > maxlength:

lib/matplotlib/tests/test_streamplot.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,18 @@ def test_maxlength():
7777
ax.set(xlim=(None, 3.2555988021882305), ylim=(None, 3.078326760195413))
7878

7979

80+
@image_comparison(['streamplot_maxlength_no_broken.png'],
81+
remove_text=True, style='mpl20', tol=0.302)
82+
def test_maxlength_no_broken():
83+
x, y, U, V = swirl_velocity_field()
84+
ax = plt.figure().subplots()
85+
ax.streamplot(x, y, U, V, maxlength=10., start_points=[[0., 1.5]],
86+
linewidth=2, density=2, broken_streamlines=False)
87+
assert ax.get_xlim()[-1] == ax.get_ylim()[-1] == 3
88+
# Compatibility for old test image
89+
ax.set(xlim=(None, 3.2555988021882305), ylim=(None, 3.078326760195413))
90+
91+
8092
@image_comparison(['streamplot_direction.png'],
8193
remove_text=True, style='mpl20', tol=0.073)
8294
def test_direction():

0 commit comments

Comments
 (0)