Make two markers share the same labels and simultaneously keep the black edge of patch in legend

Question:

How should I modify my following code to make two markers share the same labels and simultaneously keep the black edge of patch in legend? Here are code (I have plotted the bars twice to decouple the edge color and hatch color) and plotted picture. I want to make the markers as shown in the second picture.

import matplotlib
matplotlib.rcParams['pdf.fonttype'] = 42
matplotlib.rcParams['ps.fonttype'] = 42
import matplotlib.pyplot as plt

x_lable = ['3', '4', '5', '6', '7']

width = 0.3

Data1 = [96, 99, 100, 100, 100]
Data2 = [94, 96, 95, 96, 95]
Data3 = [48, 43, 42, 42, 37]

data_3 = [2228, 2621, 3165, 3761, 3763]
data_4 = [3895, 5670, 7354, 8999, 10731]
data_5 = [4355, 6373, 8279, 10105, 12018]

xcoordinate = [1, 3, 5, 7, 9]
xcoordinate_1 = [xcoordinate[0] + 0 * width, xcoordinate[1] + 0 * width, xcoordinate[2] + 0 * width, xcoordinate[3] + 0 * width, xcoordinate[4] + 0 * width]
xcoordinate_2 = [xcoordinate[0] + 1 * width, xcoordinate[1] + 1 * width, xcoordinate[2] + 1 * width, xcoordinate[3] + 1 * width, xcoordinate[4] + 1 * width]
xcoordinate_3 = [xcoordinate[0] + 2 * width, xcoordinate[1] + 2 * width, xcoordinate[2] + 2 * width, xcoordinate[3] + 2 * width, xcoordinate[4] + 2 * width]
xcoordinate_4 = [xcoordinate[0] + 3 * width, xcoordinate[1] + 3 * width, xcoordinate[2] + 3 * width, xcoordinate[3] + 3 * width, xcoordinate[4] + 3 * width]
xcoordinate_5 = [xcoordinate[0] + 4 * width, xcoordinate[1] + 4 * width, xcoordinate[2] + 4 * width, xcoordinate[3] + 4 * width, xcoordinate[4] + 4 * width]

fig = plt.figure()

# plot ax1
ax1 = fig.add_subplot(111)

ax1.bar(xcoordinate_3, data_3, width=width, label='GGG', color='none', edgecolor='orange', hatch='\\\')
ax1.bar(xcoordinate_4, data_4, width=width, label='SSS', color='none', edgecolor='blue', hatch='---')
ax1.bar(xcoordinate_5, data_5, width=width, label='KKK', color='none', edgecolor='orangered', hatch='///')

ax1.set_ylabel('BBB')
ax1.set_xlabel('AAA')

ax1.bar(xcoordinate_3, data_3, width=width, label='GGG', color='none', edgecolor='black')
ax1.bar(xcoordinate_4, data_4, width=width, label='SSS', color='none', edgecolor='black')
ax1.bar(xcoordinate_5, data_5, width=width, label='KKK', color='none', edgecolor='black')

handles1, labels1 = ax1.get_legend_handles_labels()
order = [0, 1, 2]
plt.legend([handles1[idx] + handles1[idx+3] for idx in order], [labels1[idx] for idx in order], loc=(2.5/10, 1.08), frameon=True, ncol=3, shadow=False, framealpha=1, labelspacing=1.05).get_frame().set_edgecolor('black')

# plot ax2
ax2 = ax1.twinx()

ax2.plot([i + 3 * width for i in xcoordinate], Data3, label='GGG', color='orange', linestyle='-', marker='p')
ax2.plot([i + 3 * width for i in xcoordinate], Data2, label='SSS', color='orangered', linestyle='-', marker='s')
ax2.plot([i + 3 * width for i in xcoordinate], Data1, label='KKK', color='limegreen', linestyle='-', marker='^')

ax2.set_ylabel('CCC')
plt.xticks([i + 3 * width for i in xcoordinate], x_lable)

handles2, labels2 = ax2.get_legend_handles_labels()
order = [0, 1, 2]
plt.legend([handles2[idx] for idx in order], [labels2[idx] for idx in order],  loc=(2.5/10, 1.005),  frameon=True, ncol=3, shadow=False, framealpha=1, labelspacing=1.05).get_frame().set_edgecolor('black')

plt.show()

Plotted picture:

fig1

What I want:

fig2

I want to keep the black edges of patches when two makers share the same label.

Asked By: lhhhhhhh

||

Answers:

The third example in the official guide is what you want. To combine each handler, set them up in tuple form. If you don’t want each handler to be stacked, you can add a configuration.

from matplotlib.legend_handler import HandlerLineCollection, HandlerTuple

#plt.legend([handles1[idx] + handles1[+3] for idx in order], [labels1[idx] for idx in order], loc=(2.5/10, 1.08), frameon=True, ncol=3, shadow=False, framealpha=1, labelspacing=1.05).get_frame().set_edgecolor('black')

plt.legend([(handles1[idx],handles2[idx]) for idx in order], [labels2[idx] for idx in order],  loc=(2.5/10, 1.005),  frameon=True, ncol=3, shadow=False, framealpha=1, labelspacing=1.05,handler_map={tuple: HandlerTuple(ndivide=None)}).get_frame().set_edgecolor('black')

enter image description here

Answered By: r-beginners

Seemingly, we cannot simply group handles arbitrarily using the HandlerTuple class (or I am not clever enough). However, when more entries exist as defined by ndivide, the keys start from position 1 again, so we can write:

....
from matplotlib.legend_handler import HandlerTuple
...
order = [0, 1, 2]
plt.legend([(handles1[idx], handles2[idx], handles1[idx+3]) for idx in order], [labels1[idx] for idx in order], handler_map={tuple: HandlerTuple(ndivide=2)}, loc=(0.1, 1.04), frameon=True, ncol=3, shadow=False, framealpha=1, labelspacing=1.01, handlelength=5).get_frame().set_edgecolor('black')

....

to group 3 keys into 2 positions for each legend entry. This gives the following output:
enter image description here

I also increased the handle length with …surprise… handlelength=5 because otherwise, we would not see much of the hatch pattern.

Answered By: Mr. T