matplotlib autoscale with respect to a specific plot [duplicate] - python

I use matplotib's Axes API to plot some figures. One of the lines I plot represents the theoretical expected line. It has no meaning outside of the original y and x limits. What I want, is for matlplotlib to ignore it when autoscaling the limits. What I used to do, is to check what are the current limits, then plot, and reset the limits. The problem is that when I plot a third plot, the limits get recalculated together with the theoretical line, and that really expands the graph.
# Boilerplate
from matplotlib.figure import Figure
from matplotlib.backends.backend_pdf import FigureCanvasPdf
from numpy import sin, linspace
fig = Figure()
ax = fig.add_subplot(1,1,1)
x1 = linspace(-1,1,100)
ax.plot(x1, sin(x1))
ax.plot(x1, 3*sin(x1))
# I wish matplotlib would not consider the second plot when rescaling
ax.plot(x1, sin(x1/2.0))
# But would consider the first and last
canvas_pdf = FigureCanvasPdf(fig)
canvas_pdf.print_figure("test.pdf")

The obvious way is to just manually set the limits to what you want. (e.g. ax.axis([xmin, xmax, ymin, ymax]))
If you don't want to bother with finding out the limits manually, you have a couple of options...
As several people (tillsten, Yann, and Vorticity) have mentioned, if you can plot the function you want to ignore last, then you can disable autoscaling before plotting it or pass the scaley=False kwarg to plot
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
x1 = np.linspace(-1,1,100)
ax.plot(x1, np.sin(x1))
ax.plot(x1, np.sin(x1 / 2.0))
ax.autoscale(False) #You could skip this line and use scalex=False on
ax.plot(x1, 3 * np.sin(x1)) #the "theoretical" plot. It has to be last either way
fig.savefig('test.pdf')
Note that you can adjust the zorder of the last plot so that it's drawn in the "middle", if you want control over that.
If you don't want to depend on the order, and you do want to just specify a list of lines to autoscale based on, then you could do something like this: (Note: This is a simplified version assuming you're dealing with Line2D objects, rather than matplotlib artists in general.)
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.transforms as mtransforms
def main():
fig, ax = plt.subplots()
x1 = np.linspace(-1,1,100)
line1, = ax.plot(x1, np.sin(x1))
line2, = ax.plot(x1, 3 * np.sin(x1))
line3, = ax.plot(x1, np.sin(x1 / 2.0))
autoscale_based_on(ax, [line1, line3])
plt.show()
def autoscale_based_on(ax, lines):
ax.dataLim = mtransforms.Bbox.unit()
for line in lines:
xy = np.vstack(line.get_data()).T
ax.dataLim.update_from_data_xy(xy, ignore=False)
ax.autoscale_view()
if __name__ == '__main__':
main()

Use the scalex/scaley kw arg:
plot(x1, 3*sin(x1), scaley=False)

LineCollection objects can be ignored by using the autolim=False argument:
from matplotlib.collections import LineCollection
fig, ax = plt.subplots()
x1 = np.linspace(-1,1,100)
# Will update limits
ax.plot(x1, np.sin(x1))
# Will not update limits
col = LineCollection([np.column_stack((x1, 3 * np.sin(x1)))], colors='g')
ax.add_collection(col, autolim=False)
# Will still update limits
ax.plot(x1, np.sin(x1 / 2.0))

This can be done regardless of plotting order by creating another axes to work on.
In this version, we create a twin axes and disable the autoscaling on that twin axes. In this way, the plot is scaled based on anything plotted in the original axes, but is not scaled by anything put into the twin axes.
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
x1 = np.linspace(-1,1,100)
twin_ax = ax.twinx() # Create a twin axes.
twin_ax.autoscale(False) # Turn off autoscaling on the twin axes.
twin_ax.set_yticks([]) # Remove the extra tick numbers from the twin axis.
ax.plot(x1, np.sin(x1))
twin_ax.plot(x1, 3 * np.sin(x1), c='green') # Plotting the thing we don't want to scale on in the twin axes.
ax.plot(x1, np.sin(x1 / 2.0))
twin_ax.set_ylim(ax.get_ylim()) # Make sure the y limits of the twin matches the autoscaled of the original.
fig.savefig('test.pdf')
Note, the above only prevents the un-twined axis from auto scaling (y in the above case). To get it to work for both x and y, we can do the twinning process for both x and y (or create the new axes from scratch):
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
x1 = np.linspace(-1,1,100)
x2 = np.linspace(-2,2,100) # Would extend the x limits if auto scaled
twin_ax = ax.twinx().twiny() # Create a twin axes.
twin_ax.autoscale(False) # Turn off autoscaling on the twin axes.
twin_ax.set_yticks([]) # Remove the extra tick numbers from the twin axis.
twin_ax.set_xticks([]) # Remove the extra tick numbers from the twin axis.
ax.plot(x1, np.sin(x1))
twin_ax.plot(x2, 3 * np.sin(x2), c='green') # Plotting the thing we don't want to scale on in the twin axes.
ax.plot(x1, np.sin(x1 / 2.0))
twin_ax.set_ylim(ax.get_ylim()) # Make sure the y limits of the twin matches the autoscaled of the original.
twin_ax.set_xlim(ax.get_xlim()) # Make sure the x limits of the twin matches the autoscaled of the original.
fig.savefig('test.png')

