Python annotation arrows with same headlength - python

I'm using matplotlib to create annotations with arrows.
from matplotlib import pyplot as plt
plt.figure()
plt.annotate('Text1', xy=(1, 0), xytext=(1, 1), arrowprops=dict(alpha=0.5, fc='r', ec='r', headwidth=10))
plt.annotate('Text2', xy=(3, 0), xytext=(3, 3), arrowprops=dict(alpha=0.5, fc='r', ec='r', headwidth=10))
plt.annotate('Text3', xy=(6, 0), xytext=(6, 6), arrowprops=dict(alpha=0.5, fc='r', ec='r', headwidth=10))
plt.annotate('Text4', xy=(9, 0), xytext=(9, 9), arrowprops=dict(alpha=0.5, fc='r', ec='r', headwidth=10))
plt.xlim((0, 10))
plt.ylim(0, 10)
plt.show()
As can be seen, the length of the arrow heads is affected by the line-length and varies between the arrows.
I'm looking for a way to create arrows with different line-lengths but equal arrow-headlengths.
Although many options are listed on the annotations manual page, i was not able to produce the desired result.
As an alternative approach i tried to plot the arrows seperatedely using plt.arrow. In that case the arrow heads looked as desired, but when using transparency, additional lines became visible.
Best regards, zinjaai

In the arrowprops you can't specify the length of the head, but the frac arguement specifies the length of the head as a fraction the arrow length. Since you know xy and xytext you can compute the necessary fraction for a desired headlength.
I've don't it quickly for the example you provided (with an head length of 0.5).
from matplotlib import pyplot as plt
plt.figure()
plt.annotate('Text1', xy=(1, 0), xytext=(1, 1),
arrowprops=dict(alpha=0.5, fc='r', ec='r', headwidth=10, frac=0.5/1))
plt.annotate('Text2', xy=(3, 0), xytext=(3, 3),
arrowprops=dict(alpha=0.5, fc='r', ec='r', headwidth=10, frac=0.5/3))
plt.annotate('Text3', xy=(6, 0), xytext=(6, 6),
arrowprops=dict(alpha=0.5, fc='r', ec='r', headwidth=10, frac=0.5/6))
plt.annotate('Text4', xy=(9, 0), xytext=(9, 9),
arrowprops=dict(alpha=0.5, fc='r', ec='r', headwidth=10, frac=0.5/9))
plt.xlim((0, 10))
plt.ylim(0, 10)
plt.show()
Result:
You could wrap this in a function, which computes the length of the arrow (given xy and xytext) and then passes that on to plt.annotate.

Related

Quiver plot arrows using Matplotlib

I am trying to make a quiver plot using the list I4. In I4[0], (0,0) and (1,0) represent the starting position and the direction of the arrow respectively. However, the current output doesn't match the expected one. I attach both here.
import matplotlib.pyplot as plt
I4 = [[(0, 0), (1, 0)], [(0, 0), (0, -1)], [(1, 0), (1, -1)], [(0, -1), (1, -1)]]
fig, ax = plt.subplots(figsize = (10,10))
for i in range(0,len(I4)):
ax.quiver(I4[i][0], I4[i][1])
plt.show()
The current output is
The expected output is
Alternatively, you could use plt.arrow (doc here):
import matplotlib.pyplot as plt
I4 = [(0, 0),(1, 1),(0, 1), (0, 1)]
dI4=[(1,0),(0,-1),(1,0),(0,-1)]
texts=['(0,-1)','(1,-1)','(1,0)','(0,0)']
textxy=[(0,0),(1,0),(1,1),(0,1)]
fig, ax = plt.subplots(figsize = (10,10))
for i in range(len(I4)):
ax.arrow(I4[i][0], I4[i][1],dI4[i][0],dI4[i][1],length_includes_head=True,head_width=0.05,color='k',lw=3)
if i<2:
dy=-0.1
else:
dy=0.1
plt.text(textxy[i][0], textxy[i][1]+dy,texts[i],fontsize=20)
ax.set_aspect('equal')
ax.set_xlim([-0.5,1.5])
ax.set_ylim([-0.5,1.5])
plt.axis('off')
plt.show()
And the output gives:
Please, take a look at the documentation with help(ax.quiver): you'll see that you need to specify ax.quiver(x, y, u, v) where x, y are the starting coordinates, u, v represents the directions.
In practice, you need to unpack your lists, like this:
ax.quiver(*I4[i][0], *I4[i][1])

Python matplolib legend how to reduce distance between frame left edge and markers

