Home > OS >  Is it possible to iteratively build up legend entries through multiple plot function calls?
Is it possible to iteratively build up legend entries through multiple plot function calls?

Time:12-28

Based on the question and answer here (enter image description here

Note the presence of only the final (group 3) entry in the legend.

Is there a way to get all line/path groups included in the legend so that (in this case) there are 3 items in the legend?

Bonus points if this can be handled entirely within the plot function, avoiding having to pass out handles from the plot function.

This question is not asking about multiple separate legends.

CodePudding user response:

For something to appear in the legend, matplotlib uses the label= keyword. You can use ax.get_legend_handles_labels() to find all the elements that have a label. Recombining these handles and labels can create your desired legend. Calling ax.legend multiple times will erase the old legend and set a new one.

The test code replaces ax.plot(x, y, 'k--', ... by ax.plot(x, y, '--', .... Note that here k would color the line black, but the color is already set by the color= keyword.

import matplotlib.pyplot as plt
import numpy as np

def plot(x, y, ax, col, group, **kwargs):
    ax.plot(x, y, '--', color=col, label=f"group {group}: Mean")
    ax.fill_between(x, y   10, y - 10, color=col, alpha=0.5, label='interval')
    handles, labels = ax.get_legend_handles_labels()
    print(handles, labels)
    ax.legend(handles=[(h1, h2) for h1, h2 in zip(handles[::2], handles[1::2])],
              labels=[l1   "   "  l2 for l1, l2 in zip(labels[::2], labels[1::2])])

fig, ax = plt.subplots()
x = np.linspace(1, 100, 100)
plot(x, x, ax, "C0", 1)
plot(x, x   30, ax, "C1", 2)
plot(x, x   60, ax, "C2", 3)

plt.tight_layout()
plt.show()

plot with extending the legend

CodePudding user response:

I tried the following, thinking it would work

def plot(x, y, ax, col, group, **kwargs):  
    legend = ax.get_legend() or []
    if legend:
        handles = legend.legendHandles
        labels = [txt.get_text() for txt in legend.get_texts()]
    else:
        handles = []
        labels = []
    hline, = ax.plot(x, y, ls='--', color=col)
    hpatch = ax.fill_between(x, y 10, y-10, color=col, alpha=0.5)
    
    handles  = [(hline, hpatch)]
    labels  = [f"group {group}: Mean   interval"]

    ax.legend(handles, labels)


fig, ax = plt.subplots(figsize=(9, 6))
x = np.linspace(1, 100, 100)
plot(x, x, ax, "C0", 1)
plot(x, x   30, ax, "C1", 2)
plot(x, x   60, ax, "C2", 3)

However, I get this

If we have a look at the handles, it looks like it only shows the lines

ax.get_legend().legendHandles
[<matplotlib.lines.Line2D at 0x7f066b54b370>,
 <matplotlib.lines.Line2D at 0x7f066b54b940>,
 <matplotlib.lines.Line2D at 0x7f066b54bdc0>]

I was trying to see if this is a bug or intended behavior.

I also tried

from matplotlib.lines import Line2D
from matplotlib.patches import Patch

def plot(x, y, ax, col, group, **kwargs):  
    legend = ax.get_legend() or []
    if legend:
        handles = legend.legendHandles
        labels = [txt.get_text() for txt in legend.get_texts()]
    else:
        handles = []
        labels = []
    hline, = ax.plot(x, y, ls='--', color=col)
    hpatch = ax.fill_between(x, y 10, y-10, color=col, alpha=0.5)
    label = f"group {group}: Mean   interval"
    handle = (Line2D([], [], color=col, label=label), Patch(color=col, alpha=0.5, label=label))
    handles  = [handle]
    labels  = [label]

    ax.legend(handles, labels)

but the result is still the same

  • Related