Hiding lines after showing a pyplot figure

前端 未结 3 1947
被撕碎了的回忆
被撕碎了的回忆 2020-12-04 22:33

I\'m using pyplot to display a line graph of up to 30 lines. I would like to add a way to quickly show and hide individual lines on the graph. Pyplot does have a menu wher

相关标签:
3条回答
  • 2020-12-04 23:17

    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()
    
    0 讨论(0)
  • 2020-12-04 23:22

    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:

    enter image description here

    To this:

    enter image description here

    0 讨论(0)
  • 2020-12-04 23:23

    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()
    
    0 讨论(0)
提交回复
热议问题