How to expand matplolib window without stretching the plot? - matplotlib

I want to increase the grey area around the plot, but keeping the plot the same size. I've already tried changing the figure size, which ends up stretching the plot.

The axes inside the figure is positionned relative to the figure. Per default you have e.g. a fraction of 0.125 of figure width as space at the left. This means that resizing the figure, scales the axes as well.
You may calculate how much the spacings need to change such that if the figure is rescaled, the axes size remains constant. The new spacings then need to be set using fig.subplots_adjust.
import matplotlib.pyplot as plt
def set_figsize(figw,figh, fig=None):
if not fig: fig=plt.gcf()
w, h = fig.get_size_inches()
l = fig.subplotpars.left
r = fig.subplotpars.right
t = fig.subplotpars.top
b = fig.subplotpars.bottom
hor = 1.-w/float(figw)*(r-l)
ver = 1.-h/float(figh)*(t-b)
fig.subplots_adjust(left=hor/2., right=1.-hor/2., top=1.-ver/2., bottom=ver/2.)
fig, ax=plt.subplots()
ax.plot([1,3,2])
set_figsize(9,7)
plt.show()
You may then also use this function to update the subplot params when the figure window is resized.
import matplotlib.pyplot as plt
class Resizer():
def __init__(self,fig=None):
if not fig: fig=plt.gcf()
self.fig=fig
self.w, self.h = self.fig.get_size_inches()
self.l = self.fig.subplotpars.left
self.r = self.fig.subplotpars.right
self.t = self.fig.subplotpars.top
self.b = self.fig.subplotpars.bottom
def set_figsize(self, figw,figh):
hor = 1.-self.w/float(figw)*(self.r-self.l)
ver = 1.-self.h/float(figh)*(self.t-self.b)
self.fig.subplots_adjust(left=hor/2., right=1.-hor/2., top=1.-ver/2., bottom=ver/2.)
def resize(self, event):
figw = event.width/self.fig.dpi
figh = event.height/self.fig.dpi
self.set_figsize( figw,figh)
fig, ax=plt.subplots()
ax.plot([1,3,2])
r = Resizer()
cid = fig.canvas.mpl_connect("resize_event", r.resize)
plt.show()

In the window of a matplotlib figure, there's a button called 'Configure subplots' (see below picture, screenshot on Windows 10 with matplotlib version 1.5.2). Try to change the parameters 'left' and 'right'. You can also change these parameters with plt.subplots_adjust(left=..., bottom=..., right=..., top=..., wspace=..., hspace=...).

Related

subplots_adjust moves axes unpredictably?

