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()
Related
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
I had created a chart with values (LSMA5['Low']), I'm able to plot the chart, but I want to show the values at each point of the chart, how can I do that?
Here are the code:
import matplotlib.pyplot as plt
plt.style.use('fivethirtyeight')
plt.figure(figsize=(12.6,4.6))
plt.plot(stock_store['Close'], label='ABCsTock', alpha=0.35)
plt.plot(LSMA5['Low'], label='LSMA5', alpha=1, linewidth=1)
plt.title('ABCsTock')
plt.xlabel('Jan. 01,2018 - Jul. 30,2020')
plt.ylabel('Price')
plt.legend(loc='upper right')
plt.show()
Thanks with regards
JC
If I understand what you're trying to do, here's a way to do that (with synthetic data):
x_arr = np.arange(10)
y_arr = np.random.randint(0, 10, 10)
plt.plot(x_arr, y_arr)
# zip joins x and y coordinates in pairs
for x,y in zip(x_arr,y_arr):
label = "{:.2f}".format(y)
plt.annotate(label, # this is the text
(x,y), # this is the point to label
textcoords="offset points", # how to position the text
xytext=(0,10), # distance from text to points (x,y)
ha='center') # horizontal alignment can be left, right or center
The output is:
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
I would like to plot circles with matplotlib (patches), and annotate them. The annotation would be a word, and it needs to be in the centre of the circle.
So far, I can plot a circle and annotate it:
But the annotation is not centred, neither horizontally or vertically. In order to do that, I would need access to the dimensions of the text.
Is there a way to access the dimensions of the text in "the coordinate systems" ?. For example, if the circle has a radius of 15 (15 something, not pixels), the text would have a length of 12 something (not pixels).
I'm open to any other suggestion on how to do that.
Here is my code so far:
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
fig, ax = plt.subplots()
ax = fig.add_subplot(111)
x = 0
y = 0
circle = plt.Circle((x, y), radius=1)
ax.add_patch(circle)
label = ax.annotate("cpicpi", xy=(x, y), fontsize=30)
ax.axis('off')
ax.set_aspect('equal')
ax.autoscale_view()
plt.show()
You need to set the horizontal alignment in ax.annotate using ha="center". The same thing can be done for the vertical direction if necessary using the argument va="center"
fig, ax = plt.subplots()
ax = fig.add_subplot(111)
x = 0
y = 0
circle = plt.Circle((x, y), radius=1)
ax.add_patch(circle)
label = ax.annotate("cpicpi", xy=(x, y), fontsize=30, ha="center")
ax.axis('off')
ax.set_aspect('equal')
ax.autoscale_view()
plt.show()
You can add two additional arguments to the annotate() call:
label = ax.annotate(
"cpicpi",
xy=(x, y),
fontsize=30,
verticalalignment="center",
horizontalalignment="center"
)
(See the docs for the arguments of annotate and of Text – whose constructor is called by annotate)
for a certain manuscript i need to position my label of the Graph exactly in the right or left top corner. The label needs a border with the same thickness as the spines of the graph. Currently i do it like this:
import matplotlib.pyplot as plt
import numpy as np
my_dpi=96
xposr_box=0.975
ypos_box=0.94
nrows=3
Mytext="label"
GLOBAL_LINEWIDTH=2
fig, axes = plt.subplots(nrows=nrows, sharex=True, sharey=True, figsize=
(380/my_dpi, 400/my_dpi), dpi=my_dpi)
fig.subplots_adjust(hspace=0.0001)
colors = ('k', 'r', 'b')
for ax, color in zip(axes, colors):
data = np.random.random(1) * np.random.random(10)
ax.plot(data, marker='o', linestyle='none', color=color)
for ax in ['top','bottom','left','right']:
for idata in range(0,nrows):
axes[idata].spines[ax].set_linewidth(GLOBAL_LINEWIDTH)
axes[0].text(xposr_box, ypos_box , Mytext, color='black',fontsize=8,
horizontalalignment='right',verticalalignment='top', transform=axes[0].transAxes,
bbox=dict(facecolor='white', edgecolor='black',linewidth=GLOBAL_LINEWIDTH))
plt.savefig("Label_test.png",format='png', dpi=600,transparent=True)
So i control the position of the box with the parameters:
xposr_box=0.975
ypos_box=0.94
If i change the width of my plot, the position of my box also changes, but it should always have the top and right ( or left) spine directly on top of the graphs spines:
import matplotlib.pyplot as plt
import numpy as np
my_dpi=96
xposr_box=0.975
ypos_box=0.94
nrows=3
Mytext="label"
GLOBAL_LINEWIDTH=2
fig, axes = plt.subplots(nrows=nrows, sharex=True, sharey=True, figsize=
(500/my_dpi, 400/my_dpi), dpi=my_dpi)
fig.subplots_adjust(hspace=0.0001)
colors = ('k', 'r', 'b')
for ax, color in zip(axes, colors):
data = np.random.random(1) * np.random.random(10)
ax.plot(data, marker='o', linestyle='none', color=color)
for ax in ['top','bottom','left','right']:
for idata in range(0,nrows):
axes[idata].spines[ax].set_linewidth(GLOBAL_LINEWIDTH)
axes[0].text(xposr_box, ypos_box , Mytext, color='black',fontsize=8,
horizontalalignment='right',verticalalignment='top',transform=axes[0].transAxes,
bbox=dict(facecolor='white', edgecolor='black',linewidth=GLOBAL_LINEWIDTH))
plt.savefig("Label_test.png",format='png', dpi=600,transparent=True)
This should also be the case if the image is narrower not wider as in this example.I would like to avoid doing this manually. Is there a way to always position it where it should? Independent on the width and height of the plot
and the amount of stacked Graphs?
The problem is that the position of a text element is relative to the text's extent, not to its surrounding box. While it would in principle be possible to calculate the border padding and position the text such that it sits at coordinates (1,1)-borderpadding, this is rather cumbersome since (1,1) is in axes coordinates and borderpadding in figure points.
There is however a nice alternative, using matplotlib.offsetbox.AnchoredText. This creates a textbox which can be placed easily relative the the axes, using the location parameters like a legend, e.g. loc="upper right". Using a zero padding around that text box directly places it on top of the axes spines.
from matplotlib.offsetbox import AnchoredText
txt = AnchoredText("text", loc="upper right", pad=0.4, borderpad=0, )
ax.add_artist(txt)
A complete example:
import matplotlib.pyplot as plt
from matplotlib.offsetbox import AnchoredText
import numpy as np
my_dpi=96
nrows=3
Mytext="label"
plt.rcParams["axes.linewidth"] = 2
plt.rcParams["patch.linewidth"] = 2
fig, axes = plt.subplots(nrows=nrows, sharex=True, sharey=True, figsize=
(500./my_dpi, 400./my_dpi), dpi=my_dpi)
fig.subplots_adjust(hspace=0.0001)
colors = ('k', 'r', 'b')
for ax, color in zip(axes, colors):
data = np.random.random(1) * np.random.random(10)
ax.plot(data, marker='o', linestyle='none', color=color)
txt = AnchoredText(Mytext, loc="upper right",
pad=0.4, borderpad=0, prop={"fontsize":8})
axes[0].add_artist(txt)
plt.show()
In principle you can align text to the Axes spines using Annotations and position them in Axes coordinates (x and y between 0 and 1) using xycoords = 'axes fraction. However, because you use a bbox that bbox will overlap with the spines.
Instead, you can use ax.text together a ScaledTransformation, which, if done right, also positions the text in Axes coordinates and shifts it by a fixed amount. If you provide a pad size to the bbox keyword, you know exactly how much the bbox will overlap with the spines in figure points (1 inch is 72 points), so that the shift is easily calculated. Here a little demonstration how to do this:
from matplotlib import pyplot as plt
import numpy as np
import matplotlib.transforms as transforms
GLOBAL_LINEWIDTH=2
pad = 10
fig,ax = plt.subplots()
x = np.linspace(0,1,20)
ax.plot(x,x**2, 'ro')
offset_bl = transforms.ScaledTranslation(
pad/72, pad/72, fig.dpi_scale_trans,
)
offset_br = transforms.ScaledTranslation(
-pad/72, pad/72, fig.dpi_scale_trans,
)
offset_tl = transforms.ScaledTranslation(
pad/72, -pad/72, fig.dpi_scale_trans,
)
offset_tr = transforms.ScaledTranslation(
-pad/72, -pad/72, fig.dpi_scale_trans,
)
for pos in ['top','bottom','left','right']:
ax.spines[pos].set_linewidth(GLOBAL_LINEWIDTH)
ax.text(
0,0, 'bottom left',
fontsize = 16, fontweight='bold', va='bottom', ha='left',
bbox=dict(
facecolor = 'white', edgecolor='black', lw = GLOBAL_LINEWIDTH,
pad = pad
),
transform=ax.transAxes + offset_bl,
)
ax.text(
1,0, 'bottom right',
fontsize = 16, fontweight='bold', va='bottom', ha='right',
bbox=dict(
facecolor = 'white', edgecolor='black', lw = GLOBAL_LINEWIDTH,
pad = pad
),
transform=ax.transAxes + offset_br,
)
ax.text(
0,1, 'top left',
fontsize = 16, fontweight='bold', va='top', ha='left',
bbox=dict(
facecolor = 'white', edgecolor='black', lw = GLOBAL_LINEWIDTH,
pad = pad
),
transform=ax.transAxes + offset_tl,
)
ax.text(
1,1, 'top right',
fontsize = 16, fontweight='bold', va='top', ha='right',
bbox=dict(
facecolor = 'white', edgecolor='black', lw = GLOBAL_LINEWIDTH,
pad = pad
),
transform=ax.transAxes + offset_tr,
)
plt.show()
And here is the result: