Оформление графиков в Matplotlib#

Рассмотрим различные способы изменения оформления графиков. Важно уметь это делать, чтобы с лёгкостью удовлетворять требованиям различных редакций и стандартов на оформление текстовых работ, например, требованиям кафедры СМ6 МГТУ им. Н.Э. Баумана.

В конце раздела рассматривается создание файла с собственными стилевыми настройками matplotlib. Сформированный стиль вполне удовлетворяет требованиям кафедры СМ6, поэтому студенты СМ6 могут использовать этот файл в своих работах (домашних, курсовых и прочих).

Для начала выполним некоторую рутину:

import matplotlib.pyplot as plt
import numpy as np

Будем строить тригонометрические функции:

x = np.linspace(-np.pi, np.pi, 201)
sin_x = np.sin(x)
cos_x = np.cos(x)

Использование готовых стилей#

Построим график, оформленный в стиле “по умолчанию”:

fig, ax = plt.subplots()
ax.plot(x, sin_x, label=r"$\sin{x}$")
ax.plot(x, cos_x, label=r"$\cos{x}$")
ax.set(xlabel="Аргумент $x$", ylabel="Функция $f(x)$")
ax.legend();
../_images/d162ea8b55d158c06b4c6dfa82ad26844c83cb1ba8df1a002718f4ae7aeea586.png

В Matplotlib имеется целый ряд встроенных стилей графиков. Назначить их можно, например, используя менеджер контекста with и функцию pyplot.style.context с указанием названия стиля:

with plt.style.context("ggplot"):
    fig, ax = plt.subplots()
    ax.plot(x, sin_x, label=r"$\sin{x}$")
    ax.plot(x, cos_x, label=r"$\cos{x}$")
    ax.set(xlabel="Аргумент $x$", ylabel="Функция $f(x)$")
    ax.legend()
    plt.show()
../_images/52609f4104fe72628560709ad952cd7e9b4c8c974b26606b8ac52eb33d6ed7a5.png

Также внутри менеджера контекста с заданным стилем matplotlib можно вызвать собственную функцию построения графика. Сведём построение наших синусов-косинусов в отдельную функцию, как делали в разделе Особенности matplotlib:

def plot_sincos(x, figax=None):
    sin_x = np.sin(x)
    cos_x = np.cos(x)
    fig, ax = plt.subplots() if figax is None else figax
    ax.plot(x, sin_x, label=r"$\sin{x}$")
    ax.plot(x, cos_x, label=r"$\cos{x}$")
    return fig, ax

# И вызовем её в стилевом контексте
with plt.style.context("bmh"):
    fig, ax = plot_sincos(x)
    ax.set(xlabel="Аргумент $x$", ylabel="Функция $f(x)$")
    ax.legend()
    plt.show()
../_images/1bbc6b94cbcc68187bd285b203d59cb33efa2f0b33af543b476d0cf9adef6d0a.png

Note

Заметьте, что меняется оформление как обычного текста, так и математических выражений, обрамлённых долларами $...$.

# Или в тёмном стиле
with plt.style.context("dark_background"):
    fig, ax = plot_sincos(x)
    # И можем изменить полученный график, например,
    # добавив график новой функции
    ax.plot(x, np.tanh(x), label=r"$\tanh{x}$")
    ax.set(xlabel="Аргумент $x$", ylabel="Функция $f(x)$")
    ax.legend()
    plt.show()
../_images/e4565742f9fba66c26e8038451d363c4315ac146833395024b8cb84720f0236d.png

Использовать стилевой контекст просто и удобно. Однако есть и подход назначения глобального стиля для графиков matplotlib.

Глобальная настройка стиля#

Данный подход менее универсален, чем подход стилевого контекста, однако его удобнее применять в работах, в которых вы изначально имеете чёткие требования к оформлению.

Функция plt.style.use#

Глобальный стиль можно задать так:

# В начале файла с кодом
plt.style.use("grayscale")

# И просто строим график без всякого контекста
_, ax = plot_sincos(x)
ax.legend();
../_images/36f9f920a2b6a05d7593a2e1c3840001727bfa270a13832c9e3a19b030b12c92.png

Изменение глобальной переменной matplotlib.rcParams#

Для начала вернём стиль по умолчанию:

plt.style.use("default")

