Вторичная ось с twinx (): как добавить в легенду?


290

У меня есть сюжет с двумя осями Y, используя twinx(). Я также даю метки линиям и хочу показать их с помощью legend(), но мне удается получить метки только одной оси в легенде:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rc
rc('mathtext', default='regular')

fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(time, Swdown, '-', label = 'Swdown')
ax.plot(time, Rn, '-', label = 'Rn')
ax2 = ax.twinx()
ax2.plot(time, temp, '-r', label = 'temp')
ax.legend(loc=0)
ax.grid()
ax.set_xlabel("Time (h)")
ax.set_ylabel(r"Radiation ($MJ\,m^{-2}\,d^{-1}$)")
ax2.set_ylabel(r"Temperature ($^\circ$C)")
ax2.set_ylim(0, 35)
ax.set_ylim(-20,100)
plt.show()

Поэтому я получаю только метки первой оси в легенде, а не метку 'temp' второй оси. Как я могу добавить этот третий ярлык к легенде?

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


4
[ Не делайте этого где-нибудь удаленно близко к какому-либо производственному коду ] Когда моя единственная цель - создать красивый сюжет с подходящей легендой как можно скорее, я использую уродливый метод построения пустого массива axв стиле, в котором я использую ax2: in твой случай ax.plot([], [], '-r', label = 'temp'). Это намного быстрее и проще, чем делать это правильно ...
Neinstein

Ответы:


371

Вы можете легко добавить вторую легенду, добавив строку:

ax2.legend(loc=0)

Вы получите это:

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

Но если вы хотите, чтобы все надписи были на одной легенде, вы должны сделать что-то вроде этого:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rc
rc('mathtext', default='regular')

time = np.arange(10)
temp = np.random.random(10)*30
Swdown = np.random.random(10)*100-10
Rn = np.random.random(10)*100-10

fig = plt.figure()
ax = fig.add_subplot(111)

lns1 = ax.plot(time, Swdown, '-', label = 'Swdown')
lns2 = ax.plot(time, Rn, '-', label = 'Rn')
ax2 = ax.twinx()
lns3 = ax2.plot(time, temp, '-r', label = 'temp')

# added these three lines
lns = lns1+lns2+lns3
labs = [l.get_label() for l in lns]
ax.legend(lns, labs, loc=0)

ax.grid()
ax.set_xlabel("Time (h)")
ax.set_ylabel(r"Radiation ($MJ\,m^{-2}\,d^{-1}$)")
ax2.set_ylabel(r"Temperature ($^\circ$C)")
ax2.set_ylim(0, 35)
ax.set_ylim(-20,100)
plt.show()

Что даст вам это:

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


2
Это не с errorbarграфиками. Для решения, которое правильно обрабатывает их, см. Ниже: stackoverflow.com/a/10129461/1319447
Davide

1
Чтобы предотвратить две перекрывающиеся легенды, как в моем случае, когда я указал две .legend (loc = 0), вы должны указать два разных значения для значения местоположения легенды (оба, кроме 0). Смотрите: matplotlib.org/api/legend_api.html
Roalt

У меня возникли проблемы с добавлением одной строки в какой-либо участок с несколькими строками ax1. В этом случае используйте, lns1=ax1.linesа затем добавьте lns2в этот список.
Маленькие столики Бобби

Различные значения , используемые locобъяснены здесь
Дрор

1
Смотрите ответ ниже для более автоматического способа (с matplotlib> = 2.1): stackoverflow.com/a/47370214/653364
joris

184

Я не уверен, что эта функциональность является новой, но вы также можете использовать метод get_legend_handles_labels () вместо того, чтобы отслеживать строки и метки самостоятельно:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rc
rc('mathtext', default='regular')

pi = np.pi

# fake data
time = np.linspace (0, 25, 50)
temp = 50 / np.sqrt (2 * pi * 3**2) \
        * np.exp (-((time - 13)**2 / (3**2))**2) + 15
Swdown = 400 / np.sqrt (2 * pi * 3**2) * np.exp (-((time - 13)**2 / (3**2))**2)
Rn = Swdown - 10

