Использование библиотеки Matplotlib. Как рисовать стрелки на графиках и добавлять аннотации
Иногда на графиках нужно нарисовать указатель на какую-то особую точку, на которую стоит обратить внимание, или указательные линии с номерами кривых могут служить заменой легенды. Для того, чтобы рисовать такие стрелки и линии, предназначена функция annotate. Кроме того, эту функцию можно использовать для отображения векторов на плоскости.
Функция annotate может принимать большое количество параметров, отвечающих за внешний вид стрелок и отображаемого текста. Строго говоря, обязательными являются только два параметра:
- s - строка, содержащая выводимый текст;
- xy - кортеж из двух элементов, задающие координаты точки, на которую нужно указать. По умолчанию координаты точек задаются в системе координат графика, но это поведение можно менять с помощью параметра xycoords, который в данной статье не рассматривается.
Однако, если мы ограничимся только этими параметрами, то функция annotate выведет только текст около указанной точки:
import pylab
import numpy
if __name__ == '__main__':
def sinc (x):
return numpy.sin (x) / x
# Интервал изменения и шаг переменной по оси X
xmin = -30.0
xmax = 30.0
dx = 0.01
# Создадим список координат по осям X и Y на отрезке [-xmin; xmax], включая концы
x = numpy.arange (xmin, xmax, dx)
y = sinc(x)
# Нарисуем график
pylab.plot (x, y)
# Координаты точки, куда будет указывать стрелка
selected_x = 7.7
selected_y = sinc (selected_x)
# Установим шрифт, который может отображать русские буквы
pylab.rc('font', family = 'verdana')
# !!! Добавление аннотации
pylab.annotate (u'Очень важная точка',
xy=(selected_x, selected_y))
# Покажем окно с нарисованным графиком
pylab.show()
Результат будет напоминать работу функции text:

Чтобы аннотация выглядела более интересно, добавим два именованных параметра:
- xytext - кортеж из двух элементов, который задает координаты текста; из этой же точки будет начинаться стрелка указателя;
- arrowprops - словарь, который содержит элементы, задающие внешний вид стрелки.
Чуть позже мы подробнее рассмотрим возможные значения в словаре arrowprops, а пока сделаем так, чтобы у нас отображалась обычная стрелка:
import pylab
import numpy
if __name__ == '__main__':
def sinc (x):
return numpy.sin (x) / x
# Интервал изменения и шаг переменной по оси X
xmin = -30.0
xmax = 30.0
dx = 0.01
# Создадим список координат по осям X и Y на отрезке [-xmin; xmax], включая концы
x = numpy.arange (xmin, xmax, dx)
y = sinc(x)
# Нарисуем график
pylab.plot (x, y)
# Координаты точки, куда будет указывать стрелка
selected_x = 7.7
selected_y = sinc (selected_x)
pylab.rc('font', family = 'verdana')
# !!! Словарь задает внешний вид стрелки
arrowprops = {
'arrowstyle': '->',
}
# !!! Добавление аннотации
pylab.annotate (u'Очень важная точка',
xy=(selected_x, selected_y),
xytext = (selected_x + 2, selected_y + 0.1),
arrowprops = arrowprops)
# Покажем окно с нарисованным графиком
pylab.show()
Теперь окно с графиком будет выглядеть следующим образом:

