Встроенные метки в Matplotlib
в Matplotlib не слишком сложно сделать легенду (example_legend()
, ниже), но я думаю, что лучше поместить метки прямо на кривые, которые строятся (как в example_inline()
ниже). Это может быть очень сложно, потому что я должен указать координаты вручную, и, если я переформатирую сюжет, мне, вероятно, придется переместить метки. Есть ли способ автоматически генерировать метки на кривых в Matplotlib? Бонусные баллы за возможность ориентировать текст под углом, соответствующим углу кривая.
import numpy as np
import matplotlib.pyplot as plt
def example_legend():
plt.clf()
x = np.linspace(0, 1, 101)
y1 = np.sin(x * np.pi / 2)
y2 = np.cos(x * np.pi / 2)
plt.plot(x, y1, label='sin')
plt.plot(x, y2, label='cos')
plt.legend()
def example_inline():
plt.clf()
x = np.linspace(0, 1, 101)
y1 = np.sin(x * np.pi / 2)
y2 = np.cos(x * np.pi / 2)
plt.plot(x, y1, label='sin')
plt.plot(x, y2, label='cos')
plt.text(0.08, 0.2, 'sin')
plt.text(0.9, 0.2, 'cos')
3 ответов
хороший вопрос, некоторое время назад я немного экспериментировал с этим, но не использовал его много, потому что он все еще не пуленепробиваемый. Я разделил область участка на сетку 32x32 и рассчитал "потенциальное поле" для наилучшего положения метки для каждой строки в соответствии со следующими правилами:
- пробел-хорошее место для метки
- метка должна быть рядом с соответствующей строкой
- метка должна быть вдали от других строк
в код был примерно такой:
import matplotlib.pyplot as plt
import numpy as np
from scipy import ndimage
def my_legend(axis = None):
if axis == None:
axis = plt.gca()
N = 32
Nlines = len(axis.lines)
print Nlines
xmin, xmax = axis.get_xlim()
ymin, ymax = axis.get_ylim()
# the 'point of presence' matrix
pop = np.zeros((Nlines, N, N), dtype=np.float)
for l in range(Nlines):
# get xy data and scale it to the NxN squares
xy = axis.lines[l].get_xydata()
xy = (xy - [xmin,ymin]) / ([xmax-xmin, ymax-ymin]) * N
xy = xy.astype(np.int32)
# mask stuff outside plot
mask = (xy[:,0] >= 0) & (xy[:,0] < N) & (xy[:,1] >= 0) & (xy[:,1] < N)
xy = xy[mask]
# add to pop
for p in xy:
pop[l][tuple(p)] = 1.0
# find whitespace, nice place for labels
ws = 1.0 - (np.sum(pop, axis=0) > 0) * 1.0
# don't use the borders
ws[:,0] = 0
ws[:,N-1] = 0
ws[0,:] = 0
ws[N-1,:] = 0
# blur the pop's
for l in range(Nlines):
pop[l] = ndimage.gaussian_filter(pop[l], sigma=N/5)
for l in range(Nlines):
# positive weights for current line, negative weight for others....
w = -0.3 * np.ones(Nlines, dtype=np.float)
w[l] = 0.5
# calculate a field
p = ws + np.sum(w[:, np.newaxis, np.newaxis] * pop, axis=0)
plt.figure()
plt.imshow(p, interpolation='nearest')
plt.title(axis.lines[l].get_label())
pos = np.argmax(p) # note, argmax flattens the array first
best_x, best_y = (pos / N, pos % N)
x = xmin + (xmax-xmin) * best_x / N
y = ymin + (ymax-ymin) * best_y / N
axis.text(x, y, axis.lines[l].get_label(),
horizontalalignment='center',
verticalalignment='center')
plt.close('all')
x = np.linspace(0, 1, 101)
y1 = np.sin(x * np.pi / 2)
y2 = np.cos(x * np.pi / 2)
y3 = x * x
plt.plot(x, y1, 'b', label='blue')
plt.plot(x, y2, 'r', label='red')
plt.plot(x, y3, 'g', label='green')
my_legend()
plt.show()
и результирующий график:
обновление: пользователей cphyc любезно создал репозиторий Github для кода в этом ответе (см. здесь), и в комплекте код в пакет, который может быть установлен с помощью pip install matplotlib-label-lines
.
Красивая Картинка:
на matplotlib
Это довольно легко ярлык изолиниями (либо автоматически, либо вручную размещая метки щелчками мыши). Там не представляется (пока) какой-либо эквивалентной возможностью для маркировки рядов данных таким образом! Возможно, есть какая-то семантическая причина не включать эту функцию, которую я упускаю.
независимо от того, я написал следующий модуль, который принимает любые допуски для полуавтоматической маркировки участка. Требуется только numpy
и несколько функций из стандартного math
библиотека.
описание
поведение по умолчанию labelLines
функция к космосу метки равномерно вдоль x
axis (автоматическое размещение на правильном y
-стоимость курса). Если вы хотите, вы можете просто передать массив координат x каждой из меток. Вы даже можете настроить местоположение одной метки (как показано на нижнем правом графике), а остальное пространство равномерно, если хотите.
кроме того,label_lines
функция не учитывает строки, которые не имели метки, назначенной в plot
команда (или точнее, если метка содержит '_line'
).
аргументы ключевого слова, переданные в labelLines
или labelLine
на text
вызов функции (некоторые ключевые аргументы имеют значение, если вызывающий код выбирает не указывать).
вопросы
- ограничители аннотаций иногда нежелательно мешают другим кривым. Как показано
1
и10
аннотации в левом верхнем углу графика. Я даже не уверен, что этого можно избежать. - было бы неплохо указать
y
позицию, а не иногда. - это все еще итеративный процесс, чтобы получить аннотации в правильном месте
- это работает только тогда, когда
x
-ось значенийfloat
s
Gotchas
- по умолчанию
labelLines
функция предполагает, что все ряды данных охватывают диапазон, заданный пределами оси. Взгляните на синюю кривую в левом верхнем углу симпатичной картинки. Если бы были доступны только данныеx
ряд0.5
-1
тогда мы не могли бы разместить метку в нужном месте (что немного меньше, чем0.2
). См.этот вопрос для особенно неприятного примера. Прямо сейчас код разумно не идентифицирует этот сценарий и не переупорядочивает метки, однако существует разумное обходное решение. Функция labelLines принимает
@января Kuiken, конечно, хорошо продуманной и тщательной, но есть некоторые оговорки:
- это не работает во всех случаях
- для этого требуется достаточное количество дополнительного кода
- он может значительно отличаться от одного сюжета к другому
гораздо более простой подход-аннотировать последнюю точку каждого графика. Кроме того, можно объехать, для акцента. Это можно сделать с помощью одной дополнительной строки:
from matplotlib import pyplot as plt
for i, (x, y) in enumerate(samples):
plt.plot(x, y)
plt.text(x[-1], y[-1], 'sample {i}'.format(i=i))
A вариант будет использовать ax.annotate
.