11

I want to be able to draw a selection area on a matplotlib plot with a mouse event. I didn't find information on how to do it with python.

In the end, I want to be able to draw a region of interest with my mouse on a map created with matplotlib basemap and retrieve the corner coordinates.

Anyone has an idea, example, references?

Thanks,

Greg

class Annotate(object):
  def __init__(self):
      self.ax = plt.gca()
      self.rect = Rectangle((0,0), 1, 1, facecolor='None', edgecolor='green')
      self.x0 = None
      self.y0 = None
      self.x1 = None
      self.y1 = None
      self.ax.add_patch(self.rect)
      self.ax.figure.canvas.mpl_connect('button_press_event', self.on_press)
      self.ax.figure.canvas.mpl_connect('button_release_event', self.on_release)
      self.ax.figure.canvas.mpl_connect('motion_notify_event', self.on_motion)
  def on_press(self, event):
      print 'press'
      self.x0 = event.xdata
      self.y0 = event.ydata    
      self.x1 = event.xdata
      self.y1 = event.ydata
      self.rect.set_width(self.x1 - self.x0)
      self.rect.set_height(self.y1 - self.y0)
      self.rect.set_xy((self.x0, self.y0))
      self.rect.set_linestyle('dashed')
      self.ax.figure.canvas.draw()
  def on_motion(self,event):
      if self.on_press is True:
          return
      self.x1 = event.xdata
      self.y1 = event.ydata
      self.rect.set_width(self.x1 - self.x0)
      self.rect.set_height(self.y1 - self.y0)
      self.rect.set_xy((self.x0, self.y0))
      self.rect.set_linestyle('dashed')
      self.ax.figure.canvas.draw()
  def on_release(self, event):
      print 'release'
      self.x1 = event.xdata
      self.y1 = event.ydata
      self.rect.set_width(self.x1 - self.x0)
      self.rect.set_height(self.y1 - self.y0)
      self.rect.set_xy((self.x0, self.y0))
      self.rect.set_linestyle('solid')
      self.ax.figure.canvas.draw()
      print self.x0,self.x1,self.y0,self.y1
      return [self.x0,self.x1,self.y0,self.y1]
bmu
  • 35,119
  • 13
  • 91
  • 108
leroygr
  • 2,349
  • 4
  • 18
  • 18

2 Answers2

19

Here's a small example that shows how to use the mouse to draw a rectangle on a matplotlib plot.

import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle

class Annotate(object):
    def __init__(self):
        self.ax = plt.gca()
        self.rect = Rectangle((0,0), 1, 1)
        self.x0 = None
        self.y0 = None
        self.x1 = None
        self.y1 = None
        self.ax.add_patch(self.rect)
        self.ax.figure.canvas.mpl_connect('button_press_event', self.on_press)
        self.ax.figure.canvas.mpl_connect('button_release_event', self.on_release)

    def on_press(self, event):
        print 'press'
        self.x0 = event.xdata
        self.y0 = event.ydata

    def on_release(self, event):
        print 'release'
        self.x1 = event.xdata
        self.y1 = event.ydata
        self.rect.set_width(self.x1 - self.x0)
        self.rect.set_height(self.y1 - self.y0)
        self.rect.set_xy((self.x0, self.y0))
        self.ax.figure.canvas.draw()

a = Annotate()
plt.show()
ChrisB
  • 4,628
  • 7
  • 29
  • 41
  • Thanks, that's exactly what I wanted! And do you know what I can do to show the rectangle being drawed during the on_press event? Is it also possible to set the colors to transparent light grey? Thanks a lot – leroygr Aug 22 '12 at 09:18
  • 2
    If you want to update the drawing as you move the mouse, you want to add the line `self.ax.figure.canvas.mpl_connect('motion_notify_event', self.on_motion)` and define a method on_motion that does what you want. See http://matplotlib.sourceforge.net/examples/event_handling/index.html. To see what rectangle properties you can edit, see http://matplotlib.sourceforge.net/api/artist_api.html?highlight=rectangle#matplotlib.patches.Rectangle – ChrisB Aug 22 '12 at 15:53
  • I almost succeed! But I still have an issue: the rectangle is drawn on move but I want it to be drawn during **on_press+on_motion** events. See my new code in the original question. Thanks. – leroygr Aug 31 '12 at 09:36
  • 1
    Set a boolean is_pressed attribute to true within on_press, set it to false in on_release, and only draw the rectangle if self.is_pressed == True (note your test to if self.on_press doesn't make sense, because you're testing whether a method is True) – ChrisB Aug 31 '12 at 13:19
  • @ChrisB, thanks to your answer, the mpl_connect mechanism is much more clearer for me. – Tong May 21 '15 at 00:33
  • @ChrisB any idea how to make multiple rectangles drawn on the same figure ? – Hakim Jul 26 '16 at 16:53
6

Matplotlib provides its own RectangleSelector. There is an example on the matplotlib page, which you may adapt to your needs.

A simplified version would look something like this:

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.widgets  import RectangleSelector

xdata = np.linspace(0,9*np.pi, num=301)
ydata = np.sin(xdata)

fig, ax = plt.subplots()
line, = ax.plot(xdata, ydata)


def line_select_callback(eclick, erelease):
    x1, y1 = eclick.xdata, eclick.ydata
    x2, y2 = erelease.xdata, erelease.ydata

    rect = plt.Rectangle( (min(x1,x2),min(y1,y2)), np.abs(x1-x2), np.abs(y1-y2) )
    ax.add_patch(rect)


rs = RectangleSelector(ax, line_select_callback,
                       drawtype='box', useblit=False, button=[1], 
                       minspanx=5, minspany=5, spancoords='pixels', 
                       interactive=True)

plt.show()
ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712