2011-11-23 23 views
9

Tôi muốn hiển thị một tập hợp dữ liệu xy trong Matplotlib theo cách như vậy để chỉ ra một đường dẫn cụ thể. Lý tưởng nhất, phong cách sẽ được sửa đổi để sử dụng một miếng vá giống như mũi tên. Tôi đã tạo ra một mô hình, được hiển thị bên dưới (sử dụng Omnigraphsketcher). Có vẻ như tôi có thể ghi đè một trong các tờ khai phổ biến linestyle ('-', '--', ':', v.v.) để có hiệu lực này. Lưu ý rằng tôi KHÔNG muốn chỉ đơn giản là kết nối mỗi điểm dữ liệu với một mũi tên duy nhất --- các điểm dữ liệu thực sự không phải là khoảng cách thống nhất và tôi cần khoảng cách mũi tên nhất quán.Làm cách nào để chỉ định kiểu dáng giống như mũi tên trong Matplotlib?

enter image description here

Trả lời

7

Dưới đây là một khởi đầu điểm:

  1. Đi bộ dọc theo dòng của bạn tại bước cố định (aspace trong ví dụ của tôi dưới đây).

    A. Việc này bao gồm thực hiện các bước dọc theo các đoạn đường được tạo bởi hai nhóm điểm (x1, y1) và (x2, y2).

    B. Nếu bước của bạn dài hơn đoạn đường, hãy chuyển sang tập hợp điểm tiếp theo.

  2. Tại thời điểm đó xác định góc của đường kẻ.

  3. Vẽ mũi tên có độ nghiêng tương ứng với góc.

Tôi đã viết một kịch bản ít để chứng minh điều này:

import numpy as np 
import matplotlib.pyplot as plt 

fig = plt.figure() 
axes = fig.add_subplot(111) 

# my random data 
scale = 10 
np.random.seed(101) 
x = np.random.random(10)*scale 
y = np.random.random(10)*scale 

# spacing of arrows 
aspace = .1 # good value for scale of 1 
aspace *= scale 

# r is the distance spanned between pairs of points 
r = [0] 
for i in range(1,len(x)): 
    dx = x[i]-x[i-1] 
    dy = y[i]-y[i-1] 
    r.append(np.sqrt(dx*dx+dy*dy)) 
r = np.array(r) 

# rtot is a cumulative sum of r, it's used to save time 
rtot = [] 
for i in range(len(r)): 
    rtot.append(r[0:i].sum()) 
rtot.append(r.sum()) 

arrowData = [] # will hold tuples of x,y,theta for each arrow 
arrowPos = 0 # current point on walk along data 
rcount = 1 
while arrowPos < r.sum(): 
    x1,x2 = x[rcount-1],x[rcount] 
    y1,y2 = y[rcount-1],y[rcount] 
    da = arrowPos-rtot[rcount] 
    theta = np.arctan2((x2-x1),(y2-y1)) 
    ax = np.sin(theta)*da+x1 
    ay = np.cos(theta)*da+y1 
    arrowData.append((ax,ay,theta)) 
    arrowPos+=aspace 
    while arrowPos > rtot[rcount+1]: 
     rcount+=1 
     if arrowPos > rtot[-1]: 
      break 

# could be done in above block if you want 
for ax,ay,theta in arrowData: 
    # use aspace as a guide for size and length of things 
    # scaling factors were chosen by experimenting a bit 
    axes.arrow(ax,ay, 
       np.sin(theta)*aspace/10,np.cos(theta)*aspace/10, 
       head_width=aspace/8) 


axes.plot(x,y) 
axes.set_xlim(x.min()*.9,x.max()*1.1) 
axes.set_ylim(y.min()*.9,y.max()*1.1) 

plt.show() 

Ví dụ này kết quả trong hình này: enter image description here

Có rất nhiều phòng để cải thiện ở đây, cho người mới bắt đầu:

  1. Người ta có thể sử dụng FancyArrowPatch để tùy chỉnh giao diện của rrows.
  2. Người ta có thể thêm một thử nghiệm nữa khi tạo mũi tên để đảm bảo chúng không mở rộng ra ngoài dòng. Điều này sẽ liên quan đến các mũi tên được tạo tại hoặc gần một đỉnh nơi đường thẳng thay đổi hướng mạnh. Đây là trường hợp cho đúng điểm nhất ở trên.
  3. Người ta có thể tạo một phương thức từ tập lệnh này sẽ hoạt động trên nhiều trường hợp hơn, tức là làm cho nó dễ dàng hơn.

Trong khi xem xét điều này, tôi đã phát hiện phương pháp vẽ sơ đồ quiver. Nó có thể thay thế công việc trên, nhưng không rõ ràng điều này đã được đảm bảo.

+0

tuyệt vời --- hoạt động hoàn hảo trong ứng dụng của tôi. Chân thành cảm ơn. – Deaton

5

Câu trả lời rất hay bởi Yann, nhưng bằng cách sử dụng mũi tên, các mũi tên kết quả có thể bị ảnh hưởng bởi tỷ lệ khung hình và giới hạn của trục. Tôi đã thực hiện một phiên bản sử dụng axes.annotate() thay vì axes.arrow(). Tôi đưa nó vào đây để người khác sử dụng.

