Hiding lines after showing a pyplot figure
If you'd like, you can hook up a callback to the legend that will show/hide lines when they're clicked. There's a simple example here: http://matplotlib.org/examples/event_handling/legend_picking.html
Here's a "fancier" example that should work without needing to manually specify the relationship of the lines and legend markers (Also has a few more features).
(Updated version in August 2019, as a response to repeated reports about this not working correctly; now it should! For the old version see version history)
import numpy as np
import matplotlib.pyplot as plt
def main():
x = np.arange(10)
fig, ax = plt.subplots()
for i in range(1, 31):
ax.plot(x, i * x, label=r'$y={}x$'.format(i))
ax.legend(loc='upper left', bbox_to_anchor=(1.05, 1),
ncol=2, borderaxespad=0)
fig.subplots_adjust(right=0.55)
fig.suptitle('Right-click to hide all\nMiddle-click to show all',
va='top', size='large')
leg = interactive_legend()
return fig, ax, leg
def interactive_legend(ax=None):
if ax is None:
ax = plt.gca()
if ax.legend_ is None:
ax.legend()
return InteractiveLegend(ax.get_legend())
class InteractiveLegend(object):
def __init__(self, legend):
self.legend = legend
self.fig = legend.axes.figure
self.lookup_artist, self.lookup_handle = self._build_lookups(legend)
self._setup_connections()
self.update()
def _setup_connections(self):
for artist in self.legend.texts + self.legend.legendHandles:
artist.set_picker(10) # 10 points tolerance
self.fig.canvas.mpl_connect('pick_event', self.on_pick)
self.fig.canvas.mpl_connect('button_press_event', self.on_click)
def _build_lookups(self, legend):
labels = [t.get_text() for t in legend.texts]
handles = legend.legendHandles
label2handle = dict(zip(labels, handles))
handle2text = dict(zip(handles, legend.texts))
lookup_artist = {}
lookup_handle = {}
for artist in legend.axes.get_children():
if artist.get_label() in labels:
handle = label2handle[artist.get_label()]
lookup_handle[artist] = handle
lookup_artist[handle] = artist
lookup_artist[handle2text[handle]] = artist
lookup_handle.update(zip(handles, handles))
lookup_handle.update(zip(legend.texts, handles))
return lookup_artist, lookup_handle
def on_pick(self, event):
handle = event.artist
if handle in self.lookup_artist:
artist = self.lookup_artist[handle]
artist.set_visible(not artist.get_visible())
self.update()
def on_click(self, event):
if event.button == 3:
visible = False
elif event.button == 2:
visible = True
else:
return
for artist in self.lookup_artist.values():
artist.set_visible(visible)
self.update()
def update(self):
for artist in self.lookup_artist.values():
handle = self.lookup_handle[artist]
if artist.get_visible():
handle.set_visible(True)
else:
handle.set_visible(False)
self.fig.canvas.draw()
def show(self):
plt.show()
if __name__ == '__main__':
fig, ax, leg = main()
plt.show()
This allows you to click on legend items to toggle their corresponding artists on/off. For example, you can go from this:
To this:
Thanks for the post! I extended the class above such that it can handle multiple legends - such as for example if you are using subplots. (I am sharing it here since i could not find any other example somewhere else... and it might be handy for someone else...)
class InteractiveLegend(object):
def __init__(self):
self.legends = []
self.figures = []
self.lookup_artists = []
self.lookup_handles = []
self.host = socket.gethostname()
def add_legends(self, legend):
self.legends.append(legend)
def init_legends(self):
for legend in self.legends:
self.figures.append(legend.axes.figure)
lookup_artist, lookup_handle = self._build_lookups(legend)
#print("init", type(lookup))
self.lookup_artists.append(lookup_artist)
self.lookup_handles.append(lookup_handle)
self._setup_connections()
self.update()
def _setup_connections(self):
for legend in self.legends:
for artist in legend.texts + legend.legendHandles:
artist.set_picker(10) # 10 points tolerance
for figs in self.figures:
figs.canvas.mpl_connect('pick_event', self.on_pick)
figs.canvas.mpl_connect('button_press_event', self.on_click)
def _build_lookups(self, legend):
labels = [t.get_text() for t in legend.texts]
handles = legend.legendHandles
label2handle = dict(zip(labels, handles))
handle2text = dict(zip(handles, legend.texts))
lookup_artist = {}
lookup_handle = {}
for artist in legend.axes.get_children():
if artist.get_label() in labels:
handle = label2handle[artist.get_label()]
lookup_handle[artist] = handle
lookup_artist[handle] = artist
lookup_artist[handle2text[handle]] = artist
lookup_handle.update(zip(handles, handles))
lookup_handle.update(zip(legend.texts, handles))
#print("build", type(lookup_handle))
return lookup_artist, lookup_handle
def on_pick(self, event):
#print event.artist
handle = event.artist
for lookup_artist in self.lookup_artists:
if handle in lookup_artist:
artist = lookup_artist[handle]
artist.set_visible(not artist.get_visible())
self.update()
def on_click(self, event):
if event.button == 3:
visible = False
elif event.button == 2:
visible = True
else:
return
for lookup_artist in self.lookup_artists:
for artist in lookup_artist.values():
artist.set_visible(visible)
self.update()
def update(self):
for idx, lookup_artist in enumerate(self.lookup_artists):
for artist in lookup_artist.values():
handle = self.lookup_handles[idx][artist]
if artist.get_visible():
handle.set_visible(True)
else:
handle.set_visible(False)
self.figures[idx].canvas.draw()
def show(self):
plt.show()
use it as follow:
leg1 = ax1.legend(loc='upper left', bbox_to_anchor=(1.05, 1), ncol=2, borderaxespad=0)
leg2 = ax2.legend(loc='upper left', bbox_to_anchor=(1.05, 1), ncol=2, borderaxespad=0)
fig.subplots_adjust(right=0.7)
interactive_legend = InteractiveLegend()
interactive_legend.add_legends(leg1)
interactive_legend.add_legends(leg2)
interactive_legend.init_legends()
interactive_legend.show()
Inspired by @JoeKington's answer, here is what I use (a slightly modified version, that doesn't require ax, fig
but can work directly with plt.plot(...)
; also plt.legend()
is kept outside out the scope of the main object):
Ready-to-use example pltinteractivelegend.py
:
import numpy as np
import matplotlib.pyplot as plt
class InteractiveLegend(object):
def __init__(self, legend=None):
if legend == None:
legend = plt.gca().get_legend()
self.legend = legend
self.fig = legend.axes.figure
self.lookup_artist, self.lookup_handle = self._build_lookups(legend)
self._setup_connections()
self.update()
def _setup_connections(self):
for artist in self.legend.texts + self.legend.legendHandles:
artist.set_picker(10) # 10 points tolerance
self.fig.canvas.mpl_connect('pick_event', self.on_pick)
self.fig.canvas.mpl_connect('button_press_event', self.on_click)
def _build_lookups(self, legend):
labels = [t.get_text() for t in legend.texts]
handles = legend.legendHandles
label2handle = dict(zip(labels, handles))
handle2text = dict(zip(handles, legend.texts))
lookup_artist = {}
lookup_handle = {}
for artist in legend.axes.get_children():
if artist.get_label() in labels:
handle = label2handle[artist.get_label()]
lookup_handle[artist] = handle
lookup_artist[handle] = artist
lookup_artist[handle2text[handle]] = artist
lookup_handle.update(zip(handles, handles))
lookup_handle.update(zip(legend.texts, handles))
return lookup_artist, lookup_handle
def on_pick(self, event):
handle = event.artist
if handle in self.lookup_artist:
artist = self.lookup_artist[handle]
artist.set_visible(not artist.get_visible())
self.update()
def on_click(self, event):
if event.button == 3:
visible = False
elif event.button == 2:
visible = True
else:
return
for artist in self.lookup_artist.values():
artist.set_visible(visible)
self.update()
def update(self):
for artist in self.lookup_artist.values():
handle = self.lookup_handle[artist]
if artist.get_visible():
handle.set_visible(True)
else:
handle.set_visible(False)
self.fig.canvas.draw()
if __name__ == '__main__':
for i in range(20):
plt.plot(np.random.randn(1000), label=i)
plt.legend()
leg = InteractiveLegend()
plt.show()
Usage as a library:
import numpy as np
import matplotlib.pyplot as plt
import pltinteractivelegend
for i in range(20):
plt.plot(np.random.randn(1000), label=i)
plt.legend()
leg = pltinteractivelegend.InteractiveLegend() # mandatory: keep the object with leg = ...; else it won't work
plt.show()