简体   繁体   中英

Matplotlib with fixed axes size and legend outside of axes

I want to generate publication quality plots with matplotlib. For consistency reasons I want all diagrams (axes) to look the same, particularly in size. I look through many tutorials and came up with the following idea: 1. create a plot and determine the height of the axes in inches 2. add the legend and determine the height of the legend in inches 3. enlarge the figure height by the legend height in inches 4. shrink the new axes height to the original axes height to keep the legend in the figure area

Unfortunately, this is not working as intended. Any suggestions on the Code?

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.transforms

# GENERATE DATA
x       = np.arange(-2.*np.pi,4.*np.pi,0.01)
sin_arr = [np.sin(x-dx) for dx in np.arange(0.,2.*np.pi,0.5)]
# SET FIGURE SIZE AND RCPARAMETERS
plt.rcParams.update( {'figure.figsize' : [5.90551197, 3.64980712]} )
params = {
    'text.usetex'        : True,
    'backend'            :'ps',
    ## FONTS 
    "font.family"    : "serif",
    "font.serif"     : [],          # blank entries should cause plots to inherit fonts from the document
    "font.monospace" : [],
    ## FONT SIZES
    'axes.labelsize' : 12,
    'font.size'      : 12,
    'legend.fontsize': 12,
    'xtick.labelsize': 12,
    'ytick.labelsize': 12,
    ## LINEWIDTH
    'axes.linewidth' : 0.5,
    'patch.linewidth': 0.5,    # legend frame
    'lines.linewidth': 1.5,
    ## LEGEND
    'legend.edgecolor':'black',
    'legend.frameon'  :True,
    'legend.fancybox' :False,
} 
plt.rcParams.update(params)
# GENERATE PLOT
fig = plt.figure()
ax  = fig.add_subplot(111)

for i,sin in enumerate(sin_arr):
    ax.plot(x,sin,label=r'$\sin(x)$ '+str(i))
ax.set_xlabel(r'$\varepsilon$')  
ax.set_ylabel(r'$\sigma$ in [MPa]', labelpad=15)
ax.set_xlim([0.,2.*np.pi])
# SHRINK PADDING
plt.tight_layout(pad=0)  

# ADD LEGEND ON TOP OF AXES WITHOUT CHANGING AXES SIZE
legend    = ax.legend( bbox_to_anchor=(0., 1.02, 1., .102), 
                    loc='lower left',
                    ncol=3,
                    borderaxespad=0.,mode="expand" )

# GET LEGEND / AXES HEIGHT IN INCHES 
ax_box  = ax.get_window_extent()        # AXES BBOX IN DISPLAY UNITS
leg_box = legend.get_bbox_to_anchor()   # LEGEND BBOX IN DISPLAY UNITS

ax_box_inch  = ax_box.transformed( fig.dpi_scale_trans.inverted() )  # TRANSFORM TO INCHES
leg_box_inch = leg_box.transformed( fig.dpi_scale_trans.inverted() ) # TRANSFORM TO INCHES

# ORIGINAL_AX_HEIGHT
ax_height_inch_orig = ax_box_inch.height

# CHANGE FINGURE TO FIT LEGEND
fig.set_size_inches(fig.get_figwidth(), fig.get_figheight() + leg_box_inch.height)

# GET NEW HEIGHT OF AXES 
ax_box_new_inch    = ax.get_window_extent().transformed( fig.dpi_scale_trans.inverted() )
ax_height_inch_new = ax_box_new_inch.height
factor = ax_height_inch_orig/ax_height_inch_new

# GET AXES BBOX IN FIGURE COORDINATES
ax_box = ax.get_window_extent().transformed( fig.transFigure.inverted() )

# CHANGE AXES TO ORIGINAL HEIHGT BUT WITH LEGEND FULLY VISIBLE
ax.set_position([ax_box.x0, ax_box.y0,ax_box.width, ax_box.height*factor])

plt.savefig('test.pdf',format='pdf',dpi=90)

It looks like you can achieve the desired outcome much easier by using savefig 's bbox_inches argument.

plt.savefig("output.pdf", bbox_inches="tight", pad_inches=0)

This works if you don't need the figure for anything but saving it.

import matplotlib.pyplot as plt
import numpy as np

# GENERATE DATA
x       = np.arange(-2.*np.pi,4.*np.pi,0.01)
sin_arr = [np.sin(x-dx) for dx in np.arange(0.,2.*np.pi,0.5)]
# SET FIGURE SIZE AND RCPARAMETERS
plt.rcParams.update( {'figure.figsize' : [5.90551197, 3.64980712]} )

fig, ax = plt.subplots()

for i,sin in enumerate(sin_arr):
    ax.plot(x,sin,label=r'$\sin(x)$ '+str(i))
ax.set_xlabel(r'$\varepsilon$')  
ax.set_ylabel(r'$\sigma$ in [MPa]', labelpad=15)
ax.set_xlim([0.,2.*np.pi])
# SHRINK PADDING
fig.tight_layout(pad=0)  

# ADD LEGEND ON TOP OF AXES WITHOUT CHANGING AXES SIZE
legend    = ax.legend( bbox_to_anchor=(0., 1.02, 1., .102), 
                    loc='lower left',
                    ncol=3,
                    borderaxespad=0.,mode="expand" )

plt.savefig("output.pdf", bbox_inches="tight", pad_inches=0)

Note that if you use plt.tight_layout the resulting axes size may still be different if you use different x- or y-labels (eg if they sometimes contain capital letters or letters which go below the baseline, like "p" or "g"). In such case it would be better to manually decide for some parameters, and replace tight_layout with

fig.subplots_adjust(left=0.151, bottom=0.130, right=0.994, top=0.990) 

or whatever other parameters work for you, given the fontsizes in use.


The problem of constant axes size is hence rather easy to solve. What would be more complicated is the inverse. Having a constant figure size, but shrinking the axes such that the figure still accommodates the legend. This would be shown in this question Creating figure with exact size and no padding (and legend outside the axes)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM