3

Given the following code:

import numpy as np
import matplotlib.pyplot as plt
import os, sys

labels = ['G1', 'G2', 'G3', 'G4', 'G5']
men_means = [20, 34, 30, 35, 27]
women_means = [25, 32, 34, 20, 25]

x = np.arange(len(labels))  # the label locations
width = 0.35

fig, ax = plt.subplots()
rects1 = ax.bar(x - width/2, men_means, width, label='Men')
rects2 = ax.bar(x + width/2, women_means, width, label='Women')

# Add some text for labels, title and custom x-axis tick labels, etc.
ax.set_ylabel('Scores')
ax.set_title('Scores by group and gender')
ax.set_xticks(x)
ax.legend() # oringinal legend

ax.bar_label(rects1, padding=3)
ax.bar_label(rects2, padding=3)
fig.tight_layout()
plt.savefig('bar.png')

which returns this barplot:

enter image description here

I'd like to replace the legend ['Men', 'Women'] with emojis of enter image description here and enter image description here which are saved in my directory from emojipedia.

To do so, I have the following class ImageHandler added to my script, taken from here:

from matplotlib.transforms import Bbox, TransformedBbox
from matplotlib.legend_handler import HandlerBase
from matplotlib.image import BboxImage

class ImageHandler(HandlerBase):
    def create_artists(self, legend, orig_handle, Xd_, Yd_, W_, H_, fontsize, trans):
        # enlarge the image by these margins
        sx, sy = self.image_stretch 

        # create a bounding box to house the image
        bb = Bbox.from_bounds(Xd_ - sx, Yd_ - sy, W_ + sx, H_ + sy )
        tbb = TransformedBbox(bb, trans)
        image = BboxImage(tbb)
        image.set_data(self.image_data)
        self.update_prop(image, orig_handle, legend)
        return [image]

    def set_image(self, image_path, image_stretch=(0, 0)):
        self.image_data = plt.imread(image_path)
        self.image_stretch = image_stretch
 

Therefore, I replace ax.legend() with:

emoji_dataset = os.path.join( os.environ['HOME'], 'Datasets', 'Emojis')
h1 = ImageHandler()
h2 = ImageHandler()

h1.set_image(os.path.join(emoji_dataset, 'man.png'), image_stretch=(0, 20))
h2.set_image(os.path.join(emoji_dataset, 'woman.png'), image_stretch=(0, 20))

ax.legend(  handler_map={rects1: h1, rects2: h2}, 
                        handlelength=2, labelspacing=0.0, 
                        fontsize=36, borderpad=0.15, loc='best', 
                        handletextpad=0.2, borderaxespad=0.15)

However, I get the following error:

Traceback (most recent call last):
  File "img_in_legend.py", line 117, in <module>
    handletextpad=0.2, borderaxespad=0.15)
  File "/home/xenial/anaconda3/envs/py37/lib/python3.7/site-packages/matplotlib/pyplot.py", line 2886, in legend
    return gca().legend(*args, **kwargs)
  File "/home/xenial/anaconda3/envs/py37/lib/python3.7/site-packages/matplotlib/axes/_axes.py", line 290, in legend
    self.legend_ = mlegend.Legend(self, handles, labels, **kwargs)
  File "/home/xenial/anaconda3/envs/py37/lib/python3.7/site-packages/matplotlib/legend.py", line 503, in __init__
    self._init_legend_box(handles, labels, markerfirst)
  File "/home/xenial/anaconda3/envs/py37/lib/python3.7/site-packages/matplotlib/legend.py", line 767, in _init_legend_box
    fontsize, handlebox))
  File "/home/xenial/anaconda3/envs/py37/lib/python3.7/site-packages/matplotlib/legend_handler.py", line 117, in legend_artist
    fontsize, handlebox.get_transform())
  File "img_in_legend.py", line 48, in create_artists
    self.update_prop(image, orig_handle, legend)
  File "/home/xenial/anaconda3/envs/py37/lib/python3.7/site-packages/matplotlib/legend_handler.py", line 74, in update_prop
    self._update_prop(legend_handle, orig_handle)
  File "/home/xenial/anaconda3/envs/py37/lib/python3.7/site-packages/matplotlib/legend_handler.py", line 65, in _update_prop
    self._default_update_prop(legend_handle, orig_handle)
  File "/home/xenial/anaconda3/envs/py37/lib/python3.7/site-packages/matplotlib/legend_handler.py", line 70, in _default_update_prop
    legend_handle.update_from(orig_handle)
  File "/home/xenial/anaconda3/envs/py37/lib/python3.7/site-packages/matplotlib/artist.py", line 1133, in update_from
    self._transform = other._transform
