Ooops, I had forgotten to add the Wx backend. Here is a new patch.

By the way, with the wx backend, there seems to be a simple mistake in
the "show" method of the figure manager, to reproduce the traceback do
(with a recent ipython):

ipython -wthread -pylab
In [1]: f = figure()

In [2]: f.show()

Cheers,

Gaël

On Wed, Jan 30, 2008 at 03:05:13AM +0100, Gael Varoquaux wrote:
> Hi all,

> A while ago (a year or so), I was looking for a ginput-like function with
> matplotlib. For those who don't know what I am talking about, it is a
> blocking call that can be used in a script to ask the user to enter
> coordinnate by clicking on the figure. This is incredibly handy, as it
> allows some simple user interaction without worrying about events loop
> and Co.

> At the time I gave up on that. I have made progress with GUI and event
> loops, and a recent post on the matplotlib-user list put me back to work
> on this (
> http://www.nabble.com/ginput-is-here%21-%28for-wx-anyway%29-to14960506.html
> ).

> I have a preliminary patch to add this feature to matplotlib that I
> attach. I add a method to the backend canvases, to be able to deal with
> event-loop-handling, and following Jack Gurkesaft I create my own a small
> event-loop to poll for events inside my function. This is a kludge, but it
> allows to block the function while still processing events. If you have
> better ideas.

> I have implemented the functionnality for all the interactive backends I
> could test (and thus not cocoa), but I am not too sure my gtk code is
> kosher, I wouldn't mind advice on this one.

> The code of ginput itself is currently in a seperate file
> (matplotlib.ginput, that you can run for a demo) because I wasn't too
> sure where to add it.

> This is a first version of the patch. I would like comments on it,
> especially where to add the code itself, and if the proposed additions to
> the backends look all right.

> Once I have comments, I plan to:

>     * Make right click cancel an input
>     * Allow infinite number of inputs, terminated by right-click
>     * Add optional display of inputs
>     * Rework docstrings

> More suggestions welcomed,

> This could (and should, IMHO) open the road to other blocking calls for
> user interaction, like the selection of a window, as they make life
> really easy for people wanting to hack quick data analysis scripts.

> Cheers,

> Gaël