As a generalisation of jam's answer, a collection object can be obtained from any of matplotlib's plotting functions and then re-added with autolim=False. For example,
fig, ax = plt.subplots()
x1 = np.linspace(-1,1,100)
# Get hold of collection
collection = ax.plot(x1, np.sin(x1))
# Remove collection from the plot
collection.remove()
# Rescale
ax.relim()
# Add the collection without autoscaling
ax.add_collection(collection, autolim=False)

Related

Is there any way to get extend axhspan to the outer edges of the plot? [duplicate]

Using Matplotlib I am trying to shade the region y > N, with N some number.
The problem is that I am not able to have the shaded region going to the end of the frame.
Here is a simple example.
import matplotlib.pyplot as plt
fig, ax = plt.subplots(1,1)
x = [i for i in range(0,100,10)]
ax.plot(x,x,'-o')
N=110
ylim = max(ax.get_ylim())
ax.axhspan(N,ylim,alpha=.5)
plt.show()
The result is this:
How to have the shaded region not stopping, but arriving up to the end of the frame?
ax.autoscale() can come in handy here. Default, matplotlib automatically adapts the limits of the axes every time something is added. Normally also some padding is added to leave some free space above and below (and left and right).
Calling ax.autoscale(enable=True, axis='y', tight=True) changes this behavior for the y-axis, forcing "tight" limits, so without padding.
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
x = [i for i in range(0, 100, 10)]
ax.plot(x, x, '-o')
N = 110
ylim1 = max(ax.get_ylim())
ax.autoscale(enable=True, axis='y', tight=True)
ax.axhspan(N, ylim1, alpha=.5)
plt.show()
Alternatively, you could collect the limits before calling axhspan and setting them manually afterwards:
ax.plot(x, x, '-o')
N = 110
ylim0, ylim1 = ax.get_ylim()
ax.axhspan(N, ylim1, alpha=.5)
ax.set_ylim(ylim0, max(N, ylim1))

Share scaling of differntly sized subplots' axes (not sharing axes)

