Matplotlib с фиксированным размером осей и надписью вне осей - PullRequest
0 голосов
/ 08 сентября 2018

Я хочу создавать графики качества публикации с помощью matplotlib. Из соображений согласованности я хочу, чтобы все диаграммы (оси) выглядели одинаково, особенно по размеру. Я просмотрел много уроков и придумал следующую идею: 1. создать сюжет и определить высоту осей в дюймах 2. добавить легенду и определить высоту легенды в дюймах 3. увеличить высоту фигуры на высоту легенды в дюймах 4. уменьшите высоту новых осей до исходной высоты осей, чтобы легенда оставалась в области рисунка

К сожалению, это не работает, как задумано. Есть предложения по Кодексу?

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)

1 Ответ

0 голосов
/ 08 сентября 2018

Похоже, что вы можете достичь желаемого результата гораздо проще, используя savefig 'bbox_inches аргумент.

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

Это работает, если вам не нужна фигура для чего-либо, кроме ее сохранения.

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)

Обратите внимание, что если вы используете plt.tight_layout, результирующий размер осей может по-прежнему отличаться, если вы используете разные x- или y-метки (например, если они иногда содержат заглавные буквы или буквы, которые идут ниже базовой линии, например, "p" или "г"). В таком случае было бы лучше выбрать некоторые параметры вручную и заменить tight_layout на

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

или другие параметры, которые вам подходят, с учетом используемых шрифтов.


Следовательно, проблему постоянного размера осей довольно легко решить. Что будет сложнее, так это обратное. Имея постоянный размер фигуры, но сжимая оси таким образом, чтобы фигура все еще соответствовала легенде. Это будет показано в этом вопросе Создание фигуры с точным размером и без отступов (и условных обозначений вне осей)

...