> Index: trunk/matplotlib/lib/matplotlib/backend_bases.py
> ===================================================================
> --- trunk/matplotlib/lib/matplotlib/backend_bases.py  (revision 4908)
> +++ trunk/matplotlib/lib/matplotlib/backend_bases.py  (working copy)
> @@ -1151,7 +1151,13 @@
>          """
>          return self.callbacks.disconnect(cid)

> +    def flush_events(self):
> +        """ Flush the GUI events for the figure. Implemented only for
> +        backends with GUIs.
> +        """
> +        raise NotImplementedError

> +
>  class FigureManagerBase:
>      """
>      Helper class for matlab mode, wraps everything up into a neat bundle
> Index: trunk/matplotlib/lib/matplotlib/ginput.py
> ===================================================================
> --- trunk/matplotlib/lib/matplotlib/ginput.py (revision 0)
> +++ trunk/matplotlib/lib/matplotlib/ginput.py (revision 0)
> @@ -0,0 +1,80 @@
> +
> +from matplotlib.pyplot import gcf
> +import time
> +
> +class BlockingMouseInput(object):
> +    """ Class that creates a callable object to retrieve mouse click 
> +        in a blocking way, a la MatLab. 
> +    """
> +
> +    callback = None 
> +    verbose = False
> +
> +    def on_click(self, event):
> +        """ Event handler that will be passed to the current figure to
> +            retrieve clicks.
> +        """
> +        # if it's a valid click, append the coordinates to the list
> +        if event.inaxes:
> +            self.clicks.append((event.xdata, event.ydata))
> +            if self.verbose: 
> +                print "input %i: %f,%f" % (len(self.clicks), 
> +                                    event.xdata, event.ydata)
> +
> +    def __call__(self, fig, n=1, timeout=30, verbose=False):
> +        """ Blocking call to retrieve n coordinate pairs through mouse 
> +            clicks.
> +        """
> +
> +        assert isinstance(n, int), "Requires an integer argument"
> +
> +        # Ensure that the current figure is shown 
> +        fig.show()
> +        # connect the click events to the on_click function call
> +        self.callback = fig.canvas.mpl_connect('button_press_event', 
> +                                                    self.on_click)
> +
> +        # initialize the list of click coordinates
> +        self.clicks = []
> +
> +        self.verbose = verbose
> +
> +        # wait for n clicks
> +        counter = 0
> +        while len(self.clicks) < n:
> +            fig.canvas.flush_events()
> +            # rest for a moment
> +            time.sleep(0.01)
> +
> +            # check for a timeout
> +            counter += 1
> +            if timeout > 0 and counter > timeout/0.01: 
> +                print "ginput timeout"; 
> +                break;
> +
> +        # All done! Disconnect the event and return what we have
> +        fig.canvas.mpl_disconnect(self.callback)
> +        self.callback = None
> +        return self.clicks
> +
> +
> +def ginput(n=1, timeout=30, verbose=False):
> +    """
> +    Blocking call to interact with the figure. 
> +
> +    This will wait for n clicks from the user and return a list of the 
> +    coordinates of each click.
> +
> +    If timeout is negative, does not timeout.
> +    """
> +
> +    blocking_mouse_input = BlockingMouseInput()
> +    return blocking_mouse_input(gcf(), n, timeout, verbose=verbose)
> +
> +if __name__ == "__main__":
> +    import pylab
> +    t = pylab.arange(10)
> +    pylab.plot(t, pylab.sin(t))
> +    print "Please click"
> +    ginput(3, verbose=True)
> +
> Index: trunk/matplotlib/lib/matplotlib/backends/backend_qt.py
> ===================================================================
> --- trunk/matplotlib/lib/matplotlib/backends/backend_qt.py    (revision 4908)
> +++ trunk/matplotlib/lib/matplotlib/backends/backend_qt.py    (working copy)
> @@ -175,6 +175,9 @@

>          return key

> +    def flush_events(self):
> +        qt.qApp.processEvents()
> +
>  class FigureManagerQT( FigureManagerBase ):
>      """
>      Public attributes
> Index: trunk/matplotlib/lib/matplotlib/backends/backend_gtk.py
> ===================================================================
> --- trunk/matplotlib/lib/matplotlib/backends/backend_gtk.py   (revision 4908)
> +++ trunk/matplotlib/lib/matplotlib/backends/backend_gtk.py   (working copy)
> @@ -386,6 +386,13 @@
>      def get_default_filetype(self):
>          return 'png'

> +    def flush_events(self):
> +        gtk.gdk.threads_enter()        
> +        while gtk.events_pending():
> +            gtk.main_iteration(True)
> +        gtk.gdk.flush()
> +        gtk.gdk.threads_leave()
> +        

>  class FigureManagerGTK(FigureManagerBase):
>      """
> Index: trunk/matplotlib/lib/matplotlib/backends/backend_tkagg.py
> ===================================================================
> --- trunk/matplotlib/lib/matplotlib/backends/backend_tkagg.py (revision 4908)
> +++ trunk/matplotlib/lib/matplotlib/backends/backend_tkagg.py (working copy)
> @@ -269,8 +269,9 @@
>          key = self._get_key(event)
>          FigureCanvasBase.key_release_event(self, key, guiEvent=event)

> +    def flush_events(self):
> +        self._master.update()

