Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a draggable legend in matplotlib?

I'm drawing a legend on an axes object in matplotlib but the default positioning which claims to place it in a smart place doesn't seem to work. Ideally, I'd like to have the legend be draggable by the user. How can this be done?

like image 356
Adam Fraser Avatar asked Mar 29 '10 16:03

Adam Fraser


3 Answers

Note: This is now built into matplotlib

leg = plt.legend()
if leg:
    leg.draggable()

will work as expected


Well, I found bits and pieces of the solution scattered among mailing lists. I've come up with a nice modular chunk of code that you can drop in and use... here it is:

class DraggableLegend:
    def __init__(self, legend):
        self.legend = legend
        self.gotLegend = False
        legend.figure.canvas.mpl_connect('motion_notify_event', self.on_motion)
        legend.figure.canvas.mpl_connect('pick_event', self.on_pick)
        legend.figure.canvas.mpl_connect('button_release_event', self.on_release)
        legend.set_picker(self.my_legend_picker)

    def on_motion(self, evt):
        if self.gotLegend:
            dx = evt.x - self.mouse_x
            dy = evt.y - self.mouse_y
            loc_in_canvas = self.legend_x + dx, self.legend_y + dy
            loc_in_norm_axes = self.legend.parent.transAxes.inverted().transform_point(loc_in_canvas)
            self.legend._loc = tuple(loc_in_norm_axes)
            self.legend.figure.canvas.draw()

    def my_legend_picker(self, legend, evt): 
        return self.legend.legendPatch.contains(evt)   

    def on_pick(self, evt): 
        if evt.artist == self.legend:
            bbox = self.legend.get_window_extent()
            self.mouse_x = evt.mouseevent.x
            self.mouse_y = evt.mouseevent.y
            self.legend_x = bbox.xmin
            self.legend_y = bbox.ymin 
            self.gotLegend = 1

    def on_release(self, event):
        if self.gotLegend:
            self.gotLegend = False

...and in your code...

def draw(self): 
    ax = self.figure.add_subplot(111)
    scatter = ax.scatter(np.random.randn(100), np.random.randn(100))


legend = DraggableLegend(ax.legend())

I emailed the Matplotlib-users group and John Hunter was kind enough to add my solution it to SVN HEAD.

On Thu, Jan 28, 2010 at 3:02 PM, Adam Fraser wrote:

I thought I'd share a solution to the draggable legend problem since it took me forever to assimilate all the scattered knowledge on the mailing lists...

Cool -- nice example. I added the code to legend.py. Now you can do

leg = ax.legend()
leg.draggable()

to enable draggable mode. You can repeatedly call this func to toggle the draggable state.

I hope this is helpful to people working with matplotlib.

like image 168
Adam Fraser Avatar answered Sep 25 '22 13:09

Adam Fraser


In newer versions of Matplotlib (v1.0.1), this is built-in.

def draw(self): 
    ax = self.figure.add_subplot(111)
    scatter = ax.scatter(np.random.randn(100), np.random.randn(100))
    legend = ax.legend()
    legend.draggable(state=True)

If you are using matplotlib interactively (for example, in IPython's pylab mode).

plot(range(10), range(10), label="test label")
plot(range(10), [5 for x in range(10)], label="another test")
l = legend()
l.draggable(True)
like image 21
Tim Swast Avatar answered Sep 28 '22 13:09

Tim Swast


In even newer versions (3.0.2) it is deprecated and will potentially be representing a property in future versions (hence, it will not be callable).

plot(range(10), range(10), label="test label")
plot(range(10), [5 for x in range(10)], label="another test")
plt.legend().set_draggable(True)
like image 24
borgr Avatar answered Sep 28 '22 13:09

borgr