Home > Software design >  Sizing figure with variable number of subplots and 2 legends
Sizing figure with variable number of subplots and 2 legends

Time:07-03

I'm having a really hard time attempting to properly size a figure with a variable number of subplots (between 3 and 8) and 2 legends that should appear glued to each other.

I also checked every related issue here in stack overflow, but couldn't get any answer to this specific case, due to my need for 2 legends.

The important to me is to get an optimal figure that I save as pdf to include in a report. I tried everything, and in the end the closes I got was with using tight: fig.savefig(f'variations{len(list_variations)}_B.pdf', bbox_inches='tight').

Here is a fully reproducible example (that emulates my code and figures):

list_variations = [0, 1, 2, 3, 4, 5, 6, 7, 8]  # Does not work for any option
list_variations = [0, 1, 2]  # Works Fine for Option A
n_subplots = len(list_variations)
fig_size = (5.457, n_subplots*3.5/3)
fig, axs = plt.subplots(n_subplots, 1, figsize=fig_size, sharex=True, sharey=True)
labels_upp = ('abdications', 'liner wint.ol.:$\\pm$0.19e', 'liner wint.ol.:$\\pm$0.1e')
labels_low = ('apportions', 'bisections', 'ablations', 'saktis')
for idx in list_variations:
    for i, lab_upp in enumerate(labels_upp):
        axs[idx].plot(60 i, 0.2, label=lab_upp)
    for lab_low in labels_low:
        axs[idx].plot(60 i, -0.2, label=lab_low)
    axs[idx].set_title(f'Variation {idx}', fontsize=8)
axs[-1].set_xlim((60, 80))
axs[-1].set(ylim=(-1, 1))
axs[-1].set(xlabel='elasticity (e)')
plt.subplots_adjust(hspace=0.25)

# Make Legends (PROBLEM IS HERE)
# Option A - relative to fig
props_leg_upp = dict(facecolor='white', bbox_to_anchor=(0, -0.102, 1, 0.1), mode='expand', loc='upper center')
props_leg_low = dict(facecolor='lightgrey', bbox_to_anchor=(0, -0.172, 1, 0.1), mode='expand', loc='upper center')
upper_leg = fig.legend(labels_upp, ncol=len(labels_upp), **props_leg_upp)
lower_leg = fig.legend(labels_low, ncol=len(labels_low), **props_leg_low)
axs[-1].add_artist(upper_leg)

# Option B - relative to axs[-1]
props_leg_upp = dict(facecolor='white', bbox_to_anchor=(0, -0.262, 1, 0.1), mode='expand', loc='upper center')
props_leg_low = dict(facecolor='lightgrey', bbox_to_anchor=(0, -0.322, 1, 0.1), mode='expand', loc='upper center')
upper_leg = axs[-1].legend(labels_upp, ncol=len(labels_upp), **props_leg_upp)
lower_leg = axs[-1].legend(labels_low, ncol=len(labels_low), **props_leg_low)
axs[-1].add_artist(upper_leg)

I tried every combination of matplotlib.legend properties that I could think of, and in the end I got to these 2 options: A-apply the legend to figure; B-apply the legend to the last axis.

Option A works pretty well for 3 subplots: enter image description here

In Option B (adding the legend to last axis), that I tried to force the legend to be the same width of the axis, the legends appear on top of each other (although I tried to finetune the bbox_to_anchor properties). enter image description here

Yet, the biggest problem is when I use a greater number of subplots (e.g. 9 which is the maximum). For these case none of the options work. Option A: enter image description here

Option B: enter image description here

Is there any way that I can make it work for different numbers of subplots, while (ideally) keeping the width of the legends the same as the width of the axis?

CodePudding user response:

To align the legend in the subplot, I would need to set the transform coordinate axis of the legend box. In this case, the settings are added to match the last axis of the subplot. The box values were adjusted manually. Since the box value parameters are bbox_to_anchor=(x0,y0,x1,y1), in this case y0,y1 are the same value.

import matplotlib.pyplot as plt

list_variations = [0, 1, 2, 3, 4, 5, 6, 7, 8]  # Does not work for any option
#list_variations = [0, 1, 2]  # Works Fine for Option A
n_subplots = len(list_variations)
fig_size = (5.457, n_subplots*3.5/3)

fig, axs = plt.subplots(n_subplots, 1, figsize=fig_size, sharex=True, sharey=True)
labels_upp = ('abdications', 'liner wint.ol.:$\\pm$0.19e', 'liner wint.ol.:$\\pm$0.1e')
labels_low = ('apportions', 'bisections', 'ablations', 'saktis')

for idx in list_variations:
    for i, lab_upp in enumerate(labels_upp):
        axs[idx].plot(60 i, 0.2, label=lab_upp)
    for lab_low in labels_low:
        axs[idx].plot(60 i, -0.2, label=lab_low)
    axs[idx].set_title(f'Variation {idx}', fontsize=8)

axs[-1].set_xlim((60, 80))
axs[-1].set(ylim=(-1, 1))
axs[-1].set(xlabel='elasticity (e)')
plt.subplots_adjust(hspace=0.25)

# Make Legends (PROBLEM IS HERE)
# # Option A - relative to fig
props_leg_upp = dict(facecolor='white', bbox_to_anchor=(-0.1, -0.350, 1.2, 0.-0.350), mode='expand', loc='upper center')
props_leg_low = dict(facecolor='lightgrey', bbox_to_anchor=(-0.1, -0.650, 1.2, -0.650), mode='expand', loc='upper center')
upper_leg = fig.legend(labels_upp, ncol=len(labels_upp), bbox_transform=axs[-1].transAxes, **props_leg_upp)
lower_leg = fig.legend(labels_low, ncol=len(labels_low), bbox_transform=axs[-1].transAxes, **props_leg_low)
axs[-1].add_artist(upper_leg)

plt.show()

enter image description here

If you enable the following: list_variations = [0, 1, 2]

enter image description here

  • Related