Matplotlib хранит настройки стилей в специальных текстовых файлах с расширением .mplstyle. При импортировании библиотеки происходит считывание файла с настройками по умолчанию и сохранение этих настроек в глобальном Python-словаре matplotlib.rcParams (по ссылке найдёте описание всех настраиваемых параметров). Вот, для примера, его содержимое:

import matplotlib as mpl
mpl.rcParams
RcParams({'_internal.classic_mode': False,
          'agg.path.chunksize': 0,
          'animation.bitrate': -1,
          'animation.codec': 'h264',
          'animation.convert_args': ['-layers', 'OptimizePlus'],
          'animation.convert_path': 'convert',
          'animation.embed_limit': 20.0,
          'animation.ffmpeg_args': [],
          'animation.ffmpeg_path': 'ffmpeg',
          'animation.frame_format': 'png',
          'animation.html': 'none',
          'animation.writer': 'ffmpeg',
          'axes.autolimit_mode': 'data',
          'axes.axisbelow': 'line',
          'axes.edgecolor': 'black',
          'axes.facecolor': 'white',
          'axes.formatter.limits': [-5, 6],
          'axes.formatter.min_exponent': 0,
          'axes.formatter.offset_threshold': 4,
          'axes.formatter.use_locale': False,
          'axes.formatter.use_mathtext': False,
          'axes.formatter.useoffset': True,
          'axes.grid': False,
          'axes.grid.axis': 'both',
          'axes.grid.which': 'major',
          'axes.labelcolor': 'black',
          'axes.labelpad': 4.0,
          'axes.labelsize': 'medium',
          'axes.labelweight': 'normal',
          'axes.linewidth': 0.8,
          'axes.prop_cycle': cycler('color', ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf']),
          'axes.spines.bottom': True,
          'axes.spines.left': True,
          'axes.spines.right': True,
          'axes.spines.top': True,
          'axes.titlecolor': 'auto',
          'axes.titlelocation': 'center',
          'axes.titlepad': 6.0,
          'axes.titlesize': 'large',
          'axes.titleweight': 'normal',
          'axes.titley': None,
          'axes.unicode_minus': True,
          'axes.xmargin': 0.05,
          'axes.ymargin': 0.05,
          'axes.zmargin': 0.05,
          'axes3d.automargin': False,
          'axes3d.grid': True,
          'axes3d.xaxis.panecolor': (0.95, 0.95, 0.95, 0.5),
          'axes3d.yaxis.panecolor': (0.9, 0.9, 0.9, 0.5),
          'axes3d.zaxis.panecolor': (0.925, 0.925, 0.925, 0.5),
          'backend': 'module://matplotlib_inline.backend_inline',
          'backend_fallback': True,
          'boxplot.bootstrap': None,
          'boxplot.boxprops.color': 'black',
          'boxplot.boxprops.linestyle': '-',
          'boxplot.boxprops.linewidth': 1.0,
          'boxplot.capprops.color': 'black',
          'boxplot.capprops.linestyle': '-',
          'boxplot.capprops.linewidth': 1.0,
          'boxplot.flierprops.color': 'black',
          'boxplot.flierprops.linestyle': 'none',
          'boxplot.flierprops.linewidth': 1.0,
          'boxplot.flierprops.marker': 'o',
          'boxplot.flierprops.markeredgecolor': 'black',
          'boxplot.flierprops.markeredgewidth': 1.0,
          'boxplot.flierprops.markerfacecolor': 'none',
          'boxplot.flierprops.markersize': 6.0,
          'boxplot.meanline': False,
          'boxplot.meanprops.color': 'C2',
          'boxplot.meanprops.linestyle': '--',
          'boxplot.meanprops.linewidth': 1.0,
          'boxplot.meanprops.marker': '^',
          'boxplot.meanprops.markeredgecolor': 'C2',
          'boxplot.meanprops.markerfacecolor': 'C2',
          'boxplot.meanprops.markersize': 6.0,
          'boxplot.medianprops.color': 'C1',
          'boxplot.medianprops.linestyle': '-',
          'boxplot.medianprops.linewidth': 1.0,
          'boxplot.notch': False,
          'boxplot.patchartist': False,
          'boxplot.showbox': True,
          'boxplot.showcaps': True,
          'boxplot.showfliers': True,
          'boxplot.showmeans': False,
          'boxplot.vertical': True,
          'boxplot.whiskerprops.color': 'black',
          'boxplot.whiskerprops.linestyle': '-',
          'boxplot.whiskerprops.linewidth': 1.0,
          'boxplot.whiskers': 1.5,
          'contour.algorithm': 'mpl2014',
          'contour.corner_mask': True,
          'contour.linewidth': None,
          'contour.negative_linestyle': 'dashed',
          'date.autoformatter.day': '%Y-%m-%d',
          'date.autoformatter.hour': '%m-%d %H',
          'date.autoformatter.microsecond': '%M:%S.%f',
          'date.autoformatter.minute': '%d %H:%M',
          'date.autoformatter.month': '%Y-%m',
          'date.autoformatter.second': '%H:%M:%S',
          'date.autoformatter.year': '%Y',
          'date.converter': 'auto',
          'date.epoch': '1970-01-01T00:00:00',
          'date.interval_multiples': True,
          'docstring.hardcopy': False,
          'errorbar.capsize': 0.0,
          'figure.autolayout': False,
          'figure.constrained_layout.h_pad': 0.04167,
          'figure.constrained_layout.hspace': 0.02,
          'figure.constrained_layout.use': False,
          'figure.constrained_layout.w_pad': 0.04167,
          'figure.constrained_layout.wspace': 0.02,
          'figure.dpi': 100.0,
          'figure.edgecolor': 'white',
          'figure.facecolor': 'white',
          'figure.figsize': [6.4, 4.8],
          'figure.frameon': True,
          'figure.hooks': [],
          'figure.labelsize': 'large',
          'figure.labelweight': 'normal',
          'figure.max_open_warning': 20,
          'figure.raise_window': True,
          'figure.subplot.bottom': 0.11,
          'figure.subplot.hspace': 0.2,
          'figure.subplot.left': 0.125,
          'figure.subplot.right': 0.9,
          'figure.subplot.top': 0.88,
          'figure.subplot.wspace': 0.2,
          'figure.titlesize': 'large',
          'figure.titleweight': 'normal',
          'font.cursive': ['Apple Chancery',
                           'Textile',
                           'Zapf Chancery',
                           'Sand',
                           'Script MT',
                           'Felipa',
                           'Comic Neue',
                           'Comic Sans MS',
                           'cursive'],
          'font.family': ['sans-serif'],
          'font.fantasy': ['Chicago',
                           'Charcoal',
                           'Impact',
                           'Western',
                           'xkcd script',
                           'fantasy'],
          'font.monospace': ['DejaVu Sans Mono',
                             'Bitstream Vera Sans Mono',
                             'Computer Modern Typewriter',
                             'Andale Mono',
                             'Nimbus Mono L',
                             'Courier New',
                             'Courier',
                             'Fixed',
                             'Terminal',
                             'monospace'],
          'font.sans-serif': ['DejaVu Sans',
                              'Bitstream Vera Sans',
                              'Computer Modern Sans Serif',
                              'Lucida Grande',
                              'Verdana',
                              'Geneva',
                              'Lucid',
                              'Arial',
                              'Helvetica',
                              'Avant Garde',
                              'sans-serif'],
          'font.serif': ['DejaVu Serif',
                         'Bitstream Vera Serif',
                         'Computer Modern Roman',
                         'New Century Schoolbook',
                         'Century Schoolbook L',
                         'Utopia',
                         'ITC Bookman',
                         'Bookman',
                         'Nimbus Roman No9 L',
                         'Times New Roman',
                         'Times',
                         'Palatino',
                         'Charter',
                         'serif'],
          'font.size': 10.0,
          'font.stretch': 'normal',
          'font.style': 'normal',
          'font.variant': 'normal',
          'font.weight': 'normal',
          'grid.alpha': 1.0,
          'grid.color': '#b0b0b0',
          'grid.linestyle': '-',
          'grid.linewidth': 0.8,
          'hatch.color': 'black',
          'hatch.linewidth': 1.0,
          'hist.bins': 10,
          'image.aspect': 'equal',
          'image.cmap': 'viridis',
          'image.composite_image': True,
          'image.interpolation': 'antialiased',
          'image.interpolation_stage': 'data',
          'image.lut': 256,
          'image.origin': 'upper',
          'image.resample': True,
          'interactive': True,
          'keymap.back': ['left', 'c', 'backspace', 'MouseButton.BACK'],
          'keymap.copy': ['ctrl+c', 'cmd+c'],
          'keymap.forward': ['right', 'v', 'MouseButton.FORWARD'],
          'keymap.fullscreen': ['f', 'ctrl+f'],
          'keymap.grid': ['g'],
          'keymap.grid_minor': ['G'],
          'keymap.help': ['f1'],
          'keymap.home': ['h', 'r', 'home'],
          'keymap.pan': ['p'],
          'keymap.quit': ['ctrl+w', 'cmd+w', 'q'],
          'keymap.quit_all': [],
          'keymap.save': ['s', 'ctrl+s'],
          'keymap.xscale': ['k', 'L'],
          'keymap.yscale': ['l'],
          'keymap.zoom': ['o'],
          'legend.borderaxespad': 0.5,
          'legend.borderpad': 0.4,
          'legend.columnspacing': 2.0,
          'legend.edgecolor': '0.8',
          'legend.facecolor': 'inherit',
          'legend.fancybox': True,
          'legend.fontsize': 'medium',
          'legend.framealpha': 0.8,
          'legend.frameon': True,
          'legend.handleheight': 0.7,
          'legend.handlelength': 2.0,
          'legend.handletextpad': 0.8,
          'legend.labelcolor': 'None',
          'legend.labelspacing': 0.5,
          'legend.loc': 'best',
          'legend.markerscale': 1.0,
          'legend.numpoints': 1,
          'legend.scatterpoints': 1,
          'legend.shadow': False,
          'legend.title_fontsize': None,
          'lines.antialiased': True,
          'lines.color': 'C0',
          'lines.dash_capstyle': <CapStyle.butt: 'butt'>,
          'lines.dash_joinstyle': <JoinStyle.round: 'round'>,
          'lines.dashdot_pattern': [6.4, 1.6, 1.0, 1.6],
          'lines.dashed_pattern': [3.7, 1.6],
          'lines.dotted_pattern': [1.0, 1.65],
          'lines.linestyle': '-',
          'lines.linewidth': 1.5,
          'lines.marker': 'None',
          'lines.markeredgecolor': 'auto',
          'lines.markeredgewidth': 1.0,
          'lines.markerfacecolor': 'auto',
          'lines.markersize': 6.0,
          'lines.scale_dashes': True,
          'lines.solid_capstyle': <CapStyle.projecting: 'projecting'>,
          'lines.solid_joinstyle': <JoinStyle.round: 'round'>,
          'macosx.window_mode': 'system',
          'markers.fillstyle': 'full',
          'mathtext.bf': 'sans:bold',
          'mathtext.bfit': 'sans:italic:bold',
          'mathtext.cal': 'cursive',
          'mathtext.default': 'it',
          'mathtext.fallback': 'cm',
          'mathtext.fontset': 'dejavusans',
          'mathtext.it': 'sans:italic',
          'mathtext.rm': 'sans',
          'mathtext.sf': 'sans',
          'mathtext.tt': 'monospace',
          'patch.antialiased': True,
          'patch.edgecolor': 'black',
          'patch.facecolor': 'C0',
          'patch.force_edgecolor': False,
          'patch.linewidth': 1.0,
          'path.effects': [],
          'path.simplify': True,
          'path.simplify_threshold': 0.111111111111,
          'path.sketch': None,
          'path.snap': True,
          'pcolor.shading': 'auto',
          'pcolormesh.snap': True,
          'pdf.compression': 6,
          'pdf.fonttype': 3,
          'pdf.inheritcolor': False,
          'pdf.use14corefonts': False,
          'pgf.preamble': '',
          'pgf.rcfonts': True,
          'pgf.texsystem': 'xelatex',
          'polaraxes.grid': True,
          'ps.distiller.res': 6000,
          'ps.fonttype': 3,
          'ps.papersize': 'letter',
          'ps.useafm': False,
          'ps.usedistiller': None,
          'savefig.bbox': None,
          'savefig.directory': '~',
          'savefig.dpi': 'figure',
          'savefig.edgecolor': 'auto',
          'savefig.facecolor': 'auto',
          'savefig.format': 'png',
          'savefig.orientation': 'portrait',
          'savefig.pad_inches': 0.1,
          'savefig.transparent': False,
          'scatter.edgecolors': 'face',
          'scatter.marker': 'o',
          'svg.fonttype': 'path',
          'svg.hashsalt': None,
          'svg.image_inline': True,
          'text.antialiased': True,
          'text.color': 'black',
          'text.hinting': 'force_autohint',
          'text.hinting_factor': 8,
          'text.kerning_factor': 0,
          'text.latex.preamble': '',
          'text.parse_math': True,
          'text.usetex': False,
          'timezone': 'UTC',
          'tk.window_focus': False,
          'toolbar': 'toolbar2',
          'webagg.address': '127.0.0.1',
          'webagg.open_in_browser': True,
          'webagg.port': 8988,
          'webagg.port_retries': 50,
          'xaxis.labellocation': 'center',
          'xtick.alignment': 'center',
          'xtick.bottom': True,
          'xtick.color': 'black',
          'xtick.direction': 'out',
          'xtick.labelbottom': True,
          'xtick.labelcolor': 'inherit',
          'xtick.labelsize': 'medium',
          'xtick.labeltop': False,
          'xtick.major.bottom': True,
          'xtick.major.pad': 3.5,
          'xtick.major.size': 3.5,
          'xtick.major.top': True,
          'xtick.major.width': 0.8,
          'xtick.minor.bottom': True,
          'xtick.minor.ndivs': 'auto',
          'xtick.minor.pad': 3.4,
          'xtick.minor.size': 2.0,
          'xtick.minor.top': True,
          'xtick.minor.visible': False,
          'xtick.minor.width': 0.6,
          'xtick.top': False,
          'yaxis.labellocation': 'center',
          'ytick.alignment': 'center_baseline',
          'ytick.color': 'black',
          'ytick.direction': 'out',
          'ytick.labelcolor': 'inherit',
          'ytick.labelleft': True,
          'ytick.labelright': False,
          'ytick.labelsize': 'medium',
          'ytick.left': True,
          'ytick.major.left': True,
          'ytick.major.pad': 3.5,
          'ytick.major.right': True,
          'ytick.major.size': 3.5,
          'ytick.major.width': 0.8,
          'ytick.minor.left': True,
          'ytick.minor.ndivs': 'auto',
          'ytick.minor.pad': 3.4,
          'ytick.minor.right': True,
          'ytick.minor.size': 2.0,
          'ytick.minor.visible': False,
          'ytick.minor.width': 0.6,
          'ytick.right': False})

Количество настраиваемых параметров впечатляет. Но к концу раздела мы сформируем собственный файл настроек, и вы убедитесь, насколько легко это на самом деле.

# Это обычный словарь, а значит, можно читать данные
mpl.rcParams["axes.edgecolor"]
'black'

Можно изменять данные в этом словаре. Это изменит стиль графиков глобальным образом (рубрика “Вредные советы”):

# Сделаем оси ядовито-зелёного цвета
mpl.rcParams["axes.edgecolor"] = "#40fd14"
# И утолстим их
mpl.rcParams["axes.linewidth"] = 5
# А ещё сделаем фиолетовые числовые метки на осях
mpl.rcParams["xtick.color"] = "purple"
mpl.rcParams["ytick.color"] = "purple"
# И розовую сетку
mpl.rcParams["grid.color"] = "pink"
mpl.rcParams["grid.linewidth"] = 2
mpl.rcParams["axes.grid"] = True
# Ну и жёлтый фон - куда же без него)
mpl.rcParams["axes.facecolor"] = "yellow"

# И просто построим график
plot_sincos(x);
../_images/32734f680cea7c52ff0e86090d0545ac985d8f4ca3ba3023b28673310aff4095.png

Warning

Кстати, вы изменили поведение любого стиля, т.к. изменили глобальную переменную mpl.rcParams. Это нехорошо. Нужно бы вернуть всё, как было. К счастью, сделать это в программе гораздо проще, чем в жизни:

# Если и делать такое, то строго так
plt.style.use("default")
mpl.rcParams = mpl.rcParamsDefault.copy()

Разумеется, не стоит так оформлять свои графики. Также не стоит менять оформление через изменение mpl.rcParams - это крайне неудобно в реальных проектах. Выход есть - использовать собственный файл настроек.

Файл собственных настроек стиля#

Назовём наш файл с настройками sciart.mplstyle. Это обычный текстовый файл, который определённым образом интерпретируется (считывается) пакетом matplotlib. Выглядит он просто:

# <- этот символ означает комментарий, как и в Python

# Настраиваем шрифт

# - обычный
font.family:    serif
font.size:      12.0
font.serif:     Times New Roman

# - в математических выражениях                                                     
mathtext.fontset:   stix
mathtext.bf:        serif:bold
mathtext.bfit:      serif:italic:bold
mathtext.it:        serif:italic
mathtext.rm:        serif

# Настраиваем оси

# - 2D
axes.linewidth: 1.0
axes.grid:      True
axes.labelsize: large

# - 3D
axes3d.xaxis.panecolor: (1, 1, 1, 0.5)
axes3d.yaxis.panecolor: (1, 1, 1, 0.5)
axes3d.zaxis.panecolor: (1, 1, 1, 0.5)

# Числовые метки осей
xtick.major.size:   4.0
xtick.major.width:  1.0
ytick.major.size:    4.0
ytick.major.width:   1.0

# Сетка
grid.linestyle: :

# Легенда
legend.fancybox:    False
legend.fontsize:    medium

# Слегка подстраиваем различные типы графиков
image.origin: lower
hist.bins: auto
scatter.marker: .

# Параметры сохранения по умолчанию
savefig.dpi:         300
savefig.format:      tiff
savefig.bbox:        tight

Настроек гораздо больше, указали мы далеко не все. Однако неуказанные настройки будут взяты из mpl.rcParams.

Добавьте этот файл в свой проект и с лёгкостью получайте кастомное оформление либо через контекстный стиль, либо через глобальную настройку:

# Используем контекст
with plt.style.context("sciart.mplstyle"):
    _, ax = plot_sincos(x)
    ax.set(xlabel="Аргумент $x$", ylabel="Функция $f(x)$")
    ax.legend()
    plt.show()
../_images/e31fa380ace00e9430e5c43a1c453cf85fda36cb8af2b7d6fb18456ea4b8819e.png

Important

Важно, чтобы используемый вами шрифт (в данном случае Times New Roman) был системным.

# Или через глобальную установку
plt.style.use("sciart.mplstyle")

_, ax = plot_sincos(x)
ax.set(xlabel="Аргумент $x$", ylabel="Функция $f(x)$")
ax.legend()
plt.show()
../_images/e31fa380ace00e9430e5c43a1c453cf85fda36cb8af2b7d6fb18456ea4b8819e.png

Комбинация стилей#

А что, если в качестве стиля указать несколько различных стилей?

with plt.style.context(["dark_background", "sciart.mplstyle"]):
    _, ax = plot_sincos(x)
    ax.set(xlabel="Аргумент $x$", ylabel="Функция $f(x)$")
    ax.legend()
    plt.show()
../_images/2a2ecc224c2445e17a2e44a97172aa9e958edbd3c14dc56295b30a1797de7a73.png

Чудесным образом мы получили оформление "dark_background", некоторые настройки которого перезаписаны нашим собственным стилем "sciart.mplstyle".

Но и это ещё не всё. Что насчёт чтения настроек стиля прямиком из репозитория в интернете?

Стиль из интернета#

Для того, чтобы получить стиль из любого файла .mplstyle, находящегося где-то в сети, достаточно вместо имени стиля (типа "ggplot") или пути к файлу с настройками (типа "sciart.mplstyle") указать его URL:

# Например, так
with plt.style.context("https://github.com/dhaitz/matplotlib-stylesheets/raw/master/pitayasmoothie-dark.mplstyle"):
    _, ax = plot_sincos(x)
    ax.set(xlabel="Аргумент $x$", ylabel="Функция $f(x)$")
    ax.legend()
    plt.show()
../_images/757651c4cf6cffabac4c05c4444af09e9a2eeec560ee3026e6ad71284b05afd7.png

Настолько Matplotlib функционален.

См. также#

  1. Список стандартных стилей Matplotlib.

  2. Всё о кастомизации Matplotlib…

  3. И даже больше.