使用曲线(路径跟随)向量在Python中进行流程可视化

16

我想在Python中绘制一个带有曲线箭头的向量场,就像在vfplot(见下文)或IDL中可以做的那样。

Boussinesq flow with curved vectors

你可以在matplotlib中使用quiver()接近,但是它限制了直线向量的使用(见下方左图),而streamplot()似乎不能对箭头长度或箭头位置进行有意义的控制(见下方右图),即使更改integration_directiondensitymaxlength也是如此。

Example matplotlib quiver and stream plots

那么,有没有Python库可以做到这一点?或者有没有让Matplotlib实现它的方法?


你能提供一些样本数据或函数来使用吗? - Thomas Kühn
1
@ThomasKühn 我也对这个问题很感兴趣。在这里,您可以找到matplotlib文档中streamplot的示例数据。https://matplotlib.org/gallery/images_contours_and_fields/plot_streamplot.html#sphx-glr-gallery-images-contours-and-fields-plot-streamplot-py - MaxPlankton
请查看以下链接:https://dev59.com/QVcP5IYBdhLWcg3wXIwy 和 https://dev59.com/fFoU5IYBdhLWcg3wYWSx - Marius Brits
如果有人感兴趣,我的简陋的 bare-bones matplotlib bolt-on 在这里:https://github.com/kieranmrhunt/curved-quivers。 - Kieran Hunt
https://stackoverflow.com/q/67347019/14105784 - Veenita Roy
5个回答

7
如果您查看matplotlib中包含的streamplot.py文件,可以在第196-202行(大约是这样,我不知道不同版本之间是否有所不同 - 我使用的是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,如果您能预先计算所需箭头的宽度,则可以在绘制箭头时修改笔画宽度。看起来这部分已经为您完成。

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

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

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()

示例matplotlib图表

简而言之:复制源代码并将箭头放在每个路径的末尾,而不是中间。然后使用您的streamplot而不是matplotlib streamplot。

编辑:我已经使线宽度变化。


这看起来像是正确的方法。我会尝试让长度与矢量场的大小成比例,从这里开始应该不太难(他天真地说)。 - Kieran Hunt

6

David Culbreth的修改开始,我重写了streamplot函数的部分代码以实现所需的行为。这些改进过多,无法在此一一列举,但包括长度归一化方法和禁用轨迹重叠检查等。我附加了两个新的曲线箭头图函数与原始的streamplotquiver的比较图像。

enter image description here enter image description here


2
这太棒了!您是否愿意提交您的“曲线箭头”代码以添加到Matplotlib代码库中?这似乎应该是核心图形功能。这是他们贡献页面的链接。 - David Culbreth
嗨,@Kieran Hunt。我尝试了你在Github上的脚本,并注意到如果你放大足够多,箭头不在曲线的最末端,而是在曲线长度的约90%处。有什么想法和解决方法吗? - Jason

6

以下是在vanilla pyplot中获得所需输出的方法(即不修改streamplot函数或任何复杂操作)。 作为提醒,目标是使用弯曲的箭头可视化矢量场,其长度与矢量的范数成比例。

技巧如下:

  1. 使用没有箭头的streamplot从给定点开始向后追踪(见)
  2. 从该点绘制quiver。 让quiver足够小,以便仅可见箭头
  3. 对每个种子重复1.和2.的操作,并将streamplot的长度按比例缩放以表示矢量的范数。
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')

这是结果:

曲线箭图


1
不需要在循环中使用 quiver(),因为输入参数是相同的。 - Jason
1
@Jason 确实如此!我按照你的建议修复了解决方案。 - Arthur Bauville

2

仅查看streamplot()文档,可在此处找到链接--如果您使用类似于streamplot( ... ,minlength = n/2, maxlength = n)的东西,其中n是所需长度--您将需要稍微调整这些数字以获得所需的图形。

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

以下是我使用streamplot()控制长度的示例--它基本上是从上面的示例中复制/粘贴/裁剪而来的。

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) "长度"并不对应任何有意义的东西,比如向量大小 - 据我所知,它只受本地流线密度的控制。 - Kieran Hunt
我无法完成你所寻求的...这似乎是实现新绘图类型的好机会...如果你能让它工作,应该提交以作为Matplotlib的一部分添加。 - David Culbreth

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接