Если говорить о внутреннем устройстве Matplotlib, то функция annotate для отображения стрелок использует один из двух классов: FancyArrowPatch, если словарь arrowprops содержит ключ "arrowstyle" (как в предыдущем примере), или класс YAArrow, если такого ключа в словаре нет. На мой взгляд стрелки, которые рисуются с помощью FancyArrowPatch, по умолчанию выглядят более привлекательно.
С помощью элементов словаря arrowprops мы можем передавать множество настроек в класс FancyArrowPatch. Коротко рассмотрим только некоторые из них:
- arrowstyle - задает стиль стрелок (список возможных значений будет приведен ниже).
- connectionstyle - задает способ проведения линии указателя, будет она соединять две точки по прямой, по дуге или нужно, чтобы стрелки состояли из ломанных отрезков, расположенных параллельно координатным осям. С помощью этого параметра можно задавать и другие настройки линии.
- linewidth - задает толщину линии.
- linestyle - задает стиль линии. Возможные значения - 'solid', 'dashed', 'dashdot', '‘dotted'.
- mutation_scale - задает размер "головы" стрелки на конце.
Если словарь arrowprops не содержит ключ "arrowstyle", то для отображения стрелки будет использоваться класс YAArrow. Этот класс будет использовать следующие ключи в словаре arrowprops:
- width - задает ширину линии указателя.
- frac - коэффициент, показывающий, какую часть длины указателя будет занимать "голова" стрелки. Например, если frac = 0.5, то стрелка будет занимать половину длины указателя.
- headwidth - задает ширину "головы" стрелки.
Кроме того, независимо от используемого класса FancyArrowPatch / YAArrow, можно задавать следующие параметры:
- color - задает цвет указателя.
- alpha - задает степень прозрачности. 0 - полностью прозрачный, 1 - полностью непрозрачный.
Если используется параметр arrowstyle, то он может быть экземпляром класса ArrowStyle, который позволяет очень гибко настраивать внешний вид стрелок, а может быть строкой, описывающей уже предустановленные стили стрелок. В качестве строки arrowstyle может принимать следующие значения:
'-' |
'->' |
'-[' |
'-|>' |
'<-' |
'<->' |
'<|-' |
'-' |
']-' |
']-[' |
'fancy' |
'simple' |
'wedge' |
'|-|' |
Чтобы продемонстрировать, как выглядят эти стили, был написан следующий скрипт:
import pylab
if __name__ == '__main__':
pylab.rc('font', family = 'verdana')
pylab.xlim ([-10, 50])
pylab.ylim ([-180, 20])
arrowstyles = ['-', '->', '-[', '-|>',
'<-', '<->', '<|-', '<|-|>',
']-', ']-[', 'fancy', 'simple',
'wedge', '|-|']
shift_x = 2
shift_y = -12
dx = 10
dy = 10
for n, arrowstyle in enumerate (arrowstyles):
# !!! Словарь задает внешний вид стрелки
arrowprops = {
'arrowstyle': arrowstyle,
}
x = n * shift_x
y = n * shift_y
# !!! Добавление аннотации
pylab.annotate (u'arrowstyle: ' + arrowstyle,
xy=(x, y),
xytext = (x + dx, y + dy),
arrowprops = arrowprops)
# Покажем окно с нарисованным графиком
pylab.show()
С помощью него была получена следующая картинка:

Кроме того, мы можем влиять на то, каким образом будет проводиться линия от начальной точки до конечной: будет это прямая линия, ломаная или закругленная. На это влияет параметр connectionstyle, который может быть экземпляром класса ConnectionStyle для очень точной настройки внешнего вида, а может быть одной из строк, с помощью которых можно задать один из предустановленных внешних видов указателей. Вот эти возможные строки:
'angle' |
'angle3' |
'arc' |
'arc3' |
'bar' |
Цифра "3" в конце некоторых стилей обозначают, что для проведения линии используется квадратичный сплайн, то есть линия проводится через три точки. Чтобы продемонстрировать эти стили, был написан следующий скрипт:
import pylab
if __name__ == '__main__':
pylab.rc('font', family = 'verdana')
pylab.xlim ([-10, 50])
pylab.ylim ([-190, 40])
connectionstyle = ['angle', 'angle3', 'arc', 'arc3', 'bar']
shift_x = 2
shift_y = -45
dx = 10
dy = 20
for n, style in enumerate (connectionstyle):
# !!! Словарь задает внешний вид стрелки
arrowprops = {
'arrowstyle': '->',
'connectionstyle': style,
}
x = n * shift_x
y = n * shift_y
# !!! Добавление аннотации
pylab.annotate (u'connectionstyle: ' + style,
xy=(x, y),
xytext = (x + dx, y + dy),
arrowprops = arrowprops)
# Покажем окно с нарисованным графиком
pylab.show()
Результат его работы выглядит следующим образом:

