简体   繁体   English

matplotlib流图上的箭头数

[英]Number of arrowheads on matplotlib streamplot

Is there anyway to increase the number of arrowheads on a matplotlib streamplot? 无论如何,有没有增加matplotlib流图上箭头的数量? Right now it appears as if three is only one arrowhead per streamline, which is a problem if I want to change to x/y axes limits to zoom in on the data. 现在,似乎每个流线只有三个箭头,如果我想更改为x / y轴限制以放大数据,这是一个问题。

I'm not sure about just increasing the number of arrowheads - but you can increase the density of streamlines with the density parameter in the streamplot function, here's the documentation: 我不确定只是增加箭头的数量-但您可以使用streamplot函数中的density参数来增加流线的密度 ,这是文档:

*density* : float or 2-tuple
    Controls the closeness of streamlines. When `density = 1`, the domain
    is divided into a 30x30 grid---*density* linearly scales this grid.
    Each cell in the grid can have, at most, one traversing streamline.
    For different densities in each direction, use [density_x, density_y].

Here is an example: 这是一个例子:

import matplotlib.pyplot as plt
import numpy as np

x = np.arange(0,20,1)
y = np.arange(0,20,1)

u=np.random.random((x.shape[0], y.shape[0]))
v=np.random.random((x.shape[0], y.shape[0]))


fig, ax = plt.subplots(2,2)

ax[0,0].streamplot(x,y,u,v,density=1)
ax[0,0].set_title('Original')

ax[0,1].streamplot(x,y,u,v,density=4)
ax[0,1].set_xlim(5,10)
ax[0,1].set_ylim(5,10)
ax[0,1].set_title('Zoomed, higher density')

ax[1,1].streamplot(x,y,u,v,density=1)
ax[1,1].set_xlim(5,10)
ax[1,1].set_ylim(5,10)
ax[1,1].set_title('Zoomed, same density')

ax[1,0].streamplot(x,y,u,v,density=4)
ax[1,0].set_title('Original, higher density')

fig.show()

在此处输入图片说明

Building on @Richard_wth's answer, I wrote a function to provide control on the location of the arrows on a streamplot. 在@Richard_wth的答案的基础上,我编写了一个函数来控制流图上箭头的位置。 One can choose n arrows per streamline, or choose to have the arrows equally spaced on a streamline. 每个流线可以选择n箭头,也可以选择使箭头在流线上等距分布。

First, you do a normal streamplot , until you are happy with the location and number of streamlines. 首先,进行普通的streamplot ,直到streamplot线的位置和数量感到满意为止。 You keep the returned argument sp . 您保留返回的参数sp For instance: 例如:

sp = ax.streamplot(x,y,u,v,arrowstyle='-',density=10)

What's important here is to have arrowstyle='-' so that arrows are not displayed. 在这里重要的是使用arrowstyle='-'以便不显示箭头。

Then, you can call the function streamQuiver (provided below) to control the arrows on the each streamline. 然后,您可以调用函数streamQuiver (下面提供)来控制每个流线上的箭头。 If you want 3 arrows per streamline: 如果您希望每个流线3个箭头:

streamQuiver(ax, sp, n=3, ...)

If you want a streamline every 1.5 curvilinear length: 如果要每1.5曲线长度使用一条流线:

streamQuiver(ax, sp, spacing=1.5, ...)

where ... are options that would be passed to quiver . 其中...是将传递到quiver选项。 The function streamQuiver is probably not fully bulletproof and may need some additional handling for particular cases. 函数streamQuiver可能不是完全防弹的,在某些情况下可能需要一些其他处理。 It relies on 4 subfunctions: 它依赖于4个子功能:

  • curve_coord to get the curvilinear length along a path curve_coord获取沿路径的曲线长度
  • curve extract to extract equidistant point along a path curve extract以提取沿路径的等距点
  • seg_to_lines to convert the segments from streamplot into continuous lines. seg_to_lines将段从流图转换为连续的线。 There might be a better way to do that! 可能会有更好的方法!
  • lines_to_arrows : this is the main function that extract arrows on each lines lines_to_arrows :这是提取每行箭头的主要功能

