I would like to display a set of xy-data in Matplotlib in such a way as to indicate a particular path. Ideally, the linestyle would be modified to use an arrow-like patch. I have created a mock-up, shown below (using Omnigraphsketcher). It seems like I should be able to override one of the common linestyle
declarations ('-'
, '--'
, ':'
, etc) to this effect.
Note that I do NOT want to simply connect each datapoint with a single arrow---the actually data points are not uniformly spaced and I need consistent arrow spacing.
Simple linestyles can be defined using the strings "solid", "dotted", "dashed" or "dashdot". More refined control can be achieved by providing a dash tuple (offset, (on_off_seq)) . For example, (0, (3, 10, 1, 15)) means (3pt line, 10pt space, 1pt line, 15pt space) with no offset.
Advertisements. A quiver plot displays the velocity vectors as arrows with components (u,v) at the points (x,y). quiver(x,y,u,v) The above command plots vectors as arrows at the coordinates specified in each corresponding pair of elements in x and y.
Here's a starting off point:
Walk along your line at fixed steps (aspace
in my example below) .
A. This involves taking steps along the line segments created by two sets of points (x1
,y1
) and (x2
,y2
).
B. If your step is longer than the line segment, shift to the next set of points.
At that point determine the angle of the line.
Draw an arrow with an inclination corresponding to the angle.
I wrote a little script to demonstrate this:
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()
This example results in this figure:
There's plenty of room for improvement here, for starters:
While looking into this, I discovered the quiver plotting method. It might be able to replace the above work, but it wasn't immediately obvious that this was guaranteed.
Very nice answer by Yann, but by using arrow the resulting arrows can be affected by the axes aspect ratio and limits. I have made a version that uses axes.annotate() instead of axes.arrow(). I include it here for others to use.
In short this is used to plot arrows along your lines in matplotlib. The code is shown below. It can still be improved by adding the possibility of having different arrowheads. Here I only included control for the width and length of the arrowhead.
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()
The resulting figure can be seen here:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With