With matplotlib, I want to plot two graphs with the same x-axis scale, but I want to show different sized sections. How can I accomplish that?
So far I can plot differently sized subplots with GridSpec or same sized ones who share the x-axis. When I try both at once, the smaller subplot has the same axis but smaller scaled, while I want the same scaling and a different axis, so sharing the axis might be a wrong idea.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
x=np.linspace(0,10,100)
y=np.sin(x)
x2=np.linspace(0,5,60)
y2=np.cos(x2)
fig=plt.figure()
gs=GridSpec(2,3)
ax1 = fig.add_subplot(gs[0, :])
ax1.plot(x,y)
ax2 = fig.add_subplot(gs[1,:-1])
#using sharex=ax1 here decreases the scaling of ax2 too much
ax2.plot(x2,y2)
plt.show()
I want the x.axes to have the same scaling, i.e. the same x values are always exactly on top of each other, this should give you an idea. The smaller plot's frame could be expanded or fit the plot, that doesn't matter. As it is now, the scales don't match.
Thanks in advance.
This is still a bit rough. I'm sure there's a slightly more elegant way to do this, but you can create a custom transformation (see Transformations Tutorial) between the Axes coordinates of ax2 and the data coordinates of ax1. In other word, your calculating what is the data-value (according to ax1) at the position corresponding to the left and right edges of ax2, and then adjust the xlim of ax2 accordingly.
Here is a demonstration showing that it works even if the second subplot is not aligned in any particular way with the first.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
x=np.linspace(0,25,100)
y=np.sin(x)
x2=np.linspace(10,30,60)
y2=np.cos(x2)
fig=plt.figure()
gs=GridSpec(2,6)
ax1 = fig.add_subplot(gs[0, :])
ax1.plot(x,y)
ax2 = fig.add_subplot(gs[1,3:-1])
ax2.plot(x2,y2)
# here is where the magic happens
trans = ax2.transAxes + ax1.transData.inverted()
((xmin,_),(xmax,_)) = trans.transform([[0,1],[1,1]])
ax2.set_xlim(xmin,xmax)
# for demonstration, show that the vertical lines end up aligned
for ax in [ax1,ax2]:
for pos in [15,20]:
ax.axvline(pos)
plt.show()
EDIT: One possible refinement would be to do the transform in the xlim_changed event callback. That way, the axes stay in sync even when zooming/panning in the first axes.
There is also a slight issue with tight_layout() as you noted, but that is easily fixed by calling the callback function directly.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
def on_xlim_changed(event):
# here is where the magic happens
trans = ax2.transAxes + ax1.transData.inverted()
((xmin, _), (xmax, _)) = trans.transform([[0, 1], [1, 1]])
ax2.set_xlim(xmin, xmax)
x = np.linspace(0, 25, 100)
y = np.sin(x)
x2 = np.linspace(10, 30, 60)
y2 = np.cos(x2)
fig = plt.figure()
gs = GridSpec(2, 6)
ax1 = fig.add_subplot(gs[0, :])
ax1.plot(x, y)
ax2 = fig.add_subplot(gs[1, 3:-1])
ax2.plot(x2, y2)
# for demonstration, show that the vertical lines end up aligned
for ax in [ax1, ax2]:
for pos in [15, 20]:
ax.axvline(pos)
# tight_layout() messes up the axes xlim
# but can be fixed by calling on_xlim_changed()
fig.tight_layout()
on_xlim_changed(None)
ax1.callbacks.connect('xlim_changed', on_xlim_changed)
plt.show()
I suggest setting limits of the second axis based on the limits of ax1.
Try this!
ax2 = fig.add_subplot(gs[1,:-1])
ax2.plot(x2,y2)
lb, ub = ax1.get_xlim()
# Default margin is 0.05, which would be used for auto-scaling, hence reduce that here
# Set lower bound and upper bound based on the grid size, which you choose for second plot
ax2.set_xlim(lb, ub *(2/3) -0.5)
plt.show()

Prevent matplotlib to sort axis

Consider the following test case:
import numpy as np
import matplotlib.pyplot as plt
x1 = np.arange(10)
x2 = np.hstack((np.linspace(0.12312, 3.94, 5), np.linspace(-10.0121, -9.333411, 5)))
y = 2.0 * x1
fig = plt.figure(1)
ax = fig.gca()
ax.plot(x1, y)
fig = plt.figure(2)
ax = fig.gca()
ax.plot(x2, y)
fig = plt.figure(3)
ax = fig.gca()
ax.plot(x1, y)
ax.set_xticklabels(x2[ax.get_xticks().astype(int)])
plt.show()
I'd like to plot figure(2) with x2 original ordering. But when I use it with pyplot.plot it's automatically sorted and it messes up my axis.
I can manually set the ticklabels like in figure(3) but I don't get automatic axis scaling, formatting and so on.
I could probably hack something myself with formatters, locators etc, but first I'd like to ask: is there a proper way to achieve what I'd like?
I don't think it's possible with this kind of plot. You can manage it somehow, like you did it with figure(3) but it's totally against intuition or good practice. Normally if you are looking on a 2D plot you assume that each axis is a continuous scale (ascending or descending). It can be even logarithmic or other scale.

matplotlib: adding second axes() with transparent background?