В этом примере стили arc и arc3 не отличаются, потому что мы не воспользовались дополнительными параметрами для каждого стиля. Дополнительные параметры задаются через запятую после имени стиля, причем у каждого стиля свой набор параметров. Ниже приведена таблица с параметрами по умолчанию, взятая из документации. Там же видно, какие параметры существуют для каждого стиля.
'angle' | angleA=90,angleB=0,rad=0.0 |
'angle3' | angleA=90,angleB=0 |
'arc' | angleA=0,angleB=0,armA=None,armB=None,rad=0.0 |
'arc3' | rad=0.0 |
'bar' | armA=0.0,armB=0.0,fraction=0.3,angle=None |
Для демонстрации того, как задаются параметры стилей, изменим предыдущий пример:
import pylab
if __name__ == '__main__':
pylab.rc('font', family = 'verdana')
pylab.xlim ([-10, 50])
pylab.ylim ([-190, 40])
connectionstyle = ['angle,angleA=0,angleB=45',
'angle3,angleA=45,angleB=0',
'arc,angleA=-90,armA=30,armB=80',
'arc3,rad=0.5',
'bar,armA=15,fraction=0.1']
shift_x = 2
shift_y = -45
dx = 10
dy = 20
for n, style in enumerate (connectionstyle):
# !!! Словарь задает внешний вид стрелки
arrowprops = {
'arrowstyle': '->',
'connectionstyle': style,
}
x = n * shift_x
y = n * shift_y
# !!! Добавление аннотации
pylab.annotate (u'connectionstyle: ' + style,
xy=(x, y),
xytext = (x + dx, y + dy),
arrowprops = arrowprops)
# Покажем окно с нарисованным графиком
pylab.show()
Не будем приводить здесь описание каждого параметра, потому что проще всего их "пощупать" самим, чтобы понять, что они делают. Результат данного скрипта выглядит следующим образом:

Функцию annotate можно использовать для отображения векторов на плоскости, для этого достаточно в качестве первого параметра передать пустую строку.
import pylab
import numpy
if __name__ == '__main__':
pylab.rc('font', family = 'verdana')
pylab.xlim ([-1, 10])
pylab.ylim ([-1, 10])
a = numpy.array ([1, 3])
b = numpy.array ([5, 2])
c = a + b
arrowprops = {
'arrowstyle': '<|-',
}
# !!! Добавление аннотации
pylab.annotate (u'',
xy=(0, 0),
xytext = a,
arrowprops = arrowprops)
pylab.annotate (u'',
xy=(0, 0),
xytext = b,
arrowprops = arrowprops)
pylab.annotate (u'',
xy=(0, 0),
xytext = c,
arrowprops = arrowprops)
# Покажем окно с нарисованным графиком
pylab.grid()
pylab.show()

С другой стороны, можно воспользоваться тем фактом, что в строке (в первом параметре) может использоваться нотация LaTeX, и мы можем отображать названия векторов.
import pylab
import numpy
if __name__ == '__main__':
pylab.rc('font', family = 'verdana')
pylab.xlim ([-1, 10])
pylab.ylim ([-1, 10])
a = numpy.array ([1, 3])
b = numpy.array ([5, 2])
c = a + b
arrowprops = {
'arrowstyle': '<|-',
}
# !!! Добавление аннотации
pylab.annotate (u'$\\vec a$',
xy=(0, 0),
xytext = a,
arrowprops = arrowprops)
pylab.annotate (u'$\\vec b$',
xy=(0, 0),
xytext = b,
arrowprops = arrowprops)
pylab.annotate (u'$\\vec c = \\vec a + \\vec b$',
xy=(0, 0),
xytext = c,
arrowprops = arrowprops)
# Покажем окно с нарисованным графиком
pylab.grid()
pylab.show()

В этой статье были рассмотрены основные параметры, влияющие на внешний вид указателей, отображаемых с помощью функции annotate. Какие-то параметры не попали в статью, но они все описаны в документации, хоть и разбросаны по разным разделам. В конце приведу ссылки на документацию, где можно найти больше информации.
- http://matplotlib.org/users/annotations_intro.html
- http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.annotate
- http://matplotlib.org/api/lines_api.html#matplotlib.lines.Line2D
- http://matplotlib.org/api/patches_api.html#matplotlib.patches.YAArrow
- http://matplotlib.org/api/patches_api.html#matplotlib.patches.FancyArrowPatch
Вы можете подписаться на новости сайта через RSS, Группу Вконтакте или Канал в Telegram.