В 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')
источник
plt.plot(x2, 3*x2**2, label="3x*x"); plt.plot(x2, 2*x2**2, label="2x*x"); plt.plot(x2, 0.5*x2**2, label="0.5x*x"); plt.plot(x2, -1*x2**2, label="-x*x"); plt.plot(x2, -2.5*x2**2, label="-2.5*x*x"); my_legend();
одна из меток помещается в левый верхний угол. Есть какие нибудь идеи как это починить? Похоже, проблема в том, что линии расположены слишком близко друг к другу.x2 = np.linspace(0,0.5,100)
.print
команды она запускается и создает 4 графика, 3 из которых кажутся пиксельной тарабарщиной (вероятно, что-то связано с 32x32), а четвертый с метками в нечетных местах.Обновление: пользователь 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
значения -axis равныfloat
sПопался
labelLines
функция предполагает, что все серии данных охватывают диапазон, заданный пределами оси. Взгляните на синюю кривую в верхнем левом графике красивой картинки. Если бы были доступны только данные дляx
диапазона0.5
-1
тогда мы не могли бы разместить метку в желаемом месте (которое немного меньше0.2
). См. Этот вопрос для особенно неприятного примера. Прямо сейчас код не определяет этот сценарий и не переупорядочивает метки, однако есть разумный обходной путь. Функция labelLines принимаетxvals
аргумент; список значений,x
заданных пользователем, вместо линейного распределения по умолчанию по ширине. Таким образом, пользователь может решить, какойx
-значения, используемые для размещения меток каждой серии данных.Кроме того, я считаю, что это первый ответ для выполнения бонусной задачи по выравниванию меток по кривой, на которой они находятся. :)
label_lines.py:
from math import atan2,degrees import numpy as np #Label line with line2D label data def labelLine(line,x,label=None,align=True,**kwargs): ax = line.axes xdata = line.get_xdata() ydata = line.get_ydata() if (x < xdata[0]) or (x > xdata[-1]): print('x label location is outside data range!') return #Find corresponding y co-ordinate and angle of the line ip = 1 for i in range(len(xdata)): if x < xdata[i]: ip = i break y = ydata[ip-1] + (ydata[ip]-ydata[ip-1])*(x-xdata[ip-1])/(xdata[ip]-xdata[ip-1]) if not label: label = line.get_label() if align: #Compute the slope dx = xdata[ip] - xdata[ip-1] dy = ydata[ip] - ydata[ip-1] ang = degrees(atan2(dy,dx)) #Transform to screen co-ordinates pt = np.array([x,y]).reshape((1,2)) trans_angle = ax.transData.transform_angles(np.array((ang,)),pt)[0] else: trans_angle = 0 #Set a bunch of keyword arguments if 'color' not in kwargs: kwargs['color'] = line.get_color() if ('horizontalalignment' not in kwargs) and ('ha' not in kwargs): kwargs['ha'] = 'center' if ('verticalalignment' not in kwargs) and ('va' not in kwargs): kwargs['va'] = 'center' if 'backgroundcolor' not in kwargs: kwargs['backgroundcolor'] = ax.get_facecolor() if 'clip_on' not in kwargs: kwargs['clip_on'] = True if 'zorder' not in kwargs: kwargs['zorder'] = 2.5 ax.text(x,y,label,rotation=trans_angle,**kwargs) def labelLines(lines,align=True,xvals=None,**kwargs): ax = lines[0].axes labLines = [] labels = [] #Take only the lines which have labels other than the default ones for line in lines: label = line.get_label() if "_line" not in label: labLines.append(line) labels.append(label) if xvals is None: xmin,xmax = ax.get_xlim() xvals = np.linspace(xmin,xmax,len(labLines)+2)[1:-1] for line,x,label in zip(labLines,xvals,labels): labelLine(line,x,label,align,**kwargs)
Тестовый код для создания красивой картинки выше:
from matplotlib import pyplot as plt from scipy.stats import loglaplace,chi2 from labellines import * X = np.linspace(0,1,500) A = [1,2,5,10,20] funcs = [np.arctan,np.sin,loglaplace(4).pdf,chi2(5).pdf] plt.subplot(221) for a in A: plt.plot(X,np.arctan(a*X),label=str(a)) labelLines(plt.gca().get_lines(),zorder=2.5) plt.subplot(222) for a in A: plt.plot(X,np.sin(a*X),label=str(a)) labelLines(plt.gca().get_lines(),align=False,fontsize=14) plt.subplot(223) for a in A: plt.plot(X,loglaplace(4).pdf(a*X),label=str(a)) xvals = [0.8,0.55,0.22,0.104,0.045] labelLines(plt.gca().get_lines(),align=False,xvals=xvals,color='k') plt.subplot(224) for a in A: plt.plot(X,chi2(5).pdf(a*X),label=str(a)) lines = plt.gca().get_lines() l1=lines[-1] labelLine(l1,0.6,label=r'$Re=${}'.format(l1.get_label()),ha='left',va='bottom',align = False) labelLines(lines[:-1],align=False) plt.show()
источник
xvals
, вы можетеlabelLines
немного изменить код: измените код вif xvals is None:
области видимости, чтобы создать список на основе других критериев. Вы могли бы начать сxvals = [(np.min(l.get_xdata())+np.max(l.get_xdata()))/2 for l in lines]
.get_axes()
и.get_axis_bgcolor()
устарели. Пожалуйста, замените на.axes
и.get_facecolor()
, соответственно.labellines
- это свойства, связанные с нимplt.text
илиax.text
применяемые к нему. Значение можно установитьfontsize
иbbox
параметры вlabelLines()
функции.Ответ @Jan 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))
Вариант был бы использовать
ax.annotate
.источник
-1
, 2) установить соответствующие пределы оси, чтобы оставить место для меток.Более простой подход, такой как у Иоанниса Филиппидиса:
import matplotlib.pyplot as plt import numpy as np # evenly sampled time at 200ms intervals tMin=-1 ;tMax=10 t = np.arange(tMin, tMax, 0.1) # red dashes, blue points default plt.plot(t, 22*t, 'r--', t, t**2, 'b') factor=3/4 ;offset=20 # text position in view textPosition=[(tMax+tMin)*factor,22*(tMax+tMin)*factor] plt.text(textPosition[0],textPosition[1]+offset,'22 t',color='red',fontsize=20) textPosition=[(tMax+tMin)*factor,((tMax+tMin)*factor)**2+20] plt.text(textPosition[0],textPosition[1]+offset, 't^2', bbox=dict(facecolor='blue', alpha=0.5),fontsize=20) plt.show()
код Python 3 на sageCell
источник