Home > Software design >  Matplotlib - legend picking on fill_between
Matplotlib - legend picking on fill_between

Time:07-18

I am trying to use legend picking (Link to legend picking dokumentation) on matplotlib's fill_between (Link to fill_between dokumentation).

Its works just fine when i use matplotlib.pyplot.plot but when i use it on matplotlib.pyplot.fill_between nothing happens, but i receive no errors to fix. I suspect i has something to do with the fill command not being lines and i use the get_lines() function, but i am having a hard time to actually understand what is happening.

The extra comma on output "the working line" using plt.plot is due to plot returns a tuple and fill_between return a value.

    df = pd.DataFrame(...)
    
    x_dist = df['distance']
    df.pop('distance')

    labels = []
    lines = []

    fig, ax = plt.subplots()

    cols = df.columns
    for i, col in enumerate(cols):
        bot = df[cols[i]]  # layer bot as bot
        top = df[cols[i - 1]]  # previous layer bot as top

        # plot layer if the layer has different values from the layers above
        if col[:4] != '0000':  # discard terrain (first entry)
            if not bot.equals(top):
                pl = ax.fill_between(x_dist, top, bot, label=col)  # non-working line
                # pl, = ax.plot(x_dist, bot, label=col)  # working line
                lines.append(pl)
                labels.append(pl.get_label())

    # set grid on plot
    ax.grid('both')

    # set legend on plot and place it outside plot
    box = ax.get_position()
    ax.set_position([box.x0, box.y0, box.width * 0.8, box.height])
    leg = ax.legend(fancybox=True, shadow=True, loc='center left', bbox_to_anchor=(1, 0.5))

    lined = {}  # Will map legend lines to original lines.
    for legline, origline in zip(leg.get_lines(), lines):
        legline.set_picker(True)  # Enable picking on the legend line.
        lined[legline] = origline

    def on_pick(event):
        legline = event.artist
        origline = lined[legline]
        visible = not origline.get_visible()
        origline.set_visible(visible)
        legline.set_alpha(1.0 if visible else 0.2)
        fig.canvas.draw()

    fig.canvas.mpl_connect('pick_event', on_pick)
    plt.show()

CodePudding user response:

Found a solution.

Instead of the leg.get_lines() is used leg.findobj where i am searching for the patches.Rectangles using the "match" parameter in the findobj function. remember to import the library "from matplotlib import patches".

For some reason when clicking the legend and disabling the plot and changing the opacity of the legend rectangle, the color of the rectangle changes to default blue (don't know why?!). To fix this i had to get the color from the fill_between plot and set the color of the legend rectangle within the on_pick function.

The solution to my code above:

df = pd.DataFrame(...)

x_dist = df['distance']
df.pop('distance')

labels = []
lines = []

fig, ax = plt.subplots()

cols = df.columns
for i, col in enumerate(cols):
    bot = df[cols[i]]  # layer bot as bot
    top = df[cols[i - 1]]  # previous layer bot as top

    # plot layer if the layer has different values from the layers above
    if col[:4] != '0000':  # discard terrain (first entry)
        if not bot.equals(top):
            pl = ax.fill_between(x_dist, top, bot, label=col)  # non-working line
            # pl, = ax.plot(x_dist, bot, label=col)  # working line
            lines.append(pl)
            labels.append(pl.get_label())

# set grid on plot
ax.grid('both')

# set legend on plot and place it outside plot
box = ax.get_position()
ax.set_position([box.x0, box.y0, box.width * 0.8, box.height])
leg = ax.legend(fancybox=True, shadow=True, loc='center left', bbox_to_anchor=(1, 0.5))

lined = {}  # Will map legend lines to original lines.
for legline, origline in zip(leg.findobj(patches.Rectangle), lines):
    legline.set_picker(True)  # Enable picking on the legend line.
    lined[legline] = origline

def on_pick(event):
    legline = event.artist
    origline = lined[legline]
    color_fill = origline.get_facecolor()
    visible = not origline.get_visible()
    origline.set_visible(visible)
    legline.set_alpha(1.0 if visible else 0.2)
    legline.set_color(color_fill)
    fig.canvas.draw()

fig.canvas.mpl_connect('pick_event', on_pick)
plt.show()

A more generic answer:

from matplotlib import pyplot as plt, patches
import numpy as np

x = np.linspace(1, 10, 10)
y1 = np.linspace(1, 5, 10)
y2 = np.linspace(10, 20, 10)

fig, ax = plt.subplots()
pl = ax.fill_between(x, y1, y2, label='fill_plot')
leg = ax.legend()
fill_plots = [pl]

leg_to_fill = {}
for leg_obj, fill_plot in zip(leg.findobj(patches.Rectangle), fill_plots):
    leg_obj.set_picker(True)
    leg_to_fill[leg_obj] = fill_plot


def on_pick(event):
    leg_obj = event.artist
    fill_plot = leg_to_fill[leg_obj]
    fill_plot_color = fill_plot.get_facecolor()
    visible = not fill_plot.get_visible()
    fill_plot.set_visible(visible)
    leg_obj.set_alpha(1.0 if visible else 0.2)
    leg_obj.set_color(fill_plot_color)
    fig.canvas.draw()

fig.canvas.mpl_connect('pick_event', on_pick)
plt.show()
  • Related