Cheng Cheng - 21 days ago 5
Python Question

Matplotlib customize the legend to show squares instead of rectangles

Here is my attempt to change the legend of a barplot from rectangle to square:

import matplotlib.patches as patches

rect1 = patches.Rectangle((0,0),1,1,facecolor='#FF605E')
rect2 = patches.Rectangle((0,0),1,1,facecolor='#64B2DF')
plt.legend((rect1, rect2), ('2016', '2015'))


But when I plot this, I still see rectangles instead of squares:

enter image description here



Any suggestions on how can I do this?




I tried both solutions provided by @ImportanceOfBeingErnest and @furas, here are the results:

@ImportanceOfBeingErnest's solution is the easiest to do:

plt.rcParams['legend.handlelength'] = 1
plt.rcParams['legend.handleheight'] = 1.125


Here is the result:

enter image description here

My final code looks like this:

plt.legend((df.columns[1], df.columns[0]), handlelength=1, handleheight=1) # the df.columns = the legend text


@furas's solution produces this, I don't know why the texts are further away from the rectangles, but I am sure the gap can be changed somehow:

enter image description here

Answer

Matplotlib provides the rcParams

legend.handlelength  : 2.    # the length of the legend lines in fraction of fontsize
legend.handleheight  : 0.7   # the height of the legend handle in fraction of fontsize

You can set those within the call to plt.legend()

plt.legend(handlelength=1, handleheight=1)

or using the rcParams at the beginning of your script

import matplotlib
matplotlib.rcParams['legend.handlelength'] = 1
matplotlib.rcParams['legend.handleheight'] = 1

Unfortunately providing equal handlelength=1, handleheight=1 will not give a perfect rectange. It seems handlelength=1, handleheight=1.125 will do the job, but this may depend on the font being used.


An alternative, if you want to use proxy artists may be to use the square markers from the plot/scatter methods.

bar1 = plt.plot([], marker="s", markersize=15, linestyle="", label="2015")

and supply it to the legend, legend(handles=[bar1]). Using this approach needs to have set matplotlib.rcParams['legend.numpoints'] = 1, otherwise two markers would appear in the legend.


Here is a full example of both methods

import matplotlib.pyplot as plt
plt.rcParams['legend.handlelength'] = 1
plt.rcParams['legend.handleheight'] = 1.125
plt.rcParams['legend.numpoints'] = 1


fig, ax = plt.subplots(ncols=2, figsize=(5,2.5))

# Method 1: Set the handlesizes already in the rcParams
ax[0].set_title("Setting handlesize")
ax[0].bar([0,2], [6,3], width=0.7, color="#a30e73", label="2015", align="center")
ax[0].bar([1,3], [3,2], width=0.7, color="#0943a8", label="2016", align="center" )
ax[0].legend()

# Method 2: use proxy markers. (Needs legend.numpoints to be 1)
ax[1].set_title("Proxy markers")
ax[1].bar([0,2], [6,3], width=0.7, color="#a30e73", align="center" )
ax[1].bar([1,3], [3,2], width=0.7, color="#0943a8", align="center" )
b1, =ax[1].plot([], marker="s", markersize=15, linestyle="", color="#a30e73",  label="2015")
b2, =ax[1].plot([], marker="s", markersize=15, linestyle="", color="#0943a8",  label="2016")
ax[1].legend(handles=[b1, b2])

[a.set_xticks([0,1,2,3]) for a in ax]
plt.show()

producing

enter image description here