Встроенные ярлыки в 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 Solutions collect form web for “Встроенные ярлыки в Matplotlib”

Хороший вопрос, некоторое время назад я немного экспериментировал с этим, но не использовал его много, потому что он все еще не пуленепробиваемый. Я разделил участок участка на сетку 32×32 и вычислил «потенциальное поле» для наилучшего положения метки для каждой строки в соответствии со следующими правилами:

  • белое пространство – хорошее место для ярлыка
  • Метка должна быть рядом с соответствующей строкой
  • Этикетка должна быть вдалеке от других линий

Код был примерно таким:

 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 любезно создал repository Github для кода в этом ответе (см. Здесь ) и связал код с пакетом, который может быть установлен с помощью pip install matplotlib-label-lines .


Приятная картина:

полуавтоматическая маркировка

В matplotlib довольно легко обозначить контурные графики (либо автоматически, либо вручную помещая метки с щелчками мыши). Пока нет (пока) эквивалентной возможности маркировки серий данных таким образом! Может возникнуть какая-то смысловая причина не включать эту функцию, которую мне не хватает.

Несмотря на это, я написал следующий модуль, который позволяет использовать полуавтоматическую маркировку. Это требует только numpy и нескольких функций из стандартной math библиотеки.

Описание

Поведение функции labelLines по labelLines заключается в равномерном размещении меток вдоль оси x (автоматически, конечно, при правильном значении y ). Если вы хотите, вы можете просто передать массив x-координат каждой из меток. Вы можете даже настроить расположение одной метки (как показано в нижнем правом графике) и разместить остальное равномерно, если хотите.

Кроме того, функция label_lines не учитывает строки, у которых не было метки, назначенной в команде plot (или, точнее, если метка содержит '_line' ).

Аргументы labelLines слова, переданные в labelLines или labelLine , передаются на вызов text функции (некоторые аргументы ключевого слова устанавливаются, если вызывающий код выбирает не указывать).

вопросы

  • Обозначения ограничивающих коробок иногда мешают нежелательно с другими кривыми. Как показано 1 и 10 аннотациями в верхнем левом графике. Я даже не уверен, что этого можно избежать.
  • Было бы неплохо указать y позицию вместо этого.
  • Это еще итеративный процесс, чтобы получить annotations в нужном месте
  • Он работает только тогда, когда значения x axis равны

Gotchas

  • По умолчанию функция labelLines предполагает, что все серии данных охватывают диапазон, заданный предельными значениями оси. Взгляните на синюю кривую в левом верхнем углу красивой картинки. Если бы имелись только данные для диапазона x 0.51 тогда мы не могли бы поместить метку в нужное место (что немного меньше 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 label_lines 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() 

Ответ Джан Куйкена, безусловно, продуманный и тщательный, но есть некоторые оговорки:

  • он не работает во всех случаях
  • это требует достаточного количества дополнительного кода
  • он может значительно варьироваться от одного участка к следующему

Более простой подход состоит в том, чтобы аннотировать последнюю точку каждого сюжета. Точка также может быть обведена, для акцента. Это может быть выполнено с помощью одной дополнительной строки:

 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 .

  • Как получить x и y windows программы на Java?
  • Получение широты и долготы за 30 секунд
  • Вычисление угла между двумя линиями без вычисления наклона? (Ява)
  • Давайте будем гением компьютера.