This question already has answers here:
Animate points with labels with matplotlib
(3 answers)
Closed 5 years ago.
I made this animation using matplotlib and it is working properly, however, i need to add some animated labels to be moving with its corresponding points.
The first label to be referring to the intersection point between the circle and the horizontal line from the centre of the ellipse and the other text label is to be in the middle of the inclined line annotating its length.
I tried some ideas but nothing worked properly. Any ideas?
screenshot
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig = plt.figure()
ax = fig.add_subplot(111, autoscale_on=True, xlim=(-6, 6), ylim=(-7, 17))
# ax.grid()
line, = ax.plot([], [], 'k', lw=1)
line2, = ax.plot([], [], 'k--', lw=1)
a,b = 3,2
x,y = list(),list()
x1 =np.array([item/10 for item in range(-30,31)])
y1 = np.sqrt(b**2 * (1-(x1**2 / a**2)))
x =list(x1)+[-item for item in list(x1)]
y =list(y1)+[-item for item in list(y1)]
plt.plot(x, y, 'k:')
plt.plot((0,0), (0,15), 'k--')
ax.annotate('$A$', xy=(0,15), xytext=(-10, 10),color='b',
textcoords='offset points')
ax.annotate('$O$', xy=(0,-1), xytext=(-10, 10),color='b',ha='center',
textcoords='offset points')
ax.annotate('$4a$', xy=(0,7), xytext=(-10, 10),color='b',ha='center',
textcoords='offset points', family='sans serif')
def animate(i):
thisx = [0, x[i]]
thisy = [15, y[i]]
xx = [x[i], 0]
yy = [y[i], 0]
line.set_data(thisx, thisy)
line2.set_data(xx, yy)
return line, line2
ani = animation.FuncAnimation(fig, animate, np.arange(0, len(x)), interval=20, blit=False)
ax.annotate('$P$', xy=(3,0), xytext=(0, 0),color='b',ha='center',
textcoords='offset points', family='sans serif', style='italic')
plt.show()
# ani.save('circular_motion.mp4', fps=20)
#
plt.close()
You can alter the annotation properties in the same way that you alter the line properties. Just store the Annotation object that is returned from the ax.annotation command and then update its position in the animate function. Note that the function set_position does not work properly, as was also noted in here, therefore you have to use the xy attribute.
Furthermore, I noticed that your animation runs faster when you y values are close to zero. You can fix that (if it needs fixing) by defining a vector of angles and computing the xy coordinates from that. I took the freedom to alter your code to show what I mean.
About the length of the inclined line, I annotated it here as L, as you don't state what the length of the distance OP is, but I guess that you can fill that in yourself.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig = plt.figure()
ax = fig.add_subplot(111, autoscale_on=True, xlim=(-6, 6), ylim=(-7, 17))
# ax.grid()
line, = ax.plot([], [], 'k', lw=1)
line2, = ax.plot([], [], 'k--', lw=1)
a,b = 3,2
x,y = list(),list()
z = 15
phi = np.linspace(0,2*np.pi,100)
x = a * np.cos(phi)
y = -b * np.sin(phi)
plt.plot(x, y, 'k:')
plt.plot((0,0), (0,z), 'k--')
ax.annotate('$A$', xy=(0,15), xytext=(-10, 10),color='b',
textcoords='offset points')
ax.annotate('$O$', xy=(0,-1), xytext=(-10, 10),color='b',ha='center',
textcoords='offset points')
ax.annotate('$4a$', xy=(0,7), xytext=(-10, 10),color='b',ha='center',
textcoords='offset points', family='sans serif')
def animate(i):
thisx = [0, x[i]]
thisy = [z, y[i]]
xx = [x[i], 0]
yy = [y[i], 0]
line.set_data(thisx, thisy)
line2.set_data(xx, yy)
P.xy = (x[i]*1.05,y[i])
L.xy = ((x[i]/2)*1.05, z/2+y[i]/2)
return line, line2, P, L
ani = animation.FuncAnimation(fig, animate, np.arange(0, len(x)), interval=20, blit=False)
P = ax.annotate('$P$', xy=(a,0), xytext=(0, 0),color='b',ha='center',
textcoords='offset points', family='sans serif', style='italic')
L = ax.annotate('$L$', xy=(a/2,z/2), xytext=(0, 0),color='b',ha='center',
textcoords='offset points', family='sans serif', style='italic')
plt.show()
# ani.save('circular_motion.mp4', fps=20)
#
plt.close()
Hope this helps.
Related
I was using an image as a matplotlib marker , I have attached the code below
import matplotlib.pyplot as plt
from matplotlib import patches
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
import random
plt.rcParams["figure.figsize"] = [10.0,10.0]
plt.rcParams["figure.autolayout"] = True
def getImage(path):
return OffsetImage(plt.imread(path, format="png"), zoom=.1)
paths = ['BS.png']
for i in range(49):
paths.append('BS.png',)
#print(paths)
x = []
for i in range(50):
x.append(random.randint(0,10))
print(x)
y = []
for i in range(50):
y.append(random.randint(0,10))
print(y)
fig, ax = plt.subplots()
circle1 = patches.Circle((5, 5), radius=20, fill = False ,edgecolor = 'black')
for x0,y0,path in zip(x, y, paths):
ab = AnnotationBbox(getImage(path), (x0, y0), frameon=False)
ax.add_artist(ab)
plt.xticks(range(10))
plt.yticks(range(10))
ax.axis('off')
plt.show()
The output is as follows
Now how do I draw a circle around these markers in the graph? A dashed circle is preferrable.
I tried to use circle1 = patches.Circle((x0, y0), radius=20, fill = False ,edgecolor = 'black')
So that a circle is drawn around every marker but it does not draw anything.
You cannot see the circle because the set radius is too large to be displayed inside the canvas (radius = 20, while the axis range is (0, 10)).
These lines of code
fig, ax = plt.subplots()
for x0,y0,path in zip(x, y, paths):
ab = AnnotationBbox(getImage(path), (x0, y0), frameon=False)
ax.add_artist(ab)
circle = plt.Circle((x0, y0), radius=0.5, color='black', fill=False, linestyle='--')
ax.add_artist(circle)
plt.xticks(range(-1, 12))
plt.yticks(range(-1, 12))
ax.axis('off')
plt.show()
produces
In this video of backtrader's matplotlib implementation https://youtu.be/m6b4Ti4P2HA?t=2008 I can see that a default and very fast and CPU saving crosshair mouse cursor seems to exist in matplotlib.
I would like to have the same kind of mouse cursor for a simple multi subplot plot in matplotlib like this:
import numpy as np
import matplotlib
matplotlib.use('QT5Agg')
matplotlib.rcParams['figure.figsize'] = (20.0, 22.0)
import matplotlib.pyplot as plt
fig = plt.figure()
ax1 = plt.subplot(2, 1, 1)
ax2 = plt.subplot(2, 1, 2, sharex=ax1)
ax1.plot(np.array(np.random.rand(100)))
ax2.plot(np.array(np.random.rand(100)))
plt.show()
So, if I am with my mouse in the lower subplot, I want to see directly and very precisely, which value of x/y in the lower plot corresponds to which value pair in the upper plot.
I have found other solutions to do this but they seem to be very slow compared to the implementation in the video.
You can create a crosshair cursor via mplcursors. sel.extras.append() takes care that the old cursor is removed when a new is drawn. With sel.annotation.set_text you can adapt the popup annotation shown. To leave out the annotation, use sel.annotation.set_visible(False). To find the corresponding y-value in the other subplot, np.interp with the data extracted from the curve can be used.
import numpy as np
import matplotlib.pyplot as plt
import mplcursors
def crosshair(sel):
x, y2 = sel.target
y1 = np.interp( sel.target[0], plot1.get_xdata(), plot1.get_ydata() )
sel.annotation.set_text(f'x: {x:.2f}\ny1: {y1:.2f}\ny2: {y2:.2f}')
# sel.annotation.set_visible(False)
hline1 = ax1.axhline(y1, color='k', ls=':')
vline1 = ax1.axvline(x, color='k', ls=':')
vline2 = ax2.axvline(x, color='k', ls=':')
hline2 = ax2.axhline(y2, color='k', ls=':')
sel.extras.append(hline1)
sel.extras.append(vline1)
sel.extras.append(hline2)
sel.extras.append(vline2)
fig = plt.figure(figsize=(15, 10))
ax1 = plt.subplot(2, 1, 1)
ax2 = plt.subplot(2, 1, 2, sharex=ax1)
plot1, = ax1.plot(np.array(np.random.uniform(-1, 1, 100).cumsum()))
plot2, = ax2.plot(np.array(np.random.uniform(-1, 1, 100).cumsum()))
cursor = mplcursors.cursor(plot2, hover=True)
cursor.connect('add', crosshair)
plt.show()
Here is an alternative implementation that stores the data in global variables and moves the lines (instead of deleting and recreating them):
import numpy as np
import matplotlib.pyplot as plt
import mplcursors
def crosshair(sel):
x = sel.target[0]
y1 = np.interp(x, plot1x, plot1y)
y2 = np.interp(x, plot2x, plot2y)
sel.annotation.set_visible(False)
hline1.set_ydata([y1])
vline1.set_xdata([x])
hline2.set_ydata([y2])
vline2.set_xdata([x])
hline1.set_visible(True)
vline1.set_visible(True)
hline2.set_visible(True)
vline2.set_visible(True)
fig = plt.figure(figsize=(15, 10))
ax1 = plt.subplot(2, 1, 1)
ax2 = plt.subplot(2, 1, 2, sharex=ax1)
plot1, = ax1.plot(np.array(np.random.uniform(-1, 1, 100).cumsum()))
plot2, = ax2.plot(np.array(np.random.uniform(-1, 1, 100).cumsum()))
plot1x = plot1.get_xdata()
plot1y = plot1.get_ydata()
plot2x = plot2.get_xdata()
plot2y = plot2.get_ydata()
hline1 = ax1.axhline(plot1y[0], color='k', ls=':', visible=False)
vline1 = ax1.axvline(plot1x[0], color='k', ls=':', visible=False)
hline2 = ax2.axhline(plot2y[0], color='k', ls=':', visible=False)
vline2 = ax2.axvline(plot2x[0], color='k', ls=':', visible=False)
cursor = mplcursors.cursor([plot1, plot2], hover=True)
cursor.connect('add', crosshair)
plt.show()
Sorry for the late answer, but I was horrified by how much code was suggested above, when there is this one-liner on matplotlib to do a simple crosshair accross different axes. It won't show your labels but it's CPU-light.
from matplotlib.widgets import MultiCursor
cursor = MultiCursor(fig.canvas, (ax[0], ax[1]), color='r',lw=0.5, horizOn=True, vertOn=True)
I have the following plot:
import matplotlib.pyplot as plt
fig2 = plt.figure()
ax3 = fig2.add_subplot(2,1,1)
ax4 = fig2.add_subplot(2,1,2)
ax4.loglog(x1, y1)
ax3.loglog(x2, y2)
ax3.set_ylabel('hello')
I want to be able to create axes labels and titles not just for each of the two subplots, but also common labels that span both subplots. For example, since both plots have identical axes, I only need one set of x and y- axes labels. I do want different titles for each subplot though.
I tried a few things but none of them worked right
You can create a big subplot that covers the two subplots and then set the common labels.
import random
import matplotlib.pyplot as plt
x = range(1, 101)
y1 = [random.randint(1, 100) for _ in range(len(x))]
y2 = [random.randint(1, 100) for _ in range(len(x))]
fig = plt.figure()
ax = fig.add_subplot(111) # The big subplot
ax1 = fig.add_subplot(211)
ax2 = fig.add_subplot(212)
# Turn off axis lines and ticks of the big subplot
ax.spines['top'].set_color('none')
ax.spines['bottom'].set_color('none')
ax.spines['left'].set_color('none')
ax.spines['right'].set_color('none')
ax.tick_params(labelcolor='w', top=False, bottom=False, left=False, right=False)
ax1.loglog(x, y1)
ax2.loglog(x, y2)
# Set common labels
ax.set_xlabel('common xlabel')
ax.set_ylabel('common ylabel')
ax1.set_title('ax1 title')
ax2.set_title('ax2 title')
plt.savefig('common_labels.png', dpi=300)
Another way is using fig.text() to set the locations of the common labels directly.
import random
import matplotlib.pyplot as plt
x = range(1, 101)
y1 = [random.randint(1, 100) for _ in range(len(x))]
y2 = [random.randint(1, 100) for _ in range(len(x))]
fig = plt.figure()
ax1 = fig.add_subplot(211)
ax2 = fig.add_subplot(212)
ax1.loglog(x, y1)
ax2.loglog(x, y2)
# Set common labels
fig.text(0.5, 0.04, 'common xlabel', ha='center', va='center')
fig.text(0.06, 0.5, 'common ylabel', ha='center', va='center', rotation='vertical')
ax1.set_title('ax1 title')
ax2.set_title('ax2 title')
plt.savefig('common_labels_text.png', dpi=300)
One simple way using subplots:
import matplotlib.pyplot as plt
fig, axes = plt.subplots(3, 4, sharex=True, sharey=True)
# add a big axes, hide frame
fig.add_subplot(111, frameon=False)
# hide tick and tick label of the big axes
plt.tick_params(labelcolor='none', top=False, bottom=False, left=False, right=False)
plt.grid(False)
plt.xlabel("common X")
plt.ylabel("common Y")
New in matplotlib 3.4.0
There are now built-in methods to set common axis labels:
supxlabel
fig.supxlabel('common x label')
supylabel
fig.supylabel('common y label')
To reproduce OP's loglog plots (common labels but separate titles):
x = np.arange(0.01, 10.01, 0.01)
y = 2 ** x
fig, (ax1, ax2) = plt.subplots(2, 1, constrained_layout=True)
ax1.loglog(y, x)
ax2.loglog(x, y)
# separate subplot titles
ax1.set_title('ax1.title')
ax2.set_title('ax2.title')
# common axis labels
fig.supxlabel('fig.supxlabel')
fig.supylabel('fig.supylabel')
plt.setp() will do the job:
# plot something
fig, axs = plt.subplots(3,3, figsize=(15, 8), sharex=True, sharey=True)
for i, ax in enumerate(axs.flat):
ax.scatter(*np.random.normal(size=(2,200)))
ax.set_title(f'Title {i}')
# set labels
plt.setp(axs[-1, :], xlabel='x axis label')
plt.setp(axs[:, 0], ylabel='y axis label')
Wen-wei Liao's answer is good if you are not trying to export vector graphics or that you have set up your matplotlib backends to ignore colorless axes; otherwise the hidden axes would show up in the exported graphic.
My answer suplabel here is similar to the fig.suptitle which uses the fig.text function. Therefore there is no axes artist being created and made colorless.
However, if you try to call it multiple times you will get text added on top of each other (as fig.suptitle does too). Wen-wei Liao's answer doesn't, because fig.add_subplot(111) will return the same Axes object if it is already created.
My function can also be called after the plots have been created.
def suplabel(axis,label,label_prop=None,
labelpad=5,
ha='center',va='center'):
''' Add super ylabel or xlabel to the figure
Similar to matplotlib.suptitle
axis - string: "x" or "y"
label - string
label_prop - keyword dictionary for Text
labelpad - padding from the axis (default: 5)
ha - horizontal alignment (default: "center")
va - vertical alignment (default: "center")
'''
fig = pylab.gcf()
xmin = []
ymin = []
for ax in fig.axes:
xmin.append(ax.get_position().xmin)
ymin.append(ax.get_position().ymin)
xmin,ymin = min(xmin),min(ymin)
dpi = fig.dpi
if axis.lower() == "y":
rotation=90.
x = xmin-float(labelpad)/dpi
y = 0.5
elif axis.lower() == 'x':
rotation = 0.
x = 0.5
y = ymin - float(labelpad)/dpi
else:
raise Exception("Unexpected axis: x or y")
if label_prop is None:
label_prop = dict()
pylab.text(x,y,label,rotation=rotation,
transform=fig.transFigure,
ha=ha,va=va,
**label_prop)
Here is a solution where you set the ylabel of one of the plots and adjust the position of it so it is centered vertically. This way you avoid problems mentioned by KYC.
import numpy as np
import matplotlib.pyplot as plt
def set_shared_ylabel(a, ylabel, labelpad = 0.01):
"""Set a y label shared by multiple axes
Parameters
----------
a: list of axes
ylabel: string
labelpad: float
Sets the padding between ticklabels and axis label"""
f = a[0].get_figure()
f.canvas.draw() #sets f.canvas.renderer needed below
# get the center position for all plots
top = a[0].get_position().y1
bottom = a[-1].get_position().y0
# get the coordinates of the left side of the tick labels
x0 = 1
for at in a:
at.set_ylabel('') # just to make sure we don't and up with multiple labels
bboxes, _ = at.yaxis.get_ticklabel_extents(f.canvas.renderer)
bboxes = bboxes.inverse_transformed(f.transFigure)
xt = bboxes.x0
if xt < x0:
x0 = xt
tick_label_left = x0
# set position of label
a[-1].set_ylabel(ylabel)
a[-1].yaxis.set_label_coords(tick_label_left - labelpad,(bottom + top)/2, transform=f.transFigure)
length = 100
x = np.linspace(0,100, length)
y1 = np.random.random(length) * 1000
y2 = np.random.random(length)
f,a = plt.subplots(2, sharex=True, gridspec_kw={'hspace':0})
a[0].plot(x, y1)
a[1].plot(x, y2)
set_shared_ylabel(a, 'shared y label (a. u.)')
# list loss and acc are your data
fig = plt.figure()
ax1 = fig.add_subplot(121)
ax2 = fig.add_subplot(122)
ax1.plot(iteration1, loss)
ax2.plot(iteration2, acc)
ax1.set_title('Training Loss')
ax2.set_title('Training Accuracy')
ax1.set_xlabel('Iteration')
ax1.set_ylabel('Loss')
ax2.set_xlabel('Iteration')
ax2.set_ylabel('Accuracy')
The methods in the other answers will not work properly when the yticks are large. The ylabel will either overlap with ticks, be clipped on the left or completely invisible/outside of the figure.
I've modified Hagne's answer so it works with more than 1 column of subplots, for both xlabel and ylabel, and it shifts the plot to keep the ylabel visible in the figure.
def set_shared_ylabel(a, xlabel, ylabel, labelpad = 0.01, figleftpad=0.05):
"""Set a y label shared by multiple axes
Parameters
----------
a: list of axes
ylabel: string
labelpad: float
Sets the padding between ticklabels and axis label"""
f = a[0,0].get_figure()
f.canvas.draw() #sets f.canvas.renderer needed below
# get the center position for all plots
top = a[0,0].get_position().y1
bottom = a[-1,-1].get_position().y0
# get the coordinates of the left side of the tick labels
x0 = 1
x1 = 1
for at_row in a:
at = at_row[0]
at.set_ylabel('') # just to make sure we don't and up with multiple labels
bboxes, _ = at.yaxis.get_ticklabel_extents(f.canvas.renderer)
bboxes = bboxes.inverse_transformed(f.transFigure)
xt = bboxes.x0
if xt < x0:
x0 = xt
x1 = bboxes.x1
tick_label_left = x0
# shrink plot on left to prevent ylabel clipping
# (x1 - tick_label_left) is the x coordinate of right end of tick label,
# basically how much padding is needed to fit tick labels in the figure
# figleftpad is additional padding to fit the ylabel
plt.subplots_adjust(left=(x1 - tick_label_left) + figleftpad)
# set position of label,
# note that (figleftpad-labelpad) refers to the middle of the ylabel
a[-1,-1].set_ylabel(ylabel)
a[-1,-1].yaxis.set_label_coords(figleftpad-labelpad,(bottom + top)/2, transform=f.transFigure)
# set xlabel
y0 = 1
for at in axes[-1]:
at.set_xlabel('') # just to make sure we don't and up with multiple labels
bboxes, _ = at.xaxis.get_ticklabel_extents(fig.canvas.renderer)
bboxes = bboxes.inverse_transformed(fig.transFigure)
yt = bboxes.y0
if yt < y0:
y0 = yt
tick_label_bottom = y0
axes[-1, -1].set_xlabel(xlabel)
axes[-1, -1].xaxis.set_label_coords((left + right) / 2, tick_label_bottom - labelpad, transform=fig.transFigure)
It works for the following example, while Hagne's answer won't draw ylabel (since it's outside of the canvas) and KYC's ylabel overlaps with the tick labels:
import matplotlib.pyplot as plt
import itertools
fig, axes = plt.subplots(3, 4, sharey='row', sharex=True, squeeze=False)
fig.subplots_adjust(hspace=.5)
for i, a in enumerate(itertools.chain(*axes)):
a.plot([0,4**i], [0,4**i])
a.set_title(i)
set_shared_ylabel(axes, 'common X', 'common Y')
plt.show()
Alternatively, if you are fine with colorless axis, I've modified Julian Chen's solution so ylabel won't overlap with tick labels.
Basically, we just have to set ylims of the colorless so it matches the largest ylims of the subplots so the colorless tick labels sets the correct location for the ylabel.
Again, we have to shrink the plot to prevent clipping. Here I've hard coded the amount to shrink, but you can play around to find a number that works for you or calculate it like in the method above.
import matplotlib.pyplot as plt
import itertools
fig, axes = plt.subplots(3, 4, sharey='row', sharex=True, squeeze=False)
fig.subplots_adjust(hspace=.5)
miny = maxy = 0
for i, a in enumerate(itertools.chain(*axes)):
a.plot([0,4**i], [0,4**i])
a.set_title(i)
miny = min(miny, a.get_ylim()[0])
maxy = max(maxy, a.get_ylim()[1])
# add a big axes, hide frame
# set ylim to match the largest range of any subplot
ax_invis = fig.add_subplot(111, frameon=False)
ax_invis.set_ylim([miny, maxy])
# hide tick and tick label of the big axis
plt.tick_params(labelcolor='none', top=False, bottom=False, left=False, right=False)
plt.xlabel("common X")
plt.ylabel("common Y")
# shrink plot to prevent clipping
plt.subplots_adjust(left=0.15)
plt.show()
You could use "set" in axes as follows:
axes[0].set(xlabel="KartalOl", ylabel="Labeled")
I have a subplot of subplots. The outer subplot consists of one row by two columns, and the two inner subplots each consist of four rows and four columns. Suppose I wanted the legend labels that correspond to only the first 2x2 inner subplot. How can I go about doing this? My attempt is below:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
outerD = dict(nrows=1, ncols=2)
innerD = dict(nrows=2, ncols=2)
D = dict(inner=innerD, outer=outerD)
def initialize_dubsub(D, figsize=None):
""" """
fig = plt.figure(figsize=figsize)
outerG = gridspec.GridSpec(D['outer']['nrows'], D['outer']['ncols'], wspace=0.2, hspace=0.2, width_ratios=[5, 5])
axes = []
for n in range(D['inner']['nrows']):
inner = gridspec.GridSpecFromSubplotSpec(D['inner']['nrows'], D['inner']['ncols'], subplot_spec=outerG[n], wspace=0.25, hspace=0.3, width_ratios=[10, 10], height_ratios=[2, 2])
for m in range(D['inner']['nrows']*D['inner']['ncols']):
ax = plt.Subplot(fig, inner[m])
ax.plot([], [], label='{}x{}'.format(n, m))
ax.set_xticks([])
ax.set_yticks([])
axes.append(ax)
fig.add_subplot(ax)
# handles, labels = axes[:4].get_legend_handles_labels() # first 2x2
# fig.legend(handles=handles, labels=labels, loc='lower center')
fig.legend(loc='lower center', ncol=4, mode='expand')
plt.show()
plt.close(fig)
initialize_dubsub(D)
This code will output 8 handles and 8 labels, whereas I want 4 each. I commented out the get_legend_handles_labels() method as this does not work on arrays.
I realize I can do ax.legend() but I prefer to use fig.legend(...). How can I achieve this?
Rather than trying to call .get_legend_handles_labels on the array of subplots you want, you can just loop over the axes in that array, and append the handles and labels from those four subplots to a list.
For example:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
outerD = dict(nrows=1, ncols=2)
innerD = dict(nrows=2, ncols=2)
D = dict(inner=innerD, outer=outerD)
def initialize_dubsub(D, figsize=None):
""" """
fig = plt.figure(figsize=figsize)
outerG = gridspec.GridSpec(D['outer']['nrows'], D['outer']['ncols'], wspace=0.2, hspace=0.2, width_ratios=[5, 5])
axes = []
for n in range(D['inner']['nrows']):
inner = gridspec.GridSpecFromSubplotSpec(D['inner']['nrows'], D['inner']['ncols'], subplot_spec=outerG[n], wspace=0.25, hspace=0.3, width_ratios=[10, 10], height_ratios=[2, 2])
for m in range(D['inner']['nrows']*D['inner']['ncols']):
ax = plt.Subplot(fig, inner[m])
ax.plot([], [], label='{}x{}'.format(n, m))
ax.set_xticks([])
ax.set_yticks([])
axes.append(ax)
fig.add_subplot(ax)
handles, labels = [], []
for ax in axes[:4]:
handles_, labels_ = ax.get_legend_handles_labels()
handles += handles_
labels += labels_
fig.legend(handles=handles, labels=labels, loc='lower center')
#fig.legend(loc='lower center', ncol=4, mode='expand')
plt.show()
plt.close(fig)
initialize_dubsub(D)
Try replacing
ax.plot([], [], label='{}x{}'.format(n, m))
by
ax.plot([], [], label=('' if n==0 else '_') + '{}x{}'.format(n, m))
if I understand your setup correct...
I wrote some code to try and solve this question:
https://stackoverflow.com/questions/39477748/how-to-annotate-bars-with-values-on-pandas-on-seaborn-factorplot-bar-plot
I used part of the code that can be found here:
matplotlib advanced bar plot
Why is the graph so small? The code just tells to grab the accuracies from Pandas dataframe .
The code:
sns.set(style="white")
g = sns.factorplot(x="Stages", y="Accuracy", hue="Dataset", data=df, saturation = 5, size=4, aspect=2, kind="bar",
palette= myPalette, legend=False)
ax=g.ax
def annotateBars(row, ax=ax):
if row['Accuracy'] < 20:
color = 'white'
vertalign = 'bottom'
vertpad = 2
else:
color = 'black'
vertalign = 'top'
vertpad = -2
ax.text(row.name, row['Accuracy'] + vertpad, "{:.1f}%".format(row['Accuracy']),
zorder=10, rotation=90, color=color,
horizontalalignment='center',
verticalalignment=vertalign,
fontsize=12, weight='heavy')
junk = df.apply(annotateBars, ax=ax, axis=1)
This is code to annotate each bar, but ...with Pandas and Matplotlib. The only problem is that I do not know how to change colors and group the "x axis" :(
df = df.set_index('Stages')
ax = df.plot.bar(title="Accuracy")
ax.set_ylim(0, 120)
for p in ax.patches:
ax.annotate("%.2f" % p.get_height(), (p.get_x() + p.get_width() / 2., p.get_height()),
ha='center', va='center', rotation=90, xytext=(0, 20), textcoords='offset points') #vertical bars
#Seaborn --factorplot
colors = ["windows blue", "orange red", "grey", "amber"]
myPalette = sns.xkcd_palette(colors) #envío "colors" a la función xkcd_palette
sns.set(style="white") #fondo blanco
g = sns.factorplot(x="Stages", y="Accuracy", hue="Dataset", data=df, saturation=5, size=4, aspect=3, kind="bar",
palette= myPalette, legend=False) #se suprime la leyenda
g.set(ylim=(0, 140))
g.despine(right=False)
g.set_xlabels("")
g.set_ylabels("")
g.set_yticklabels("")
#Matplotlib --legend creation
myLegend=plt.legend(bbox_to_anchor=(0., 1.2, 1., .102), prop ={'size':10}, loc=10, ncol=4, #left, bottom, width, height
title=r'TOTAL ACCURACY AND PER STAGE-RANDOM FOREST')
myLegend.get_title().set_fontsize('24')
#Matplotlib --anotación de barras
ax=g.ax #annotate axis = seaborn axis
def annotateBars(row, ax=ax):
for p in ax.patches:
ax.annotate("%.2f" % p.get_height(), (p.get_x() + p.get_width() / 2., p.get_height()),
ha='center', va='center', fontsize=11, color='gray', rotation=90, xytext=(0, 20),
textcoords='offset points') verticales
plot = df.apply(annotateBars, ax=ax, axis=1)
This can now be plotted much more concisely
Axes.bar_label automatically labels bars [since matplotlib 3.4]
for container in ax.containers:
ax.bar_label(container)
Axes.legend includes fontsize and title_fontsize params [since matplotlib 3.0]
ax.legend(fontsize=10, title='ACCURACY', title_fontsize=24)
Also note that seaborn.factorplot has been renamed to seaborn.catplot [since seaborn 0.9]
Updated seaborn.catplot
colors = ['xkcd:windows blue', 'xkcd:orange red', 'xkcd:grey', 'xkcd:amber']
g = sns.catplot(x='Stages', y='Accuracy', hue='Dataset', data=df,
kind='bar', height=4, aspect=3, palette=colors, legend=False)
# auto-label bars
for container in g.ax.containers:
g.ax.bar_label(container, fmt='%.2f', padding=2, rotation=90)
# add legend with custom font sizes
ax.legend(bbox_to_anchor=(0, 1.2, 1, 0.102), loc=10, ncol=4, fontsize=10,
title='TOTAL ACCURACY AND PER STAGE-RANDOM FOREST', title_fontsize=24)
# redecorate
g.despine(right=False)
g.set_xlabels('')
g.set_ylabels('')
g.ax.set_yticklabels([])
Updated DataFrame.plot.bar
ax = df.pivot('Stages', 'Dataset', 'Accuracy').plot.bar(legend=False)
# auto-label bars
for container in ax.containers:
ax.bar_label(container, fmt='%.2f', padding=3, rotation=90, size='small')
# add legend with custom font sizes
ax.legend(bbox_to_anchor=(0, 1.1, 1, 0.102), loc=10, ncol=4, fontsize='small',
title='TOTAL ACCURACY AND PER STAGE-RANDOM FOREST', title_fontsize='xx-large')
# redecorate
sns.despine(right=False)
ax.set_yticklabels([])
plt.xticks(rotation=0)