Boa pergunta, há um tempo eu experimentei um pouco com isso, mas não usei muito porque ainda não é à prova de balas. Dividi a área do gráfico em uma grade de 32x32 e calculei um 'campo potencial' para a melhor posição de um rótulo para cada linha de acordo com as seguintes regras:
- o espaço em branco é um bom lugar para um rótulo
- A etiqueta deve estar perto da linha correspondente
- A etiqueta deve estar longe das outras linhas
O código era mais ou menos assim:
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()
E o enredo 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();
Isso coloca um dos rótulos no canto superior esquerdo. Alguma ideia de como consertar isso? Parece que o problema pode ser que as linhas estão muito próximas.x2 = np.linspace(0,0.5,100)
.print
comando, ele executa e cria 4 gráficos, 3 dos quais parecem ser rabiscos pixelados (provavelmente algo a ver com o 32x32) e o quarto com rótulos em lugares estranhos.Atualização: O usuário cphyc gentilmente criou um repositório Github para o código desta resposta (veja aqui ) e agrupou o código em um pacote que pode ser instalado usando
pip install matplotlib-label-lines
.Bonita foto:
Em
matplotlib
que é bastante fácil de rotular gráficos de contorno (automaticamente ou manualmente, colocando etiquetas com cliques do mouse). Não parece (ainda) haver qualquer capacidade equivalente para rotular séries de dados desta forma! Pode haver alguma razão semântica para não incluir esse recurso que estou perdendo.Independentemente disso, escrevi o módulo a seguir, que permite a rotulagem semiautomática de plotagem. Requer apenas
numpy
e algumas funções damath
biblioteca padrão .Descrição
O comportamento padrão da
labelLines
função é espaçar os rótulos uniformemente ao longo dox
eixo (posicionando automaticamente noy
-valor claro). Se você quiser, pode simplesmente passar um array das coordenadas x de cada um dos rótulos. Você pode até ajustar a localização de um rótulo (conforme mostrado no gráfico inferior direito) e espaçar o resto uniformemente, se desejar.Além disso, a
label_lines
função não leva em conta as linhas que não tiveram um rótulo atribuído noplot
comando (ou mais precisamente se o rótulo contiver'_line'
).Argumentos de palavra- chave passados para
labelLines
oulabelLine
são passados para atext
chamada de função (alguns argumentos de palavra-chave são definidos se o código de chamada optar por não especificar).Problemas
1
e10
no gráfico superior esquerdo. Nem tenho certeza se isso pode ser evitado.y
posição às vezes.x
valores -axis sãofloat
sPegadinhas
labelLines
função assume que todas as séries de dados abrangem o intervalo especificado pelos limites do eixo. Dê uma olhada na curva azul no gráfico superior esquerdo da bela imagem. Se houvesse únicos dados disponíveis para ax
gama0.5
-1
, em seguida, em seguida, não poderíamos colocar uma etiqueta no local desejado (que é um pouco menos do que0.2
). Veja esta pergunta para um exemplo particularmente desagradável. No momento, o código não identifica de forma inteligente esse cenário e reorganiza os rótulos, no entanto, há uma solução alternativa razoável. A função labelLines leva oxvals
argumento; uma lista dex
-valores especificados pelo usuário em vez da distribuição linear padrão ao longo da largura. Assim, o usuário pode decidir qualx
-valores a serem usados para o posicionamento do rótulo de cada série de dados.Além disso, acredito que esta seja a primeira resposta para completar o objetivo bônus de alinhar os rótulos com a curva em que estão. :)
label_lines.py:
Teste o código para gerar a bela imagem acima:
fonte
xvals
, convém modificarlabelLines
um pouco o código: altere o código noif xvals is None:
escopo para criar uma lista com base em outros critérios. Você poderia começar comxvals = [(np.min(l.get_xdata())+np.max(l.get_xdata()))/2 for l in lines]
.get_axes()
e.get_axis_bgcolor()
foram descontinuados. Substitua por.axes
e.get_facecolor()
, resp.labellines
é que as propriedades relacionadasplt.text
ouax.text
se aplicam a ele. Significa que você pode definirfontsize
ebbox
parâmetros nalabelLines()
função.A resposta de @Jan Kuiken é certamente bem pensada e completa, mas há algumas ressalvas:
Uma abordagem muito mais simples é anotar o último ponto de cada gráfico. O ponto também pode ser circulado, para dar ênfase. Isso pode ser feito com uma linha extra:
Uma variante seria usar
ax.annotate
.fonte
-1
, 2) definir limites de eixo apropriados para permitir espaço para os rótulos.Uma abordagem mais simples como a de Ioannis Filippidis:
codificar python 3 em sageCell
fonte