Выровнять маркер разметки matplotlib слева и справа

Я использую функцию matplotlib чтобы создать внешний вид ручек на вертикальных линиях, чтобы очертить определенные части графика. Однако, чтобы заставить их выглядеть правильно, мне нужно уметь выравнивать маркер диаграммы рассеяния слева (для левой линии / ограничителя) и / или вправо (для правой линии / ограничителя).

Вот пример:

 #create the figure fig = plt.figure(facecolor = '#f3f3f3', figsize = (11.5, 6)) ax = plt. ax = plt.subplot2grid((1, 1), (0,0)) #make some random data index = pandas.DatetimeIndex(start = '01/01/2000', freq = 'b', periods = 100) rand_levels = pandas.DataFrame( numpy.random.randn(100, 4)/252., index = index, columns = ['a', 'b', 'c', 'd']) rand_levels = 100*numpy.exp(rand_levels.cumsum(axis = 0)) ax.stackplot(rand_levels.index, rand_levels.transpose()) #create the place holder for the vertical lines d1, d2 = index[25], index[50] #draw the lines ymin, ymax = ax.get_ylim() ax.vlines([index[25], index[50]], ymin = ymin, ymax = ymax, color = '#353535', lw = 2) #draw the markers ax.scatter(d1, ymax, clip_on = False, color = '#353535', marker = '>', s = 200, zorder = 3) ax.scatter(d2, ymax, clip_on = False, color = '#353535', marker = '<', s = 200, zorder = 3) #reset the limits ax.set_ylim(ymin, ymax) ax.set_xlim(rand_levels.index[0], rand_levels.index[-1]) plt.show() 

Приведенный выше код дает мне почти график, который я ищу, например:

focus_timeline

Тем не менее, я бы хотел, чтобы крайний левый маркер («>») был «выровнен влево» (т.е. слегка сдвинут вправо), чтобы линия продолжалась до конца маркера. Аналогично, мне нужен самый правый маркер ( «<») «выравниваться вправо» (т.е. слегка сдвигается влево). Как это:

desired_fig

Какие-либо рекомендации или предложения о том, как это сделать гибким образом?

ПРИМЕЧАНИЕ. На практике мой индекс DataFrame представляет собой pandas.Datetime не целые числа, как я представил для этого простого примера.

Мне понравился этот вопрос и я не был доволен своим первым ответом. В частности, для выравнивания маркеров казалось излишне громоздким создавать конкретные объекты ( mark_align_* ). В конечном итоге я нашел функциональность для указания маркера по verts (список 2-элементных поплавков или массив Nx2, который указывает вершины маркера относительно целевой точки в точке (0, 0) ). Чтобы использовать эту функциональность для этой цели, я написал эту функцию,

 from matplotlib import markers from matplotlib.path import Path def align_marker(marker, halign='center', valign='middle',): """ create markers with specified alignment. Parameters ---------- marker : a valid marker specification. See mpl.markers halign : string, float {'left', 'center', 'right'} Specifies the horizontal alignment of the marker. *float* values specify the alignment in units of the markersize/2 (0 is 'center', -1 is 'right', 1 is 'left'). valign : string, float {'top', 'middle', 'bottom'} Specifies the vertical alignment of the marker. *float* values specify the alignment in units of the markersize/2 (0 is 'middle', -1 is 'top', 1 is 'bottom'). Returns ------- marker_array : numpy.ndarray A Nx2 array that specifies the marker path relative to the plot target point at (0, 0). Notes ----- The mark_array can be passed directly to ax.plot and ax.scatter, eg:: ax.plot(1, 1, marker=align_marker('>', 'left')) """ if isinstance(halign, (str, unicode)): halign = {'right': -1., 'middle': 0., 'center': 0., 'left': 1., }[halign] if isinstance(valign, (str, unicode)): valign = {'top': -1., 'middle': 0., 'center': 0., 'bottom': 1., }[valign] # Define the base marker bm = markers.MarkerStyle(marker) # Get the marker path and apply the marker transform to get the # actual marker vertices (they should all be in a unit-square # centered at (0, 0)) m_arr = bm.get_path().transformed(bm.get_transform()).vertices # Shift the marker vertices for the specified alignment. m_arr[:, 0] += halign / 2 m_arr[:, 1] += valign / 2 return Path(m_arr, bm.get_path().codes) 

