Schöne Frage, vor einiger Zeit habe ich ein bisschen damit experimentiert, aber nicht viel benutzt, weil es immer noch nicht kugelsicher ist. Ich habe den Plotbereich in ein 32x32-Raster unterteilt und ein "potenzielles Feld" für die beste Position eines Etiketts für jede Linie nach den folgenden Regeln berechnet:
- Leerraum ist ein guter Ort für ein Etikett
- Das Etikett sollte sich in der Nähe der entsprechenden Linie befinden
- Das Etikett sollte von den anderen Zeilen entfernt sein
Der Code war ungefähr so:
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()
Und die daraus resultierende Handlung:
![Geben Sie hier die Bildbeschreibung ein](https://i.stack.imgur.com/dFDGl.png)
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();
Dadurch wird eine der Beschriftungen in der oberen linken Ecke platziert. Irgendwelche Ideen, wie man das behebt? Das Problem scheint zu sein, dass die Linien zu nahe beieinander liegen.x2 = np.linspace(0,0.5,100)
.print
Befehls wird er ausgeführt und erstellt 4 Diagramme, von denen 3 pixeliger Kauderwelsch zu sein scheinen (wahrscheinlich etwas mit dem 32x32 zu tun), und der vierte mit Beschriftungen an ungeraden Stellen.Update: Benutzer cphyc hat freundlicherweise ein Github-Repository für den Code in dieser Antwort erstellt (siehe hier ) und den Code in einem Paket gebündelt, das möglicherweise mit installiert wird
pip install matplotlib-label-lines
.Schönes Bild:
In
matplotlib
es ist ziemlich leicht zu Etikett Kontur - Plots (entweder automatisch oder manuell durch Etiketten mit Mausklicks Platzierung). Es scheint (noch) keine äquivalente Fähigkeit zu geben, Datenreihen auf diese Weise zu kennzeichnen! Es kann einen semantischen Grund dafür geben, diese Funktion, die mir fehlt, nicht aufzunehmen.Unabhängig davon habe ich das folgende Modul geschrieben, das halbautomatische Plotbeschriftungen zulässt. Es werden nur
numpy
einige Funktionen aus der Standardbibliothek benötigtmath
.Beschreibung
Das Standardverhalten der
labelLines
Funktion besteht darin, die Beschriftungen gleichmäßig entlang derx
Achse zu platzieren (y
natürlich automatisch mit dem richtigen Wert). Wenn Sie möchten, können Sie einfach ein Array der x-Koordinaten der einzelnen Beschriftungen übergeben. Sie können sogar die Position eines Etiketts anpassen (siehe Abbildung unten rechts) und den Rest gleichmäßig verteilen, wenn Sie möchten.Darüber hinaus
label_lines
berücksichtigt die Funktion nicht die Zeilen, denen implot
Befehl keine Beschriftung zugewiesen wurde (oder genauer, wenn die Beschriftung enthält'_line'
).Schlüsselwortargumente, die an den Funktionsaufruf übergeben
labelLines
oder an diesenlabelLine
weitergegeben werdentext
(einige Schlüsselwortargumente werden festgelegt, wenn der aufrufende Code keine Angabe macht).Probleme
1
und10
Anmerkungen im oberen linken Diagramm gezeigt. Ich bin mir nicht mal sicher, ob dies vermieden werden kann.y
stattdessen manchmal eine Position anzugeben .x
-axis-Wertefloat
s sindFallstricke
labelLines
Funktion davon aus, dass alle Datenreihen den durch die Achsengrenzen angegebenen Bereich umfassen. Schauen Sie sich die blaue Kurve oben links im hübschen Bild an. Wenn nur Daten zur Verfügung standen für denx
Bereich0.5
-1
dann könnten wir nicht möglicherweise eine Markierung setzen an der gewünschten Stelle (was etwas weniger als ist0.2
). In dieser Frage finden Sie ein besonders unangenehmes Beispiel. Derzeit identifiziert der Code dieses Szenario nicht intelligent und ordnet die Beschriftungen neu an. Es gibt jedoch eine angemessene Problemumgehung. Die Funktion labelLines übernimmt dasxvals
Argument. Eine Liste derx
vom Benutzer angegebenen Werte anstelle der standardmäßigen linearen Verteilung über die Breite. So kann der Benutzer entscheiden, welchex
-Werte, die für die Etikettenplatzierung jeder Datenreihe verwendet werden sollen.Ich glaube auch, dass dies die erste Antwort ist, um das Bonusziel zu erreichen, die Beschriftungen an der Kurve auszurichten, auf der sie sich befinden. :) :)
label_lines.py:
Testcode, um das hübsche Bild oben zu erzeugen:
quelle
xvals
Sie denlabelLines
Code möglicherweise ein wenig ändern, wenn Sie nicht manuell eine Liste erstellen möchten : Ändern Sie den Code imif xvals is None:
Bereich, um eine Liste basierend auf anderen Kriterien zu erstellen. Sie könnten beginnen mitxvals = [(np.min(l.get_xdata())+np.max(l.get_xdata()))/2 for l in lines]
.get_axes()
und.get_axis_bgcolor()
sind veraltet. Bitte ersetzen Sie durch.axes
und.get_facecolor()
bzw.labellines
ist, dass Eigenschaften damit zusammenhängenplt.text
oderax.text
gelten. Das heißt, Sie könnenfontsize
undbbox
Parameter in derlabelLines()
Funktion einstellen .@ Jan Kuikens Antwort ist sicherlich gut durchdacht und gründlich, aber es gibt einige Einschränkungen:
Ein viel einfacherer Ansatz besteht darin, den letzten Punkt jedes Diagramms mit Anmerkungen zu versehen. Der Punkt kann zur Hervorhebung auch eingekreist werden. Dies kann mit einer zusätzlichen Zeile erreicht werden:
Eine Variante wäre zu verwenden
ax.annotate
.quelle
-1
. 2) Legen Sie geeignete Achsengrenzen fest, um Platz für die Beschriftungen zu schaffen.Ein einfacherer Ansatz wie der von Ioannis Filippidis:
Code Python 3 auf sageCell
quelle