Matplotlib - Move text label right by 'x' points - python

I have the following code which produces a bubble chart, and then adds the labels as text to the plot:
fig, ax = plt.subplots(figsize = (5,10))
# create data
x = [1,1,1,1,1,1,1,1,1,1]
y = ['A','B','C','D',
'E','F','G','H','I','']
z = [10,20,80,210,390,1050,2180,4690,13040,0]
labels = [1,2,8,21,39,105,218,469,1304]
plt.xlim(0.9,1.1)
for i, txt in enumerate(labels):
ax.annotate(txt, (x[i], y[i]), ha='center', va='center', )
plt.scatter(x, y, s=z*4000, c="#8C4799", alpha=0.3)
I have the text labels centered vertically and horizontally (i.e. the 1304,469 etc), but ideally I want it shifted to the right so it is away from the bubble. I have tried ha=right, but it only nudges it a tiny bit.
Is there anything I can use to move it completely away from the bubble? I.e. code I can put it the following for loop:
for i, txt in enumerate(labels):
ax.annotate(txt, (x[i], y[i]), ha='center', va='center', )

Since the size s of the bubbles is s=z*4000, a bubble's radius is np.sqrt(z*4000)/2. (For an explanation see scatter plot marker size).
You would hence create an annotation which is positionned at the center of the bubbles in data coordinates and offset it by np.sqrt(z*4000)/2 in units of points (or possibly 2 or 3 points more to have it look nicely).
This would be done using
annotate("text", xy=(x[i],y[i]),
xytext=(np.sqrt(z[i]*4000)/2+2, 0), textcoords="offset points")
Complete example:
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots(figsize = (5,10))
# create data
x = [1,1,1,1,1,1,1,1,1,1]
y = ['A','B','C','D',
'E','F','G','H','I','']
z = [10,20,80,210,390,1050,2180,4690,13040,0]
labels = [1,2,8,21,39,105,218,469,1304]
plt.xlim(0.9,1.1)
sc = plt.scatter(x, y, s=z*4000, c="#8C4799", alpha=0.3)
for txt, size, xi, yi in zip(labels, sc.get_sizes(), x,y):
ax.annotate(txt, xy=(xi,yi), xytext=(np.sqrt(size)/2+2, 0),
textcoords="offset points",
ha='left', va='center', )
plt.show()

I would simply use an offset percentage (20% for example) to reposition the x-coordinate of the text. Additionally you can turn off the manual setting of x-limits.
fig, ax = plt.subplots(figsize=(4, 10))
x = [1,1,1,1,1,1,1,1,1,1]
y = ['A','B','C','D',
'E','F','G','H','I','']
z = [10,20,80,210,390,1050,2180,4690,13040,0]
labels = [1,2,8,21,39,105,218,469,1304]
for i, txt in enumerate(labels):
ax.annotate(txt, (x[i]*1.2, y[i]), ha='center', va='center', )
plt.scatter(x, y, s=z*4000, c="#8C4799", alpha=0.3)

the parameter xytext of ax.annotate lets you do this:
fig, ax = plt.subplots(figsize = (5,10))
# create data
x = [1,1,1,1,1,1,1,1,1,1]
y = ['A','B','C','D',
'E','F','G','H','I','']
z = [10,20,80,210,390,1050,2180,4690,13040,0]
labels = [1,2,8,21,39,105,218,469,1304]
plt.xlim(0.9,1.1)
for i, txt in enumerate(labels):
ax.annotate(txt, (x[i], y[i]), ha='center', va='center', xytext=(1.05,y[i]) )
plt.scatter(x, y, s=z*4000, c="#8C4799", alpha=0.3)
Brings this:
Edit: if you want the labels to be just to the right of every circle, you'll have to create an array of positions and then loop through it

Related

Labelling and color coding lines in a plot using secondary y-axis

I would like to label the lines of my plots on a secondary y-axis to the right of my plot, with the label of each line at the height of its final point, as well as the color of that line as shown in this image in matplotlib.
I have tried using an annotate solution based on this answer, however this places the label inside of the plot and overlaps for certain points.
import matplotlib.pyplot as plt
import numpy as np
l = ['label {:d}'.format(i) for i in range(1,5)]
x = [0,1]
yy = np.transpose([np.arange(1,5),np.arange(1,5)])
l2 = 'overlap'
y2 = [3.95,3.95]
fig, ax = plt.subplots()
for y,l in zip(yy,l):
ax.plot(x,y)
ax.annotate(xy=(x[-1],y[-1]), xytext=(20,0), textcoords='offset points', text=l, va='center')
ax.plot(x,y2)
ax.annotate(xy=(x[-1],y2[-1]), xytext=(20,0), textcoords='offset points', text=l2, va='center')
plt.show()
I am assuming there is a better way to accomplish this goal, could you please point me towards a solution?
Edited sample code to highlight specific overlap issue.
Try to tune the x,y parameters in the xytext=(x,y).
import matplotlib.pyplot as plt
import numpy as np
l = ['label {:d}'.format(i) for i in range(1,5)]
x = [0,1]
yy = np.transpose([np.arange(1,5),np.arange(1,5)])
l2 = 'overlap'
y2 = [3.95,3.95]
fig, ax = plt.subplots()
for y,l in zip(yy,l):
ax.plot(x,y)
ax.annotate(xy=(x[-1],y[-1]), xytext=(20,0), textcoords='offset points', text=l, va='center')
ax.plot(x,y2)
ax.annotate(xy=(x[-1],y2[-1]), xytext=(20,-5), textcoords='offset points', text=l2, va='center')
plt.show()