Trong ngắn hạn này được sử dụng để vẽ các mũi tên dọc theo các dòng của bạn trong matplotlib. Các mã được hiển thị dưới đây. Nó vẫn có thể được cải thiện bằng cách thêm khả năng có các đầu mũi tên khác nhau.Ở đây tôi chỉ bao gồm kiểm soát chiều rộng và chiều dài của đầu mũi tên.

import numpy as np 
import matplotlib.pyplot as plt 


def arrowplot(axes, x, y, narrs=30, dspace=0.5, direc='pos', \ 
          hl=0.3, hw=6, c='black'): 
    ''' narrs : Number of arrows that will be drawn along the curve 

     dspace : Shift the position of the arrows along the curve. 
        Should be between 0. and 1. 

     direc : can be 'pos' or 'neg' to select direction of the arrows 

     hl  : length of the arrow head 

     hw  : width of the arrow head   

     c  : color of the edge and face of the arrow head 
    ''' 

    # r is the distance spanned between pairs of points 
    r = [0] 
    for i in range(1,len(x)): 
     dx = x[i]-x[i-1] 
     dy = y[i]-y[i-1] 
     r.append(np.sqrt(dx*dx+dy*dy)) 
    r = np.array(r) 

    # rtot is a cumulative sum of r, it's used to save time 
    rtot = [] 
    for i in range(len(r)): 
     rtot.append(r[0:i].sum()) 
    rtot.append(r.sum()) 

    # based on narrs set the arrow spacing 
    aspace = r.sum()/narrs 

    if direc is 'neg': 
     dspace = -1.*abs(dspace) 
    else: 
     dspace = abs(dspace) 

    arrowData = [] # will hold tuples of x,y,theta for each arrow 
    arrowPos = aspace*(dspace) # current point on walk along data 
           # could set arrowPos to 0 if you want 
           # an arrow at the beginning of the curve 

    ndrawn = 0 
    rcount = 1 
    while arrowPos < r.sum() and ndrawn < narrs: 
     x1,x2 = x[rcount-1],x[rcount] 
     y1,y2 = y[rcount-1],y[rcount] 
     da = arrowPos-rtot[rcount] 
     theta = np.arctan2((x2-x1),(y2-y1)) 
     ax = np.sin(theta)*da+x1 
     ay = np.cos(theta)*da+y1 
     arrowData.append((ax,ay,theta)) 
     ndrawn += 1 
     arrowPos+=aspace 
     while arrowPos > rtot[rcount+1]: 
      rcount+=1 
      if arrowPos > rtot[-1]: 
       break 

    # could be done in above block if you want 
    for ax,ay,theta in arrowData: 
     # use aspace as a guide for size and length of things 
     # scaling factors were chosen by experimenting a bit 

     dx0 = np.sin(theta)*hl/2. + ax 
     dy0 = np.cos(theta)*hl/2. + ay 
     dx1 = -1.*np.sin(theta)*hl/2. + ax 
     dy1 = -1.*np.cos(theta)*hl/2. + ay 

     if direc is 'neg' : 
      ax0 = dx0 
      ay0 = dy0 
      ax1 = dx1 
      ay1 = dy1 
     else: 
      ax0 = dx1 
      ay0 = dy1 
      ax1 = dx0 
      ay1 = dy0 

     axes.annotate('', xy=(ax0, ay0), xycoords='data', 
       xytext=(ax1, ay1), textcoords='data', 
       arrowprops=dict(headwidth=hw, frac=1., ec=c, fc=c)) 


    axes.plot(x,y, color = c) 
    axes.set_xlim(x.min()*.9,x.max()*1.1) 
    axes.set_ylim(y.min()*.9,y.max()*1.1) 


if __name__ == '__main__': 
    fig = plt.figure() 
    axes = fig.add_subplot(111) 

    # my random data 
    scale = 10 
    np.random.seed(101) 
    x = np.random.random(10)*scale 
    y = np.random.random(10)*scale 
    arrowplot(axes, x, y) 

    plt.show() 

Con số kết quả có thể được nhìn thấy ở đây:

enter image description here

+0

Điều này thật tuyệt vời nhưng nó không hoạt động tốt nếu x và y có độ dài 200. – chrisdembia

1

vector hóa phiên bản của câu trả lời Yann của:

import numpy as np 
import matplotlib.pyplot as plt 

def distance(data): 
    return np.sum((data[1:] - data[:-1]) ** 2, axis=1) ** .5 

def draw_path(path): 
    HEAD_WIDTH = 2 
    HEAD_LEN = 3 

    fig = plt.figure() 
    axes = fig.add_subplot(111) 

    x = path[:,0] 
    y = path[:,1] 
    axes.plot(x, y) 

    theta = np.arctan2(y[1:] - y[:-1], x[1:] - x[:-1]) 
    dist = distance(path) - HEAD_LEN 

    x = x[:-1] 
    y = y[:-1] 
    ax = x + dist * np.sin(theta) 
    ay = y + dist * np.cos(theta) 

    for x1, y1, x2, y2 in zip(x,y,ax-x,ay-y): 
     axes.arrow(x1, y1, x2, y2, head_width=HEAD_WIDTH, head_length=HEAD_LEN) 
    plt.show() 