fig = plt.figure()
ax = fig.add_subplot(111)

ax.plot(time, Swdown, '-', label = 'Swdown')
ax.plot(time, Rn, '-', label = 'Rn')
ax2 = ax.twinx()
ax2.plot(time, temp, '-r', label = 'temp')

# ask matplotlib for the plotted objects and their labels
lines, labels = ax.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
ax2.legend(lines + lines2, labels + labels2, loc=0)

ax.grid()
ax.set_xlabel("Time (h)")
ax.set_ylabel(r"Radiation ($MJ\,m^{-2}\,d^{-1}$)")
ax2.set_ylabel(r"Temperature ($^\circ$C)")
ax2.set_ylim(0, 35)
ax.set_ylim(-20,100)
plt.show()

1
Это единственное решение, которое может обрабатывать оси, где сюжеты перекрываются с легендами (последние оси - это те, которые должны отображать легенды)
Амелио Васкес-Рейна

5
Это решение также работает с errorbarграфиками, в то время как принятый отказывает (показывая строку и ее строки ошибок отдельно, и ни один из них с правильной меткой). Плюс это проще.
Давиде

Небольшой улов: он не работает, если вы хотите перезаписать ярлык, ax2и у него нет сета с самого начала
Ciprian Tomoiagă

Примечание: для классических графиков вам не нужно указывать аргумент метки. Но для других, например. бары вам нужно.
Belka

Это также делает все намного проще, если вы не знаете заранее, сколько линий будет построено.
Вегард Джервелл

77

Начиная с версии 2.1 от matplotlib, вы можете использовать легенду фигуры . Вместо того ax.legend(), чтобы создать легенду с ручками от осей ax, можно создать легенду фигуры

fig.legend (loc = "верхний правый")

который соберет все ручки со всех участков на рисунке. Поскольку это легенда фигуры, она будет размещена в углу фигуры, и locаргумент будет относиться к фигуре.

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0,10)
y = np.linspace(0,10)
z = np.sin(x/3)**2*98

fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(x,y, '-', label = 'Quantity 1')

ax2 = ax.twinx()
ax2.plot(x,z, '-r', label = 'Quantity 2')
fig.legend(loc="upper right")

ax.set_xlabel("x [units]")
ax.set_ylabel(r"Quantity 1")
ax2.set_ylabel(r"Quantity 2")

plt.show()

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

Чтобы поместить легенду обратно в оси, нужно указать a bbox_to_anchorи a bbox_transform. Последним будет преобразование осей осей, в которых должна находиться легенда. Первым могут быть координаты ребра, locзаданные в координатах осей.

fig.legend(loc="upper right", bbox_to_anchor=(1,1), bbox_transform=ax.transAxes)

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


Итак, версия 2.1 уже выпущена? Но в Anaconda 3 я не пытался conda upgrade matplotlibнайти более новые версии, я все еще использую v.2.0.2
StayFoolish

1
Это более чистый способ достижения конечного результата.
Goutham

1
красивый и
питонический

1
Похоже, это не работает, когда у вас есть много участков. Он добавляет одну легенду для всех сюжетов. Обычно требуется одна легенда для каждого вспомогательного сюжета, содержащая серии в первичной и вторичной осях в каждой легенде.
sancho.s ReinstateMonicaCellio

@sancho Правильно, это то, что написано в третьем предложении этого ответа: «... который соберет все дескрипторы из всех подзаговоров на рисунке.».
Важная информация

38

Вы можете легко получить то, что вы хотите, добавив строку в топоре:

ax.plot([], [], '-r', label = 'temp')

или

ax.plot(np.nan, '-r', label = 'temp')

Это ничего не изменит, но добавит метку к легенде об топоре.

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

Весь код как показано ниже:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rc
rc('mathtext', default='regular')

time = np.arange(22.)
temp = 20*np.random.rand(22)
Swdown = 10*np.random.randn(22)+40
Rn = 40*np.random.rand(22)

fig = plt.figure()
ax = fig.add_subplot(111)
ax2 = ax.twinx()

#---------- look at below -----------

