пятница, 25 марта 2011 г.

Стрелки в Pylab

Pylab представляет собой очень мощную библиотеку для графопостроения. При проведении расчётов и анализа данных в Python, при помощи pylab не представляет труда вывести результат на график. Однако просто графики зачастую не являются информативными для неподготовленного читателя. В этом случае, помогают подсказки, сделанные на графиках, в частности, стрелки с указаниями. Кроме того, стрелки являются незаменимыми при построении графиков с несколькими y-осями. В этом случае весьма желательно указывать с помощью стрелок к какой оси относится тот или иной график.

Pylab.arrow

Казалось бы, при всей мощи, от pylab следовало бы ожидать удобной возможности нанесения такого рода деталей на графики. Но, надо признать, что здесь всё не очень очевидно. К примеру, имеется функция pylab.arrow, принимающая начальные координаты (x,y) и (dx,dy). Казалось бы, нет ничего проще.

import pylab as pl
import numpy as np
pl.figure(figsize=(4,4))
pl.title('pylab.arrow, square ratio')
z = np.arange(10)
pl.plot(z,z,'ro')
pl.arrow(2,2,3,4, head_width=0.5, head_length=1)
pl.show()

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

x = np.arange(0.1, 1000, 0.1)
y = np.log(x)

pl.figure(figsize=(6,4))
pl.title('pylab.arrow, different axis scale')
pl.plot(x, y, 'b-')
pl.arrow(400,2,200,3, head_width=0.5, head_length=1)
pl.arrow(200,2,300,4, head_width=0.5, head_length=100)
pl.ylim(ymin = 0)
pl.show()

Мало того, что форма стрелки искажённая, так ещё и надо вручную задавать размеры наконечника head_length и head_width под каждый масштаб.

Pylab.annotate

Однако оказалось, что есть более приличный способ отрисовки стрелок, pl.annotate. На самом деле основное предназначение pl.annotate состоит в том, чтобы отмечать стрелками заданные точки, и одновременно указывать подписи. Например, таким образом:

Для этого был использован следующий код:

pl.figure(figsize=(6,4))
pl.plot(x, y, 'b-')
pl.title('pylab.annotate example')
pl.annotate('Label', xy = (500, np.log(500)), xytext=(300,1),\
                arrowprops=dict(facecolor='red', width=0.5,\
                                    headwidth=10, shrink=0.05),\
                ha='center', va='baseline', fontsize='large')

В качестве аргументов передаются

  • строка с надписью
  • координаты точки, куда указывает стрелка
  • координаты надписи
  • свойства стрелки в ввиде словаря
  • остальные свойства, касающиеся свойств текста

Все свойства текста описаны в мануале по pylab.annotate, посмотреть который можно в интерактивном режиме питона. Например:

$ ipython
> import pylab as pl
> help(pl.annotate)

Что касается свойств стрелки, то их можно посмотреть так:

$ ipython
> import matplotlib
> help(matplotlib.lines.Line2D)

Из основных характеристик стрелок, на которые следует обратить внимание, отмечу следующие:

  • facecolor — цвет наконечника
  • width — толщина линии в пунктах
  • headwidth — ширина наконечника в пунктах
  • frac — доля длины, которую занимает наконечник
  • shrink — отступ, от заданной точки xy до конца наконечника (чтобы наконечник не втыкался точно в заданную точку, а оставил некоторое пространство). Измеряется в долях длины.

Что касается свойств текста, то я бы в первую очередь отметил следующие:

  • ha (horizontalalignment) — способ выравнивания по горизонтали ('center', 'right', 'left')
  • va (verticalalignment) — соответственно, по вертикали ('center', 'top', 'bottom', 'baseline')
  • rotation — поворот текста (угол в градусах, 'vertical', 'horizontal')
  • fontsize — размер шрифта (можно указать в пунктах, а можно и словами 'small', 'medium', 'large', 'x-large', 'xx-large')

Как видно, при использовании pylab.annotate форма стрелки получилась адекватной. А если вместо надписи оставить пустую строку, то просто получится стрелка от xytext до xy (при условии, что shrink = 0)

Лично мне весьма неудобно выписывать все свойства arrowprops всякий раз, когда надо нарисовать стрелку, поэтому я нацарапал простейшую функцию, включающую в себя обязательные аргументы, такие как строка с надписью и координаты начальной и конечной точек, а также необязательные, касающиеся характеристик стрелок и текста:

def my_arrow(label, xy_from, xy_to, color='blue', shrink=0.05, width=0.5, headwidth=10):
    pl.annotate(label, xy = xy_to, xytext=xy_from,\
                    arrowprops=dict(facecolor=color, width=width,\
                                        shrink=shrink, frac=0.1),\
                    ha='center', va='baseline', fontsize='large')

Теперь нет ничего проще, нарисовать стрелку:

pl.figure(figsize=(6,4))
pl.title('pylab.annotate')
pl.plot(x, y, 'b-')
my_arrow('Label', (100,1), (600, 5))
my_arrow('', (300,1), (800, 5))
# красные точки для понимания каким образом позиционируется текст
pl.plot([100,600],[1,5], 'ro')
pl.ylim(ymin = 0)

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

Стоит заметить, что этим вид стрелок не ограничивается. Можно также рисовать стрелки с рюшечками, стоит только в arrowprops упомянуть про arrowstyle.

pl.figure(figsize=(6,4))
pl.title('Fancy arrow')
pl.plot(x, y, 'b-')
pl.annotate('Fancy arrow', xy = (500, np.log(500)), xytext=(300,1),\
                arrowprops=dict(facecolor='red',arrowstyle='fancy',\
                           connectionstyle='angle3, angleA=0, angleB=90'),\
                fontsize='x-large')
pl.ylim(ymin = 0)

Для более детального ознакомления следует посмотреть в примеры mpl_examples/pylab_examples/annotation_demo2.py

Копируете статью - поставьте ссылку на оригинал!

1 комментарий:

  1. спасибо за информацию. строю векторные диаграммы с помощью matplotlib, пришлось повозиться с этими стрелками.

    ОтветитьУдалить