Используя эту функцию, желаемые маркеры можно изобразить как,

 ax.plot(d1, 1, marker=align_marker('>', halign='left'), ms=20, clip_on=False, color='k', transform=ax.get_xaxis_transform()) ax.plot(d2, 1, marker=align_marker('<', halign='right'), ms=20, clip_on=False, color='k', transform=ax.get_xaxis_transform()) 

или используя ax.scatter ,

 ax.scatter(d1, 1, 200, marker=align_marker('>', halign='left'), clip_on=False, color='k', transform=ax.get_xaxis_transform()) ax.scatter(d2, 1, 200, marker=align_marker('<', halign='right'), clip_on=False, color='k', transform=ax.get_xaxis_transform()) 

В обоих этих примерах я указал transform=ax.get_xaxis_transform() чтобы вертикальное положение маркеров transform=ax.get_xaxis_transform() в координатах осей ( 1 – верхняя часть осей), это не имеет никакого отношения к выравниванию маркера.

Очевидным преимуществом этого решения по сравнению с моим предыдущим является то, что он не требует знания маркеров , функции построения ( ax.plot против ax.scatter ) или осей (для преобразования). Вместо этого просто указывается маркер и его выравнивание!

Ура!

Одним из решений было бы использовать mpl.transforms и входной параметр transform в ax.scatter или ax.plot . В частности, я бы начал с добавления,

 from matplotlib import transforms as tf 

В этом подходе я использую tf.offset_copy для создания маркеров, смещенных на половину их размера. Но каков размер маркеров? Оказывается, что ax.scatter и ax.plot определяют размеры маркеров по-разному. См. Этот вопрос для получения дополнительной информации.

  1. Параметр s= input для ax.scatter указывает размеры маркеров в точках ^ 2 (т. ax.scatter Это площадь квадрата, в который помещаются маркеры).

  2. Входной параметр ax.plot в ax.plot указывает ширину и высоту маркеров в точках (т. ax.plot Ширину и высоту квадрата, в который помещаются маркеры).

Использование ax.scatter

Итак, если вы хотите ax.scatter свои маркеры с помощью ax.scatter вы можете сделать,

 ms_scatter = 200 # define markersize mark_align_left_scatter = tf.offset_copy(ax.get_xaxis_transform(), fig, ms_scatter ** 0.5 / 2, units='points') mark_align_right_scatter = tf.offset_copy(ax.get_xaxis_transform(), fig, -ms_scatter ** 0.5 / 2, units='points') 

Здесь я использовал ax.get_xaxis_transform , который представляет собой преобразование, которое помещает точки в координаты данных вдоль оси x, но в координатах осей (от 0 до 1) по оси y. Таким образом, вместо использования ymax , я могу разместить точку в верхней части графика с помощью 1 . Кроме того, если я панорамирую или увеличиваю цифру, маркеры все равно будут наверху! Как только я определил новые преобразования, я назначаю их свойству transform когда я вызываю ax.scatter ,

 ax.scatter(d1, 1, s=ms_scatter, marker='>', transform=mark_align_left_scatter, clip_on=False, color='k') ax.scatter(d2, 1, s=ms_scatter, marker='<', transform=mark_align_right_scatter, clip_on=False, color='k') 

Использование ax.plot

Поскольку это несколько проще, я бы, вероятно, использовал ax.plot . В таком случае я бы сделал,

 ms = 20 mark_align_left = tf.offset_copy(ax.get_xaxis_transform(), fig, ms / 2, units='points') mark_align_right = tf.offset_copy(ax.get_xaxis_transform(), fig, -ms / 2, units='points') ax.plot(d1, 1, marker='>', ms=ms, transform=mark_align_left, clip_on=False, color='k') ax.plot(d2, 1, marker='<', ms=ms, transform=mark_align_right, clip_on=False, color='k') 

Последние комментарии

Вы можете создать оболочку, чтобы сделать преобразование mark_align_* проще, но я оставлю это для вас, если вы захотите.

Если вы используете ax.scatter или ax.plot ваш выходной график будет выглядеть примерно так:

Конечная цифра

Не самое изящное решение, но если я правильно понимаю ваш вопрос, вычитание и добавление одного из / в d1 и d2 соответственно должно сделать это:

 ax.scatter(d1-1, ymax, clip_on = False, color = '#353535', marker = '>', s = 200, zorder = 3) ax.scatter(d2+1, ymax, clip_on = False, color = '#353535', marker = '<', s = 200, zorder = 3)