Построение лицеприятных графиков в matplotlib сопряжено с рядом технических трудностей. Для того, чтобы график был хорошо читаем приходится настраивать большое количество параметров, как то шрифт подписей, толщину линий ошибки, легенду, частоту подписей осей, шаг сетки и многое другое. В данной статье подробно разбирается построение одного графика, на примере которого Вы можете составить свой собственный скрипт.

Разберем пример построения графика распределения концентраций и электростатического потенциала в мембране DPPC на границе фосфолипидный бислой-вода в присутствии адсорбированных катионов Be2+ [Нестеренко, и др., 2011]. Выглядит этот график следующим образом:

График локальных концентрации и потенциала на границе раздела липидный бислой-вода
Результат работы скрипта, разобранного в данной статье.

Включаем псевдокод для оболочки *nix (#!), чтобы можно было запускать ./script.py, не забываем устанавливать кодировку, если хотим использовать русские заголовки.

#!/usr/bin/python
# -*- coding: utf-8 -*-

Стандартный набор заголовков для работы в среде numpy.

from numpy import *
from matplotlib.pyplot import *
from matplotlib.mlab import *

Заголовки для рисования на панели графика.

from matplotlib.lines import Line2D

Устанавливаем глобальные шрифты и прочие настройки. Переменные rc можно посмотреть в matplotlibrc-файле, например, здесь.

rc('font',**{'family':'sans-serif','sans-serif':['DejavuSansMono']})

Заранее определим используемые при построении графиков константы:

Tcurr = 2.77
_eopt = 80.0
_k = 1.38e-16
_T = 340
_e = 4.8e-10
_Na = 6.022e+23
__cinf = +240.0  # infinite conc., mol*m^-3
__x0   = +10.0  # surface position, Ang

Для задания функций для рисования теоретических кривых удобно пользоваться лямбда-функциями, как это сделано здесь:

newpsi   = lambda x,eps: -2*_k*_T/_e * \
 log(cos(sqrt(2*pi*_e**2*_Na*__cinf*1.e-6/eps/_k/_T)*(x-17)*1.e-8))
newconc  = lambda x,eps: __cinf*exp(newpsi(x,eps)*_e/_k/_T)

Для задание табулированной переменной x удобно пользоваться функцией linspace(), которая берет начальную, конечную координаты и количество точек. Для логарифмической шкалы есть соответствующая функция — logspace(). Блок загрузки данных и определения доп. констант здесь не комментируем.

x = linspace(10, 16, 50)
y = newconc(x,40)
print x,'\n',newpsi(x,20)

p1 = loadtxt('becl_fiii.xvg')
d1 = loadtxt('becl_gdens.xvg')

mna = 1.e3/22.989
mcl = 1.e3/35.452
na2mM = 1.e6/22.989
cl2mM = 1.e6/35.452
F = 96485.3 #C/mol

Определить, какой номер экспериментальной точке наиболее близко расположен к числу 12 можно при помощи функции searchsorted() в том случае, если массив уже отсортирован. В данном случае d1[:,0] — это отсортированный массив X-координат.

x_s = searchsorted(d1[:,0], 12.)
print 'Adsorbed surface charge, uC/cm2'

Для численного интегрирования поточечной функции наиболее часто пользуются функцией trapz(), осуществляющей интегрирование методом трапеций. На вход она получает массивы X и Y координат точек.

print '+',trapz(2*d1[:x_s,5]*na2mM-d1[:x_s,7]*cl2mM, d1[:x_s,0]*1.e-8)*F

Мы начинаем процедуры по выводу изображения с функции figure(), которая создает пустое окно и функции axes(), создающей в этом окне координатную сетку. Если обе эти функции пропустить, то они будут запущены автоматически при первом вызове функции plot() или ее аналогов, но запрашивая их явно, мы можем получить доступ к соответствующим указателям. Также это рекомендуется делать для повышения читаемости кода.

figure()
a1 = axes()

Вызов текста осуществляется функцией text(), причем координаты задаются на тех осях, в которых рисуются графики. Последующий вызов функции xlim() не изменит положения текста. В данном случае text.usetex установлен True в matplotlibrc, поэтому можно сразу писать ‘\em la-la’ и текст ‘la-la’ будет выведен курсивом.

text(-0.7, 0.45, u'\em а')

Для рисования графиков с «усами» можно пользоваться функцией errorbar(), ошибки этой функции подавать нужно в параметр yerr. У этой функции есть отдельно настройки для отображения разброса и основной кривой.

lns1 = errorbar(p1[:,0], p1[:,1], yerr=p1[:,2]*Tcurr, fmt='-k', lw=2,\
 label=u'$\\Psi$')

Продолжаем использовать функции векторного рисования: заполняем прямоугольник полупрозрачным серым цветом.

fill_between([-1,1], [0.5,0.5] , -0.025, color='#c0c0c0', alpha=0.5)
ylim(-0.025,0.5)

Ключевое событие: наложение новой координатной сетки функцией twinx(). Можно перевести название функции как «Раздвоение с сохранением оси X». Все последующие графики будут рисоваться на той же оси X, но ось Y будет уже другой. По умолчанию она отобразится справа.

a2 = twinx()
xlim(0, 1.6)

Мы можем устанавливать параметры координатных сеток отдельно, обращаясь к указателям на них напрямую. Напомним, что указатель на объект изначальной координатной сетки можно получить функцией axes(), как это делалось выше. Отметим, что ylim нужно подобрать так, чтобы потом сетки (grid) обоих наложенных координатных осей не оказались черезстрочными. Обычно это делается вручную.

a2.set_ylim(-0.05,1)
a2.set_ylabel(u'Концентрация ионов, М')

Полного отключения подписей на одной из осей можно добиться, если раскомментировать этот код:

#a2.tick_params(axis='y', labelright=False);

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

lns2 = errorbar(d1[:,0]/10., d1[:,5]*mna, yerr=d1[:,6]*mna*Tcurr, fmt='sw',\
 ecolor='black', label=u'[Be$^{2+}$]', ms=10)
lns3 = errorbar(d1[:,0]/10., d1[:,7]*mcl, yerr=d1[:,8]*mcl*Tcurr, fmt='ow',\
 ecolor='black', label=u'[Cl$^-$]', ms=10)
lnst = plot(x/10.,y/1.e3, '-', lw=1.5,color='#707070', label=u'Теор. [CL$^-$]')
a1.set_xlabel(u'Расстояние от C$_\\alpha$')
a1.set_ylabel(u'Электростатический потенциал $\\Psi$, В')

Масштаб оси Y был подобран так, чтобы засечки на левой и правой осях оказались друг на против друга. В этом случае мы можем включать grid() на исходной координатной сетке.

a1.grid()
title(u'DPPC + BeCl$_2$')

Рисования линий, ломанных многоугольников и других фигур использует интерфейс artist. Мы создаем объект фигуры при помощи соответствующей функции, в данном случае — Line2D(), и добавляем эту фигуру на координатную сетку:

l = Line2D([1,1],[-0.025,0.5],color='k', linestyle='--', lw=1.5)
a1.add_artist(l)

Если легенду вывести отдельно для сетки a1 и a2, выведется две легенды. Обычно это бессмысленно, поэтому ниже предлагается решение, как объединить две легенды в одну. Для это выше мы запомнили указатели на объекты графиков (plot) и графиков с ошибкой (errorbar). Разница состоит в том, что plot возвращает список указателей на объекты графиков из одного элемента, а errorbar — сам указатель. Поэтому мы объединяем все графики в массив вот так:

lns = [lns1]+[lns2]+[lns3] + lnst

Получаем массив названий для легенды:

labs = [l.get_label() for l in lns]

И выводим, наконец, легенду. Если мы хотим задать особые параметры шрифта (это касается не только легенды), то можно использовать объект типа FontProperties. Для его создания есть стандартный конструктор FontProperties(). Указатель на объект мы передаем в функцию рисования легенды:

_fp = matplotlib.font_manager.FontProperties(size=11)
a2.legend(lns, labs, loc=1, prop=_fp)

Каждый скрипт, выводящий на экран мы заканчиваем командой:

show()

Вы можете попробовать запустить данный скрипт на своем компьютере [архив со скриптом и исходными данными] и попробовать варьировать какие-то локальные параметры.