0

Đây là một phiên bản sửa đổi và sắp xếp hợp lý mã Duarte của. Tôi đã có vấn đề khi tôi chạy mã của mình với các bộ dữ liệu khác nhau và tỷ lệ khía cạnh, vì vậy tôi làm sạch nó lên và sử dụng FancyArrowPatches cho các mũi tên. Lưu ý rằng ô mẫu có quy mô 1.000.000 lần khác nhau trong x so với y.

Tôi cũng đã thay đổi để vẽ mũi tên ở chế độ hiển thị do đó tỷ lệ khác nhau trên trục x và trục y sẽ không thay đổi độ dài mũi tên.

Trên đường đi, tôi tìm thấy một lỗi trong FancyplrowPatch của matplotlib mà bom khi vẽ một mũi tên hoàn toàn thẳng đứng. Tôi tìm thấy một công việc xung quanh đó là trong mã của tôi.

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


def arrowplot(axes, x, y, nArrs=30, mutateSize=10, color='gray', markerStyle='o'): 
    '''arrowplot : plots arrows along a path on a set of axes 
     axes : the axes the path will be plotted on 
     x  : list of x coordinates of points defining path 
     y  : list of y coordinates of points defining path 
     nArrs : Number of arrows that will be drawn along the path 
     mutateSize : Size parameter for arrows 
     color : color of the edge and face of the arrow head 
     markerStyle : Symbol 

     Bugs: If a path is straight vertical, the matplotlab FanceArrowPatch bombs out. 
      My kludge is to test for a vertical path, and perturb the second x value 
      by 0.1 pixel. The original x & y arrays are not changed 

     MHuster 2016, based on code by 
    ''' 
    # recast the data into numpy arrays 
    x = np.array(x, dtype='f') 
    y = np.array(y, dtype='f') 
    nPts = len(x) 

    # Plot the points first to set up the display coordinates 
    axes.plot(x,y, markerStyle, ms=5, color=color) 

    # get inverse coord transform 
    inv = ax.transData.inverted() 

    # transform x & y into display coordinates 
    # Variable with a 'D' at the end are in display coordinates 
    xyDisp = np.array(axes.transData.transform(zip(x,y))) 
    xD = xyDisp[:,0] 
    yD = xyDisp[:,1] 

    # drD is the distance spanned between pairs of points 
    # in display coordinates 
    dxD = xD[1:] - xD[:-1] 
    dyD = yD[1:] - yD[:-1] 
    drD = np.sqrt(dxD**2 + dyD**2) 

    # Compensating for matplotlib bug 
    dxD[np.where(dxD==0.0)] = 0.1 


    # rtotS is the total path length 
    rtotD = np.sum(drD) 

    # based on nArrs, set the nominal arrow spacing 
    arrSpaceD = rtotD/nArrs 

    # Loop over the path segments 
    iSeg = 0 
    while iSeg < nPts - 1: 
     # Figure out how many arrows in this segment. 
     # Plot at least one. 
     nArrSeg = max(1, int(drD[iSeg]/arrSpaceD + 0.5)) 
     xArr = (dxD[iSeg])/nArrSeg # x size of each arrow 
     segSlope = dyD[iSeg]/dxD[iSeg] 
     # Get display coordinates of first arrow in segment 
     xBeg = xD[iSeg] 
     xEnd = xBeg + xArr 
     yBeg = yD[iSeg] 
     yEnd = yBeg + segSlope * xArr 
     # Now loop over the arrows in this segment 
     for iArr in range(nArrSeg): 
      # Transform the oints back to data coordinates 
      xyData = inv.transform(((xBeg, yBeg),(xEnd,yEnd))) 
      # Use a patch to draw the arrow 
      # I draw the arrows with an alpha of 0.5 
      p = patches.FancyArrowPatch( 
       xyData[0], xyData[1], 
       arrowstyle='simple', 
       mutation_scale=mutateSize, 
       color=color, alpha=0.5) 
      axes.add_patch(p) 
      # Increment to the next arrow 
      xBeg = xEnd 
      xEnd += xArr 
      yBeg = yEnd 
      yEnd += segSlope * xArr 
     # Increment segment number 
     iSeg += 1 

if __name__ == '__main__': 
    import numpy as np 
    import matplotlib.pyplot as plt 
    fig = plt.figure() 
    ax = fig.add_subplot(111) 
    # my random data 
    xScale = 1e6 
    np.random.seed(1) 
    x = np.random.random(10) * xScale 
    y = np.random.random(10) 
    arrowplot(ax, x, y, nArrs=4*(len(x)-1), mutateSize=10, color='red') 
    xRng = max(x) - min(x) 
    ax.set_xlim(min(x) - 0.05*xRng, max(x) + 0.05*xRng) 
    yRng = max(y) - min(y) 
    ax.set_ylim(min(y) - 0.05*yRng, max(y) + 0.05*yRng) 
    plt.show() 

enter image description here

Các vấn đề liên quan