【问题标题】:Flow visualisation in python using curved (path-following) vectors使用弯曲(路径跟随)向量在 python 中的流可视化
【发布时间】:2019-01-21 10:08:24
【问题描述】:

我想在 python 中绘制一个带有弯曲箭头的向量场,这可以在 vfplot(见下文)或 IDL 中完成。

您可以在 matplotlib 中接近,但使用 quiver() 会将您限制为直向量(见左下角),而 streamplot() 似乎不允许对箭头长度或箭头位置进行有意义的控制(见右下角),甚至更改 integration_directiondensitymaxlength 时。

那么,有没有可以做到这一点的 python 库?或者有没有办法让 matplotlib 做到这一点?

【问题讨论】:

标签: python matplotlib data-visualization vector-graphics


【解决方案1】:

只需查看streamplot() 上的文档,就可以找到here——如果你使用streamplot( ... ,minlength = n/2, maxlength = n) 之类的东西,其中n 是所需的长度——你需要稍微调整一下这些数字才能得到你想要的图表

您可以使用start_points 控制点,如@JohnKoch 提供的示例所示

这是我如何使用streamplot() 控制长度的示例——它几乎是从上面的example 直接复制/粘贴/裁剪。

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import matplotlib.patches as pat

w = 3
Y, X = np.mgrid[-w:w:100j, -w:w:100j]
U = -1 - X**2 + Y
V = 1 + X - Y**2
speed = np.sqrt(U*U + V*V)

fig = plt.figure(figsize=(14, 18))
gs = gridspec.GridSpec(nrows=3, ncols=2, height_ratios=[1, 1, 2])

grains = 10
tmp = tuple([x]*grains for x in np.linspace(-2, 2, grains))
xs = []
for x in tmp:
    xs += x
ys = tuple(np.linspace(-2, 2, grains))*grains


seed_points = np.array([list(xs), list(ys)])
arrowStyle = pat.ArrowStyle.Fancy()
# Varying color along a streamline
ax1 = fig.add_subplot(gs[0, 1])
strm = ax1.streamplot(X, Y, U, V, color=U, linewidth=1.5, cmap='winter', density=10,
                      minlength=0.001, maxlength = 0.1, arrowstyle='->',
                      integration_direction='forward', start_points = seed_points.T)
fig.colorbar(strm.lines)
ax1.set_title('Varying Color')

plt.tight_layout()
plt.show()

编辑:让它更漂亮,但仍然不是我们想要的。

【讨论】:

  • 我试过了,不幸的是(1)你不能强迫箭头位于流线的末端,(2)“长度”不对应任何有意义的东西,就像矢量幅度一样——据我所知,它只是由局部流线密度控制。
  • 我无法完成您正在寻找的内容...这看起来是一个实施新情节类型的好机会...如果您成功了,您应该提交它作为 matplotlib 的一部分添加
【解决方案2】:

如果您查看 matplotlib 中包含的 streamplot.py,在第 196 - 202 行(如果这在版本之间发生了变化,则为 idk - 我在 matplotlib 2.1.2 上),我们会看到以下内容:

 ... (to line 195)
    # Add arrows half way along each trajectory.
    s = np.cumsum(np.sqrt(np.diff(tx) ** 2 + np.diff(ty) ** 2))
    n = np.searchsorted(s, s[-1] / 2.)
    arrow_tail = (tx[n], ty[n])
    arrow_head = (np.mean(tx[n:n + 2]), np.mean(ty[n:n + 2]))
 ... (after line 196)

把那部分改成这个就行了(改变 n 的赋值):

 ... (to line 195)
    # Add arrows half way along each trajectory.
    s = np.cumsum(np.sqrt(np.diff(tx) ** 2 + np.diff(ty) ** 2))
    n = np.searchsorted(s, s[-1]) ### THIS IS THE EDITED LINE! ###
    arrow_tail = (tx[n], ty[n])
    arrow_head = (np.mean(tx[n:n + 2]), np.mean(ty[n:n + 2]))
 ... (after line 196)

如果您修改它以将箭头放在末尾,那么您可以根据自己的喜好生成更多箭头。

此外,从函数顶部的文档中,我们看到以下内容:

*linewidth* : numeric or 2d array
        vary linewidth when given a 2d array with the same shape as velocities.

线宽可以是numpy.ndarray,如果您可以预先计算所需的箭头宽度,您就可以在绘制箭头时修改铅笔宽度。看起来这部分已经为您完成了。

因此,结合缩短箭头 maxlength、增加密度和添加 start_points,以及调整函数以将箭头放在末端而不是中间,您可以获得所需的图形。