Here's an example where the arrows are at equidistant points on each streamlines. 这是一个示例,其中箭头在每条流线上的等距点处。

import numpy as np
import matplotlib.pyplot as plt

def streamQuiver(ax,sp,*args,spacing=None,n=5,**kwargs):
    """ Plot arrows from streamplot data  
    The number of arrows per streamline is controlled either by `spacing` or by `n`.
    See `lines_to_arrows`.
    """
    def curve_coord(line=None):
        """ return curvilinear coordinate """
        x=line[:,0]
        y=line[:,1]
        s     = np.zeros(x.shape)
        s[1:] = np.sqrt((x[1:]-x[0:-1])**2+ (y[1:]-y[0:-1])**2)
        s     = np.cumsum(s)                                  
        return s

    def curve_extract(line,spacing,offset=None):
        """ Extract points at equidistant space along a curve"""
        x=line[:,0]
        y=line[:,1]
        if offset is None:
            offset=spacing/2
        # Computing curvilinear length
        s = curve_coord(line)
        offset=np.mod(offset,s[-1]) # making sure we always get one point
        # New (equidistant) curvilinear coordinate
        sExtract=np.arange(offset,s[-1],spacing)
        # Interpolating based on new curvilinear coordinate
        xx=np.interp(sExtract,s,x);
        yy=np.interp(sExtract,s,y);
        return np.array([xx,yy]).T

    def seg_to_lines(seg):
        """ Convert a list of segments to a list of lines """ 
        def extract_continuous(i):
            x=[]
            y=[]
            # Special case, we have only 1 segment remaining:
            if i==len(seg)-1:
                x.append(seg[i][0,0])
                y.append(seg[i][0,1])
                x.append(seg[i][1,0])
                y.append(seg[i][1,1])
                return i,x,y
            # Looping on continuous segment
            while i<len(seg)-1:
                # Adding our start point
                x.append(seg[i][0,0])
                y.append(seg[i][0,1])
                # Checking whether next segment continues our line
                Continuous= all(seg[i][1,:]==seg[i+1][0,:])
                if not Continuous:
                    # We add our end point then
                    x.append(seg[i][1,0])
                    y.append(seg[i][1,1])
                    break
                elif i==len(seg)-2:
                    # we add the last segment
                    x.append(seg[i+1][0,0])
                    y.append(seg[i+1][0,1])
                    x.append(seg[i+1][1,0])
                    y.append(seg[i+1][1,1])
                i=i+1
            return i,x,y
        lines=[]
        i=0
        while i<len(seg):
            iEnd,x,y=extract_continuous(i)
            lines.append(np.array( [x,y] ).T)
            i=iEnd+1
        return lines

    def lines_to_arrows(lines,n=5,spacing=None,normalize=True):
        """ Extract "streamlines" arrows from a set of lines 
        Either: `n` arrows per line
            or an arrow every `spacing` distance
        If `normalize` is true, the arrows have a unit length
        """
        if spacing is None:
            # if n is provided we estimate the spacing based on each curve lenght)
            spacing = [ curve_coord(l)[-1]/n for l in lines]
        try:
            len(spacing)
        except:
            spacing=[spacing]*len(lines)

        lines_s=[curve_extract(l,spacing=sp,offset=sp/2)         for l,sp in zip(lines,spacing)]
        lines_e=[curve_extract(l,spacing=sp,offset=sp/2+0.01*sp) for l,sp in zip(lines,spacing)]
        arrow_x  = [l[i,0] for l in lines_s for i in range(len(l))]
        arrow_y  = [l[i,1] for l in lines_s for i in range(len(l))]
        arrow_dx = [le[i,0]-ls[i,0] for ls,le in zip(lines_s,lines_e) for i in range(len(ls))]
        arrow_dy = [le[i,1]-ls[i,1] for ls,le in zip(lines_s,lines_e) for i in range(len(ls))]

        if normalize:
            dn = [ np.sqrt(ddx**2 + ddy**2) for ddx,ddy in zip(arrow_dx,arrow_dy)]
            arrow_dx = [ddx/ddn for ddx,ddn in zip(arrow_dx,dn)] 
            arrow_dy = [ddy/ddn for ddy,ddn in zip(arrow_dy,dn)] 
        return  arrow_x,arrow_y,arrow_dx,arrow_dy 

    # --- Main body of streamQuiver
    # Extracting lines
    seg   = sp.lines.get_segments() # list of (2, 2) numpy arrays
    lines = seg_to_lines(seg)       # list of (N,2) numpy arrays
    # Convert lines to arrows
    ar_x, ar_y, ar_dx, ar_dy = lines_to_arrows(lines,spacing=spacing,n=n,normalize=True)
    # Plot arrows
    qv=ax.quiver(ar_x, ar_y, ar_dx, ar_dy, *args, angles='xy', **kwargs)
    return qv

