Search code examples
pythonmatplotlibhealpy

Adding a single colorbar to multiple healpy subplots


I would like to add a custom plt.colorbar to a figure containing multiple healpy plots. I have found many posts on how to do this for the usual case of multiple axes objects, but the healpy makes it difficult.

I have the following MWE so far:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
import healpy as hp

rows, cols = 8, 8
nplots = rows * cols
npix = 48
data = np.random.uniform(size=(nplots, npix))
fig = plt.figure()
for i in range(len(data)):
    hp.mollview(data[i, :], title='', cbar=False, fig=fig,
                sub=(rows, cols, i+1), margins=(0, 0, 0, 0),
                min=data.min(), max=data.max())

fig, ax = plt.gcf(), plt.gca()
image = ax.get_images()[0]
norm =  mpl.colors.Normalize(vmin=data.min(), vmax=data.max())

from mpl_toolkits.axes_grid1 import make_axes_locatable
divider = make_axes_locatable(ax)
cax = divider.new_vertical(size="5%", pad=0.7, pack_start=True)
fig.add_axes(cax)
fig.colorbar(image, cax=cax, norm=norm, orientation='horizontal',
             label='colorbar')
plt.show()

See the erroneous plot here

As shown in the linked image I end up with a colorbar attached to the last ax rather than the entire fig. I would like a simple colorbar on the bottom (or right side) of the fig, with a range specified through Normalize as above. Again, it is the fact that I am using healpy to produce the figure that rules out the usual solutions, at least to my knowledge.


Solution

  • I don't have healpy installed, but probably this library just creates its own axes. The code below emulates such a situation. You can get the axes from fig.axes. As in this tutorial, a default colorbar can be placed just by giving a list of all the 'axes' (an ax is more or less matplotlib's name for a subplot): plt.colorbar(im, ax=fig.axes). If the colorbar would be too large, it has a shrink=0.6 parameter.

    from matplotlib import pyplot as plt
    import numpy as np
    
    fig = plt.figure(figsize=(20, 6))
    
    nrows = 4
    ncols = 6
    for i in range(1, nrows + 1):
        for j in range(1, ncols + 1):
            plt.subplot(nrows, ncols, (i - 1) * ncols + j, projection="mollweide")
            arr = np.random.rand(18, 36)
            Lon, Lat = np.meshgrid(np.linspace(-np.pi, np.pi, 36 + 1), np.linspace(-np.pi / 2., np.pi / 2., 18 + 1))
            plt.pcolormesh(Lon, Lat, arr, cmap=plt.cm.hot)
    im = fig.axes[0].collections[0] # or fig.axes[0].get_images()[0] when created as image
    plt.colorbar(im, ax=fig.axes)
    
    plt.show()
    

    example plot

    Note that in your code, fig already points to the current figure, making fig = plt.gcf() unnecessary. ax = plt.gca() indicates the ax that was last active. In the case of the example plot this seems to be the lower right one. So, this helps to find an example image, but not to position the colorbar next to all subplots.

    If you need more control about the colorbar placement, you can also adopt the approach from this post:

    fig.subplots_adjust(right=0.85)
    cbar_ax = fig.add_axes([0.90, 0.15, 0.03, 0.7])
    fig.colorbar(im, cax=cbar_ax)