Buena pregunta, hace un tiempo experimenté un poco con esto, pero no lo he usado mucho porque todavía no es a prueba de balas. Dividí el área de la parcela en una cuadrícula de 32x32 y calculé un 'campo potencial' para la mejor posición de una etiqueta para cada línea de acuerdo con las siguientes reglas:
- el espacio en blanco es un buen lugar para una etiqueta
- La etiqueta debe estar cerca de la línea correspondiente
- La etiqueta debe estar alejada de las otras líneas
El código era algo como esto:
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()
Y la trama resultante:
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();
esto coloca una de las etiquetas en la esquina superior izquierda. ¿Alguna idea sobre cómo solucionar este problema? Parece que el problema puede ser que las líneas estén demasiado juntas.x2 = np.linspace(0,0.5,100)
.print
comando, se ejecuta y crea 4 gráficos, 3 de los cuales parecen ser un galimatías pixelado (probablemente algo relacionado con el 32x32), y el cuarto con etiquetas en lugares extraños.Actualización: el usuario cphyc ha creado amablemente un repositorio de Github para el código de esta respuesta (ver aquí ) y ha empaquetado el código en un paquete que puede instalarse usando
pip install matplotlib-label-lines
.Bonita imagen:
En
matplotlib
que es bastante fácil de parcelas etiqueta de contorno (ya sea de forma automática o manualmente mediante la colocación de etiquetas con clics del ratón). ¡No parece (todavía) haber ninguna capacidad equivalente para etiquetar series de datos de esta manera! Puede haber alguna razón semántica para no incluir esta característica que me falta.Independientemente, he escrito el siguiente módulo que admite cualquier etiquetado de trazado semiautomático. Requiere solo
numpy
un par de funciones de lamath
biblioteca estándar .Descripción
El comportamiento predeterminado de la
labelLines
función es espaciar las etiquetas de manera uniforme a lo largo delx
eje (colocándolas automáticamente en ely
valor correcto, por supuesto). Si lo desea, puede simplemente pasar una matriz de las coordenadas x de cada una de las etiquetas. Incluso puede modificar la ubicación de una etiqueta (como se muestra en el gráfico inferior derecho) y espaciar el resto de manera uniforme si lo desea.Además, la
label_lines
función no tiene en cuenta las líneas que no han tenido una etiqueta asignada en elplot
comando (o más exactamente si la etiqueta contiene'_line'
).Los argumentos de palabra clave pasados
labelLines
olabelLine
se pasan a latext
llamada de función (algunos argumentos de palabra clave se establecen si el código de llamada elige no especificar).Cuestiones
1
y10
en el gráfico superior izquierdo. Ni siquiera estoy seguro de que esto pueda evitarse.y
posición a veces.x
valores de -axis sonfloat
sGotchas
labelLines
función asume que todas las series de datos abarcan el rango especificado por los límites del eje. Eche un vistazo a la curva azul en el gráfico superior izquierdo de la bonita imagen. Si solo hubiera datos disponibles para elx
rango0.5
,1
entonces no podríamos colocar una etiqueta en la ubicación deseada (que es un poco menos que0.2
). Consulte esta pregunta para ver un ejemplo particularmente desagradable. En este momento, el código no identifica inteligentemente este escenario y reorganiza las etiquetas, sin embargo, hay una solución razonable. La función labelLines toma elxvals
argumento; una lista dex
valores especificados por el usuario en lugar de la distribución lineal predeterminada a lo ancho. Para que el usuario pueda decidir quéx
-valores que se utilizarán para la ubicación de la etiqueta de cada serie de datos.Además, creo que esta es la primera respuesta para completar el objetivo adicional de alinear las etiquetas con la curva en la que se encuentran. :)
label_lines.py:
Pruebe el código para generar la bonita imagen de arriba:
fuente
xvals
, es posible que desee modificarlabelLines
un poco el código: cambie el código bajo elif xvals is None:
alcance para crear una lista basada en otros criterios. Podría comenzar conxvals = [(np.min(l.get_xdata())+np.max(l.get_xdata()))/2 for l in lines]
.get_axes()
y.get_axis_bgcolor()
han quedado obsoletos. Reemplace con.axes
y.get_facecolor()
, resp.labellines
es que las propiedades se relacionan con élplt.text
o seax.text
aplican a él. Lo que significa que puede configurarfontsize
ybbox
parámetros en lalabelLines()
función.La respuesta de @Jan Kuiken es ciertamente bien pensada y completa, pero hay algunas advertencias:
Un enfoque mucho más simple es anotar el último punto de cada gráfico. El punto también se puede encerrar en un círculo para enfatizarlo. Esto se puede lograr con una línea adicional:
Una variante sería utilizar
ax.annotate
.fuente
-1
, 2) establecer límites de eje apropiados para dejar espacio para las etiquetas.Un enfoque más simple como el que hace Ioannis Filippidis:
código python 3 en sageCell
fuente