I would like to achieve two objectives with matplotlib:
Dynamically update a scatter plot
Slowly make the points that were plotted at previous iterations more transparent.
Currently, I am able to achieve the opposite goal using colormaps. That is, I can plot points over time but the recent points look more transparent.
Is it possible to get a 'fading' effect on matplotlib using cmap or other tools? Thanks! My code is below:
def plotter_fader(iterations = 100, stay_open = True):
# Set up plot
fig, ax = plt.subplots()
x_data = []
y_data = []
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111)
t_vals = np.linspace(0,1, iterations)
cmap = (0.09803921568627451, 0.09803921568627451, 0.09803921568627451, .05)
for t in t_vals:
# Get intermediate points
intermediate = (1-t)*A + t*B
new_xvals, new_yvals = ... #Get these through some process
x_vals.extend(new_xvals)
y_vals.extend(new_yvals)
# Put new values in your plot
plt.plot(x_vals, y_vals, '.', color = cmap)
# Recompute plot limits
ax.relim()
# Set title and wait a little bit for 'smoothness'
ax.set_xlabel('X Axis', size = 12)
ax.set_ylabel('Y Axis', size = 12)
ax.set_title('Time: %0.3f' %t)
ax.autoscale_view()
fig.canvas.draw()
time.sleep(0.005)
# Stay open after plotting ends
while stay_open:
pass
Just as usual with a scatter plot you may define an array of values and a colormap that maps those values to colors. You can change this array in each iteration to make older points have a different value.
In the following we take a value of 0 as transparent and a value of 1 as dark blue and create a colormap with those colors.
In each iteration old values are multiplied by a number smaller than one, new values are set to have a value of 1.
Running the animation will hence produce fading points.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation
from matplotlib.colors import LinearSegmentedColormap
fig, ax = plt.subplots()
ax.set_xlabel('X Axis', size = 12)
ax.set_ylabel('Y Axis', size = 12)
ax.axis([0,1,0,1])
x_vals = []
y_vals = []
intensity = []
iterations = 100
t_vals = np.linspace(0,1, iterations)
colors = [[0,0,1,0],[0,0,1,0.5],[0,0.2,0.4,1]]
cmap = LinearSegmentedColormap.from_list("", colors)
scatter = ax.scatter(x_vals,y_vals, c=[], cmap=cmap, vmin=0,vmax=1)
def get_new_vals():
n = np.random.randint(1,5)
x = np.random.rand(n)
y = np.random.rand(n)
return list(x), list(y)
def update(t):
global x_vals, y_vals, intensity
# Get intermediate points
new_xvals, new_yvals = get_new_vals()
x_vals.extend(new_xvals)
y_vals.extend(new_yvals)
# Put new values in your plot
scatter.set_offsets(np.c_[x_vals,y_vals])
#calculate new color values
intensity = np.concatenate((np.array(intensity)*0.96, np.ones(len(new_xvals))))
scatter.set_array(intensity)
# Set title
ax.set_title('Time: %0.3f' %t)
ani = matplotlib.animation.FuncAnimation(fig, update, frames=t_vals,interval=50)
plt.show()
Is this what you are looking for ?
from matplotlib import pyplot as plt
# Creates new axis.
plt.axis([0, 10, 0, 1])
# Allows interactive plotting
plt.ion()
# Transparency
alpha = 1
# Plotting first point outside of loop because more convenient for example
point = plt.scatter(0.5, 0.5, alpha=alpha)
for i in range(10):
# As the loop goes on, increase transparency, remove the current point,
# and plots another one, more transparent.
alpha -= 0.1
point.remove()
point = plt.scatter(5, .5, alpha=alpha, color='r')
plt.pause(0.05)
while True:
plt.pause(0.05)
Related
I have two datasets (corresponding with the time-positional data of hydrogen atoms and time-positional data of alumina atoms) in the same system.
I want to plot the density of each element by overlaying two hist2d plots using matplotlib.
I am currently doing this by setting an alpha value on the second hist2d:
fig, ax = plt.subplots(figsize=(4, 4))
v = ax.hist2d(x=alx, y=aly,
bins=50, cmap='Reds')
h = ax.hist2d(x=hx, y=hy,
bins=50, cmap='Blues',
alpha=0.7)
ax.set_title('Adsorption over time, {} K'.format(temp))
ax.set_xlabel('picoseconds')
ax.set_ylabel('z-axis')
fig.colorbar(h[3], ax=ax)
fig.savefig(savename, dpi=300)
I do get the plot that I want, however the colors seem washed out due to the alpha value.
Is there a more correct way to do generate such plots?
One way to achieve this would be a to add fading alphas towards lower levels to the existing color maps:
import numpy as np
import matplotlib.pylab as pl
from matplotlib import pyplot as plt
from matplotlib.colors import ListedColormap
# modify existing Reds colormap with a linearly fading alpha
red = pl.cm.Reds # original colormap
fading_red = red(np.arange(red.N)) # extract colors
fading_red[:, -1] = np.linspace(0, 1, red.N) # modify alpha
fading_red = ListedColormap(fading_red) # convert to colormap
# data generation
random_1 = np.random.randn(10000)+1
random_2 = np.random.randn(10000)+1
random_3 = np.random.randn(10000)
random_4 = np.random.randn(10000)
# plot
fig, ax = plt.subplots(1,1)
plt.hist2d(x=random_3, y=random_4, bins=100, cmap="Blues")
plt.hist2d(x=random_1, y=random_2, bins=50, cmap=fading_red)
plt.show()
I need to plot a collection of patches using a custom colormap. I have gotten this working fine, however I can't use alpha properly with my custom colormap. As you can see in the images and attached code, the alpha is applied to the patches, but the colorbar still shows with alpha=1 making the map "wrong". To check, I tested the with jet, and the colorbar reflects the proper alpha. I am assuming something is missing in the way I defined my colorbar, but the documentation has not been helpful in figuring out what...
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
x = np.random.rand(50,1)*500; y = np.random.rand(50,1)*500;
radius = np.random.rand(50,1)*50
patches = []
for i in range(len(radius)):
circle = matplotlib.patches.Circle((x[i], y[i]), radius[i])
patches.append(circle)
fig, ax = plt.subplots()
colors = 100*np.random.rand(len(patches))
p = matplotlib.collections.PatchCollection(patches)
p.set(array = colors, cmap = 'Spectral', alpha=0.5)
ax.add_collection(p)
plt.colorbar(p, alpha=0.5)
plt.xlim(0,500);plt.ylim(0,500);
plt.gca().set_aspect(1)
plt.show()
############
r = np.hstack((np.zeros(425),np.linspace(0,255,430), np.linspace(254,0,425)))
g = np.hstack((np.linspace(0,255,430), np.linspace(254,0,425), np.zeros(425)))
b = np.hstack((np.linspace(255,0,430), np.zeros(425),np.linspace(0,254,425)))
c = np.array([r,g,b]).T
mycm = matplotlib.colors.ListedColormap(c/255.0)
###########
x = np.random.rand(50,1)*500; y = np.random.rand(50,1)*500;
radius = np.random.rand(50,1)*50
patches = []
for i in range(len(radius)):
circle = matplotlib.patches.Circle((x[i], y[i]), radius[i])
patches.append(circle)
fig, ax = plt.subplots()
colors = 100*np.random.rand(len(patches))
p = matplotlib.collections.PatchCollection(patches)
p.set(array = colors, cmap = mycm, alpha=0.5)
ax.add_collection(p)
plt.colorbar(p, alpha=0.5)
plt.xlim(0,500);plt.ylim(0,500);
plt.gca().set_aspect(1)
plt.show()
As can be seen in the first case picture, matplotlib adds small lines between the colors in the colorbar. Those lines come from the pcolormesh that is used to produce the colorbar. I have no idea why they are there, nor would I know how to get rid of them, but those lines are the reason for the problem of the alpha value not being shown correctly.
As they appear a bit darker then the real color shown, it is clear that if one adds more and more lines to the colorbar, the complete colorbar will be composed of those lines and thus appear darker or less transparent.
This is exactly what is done in the second case, where a ListedColormap with 430+425+425 = 1280 values is used.
The solution would therefore be to reduce the number of colors in the ListedColormap to a value below 255. In the example code below I used 90.
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
############
n= 30
r = np.hstack((np.zeros(n),np.linspace(0,255,n), np.linspace(254,0,n)))
g = np.hstack((np.linspace(0,255,n), np.linspace(254,0,n), np.zeros(n)))
b = np.hstack((np.linspace(255,0,n), np.zeros(n),np.linspace(0,254,n)))
c = np.array([r,g,b]).T
mycm = matplotlib.colors.ListedColormap(c/255.0)
###########
x = np.random.rand(50,1)*500; y = np.random.rand(50,1)*500;
radius = np.random.rand(50,1)*50
patches = []
for i in range(len(radius)):
circle = matplotlib.patches.Circle((x[i], y[i]), radius[i])
patches.append(circle)
fig, ax = plt.subplots()
colors = 100*np.random.rand(len(patches))
p = matplotlib.collections.PatchCollection(patches)
p.set(array = colors, cmap = mycm, alpha=0.5)
ax.add_collection(p)
plt.colorbar(p, alpha=0.5)
plt.xlim(0,500);plt.ylim(0,500);
plt.gca().set_aspect(1)
plt.savefig(__file__+".png")
plt.show()
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:
I have a matrix of data in which the x and y axes are logarithmic. I'm trying to use imshow to display the matrix, but since I want log axes I'm setting the ticks in the imshow axes to [], and then overlaying another set of axes:
import matplotlib.pyplot as plt
import numpy as np
# the x,y max and min are the log values
array = np.zeros((2,2))
array[1,1] = -1
fig = plt.figure()
ax = plt.imshow(
array,
extent = (0,1, 1, 0),
interpolation = 'nearest').get_axes()
ax.invert_yaxis()
# add a colorbar
# cb = plt.colorbar() # <----- THIS CAUSES TROUBLE
# cb.set_label('zbar')
ax.set_aspect(1)
ax.xaxis.set_ticks([])
ax.yaxis.set_ticks([])
position = ax.get_position()
aspect = ax.get_aspect()
# overlay another set of axes
ax_log = fig.add_subplot(111, frameon = False)
ax_log.set_xscale('log')
ax_log.set_yscale('log')
ax_log.axis((10**0, 10**1, 10**0, 10**1)) # old min and max but exponentiated
ax_log.set_position(position)
ax_log.set_aspect(aspect)
plt.savefig('test.png', bbox_inches = 'tight')
plt.close()
without the colorbar this works fine:
but when I uncomment the lines adding a colorbar, I get a weird shift:
It looks like the colorbar somehow shifts the image slightly to the left, but given that I'm calling get_position() after I create the colorbar this seems weird. Am I overlooking an easier way to make this plot? Is there some easy fix?
Searching around a bit, I found a workaround, maybe there's a better one...
The issue seems to be that plt.colorbar() will 'steal' space from the plot it's drawn on. It's still a bit strange, because I'd still expect get_position() to return the proper coordinates. But as a workaround I used GridSpec and the raw Colorbar constructor.
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.gridspec import GridSpec
from matplotlib.colorbar import Colorbar
# the x,y max and min are the log values
array = np.zeros((2,2))
array[1,1] = -1
fig = plt.figure()
gs = GridSpec(10,11) # create a 10 x 11 grid
ax = plt.subplot(gs[:,0:-1]) # make subplot on 10 x 10 part
im = plt.imshow(
array,
extent = (0,1, 1, 0),
interpolation = 'nearest',
axes = ax)
ax.invert_yaxis()
# add a colorbar
cb_ax = plt.subplot(gs[:,-1]) # put the colorbar on the last column
cb = Colorbar(ax = cb_ax, mappable = im ) # use the raw colorbar constructor
cb.set_label('zbar')
ax.set_aspect(1)
ax.xaxis.set_ticks([])
ax.yaxis.set_ticks([])
position = ax.get_position()
aspect = ax.get_aspect()
# overlay another set of axes
ax_log = fig.add_subplot(111, frameon = False) # can't use gridspec?
ax_log.set_xscale('log')
ax_log.set_yscale('log')
ax_log.axis((10**0, 10**1, 10**0, 10**1)) # old min and max but exponentiated
ax_log.set_position(position)
ax_log.set_aspect(aspect)
plt.savefig('test.pdf', bbox_inches = 'tight')
plt.close()
It's also quite strange that I can't use the GridSpec object to initialize the second set of axes (doing so makes the image disappear).
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)