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

У меня есть сюжет с двумя осями 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' второй оси. Как я могу добавить этот третий ярлык к легенде?

11 ответов

Решение

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

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

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

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

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

fig.legend(LOC =1)

который соберет все ручки со всех участков на рисунке. Так как это легенда фигуры, она будет размещена в углу фигуры, а 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=1)

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

plt.show()

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

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

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

ax.plot(0, 0, '-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) может изменить диапазон оси.

Подготовка

      import numpy as np
from matplotlib import pyplot as plt

fig, ax1 = plt.subplots( figsize=(15,6) )

Y1, Y2 = np.random.random((2,100))

ax2 = ax1.twinx()

Содержание

Я удивлен, что это не появилось до сих пор, но самый простой способ - либо собрать их вручную в одну из осей objs (которые лежат друг над другом)

      l1 = ax1.plot( range(len(Y1)), Y1, label='Label 1' )
l2 = ax2.plot( range(len(Y2)), Y2, label='Label 2', color='orange' )

ax1.legend( handles=l1+l2 )

или автоматически собрать их в окружающую фигуру с помощью fig.legend()и возиться с bbox_to_anchorпараметр:

      ax1.plot( range(len(Y1)), Y1, label='Label 1' )
ax2.plot( range(len(Y2)), Y2, label='Label 2', color='orange' )

fig.legend( bbox_to_anchor=(.97, .97) )

Доработка

      fig.tight_layout()
fig.savefig('stackoverflow.png', bbox_inches='tight')

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

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

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

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

Я нашел следующий официальный пример 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()

Если вы используете Seaborn, вы можете сделать это:

      g = sns.barplot('arguments blah blah')
g2 = sns.lineplot('arguments blah blah')
h1,l1 = g.get_legend_handles_labels()
h2,l2 = g2.get_legend_handles_labels()
#Merging two legends
g.legend(h1+h2, l1+l2, title_fontsize='10')
#removes the second legend
g2.get_legend().remove()

Предложенные до сих пор решения имеют одно или два неудобства:

  • Ручки должны быть собраны индивидуально при построении графика, напримерlns1 = ax.plot(time, Swdown, '-', label = 'Swdown'). Есть риск забыть дескрипторы при обновлении кода.

  • Легенда рисуется для всей фигуры, а не для подсюжетов, что, вероятно, не подходит, если у вас есть несколько подсюжетов.

Это новое решение использует преимущества Axes.get_legend_handles_labels() для сбора существующих маркеров и меток для главной и двойной оси.

Автоматический сбор ручек и меток

Эта операция numpy будет сканировать все оси, которые имеют ту же область подграфика, что и , включаяaxи вернуть объединенные дескрипторы и метки:

      hl = np.hstack([axis.get_legend_handles_labels()
                for axis in ax.figure.axes
                if axis.bbox.bounds == ax.bbox.bounds])

Его можно использовать для кормленияlegend()аргументы таким образом:

      import numpy as np
import matplotlib.pyplot as plt

t = np.arange(1, 200)
signals = [np.exp(-t/20) * np.cos(t*k) for k in (1, 2)]

fig, axes = plt.subplots(nrows=2, figsize=(10, 3), layout='constrained')
axes = axes.flatten()

for i, (ax, signal) in enumerate(zip(axes, signals)):
    # Plot as usual, no change to the code
    ax.plot(t, signal, label=f'plotted on axes[{i}]', c='C0', lw=9, alpha=0.3)
    ax2 = ax.twinx()
    ax2.plot(t, signal, label=f'plotted on axes[{i}].twinx()', c='C1')

    # The only specificity of the code is when plotting the legend
    h, l = np.hstack([axis.get_legend_handles_labels()
                      for axis in ax.figure.axes
                      if axis.bbox.bounds == ax.bbox.bounds]).tolist()
    ax2.legend(handles=h, labels=l, loc='upper right')

Вот еще один способ сделать это:

      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)
pl_1, = ax.plot(time, Swdown, '-')
label_1 = 'Swdown'
pl_2, = ax.plot(time, Rn, '-')
label_2 = 'Rn'

ax2 = ax.twinx()
pl_3, = ax2.plot(time, temp, '-r')
label_3 = 'temp'

ax.legend([pl[enter image description here][1]_1, pl_2, pl_3], [label_1, label_2, label_3], 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()

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

Как показано в примере с сайта matplotlib.org, простой способ реализовать одну легенду из нескольких осей - это использовать маркеры графика:

      import matplotlib.pyplot as plt


fig, ax = plt.subplots()
fig.subplots_adjust(right=0.75)

twin1 = ax.twinx()
twin2 = ax.twinx()

# Offset the right spine of twin2.  The ticks and label have already been
# placed on the right by twinx above.
twin2.spines.right.set_position(("axes", 1.2))

p1, = ax.plot([0, 1, 2], [0, 1, 2], "b-", label="Density")
p2, = twin1.plot([0, 1, 2], [0, 3, 2], "r-", label="Temperature")
p3, = twin2.plot([0, 1, 2], [50, 30, 15], "g-", label="Velocity")

ax.set_xlim(0, 2)
ax.set_ylim(0, 2)
twin1.set_ylim(0, 4)
twin2.set_ylim(1, 65)

ax.set_xlabel("Distance")
ax.set_ylabel("Density")
twin1.set_ylabel("Temperature")
twin2.set_ylabel("Velocity")

ax.yaxis.label.set_color(p1.get_color())
twin1.yaxis.label.set_color(p2.get_color())
twin2.yaxis.label.set_color(p3.get_color())

tkw = dict(size=4, width=1.5)
ax.tick_params(axis='y', colors=p1.get_color(), **tkw)
twin1.tick_params(axis='y', colors=p2.get_color(), **tkw)
twin2.tick_params(axis='y', colors=p3.get_color(), **tkw)
ax.tick_params(axis='x', **tkw)

ax.legend(handles=[p1, p2, p3])

plt.show()
Другие вопросы по тегам