As question suggests, I'm trying to reduce the distance between the left edge of the legend frame and the markers.
In figure there is the current plot. I would like to find a way to:
1) keeping the frameon = True in order to partially mask the underlying points;
2) move markers and labels toward left reducing the distance between legend edge and markers
the actual legend configuration is the follow:
leg = ax.legend(handles=legend_elements,
fontsize=13, loc=(0.03, 0.01), frameon=True,
framealpha=0.5, handletextpad=-0.6,
labelspacing=0.08, borderpad=0)
EDIT: Solution
Thanks both for the quickest answers, the solution was combining both your suggestions:
leg = ax.legend(handles=legend_elements,
fontsize=13, loc=(0.03, 0.01), frameon=True,
framealpha=0.5, handletextpad=0.,
labelspacing=0.08, borderpad=0.,
handlelength=1.2, borderaxespad=1)
I chose Sinan Kurmus answer as best to support his rank
Try using a negative value for borderpad. That will likely cause the legend to move downward as well, so adjust with borderaxespad.
Something like this:
leg = ax.legend(
fontsize=13, loc="lower left", frameon=True,
framealpha=1, handletextpad=-0.6,
labelspacing=0.08, borderpad=-0.5, borderaxespad=1)
Please note that rather than an absolute location, I used "lower left" for the legend location. Otherwise the whole "move stuff around with borderpad and borderaxspread" gets wonky (not a very precise explanation, I know:).
You can try adding the following parameter and change the value as per your choice
handlelength=1
Example
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(9, 3))
np.random.seed(10)
# WITHOUT handlelength
ax1.plot(np.random.randint(0, 10, 5), np.random.randint(0, 10, 5), 'bo', label='data1')
ax1.plot(np.random.randint(0, 10, 5), np.random.randint(0, 10, 5), 'rs', label='data2')
leg = ax1.legend(fontsize=19, loc=(0.03, 0.01), frameon=True,
framealpha=0.5, handletextpad=0.5,
labelspacing=0.08, borderpad=0.0)
# WITH handlelength
ax2.plot(np.random.randint(0, 10, 5), np.random.randint(0, 10, 5), 'bo', label='data1')
ax2.plot(np.random.randint(0, 10, 5), np.random.randint(0, 10, 5), 'rs', label='data2')
leg = ax2.legend(fontsize=19, loc=(0.03, 0.01), frameon=True,
framealpha=0.5, handletextpad=0.5,
labelspacing=0.08, borderpad=0.0, handlelength=1)

I want to plot perpendicular vectors in Python

I would like to simply plot perpendicular vectors in 2D. I've implemented 2 ways to plot them in the code below but the vectors don't "look" perpendicular to me when the plots are drawn. If it makes any difference I'm using Spyder.
import numpy as np
import matplotlib.pyplot as plt
x1=[0,0,4,3]
x2=[0,0,-3,4]
x3=[0,0,3,-4]
soa =np.array([x1,x2,x3])
X,Y,U,V = zip(*soa)
plt.figure()
ax = plt.gca()
ax.quiver(X,Y,U,V,angles='xy',scale_units='xy',scale=1)
ax.set_xlim([-10,10])
ax.set_ylim([-10,10])
plt.draw()
plt.show()
import pylab as pl
from matplotlib import collections as mc
lines = [[(0, 1), (4, 3)], [(-3, 4), (3, -4)]]
c = np.array([(1, 0, 0, 1), (0, 1, 0, 1), (0, 0, 1, 1)])
lc = mc.LineCollection(lines, colors=c, linewidths=2)
fig, ax = pl.subplots()
ax.add_collection(lc)
ax.autoscale()
ax.margins(0.1)
Your problem is that the size of the unit differs on the x and y axes. You need to force them to be equal.
In matplotlib.pyplot, add the line
plt.axes().set_aspect('equal')
just before you show the graph with
plt.show()
I get this result in the IPython console in Spyder:
In pylab, add the line
ax.set_aspect('equal')
at the end. However, these line segments still do not look perpendicular, and that is because they really are not perpendicular. The slope of your first, red line segment is 2/3, so your second, green line segment should have slope -3/2 but it actually has slope -4/3. Perhaps change your line to
lines = [[(0, 1), (4, 3)], [(-3, 4), (3, -5)]]
(I changed the ending -4 to -5) to get the correct second slope. You get a change from this first figure to the second:
and that last does look perpendicular.
The problem is the aspect ratio of the figure canvas.
Use:
plt.figure(figsize=(6,6))

Matplotlib errorbar plot does not use marker cycling

