2015-08-14 15 views
10

Czy ktoś może wiedzieć, czy można obrócić legendę na działce w matplotlib? Zrobiłem prosty spisek z poniższym kodem i edytowałem wykres w farbie, aby pokazać, czego chcę.Obrót pionowy legendy Matplotlib

plt.plot([4,5,6], label = 'test') 
ax = plt.gca() 
ax.legend() 
plt.show() 

http://www.abraham.dreamhosters.com/Capture.PNG

+4

Było pytanie na liście matplotlib korespondencji, a odpowiedź brzmiała, że ​​nie jest to możliwe z obecnym (2009) wdrożenie legendy (https://www.mail-archive.com/[email protected]/msg10474.html). Nigdy nie spotkałem się z taką opcją podczas korzystania z nowszych wersji matplotlib, może to nadal nie jest możliwe. –

+0

Dzięki Chris - Znalazłem ten sam wątek i powinienem uwzględnić to w moim oryginalnym wpisie. –

Odpowiedz

1

Spędziłem kilka godzin Zmniejszanie tego wczoraj, wykonane nieco postępu więc będę dzielić się poniżej wraz z kilka sugestii posuwa się naprzód.

Po pierwsze, wydaje się, że możemy z pewnością obrócić i przetłumaczyć ramkę ograniczającą (bbox) lub ramkę wokół legendy. W pierwszym przykładzie poniżej można zobaczyć, że można zastosować transform, aczkolwiek po zastosowaniu obrotu o 90 stopni wymagane są niektóre dziwnie duże liczby tłumaczeń. Ale faktycznie istnieją problemy z zapisaniem przetłumaczonej ramki legendy na plik obrazu, więc musiałem wykonać zrzut ekranu z notatnika IPython. Dodałem także kilka komentarzy.

import matplotlib.pyplot as plt 
%matplotlib inline 
import numpy as np 
import matplotlib.transforms 

fig = plt.figure() 
ax = fig.add_subplot('121') #make room for second subplot, where we are actually placing the legend 
ax2 = fig.add_subplot('122') #blank subplot to make space for legend 
ax2.axis('off') 
ax.plot([4,5,6], label = 'test') 

transform = matplotlib.transforms.Affine2D(matrix=np.eye(3)) #start with the identity transform, which does nothing 
transform.rotate_deg(90) #add the desired 90 degree rotation 
transform.translate(410,11) #for some reason we need to play with some pretty extreme translation values to position the rotated legend 

legend = ax.legend(bbox_to_anchor=[1.5,1.0]) 
legend.set_title('test title') 
legend.get_frame().set_transform(transform) #This actually works! But, only for the frame of the legend (see below) 
frame = legend.get_frame() 
fig.subplots_adjust(wspace = 0.4, right = 0.9) 
fig.savefig('rotate_legend_1.png',bbox_extra_artists=(legend,frame),bbox_inches='tight', dpi = 300) #even with the extra bbox parameters the legend frame is still getting clipped 

enter image description here

Następny, myślałem, że będzie mądry, żeby zbadać get_methods() innych elementów legendy. Możesz sortować te rzeczy z dir(legend) i legend.__dict__ i tak dalej. W szczególności zauważyłem, że możesz to zrobić: legend.get_title().set_transform(transform), co mogłoby sugerować, że możemy przetłumaczyć tekst legendy (a nie tylko ramkę jak wyżej). Zobaczmy, co się dzieje, gdy próbowałem to:

tytuł
fig2 = plt.figure() 
ax = fig2.add_subplot('121') 
ax2 = fig2.add_subplot('122') 
ax2.axis('off') 
ax.plot([4,5,6], label = 'test') 

transform = matplotlib.transforms.Affine2D(matrix=np.eye(3)) 
transform.rotate_deg(90) 
transform.translate(410,11) 

legend = ax.legend(bbox_to_anchor=[1.5,1.0]) 
legend.set_title('test title') 
legend.get_frame().set_transform(transform) 
legend.get_title().set_transform(transform) #one would expect this to apply the same transformation to the title text in the legend, rotating it 90 degrees and translating it 

frame = legend.get_frame() 
fig2.subplots_adjust(wspace = 0.4, right = 0.9) 
fig2.savefig('rotate_legend_1.png',bbox_extra_artists=(legend,frame),bbox_inches='tight', dpi = 300) 

enter image description here

legenda wydaje się, że zniknął na zrzucie z notebooka ipython. Ale jeśli spojrzymy na zapisanego pliku tytuł legendy jest w lewym dolnym rogu i wydaje się być ignorowane komponent obrotu przekształcenia (dlaczego?):

enter image description here

miałem podobne problemy techniczne z ten rodzaj podejścia:

bbox = matplotlib.transforms.Bbox([[0.,1],[1,1]]) 
trans_bbox = matplotlib.transforms.TransformedBbox(bbox, transform) 
legend.set_bbox_to_anchor(trans_bbox) 

Inne uwagi i sugestie:

  1. to może być sensowny pomysł, aby zagłębić się różnic w zachowaniu się pomiędzy tytułem legendy a obiektami ramek - dlaczego obie akceptują transformacje, ale tylko ramka akceptuje obrót? Być może możliwe byłoby podklasy obiektu legendy w kodzie źródłowym i dokonać pewnych korekt.
  2. Musimy również znaleźć rozwiązanie, w którym obrócona/przetłumaczona ramka legendy nie jest zapisywana na wyjściu, nawet po wykonaniu różnych powiązanych sugestii na temat SO (tj. Matplotlib savefig with a legend outside the plot).
+0

Dzięki za zbadanie tego! –

1

Poszedłem do podobnego problemu i rozwiązałem go, pisząc funkcję legendAsLatex, która generuje kod latexowy, który ma być używany jako etykieta osi Y. Funkcja zbiera kolor, znacznik, styl linii i etykietę dostarczoną do funkcji fabuły. Wymaga włączenia lateksu i załadowania wymaganych paczek. Oto kod do generowania wykresu z dodatkowymi krzywymi, które wykorzystują obie osie pionowe.

from matplotlib import pyplot as plt 
import matplotlib.colors as cor 

plt.rc('text', usetex=True) 
plt.rc('text.latex', preamble=r'\usepackage{amsmath} \usepackage{wasysym}'+ 
    r'\usepackage[dvipsnames]{xcolor} \usepackage{MnSymbol} \usepackage{txfonts}') 

def legendAsLatex(axes, rotation=90) : 
    '''Generate a latex code to be used instead of the legend. 
     Uses the label, color, marker and linestyle provided to the pyplot.plot. 
     The marker and the linestyle must be defined using the one or two character 
      abreviations shown in the help of pyplot.plot. 
     Rotation of the markers must be multiple of 90. 
    ''' 
    latexLine = {'-':'\\textbf{\Large ---}', 
     '-.':'\\textbf{\Large --\:\!$\\boldsymbol{\cdot}$\:\!--}', 
     '--':'\\textbf{\Large --\,--}',':':'\\textbf{\Large -\:\!-}'} 
    latexSymbol = {'o':'medbullet', 'd':'diamond', 's':'filledmedsquare', 
     'D':'Diamondblack', '*':'bigstar', '+':'boldsymbol{\plus}', 
     'x':'boldsymbol{\\times}', 'p':'pentagon', 'h':'hexagon', 
     ',':'boldsymbol{\cdot}', '_':'boldsymbol{\minus}','<':'LHD', 
     '>':'RHD','v':'blacktriangledown', '^':'blacktriangle'} 
    rot90=['^','<','v','>'] 
    di = [0,-1,2,1][rotation%360//90] 
    latexSymbol.update({rot90[i]:latexSymbol[rot90[(i+di)%4]] for i in range(4)}) 
    return ', '.join(['\\textcolor[rgb]{'\ 
      + ','.join([str(x) for x in cor.to_rgb(handle.get_color())]) +'}{' 
      + '$\\'+latexSymbol.get(handle.get_marker(),';')+'$' 
      + latexLine.get(handle.get_linestyle(),'') + '} ' + label 
       for handle,label in zip(*axes.get_legend_handles_labels())]) 

ax = plt.axes() 
ax.plot(range(0,10), 'b-', label = 'Blue line') 
ax.plot(range(10,0,-1), 'sm', label = 'Magenta squares') 
ax.set_ylabel(legendAsLatex(ax)) 

ax2 = plt.twinx() 
ax2.plot([x**0.5 for x in range(0,10)], 'ro', label = 'Red circles') 
ax2.plot([x**0.5 for x in range(10,0,-1)],'g--', label = 'Green dashed line') 
ax2.set_ylabel(legendAsLatex(ax2)) 

plt.savefig('legend.eps') 

plt.close() 

Rysunek generowane przez kod:

enter image description here

+0

To jest doskonałe. W końcu użyłem podobnego, ale znacznie mniej ogólnego fragmentu kodu dla moich postaci. Włączę niektóre z twoich pomysłów do mojej funkcji. –

+0

Dziękuję, @MadPhysicist. Możesz sprawdzić wersję, którą właśnie przesłałem. Jest prostszy, zajmuje się kolorami i akceptuje więcej kątów obrotu. – bmello

+0

Nie jestem pewien, czy to ma znaczenie, ale można "rot90" w dyktafon wpisać "rotation% 360", zamiast przechodzić przez tego potwora "jeśli". –