Встроенной наклейки в библиотек 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ось (автоматически устанавливая на правильноеy-стоимость курса). Если вы хотите, вы можете просто передать массив координат x каждой из меток. Вы даже можете настроить расположение одной метки (как показано на нижнем правом графике) и равномерно распределить остальные, если хотите.кроме того,
label_linesфункция не учитывает строки, которым не была присвоена метка вplotкоманда (или точнее, если метка содержит'_line').ключевые аргументы передаются в
labelLinesилиlabelLineнаtextвызов функции (некоторые ключевые аргументы имеют значение, если вызывающий код выбирает не указывать).вопросы
- ограничительные рамки аннотаций иногда нежелательно мешают другим кривым. Как показано на
1и10аннотации в верхнем левом углу графика. Я даже не уверен, что этого можно избежать.- было бы неплохо указать
yпозицию, а не иногда.- это все еще итеративный процесс, чтобы получить аннотации в нужном месте
- это работает только тогда, когда
x-ось значенийfloatsGotchas
- по умолчанию
labelLinesфункция предполагает, что все ряды данных охватывают диапазон, заданный пределами оси. Взгляните на синюю кривую в верхнем левом углу графика красивой картины. Если бы были доступны только данные дляxряд0.5-1тогда мы не могли бы разместить этикетку в нужном месте (что немного меньше, чем0.2). Смотрите этот вопрос для особенно неприятного примера. Прямо сейчас код не интеллектуально идентифицирует этот сценарий и переупорядочивает метки, однако есть разумный обходной путь. Функция labelLines принимает значение
@ответ Яна Куикена, безусловно, хорошо продуман и тщателен, но есть некоторые предостережения:
- это не работает во всех случаях
- это требует изрядного количества дополнительного кода
- он может значительно отличаться от одного сюжета к другому
гораздо более простой подход заключается в аннотировании последней точки каждого участка. Точка также может быть обведена, для акцента. Это можно сделать с помощью одной дополнительной строки:
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.


Comments