0

Okay I've been working on this for a few days, and despite many searches here and elsewhere and trying a variety of methods I have not found any solutions that satisfy everything I'm hoping for.

The Goal: One large plot with four smaller plots to the right, and one colorbar at the bottom.

I am open to even radically different solutions. I have tried gridspecs, and subplots, and so on. This is just one of the closest solutions I have and it has been adapted from the matplotlib examples.

Things That are important to me but are still not happening in the current state:

  • Colorbar is as wide as the two outside plots (for some time I have been using variations of bogatron's answer, but I cannot make it work here), and this really has been the biggest struggle. I have seen answers such as philn's which beautifully illustrates how to do one vertically across multiple plots(of the same size), but I cannot find any that show a fitted colorbar for a similar case to this.
  • Very Important: Final saved plot a given 'exact' width (if you have a general solution that's great but not necessary). Despite setting the width, in this case, the result is ~5.8" (which is close enough). I care very little about how tall the figure is.
  • Scientific notation displayed on all colorbar labels
  • Less whitespace would be nice still with the top of the top plots aligned to the top of the main and likewise for the bottom. I have tried various adjustments to wspace with no real success.
  • Finally, it would be nice to have one answer that works regardless of if I have the x-ticks on or off.

I have tried to use Inkscape to illustrate how the original plot from this code appears in red, and in green a more ideal output.

#%% Example for Stack
# Set page width
# This may be found using \usepackage{layouts} and then in the body
# textwidth = \printinunitsof{in}\prntlen{\textwidth}
textwidth = 5.90666

# Set Plot Color
color = 'cividis'

data = np.random.rand(100, 100)*1e7
AoI = [[25, 75], [25, 75]]

fig = plt.figure(constrained_layout=True)
fig.set_figwidth(textwidth)
 
gs = fig.add_gridspec(3, 4)
ax1 = fig.add_subplot(gs[0:2, 0:2])
ax1.set(title = 'No Polarization', yticks = [], #xticks = [],
       xlim = AoI[0], ylim = AoI[1],
       )
ax1.imshow(data, cmap = color)

ax2 = fig.add_subplot(gs[0, 2])
ax2.set(title = '0', yticks = [], #xticks = [],
       xlim = AoI[0], ylim = AoI[1],
       )
ax2.imshow(data, cmap = color)
ax3 = fig.add_subplot(gs[0, 3])
ax3.set(title = '45', yticks = [], #xticks = [],
       xlim = AoI[0], ylim = AoI[1],
       )
ax3.imshow(data, cmap = color)
ax4 = fig.add_subplot(gs[1, 2])
ax4.set(title = '90', yticks = [], #xticks = [],
       xlim = AoI[0], ylim = AoI[1],
       )
ax4.imshow(data, cmap = color)
ax5 = fig.add_subplot(gs[1, 3])
ax5.set(title = '135', yticks = [], #xticks = [],
       xlim = AoI[0], ylim = AoI[1],
       )
ax5.imshow(data, cmap = color)

ticks = np.array([data.min()*0.9 +data.max()*0.1,
                  np.average([data.min(),data.max()]),
                  data.max()*0.9 +data.min()*0.1]).astype(int)


def fmt(x, pos):
    a, b = '{:.2e}'.format(x).split('e')
    b = int(b)
    return r'${} \times 10^{{{}}}$'.format(a, b)

cbar = plt.colorbar(ax1.imshow(data, cmap = cmap), ax=[ax1, ax4, ax5], 
             #shrink = 0.935, #with ticks
             shrink = 0.922, #without ticks
             location = 'bottom', ticks = ticks,
             anchor = (10,10), format=ticker.FuncFormatter(fmt)
             )

fig.suptitle('d = 160 nm (p = 570 nm)')
plt.savefig(fname = 'demo.pdf',
            format = 'pdf', dpi = 600, bbox_inches = 'tight', pad_inches = 0)

Corrected Version

Or perhaps a different way to visualize achieving basically the same thing. another

Charles
  • 104
  • 12
  • Updated with more generalized code, and I have attempted to make my goals more clear in the corrected image. – Charles Mar 28 '21 at 20:55

1 Answers1

0

You are asking for a number of things that are not possible automatically.

If instead you used aspect='auto' you would get exactly what you want, except the axes would not have an equal aspect ratio. But otherwise constrained_layout works with the area the axes could be drawn in, not the area the axes is actually drawn in.

You can of course do some math and do this particular layout manually. But what you are asking is almost impossible to do generally.

Jody Klymak
  • 4,979
  • 2
  • 15
  • 31
  • Perhaps you could explain what part of this isn't possible? It is obviously possible to match the bar for multiple plots...https://stackoverflow.com/questions/13784201/matplotlib-2-subplots-1-colorbar. I cannot find an example though for doing this on the bottom of a figure with multiple size plots perhaps that is indeed because it is not possible. Everything else seems more or less standard after that is out of the way which is why it is the title, the rest is just so that I don't end up with an overly simplified solution. Thanks. – Charles Mar 28 '21 at 19:03
  • As my data is square, the `axis='equal'` is no longer necessary at all, Thank you for calling attention to this. I am not sure what you are suggesting it should fix, as nothing appears to have changed. I have edited the problem to remove this. – Charles Mar 28 '21 at 19:41
  • I am suggesting you set `aspect='auto'` in `imshow`, then constrained_layout works as it was designed. The problem is that you are asking for too many constraints: matplotlib sets the figsize, so that is the outer constraint. You want the axes edges to line up, but you also want the aspect ratio of the axes to be 1:1, and you want there to be less white space. All four of those can't be constrained at the same time. If you let the height of the figure change, then you *could* get what you want, but in matplotlib the size of the figure is a hard outer constraint. – Jody Klymak Mar 28 '21 at 19:47
  • I have said specifically that I do not care what the height is. Putting auto into all of the imshow's made the smaller plots rectangular. Perhaps it is difficult to read or the image did not help you, but the 3 alignments that are important are the tops of the plots, bottom of the plots and the color bar to the edges. – Charles Mar 28 '21 at 19:57
  • Yes I understood what you want, and I said it is not possible with constrained layout. You can of course write a different constraint solver that allows the figure height to vary but meets your other constraints. – Jody Klymak Mar 28 '21 at 20:10
  • Great, now that's a much more informative answer. I generally despise the constrained layout. It is obvious that it is still experimental. In this case, it has seemed more helpful. In one trial without it, my figure looked exactly the same but it was 1.5" narrower. I have also said that I would not mind a different solution. As the constrained layout is the whole problem perhaps you can explain why the title does not show with `suptitle` or any other method I have tried. Or perhaps just suggest another way to do the colorbar. You have been so helpful I would like to give you the points. – Charles Mar 28 '21 at 20:20
  • Please report issues with any experimental features to the project on GitHub. – Jody Klymak Mar 28 '21 at 20:54
  • Will take that under advisement – Charles Mar 28 '21 at 21:04