Define data
x = np.linspace(0,2*np.pi,100)
y = 2*np.sin(x)
Plot
fig = plt.figure()
ax = plt.axes()
fig.add_subplot(ax)
ax.plot(x,y)
Add second axis
newax = plt.axes(axisbg='none')
Gives me ValueError: Unknown element o, even though it does the same thing as what I am about to describe. I can also see that this works (no error) to do the same thing:
newax = plt.axes()
fig.add_subplot(newax)
newax.set_axis_bgcolor('none')
However, it turns the background color of the original figure "gray" (or whatever the figure background is)? I don't understand, as I thought this would make newax transparent except for the axes and box around the figure. Even if I switch the order, same thing:
plt.close('all')
fig = plt.figure()
newax = plt.axes()
fig.add_subplot(newax)
newax.set_axis_bgcolor('none')
ax = plt.axes()
fig.add_subplot(ax)
ax.plot(x,y)
This is surprising because I thought the background of one would be overlaid on the other, but in either case it is the newax background that appears to be visible (or at least this is the color I see).
What is going on here?
You're not actually adding a new axes.
Matplotlib is detecting that there's already a plot in that position and returning it instead of a new axes object.
(Check it for yourself. ax and newax will be the same object.)
There's probably not a reason why you'd want to, but here's how you'd do it.
(Also, don't call newax = plt.axes() and then call fig.add_subplot(newax) You're doing the same thing twice.)
Edit: With newer (>=1.2, I think?) versions of matplotlib, you can accomplish the same thing as the example below by using the label kwarg to fig.add_subplot. E.g. newax = fig.add_subplot(111, label='some unique string')
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
# If you just call `plt.axes()` or equivalently `fig.add_subplot()` matplotlib
# will just return `ax` again. It _won't_ create a new axis unless we
# call fig.add_axes() or reset fig._seen
newax = fig.add_axes(ax.get_position(), frameon=False)
ax.plot(range(10), 'r-')
newax.plot(range(50), 'g-')
newax.axis('equal')
plt.show()
Of course, this looks awful, but it's what you're asking for...
I'm guessing from your earlier questions that you just want to add a second x-axis? If so, this is a completely different thing.
If you want the y-axes linked, then do something like this (somewhat verbose...):
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
newax = ax.twiny()
# Make some room at the bottom
fig.subplots_adjust(bottom=0.20)
# I'm guessing you want them both on the bottom...
newax.set_frame_on(True)
newax.patch.set_visible(False)
newax.xaxis.set_ticks_position('bottom')
newax.xaxis.set_label_position('bottom')
newax.spines['bottom'].set_position(('outward', 40))
ax.plot(range(10), 'r-')
newax.plot(range(21), 'g-')
ax.set_xlabel('Red Thing')
newax.set_xlabel('Green Thing')
plt.show()
If you want to have a hidden, unlinked y-axis, and an entirely new x-axis, then you'd do something like this:
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.2)
newax = fig.add_axes(ax.get_position())
newax.patch.set_visible(False)
newax.yaxis.set_visible(False)
for spinename, spine in newax.spines.iteritems():
if spinename != 'bottom':
spine.set_visible(False)
newax.spines['bottom'].set_position(('outward', 25))
ax.plot(range(10), 'r-')
x = np.linspace(0, 6*np.pi)
newax.plot(x, 0.001 * np.cos(x), 'g-')
plt.show()
Note that the y-axis values for anything plotted on newax are never shown.
If you wanted, you could even take this one step further, and have independent x and y axes (I'm not quite sure what the point of it would be, but it looks neat...):
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.2, right=0.85)
newax = fig.add_axes(ax.get_position())
newax.patch.set_visible(False)
newax.yaxis.set_label_position('right')
newax.yaxis.set_ticks_position('right')
newax.spines['bottom'].set_position(('outward', 35))
ax.plot(range(10), 'r-')
ax.set_xlabel('Red X-axis', color='red')
ax.set_ylabel('Red Y-axis', color='red')
x = np.linspace(0, 6*np.pi)
newax.plot(x, 0.001 * np.cos(x), 'g-')
newax.set_xlabel('Green X-axis', color='green')
newax.set_ylabel('Green Y-axis', color='green')
plt.show()
You can also just add an extra spine at the bottom of the plot. Sometimes this is easier, especially if you don't want ticks or numerical things along it. Not to plug one of my own answers too much, but there's an example of that here: How do I plot multiple X or Y axes in matplotlib?
As one last thing, be sure to look at the parasite axes examples if you want to have the different x and y axes linked through a specific transformation.

Remove colorbar from figure