# --- Example
x = np.linspace(-1,1,100)
y = np.linspace(-1,1,100)
X,Y=np.meshgrid(x,y)
u = -np.sin(np.arctan2(Y,X))
v =  np.cos(np.arctan2(Y,X))

xseed=np.linspace(0.1,1,4)

fig=plt.figure()
ax=fig.add_subplot(111)
sp = ax.streamplot(x,y,u,v,color='k',arrowstyle='-',start_points=np.array([xseed,xseed*0]).T,density=30)
qv = streamQuiver(ax,sp,spacing=0.5, scale=60)
plt.show()

每个箭头沿流线线处于给定距离的示例

I have found a way to customize the number of arrowheads on streamline plot. 我找到了一种自定义流线图中箭头数量的方法。

The idea is to plot streamline and arrows separately: 这个想法是分别绘制流线和箭头:

  • plt.streamplot returns a stream_container with two attributes: lines and arrows . plt.streamplot返回具有两个属性的stream_container: linesarrows The lines contain line segments that can be used to reconstruct streamline without arrows. 这些lines包含可用于重建无箭头的流线的线段。
  • plt.quiver can be used to plot gradient fields. plt.quiver可用于绘制梯度场。 With the proper scaling, the length of the arrows is neglectable, leaving only arrowheads. 使用适当的缩放比例,箭头的长度可以忽略不计,仅留下箭头。

Thus, we only need to define the positions of arrows using the line segments and pass them to plt.quiver . 因此,我们只需要使用线段定义箭头的位置,然后将它们传递到plt.quiver

Here is a toy example: 这是一个玩具示例:

import matplotlib.pyplot as plt
from matplotlib import collections as mc
import numpy as np

# get line segments
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
sp = ax.streamplot(x, y, u, v, start_points=start_points, density=10)
seg = sps.lines.get_segments()  # seg is a list of (2, 2) numpy arrays
lc = mc.LineCollection(seg, ...)

# define arrows
# here I define one arrow every 50 segments
# you could also select segs based on some criterion, e.g. intersect with certain lines
period = 50
arrow_x = np.array([seg[i][0, 0] for i in range(0, len(seg), period)])
arrow_y = np.array([seg[i][0, 1] for i in range(0, len(seg), period)])
arrow_dx = np.array([seg[i][1, 0] - seg[i][0, 0] for i in range(0, len(seg), period)])
arrow_dy = np.array([seg[i][1, 1] - seg[i][0, 1] for i in range(0, len(seg), period)])

# plot the final streamline
fig = plt.figure(figsize=(12.8, 10.8))
ax = fig.add_subplot(1, 1, 1)
ax.add_collection(lc)
ax.autoscale()
ax.quiver(
    arrow_x, arrow_y, arrow_dx, arrow_dy, angles='xy',  # arrow position
    scale=0.2, scale_units='inches', units='y', minshaft=0,  # arrow scaling
    headwidth=6, headlength=10, headaxislength=9)  # arrow style
fig.show()

There is more than one way to scale the arrows so that they appear to have zero length. 缩放箭头的方式不只一种,因此箭头的长度看起来为零。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM