7

I am using FontAwesome in my chart, and each data point is a symbol in FontAwesome font, displaying like an icon. Therefore, in the legend, I would like to use text (symbols in FontAwesome) to describe the items.

The code I am using is like:

from matplotlib.patches import Patch
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm

ax = plt.gca()
ax.axis([0, 3, 0, 3])
prop = fm.FontProperties(fname='FontAwesome.otf', size=18)
ax.text(x=0, y=0, s='\uf1c7', color='r', fontproperties=prop)
ax.text(x=2, y=0, s='\uf050', color='g', fontproperties=prop)

plt.legend(handles=[Patch(color='r', label='label1'), Patch(color='g', label='label2')])

And the plot is like: enter image description here

So what I want to do is to replace the color bar in the legend to the same icons as it in the plot.

The handle I am using is a list of Patches. But I found that it's hard to add text into Patch. I found there's a great solution to add picture into legend here: Insert image in matplotlib legend

I have tried using TextArea replacing BboxImage in that answer, but it doesn't work, and TextArea doesn't support fontproperties like axis.text.

So is there a way I can use text instead of markers in the legend?

1

3 Answers 3

8

For a solution using TextArea see this answer. You would then need to recreate the fontproperties for the text inside the TextArea.

Since here you want to show exactly the symbol you have as text also in the legend, a simpler way to create a legend handler for some text object would be the following, which maps the text to a TextHandler. The TextHandler subclasses matplotlib.legend_handler.HandlerBase and its create_artists produces a copy of the text to show in the legend. Some of the text properties then need to be adjusted for the legend.

import matplotlib.pyplot as plt
from matplotlib.legend_handler import HandlerBase
import copy

ax = plt.gca()
ax.axis([-1, 3,-1, 2])

tx1 = ax.text(x=0, y=0, s=ur'$\u2660$', color='r',size=30, ha="right")
tx2 = ax.text(x=2, y=0, s=ur'$\u2665$', color='g',size=30)


class TextHandler(HandlerBase):
    def create_artists(self, legend, orig_handle,xdescent, ydescent,
                        width, height, fontsize,trans):
        h = copy.copy(orig_handle)
        h.set_position((width/2.,height/2.))
        h.set_transform(trans)
        h.set_ha("center");h.set_va("center")
        fp = orig_handle.get_font_properties().copy()
        fp.set_size(fontsize)
        # uncomment the following line, 
        # if legend symbol should have the same size as in the plot
        h.set_font_properties(fp)
        return [h]

labels = ["label 1", "label 2"]
handles = [tx1,tx2]
handlermap = {type(tx1) : TextHandler()}
ax.legend(handles, labels, handler_map=handlermap,) 

plt.show()

enter image description here

Also see this more generic answer

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

1 Comment

Thank you for your solution! I update it to avoid using copy. You can see it in another answer.
2

Thanks for ImportanceOfBeingErnest's answer, I update his solution a little bit to generate Text in the legend handler:

import matplotlib.pyplot as plt
from matplotlib.legend_handler import HandlerBase
import matplotlib.font_manager as fm
from matplotlib.text import Text

class TextLegend(object):
    def __init__(self, icon, color, **kwargs):
        self.icon = icon
        self.color = color
        self.kwargs = kwargs

class TextLegendHandler(HandlerBase):
    def create_artists(self, legend, orig_handle, xdescent, ydescent,
                       width, height, fontsize, trans):
        x = xdescent + width / 2.0
        y = ydescent + height / 2.0
        kwargs = {
            'horizontalalignment': 'center',
            'verticalalignment': 'center',
            'color': orig_handle.color,
            'fontproperties': fm.FontProperties(fname='FontAwesome.otf', size=fontsize)
        }
        kwargs.update(orig_handle.kwargs)
        annotation = Text(x, y, orig_handle.icon, **kwargs)
        return [annotation]

ax = plt.gca()
ax.axis([0, 3, 0, 3])
prop = fm.FontProperties(fname='FontAwesome.otf', size=18)
ax.text(x=0, y=0, s='\uf1c7', color='r', fontproperties=prop)
ax.text(x=2, y=0, s='\uf050', color='g', fontproperties=prop)

plt.legend(
    handles=[TextLegend(color='r', icon='\uf1c7'), TextLegend(color='g', icon='\uf050')], 
    labels=['cat1', 'cat2'], 
    handler_map={TextLegend: TextLegendHandler()}
)
plt.show()

enter image description here

Comments

2

Here is how the code could look like without explicitly keeping a list of handles. Adding a label= to the text can do the same job:

import matplotlib
import matplotlib.pyplot as plt
from matplotlib.legend_handler import HandlerBase
import copy

class TextHandler(HandlerBase):
    def create_artists(self, legend, orig_handle, xdescent, ydescent,
                       width, height, fontsize, trans):
        h = copy.copy(orig_handle)
        h.set_position((width / 2., height / 2.))
        h.set_transform(trans)
        h.set_ha("center");
        h.set_va("center")
        fp = orig_handle.get_font_properties().copy()
        fp.set_size(fontsize*0.8) # smaller fontsize allowing space for the textbox
        # uncomment the following line,
        # if legend symbol should have the same size as in the plot
        h.set_font_properties(fp)
        return [h]

fig, ax = plt.subplots()
bbox = {"boxstyle": "circle", "color": "PaleGoldenrod"}
ax.text(x=0.2, y=0.2, s='\u2665', color='r', size=30, bbox=bbox, label='Hearts')
ax.text(x=0.5, y=0.7, s='\u2660', color='k', size=30, bbox=bbox, label='Spades')
ax.text(x=0.8, y=0.3, s='\u2666', color='b', size=30, bbox=bbox, label='Diamonds')
ax.text(x=0.1, y=0.7, s='\u2663', color='g', size=30, bbox=bbox, label='Clubs')

ax.legend(handler_map={matplotlib.text.Text: TextHandler()})

plt.show()

legend handler for matplotlib text

Here is another example, combining text and line legend handles:

import matplotlib
import matplotlib.pyplot as plt
import numpy as np

fig, ax = plt.subplots()
x, y = np.random.normal(0.1, 1, size=(2, 1000)).cumsum(axis=1)

bbox = {"boxstyle": "circle", "color": "PaleGoldenrod"}
ax.text(x=x[0], y=y[0], s='S', color='r', size=10, bbox=bbox, label='Start')
ax.text(x=x[-1], y=y[-1], s='F', color='b', size=10, bbox=bbox, label='Finish')
ax.plot(x, y, label="Route")

ax.legend(handler_map={matplotlib.text.Text: TextHandler()})

plt.show()

combining text and line handles in legend

1 Comment

Thank you very much for this!! Got me out of a pickle!!

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.