Revision: 5799
          http://matplotlib.svn.sourceforge.net/matplotlib/?rev=5799&view=rev
Author:   dmkaplan
Date:     2008-07-21 12:26:35 +0000 (Mon, 21 Jul 2008)

Log Message:
-----------
Recommitting my changes to clabel to allow for manual labeling after fixing
problems with indexing identified by Eric Firing on 19 July 2008.

In addition, I have made the following changes:

1) Placed more comments in contour.py with the intention of eventually doing
a rewrite or restructuring of that code.

2) Added two pylab_examples: contour_label_demo.py that tests out some of the
more advanced things that can now be done with contour labeling, and
ginput_manual_clabel.py that demonstrates some uses of ginput,
waitforbuttonpress, and clabel(...,manual=True).  The first of these has been
integrated into backend_driver.py, but the second cannot be because it
requires interaction.

Modified Paths:
--------------
    trunk/matplotlib/examples/tests/backend_driver.py
    trunk/matplotlib/lib/matplotlib/blocking_input.py
    trunk/matplotlib/lib/matplotlib/contour.py

Added Paths:
-----------
    trunk/matplotlib/examples/pylab_examples/contour_label_demo.py
    trunk/matplotlib/examples/pylab_examples/ginput_manual_clabel.py

Added: trunk/matplotlib/examples/pylab_examples/contour_label_demo.py
===================================================================
--- trunk/matplotlib/examples/pylab_examples/contour_label_demo.py              
                (rev 0)
+++ trunk/matplotlib/examples/pylab_examples/contour_label_demo.py      
2008-07-21 12:26:35 UTC (rev 5799)
@@ -0,0 +1,74 @@
+#!/usr/bin/env python
+"""
+Illustrate some of the more advanced things that one can do with
+contour labels.
+
+See also contour_demo.py.
+"""
+import matplotlib
+import numpy as np
+import matplotlib.cm as cm
+import matplotlib.mlab as mlab
+import matplotlib.pyplot as plt
+
+matplotlib.rcParams['xtick.direction'] = 'out'
+matplotlib.rcParams['ytick.direction'] = 'out'
+
+##################################################
+# Define our surface
+##################################################
+delta = 0.025
+x = np.arange(-3.0, 3.0, delta)
+y = np.arange(-2.0, 2.0, delta)
+X, Y = np.meshgrid(x, y)
+Z1 = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)
+Z2 = mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1)
+# difference of Gaussians
+Z = 10.0 * (Z2 - Z1)
+
+##################################################
+# Make contour labels using creative float classes
+# Follows suggestion of Manuel Metz
+##################################################
+plt.figure()
+
+# Basic contour plot
+CS = plt.contour(X, Y, Z)
+
+# Define a class that forces representation of float to look a certain way
+# This remove trailing zero so '1.0' becomes '1'
+class nf(float):
+     def __repr__(self):
+         str = '%.1f' % (self.__float__(),)
+         if str[-1]=='0':
+             return '%.0f' % self.__float__()
+         else:
+             return '%.1f' % self.__float__()
+
+# Recast levels to new class
+CS.levels = [nf(val) for val in CS.levels ]
+
+# Label levels with specially formatted floats
+plt.clabel(CS, CS.levels, inline=True, fmt='%r %%', fontsize=10)
+
+##################################################
+# Label contours with arbitrary strings using a
+# dictionary
+##################################################
+plt.figure()
+
+# Basic contour plot
+CS = plt.contour(X, Y, Z)
+
+fmt = {}
+strs = [ 'first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh' ]
+for l,s in zip( CS.levels, strs ):
+    fmt[l] = s
+
+# Label every other level using strings
+plt.clabel(CS,CS.levels[::2],inline=True,fmt=fmt,fontsize=10)
+
+##################################################
+# Show the hole thing
+##################################################
+plt.show()

Added: trunk/matplotlib/examples/pylab_examples/ginput_manual_clabel.py
===================================================================
--- trunk/matplotlib/examples/pylab_examples/ginput_manual_clabel.py            
                (rev 0)
