Вторинна вісь з 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.