Табличная легенда в matplotlib

Я хотел бы сделать сложную легенду в matplotlib. Я сделал следующий код

import matplotlib.pylab as plt
import numpy as np

N = 25
y = np.random.randn(N)
x = np.arange(N)

y2 = np.random.randn(25)

# serie A
p1a, = plt.plot(x, y,       "ro", ms=10, mfc="r", mew=2, mec="r")
p1b, = plt.plot(x[:5], y[:5] ,  "w+", ms=10, mec="w", mew=2) 
p1c, = plt.plot(x[5:10], y[5:10], "w*", ms=10, mec="w", mew=2) 

# serie B
p2a, = plt.plot(x, y2,       "bo", ms=10, mfc="b", mew=2, mec="b")
p2b, = plt.plot(x[15:20], y2[15:20] ,  "w+", ms=10, mec="w", mew=2) 
p2c, = plt.plot(x[10:15], y2[10:15], "w*", ms=10, mec="w", mew=2) 


plt.legend([p1a, p2a, (p1a, p1b), (p2a,p2b), (p1a, p1c), (p2a,p2c)], 
 ["No prop", "No prop", "Prop +", "Prop +", "Prop *", "Prop *"], ncol=3, numpoints=1)

plt.show()

Он создает такой сюжет: введите описание изображения здесь

Но я хотел бы построить сложную легенду, как здесь:

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

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

Ответ 1

Является ли это решение достаточно близким по своему вкусу? Это слегка вдохновляет Рикардо, но я использовал только один объект legend для каждого столбца, а затем использовал title -keyword, чтобы установить заголовок каждого отдельного столбца. Чтобы поместить маркеры в центр каждого столбца, я использовал handletextpad с отрицательным значением, чтобы оттолкнуть его назад. Для отдельных линий нет легенд. Мне также пришлось вставить некоторые пробелы в строки заголовков, чтобы они выглядели одинаково большими при рисовании на экране.

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

import matplotlib.pylab as plt
import numpy as np
plt.close('all')

N = 25
y = np.random.randn(N)
x = np.arange(N)

y2 = np.random.randn(25)

# serie A
p1a, = plt.plot(x, y,       "ro", ms=10, mfc="r", mew=2, mec="r")
p1b, = plt.plot(x[:5], y[:5] ,  "w+", ms=10, mec="w", mew=2) 
p1c, = plt.plot(x[5:10], y[5:10], "w*", ms=10, mec="w", mew=2) 

# serie B
p2a, = plt.plot(x, y2,       "bo", ms=10, mfc="b", mew=2, mec="b")
p2b, = plt.plot(x[15:20], y2[15:20] ,  "w+", ms=10, mec="w", mew=2) 
p2c, = plt.plot(x[10:15], y2[10:15], "w*", ms=10, mec="w", mew=2) 

line_columns = [
                p1a, p2a,
                (p1a, p1b), (p2a, p2b),
                (p1a, p1c), (p2a, p2c)
                ]


leg1 = plt.legend(line_columns[0:2], ['', ''], ncol=1, numpoints=1, 
                  title='No prop', handletextpad=-0.4, 
                  bbox_to_anchor=[0.738, 1.])
leg2 = plt.legend(line_columns[2:4], ['', ''], ncol=1, numpoints=1, 
                  title=' Prop  + ', handletextpad=-0.4,
                  bbox_to_anchor=[0.87, 1.])
leg3 = plt.legend(line_columns[4:6], ['', ''], ncol=1, numpoints=1, 
                  title=' Prop  * ', handletextpad=-0.4, 
                  bbox_to_anchor=[0.99, 1.])

plt.gca().add_artist(leg1)
plt.gca().add_artist(leg2)
plt.gca().add_artist(leg3)

plt.gcf().show()

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

Edit

Возможно, это будет работать лучше. Вам все равно придется подстроить несколько вещей, но проблема выравнивания bboxes отсутствует.

leg = plt.legend(line_columns, ['']*len(line_columns), 
             title='No Prop    Prop +    Prop *',  
             ncol=3, numpoints=1, handletextpad=-0.5)

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

Ответ 2

Кажется, что для этого нет стандартного подхода, а не нескольких трюков, доступных здесь.

Стоит отметить, что вы должны проверить коэффициент bbox размера, который вам больше всего подходит.

Лучшее, что я смог найти, возможно, может привести к лучшему решению:

N = 25
y = np.random.randn(N)
x = np.arange(N)

y2 = np.random.randn(25)