+++ trunk/matplotlib/examples/pylab_examples/ginput_manual_clabel.py    
2008-07-21 12:26:35 UTC (rev 5799)
@@ -0,0 +1,88 @@
+#!/usr/bin/env python
+"""
+This provides examples of uses of interactive functions, such as ginput,
+waitforbuttonpress and manual clabel placement.
+
+This script must be run interactively using a backend that has a
+graphical user interface (for example, from inside ipython using
+GTKAgg backend, but not PS backend).
+"""
+import time
+import matplotlib
+import numpy as np
+import matplotlib.cm as cm
+import matplotlib.mlab as mlab
+import matplotlib.pyplot as plt
+
+def tellme(s):
+    print s
+    plt.title(s,fontsize=16)
+    plt.draw()
+
+##################################################
+# Define a triangle by clicking three points
+##################################################
+plt.clf()
+plt.axis([-1.,1.,-1.,1.])
+plt.setp(plt.gca(),autoscale_on=False)
+
+tellme('You will define a triangle, click to begin')
+
+plt.waitforbuttonpress()
+
+happy = False
+while not happy:
+    pts = []
+    while len(pts) < 3:
+        tellme('Select 3 corners with mouse')
+        pts = np.asarray( plt.ginput(3,timeout=-1) )
+        if len(pts) < 3:
+            tellme('Too few points, starting over')
+            time.sleep(1) # Wait a second
+
+    ph = plt.fill( pts[:,0], pts[:,1], 'r', lw=2 )
+
+    tellme('Happy? Key click for yes, mouse click for no')
+
+    happy = plt.waitforbuttonpress()
+
+    # Get rid of fill
+    if not happy:
+        for p in ph: p.remove()
+
+##################################################
+# Now contour according to distance from triangle
+# corners - just an example
+##################################################
+
+# Define a nice function of distance from individual pts
+def f(x,y,pts):
+    z = np.zeros_like(x)
+    for p in pts:
+        z = z + 1/(np.sqrt((x-p[0])**2+(y-p[1])**2))
+    return 1/z
+
+X,Y = np.meshgrid( np.linspace(-1,1,51), np.linspace(-1,1,51) )
+Z = f(X,Y,pts)
+
+CS = plt.contour( X, Y, Z, 20 )
+
+tellme( 'Use mouse to select contour label locations, middle button to finish' 
)
+CL = plt.clabel( CS, manual=True )
+
+##################################################
+# Now do a zoom
+##################################################
+tellme( 'Now do a nested zoom, click to begin' )
+plt.waitforbuttonpress()
+
+happy = False
+while not happy:
+    tellme( 'Select two corners of zoom, middle mouse button to finish' )
+    pts = np.asarray( plt.ginput(2,timeout=-1) )
+
+    happy = len(pts) < 2
+    if happy: break
+
+    pts = np.sort(pts,axis=0)
+    plt.axis( pts.T.ravel() )

Modified: trunk/matplotlib/examples/tests/backend_driver.py
===================================================================
--- trunk/matplotlib/examples/tests/backend_driver.py   2008-07-21 09:50:36 UTC 
(rev 5798)
+++ trunk/matplotlib/examples/tests/backend_driver.py   2008-07-21 12:26:35 UTC 
(rev 5799)
@@ -39,6 +39,7 @@
     'color_demo.py',
     'cohere_demo.py',
     'contour_demo.py',
+    'contour_label_demo.py',
     'contourf_demo.py',
     'csd_demo.py',
     'custom_ticker1.py',

Modified: trunk/matplotlib/lib/matplotlib/blocking_input.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/blocking_input.py   2008-07-21 09:50:36 UTC 
(rev 5798)
+++ trunk/matplotlib/lib/matplotlib/blocking_input.py   2008-07-21 12:26:35 UTC 
(rev 5799)
@@ -5,11 +5,11 @@
     creates a callable object to retrieve events in a blocking way for 
interactive sessions
 
 :class:`BlockingKeyMouseInput`
-    creates a callable object to retrieve key or mouse clicks in a blocking 
way for interactive sessions.  
+    creates a callable object to retrieve key or mouse clicks in a blocking 
way for interactive sessions.
     Note: Subclass of BlockingInput. Used by waitforbuttonpress
 
 :class:`BlockingMouseInput`