Position label of colorbar

I have this function:
def scatter_diagdistance(x, y) :
z = abs(y-x)
fig, ax = plt.subplots(dpi=200)
sc = ax.scatter(x, y, c=z, s=50, edgecolor='none')
x_diag = np.arange(min(x*100), max(x*100))/100
ax.plot(x_diag, x_diag, '-', c="red")
cbar = fig.colorbar(sc)
cbar.set_label('Distance from diagonal')
return(fig)
Which gives me this sort of image:
How can I position the "Distance from diagonal" to the left of the colorbar?
(Also, is there a cleaner way to plot the diagonal over a scatter plot like this?)
one way to do it is to use the text as the label for the secondary y-axis. That will keep the text before the colorbar. Also, you can draw a line for the diagonal. The code (without your data) is shown below. If you use transform=ax.transAxes details, the coordinates are interpreted as axes coordinates
fig, ax = plt.subplots(dpi=200)
ax2 = ax.twinx() ##Create secondary axis
ax2.set_yticks([]) ##No ticks for the secondary axis
sc = ax.scatter(0.5, 0.5, c=1, s=50, edgecolor='none')
ax2.set_ylabel('Distance from diagonal') ##Label for secondary axis
ax.plot([0, 1], [0, 1], '-', c="red", transform=ax.transAxes) #Line from 0 to 1
cbar = fig.colorbar(sc)
Plot

Center specified tick labels for matplotlib's pcolomesh at the boxes

I do not understand, how to properly plot my heatmap (pcolormesh) with matplotlib. I want the tick's labels be centered below/beside the corresponding boxes - and only my given data, not some artificially extended ranges.
In the docs I found an example, which works slightly modified to floats just fine for me.
Z = []
for i in range(1, 7):
Z.append([j*i for j in range(1, 11)])
Z = np.asarray(Z)
x = np.linspace(0.1, 1.0, num=10)
y = np.linspace(0.1, 0.6, num=6)
fig, ax = plt.subplots()
ax.pcolormesh(x, y, Z, vmin=np.min(Z), edgecolors='w', linewidths=0.5, vmax=np.max(Z), shading='auto')
plt.show()
The result prints the ticks centered at the boxes, which is exactly what I want.
But as soon as I use my own data it ends up with some weird behaviour
data = pd.DataFrame(index=[0, 0.25, 0.5], data={0: [31.40455938, 101.43291831, 101.67128077], 0.25: [31.40455938, 89.81448724, 99.65066293], 0.5: [31.40455938, 57.01406046, 101.47536496]})
x = data.columns.astype(np.float64).to_numpy()
y = data.index.astype(np.float64).to_numpy()
z = data.to_numpy()
cmap = LinearSegmentedColormap.from_list('G2R', ["green", "red"])
fig, ax = plt.subplots()
ax.pcolormesh(x, y, z, shading='auto', cmap=cmap, edgecolors='w', linewidths=0.5, vmin=0, vmax=100) # shading='gouraud'
ax.set_title('not what i want')
ax.set_xlabel('X')
ax.set_ylabel('Y')
plt.show()
How do I get my heatmap to simply plot the given floats as centered tick labels without those unwanted floats? I literally want to specify my tick labels (floats or strings) to be shown centered to the boxes. I would assume there must be a way, to specify a list or array as tick labels. How do I accomplish that?
After plotting the pcolormesh you can set x and y tick with matplotlib.axes.Axes.set_xticks and matplotlib.axes.Axes.set_yticks respectively:
ax.pcolormesh(x, y, z, shading='auto', cmap=cmap, edgecolors='w', linewidths=0.5, vmin=0, vmax=100) # shading='gouraud'
ax.set_xticks(data.columns)
ax.set_yticks(data.index)

How to fix the anotation probleme with matplotlib