# Get current size
fig_size = list(plt.rcParams["figure.figsize"])

# Set figure width to 12 and height to 9
fig_size[0] = 12
fig_size[1] = 12
plt.rcParams["figure.figsize"] = fig_size

# serie A
p1a, = plt.plot(x, y,       "ro", ms=10, mfc="r", mew=2, mec="r")
p1b, = plt.plot(x[:5], y[:5] ,  "w+", ms=10, mec="w", mew=2) 
p1c, = plt.plot(x[5:10], y[5:10], "w*", ms=10, mec="w", mew=2) 

# serie B
p2a, = plt.plot(x, y2,       "bo", ms=10, mfc="b", mew=2, mec="b")
p2b, = plt.plot(x[15:20], y2[15:20] ,  "w+", ms=10, mec="w", mew=2) 
p2c, = plt.plot(x[10:15], y2[10:15], "w*", ms=10, mec="w", mew=2) 

v_factor = 1.
h_factor = 1.

leg1 = plt.legend([(p1a, p1a)], ["No prop"], bbox_to_anchor=[0.78*h_factor, 1.*v_factor])
leg2 = plt.legend([(p2a, p2a)], ["No prop"], bbox_to_anchor=[0.78*h_factor, .966*v_factor])

leg3 = plt.legend([(p2a,p2b)], ["Prop +"], bbox_to_anchor=[0.9*h_factor, 1*v_factor])
leg4 = plt.legend([(p1a, p1b)], ["Prop +"], bbox_to_anchor=[0.9*h_factor, .966*v_factor])

leg5 = plt.legend([(p1a, p1c)], ["Prop *"], bbox_to_anchor=[1.*h_factor, 1.*v_factor])
leg6 = plt.legend([(p2a,p2c)], ["Prop *"], bbox_to_anchor=[1.*h_factor, .966*v_factor])

plt.gca().add_artist(leg1)
plt.gca().add_artist(leg2)
plt.gca().add_artist(leg3)
plt.gca().add_artist(leg4)
plt.gca().add_artist(leg5)
plt.gca().add_artist(leg6)
plt.show()

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

Ответ 3

Я улучшаю ответ @pathoren, чтобы автоматически позиционировать легенды в цикле в соответствии с координатой bbox легенд. Этот код позволяет показать все необходимые граничные линии сложной легенды:

import matplotlib.pylab as plt
import numpy as np
plt.close('all')

# test data
N = 25
y = np.random.randn(N)
x = np.arange(N)
y2 = np.random.randn(25)

# serie A
p1a, = plt.plot(x, y, "ro", ms=10, mfc="r", mew=2, mec="r")
p1b, = plt.plot(x[:5], y[:5], "w+", ms=10, mec="w", mew=2) 
p1c, = plt.plot(x[5:10], y[5:10], "w*", ms=10, mec="w", mew=2) 
# serie B
p2a, = plt.plot(x, y2, "bo", ms=10, mfc="b", mew=2, mec="b")
p2b, = plt.plot(x[15:20], y2[15:20], "w+", ms=10, mec="w", mew=2) 
p2c, = plt.plot(x[10:15], y2[10:15], "w*", ms=10, mec="w", mew=2) 

# legend handlers
columns = [p1a, p2a, 
 (p1a, p1b), (p2a, p2b),
 (p1a, p1c), (p2a, p2c)]

ax = plt.gca()
fig = plt.gcf()
legs = []
# set the first legend in desired position
leg = plt.legend(columns[0:2], ['', ''], ncol=1, numpoints=1, 
 borderaxespad=0., title='No prop.', framealpha=.75,
 facecolor='w', edgecolor='k', loc=2, fancybox=None)
ax.add_artist(leg)
fig.canvas.draw()
plt.pause(1.e-3)

# get bbox postion of 1st legend to calculate
# postion of 2nd and 3rd legends according to loc
for i,si in enumerate(['+','*']):
    bbox = leg.get_window_extent().inverse_transformed(ax.transAxes)
    # next legends
    leg = plt.legend(columns[(i+1)*2:(i+1)*2+2], ['', ''], ncol=1, numpoints=1, 
     title='Prop. '+si, framealpha=.75, borderaxespad=.0,
     bbox_to_anchor=(bbox.x1-bbox.height*.08, bbox.y0, bbox.width, bbox.height),
     facecolor='w', edgecolor='k')
    ax.add_artist(leg)
    fig.canvas.draw()
    plt.pause(1.e-3)

plt.show()

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