Search code examples
pythonmatplotlibseaborn

remove legend handles and labels completely


Some plotting tools such as seaborn automatically label plots and add a legend. Removing the legend is fairly easy with ax.get_legend().remove(), but the handles and labels are still stored somewhere in the axes object. Thus when adding another line or other plot type, for which I want to have a legend, the "old" legend handles and labels are also displayed. In other words: ax.get_legend_handles_labels() still returns the labels and handles introduced by f.i. seaborn.

Is there any way to completely remove the handles and labels to be used for a legend from an axis? Such that ax.get_legend_handles_labels() returns ([], [])?

I know that I can set sns.lineplot(..., legend=False) in seaborn. I am just using seaborn to produce a nice example. The question is how to remove existing legend labels and handles in general.

Here is a minimum working example:

import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns

df = pd.DataFrame(data={
    'x': [0, 1, 0, 1], 
    'y': [7.2, 10., 11.2, 3.],
    'id': [4, 4, 7, 7]
})
# get mean per x location:
grpd = df.groupby('x').mean()

ax = sns.lineplot(data=df, x='x', y='y', hue='id', palette=['b', 'g'])
ax.get_legend().remove()

# this still returns the handles and labels:
print(ax.get_legend_handles_labels())
# Out: 
# ([<matplotlib.lines.Line2D at 0x17c1c208f88>,
#   <matplotlib.lines.Line2D at 0x17c1c1f8888>,
#   <matplotlib.lines.Line2D at 0x17c1c20af88>],
#  ['id', '4', '7'])

# plot some other lines, for which I want to have a legend:
ax.plot(grpd.index, grpd['y'], label='mean')
# add legend, ONLY FOR MEAN, if possible:
ax.legend()

I tried manipulating the axes ax.get_legend() and ax.legend_ objects, f.i. with ax.legend_.legendHandles = [], setting the labels, texts and everything else, but the "old" seaborn entries keep reappiering each time.

Since this is meant to be used within a module, it is not applicable to explicitly set a legend only containing the "mean"-entry. I need to "clear" everything happening in the backend to provide a clean API to the user.


Solution

  • Change:

    ax = sns.lineplot(data=df, x='x', y='y', hue='id', palette=['b', 'g'])
    

    to:

    ax = sns.lineplot(data=df, x='x', y='y', hue='id', palette=['b', 'g'], legend=False)
    

    That way you also don't need to make the call to remove the legend as none is created in the first place.

    EDIT UPDATE:

    If you want to clear the legend handles and label, I think you'll have to do something like:

    for line in ax.lines: # put this before you call the 'mean' plot function.
        line.set_label(s='')
    

    This will loop over the lines draw on the plot and then set their label to be empty. That way the legend doesn't consider them and the print(ax.get_legend_handles_labels()) will return empty iterables.