I am currently trying to plot two line graphs together with an anotation under each marker position of the of the ylabel. However I am facing an issue where only the first one is plotted and not at its position.
Here is my code:
import matplotlib.pyplot as plt
ax = plt.subplot(111)
first =[0.9122,0.9091,0.9073,0.898,0.888,0.8855,0.8831,0.8837,0.8815,0.8612,0.8628,0.8419,0.8022,0.7805,0.7414]
second=[0.9499,0.9472,0.9421,0.938,0.9401,0.94,0.9417,0.9387,0.9398,0.9395,0.9263,0.9115,0.9089,0.9050,0.8893]
x = [10,20,30,40,50,60,70,80,90,100,200,400,600,800,1000]
xi = [i for i in range(0, len(x))]
plt.xticks(xi, x)
ax.plot(xi, first, marker='x')
ax.plot(xi, second, marker='^')
plt.xlabel('xlabel')
plt.ylabel('ylabel')
ax.legend(loc='upper center',
bbox_to_anchor=(0.5, # horizontal
1.12),# vertical
ncol=3, fancybox=True)
for x, y in zip(x, first):
label = y * 100
ax.annotate(label, # this is the text
(x, y), # this is the point to label
textcoords="offset points", # how to position the text
xytext=(0, 0), # distance from text to points (x,y)
ha='center')
plt.show()

rainbowtext() function and y axis label

Hey I'm using rainbow text function, which can be found in here
in order to make y axis label have colors that match closest colors of the conosle names on y axis.
So currently I've came up with this code:
fig, ax= plt.subplots(figsize=(5,6)) #used to take care of the size
sns.barplot(x=gbyplat,y=gbyplat.index, palette='husl') #creating barplot
ax.set_ylabel('Publisher', color='deepskyblue', size=15, alpha=0.8) #setting labels
ax.set_xlabel('Number of titles published', color='slateblue', size=15, alpha=0.7)
ax.set_title('Titles per platform ranking', color='deeppink', size=17, alpha=0.6)
ax.set_xlim(0,2350) #setting limit for the plot
ax.set_xticks(np.arange(0, max(gbyplat), 250)) #ticks frequency
ax.annotate('newest', size=12, xy=(390, 13), xytext=(700, 13.3),
arrowprops=dict(arrowstyle="fancy")) #annotations on plot
ax.annotate('max', size=9, xy=(2230,0.3), bbox=dict(boxstyle="round", fc="w", alpha=0.5))
ax.plot(2161,0, 'o', color='cyan') #creating the cricle highlight for PS2 max
p = sns.color_palette("husl", len(gbyplat))
for i, label in enumerate(ax.get_yticklabels()):
label.set_color(p[i])
rainbow_text(0,5, "Pub lis her".split(),
[p[10],p[11],p[12]],
size=10)
However, the issue is that I have to manually set coordinates for newly produced 'Publisher' label. According to the function code i can pass ax argument which would automatically fit the label to the y axis (if I understood correctly). So how can I do that? And second question, is there a way to access ylabel coordinates (of the current y axis label 'Publisher')?
Thanks
One can set the text at the position where the ylabel would normally reside by first drawing the ylabel, obtaining its coordinates and then setting it to an empty string. One can then adapt the example rainbow text function to use the obtained coordinates.
It will still be very tricky to select the colors and coordinates such that the text will have exactly the color of the bars next to it. This probably involves a lot a trial and error.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import transforms
import seaborn as sns
l =list("ABCDEFGHIJK")
x = np.arange(1,len(l)+1)[::-1]
f, ax=plt.subplots(figsize=(7,4.5))
sns.barplot(x=x,y=l, palette='husl', ax=ax)
plt.xlabel('Number of titles published', color='slateblue', size=15, alpha=0.7)
p = sns.color_palette("husl", len(l))
for i, label in enumerate(ax.get_yticklabels()):
label.set_color(p[i])
def rainbow_text(x, y, strings, colors, ax=None, **kw):
if ax is None:
ax = plt.gca()
canvas = ax.figure.canvas
lab = ax.set_ylabel("".join(strings))
canvas.draw()
labex = lab.get_window_extent()
t = ax.transAxes
labex_data = t.inverted().transform((labex.x0, labex.y0- labex.height/2.))
ax.set_ylabel("")
for s, c in zip(strings, colors):
text = ax.text(labex_data[0]+x, labex_data[1]+y, s, color=c, transform=t,
rotation=90, va='bottom', ha='center', **kw)
text.draw(canvas.get_renderer())
ex = text.get_window_extent()
t = transforms.offset_copy(text._transform, y=ex.height, units='dots')
rainbow_text(0, 0.06, ["Pub", "lish", "er"],[p[6], p[5],p[4]],size=15)
plt.show()

Categories