0

I have the following script to do what I want -

import matplotlib.pyplot as plt
from matplotlib.lines import Line2D

TITLEPAD = 25
LABELSIZE = 24
SCENARIO_LABELSIZE=25
SCENARIO_LABELPAD=85
LABELPAD = 30
LINESTYLES = ["-", "--"]

COLUMN_NAMES = ["A", "B", "C", "D"]
ROW_NAMES = ["I", "II", "III"]
ALL_METHODS = ["X", "Y", "Z"]

fig, axs = plt.subplots(nrows=len(ROW_NAMES), ncols=len(COLUMN_NAMES), figsize=(27, 18), sharex='col', sharey=True, dpi=100)

# set row and column headers
for col, name in enumerate(COLUMN_NAMES):
    tmp_ax = axs[0, col].twiny()
    tmp_ax.set_xticks([])
    tmp_ax.set_xlabel(name, labelpad=LABELPAD, fontsize=LABELSIZE)

for row, name in enumerate(ROW_NAMES):
    axs[row, 0].set_ylabel("Y", labelpad=LABELPAD, fontsize=LABELSIZE, rotation=0)
    tmp_ax = axs[row, -1].twinx()
    tmp_ax.set_ylabel(name+"\nCase", fontsize=LABELSIZE, fontweight="bold", rotation=0, labelpad=LABELPAD )
    tmp_ax.set_yticks([])

# bunch of plots

plt.subplots_adjust(left=0.125, wspace=0.2, hspace=0.2, bottom=0.15)

# legends
legends = []
for method in ALL_METHODS:
    legends.append(Line2D([0, 1], [0, 0], linestyle="-", label=method, linewidth=3))

lgd = fig.legend(handles=legends, ncol=len(legends), fontsize=30, loc="lower center", fancybox=True, bbox_to_anchor=(0.5, -0.0275))
fig.tight_layout()
fig.savefig("tmp.png", bbox_inches='tight', bbox_extra_artists=(lgd,))

It yields the image like this - enter image description here

Notice the busy and butchered legend at the bottom. I have tried several things like playing with bbox dimensions or figure size to make it look less busy and full. Nothing worked!

Question: Is there an easy and structured way to control the legend positioning in a figure? I know this is much easy when we have a single plot. Ideally, I would like to position the legend such that there is enough space between the ticks at the bottom and the top line of the legend without cutting it out from the figure.

pg2455
  • 5,039
  • 14
  • 51
  • 78
  • 1
    Have you already considered building your subplots via `gridspec` ( [documentation example](https://matplotlib.org/3.3.3/tutorials/intermediate/tight_layout_guide.html#use-with-gridspec) ) and using gridspec's `tight_layout` instead? From personal experience, I'd say it gives finer control over positioning (see e.g. `gs.tight_layout(fig, rect=[0, 0, 0.5, 1])` in the link above). Possibly unrelated: do you really want to create such a large figure (27x18 inches)? – Asmus Dec 14 '20 at 16:58
  • 1
    Also: have a look at [this detailed answer](https://stackoverflow.com/a/43439132/565489), specifically where it talks about "Legend inside dedicated subplot axes" (search for `lax.legend`) – Asmus Dec 14 '20 at 17:02
  • No. that's just an example. I usually go for (15x10). Thanks for sharing the links. Can you explain what exactly does tight_layout do? – pg2455 Dec 14 '20 at 17:02

1 Answers1

1

As pointed by @Asmus this detailed answer has the answer to what I was looking for.

For the sake of completeness, here is what I did -

# no need for subplots_adjust 
# plt.subplots_adjust(left=0.125, wspace=0.2, hspace=0.2, bottom=0.25)

# added reference for bbox_to_anchor as the coordinates of figure  using bbox_transform
lgd = fig.legend(handles=legends, ncol=len(legends), fontsize=30, loc="lower center", fancybox=True, bbox_to_anchor=(0.5, 0), bbox_transform=fig.transFigure)

# pushed the entire subplot withing a rectangle as specified in rect argument to tight_layout 
fig.tight_layout(rect=[0, 0.05, 1, 1])
pg2455
  • 5,039
  • 14
  • 51
  • 78