In the following example, the error bar plot does not use the marker cycle specified in the axes rc, although it does use the color cycle. Specifying fmt rather than marker in the cycler throws an AttributeError: Unknown property fmt. How can I use the prop_cycle to specify the markers of an error bar plot? I am using Matplotlib 1.5.1 and Python 3.5.1.
import matplotlib.pyplot as plt
from cycler import cycler
plt.rc('axes', prop_cycle=cycler(color=['orange', 'violet', 'darkgreen'],
marker=['s', 'o', 'd']))
plt.rc('lines', linestyle=None)
plt.errorbar((0, 1), (1, 0), yerr=(0.1, 0.1))
plt.errorbar((0, 1), (0, 1), yerr=(0.1, 0.1))
plt.errorbar((0, 1), (0.5, 0.75), yerr=(0.1, 0.1))
Although this doesn't answer the question as posed, there are two ways to get around this using a prop_cycle. One is to simply use a for loop to construct the graphs. If you'd rather not use a for loop, you can define a generator function to yield values of the markers. This has the disadvantage that it doesn't automatically reset for every axis, but I think its easier to use than a for loop. The while True: statement is necessary to get it to loop back to the beginning as though it were a cycle of markers to use.
import matplotlib.pyplot as plt
from cycler import cycler
def mfunc(syms):
while True:
for s in syms:
yield s
markers = ['s', 'o']
mark = mfunc(markers)
plt.rc('axes', prop_cycle=cycler(color=['orange', 'violet', 'darkgreen']))
plt.rc('lines', linestyle=None)
plt.errorbar((0, 1), (1, 0), yerr=(0.1, 0.1), marker=next(mark))
plt.errorbar((0, 1), (0, 1), yerr=(0.1, 0.1), marker=next(mark))
plt.errorbar((0, 1), (0.5, 0.75), yerr=(0.1, 0.1), marker=next(mark))

Plot counter of data points in matlpotlib

I have a matplotlib scatter plot like following.
import matplotlib.pyplot as plt
from pylab import plot,axis,show,pcolor,colorbar,bone
axiss = [(0, 0), (0, 1), (0, 0), (2, 2), (0, 2), (2, 2), (2, 0), (0, 2), (1, 2), (2, 0)]
x,y = zip(*axiss)
labels = ['u1', 'u2', 'u3', 'u4',
'u5', 'u6', 'u7', 'u8',
'u9', 'u10']
fig, ax = plt.subplots()
ax.scatter(x, y)
for i, txt in enumerate(labels):
ax.annotate(txt, (x[i],y[i]))
show()
Instead of labels I want to show how many data points are scattered on one point. For example in the data point I have marked in red, It should show '2'. And in the mouse hover event I need to see the labels. So in the example it should be 'u7' and 'u10'. Is it possible with matplotlib?
It got a little long, but I would first collect the unique element in axiss using set and count the number of each unique element. Then include that data into scatter's third argument, the size of the points. I would also annotate each points according the the number of datasets in that point.
Now the interactive annotation is a tricky part. I could not find mouse-hover event catcher, but you can do pretty much the same thing for mouse-clicking event. Save the first script on this page http://wiki.scipy.org/Cookbook/Matplotlib/Interactive_Plotting as interactive_annotations.py and import it in the script.
import matplotlib.pyplot as plt
from pylab import plot,axis,show,pcolor,colorbar,bone
import numpy as np
axiss = [(0, 0), (0, 1), (0, 0), (2, 2), (0, 2), (2, 2), (2, 0), (0, 2), (1, 2), (2, 0)]
# get unique elements
axiss_unique = list(set(axiss))
# get number of data at each datapoint
axiss_count = [axiss.count(x) for x in axiss_unique]
sc = 100 # scale up the datapoints for scatter
labels = [] # label each point according to number of data there
annotates = [] # intereactively label each point according to the datapoint name
for i in range(len(axiss_count)):
labels.append('%i'%axiss_count[i])
annotates.append( ' '.join(['u'+str(k) for k, j in enumerate(axiss) if j == axiss_unique[i]]))
axiss_count[i] *= sc
x,y = zip(*axiss_unique)
fig, ax = plt.subplots()
# get offsets of the labels to each point
x_os, y_os = max(x)/20., max(y)/20.
ax.scatter(x, y, axiss_count)
for i, txt in enumerate(labels):
ax.annotate(txt, (x[i]+x_os,y[i]+y_os), fontsize=15)
# interactive annotation
import interactive_annotations
af = interactive_annotations.AnnoteFinder(x,y, annotates)
connect('button_press_event', af)
show()
Result is something like this.
You can edit interactive_annotations.py to change the offset of the annotations, fonts, etc.

Categories

Resources