streamplot.py
Go to the documentation of this file.
1 """
2 Streamline plotting for 2D vector fields.
3 
4 """
5 from __future__ import (absolute_import, division, print_function,
6  unicode_literals)
7 
8 from matplotlib.externals import six
9 from matplotlib.externals.six.moves import xrange
10 
11 import numpy as np
12 import matplotlib
13 import matplotlib.cm as cm
14 import matplotlib.colors as mcolors
15 import matplotlib.collections as mcollections
16 import matplotlib.patches as patches
17 
18 
19 __all__ = ['streamplot']
20 
21 
22 def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None,
23  cmap=None, norm=None, arrowsize=1, arrowstyle='-|>',
24  minlength=0.1, maxlength=4.0, transform=None, zorder=2,
25  start_points=None, integration_direction='both'):
26  """Draws streamlines of a vector flow.
27 
28  *x*, *y* : 1d arrays
29  an *evenly spaced* grid.
30  *u*, *v* : 2d arrays
31  x and y-velocities. Number of rows should match length of y, and
32  the number of columns should match x.
33  *density* : float or 2-tuple
34  Controls the closeness of streamlines. When `density = 1`, the domain
35  is divided into a 30x30 grid---*density* linearly scales this grid.
36  Each cell in the grid can have, at most, one traversing streamline.
37  For different densities in each direction, use [density_x, density_y].
38  *linewidth* : numeric or 2d array
39  vary linewidth when given a 2d array with the same shape as velocities.
40  *color* : matplotlib color code, or 2d array
41  Streamline color. When given an array with the same shape as
42  velocities, *color* values are converted to colors using *cmap*.
43  *cmap* : :class:`~matplotlib.colors.Colormap`
44  Colormap used to plot streamlines and arrows. Only necessary when using
45  an array input for *color*.
46  *norm* : :class:`~matplotlib.colors.Normalize`
47  Normalize object used to scale luminance data to 0, 1. If None, stretch
48  (min, max) to (0, 1). Only necessary when *color* is an array.
49  *arrowsize* : float
50  Factor scale arrow size.
51  *arrowstyle* : str
52  Arrow style specification.
53  See :class:`~matplotlib.patches.FancyArrowPatch`.
54  *minlength* : float
55  Minimum length of streamline in axes coordinates.
56  *maxlength* : float
57  Maximum length of streamline in axes coordinates.
58  *start_points*: Nx2 array
59  Coordinates of starting points for the streamlines.
60  In data coordinates, the same as the ``x`` and ``y`` arrays.
61  *integration_direction* : ['foward','backward','both']
62  Integrate the streamline in forward, backward or both directions.
63  *zorder* : int
64  any number
65 
66  Returns:
67 
68  *stream_container* : StreamplotSet
69  Container object with attributes
70 
71  - lines: `matplotlib.collections.LineCollection` of streamlines
72 
73  - arrows: collection of `matplotlib.patches.FancyArrowPatch`
74  objects representing arrows half-way along stream
75  lines.
76 
77  This container will probably change in the future to allow changes
78  to the colormap, alpha, etc. for both lines and arrows, but these
79  changes should be backward compatible.
80 
81  """
82  grid = Grid(x, y)
83  mask = StreamMask(density)
84  dmap = DomainMap(grid, mask)
85 
86  # default to data coordinates
87  if transform is None:
88  transform = axes.transData
89 
90  if color is None and 'color' in axes._get_lines._prop_keys:
91  color = six.next(axes._get_lines.prop_cycler)['color']
92 
93  if linewidth is None:
94  linewidth = matplotlib.rcParams['lines.linewidth']
95 
96  line_kw = {}
97  arrow_kw = dict(arrowstyle=arrowstyle, mutation_scale=10 * arrowsize)
98 
99  if integration_direction not in ['both', 'forward', 'backward']:
100  errstr = "Integration direction " \
101  "'%s' not recognised." % integration_direction
102  errstr += "Expected 'both', 'forward' or 'backward'."
103  raise ValueError(errstr)
104 
105  if integration_direction == 'both':
106  maxlength /= 2.
107 
108  use_multicolor_lines = isinstance(color, np.ndarray)
109  if use_multicolor_lines:
110  if color.shape != grid.shape:
111  msg = "If 'color' is given, must have the shape of 'Grid(x,y)'"
112  raise ValueError(msg)
113  line_colors = []
114  color = np.ma.masked_invalid(color)
115  else:
116  line_kw['color'] = color
117  arrow_kw['color'] = color
118 
119  if isinstance(linewidth, np.ndarray):
120  if linewidth.shape != grid.shape:
121  msg = "If 'linewidth' is given, must have the shape of 'Grid(x,y)'"
122  raise ValueError(msg)
123  line_kw['linewidth'] = []
124  else:
125  line_kw['linewidth'] = linewidth
126  arrow_kw['linewidth'] = linewidth
127 
128  line_kw['zorder'] = zorder
129  arrow_kw['zorder'] = zorder
130 
131 
132  if (u.shape != grid.shape) or (v.shape != grid.shape):
133  msg = "'u' and 'v' must be of shape 'Grid(x,y)'" raise ValueError(msg)
134 
135  u = np.ma.masked_invalid(u)
136  v = np.ma.masked_invalid(v)
137 
138  integrate = get_integrator(u, v, dmap, minlength, maxlength,
139  integration_direction)
140 
141  trajectories = []
142  if start_points is None:
143  for xm, ym in _gen_starting_points(mask.shape):
144  if mask[ym, xm] == 0:
145  xg, yg = dmap.mask2grid(xm, ym)
146  t = integrate(xg, yg)
147  if t is not None:
148  trajectories.append(t)
149  else:
150  # Convert start_points from data to array coords
151  # Shift the seed points from the bottom left of the data so that
152  # data2grid works properly.
153  sp2 = np.asanyarray(start_points, dtype=np.float).copy()
154  sp2[:, 0] += np.abs(x[0])
155  sp2[:, 1] += np.abs(y[0])
156  for xs, ys in sp2:
157  xg, yg = dmap.data2grid(xs, ys)
158  t = integrate(xg, yg)
159  if t is not None:
160  trajectories.append(t)
161 
162  if use_multicolor_lines:
163  if norm is None:
164  norm = mcolors.Normalize(color.min(), color.max())
165  if cmap is None:
166  cmap = cm.get_cmap(matplotlib.rcParams['image.cmap'])
167  else:
168  cmap = cm.get_cmap(cmap)
169 
170  streamlines = []
171  arrows = []
172  for t in trajectories:
173  tgx = np.array(t[0])
174  tgy = np.array(t[1])
175  # Rescale from grid-coordinates to data-coordinates.
176  tx = np.array(t[0]) * grid.dx + grid.x_origin
177  ty = np.array(t[1]) * grid.dy + grid.y_origin
178 
179  points = np.transpose([tx, ty]).reshape(-1, 1, 2)
180  streamlines.extend(np.hstack([points[:-1], points[1:]]))
181 
182  # Add arrows half way along each trajectory.
183  s = np.cumsum(np.sqrt(np.diff(tx) ** 2 + np.diff(ty) ** 2))
184  n = np.searchsorted(s, s[-1] / 2.)
185  arrow_tail = (tx[n], ty[n])
186  arrow_head = (np.mean(tx[n:n + 2]), np.mean(ty[n:n + 2]))
187 
188  if isinstance(linewidth, np.ndarray):
189  line_widths = interpgrid(linewidth, tgx, tgy)[:-1]
190  line_kw['linewidth'].extend(line_widths)
191  arrow_kw['linewidth'] = line_widths[n]
192 
193  if use_multicolor_lines:
194  color_values = interpgrid(color, tgx, tgy)[:-1]
195  line_colors.append(color_values)
196  arrow_kw['color'] = cmap(norm(color_values[n]))
197 
198  p = patches.FancyArrowPatch(arrow_tail,
199  arrow_head,
200  transform=transform,
201  #margins=False,
202  **arrow_kw)
203  axes.add_patch(p)
204  arrows.append(p)
205 
206  lc = mcollections.LineCollection(streamlines,
207  transform=transform,
208  #margins=False,
209  **line_kw)
210  if use_multicolor_lines:
211  lc.set_array(np.ma.hstack(line_colors))
212  lc.set_cmap(cmap)
213  lc.set_norm(norm)
214  axes.add_collection(lc)
215  axes.autoscale_view()
216 
217  ac = matplotlib.collections.PatchCollection(arrows)#, margins=False)
218  stream_container = StreamplotSet(lc, ac)
219  return stream_container
220 
221 
222 class StreamplotSet(object):
224  def __init__(self, lines, arrows, **kwargs):
225  self.lines = lines
226  self.arrows = arrows
228 
229 # Coordinate definitions
230 # ========================
231 
232 class DomainMap(object):
233  """Map representing different coordinate systems.
234 
235  Coordinate definitions:
236 
237  * axes-coordinates goes from 0 to 1 in the domain.
238  * data-coordinates are specified by the input x-y coordinates.
239  * grid-coordinates goes from 0 to N and 0 to M for an N x M grid,
240  where N and M match the shape of the input data.
241  * mask-coordinates goes from 0 to N and 0 to M for an N x M mask,
242  where N and M are user-specified to control the density of streamlines.
243 
244  This class also has methods for adding trajectories to the StreamMask.
245  Before adding a trajectory, run `start_trajectory` to keep track of regions
246  crossed by a given trajectory. Later, if you decide the trajectory is bad
247  (e.g., if the trajectory is very short) just call `undo_trajectory`.
248  """
249 
250  def __init__(self, grid, mask):
251  self.grid = grid
252  self.mask = mask
253  # Constants for conversion between grid- and mask-coordinates
254  self.x_grid2mask = float(mask.nx - 1) / grid.nx
255  self.y_grid2mask = float(mask.ny - 1) / grid.ny
257  self.x_mask2grid = 1. / self.x_grid2mask
258  self.y_mask2grid = 1. / self.y_grid2mask
260  self.x_data2grid = grid.nx / grid.width
261  self.y_data2grid = grid.ny / grid.height
263  def grid2mask(self, xi, yi):
264  """Return nearest space in mask-coords from given grid-coords."""
265  return (int((xi * self.x_grid2mask) + 0.5),
266  int((yi * self.y_grid2mask) + 0.5))
267 
268  def mask2grid(self, xm, ym):
269  return xm * self.x_mask2grid, ym * self.y_mask2grid
270 
271  def data2grid(self, xd, yd):
272  return xd * self.x_data2grid, yd * self.y_data2grid
273 
274  def start_trajectory(self, xg, yg):
275  xm, ym = self.grid2mask(xg, yg)
276  self.mask._start_trajectory(xm, ym)
277 
278  def reset_start_point(self, xg, yg):
279  xm, ym = self.grid2mask(xg, yg)
280  self.mask._current_xy = (xm, ym)
281 
282  def update_trajectory(self, xg, yg):
283  if not self.grid.within_grid(xg, yg):
284  raise InvalidIndexError
285  xm, ym = self.grid2mask(xg, yg)
286  self.mask._update_trajectory(xm, ym)
287 
288  def undo_trajectory(self):
289  self.mask._undo_trajectory()
290 
291 
292 class Grid(object):
293  """Grid of data."""
294  def __init__(self, x, y):
296  if x.ndim == 1:
297  pass
298  elif x.ndim == 2:
299  x_row = x[0, :]
300  if not np.allclose(x_row, x):
301  raise ValueError("The rows of 'x' must be equal")
302  x = x_row
303  else:
304  raise ValueError("'x' can have at maximum 2 dimensions")
305 
306  if y.ndim == 1:
307  pass
308  elif y.ndim == 2:
309  y_col = y[:, 0]
310  if not np.allclose(y_col, y.T):
311  raise ValueError("The columns of 'y' must be equal")
312  y = y_col
313  else:
314  raise ValueError("'y' can have at maximum 2 dimensions")
315 
316  self.nx = len(x)
317  self.ny = len(y)
319  self.dx = x[1] - x[0]
320  self.dy = y[1] - y[0]
322  self.x_origin = x[0]
323  self.y_origin = y[0]
325  self.width = x[-1] - x[0]
326  self.height = y[-1] - y[0]
328  @property
329  def shape(self):
330  return self.ny, self.nx
331 
332  def within_grid(self, xi, yi):
333  """Return True if point is a valid index of grid."""
334  # Note that xi/yi can be floats; so, for example, we can't simply check
335  # `xi < self.nx` since `xi` can be `self.nx - 1 < xi < self.nx`
336  return xi >= 0 and xi <= self.nx - 1 and yi >= 0 and yi <= self.ny - 1
337 
338 
339 class StreamMask(object):
340  """Mask to keep track of discrete regions crossed by streamlines.
341 
342  The resolution of this grid determines the approximate spacing between
343  trajectories. Streamlines are only allowed to pass through zeroed cells:
344  When a streamline enters a cell, that cell is set to 1, and no new
345  streamlines are allowed to enter.
346  """
347 
348  def __init__(self, density):
349  if np.isscalar(density):
350  if density <= 0:
351  raise ValueError("If a scalar, 'density' must be positive")
352  self.nx = self.ny = int(30 * density)
353  else:
354  if len(density) != 2:
355  raise ValueError("'density' can have at maximum 2 dimensions")
356  self.nx = int(30 * density[0])
357  self.ny = int(30 * density[1])
358  self._mask = np.zeros((self.ny, self.nx))
359  self.shape = self._mask.shape
361  self._current_xy = None
363  def __getitem__(self, *args):
364  return self._mask.__getitem__(*args)
365 
366  def _start_trajectory(self, xm, ym):
367  """Start recording streamline trajectory"""
368  self._traj = []
369  self._update_trajectory(xm, ym)
370 
371  def _undo_trajectory(self):
372  """Remove current trajectory from mask"""
373  for t in self._traj:
374  self._mask.__setitem__(t, 0)
375 
376  def _update_trajectory(self, xm, ym):
377  """Update current trajectory position in mask.
378 
379  If the new position has already been filled, raise `InvalidIndexError`.
380  """
381  if self._current_xy != (xm, ym):
382  if self[ym, xm] == 0:
383  self._traj.append((ym, xm))
384  self._mask[ym, xm] = 1
385  self._current_xy = (xm, ym)
386  else:
387  raise InvalidIndexError
388 
389 
391  pass
392 
393 
394 class TerminateTrajectory(Exception):
395  pass
396 
397 
398 # Integrator definitions
399 #========================
400 
401 def get_integrator(u, v, dmap, minlength, maxlength, integration_direction):
403  # rescale velocity onto grid-coordinates for integrations.
404  u, v = dmap.data2grid(u, v)
405 
406  # speed (path length) will be in axes-coordinates
407  u_ax = u / dmap.grid.nx
408  v_ax = v / dmap.grid.ny
409  speed = np.ma.sqrt(u_ax ** 2 + v_ax ** 2)
410 
411  def forward_time(xi, yi):
412  ds_dt = interpgrid(speed, xi, yi)
413  if ds_dt == 0:
414  raise TerminateTrajectory()
415  dt_ds = 1. / ds_dt
416  ui = interpgrid(u, xi, yi)
417  vi = interpgrid(v, xi, yi)
418  return ui * dt_ds, vi * dt_ds
419 
420  def backward_time(xi, yi):
421  dxi, dyi = forward_time(xi, yi)
422  return -dxi, -dyi
423 
424  def integrate(x0, y0):
425  """Return x, y grid-coordinates of trajectory based on starting point.
426 
427  Integrate both forward and backward in time from starting point in
428  grid coordinates.
429 
430  Integration is terminated when a trajectory reaches a domain boundary
431  or when it crosses into an already occupied cell in the StreamMask. The
432  resulting trajectory is None if it is shorter than `minlength`.
433  """
434 
435  stotal, x_traj, y_traj = 0., [], []
436 
437  dmap.start_trajectory(x0, y0)
438  if integration_direction in ['both', 'backward']:
439  s, xt, yt = _integrate_rk12(x0, y0, dmap, backward_time, maxlength)
440  stotal += s
441  x_traj += xt[::-1]
442  y_traj += yt[::-1]
443 
444  if integration_direction in ['both', 'forward']:
445  dmap.reset_start_point(x0, y0)
446  s, xt, yt = _integrate_rk12(x0, y0, dmap, forward_time, maxlength)
447  if len(x_traj) > 0:
448  xt = xt[1:]
449  yt = yt[1:]
450  stotal += s
451  x_traj += xt
452  y_traj += yt
453 
454  if stotal > minlength:
455  return x_traj, y_traj
456  else: # reject short trajectories
457  dmap.undo_trajectory()
458  return None
459 
460  return integrate
461 
462 
463 def _integrate_rk12(x0, y0, dmap, f, maxlength):
464  """2nd-order Runge-Kutta algorithm with adaptive step size.
465 
466  This method is also referred to as the improved Euler's method, or Heun's
467  method. This method is favored over higher-order methods because:
468 
469  1. To get decent looking trajectories and to sample every mask cell
470  on the trajectory we need a small timestep, so a lower order
471  solver doesn't hurt us unless the data is *very* high resolution.
472  In fact, for cases where the user inputs
473  data smaller or of similar grid size to the mask grid, the higher
474  order corrections are negligible because of the very fast linear
475  interpolation used in `interpgrid`.
476 
477  2. For high resolution input data (i.e. beyond the mask
478  resolution), we must reduce the timestep. Therefore, an adaptive
479  timestep is more suited to the problem as this would be very hard
480  to judge automatically otherwise.
481 
482  This integrator is about 1.5 - 2x as fast as both the RK4 and RK45
483  solvers in most setups on my machine. I would recommend removing the
484  other two to keep things simple.
485  """
486  # This error is below that needed to match the RK4 integrator. It
487  # is set for visual reasons -- too low and corners start
488  # appearing ugly and jagged. Can be tuned.
489  maxerror = 0.003
490 
491  # This limit is important (for all integrators) to avoid the
492  # trajectory skipping some mask cells. We could relax this
493  # condition if we use the code which is commented out below to
494  # increment the location gradually. However, due to the efficient
495  # nature of the interpolation, this doesn't boost speed by much
496  # for quite a bit of complexity.
497  maxds = min(1. / dmap.mask.nx, 1. / dmap.mask.ny, 0.1)
498 
499  ds = maxds
500  stotal = 0
501  xi = x0
502  yi = y0
503  xf_traj = []
504  yf_traj = []
505  iterations = 0
506 
507  while dmap.grid.within_grid(xi, yi) and iterations < 10000:
508  xf_traj.append(xi)
509  yf_traj.append(yi)
510  try:
511  k1x, k1y = f(xi, yi)
512  k2x, k2y = f(xi + ds * k1x,
513  yi + ds * k1y)
514  except IndexError:
515  # Out of the domain on one of the intermediate integration steps.
516  # Take an Euler step to the boundary to improve neatness.
517  ds, xf_traj, yf_traj = _euler_step(xf_traj, yf_traj, dmap, f)
518  stotal += ds
519  break
520  except TerminateTrajectory:
521  break
522 
523  dx1 = ds * k1x
524  dy1 = ds * k1y
525  dx2 = ds * 0.5 * (k1x + k2x)
526  dy2 = ds * 0.5 * (k1y + k2y)
527 
528  nx, ny = dmap.grid.shape
529  # Error is normalized to the axes coordinates
530  error = np.sqrt(((dx2 - dx1) / nx) ** 2 + ((dy2 - dy1) / ny) ** 2)
531 
532  # Only save step if within error tolerance
533  if error < maxerror:
534  xi += dx2
535  yi += dy2
536  try:
537  dmap.update_trajectory(xi, yi)
538  except InvalidIndexError:
539  break
540  if (stotal + ds) > maxlength:
541  break
542  stotal += ds
543 
544  # recalculate stepsize based on step error
545  if error == 0:
546  ds = maxds
547  else:
548  ds = min(maxds, 0.85 * ds * (maxerror / error) ** 0.5)
549 
550  iterations += 1
551 
552  return stotal, xf_traj, yf_traj
553 
554 
555 def _euler_step(xf_traj, yf_traj, dmap, f):
556  """Simple Euler integration step that extends streamline to boundary."""
557  ny, nx = dmap.grid.shape
558  xi = xf_traj[-1]
559  yi = yf_traj[-1]
560  cx, cy = f(xi, yi)
561  if cx == 0:
562  dsx = np.inf
563  elif cx < 0:
564  dsx = xi / -cx
565  else:
566  dsx = (nx - 1 - xi) / cx
567  if cy == 0:
568  dsy = np.inf
569  elif cy < 0:
570  dsy = yi / -cy
571  else:
572  dsy = (ny - 1 - yi) / cy
573  ds = min(dsx, dsy)
574  xf_traj.append(xi + cx * ds)
575  yf_traj.append(yi + cy * ds)
576  return ds, xf_traj, yf_traj
577 
578 
579 # Utility functions
580 # ========================
581 
582 def interpgrid(a, xi, yi):
583  """Fast 2D, linear interpolation on an integer grid"""
584 
585  Ny, Nx = np.shape(a)
586  if isinstance(xi, np.ndarray):
587  x = xi.astype(np.int)
588  y = yi.astype(np.int)
589  # Check that xn, yn don't exceed max index
590  xn = np.clip(x + 1, 0, Nx - 1)
591  yn = np.clip(y + 1, 0, Ny - 1)
592  else:
593  x = np.int(xi)
594  y = np.int(yi)
595  # conditional is faster than clipping for integers
596  if x == (Nx - 2):
597  xn = x
598  else:
599  xn = x + 1
600  if y == (Ny - 2):
601  yn = y
602  else:
603  yn = y + 1
604 
605  a00 = a[y, x]
606  a01 = a[y, xn]
607  a10 = a[yn, x]
608  a11 = a[yn, xn]
609  xt = xi - x
610  yt = yi - y
611  a0 = a00 * (1 - xt) + a01 * xt
612  a1 = a10 * (1 - xt) + a11 * xt
613  ai = a0 * (1 - yt) + a1 * yt
614 
615  if not isinstance(xi, np.ndarray):
616  if np.ma.is_masked(ai):
617  raise TerminateTrajectory
618 
619  return ai
620 
621 
622 def _gen_starting_points(shape):
623  """Yield starting points for streamlines.
624 
625  Trying points on the boundary first gives higher quality streamlines.
626  This algorithm starts with a point on the mask corner and spirals inward.
627  This algorithm is inefficient, but fast compared to rest of streamplot.
628  """
629  ny, nx = shape
630  xfirst = 0
631  yfirst = 1
632  xlast = nx - 1
633  ylast = ny - 1
634  x, y = 0, 0
635  i = 0
636  direction = 'right'
637  for i in xrange(nx * ny):
638 
639  yield x, y
640 
641  if direction == 'right':
642  x += 1
643  if x >= xlast:
644  xlast -= 1
645  direction = 'up'
646  elif direction == 'up':
647  y += 1
648  if y >= ylast:
649  ylast -= 1
650  direction = 'left'
651  elif direction == 'left':
652  x -= 1
653  if x <= xfirst:
654  xfirst += 1
655  direction = 'down'
656  elif direction == 'down':
657  y -= 1
658  if y <= yfirst:
659  yfirst += 1
660  direction = 'right'
661 
def __getitem__(self, args)
Definition: streamplot.py:364
def start_trajectory(self, xg, yg)
Definition: streamplot.py:275
def reset_start_point(self, xg, yg)
Definition: streamplot.py:279
type(dict_typ) function, pointer, public dict(n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13, n14, n15, n16, n17, n18, n19, n20)
Construct a new dictionary from several key/value pairs. Together with the Assign subroutine and over...
def _gen_starting_points(shape)
Definition: streamplot.py:623
def min(send, axis=None)
Definition: coplot.py:114
def grid2mask(self, xi, yi)
Definition: streamplot.py:264
def _euler_step(xf_traj, yf_traj, dmap, f)
Definition: streamplot.py:556
def mask2grid(self, xm, ym)
Definition: streamplot.py:269
subroutine append(buffer, i, d)
def _integrate_rk12(x0, y0, dmap, f, maxlength)
Definition: streamplot.py:464
def _start_trajectory(self, xm, ym)
Definition: streamplot.py:367
def __init__(self, density)
Definition: streamplot.py:349
def within_grid(self, xi, yi)
Definition: streamplot.py:333
def update_trajectory(self, xg, yg)
Definition: streamplot.py:283
real function, public integrate(fkt, xl, xr, eps, plist, method)
Numerical integration function.
def streamplot(axes, x, y, u, v, density=1, linewidth=None, color=None, cmap=None, norm=None, arrowsize=1, arrowstyle='-|>', minlength=0.1, maxlength=4.0, transform=None, zorder=2, start_points=None, integration_direction='both')
Definition: streamplot.py:25
def __init__(self, x, y)
Definition: streamplot.py:295
def _update_trajectory(self, xm, ym)
Definition: streamplot.py:377
def __init__(self, lines, arrows, kwargs)
Definition: streamplot.py:225
def data2grid(self, xd, yd)
Definition: streamplot.py:272
def __init__(self, grid, mask)
Definition: streamplot.py:251
def _undo_trajectory(self)
Definition: streamplot.py:372
def undo_trajectory(self)
Definition: streamplot.py:289
def get_integrator(u, v, dmap, minlength, maxlength, integration_direction)
Definition: streamplot.py:402
def shape(self)
Definition: streamplot.py:330
def interpgrid(a, xi, yi)
Definition: streamplot.py:583