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


288

У мене є ділянка з двома осями 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
[ Не робіть цього в будь-якому місці віддалено близько до будь-якого виробничого коду ] Коли моя єдина мета - генерувати прекрасний сюжет з відповідною легендою ASAP, я використовую некрасивий хак для побудови порожнього масиву за axдопомогою стилю, який я використовую ax2: в ваш випадок, ax.plot([], [], '-r', label = 'temp'). Це набагато швидше і простіше, ніж робити це правильно ...
Нейнштейн

Відповіді:


370

Ви можете легко додати другу легенду, додавши рядок:

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
Девід

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

183

Я не впевнений, чи є ця функціональність новою, але ви також можете використовувати метод 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

Починаючи з matplotlib версії 2.1, ви можете використовувати фігурну легенду . Замість того 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
Це більш чистий спосіб досягнення кінцевого результату.
Гутхем

1
красивий і пітонічний
DanGoodrick

1
Це, мабуть, не спрацює, коли у вас багато субплотів. Це додає єдину легенду для всіх субплотів. Зазвичай для кожної субплоти потрібна одна легенда, що містить серії як в первинних, так і в другорядних осях у кожній легенді.
sancho.s ReinstateMonicaCellio

@sancho Правильно, ось що написано в третьому реченні цієї відповіді, "... що збере всі ручки з усіх підплотів на рисунку."
ВажливістьBeingErnest

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
Мені подобається це. Його вид некрасивий у тому, як він "хитрощі" системи, але настільки простий у здійсненні.
Daniel Power

Це реально просто здійснити. Але при використанні цього з розкиданням отриманий розмір розкиду в легенді - лише крихітний момент.
greeeeeeen

@greeeeeeen Тоді вам слід вказати розмір маркера під час створення ділянки розкидання :-)
Syrtis Major

@SyrtisMajor Я, звичайно, спробував це. Але це не змінило розмір маркера в легенді.
greeeeeeen

@greeeeeeen Ви змінили розмір маркера розкидання агента? Дивіться свій пост, я додав фрагмент коду прикладу.
Сиртіс-майор

7

Швидкий злом, який може відповідати вашим потребам ..

Зніміть рамку коробки і вручну розташуйте дві легенди поруч. Щось на зразок цього..

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

Там, де локальний кортеж є відсотками зліва направо і знизу вгору, які представляють місце на діаграмі.


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

Справді, хороша знахідка, але я б хотів, щоб ви взяли те, що ви дізналися з прикладу, застосували до МРЕ ОП та включили зображення.
aeroNotAuto
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.