ax.plot(time, Swdown, '-', label = 'Swdown')
ax.plot(time, Rn, '-', label = 'Rn')

ax2.plot(time, temp, '-r')  # The true line in ax2
ax.plot(np.nan, '-r', label = 'temp')  # Make an agent in ax

ax.legend(loc=0)

#---------------done-----------------

ax.grid()
ax.set_xlabel("Time (h)")
ax.set_ylabel(r"Radiation ($MJ\,m^{-2}\,d^{-1}$)")
ax2.set_ylabel(r"Temperature ($^\circ$C)")
ax2.set_ylim(0, 35)
ax.set_ylim(-20,100)
plt.show()

Сюжет как ниже:

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


Обновление: добавьте лучшую версию:

ax.plot(np.nan, '-r', label = 'temp')

Это ничего не сделает, в то время как plot(0, 0)может изменить диапазон оси.


Еще один дополнительный пример для разброса

ax.scatter([], [], s=100, label = 'temp')  # Make an agent in ax
ax2.scatter(time, temp, s=10)  # The true scatter in ax2

ax.legend(loc=1, framealpha=1)

3
Мне это нравится. Это отчасти уродливо в том смысле, что «обманывает» систему, но так просто реализовать.
Даниэль Пауэр

Это действительно просто реализовать. Но при использовании этого с разбросом размер разброса в легенде является лишь крошечной точкой.
greeeeeeen

@greeeeeeen Тогда вам просто нужно указать размер маркера при создании
Syrtis Major

@SyrtisMajor Я, конечно, попробовал это. Но это не изменило размер маркера в легенде.
greeeeeeen

@greeeeeeen Вы изменили размер маркера разброса агента? Смотрите мой пост, я добавил фрагмент кода примера.
Сыртис майор

8

Быстрый взлом, который может удовлетворить ваши потребности ..

Снимите рамку коробки и вручную расположите две легенды рядом друг с другом. Что-то вроде этого..

ax1.legend(loc = (.75,.1), frameon = False)
ax2.legend( loc = (.75, .05), frameon = False)

Где кортеж loc - это проценты слева направо и снизу вверх, которые представляют местоположение на диаграмме.


5

Я нашел следующий официальный пример matplotlib, который использует host_subplot для отображения нескольких осей Y и всех различных меток в одной легенде. Обходной путь не требуется. Лучшее решение, которое я нашел до сих пор. http://matplotlib.org/examples/axes_grid/demo_parasite_axes2.html

from mpl_toolkits.axes_grid1 import host_subplot
import mpl_toolkits.axisartist as AA
import matplotlib.pyplot as plt

host = host_subplot(111, axes_class=AA.Axes)
plt.subplots_adjust(right=0.75)

par1 = host.twinx()
par2 = host.twinx()

offset = 60
new_fixed_axis = par2.get_grid_helper().new_fixed_axis
par2.axis["right"] = new_fixed_axis(loc="right",
                                    axes=par2,
                                    offset=(offset, 0))

par2.axis["right"].toggle(all=True)

host.set_xlim(0, 2)
host.set_ylim(0, 2)

host.set_xlabel("Distance")
host.set_ylabel("Density")
par1.set_ylabel("Temperature")
par2.set_ylabel("Velocity")

p1, = host.plot([0, 1, 2], [0, 1, 2], label="Density")
p2, = par1.plot([0, 1, 2], [0, 3, 2], label="Temperature")
p3, = par2.plot([0, 1, 2], [50, 30, 15], label="Velocity")

par1.set_ylim(0, 4)
par2.set_ylim(1, 65)

host.legend()

plt.draw()
plt.show()

Добро пожаловать в стек переполнения! Пожалуйста, указывайте наиболее релевантную часть ссылки, если целевой сайт недоступен или постоянно недоступен. Посмотрите, как мне написать хороший ответ . Сосредоточьтесь на более текущих вопросах в будущем, этому почти 4 года.
ByteHamster

Действительно хорошая находка, но я бы хотел, чтобы вы взяли то, что вы узнали из примера, применили его к MWE ОП и включили изображение.
aeroNotAuto
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.