W Matplotlib nie jest zbyt trudno stworzyć legendę ( example_legend()
poniżej), ale myślę, że lepiej jest umieścić etykiety bezpośrednio na kreślonych krzywych (jak example_inline()
poniżej). Może to być bardzo kłopotliwe, ponieważ muszę ręcznie określić współrzędne, a jeśli ponownie sformatuję działkę, prawdopodobnie będę musiał zmienić położenie etykiet. Czy istnieje sposób na automatyczne generowanie etykiet na krzywych w Matplotlib? Dodatkowe punkty za możliwość zorientowania tekstu pod kątem odpowiadającym kątowi krzywej.
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')
źródło
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();
powoduje umieszczenie jednej z etykiet w lewym górnym rogu. Jakieś pomysły, jak to naprawić? Wygląda na to, że problem może polegać na tym, że linie są zbyt blisko siebie.x2 = np.linspace(0,0.5,100)
.print
polecenia uruchamia się i tworzy 4 wykresy, z których 3 wydają się być pikselowym bełkotem (prawdopodobnie ma to związek z 32x32), a czwarty z etykietami w dziwnych miejscach.Aktualizacja: Użytkownik cphyc uprzejmie utworzył repozytorium Github dla kodu w tej odpowiedzi (patrz tutaj ) i umieścił kod w pakiecie, który można zainstalować za pomocą
pip install matplotlib-label-lines
.Ładne zdjęcie:
W
matplotlib
to dość łatwe do działek konturu etykieta (automatycznie lub ręcznie umieszczania etykiet z kliknięć myszką). Wydaje się, że nie ma (jeszcze) równoważnej możliwości opisywania serii danych w ten sposób! Może istnieć jakiś semantyczny powód, aby nie uwzględnić tej funkcji, której mi brakuje.Mimo to napisałem następujący moduł, który przyjmuje dowolne opcje półautomatycznego etykietowania działek. Wymaga tylko
numpy
i kilku funkcji zmath
biblioteki standardowej .Opis
Domyślnym zachowaniem
labelLines
funkcji jest równomierne rozmieszczenie etykiet wzdłużx
osi (y
oczywiście automatyczne umieszczenie na prawidłowej wartości). Jeśli chcesz, możesz po prostu przekazać tablicę współrzędnych x każdej z etykiet. Możesz nawet dostosować położenie jednej etykiety (jak pokazano na wykresie w prawym dolnym rogu) i równomiernie rozmieścić resztę, jeśli chcesz.Ponadto
label_lines
funkcja nie uwzględnia wierszy, które nie mają przypisanej etykiety wplot
poleceniu (lub dokładniej, jeśli etykieta zawiera'_line'
).Argumenty słów kluczowych przekazywane
labelLines
lublabelLine
są przekazywane dotext
wywołania funkcji (niektóre argumenty słów kluczowych są ustawiane, jeśli kod wywołujący zdecyduje się nie określać).Zagadnienia
1
i10
w lewym górnym rogu wykresu. Nie jestem nawet pewien, czy można tego uniknąć.y
Przydałoby się czasem określić pozycję.x
wartości -axis sąfloat
sGotchas
labelLines
funkcja zakłada, że wszystkie serie danych obejmują zakres określony przez limity osi. Spójrz na niebieską krzywą w lewym górnym rogu tego ładnego obrazka. Gdyby dostępne były tylko dane dlax
zakresu0.5
-1
wtedy nie moglibyśmy umieścić etykiety w żądanym miejscu (czyli trochę mniej niż0.2
). Zobacz to pytanie, aby zobaczyć szczególnie nieprzyjemny przykład. Obecnie kod nie identyfikuje w inteligentny sposób tego scenariusza i nie zmienia kolejności etykiet, jednak istnieje rozsądne obejście tego problemu. Funkcja labelLines przyjmujexvals
argument; listax
-wartości określonych przez użytkownika zamiast domyślnego rozkładu liniowego na szerokości. Dzięki temu użytkownik może zdecydować, który plikx
-wartości używane do umieszczania etykiet dla każdej serii danych.Uważam również, że jest to pierwsza odpowiedź na realizację celu dodatkowego, jakim jest wyrównanie etykiet z krzywą, na której się znajdują. :)
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)
Przetestuj kod, aby wygenerować ładny obrazek powyżej:
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()
źródło
xvals
, możesz chciećlabelLines
trochę zmodyfikować kod: zmień kod wif xvals is None:
zakresie, aby utworzyć listę opartą na innych kryteriach. Możesz zacząć odxvals = [(np.min(l.get_xdata())+np.max(l.get_xdata()))/2 for l in lines]
.get_axes()
i.get_axis_bgcolor()
są one przestarzałe. Proszę wymienić na.axes
i.get_facecolor()
, odp.labellines
jest to, że właściwości są z nią powiązaneplt.text
lub mająax.text
do niej zastosowanie. Oznacza to, że możesz ustawićfontsize
ibbox
parametry wlabelLines()
funkcji.Odpowiedź @Jana Kuikena jest z pewnością dobrze przemyślana i dokładna, ale są pewne zastrzeżenia:
O wiele prostszym podejściem jest dodanie adnotacji do ostatniego punktu każdego wykresu. Punkt można również zakreślić dla podkreślenia. Można to zrobić za pomocą jednej dodatkowej linii:
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))
Wariant byłby do użycia
ax.annotate
.źródło
-1
, 2) ustawienie odpowiednich granic osi, aby zapewnić miejsce na etykiety.Prostsze podejście, takie jak to, które robi Ioannis Filippidis:
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()
code python 3 w sageCell
źródło