> -
>  class FigureManagerTkAgg(FigureManagerBase):
>      """
>      Public attributes
> Index: trunk/matplotlib/lib/matplotlib/backends/backend_qt4.py
> ===================================================================
> --- trunk/matplotlib/lib/matplotlib/backends/backend_qt4.py   (revision 4908)
> +++ trunk/matplotlib/lib/matplotlib/backends/backend_qt4.py   (working copy)
> @@ -13,7 +13,7 @@
>  from matplotlib.mathtext import MathTextParser
>  from matplotlib.widgets import SubplotTool

> -from PyQt4 import QtCore, QtGui
> +from PyQt4 import QtCore, QtGui, Qt

>  backend_version = "0.9.1"
>  def fn_name(): return sys._getframe(1).f_code.co_name
> @@ -174,6 +174,9 @@

>          return key

> +    def flush_events(self):
> +        Qt.qApp.processEvents()
> +
>  class FigureManagerQT( FigureManagerBase ):
>      """
>      Public attributes

> -------------------------------------------------------------------------
> This SF.net email is sponsored by: Microsoft
> Defy all challenges. Microsoft(R) Visual Studio 2008.
> http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
> _______________________________________________
> Matplotlib-devel mailing list
> Matplotlib-devel@lists.sourceforge.net
> https://lists.sourceforge.net/lists/listinfo/matplotlib-devel


-- 
  Gael Varoquaux,
  Quantum degenerate gases group
  European Laboratory for Non-Linear Spectroscopy
    University of Florence, Polo Scientifico
    Via Nello Carrara 1, I-50019-Sesto-Fiorentino (Firenze) Italy
    Phone: ++ 390-55-457242145      Fax: ++ 390-55-4572451
  ++ and ++
  Groupe d'optique atomique,
  Laboratoire Charles Fabry de l'Institut d'Optique
    Campus Polytechnique, RD 128, 91127 Palaiseau cedex FRANCE
    Tel : 33 (0) 1 64 53 33 23 - Fax : 33 (0) 1 64 53 31 01
    Labs: 33 (0) 1 64 53 33 63 - 33 (0) 1 64 53 33 62

Index: trunk/matplotlib/lib/matplotlib/backend_bases.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/backend_bases.py    (revision 4908)
+++ trunk/matplotlib/lib/matplotlib/backend_bases.py    (working copy)
@@ -1151,7 +1151,13 @@
         """
         return self.callbacks.disconnect(cid)
 
+    def flush_events(self):
+        """ Flush the GUI events for the figure. Implemented only for
+        backends with GUIs.
+        """
+        raise NotImplementedError
 
+
 class FigureManagerBase:
     """
     Helper class for matlab mode, wraps everything up into a neat bundle
Index: trunk/matplotlib/lib/matplotlib/ginput.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/ginput.py   (revision 0)
+++ trunk/matplotlib/lib/matplotlib/ginput.py   (revision 0)
@@ -0,0 +1,80 @@
+
+from matplotlib.pyplot import gcf
+import time
+
+class BlockingMouseInput(object):
+    """ Class that creates a callable object to retrieve mouse click 
+        in a blocking way, a la MatLab. 
+    """
+
+    callback = None 
+    verbose = False
+
+    def on_click(self, event):
+        """ Event handler that will be passed to the current figure to
+            retrieve clicks.
+        """
+        # if it's a valid click, append the coordinates to the list
+        if event.inaxes:
+            self.clicks.append((event.xdata, event.ydata))
+            if self.verbose: 
+                print "input %i: %f,%f" % (len(self.clicks), 
+                                    event.xdata, event.ydata)
+
+    def __call__(self, fig, n=1, timeout=30, verbose=False):
+        """ Blocking call to retrieve n coordinate pairs through mouse 
+            clicks.
+        """
+
+        assert isinstance(n, int), "Requires an integer argument"
+
+        # Ensure that the current figure is shown 
+        fig.show()
+        # connect the click events to the on_click function call
+        self.callback = fig.canvas.mpl_connect('button_press_event', 
+                                                    self.on_click)
+
+        # initialize the list of click coordinates
+        self.clicks = []
+
+        self.verbose = verbose
+
+        # wait for n clicks
+        counter = 0
+        while len(self.clicks) < n:
+            fig.canvas.flush_events()
+            # rest for a moment
+            time.sleep(0.01)
+
+            # check for a timeout
+            counter += 1
+            if timeout > 0 and counter > timeout/0.01: 
+                print "ginput timeout"; 
+                break;
+
+        # All done! Disconnect the event and return what we have
+        fig.canvas.mpl_disconnect(self.callback)
+        self.callback = None
+        return self.clicks
+
+
+def ginput(n=1, timeout=30, verbose=False):
+    """
+    Blocking call to interact with the figure. 
+
+    This will wait for n clicks from the user and return a list of the 
+    coordinates of each click.
+
+    If timeout is negative, does not timeout.
+    """
+
+    blocking_mouse_input = BlockingMouseInput()
+    return blocking_mouse_input(gcf(), n, timeout, verbose=verbose)
+
+if __name__ == "__main__":
+    import pylab
+    t = pylab.arange(10)
+    pylab.plot(t, pylab.sin(t))
+    print "Please click"
+    ginput(3, verbose=True)
+
Index: trunk/matplotlib/lib/matplotlib/backends/backend_qt.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/backends/backend_qt.py      (revision 4908)
+++ trunk/matplotlib/lib/matplotlib/backends/backend_qt.py      (working copy)
@@ -175,6 +175,9 @@
 
         return key
 