This should be easy but I'm having a hard time with it. Basically, I have a subplot in matplotlib that I'm drawing a hexbin plot in every time a function is called, but every time I call the function I get a new colorbar, so what I'd really like to do is update the colorbar. Unfortunately, this doesn't seem to work since the object the colorbar is attached to is being recreated by subplot.hexbin.
def foo(self):
self.subplot.clear()
hb = self.subplot.hexbin(...)
if self.cb:
self.cb.update_bruteforce() # Doesn't work (hb is new)
else:
self.cb = self.figure.colorbar(hb)
I'm now in this annoying place where I'm trying to delete the colorbar axes altogether and simply recreate it. Unfortunately, when I delete the colorbar axes, the subplot axes don't reclaim the space, and calling self.subplot.reset_position() isn't doing what I thought it would.
def foo(self):
self.subplot.clear()
hb = self.subplot.hexbin(...)
if self.cb:
self.figure.delaxes(self.figure.axes[1])
del self.cb
# TODO: resize self.subplot so it fills the
# whole figure before adding the new colorbar
self.cb = self.figure.colorbar(hb)
I think the problem is that with del you cancel the variable, but not the referenced object colorbar.
If you want the colorbar to be removed from plot and disappear, you have to use the method remove of the colorbar instance and to do this you need to have the colorbar in a variable, for which you have two options:
holding the colorbar in a value at the moment of creation, as shown in other answers e.g. cb=plt.colorbar()
retrieve an existing colorbar, that you can do following (and upvoting :)) what I wrote here: How to retrieve colorbar instance from figure in matplotlib
then:
cb.remove() plt.draw() #update plot
Full code and result:
from matplotlib import pyplot as plt
import numpy as np
plt.ion()
plt.imshow(np.random.random(15).reshape((5,3)))
cb = plt.colorbar()
plt.savefig('test01.png')
cb.remove()
plt.savefig('test02.png')
Alright, here's my solution. Not terribly elegant, but not a terrible hack either.
def foo(self):
self.subplot.clear()
hb = self.subplot.hexbin(...)
if self.cb:
self.figure.delaxes(self.figure.axes[1])
self.figure.subplots_adjust(right=0.90) #default right padding
self.cb = self.figure.colorbar(hb)
This works for my needs since I only ever have a single subplot. People who run into the same problem when using multiple subplots or when drawing the colorbar in a different position will need to tweak.
I managed to solve the same issue using fig.clear() and display.clear_output()
import matplotlib.pyplot as plt
import IPython.display as display
import matplotlib.tri as tri
from pylab import *
%matplotlib inline
def plot_res(fig):
ax=fig.add_axes([0,0,1,1])
ax.set_xlabel("x")
ax.set_ylabel('y')
plotted=ax.imshow(rand(250, 250))
ax.set_title("title")
cbar=fig.colorbar(mappable=plotted)
display.clear_output(wait=True)
display.display(plt.gcf())
fig.clear()
fig=plt.figure()
N=20
for j in range(N):
plot_res(fig)
If you have a matplotlib figure object you just need to do fig.delaxes(fig.axes[1])
For example:
Plot with colorbar
import matplotlib.pyplot as plt
# setup some generic data
N = 37
x, y = np.mgrid[:N, :N]
Z = (np.cos(x*0.2) + np.sin(y*0.3))
# mask out the negative and positive values, respectively
Zpos = np.ma.masked_less(Z, 0)
Zneg = np.ma.masked_greater(Z, 0)
fig, ax1 = plt.subplots(figsize=(13, 3), ncols=1)
# plot just the positive data and save the
# color "mappable" object returned by ax1.imshow
pos = ax1.imshow(Zpos, cmap='Blues', interpolation='none')
# add the colorbar using the figure's method,
# telling which mappable we're talking about and
# which axes object it should be near
fig.colorbar(pos, ax=ax1)
Remove colorbar
import matplotlib.pyplot as plt
# setup some generic data
N = 37
x, y = np.mgrid[:N, :N]
Z = (np.cos(x*0.2) + np.sin(y*0.3))
# mask out the negative and positive values, respectively
Zpos = np.ma.masked_less(Z, 0)
Zneg = np.ma.masked_greater(Z, 0)
fig, ax1 = plt.subplots(figsize=(13, 3), ncols=1)
# plot just the positive data and save the
# color "mappable" object returned by ax1.imshow
pos = ax1.imshow(Zpos, cmap='Blues', interpolation='none')
# add the colorbar using the figure's method,
# telling which mappable we're talking about and
# which axes object it should be near
fig.colorbar(pos, ax=ax1)
fig.delaxes(fig.axes[1])
I had a similar problem and played around a little bit. I came up with two solutions which might be slightly more elegant:
Clear the whole figure and add the subplot (+colorbar if wanted) again.
If there's always a colorbar, you can simply update the axes with autoscale which also updates the colorbar.
I've tried this with imshow, but I guess it works similar for other plotting methods.
from pylab import *
close('all') #close all figures in memory
#1. Figures for fig.clf method
fig1 = figure()
fig2 = figure()
cbar1=None
cbar2=None
data = rand(250, 250)
def makefig(fig,cbar):
fig.clf()
ax = fig.add_subplot(111)
im = ax.imshow(data)
if cbar:
cbar=None
else:
cbar = fig.colorbar(im)
return cbar
#2. Update method
fig_update = figure()
cbar3=None
data_update = rand(250, 250)
img=None
def makefig_update(fig,im,cbar,data):
if im:
data*=2 #change data, so there is change in output (look at colorbar)
#im.set_data(data) #use this if you use new array
im.autoscale()
#cbar.update_normal(im) #cbar is updated automatically
else:
ax = fig.add_subplot(111)
im = ax.imshow(data)
cbar=fig.colorbar(im)
return im,cbar,data
#Execute functions a few times
for i in range(3):
print i
cbar1=makefig(fig1,cbar1)
cbar2=makefig(fig2,cbar2)
img,cbar3,data_update=makefig_update(fig_update,img,cbar3,data_update)
cbar2=makefig(fig2,cbar2)
fig1.show()
fig2.show()
fig_update.show()
I needed to remove colorbars because I was plotting a pcolormesh and adding colorbar to a figure in a loop. Each loop would create a new colorbar and after ten loops I would have ten colorbars. That was bad.
To remove colorbars, I name the pcolormesh and colorbar a variable, then at the end of my loop I remove each. It is important to remove the colorbar before removing the pcolormesh.
Psudo Code:
for i in range(0,10):
p = plt.pcolormesh(datastuff[i])
cb = plt.colorbar(p)
plt.savefig('name_'+i)
cb.remove()
p.remove()
Again, it was necessary to remove the colorbar before the pcolormesh
I am using matplotlib 1.4.0. This is how I solve this problem:
import matplotlib
import numpy as np
import matplotlib.cm as cm
import matplotlib.mlab as mlab
import matplotlib.pyplot as plt
# A contour plot example:
delta = 0.025
x = np.arange(-3.0, 3.0, delta)
y = np.arange(-2.0, 2.0, delta)
X, Y = np.meshgrid(x, y)
Z1 = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)
Z2 = mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1)
Z = 10.0 * (Z2 - Z1)
#
# first drawing
fig = plt.figure()
ax = fig.add_subplot(111) # drawing axes
c = ax.contourf(Z) # contour fill c
cb = fig.colorbar(c) # colorbar for contour c
# clear first drawimg
ax.clear() # clear drawing axes
cb.ax.clear() # clear colorbar axes
# replace with new drawing
# 1. drawing new contour at drawing axes
c_new = ax.contour(Z)
# 2. create new colorbar for new contour at colorbar axes
cb_new = ax.get_figure().colorbar(c_new, cax=cb.ax)
plt.show()
Above code draws a contour fill plot with colorbar, clear it and draw a new contour plot with new colorbar at the same figure.
By using
cb.ax
i am able to identify the colorbar axes and clear the old colorbar.
And specifying cax=cb.ax simply draws the new colorbar in the old colorbar axes.
Don't want to take anything away from the author of this blog post (Joseph Long) but this is clearly the best solution I've found so far. It includes pieces of code, great explanations and many examples.
To summarize, from any output of an axis ax of the command: plot, image, scatter, collection, etc. such as:
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(5,5), dpi=300)
ax = fig.add_subplot(1, 1, 1)
data = ax.plot(x,y)
# or
data = ax.scatter(x, y, z)
# or
data = ax.imshow(z)
# or
data = matplotlib.collection(patches)
ax.add_collection(data)
You create a color bar axis using the make_axes_locatable and the original axis of the plot.
from mpl_toolkits.axes_grid1 import make_axes_locatable
# the magical part
divider = make_axes_locatable(ax)
caxis = divider.append_axes("right", size="5%", pad=0.05)
fig.colorbar(data, cax=caxis)
plt.show()
The created colorbar will have the same size as the figure or subplot and you can modify it's width, location, padding when using the divider.append_axes command.
My solution consists in having an Axes whose only purpose is to hold the colorbar, and clear it entirely when needed.
For example, define those once:
figure, ax = plt.subplots() # All the plotting is done on `ax`.
cax = ax.inset_axes([1.03, 0, 0.1, 1], transform=ax.transAxes) # Colorbar is held by `cax`.
Then do this as many times as needed:
cax.clear()
colorbar = figure.colorbar(mpl.cm.ScalarMappable(norm=norm, cmap=cmap),
ax=ax,
cax=cax,
**kwargs)
"on_mappable_changed" worked in my case. However, according to docs, the method "Typically ... should not be called manually."
if self.cb:
self.cb.on_mappable_changed(hb)
else:
self.cb = self.fig.colorbar(hb)

Categories

Resources