通过这些修改和以下代码,我能够得到更接近您想要的结果:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import matplotlib.patches as pat

w = 3
Y, X = np.mgrid[-w:w:100j, -w:w:100j]
U = -1 - X**2 + Y
V = 1 + X - Y**2
speed = np.sqrt(U*U + V*V)

fig = plt.figure(figsize=(14, 18))
gs = gridspec.GridSpec(nrows=3, ncols=2, height_ratios=[1, 1, 2])

grains = 10
tmp = tuple([x]*grains for x in np.linspace(-2, 2, grains))
xs = []
for x in tmp:
    xs += x
ys = tuple(np.linspace(-2, 2, grains))*grains


seed_points = np.array([list(xs), list(ys)])
# Varying color along a streamline
ax1 = fig.add_subplot(gs[0, 1])

strm = ax1.streamplot(X, Y, U, V, color=U, linewidth=np.array(5*np.random.random_sample((100, 100))**2 + 1), cmap='winter', density=10,
                      minlength=0.001, maxlength = 0.07, arrowstyle='fancy',
                      integration_direction='forward', start_points = seed_points.T)
fig.colorbar(strm.lines)
ax1.set_title('Varying Color')

plt.tight_layout()
plt.show()

tl;dr:去复制源代码,并将其更改为将箭头放在每条路径的末尾,而不是在中间。然后使用您的流图而不是 matplotlib 流图。

编辑:我得到了不同的线宽

【讨论】:

  • 这看起来是正确的方法。我将尝试获得与矢量场大小成比例的长度,从这里开始应该不会太难(他说得很天真)。
【解决方案3】:

David Culbreth's 修改开始,我将rewrote 块的streamplot 函数实现所需的行为。有点太多,无法在此处全部指定,但它包括长度归一化方法并禁用轨迹重叠检查。我添加了两个新的curved quiver 函数与原始streamplotquiver 的比较。

【讨论】:

  • 这太棒了!您愿意提交您的curved quivers 代码以添加到matplotlib 代码库吗?这似乎应该是核心图形功能。这是他们contrib page的链接
  • 嗨@Kieran Hunt。我在 github 上尝试了您的脚本,并注意到如果您放大到足够大,箭头不在曲线的最末端,而是在曲线长度的约 90% 处。知道为什么以及如何解决吗?
【解决方案4】:

这是一种在 vanilla pyplot 中获得所需输出的方法(即,无需修改 streamplot 函数或任何花哨的东西)。提醒一下,我们的目标是用弯曲箭头可视化一个向量场,其长度与向量的范数成正比。

诀窍是:

  1. 制作从给定点向后追溯的不带箭头的流图(参见)
  2. 从该点开始绘制箭筒。使箭筒足够小,以便只有箭头可见
  3. 在循环中为每个种子重复 1. 和 2. 并将流图的长度缩放为与向量的范数成比例。
import matplotlib.pyplot as plt
import numpy as np
w = 3
Y, X = np.mgrid[-w:w:8j, -w:w:8j]

U = -Y
V = X
norm = np.sqrt(U**2 + V**2)
norm_flat = norm.flatten()

start_points = np.array([X.flatten(),Y.flatten()]).T

plt.clf()
scale = .2/np.max(norm)

plt.subplot(121)
plt.title('scaling only the length')
for i in range(start_points.shape[0]):
    plt.streamplot(X,Y,U,V, color='k', start_points=np.array([start_points[i,:]]),minlength=.95*norm_flat[i]*scale, maxlength=1.0*norm_flat[i]*scale,
                integration_direction='backward', density=10, arrowsize=0.0)
plt.quiver(X,Y,U/norm, V/norm,scale=30)
plt.axis('square')



plt.subplot(122)
plt.title('scaling length, arrowhead and linewidth')
for i in range(start_points.shape[0]):
    plt.streamplot(X,Y,U,V, color='k', start_points=np.array([start_points[i,:]]),minlength=.95*norm_flat[i]*scale, maxlength=1.0*norm_flat[i]*scale,
                integration_direction='backward', density=10, arrowsize=0.0, linewidth=.5*norm_flat[i])
plt.quiver(X,Y,U/np.max(norm), V/np.max(norm),scale=30)

plt.axis('square')

结果如下:

【讨论】:

  • 不需要将quiver()放在循环中,因为输入参数是相同的。
  • @Jason Indeed!,我按照您的建议修复了解决方案
猜你喜欢
  • 1970-01-01
  • 2013-11-29
  • 2021-10-17
  • 2021-06-18
  • 2019-01-28
  • 2011-07-17
  • 1970-01-01
  • 2016-10-26
  • 1970-01-01
相关资源
最近更新 更多