Making multiple figures in matplotlib with legend on each one - python

I am trying to make multiple figures in parallel, each with its own legend. My code produces multiple figures but I can only ever get the legend to appear on the last figure instance - is there a way of getting it to appear on all figures? I have a large number of datasets so I would like to be able to use a for loop (or similar) - making each figure separately is not really an option.
I have included a minimum working example below that reproduces the problem.
import numpy as np
import matplotlib.pyplot as plt
X1 = np.linspace(0,5,5)
X2 = np.linspace(1,6,5)
Y1 = np.power(X1,2)
Y2 = np.power(X2,2)
Z1 = np.power(X1,3)
Z2 = np.power(X2,3)
Xs = [X1,X2]
Ys = [Y1,Y2]
Zs = [Z1,Z2]
# Marker size
size = 100
for x,y,z, in zip(Xs,Ys,Zs):
plt.figure()
ax = plt.subplot(111)
ax.scatter(x,y,linewidth=1.5,s=size,facecolors='#0571b0',marker='o',alpha=0.5,label='A label')
ax.scatter(x,z,linewidth=1.5,s=size,facecolors='#92c5de',marker='o',alpha=0.5,label='Another label')
plt.legend(bbox_to_anchor=(1.45,1.), loc='top left',scatterpoints=1,fontsize=8)
plt.show()