+    def flush_events(self):
+        qt.qApp.processEvents()
+
 class FigureManagerQT( FigureManagerBase ):
     """
     Public attributes
Index: trunk/matplotlib/lib/matplotlib/backends/backend_gtk.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/backends/backend_gtk.py     (revision 4908)
+++ trunk/matplotlib/lib/matplotlib/backends/backend_gtk.py     (working copy)
@@ -386,6 +386,13 @@
     def get_default_filetype(self):
         return 'png'
 
+    def flush_events(self):
+        gtk.gdk.threads_enter()        
+        while gtk.events_pending():
+            gtk.main_iteration(True)
+        gtk.gdk.flush()
+        gtk.gdk.threads_leave()
+        
             
 class FigureManagerGTK(FigureManagerBase):
     """
Index: trunk/matplotlib/lib/matplotlib/backends/backend_tkagg.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/backends/backend_tkagg.py   (revision 4908)
+++ trunk/matplotlib/lib/matplotlib/backends/backend_tkagg.py   (working copy)
@@ -269,8 +269,9 @@
         key = self._get_key(event)
         FigureCanvasBase.key_release_event(self, key, guiEvent=event)
 
+    def flush_events(self):
+        self._master.update()
 
-
 class FigureManagerTkAgg(FigureManagerBase):
     """
     Public attributes
Index: trunk/matplotlib/lib/matplotlib/backends/backend_wx.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/backends/backend_wx.py      (revision 4908)
+++ trunk/matplotlib/lib/matplotlib/backends/backend_wx.py      (working copy)
@@ -1301,6 +1301,9 @@
             wxapp.Yield()
         return True
 
+    def flush_events(self):
+        wx.Yield()
+
 class FigureManagerWx(FigureManagerBase):
     """
     This class contains the FigureCanvas and GUI frame
Index: trunk/matplotlib/lib/matplotlib/backends/backend_qt4.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/backends/backend_qt4.py     (revision 4908)
+++ trunk/matplotlib/lib/matplotlib/backends/backend_qt4.py     (working copy)
@@ -13,7 +13,7 @@
 from matplotlib.mathtext import MathTextParser
 from matplotlib.widgets import SubplotTool
 
-from PyQt4 import QtCore, QtGui
+from PyQt4 import QtCore, QtGui, Qt
 
 backend_version = "0.9.1"
 def fn_name(): return sys._getframe(1).f_code.co_name
@@ -174,6 +174,9 @@
 
         return key
 
+    def flush_events(self):
+        Qt.qApp.processEvents()
+
 class FigureManagerQT( FigureManagerBase ):
     """
     Public attributes
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
_______________________________________________
Matplotlib-devel mailing list
Matplotlib-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-devel

Reply via email to