Вертикальное вращение легенды Matplotlib

Может ли кто-нибудь узнать, можно ли повернуть легенду на графике в matplotlib? Я сделал простой сюжет с приведенным ниже кодом и отредактировал график в краске, чтобы показать, что я хочу.

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

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

Ответ 1

Я провел несколько часов, пробираясь вчера, и сделал немного прогресса, поэтому я расскажу об этом ниже вместе с некоторыми предложениями, продвигающимися вперед.

Во-первых, кажется, что мы можем, конечно, повернуть и перевести ограничительную рамку (bbox) или рамку вокруг легенды. В первом примере ниже вы можете увидеть, что может применяться a transform, хотя и требует некоторых странно больших чисел перевода после применения поворота на 90 градусов. Но на самом деле проблемы с сохранением переведенного кадра легенды в файл изображения, поэтому мне пришлось сделать снимок экрана из ноутбука IPython. Я также добавил некоторые комментарии.

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

введите описание изображения здесь

Затем я подумал, что было бы разумно исследовать get_methods() других компонентов легенды. Вы можете сортировать эти вещи с помощью dir(legend) и legend.__dict__ и так далее. В частности, я заметил, что вы можете сделать это: legend.get_title().set_transform(transform), что, по-видимому, означает, что мы могли бы перевести текст легенды (а не только рамку, как указано выше). Посмотрим, что произойдет, когда я попробую:

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) 

введите описание изображения здесь

Название легенды, похоже, исчезло на снимке экрана из ноутбука IPython. Но если мы посмотрим на сохраненный файл, название легенды теперь находится в левом нижнем углу и, похоже, проигнорировало компонент поворота преобразования (почему?):

введите описание изображения здесь

У меня были схожие технические трудности с этим типом подхода:

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

Другие примечания и предложения:

  • Может быть разумной идеей разобраться в различиях в поведении между заголовком легенды и объектами фрейма - почему они оба принимают преобразования, но только кадр принимает поворот? Возможно, можно было бы подклассировать объект легенды в исходном коде и внести некоторые изменения.
  • Нам также нужно найти решение для фрейма с вращающейся/переведенной легендой, которая не сохраняется на выходе, даже после того, как будет следовать различным связанным предположениям о SO (т.е. Matplotlib savefig с легендой снаружи сюжет).

Ответ 2

Я подошел к аналогичной проблеме и решил ее, написав функцию legendAsLatex, которая генерирует латексный код, который будет использоваться как метка оси y. Функция собирает цвет, маркер, стиль линии и метку, предоставленную функции графика. Это требует включения латекса и загрузки необходимых пакетов. Вот код для генерации вашего графика с дополнительными кривыми, которые используют обе вертикальные оси.

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()

Рисунок, сгенерированный кодом:

введите описание изображения здесь