AttributeError: 'BarContainer' object has no attribute '_transform'

It seems that class ImageHandler does not work with ax.bar() in my sample example.

How could I add images to legend of barplot in matplotlib?

Cheers,

3
  • An emoji won't work either (an image isn't an emoji) ax.legend(labels=[👨, 👩]) results in SyntaxError: invalid character in identifier Commented Nov 24, 2021 at 16:06
  • Did you also look at this answer? Commented Nov 24, 2021 at 16:09
  • 1
    I’m aware of that answer, but it labels the line whereas I’m looking for labeling the bars (rectangle)! It also returns the same error! Commented Nov 24, 2021 at 16:21

1 Answer 1

3

It looks like you are right and that ImageHandler doesn't work with ax.bar(). A very hacky workaround would be to create two placeholder line2d objects in order to use the HandlerLineImage code form Trenton McKinney's link. You can define them with:

line1, = ax.plot([],[],label='men',color='tab:blue',lw=15)
line2, = ax.plot([],[],label='women',color='tab:orange',lw=15)

[],[] ensures that nothing gets plotted on top of your bar chart.

Overall, the code looks like that:

import matplotlib.pyplot as plt
import matplotlib.lines
from matplotlib.transforms import Bbox, TransformedBbox
from matplotlib.legend_handler import HandlerBase
from matplotlib.image import BboxImage
import numpy as np
import os, sys

class HandlerLineImage(HandlerBase):

    def __init__(self, path, space=15, offset = 10 ):
        self.space=space
        self.offset=offset
        self.image_data = plt.imread(path)        
        super(HandlerLineImage, self).__init__()

    def create_artists(self, legend, orig_handle,
                       xdescent, ydescent, width, height, fontsize, trans):

        l = matplotlib.lines.Line2D([xdescent+self.offset,xdescent+(width-self.space)/3.+self.offset],
                                     [ydescent+height/2., ydescent+height/2.])
        l.update_from(orig_handle)
        l.set_clip_on(False)
        l.set_transform(trans)

        bb = Bbox.from_bounds(xdescent +(width+self.space)/3.+self.offset,
                              ydescent,
                              height*self.image_data.shape[1]/self.image_data.shape[0],
                              height)

        tbb = TransformedBbox(bb, trans)
        image = BboxImage(tbb)
        image.set_data(self.image_data)

        self.update_prop(image, orig_handle, legend)
        return [l,image]



plt.figure(figsize=(4.8,3.2))
#line,  = plt.plot([1,2],[1.5,3], color="#1f66e0", lw=1.3)
#line2,  = plt.plot([1,2],[1,2], color="#efe400", lw=1.3)
labels = ['G1', 'G2', 'G3', 'G4', 'G5']
men_means = [20, 34, 30, 35, 27]
women_means = [25, 32, 34, 20, 25]

x = np.arange(len(labels))  # the label locations
width = 0.35

fig, ax = plt.subplots()
rects1 = ax.bar(x - width/2, men_means, width,color='tab:blue')
rects2 = ax.bar(x + width/2, women_means, width,color='tab:orange')
line1, = ax.plot([],[],label='men',color='tab:blue',lw=15)
line2, = ax.plot([],[],label='women',color='tab:orange',lw=15)

# Add some text for labels, title and custom x-axis tick labels, etc.
ax.set_ylabel('Scores')
ax.set_title('Scores by group and gender')
ax.set_xticks(x)
ax.legend() # oringinal legend

ax.bar_label(rects1, padding=3)
ax.bar_label(rects2, padding=3)

fig.tight_layout()

leg=plt.legend([line1, line2], ["", ""],
    handler_map={line1: HandlerLineImage("man.png"), line2: HandlerLineImage("woman.png")}, 
    handlelength=2, labelspacing=0.0, fontsize=36, borderpad=0.15, loc=2, 
    handletextpad=0.2, borderaxespad=0.15)

plt.show()

And the output of this code gives: enter image description here

Sign up to request clarification or add additional context in comments.

1 Comment

That was a really nice hacky approach, thanks a lot!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.