0

The problem is a bit hard to describe, but very easy to show. I create a grid with subplots on it, where the right column is filled by a tall subplot (approximately following this) which I want to use for the colourbar. Creating a new axis of a given size and using it for a colourbar is done in many code samples (see for example here), but it's not working for me.

Here's an example with a plot layout the same as my real plot that reproduces the problem:

import matplotlib.pyplot as plt
import matplotlib.colors as clt
import numpy as np

fig, axes = plt.subplots(3, 2, figsize=(15,8), tight_layout=True,
                         gridspec_kw={'width_ratios': [1, 0.02],
                                      'height_ratios': [2, 1, 1]})

x, y = np.random.rand(500000), np.random.rand(500000)
counts, xedges, yedges, im = axes[0, 0].hist2d(x, y, bins=(149, 336), norm=clt.LogNorm(), cmap='inferno_r')
axes[1, 0].plot(np.random.rand(2184))
axes[2, 0].plot(np.random.rand(2184))

gs = axes[0, 1].get_gridspec()
for ax in axes[:, 1]:
    ax.remove()
axbig = fig.add_subplot(gs[0:, -1])
bar = fig.colorbar(im, ax=axbig)

axes[0, 0].set_ylabel("2D histogram")
axes[1, 0].set_ylabel("unrelated data")
axes[2, 0].set_ylabel("other unrelated")
bar.set_label("colourbar")

(note that I use add_subplot(gs[0:, -1]) to make the tall subplot, but something like add_axes([0.8, 0.1, 0.03, 0.8]) has the same effect)

And the output: subplots

Notice how the colourbar is added as a tiny little new axis, onto the existing axis which I created for it. I would expect it to fill in the existing axis, as in this or this example. What's going wrong? I'm running matplotlib 3.3.1 from inside spyder 5.0.0 with python 3.8.

Cycloneblaze
  • 21
  • 2
  • 8
  • For context, the reason I'm making such a plot is that all the subplots share their x-axis (which is a time series) but are otherwise unrelated data sets. I want them to have exactly the same width so that they horizontally line up visually, so I put the colourbar next to all of them to accomplish that. If I put the colourbar on only the top subplot, it gets [squished](https://i.imgur.com/xU0c40h.png). – Cycloneblaze Apr 22 '21 at 23:20
  • 3
    You need to use `cax=` instead of `ax=` in `fig.colorbar(im, cax=axbig)`. The `ax=` is the subplot where the colorbar should "steal" some space. When `cax=` is given, no "stealing" is needed. – JohanC Apr 22 '21 at 23:55
  • 1
    Yeah but don’t make the colorbar axes. Just pass fig.colorbar(im, ax=axes) and it will put to the right of all three axes. – Jody Klymak Apr 23 '21 at 04:06
  • @JodyKlymak that does [this](https://i.imgur.com/rSIN2Pu.png) – Cycloneblaze Apr 23 '21 at 09:28
  • @JohanC Yep it was as simple as that. I honestly didn't know `ax` and `cax` were different parameters, I thought I was seeing typos in places. Cheers :) – Cycloneblaze Apr 23 '21 at 09:30
  • About @JodyKlymak's suggestion to use `fig.colorbar(im, ax=axes)`: that doesn't seem to work easily together nor with `plt.subplots(..., tight_layout=True)` nor with `fig.tight_layout()`. To make it work with `tight_layout`, you need to move `fig.colorbar()` to the end, and call `fig.tight_layout()` just before `fig.colorbar()`. – JohanC Apr 23 '21 at 10:37
  • ... or simply use constrained_layout. – Jody Klymak Apr 23 '21 at 12:48

1 Answers1

0

Your original problem, that you didn't want one of three axes squished is explicitly taken care of with constrained_layout. https://matplotlib.org/stable/tutorials/intermediate/constrainedlayout_guide.html#suptitle

I think people are for some reason scared off by the warning on the CL guide, but that is really for folks running production code that must be pixel identical each run. For most users CL is a better option than tight_layout.

import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import numpy as np

fig, axs = plt.subplots(3, 1, figsize=(7,4), constrained_layout=True,
                         gridspec_kw={'height_ratios': [2, 1, 1]})

x, y = np.random.rand(500000), np.random.rand(500000)
res = axs[0].hist2d(x, y, bins=(149, 336), 
                        norm=mcolors.LogNorm(), cmap='inferno_r')
axs[1].plot(np.random.rand(2184))
axs[2].plot(np.random.rand(2184))

fig.colorbar(res[3], ax=axs[0])

plt.show()

enter image description here

Jody Klymak
  • 4,979
  • 2
  • 15
  • 31