I'm working on a python module that creates a matplotlib figure with an on_resize listener. The listener forces the height of the lower axes to a specific number of pixels (rather than scaling relative to figure size). It works. However, if (in matplotlib interactive mode) after creating the plot the user calls fig.subplots_adjust() it messes up subplot sizes. Here's a radically simplified version of what the module does:
import matplotlib.pyplot as plt
plt.ion()
def make_plot():
fig = plt.figure()
gs = plt.GridSpec(10, 1, figure=fig)
ax_upper = fig.add_subplot(gs[:-1])
ax_lower = fig.add_subplot(gs[-1])
ax_upper.plot([0, 1])
ax_lower.plot([0, 1])
fig.canvas.mpl_connect('resize_event', on_resize)
return fig
def on_resize(event):
fig = event.canvas.figure
# get the current position
ax_lower_pos = list(fig.axes[1].get_position().bounds) # L,B,W,H
# compute desired height in figure-relative coords
desired_height_px = 40
xform = fig.transFigure.inverted()
desired_height_rel = xform.transform([0, desired_height_px])[1]
# set the new height
ax_lower_pos[-1] = desired_height_rel
fig.axes[1].set_position(ax_lower_pos)
# adjust ax_upper accordingly
ax_lower_top = fig.axes[1].get_position().extents[-1] # L,B,R,T
ax_upper_pos = list(fig.axes[0].get_position().bounds) # L,B,W,H
# new bottom
new_upper_bottom = ax_lower_top + desired_height_rel
ax_upper_pos[1] = new_upper_bottom
# new height
ax_upper_top = fig.axes[0].get_position().extents[-1] # L,B,R,T
new_upper_height = ax_upper_top - new_upper_bottom
ax_upper_pos[-1] = new_upper_height
# set the new position
fig.axes[0].set_position(ax_upper_pos)
fig.canvas.draw()
Here's the output if the user calls fig = make_plot():
Now if the user calls fig.subplots_adjust, the bottom axis is squished and the space between bottom and top axes is even more squished (the on_resize listener had set them both to 40px):
fig.subplots_adjust(top=0.7)
At this point, grabbing the corner of the window and dragging even a tiny bit is enough to trigger the on_resize listener and restore what I want (fixed pixel height for bottom axes and space between axes) while keeping the newly-added wide top margin intact:
How can I get that result without having to manually trigger a resize event? As far as I can tell, subplots_adjust does not fire off any events that I could listen for.
I think the problem lies in ax.update_params() updating the axes position with a figbox taken from the underlying subplotspec (which as far as I can tell doesn't get updated after initial figure creation?). (note: update_params is called from within subplots_adjust, see here).
The underlying problem seems to be to make an axes with a specific height in pixels. An easy solution to this is to use mpl_toolkits.axes_grid1's make_axes_locatable.
This allows to get rid of any callback and hence of the complete problem of the race condition in the events.
A note: The plot seems to be part of a bigger library. Since it is always nice not to patronize the users of such packages, one would usually allow them to specify the axes to plot to, such that they can put the plot into a bigger figure with other elements. The below solution makes this particularly easy.
Of course, also calling plt.subplots_adjust is still possible at any time.
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.axes_divider import make_axes_locatable
desired_height_px = 40 #pixel
def make_plot(ax=None):
if not ax:
fig, ax = plt.subplots()
else:
fig = ax.figure
div = make_axes_locatable(ax)
cax = div.append_axes("bottom", desired_height_px/fig.dpi, pad=0.25)
sc1 = ax.scatter([2,1,3], [2,3,1], c=[1,2,3])
sc2 = cax.scatter([3,2,1],[2,3,1], c=[3,1,2])
return fig, ax, cax, (sc1, sc2)
fig, (ax1, ax2) = plt.subplots(1,2)
make_plot(ax=ax1)
#user plot on ax2
ax2.plot([1,3])
fig.subplots_adjust(top=0.7)
plt.show()

Map a colorbar based on plot instead of imshow

I'm trying to get a colorbar for the following minimal example of my code.
g1 = gridspec.GridSpec(1, 1)
f, ((ax0)) = plt.subplots(1, 1)
ax0 = subplot(g1[0])
cmap = matplotlib.cm.get_cmap('viridis')
for i in linspace(0,1,11):
x = [-1,0,1]
y = [i,i,i]
rgba = cmap(i)
im = ax0.plot(x,y,color=rgba)
f.colorbar(im)
I also tried f.colorbar(cmap)
Probably pretty obvious, but I get errors such as
'ListedColormap' object has no attribute 'autoscale_None'
In reality, the value defining i is more complex, but I think this should do the trick. My data is plotted with plot and not with imshow (for which I know how to make the colormap).
The answers so far seem overly complicated. fig.colorbar() expects a ScalarMappable as its first argument. Often ScalarMappables are produced by imshow or contourplots and are readily avaible.
In this case you would need to define your custom ScalarMappable to provide to the colorbar.
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
cmap = plt.cm.get_cmap('viridis')
for i in np.linspace(0,1,11):
x = [-1,0,1]
y = [i,i,i]
rgba = cmap(i)
im = ax.plot(x,y,color=rgba)
sm = plt.cm.ScalarMappable(cmap=cmap)
sm.set_array([])
fig.colorbar(sm)
plt.show()
You should pass an Image or ContourSet when you call colorbar on a Figure.
You can make an image of the data points by calling plt.imshow with the data. You can start with this:
data = []
for i in np.linspace(0,1,11):
x = [-1,0,1]
y = [i,i,i]
rgba = cmap(i)
ax0.plot(x,y,color=rgba)
data.append([x, y])
image = plt.imshow(data)
figure.colorbar(image)
plt.show()
Reference:
https://matplotlib.org/api/figure_api.html#matplotlib.figure.Figure.colorbar
Oluwafemi Sule's solution almost works, but it plots the matrix into the same figure as the lines. Here a solution that opens a second figure, does the imshow call on that second figure, uses the result to draw the colorbar in the first figure, and then closes the second figure before calling plt.show():
import matplotlib
from matplotlib import pyplot as plt
from matplotlib import gridspec
import numpy as np
cmap = matplotlib.cm.get_cmap('viridis')
g1 = gridspec.GridSpec(1, 1)
f0, ((ax0)) = plt.subplots(1, 1)
f1, ((ax1)) = plt.subplots(1, 1)
for i in np.linspace(0,1,11):
x = [-1,0,1]
y = [i,i,i]
rgba = cmap(i)
ax0.plot(x,y,color=rgba)
data = np.linspace(0,1,100).reshape((10,10))
image = ax1.imshow(data)
f0.colorbar(image)
plt.close(f1)
plt.show()
The result looks like this:

Draw colorbar with twin scales

I'd like to draw a (vertical) colorbar, which has two different scales (corresponding to two different units for the same quantity) on each side. Think Fahrenheit on one side and Celsius on the other side. Obviously, I'd need to specify the ticks for each side individually.
Any idea how I can do this?
That should get you started:
import matplotlib.pyplot as plt
import numpy as np
# generate random data
x = np.random.randint(0,200,(10,10))
plt.pcolormesh(x)
# create the colorbar
# the aspect of the colorbar is set to 'equal', we have to set it to 'auto',
# otherwise twinx() will do weird stuff.
cbar = plt.colorbar()
pos = cbar.ax.get_position()
cbar.ax.set_aspect('auto')
# create a second axes instance and set the limits you need
ax2 = cbar.ax.twinx()
ax2.set_ylim([-2,1])
# resize the colorbar (otherwise it overlays the plot)
pos.x0 +=0.05
cbar.ax.set_position(pos)
ax2.set_position(pos)
plt.show()
If you create a subplot for the colorbar, you can create a twin axes for that subplot and manipulate it like a normal axes.
import matplotlib.colors as mcolors
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(-1,2.7)
X,Y = np.meshgrid(x,x)
Z = np.exp(-X**2-Y**2)*.9+0.1
fig, (ax, cax) = plt.subplots(ncols=2, gridspec_kw={"width_ratios":[15,1]})
im =ax.imshow(Z, vmin=0.1, vmax=1)
cbar = plt.colorbar(im, cax=cax)
cax2 = cax.twinx()
ticks=np.arange(0.1,1.1,0.1)
iticks=1./np.array([10,3,2,1.5,1])
cbar.set_ticks(ticks)
cbar.set_label("z")
cbar.ax.yaxis.set_label_position("left")
cax2.set_ylim(0.1,1)
cax2.set_yticks(iticks)
cax2.set_yticklabels(1./iticks)
cax2.set_ylabel("1/z")
plt.show()
Note that in newer version of matplotlib, the above answers no long work (as #Ryan Skene pointed out). I'm using v3.3.2. The secondary_yaxis function works for the colorbars in the same way as for regular plot axes and gives one colorbar with two scales: https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.secondary_yaxis.html#matplotlib.axes.Axes.secondary_yaxis
import matplotlib.pyplot as plt
import numpy as np
# generate random data
x = np.random.randint(0,200,(10,10)) #let's assume these are temperatures in Fahrenheit
im = plt.imshow(x)
# create the colorbar
cbar = plt.colorbar(im,pad=0.1) #you may need to adjust this padding for the secondary colorbar label[enter image description here][1]
cbar.set_label('Temperature ($^\circ$F)')
# define functions that relate the two colorbar scales
# e.g., Celcius to Fahrenheit and vice versa
def F_to_C(x):
return (x-32)*5/9
def C_to_F(x):
return (x*9/5)+32
# create a second axes
cbar2 = cbar.ax.secondary_yaxis('left',functions=(F_to_C,C_to_F))
cbar2.set_ylabel('Temperatrue ($\circ$C)')
plt.show()
I am using an inset axis for my colorbar and, for some reason, I found the above to answers no longer worked as of v3.4.2. The twinx took up the entire original subplot.
So I just replicated the inset axis (instead of using twinx) and increased the zorder on the original inset.
axkws = dict(zorder=2)
cax = inset_axes(
ax, width="100%", height="100%", bbox_to_anchor=bbox,
bbox_transform=ax.transAxes, axes_kwargs=axkws
)
cbar = self.fig.colorbar(mpl.cm.ScalarMappable(cmap=cmap), cax=cax)
cbar.ax.yaxis.set_ticks_position('left')
caxx = inset_axes(
ax, width="100%", height="100%",
bbox_to_anchor=bbox, bbox_transform=ax.transAxes
)
caxx.yaxis.set_ticks_position('right')

matplotlib: Using append_axes multiple times

I'm new to matplotlib, so I do not have strong enough command of the language to know if I'm going about this the right way, but I've been searching for the answer for a while now, and I just cannot find anything one way or the other on this.
I know how to use matplotlib's append_axes locator function to append histograms alongside 2D plots, e.g.:
axMain= fig1.add_subplot(111)
cax = plt.contourf(xl,y1,z1)
divider = make_axes_locatable(axMain)
axHisty = divider.append_axes("right", 1.2, pad=0.1, sharey=axMain)
axHisty.plot(x,y)
and I also know how to append a colorbar in a similar manner:
divider = make_axes_locatable(axMain)
ax_cb = divider.new_horizontal(size='5%', pad=0.3)
fig1.add_axes(ax_cb)
fig1.colorbar(cax, cax=ax_cb)
What I am not clear on is how to do both in the same subplot without the two appended figures overlapping. To be clear, I want the histogram to have the same yaxis ticks and height as the axContour, and I want the colorbar to have the same height as axContour. ImageGrid doesn't seem to be quite what I want because I do not want to fix the size of my plot. It would better for me if I could add/remove these figure "embellishments" interactively, but maybe that is not possible...Let me know!
You are already fixing the size of your plot with divider.append_axes("right", 1.2, pad=0.1, sharey=axMain). 1.2 is the size of the new axis. Below is a way of plotting three axes using gridspec.
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.gridspec as grd
from numpy.random import rand
# add axes
fig1 = plt.figure(1)
gs = grd.GridSpec(1, 3, width_ratios=[5,1, 1], wspace=0.3)
axMain = plt.subplot(gs[0])
axHisty = plt.subplot(gs[1])
ax_cb = plt.subplot(gs[2])
# some things to plot
x = [1,2,3,4]
y = [1,2,3,4]
x1 = [1,2,3,4]
y1 = [1,2,3,4]
z1 = rand(4,4)
# make plots
h = axMain.contourf(x1,y1,z1)
axHisty.plot(x,y)
cb = plt.colorbar(h, cax = ax_cb)
plt.show()

automatically changing view bounds on resize matlotlib

Is it possible to setup plot to show more data when expanded?
Matplotlib plots scale when resized. To show specific area one can use set_xlim and such on axes. I have an ecg-like plot showing realtime data, its y limits are predefined, but I want to see more data along x if I expand window or just have big monitor.
Im using it in a pyside app and I could just change xlim on resize but I want more clean and generic solution.
One way to do this is to implement a handler for resize_event. Here is a short example how this might be done. You can modify it for your needs:
import numpy as np
import matplotlib.pyplot as plt
def onresize(event):
width = event.width
scale_factor = 100.
data_range = width/scale_factor
start, end = plt.xlim()
new_end = start+data_range
plt.xlim((start, new_end))
if __name__ == "__main__":
fig = plt.figure()
ax = fig.add_subplot(111)
t = np.arange(100)
y = np.random.rand(100)
ax.plot(t,y)
plt.xlim((0, 10))
cid = fig.canvas.mpl_connect('resize_event', onresize)
plt.show()