It seems the legend is simply out of the figure. You place it at (1.45, 1) (in axes coordinates. Putting it at (1,1) and setting the location e.g. to loc="upper right" (note that "top left" does not exist), will produce the legend in the plot.
Here is the complete example:
import numpy as np
import matplotlib.pyplot as plt
X1 = np.linspace(0,5,5)
X2 = np.linspace(1,6,5)
Xs = [X1,X2]
Ys = [X1**2,X2**2]
Zs = [X1**3,X2**3]
# Marker size
size = 100
for x,y,z, in zip(Xs,Ys,Zs):
plt.figure()
ax = plt.subplot(111)
ax.scatter(x,y,linewidth=1.5,s=size,facecolors='#0571b0',marker='o',alpha=0.5,label='A label')
ax.scatter(x,z,linewidth=1.5,s=size,facecolors='#92c5de',marker='o',alpha=0.5,label='Another label')
plt.legend(bbox_to_anchor=(1,1), loc='upper right',scatterpoints=1,fontsize=8)
plt.show()

Related

Setting the same scale for subplots but different limits using matplotlib

I want the scaling to be the same for my two subplots to make them comparable, but the limits should be set automatically.
Here a small working example:
import matplotlib.pyplot as plt
import numpy as np
time = range(20)
y1 = np.random.rand(20)*2
y2 = np.random.rand(20) + 10
fig, axes = plt.subplots(2, figsize=(10,4), sharex=True, sharey=True)
# OPTION 2: fig, axes = plt.subplots(2, figsize=(10,4))
axes[0].plot(time, y1)
axes[1].plot(time, y2)
plt.show()
The plot looks like this:
and with option 2 uncommented it looks like this:
In the second plot, it looks like y1 and y2 are equally noisy which is wrong, but in plot 1 the axis limits are too high/low.
I am not aware of an automatic scaling function that does this (that does not mean it does not exist - actually, I would be surprised it did not exist). But it is not difficult to write it:
import matplotlib.pyplot as plt
#data generation
import numpy as np
np.random.seed(123)
time = range(20)
y1 = np.random.rand(20)*2
y2 = np.random.rand(20) + 10
y3 = np.random.rand(20)*6-12
#plot data
fig, axes = plt.subplots(3, figsize=(10,8), sharex=True)
for ax, y in zip(axes, [y1, y2, y3]):
ax.plot(time, y)
#determine axes and their limits
ax_selec = [(ax, ax.get_ylim()) for ax in axes]
#find maximum y-limit spread
max_delta = max([lmax-lmin for _, (lmin, lmax) in ax_selec])
#expand limits of all subplots according to maximum spread
for ax, (lmin, lmax) in ax_selec:
ax.set_ylim(lmin-(max_delta-(lmax-lmin))/2, lmax+(max_delta-(lmax-lmin))/2)
plt.show()
Sample output:

Want to change the bar chart in matplotlib using slider [duplicate]

I have bar chart, with a lot of custom properties ( label, linewidth, edgecolor)
import matplotlib.pyplot as plt
fig = plt.figure()
ax = plt.gca()
x = np.arange(5)
y = np.random.rand(5)
bars = ax.bar(x, y, color='grey', linewidth=4.0)
ax.cla()
x2 = np.arange(10)
y2 = np.random.rand(10)
ax.bar(x2,y2)
plt.show()
With 'normal' plots I'd use set_data(), but with barchart I got an error: AttributeError: 'BarContainer' object has no attribute 'set_data'
I don't want to simply update the heights of the rectangles, I want to plot totally new rectangles. If I use ax.cla(), all my settings (linewidth, edgecolor, title..) are lost too not only my data(rectangles), and to clear many times, and reset everything makes my program laggy. If I don't use ax.cla(), the settings remain, the program is faster (I don't have to set my properties all the time), but the rectangles are drawn of each other, which is not good.
Can you help me with that?
In your case, bars is only a BarContainer, which is basically a list of Rectangle patches. To just remove those while keeping all other properties of ax, you can loop over the bars container and call remove on all its entries or as ImportanceOfBeingErnest pointed out simply remove the full container:
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax = plt.gca()
x = np.arange(5)
y = np.random.rand(5)
bars = ax.bar(x, y, color='grey', linewidth=4.0)
bars.remove()
x2 = np.arange(10)
y2 = np.random.rand(10)
ax.bar(x2,y2)
plt.show()

How to update barchart in matplotlib?

I have bar chart, with a lot of custom properties ( label, linewidth, edgecolor)
import matplotlib.pyplot as plt
fig = plt.figure()
ax = plt.gca()
x = np.arange(5)
y = np.random.rand(5)
bars = ax.bar(x, y, color='grey', linewidth=4.0)
ax.cla()
x2 = np.arange(10)
y2 = np.random.rand(10)
ax.bar(x2,y2)
plt.show()
With 'normal' plots I'd use set_data(), but with barchart I got an error: AttributeError: 'BarContainer' object has no attribute 'set_data'
I don't want to simply update the heights of the rectangles, I want to plot totally new rectangles. If I use ax.cla(), all my settings (linewidth, edgecolor, title..) are lost too not only my data(rectangles), and to clear many times, and reset everything makes my program laggy. If I don't use ax.cla(), the settings remain, the program is faster (I don't have to set my properties all the time), but the rectangles are drawn of each other, which is not good.
Can you help me with that?
In your case, bars is only a BarContainer, which is basically a list of Rectangle patches. To just remove those while keeping all other properties of ax, you can loop over the bars container and call remove on all its entries or as ImportanceOfBeingErnest pointed out simply remove the full container:
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax = plt.gca()
x = np.arange(5)
y = np.random.rand(5)
bars = ax.bar(x, y, color='grey', linewidth=4.0)
bars.remove()
x2 = np.arange(10)
y2 = np.random.rand(10)
ax.bar(x2,y2)
plt.show()

Put legend on a place of a subplot

I would like to put a legend on a place of a central subplot (and remove it).
I wrote this code:
import matplotlib.pylab as plt
import numpy as np
f, ax = plt.subplots(3,3)
x = np.linspace(0, 2. * np.pi, 1000)
y = np.sin(x)
for axis in ax.ravel():
axis.plot(x, y)
legend = axis.legend(loc='center')
plt.show()
I do not know how to hide a central plot. And why legend is not appear?
This link did not help http://matplotlib.org/1.3.0/examples/pylab_examples/legend_demo.html
There are several problems with your code. In your for loop, you are attempting to plot a legend on each axis (the loc="center" refers to the axis, not the figure), yet you have not given a plot label to represent in your legend.
You need to choose the central axis in your loop and only display a legend for this axis. This iteration of the loop should have no plot call either, if you don't want a line there. You can do this with a set of conditionals like I have done in the following code:
import matplotlib.pylab as plt
import numpy as np
f, ax = plt.subplots(3,3)
x = np.linspace(0, 2. * np.pi, 1000)
y = np.sin(x)
handles, labels = (0, 0)
for i, axis in enumerate(ax.ravel()):
if i == 4:
axis.set_axis_off()
legend = axis.legend(handles, labels, loc='center')
else:
axis.plot(x, y, label="sin(x)")
if i == 3:
handles, labels = axis.get_legend_handles_labels()
plt.show()
This gives me the following image:

Jupyter Notebook: Output image in previous line

I want to plot some image side by side in my jupyter notebook. So it can save some space for display. For example
This is done through
fig = plt.figure(figsize=(14,3))
ax1 = fig.add_subplot(1,3,1,projection = '3d')
ax2 = fig.add_subplot(1,3,2)
ax3 = fig.add_subplot(1,3,3)
And this makes them in one .png file. However, later on in writing the paper, I may only want part of the image. For example, the 2nd or the 3rd in previous plot. And this requires me to crop the image manually.
One way I can think of, is to make each subplot seperately, but display them in same line. In Python/Jupyter Notebook, the string output can achieve this by adding a comma at the end of previous line:
print 5,
print 6
# returns 5, 6
# instead of
# 5
# 6
I'm wondering if there is anything similar in Jupyter Nobebook, that can do something like
plot fig1,
plot fig2
# Out put [fig1],[fig2]
# instead of
# fig1
# fig2
Output fig1, fig2 in the same line, but in seperate .png file?
use the following align_figures():
def align_figures():
import matplotlib
from matplotlib._pylab_helpers import Gcf
from IPython.display import display_html
import base64
from ipykernel.pylab.backend_inline import show
images = []
for figure_manager in Gcf.get_all_fig_managers():
fig = figure_manager.canvas.figure
png = get_ipython().display_formatter.format(fig)[0]['image/png']
src = base64.encodebytes(png).decode()
images.append('<img style="margin:0" align="left" src="data:image/png;base64,{}"/>'.format(src))
html = "<div>{}</div>".format("".join(images))
show._draw_called = False
matplotlib.pyplot.close('all')
display_html(html, raw=True)
Here is a test:
fig1, ax1 = pl.subplots(figsize=(4, 3))
fig2, ax2 = pl.subplots(figsize=(4, 3))
fig3, ax3 = pl.subplots(figsize=(4, 3))
align_figures()
The code assumes that the output format is PNG image.
first let me recommend you use a colormap other than the jet colormap for the reasons detailed in A better colormap for matplotlib.
As to what you want to do you can achieve this with a modified code from: https://stackoverflow.com/a/26432947/835607
I've extended that function to handle the zaxis of 3d plots as well as the colorbars you are using.
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.transforms import Bbox
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.ticker import LinearLocator, FormatStrFormatter
def full_extent(ax, xpad=0.0, ypad=0.0, cbar=None):
"""Modified from https://stackoverflow.com/a/26432947/835607
Get the full extent of an axes, including axes labels, tick labels, and
titles.
You may need to pad the x or y dimension in order to not get slightly chopped off labels
For text objects, we need to draw the figure first, otherwise the extents
are undefined. These draws can be eliminated by calling plt.show() prior
to calling this function."""
ax.figure.canvas.draw()
items = ax.get_xticklabels() + ax.get_yticklabels()
items += [ax, ax.title, ax.xaxis.label, ax.yaxis.label]
if '3D' in str(type(ax)):
items += ax.get_zticklabels() +[ax.zaxis.label]
if cbar:
items+=cbar.ax.get_yticklabels()
bbox = Bbox.union([cbar.ax.get_window_extent()]+[item.get_window_extent() for item in items])
else:
bbox = Bbox.union([item.get_window_extent() for item in items])
return bbox.expanded(1.0 + xpad, 1.0 + ypad)
Now for an example I plot 3 subplots and save them all to separate files. Note that the full_extent function has cbar, xpad, and ypad as arguments. For the plots that have colorbars make sure to pass the colorbar axes object to the function. You may also need to play around with the padding to get the best results.
# Make an example plot with 3 subplots...
fig = plt.figure(figsize=(9,4))
#3D Plot
ax1 = fig.add_subplot(1,3,1,projection='3d')
X = np.arange(-5, 5, 0.25)
Y = np.arange(-5, 5, 0.25)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R)
surf = ax1.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap='viridis',
linewidth=0, antialiased=False)
ax1.set_zlim(-1.01, 1.01)
ax1.zaxis.set_major_locator(LinearLocator(10))
ax1.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))
# This plot has a colorbar that we'll need to pass to extent
ax2 = fig.add_subplot(1,3,2)
data = np.clip(np.random.randn(250, 250), -1, 1)
cax = ax2.imshow(data, interpolation='nearest', cmap='viridis')
ax2.set_title('Gaussian noise')
cbar = fig.colorbar(cax)
ax2.set_xlabel('asdf')
ax2.set_ylabel('Some Cool Data')
#3rd plot for fun
ax3 = fig.add_subplot(1,3,3)
ax3.plot([1,4,5,7,7],[3,5,7,8,3],'ko--')
ax3.set_ylabel('adsf')
ax3.set_title('a title')
plt.tight_layout() #no overlapping labels
plt.show() #show in notebook also give text an extent
fig.savefig('full_figure.png') #just in case
# Save just the portion _inside_ the boundaries of each axis
extent1 = full_extent(ax1).transformed(fig.dpi_scale_trans.inverted())
fig.savefig('ax1_figure.png', bbox_inches=extent1)
extent2 = full_extent(ax2,.05,.1,cbar).transformed(fig.dpi_scale_trans.inverted())
fig.savefig('ax2_figure.png', bbox_inches=extent2)
extent3 = full_extent(ax3).transformed(fig.dpi_scale_trans.inverted())
fig.savefig('ax3_figure.png', bbox_inches=extent3)
This plots the three plots on one line as you wanted and creates cropped output images such as this one:

Categories

Resources