-    creates a callable object to retrieve mouse clicks in a blocking way for 
interactive sessions.  
+    creates a callable object to retrieve mouse clicks in a blocking way for 
interactive sessions.
     Note: Subclass of BlockingInput.  Used by ginput
 
 :class:`BlockingContourLabeler`
@@ -46,7 +46,7 @@
 
         # This will extract info from events
         self.post_event()
-        
+
         # Check if we have enough events already
         if len(self.events) >= self.n and self.n > 0:
             self.done = True
@@ -84,7 +84,7 @@
         """
         Blocking call to retrieve n events
         """
-        
+
         assert isinstance(n, int), "Requires an integer argument"
         self.n = n
 
@@ -94,7 +94,7 @@
 
         # Ensure that the figure is shown
         self.fig.show()
-        
+
         # connect the events to the on_event function call
         for n in self.eventslist:
             self.callbacks.append( self.fig.canvas.mpl_connect(n, 
self.on_event) )
@@ -124,7 +124,7 @@
     blocking way.
     """
     def __init__(self, fig):
-        BlockingInput.__init__(self, fig=fig, 
+        BlockingInput.__init__(self, fig=fig,
                                eventslist=('button_press_event',) )
 
     def post_event(self):
@@ -194,7 +194,7 @@
         """
         self.clicks.append((event.xdata,event.ydata))
 
