mankoff
mankoff

Reputation: 2301

Matplotlib axis position and colorbar alignment

I'm trying to align multiple colorbars with subplots generated with either gridspec or fig.add_subplots. I'd like to add the colorbar with fig.add_axes in matplotlib (v2.02), because it allows detailed alignment control. However, I need to get the figure position in order to do the alignment. The ax.get_position() function does not appear to return the correct coordinates:

Here is an MWE:

import matplotlib.gridspec as gridspec
import scipy.misc

fig = plt.figure(1)
fig.set_tight_layout(True)
gs = gridspec.GridSpec(2,2)
ax = plt.subplot(gs[0,0])
lena = scipy.misc.face()
im = ax.imshow(lena)
axpos = ax.get_position() # bbox# get_position().get_points()
cbar_axis = fig.add_axes([axpos.x0, axpos.y0-0.1, axpos.width, 0.05])
colorbar = fig.colorbar(im, cax=cbar_axis, orientation="horizontal")

The colorbar does not align: image with misaligned colorbar

If I print axpos after the code runs, and ax.get_position(), the two are not the same. This suggests that ax.get_position() is using stale information while the code is executing. I've tried adding plt.show() and plt.draw() and it does not fix this.

How can I get the axis position?

EDIT:

I've modified the solution posted below (https://stackoverflow.com/a/44731797/238882) to better show what I am trying to do and why make_axes_locatable does not appear (yet) to work for me. I want two colorbars under one column, and one under another column. Doing this with make_axes_locatable and the following code produces the figure below. I would like the figures to all remain equal sizes and aligned in a grid.

import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import scipy.misc
from mpl_toolkits.axes_grid1 import make_axes_locatable

plt.close(1)
fig, axes = plt.subplots(2,2)
fig.set_tight_layout(True)

lena = scipy.misc.face()

for i, ax in enumerate(axes.flatten()):

    im = ax.imshow(lena)
    divider = make_axes_locatable(ax)
    if i == 2:
        ax_cb = divider.new_vertical(size="10%", pad=0.3, pack_start=True)
        fig.add_axes(ax_cb)
        fig.colorbar(im, cax=ax_cb,  orientation="horizontal")
        ax_cb2 = divider.new_vertical(size="10%", pad=0.3, pack_start=True)
        fig.add_axes(ax_cb2)
        fig.colorbar(im, cax=ax_cb2,  orientation="horizontal")
    if i == 3:
        ax_cb = divider.new_vertical(size="10%", pad=0.3, pack_start=True)
        fig.add_axes(ax_cb)
        fig.colorbar(im, cax=ax_cb,  orientation="horizontal")

plt.show()

enter image description here

Upvotes: 2

Views: 2314

Answers (1)

ImportanceOfBeingErnest
ImportanceOfBeingErnest

Reputation: 339230

Usually you don't want to use the absolute positions of the axes, because this might (as you found out) change depending on other artists drawn or the figure placed in a canvas.

If the aim is to position the colorbar below the axes, a useful way is to use mpl_toolkits.axes_grid1.make_axes_locatable as seen in this official example.

import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import scipy.misc
from mpl_toolkits.axes_grid1 import make_axes_locatable

fig = plt.figure(1)
fig.set_tight_layout(True)
gs = gridspec.GridSpec(2,2)
ax = plt.subplot(gs[0,0])
lena = scipy.misc.face()
im = ax.imshow(lena)

divider = make_axes_locatable(ax)

ax_cb = divider.new_vertical(size="5%", pad=0.3, pack_start=True)
fig.add_axes(ax_cb)

fig.colorbar(im, cax=ax_cb,  orientation="horizontal")

plt.show()

enter image description here

Note that this is absolutely compatible with tight_layout while setting an axes with add_axes is not.

You may create further errorbars by dividing the axes further using a second call to divider.new_vertical.

import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import scipy.misc
from mpl_toolkits.axes_grid1 import make_axes_locatable

fig, axes = plt.subplots(2,2)
fig.set_tight_layout(True)

lena = scipy.misc.face()

for i, ax in enumerate(axes.flatten()):

    im = ax.imshow(lena)
    divider = make_axes_locatable(ax)
    ax_cb = divider.new_vertical(size="10%", pad=0.3, pack_start=True)
    ax_cb2 = divider.new_vertical(size="10%", pad=0.3, pack_start=True)
    fig.add_axes(ax_cb)
    fig.colorbar(im, cax=ax_cb,  orientation="horizontal")
    if i % 2 - 1:
        fig.add_axes(ax_cb2)
        fig.colorbar(im, cax=ax_cb2,  orientation="horizontal")

plt.show()

enter image description here

Edit for the edited part of the question:

import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import scipy.misc
from mpl_toolkits.axes_grid1 import make_axes_locatable


fig, axes = plt.subplots(2,2)
fig.set_tight_layout(True)

lena = scipy.misc.face()

for i, ax in enumerate(axes.flatten()):

    im = ax.imshow(lena)
    divider = make_axes_locatable(ax)
    ax_cb = divider.new_vertical(size="8%", pad=0.3, pack_start=True)
    ax_cb2 = divider.new_vertical(size="8%", pad=0.02, pack_start=True)
    if i == 2:
        fig.add_axes(ax_cb)
        fig.colorbar(im, cax=ax_cb,  orientation="horizontal")
        fig.add_axes(ax_cb2)
        fig.colorbar(im, cax=ax_cb2,  orientation="horizontal")
        ax_cb.tick_params(bottom=False,labelbottom=False)
    if i == 3:
        fig.add_axes(ax_cb)
        fig.colorbar(im, cax=ax_cb,  orientation="horizontal")

plt.show()

enter image description here

Upvotes: 2

Related Questions