0

I have a figure with a fixed size (1000*1000) and I'm trying to place a legend below the plot (i.e. outside of the axes bounding box). I'm using the bbox_to_anchor argument to plt.legend() and the pad argument to plt.tight_layout(). The issue is, when I try to move the legend further down, by decreasing the second value passed to bbox_to_anchor, the padding between the bottom edge of the figure and the legend increases, even if I set pad=0 in plt.tight_layout(). I want to use a padding of 0.7, to make the plot consistent with other plots which use that padding, and have the legend sufficiently appart from the bar labels, keeping the shrinking of the plot to the minimum required. I've read the matplotlib documentation legend guide and this excelent answer to a related question, but I can't figure why the padding between the bottom of the legend and the bottom of the figure increases as I decrease the y-value of the 2-tuple passed to the bbox_to_anchor argument. I've also tried to mess with the loc argument, to no avail.

Examples

Case 1

ax.legend(loc='upper center', bbox_to_anchor=(0.5, -0.3), fontsize=30, frameon=True, borderaxespad=0)
    
plt.tight_layout(pad=0)

enter image description here

Case 2

Trying to move the legend further down:

ax.legend(loc='upper center', bbox_to_anchor=(0.5, -0.5), fontsize=30, frameon=True, borderaxespad=0)
        
plt.tight_layout(pad=0)

enter image description here

Case 3

Changing the padding to 0.2 so that the edges of the figure are not clipped:

ax.legend(loc='upper center', bbox_to_anchor=(0.5, -0.5), fontsize=30, frameon=True, borderaxespad=0)
    
plt.tight_layout(pad=0.2)

enter image description here

Code for the function that creates the plot (having removed some unnecessary parts):

def example_plot(left_total, right_total, target):
    
    fig, ax = plt.subplots(figsize=(10,10), linewidth=2, edgecolor='#9E9E9E')

    names = ['left', 'right']
        
    size_of_groups = [left_total, right_total]
    
    left_bar = ax.bar(names[0], size_of_groups[0], color='#515151', width=0.5)
    right_bar = ax.bar(names[1], size_of_groups[1], color='#FFD732', width=0.5)

    ax.axhline(y=target, color='#32CDFF', linestyle='solid', lw=3, label='Target')
        
    ymin = min(left_total, right_total, target)
    
    ymin = math.floor(ymin/1000)*1000
    ymax = max(left_total, right_total, target)       
    ymax = math.ceil(ymax/1000)*1000
    ax.set_ylim([ymin, ymax])       
        
    yticks = list(range(ymin, ymax+1000, 1000))        
    ax.set_yticks(yticks)

    ax.bar_label(left_bar, fontsize=40)
    ax.bar_label(right_bar, fontsize=40)
    
    ax.spines[:].set_visible(False)

    ax.set_axisbelow(True)
    ax.grid(True, axis='y', color='#9E9E9E', clip_on=False)

    ax.set_title("Example plot\n", fontsize=50, fontweight = 500)

    ax.legend(loc='upper center', bbox_to_anchor=(0.5, -0.3), fontsize=30, frameon=True, borderaxespad=0)
    
    plt.tight_layout(pad=0)
    
    fig.savefig("plots/example_plot.png")
RicardoC
  • 109
  • 1
  • 2
  • 9

1 Answers1

1
  1. The bbox argument is in axes co-ordinates, so 0.3 is 0.3 of the axes height.
  2. The pad argument for tight_layout is the fraction of the mean axes size, so if it is 0.7, the pad will be 70% of the axes height. I don't know why you want 0.7 but if you make the bbox move down to -0.5 from -0.3, the axes gets smaller due to tight_layout and hence the pad gets smaller.

Unsolicited advice: tight_layout and constrained_layout are meant for straightforward, default cases. They are not infinitely flexible, so if you have very particular needs, I suggest you learn how to manually place axes and legends.

Jody Klymak
  • 4,979
  • 2
  • 15
  • 31
  • Thank you for your answer! Are you sure about the pad being a fraction of the axes size? Because the [documentation specifically says](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.tight_layout.html#matplotlib.pyplot.tight_layout) it is a "fraction of the font size", and the default value is 1.08. I've been using `pad=0.7` for other plots and it definitely isn't adding a padding of 70% the axes height. What would you say is the "standard" way of moving the legend up or down, outside of the axes, without affecting the padding between the legend and the figure edge? – RicardoC May 31 '21 at 13:18
  • 1
    Sorry you are correct. The pad argument is a red herring. Legends outside of axes are a problem for tight_layout. But if you want to place the legend in figure coordinates then change the bbox_transform to fig.transFigure. – Jody Klymak May 31 '21 at 14:28