-        verbose.report("input %i: %f,%f" % 
+        verbose.report("input %i: %f,%f" %
                        (len(self.clicks),event.xdata, event.ydata))
 
         # If desired plot up click
@@ -209,7 +209,7 @@
         removing the last click.
         """
         self.clicks.pop(index)
-        
+
         if self.show_clicks:
             mark = self.marks.pop(index)
             mark.remove()
@@ -234,7 +234,7 @@
 
         # Call base class to remove callbacks
         BlockingInput.cleanup(self)
-        
+
     def __call__(self, n=1, timeout=30, show_clicks=True):
         """
         Blocking call to retrieve n coordinate pairs through mouse
@@ -261,11 +261,18 @@
         This will be called if an event involving a button other than
         2 or 3 occcurs.  This will add a label to a contour.
         """
-        if event.inaxes == self.cs.ax:
-            conmin,segmin,imin,xmin,ymin = self.cs.find_nearest_contour(
-                event.x, event.y)[:5]
 
-            paths = self.cs.collections[conmin].get_paths()
+        # Shorthand
+        cs = self.cs
+
+        if event.inaxes == cs.ax:
+            conmin,segmin,imin,xmin,ymin = cs.find_nearest_contour(
+                event.x, event.y, cs.label_indices)[:5]
+
+            # Get index of nearest level in subset of levels used for labeling
+            lmin = cs.label_indices.index(conmin)
+
+            paths = cs.collections[conmin].get_paths()
             lc = paths[segmin].vertices
 
             # Figure out label rotation.  This is very cludgy.
@@ -287,15 +294,15 @@
             if rotation < -90:
                 rotation = 180 + rotation
 
-            self.cs.add_label(xmin,ymin,rotation,conmin)
+            cs.add_label(xmin,ymin,rotation,cs.label_levels[lmin],
+                         cs.label_cvalues[lmin])
 
             if self.inline:
                 # Get label width for breaking contours
-                lw = self.cs.get_label_width(self.cs.label_levels[conmin], 
-                                             self.cs.fmt, 
-                                             self.cs.fslist[conmin])
+                lw = cs.get_label_width(cs.label_levels[lmin],
+                                        cs.fmt, cs.fslist[lmin])
                 # Break contour
-                new=self.cs.break_linecontour(lc,rotation,lw,imin)
+                new=cs.break_linecontour(lc,rotation,lw,imin)
                 if len(new[0]):
                     paths[segmin] = path.Path(new[0])
                 if len(new[1]):
@@ -304,7 +311,7 @@
             self.fig.canvas.draw()
         else: # Remove event if not valid
             BlockingInput.pop(self)
-            
+
     def button3(self,event):
         """
         This will be called if button 3 is clicked.  This will remove

Modified: trunk/matplotlib/lib/matplotlib/contour.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/contour.py  2008-07-21 09:50:36 UTC (rev 
5798)
+++ trunk/matplotlib/lib/matplotlib/contour.py  2008-07-21 12:26:35 UTC (rev 
5799)
@@ -17,6 +17,9 @@
 import matplotlib.text as text
 import matplotlib.cbook as cbook
 
+# Import needed for adding manual selection capability to clabel
+from matplotlib.blocking_input import BlockingContourLabeler
+
 # We can't use a single line collection for contour because a line
 # collection can have only a single line style, and we want to be able to have
 # dashed negative contours, for example, and solid positive contours.
@@ -68,16 +71,49 @@
 
           *fmt*:
             a format string for the label. Default is '%1.3f'
+            Alternatively, this can be a dictionary matching contour
+            levels with arbitrary strings to use for each contour level
+            (i.e., fmt[level]=string)
 
+          *manual*:
+            if *True*, contour labels will be placed manually using
+            mouse clicks.  Click the first button near a contour to
+            add a label, click the second button (or potentially both
+            mouse buttons at once) to finish adding labels.  The third
+            button can be used to remove the last label added, but
+            only if labels are not inline.
 
         """
+
+        """"
+        NOTES on how this all works:
+
+        clabel basically takes the input arguments and uses them to
+        add a list of "label specific" attributes to the ContourSet
+        object.  These attributes currently include: label_indices,
+        label_levels, label_cvalues, fp (font properties), fslist
+        (fontsize list), label_mappable, cl (list of text objects of
+        labels), cl_xy (coordinates of labels), cl_cvalues (color
+        values of the actual labels).
+
+        Note that these property names do not conform to the standards
+        set for coding matplotlib and I (DMK) eventually plan on
+        changing them so that they are clearer and conform to
+        standards.
+
+        Once these attributes are set, clabel passes control to the
+        labels method (case of automatic label placement) or
+        BlockingContourLabeler (case of manual label placement.
+        """
+
         fontsize = kwargs.get('fontsize', None)
         inline = kwargs.get('inline', 1)
         self.fmt = kwargs.get('fmt', '%1.3f')
         _colors = kwargs.get('colors', None)
 
+        # Detect if manual selection is desired and remove from argument list
+        self.manual_select=kwargs.get('manual',False)
 
-
         if len(args) == 0:
             levels = self.levels
             indices = range(len(self.levels))
@@ -126,10 +162,16 @@
         #self.cl_cvalues = [] # same
         self.cl_xy = []
 
-        self.labels(inline)
+        if self.manual_select:
+            print 'Select label locations manually using first mouse button.'
+            print 'End manual selection with second mouse button.'
+            if not inline:
+                print 'Remove last label by clicking third mouse button.'
 
-        for label in self.cl:
-            self.ax.add_artist(label)
+            blocking_contour_labeler = BlockingContourLabeler(self)
+            blocking_contour_labeler(inline)
+        else:
+            self.labels(inline)
 
         self.label_list =  cbook.silent_list('text.Text', self.cl)
         return self.label_list
@@ -141,10 +183,10 @@
         if lcsize > 10 * labelwidth:
             return 1
 
-        xmax = np.amax(np.array(linecontour)[:,0])
-        xmin = np.amin(np.array(linecontour)[:,0])
-        ymax = np.amax(np.array(linecontour)[:,1])
-        ymin = np.amin(np.array(linecontour)[:,1])
+        xmax = np.amax(linecontour[:,0])
+        xmin = np.amin(linecontour[:,0])
+        ymax = np.amax(linecontour[:,1])
+        ymin = np.amin(linecontour[:,1])
 
         lw = labelwidth
         if (xmax - xmin) > 1.2* lw or (ymax - ymin) > 1.2 * lw:
@@ -180,12 +222,10 @@
             if self.too_close(x,y, lw):
                 continue
             else:
-                self.cl_xy.append((x,y))
                 return x,y, ind
 
         ind = adist[0]
         x, y = XX[ind][hysize], YY[ind][hysize]
-        self.cl_xy.append((x,y))
         return x,y, ind
 
     def get_label_width(self, lev, fmt, fsize):
@@ -193,7 +233,7 @@
         if cbook.is_string_like(lev):
             lw = (len(lev)) * fsize
         else:
-            lw = (len(fmt%lev)) * fsize
+            lw = (len(self.get_text(lev,fmt))) * fsize
 
         return lw
 
@@ -210,9 +250,11 @@
         if cbook.is_string_like(lev):
             return lev
         else:
-            return fmt%lev
+            if isinstance(fmt,dict):
+                return fmt[lev]
+            else:
+                return fmt%lev
 
-
     def break_linecontour(self, linecontour, rot, labelwidth, ind):
         "break a contour in two contours at the location of the label"
         lcsize = len(linecontour)
@@ -226,8 +268,8 @@
 
         slc = trans.transform(linecontour)
         x,y = slc[ind]
-        xx= np.asarray(slc)[:,0].copy()
-        yy=np.asarray(slc)[:,1].copy()
+        xx=slc[:,0].copy()
+        yy=slc[:,1].copy()
 
         #indices which are under the label
         inds, = np.nonzero(((xx < x+xlabel) & (xx > x-xlabel)) &
@@ -308,8 +350,8 @@
         else:
             ysize = labelwidth
 
-        XX = np.resize(np.asarray(linecontour)[:,0],(xsize, ysize))
-        YY = np.resize(np.asarray(linecontour)[:,1],(xsize, ysize))
+        XX = np.resize(linecontour[:,0],(xsize, ysize))
+        YY = np.resize(linecontour[:,1],(xsize, ysize))
         #I might have fouled up the following:
         yfirst = YY[:,0].reshape(xsize, 1)
         ylast = YY[:,-1].reshape(xsize, 1)
@@ -335,19 +377,38 @@
 
         return x,y, rotation, dind
 
+    def add_label(self,x,y,rotation,lev,cvalue):
+        dx,dy = self.ax.transData.inverted().transform_point((x,y))
+        t = text.Text(dx, dy, rotation = rotation,
+                      horizontalalignment='center',
+                      verticalalignment='center')
+
+        color = self.label_mappable.to_rgba(cvalue,alpha=self.alpha)
+
+        _text = self.get_text(lev,self.fmt)
+        self.set_label_props(t, _text, color)
+        self.cl.append(t)
+        self.cl_cvalues.append(cvalue)
+        self.cl_xy.append((x,y))
+
+        # Add label to plot here - useful for manual mode label selection
+        self.ax.add_artist(t)
+
+    def pop_label(self,index=-1):
+        '''Defaults to removing last label, but any index can be supplied'''
+        self.cl_cvalues.pop(index)
+        t = self.cl.pop(index)
+        t.remove()
+
     def labels(self, inline):
-        levels = self.label_levels
-        fslist = self.fslist
-        trans = self.ax.transData
-        _colors = self.label_mappable.to_rgba(self.label_cvalues,
-                                                        alpha=self.alpha)
-        fmt = self.fmt
-        for icon, lev, color, cvalue, fsize in zip(self.label_indices,
-                                          self.label_levels,
-                                          _colors,
-                                          self.label_cvalues, fslist):
+        trans = self.ax.transData # A bit of shorthand
+
+        for icon, lev, fsize, cvalue in zip(
+            self.label_indices, self.label_levels, self.fslist,
+            self.label_cvalues ):
+
             con = self.collections[icon]
-            lw = self.get_label_width(lev, fmt, fsize)
+            lw = self.get_label_width(lev, self.fmt, fsize)
             additions = []
             paths = con.get_paths()
             for segNum, linepath in enumerate(paths):
@@ -356,28 +417,27 @@
                 # avoid division by zero
                 if np.all(linecontour[0] == linecontour[-1]):
                     linecontour = np.concatenate((linecontour,
-                                                   
linecontour[1][np.newaxis,:]))
+                                                  
linecontour[1][np.newaxis,:]))
                     #linecontour.append(linecontour[1])
                 # transfer all data points to screen coordinates
                 slc = trans.transform(linecontour)
                 if self.print_label(slc,lw):
                     x,y, rotation, ind  = self.locate_label(slc, lw)
-                    # transfer the location of the label back to
-                    # data coordinates
-                    dx,dy = trans.inverted().transform_point((x,y))
-                    t = text.Text(dx, dy, rotation = rotation,
-                             horizontalalignment='center',
-                             verticalalignment='center')
-                    _text = self.get_text(lev,fmt)
-                    self.set_label_props(t, _text, color)
-                    self.cl.append(t)
-                    self.cl_cvalues.append(cvalue)
+
+                    # Actually add the label
+                    self.add_label(x,y,rotation,lev,cvalue)
+
+                    # Use break_linecontour to split contours for inlining
                     if inline:
-                        new = self.break_linecontour(linecontour, rotation, 
lw, ind)
+                        new = self.break_linecontour(linecontour, rotation,
+                                                     lw, ind)
                         if len(new[0]):
                             paths[segNum] = path.Path(new[0])
                         if len(new[1]):
                             additions.append(path.Path(new[1]))
+
+            # After looping over all segments on a contour, append
+            # new paths to existing
             paths.extend(additions)
 
 
@@ -802,19 +862,8 @@
         Use keyword args to control colors, linewidth, origin, cmap ... see
         below for more details.
 
-        *X*, *Y*, and *Z* may be arrays all with the same 2-D shape, or
-        *X* and *Y* can be 1-D while *Z* is 2-D.  In the latter
-        case, the following must be true:
+        *X*, *Y*, and *Z* must be arrays with the same dimensions.
 
-        ::
-
-          Z.shape == len(Y), len(X)
-
-        Note that the first index of *Z*, the row number, corresponds
-        to the vertical coordinate on the page, while the second
-        index, the column number, corresponds to the horizontal
-        coordinate on the page.
-
         *Z* may be a masked array, but filled contouring may not
         handle internal masked regions correctly.
 
@@ -908,3 +957,70 @@
 
         .. plot:: contour_demo.py
         """
+
+    def find_nearest_contour( self, x, y, indices=None, pixel=True ):
+        """
+        Finds contour that is closest to a point.  Defaults to
+        measuring distance in pixels (screen space - useful for manual
+        contour labeling), but this can be controlled via a keyword
+        argument.
+
+        Returns a tuple containing the contour, segment, index of
+        segment, x & y of segment point and distance to minimum point.
+
+        Call signature::
+
+        conmin,segmin,imin,xmin,ymin,dmin = find_nearest_contour(
+                   self, x, y, indices=None, pixel=True )
+
+        Optional keyword arguments::
+
+        *indices*:
+           Indexes of contour levels to consider when looking for
+           nearest point.  Defaults to using all levels.
+
+        *pixel*:
+           If *True*, measure distance in pixel space, if not, measure
+           distance in axes space.  Defaults to *True*.
+
+        """
+
+        # This function uses a method that is probably quite
+        # inefficient based on converting each contour segment to
+        # pixel coordinates and then comparing the given point to
+        # those coordinates for each contour.  This will probably be
+        # quite slow for complex contours, but for normal use it works
+        # sufficiently well that the time is not noticeable.
+        # Nonetheless, improvements could probably be made.
+
+        if indices==None:
+            indices = range(len(self.levels))
+
+        dmin = 1e10
+        conmin = None
+        segmin = None
+        xmin = None
+        ymin = None
+
+        for icon in indices:
+            con = self.collections[icon]
+            paths = con.get_paths()
+            for segNum, linepath in enumerate(paths):
+                lc = linepath.vertices
+
+                # transfer all data points to screen coordinates if desired
+                if pixel:
+                    lc = self.ax.transData.transform(lc)
+
+                ds = (lc[:,0]-x)**2 + (lc[:,1]-y)**2
+                d = min( ds )
+                if d < dmin:
+                    dmin = d
+                    conmin = icon
+                    segmin = segNum
+                    imin = mpl.mlab.find( ds == d )[0]
+                    xmin = lc[imin,0]
+                    ymin = lc[imin,1]
+
+        return (conmin,segmin,imin,xmin,ymin,dmin)
+


This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.

-------------------------------------------------------------------------
This SF.Net email is sponsored by the Moblin Your Move Developer's challenge
Build the coolest Linux based applications with Moblin SDK & win great prizes
Grand prize is a trip for two to an Open Source event anywhere in the world
http://moblin-contest.org/redirect.php?banner_id=100&url=/
_______________________________________________
Matplotlib-checkins mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/matplotlib-checkins

Reply via email to