Hi,

Well, I now know more than I ever wanted to about clabel.  I decided to
improve a bit on the inlining and ended up rewriting it.  For automatic
label placement, I basically use the existing algorithm for determining
label location, but have replaced existing code for determining the
angle of rotation and the amount of the contour to take off for inlining
with new code.  This new code is based on using pixel-space distances
along the contour.  This allows one to (1) get nice balanced inlining
with the same amount of space on either side of the label, and (2) to
vary the amount of space you want around the label.  It also should deal
better with labels located near contour edges and labels covering the
entire contour.

Along the way, I renamed all ContourLabeler specific attributes to
something like .labelAttribute.  This makes the namespace cleaner in my
mind, but might break existing user code that does things directly with
ContourLabeler attributes.

I also added a few new functions to cbook.  One does simple linear
interpolation (in addition to an existing cbook function that is similar
but a bit different).  Others do things with vector lengths.  I also
added a function called isvector that is identical to a Matlab function
of the same name.  If desired, I can move this to mlab.py, but for the
moment it is in cbook.py because most of the is? functions are there.

On an aside, I noted that mlab.norm uses cut-and-paste documentation
from Matlab. Is this wise?

I have tested all the changes against the existing pylab_examples and
things work fine.  Nonetheless, since lots of things have been changed,
I haven't committed them for fear of interfering with the release.
Instead, I am attaching the patch set.  If I get the green light, I will
commit these changes.

Related: while I am digging around in there, now is probably the moment
for me to integrate Paul Kienzle's comments on start/stop_event_loop in
FigureCanvasBase, etc.  I am not sure there is a consensus on this.  I
am currently thinking that the best way to integrate this, while
minimizing repeated code, is a mixin plus a couple of new classes,
FigureCanvasBaseGUI and FigureCanvasGUIAgg.  These new classes would
inherit the mixin and the base classes without "GUI".  Interactive
backends would then inherit these.  Non-interactive backends would
inherit versions that throw errors from FigureBaseCanvas.  Complex, but
this assures clean inheritance.  Thoughts welcome.

Cheers,
David
 

-- 
**********************************
David M. Kaplan
Charge de Recherche 1
Institut de Recherche pour le Developpement
Centre de Recherche Halieutique Mediterraneenne et Tropicale
av. Jean Monnet
B.P. 171
34203 Sete cedex
France

Phone: +33 (0)4 99 57 32 27
Fax: +33 (0)4 99 57 32 95
http://www.ur097.ird.fr/team/dkaplan/index.html
**********************************
Index: lib/matplotlib/cbook.py
===================================================================
--- lib/matplotlib/cbook.py	(revision 5797)
+++ lib/matplotlib/cbook.py	(working copy)
@@ -287,7 +287,7 @@
 
 def is_scalar(obj):
     'return true if *obj* is not string like and is not iterable'
-    return is_string_like(obj) or not iterable(obj)
+    return not is_string_like(obj) and not iterable(obj)
 
 def is_numlike(obj):
     'return true if *obj* looks like a number'
@@ -1154,6 +1154,46 @@
 
     return result
 
+def less_simple_linear_interpolation( x, y, xi, extrap=False ):
+    """
+    This function provides simple (but somewhat less so than
+    simple_linear_interpolation) linear interpolation.  This is very
+    inefficient linear interpolation meant to be used only for a small
+    number of points in relatively non-intensive use cases.
+
+    Call signature::
+
+    yi = less_simple_linear_interpolation(x,y,xi)
+    """
+    if is_scalar(xi): xi = [xi]
+
+    x = np.asarray(x)
+    y = np.asarray(y)
+    xi = np.asarray(xi)
+
+    s = list(y.shape)
+    s[0] = len(xi)
+    yi = np.tile( np.nan, s )
+
+    for ii,xx in enumerate(xi):
+        bb = x == xx
+        if np.any(bb):
+            jj, = np.nonzero(bb)
+            yi[ii] = y[jj[0]]
+        elif xx<x[0]:
+            if extrap:
+                yi[ii] = y[0]
+        elif xx>x[-1]:
+            if extrap:
+                yi[ii] = y[-1]
+        else:
+            jj, = np.nonzero(x<xx)
+            jj = max(jj)
+
+            yi[ii] = y[jj] + (xx-x[jj])/(x[jj+1]-x[jj]) * (y[jj+1]-y[jj])
+
+    return yi
+
 def recursive_remove(path):
     if os.path.isdir(path):
         for fname in glob.glob(os.path.join(path, '*')) + glob.glob(os.path.join(path, '.*')):
@@ -1246,7 +1286,76 @@
             margs[i] = x.filled()
     return margs
 
+def isvector(X):
+    """
+    Like the Matlab (TM) function with the same name, returns true if
+    the supplied numpy array or matrix looks like a vector, meaning it
+    has a one non-singleton axis (i.e., it can have multiple axes, but
+    all must have length 1, except for one of them).
 
+    If you just want to see if the array has 1 axis, use X.ndim==1
+
+    Call signature::
+    isvector(X)
+    """
+    return np.prod(X.shape)==np.max(X.shape)
+
+def vector_lengths( X, P=2., axis=None ):
+    """
+    Finds the length of a set of vectors in n dimensions.  This is
+    like the mlab.norm function for vectors, but has the ability to
+    work over a particular axis of the supplied array or matrix.
+
+    Call signature::
+
+    vector_lengths( X, P=2., axis=None )
+
+    Computes (sum((x_i)^P))^(1/P) for each {x_i} being the elements of X along
+    the given axis.  If *axis* is *None*, compute over all elements of X.
+    """
+    X = np.asarray(X)
+    return (np.sum(X**(P),axis=axis))**(1./P)
+
+def distances_along_curve( X ):
+    """
+    Computes the distance between a set of successive points in N dimensions.
+
+    Call signature::
+
+    distances_along_curve(X)
+
+    where X is an MxN array or matrix.  The distances between successive rows
+    is computed.  Distance is the standard Euclidean distance.
+    """
+    X = np.diff( X, axis=0 )
+    return vector_lengths(X,axis=1)
+
+def path_length(X):
+    """
+    Computes the distance travelled along a polygonal curve in N dimensions.
+
+    Call signature::
+
+    path_length(X)
+
+    where X is an MxN array or matrix.  Returns an array of length M consisting
+    of the distance along the curve at each point (i.e., the rows of X).
+    """
+    X = distances_along_curve(X)
+    return np.concatenate( (np.zeros(1), np.cumsum(X)) )
+
+def is_closed_polygon(X):
+    """
+    Tests whether first and last object in a sequence are the same.  These are
+    presumably coordinates on a polygonal curve, in which case this function
+    tests if that curve is closed.
+
+    Call signature::
+
+    is_closed_polygon(X)
+    """
+    return np.all(X[0] == X[-1])
+
 # a dict to cross-map linestyle arguments
 _linestyles = [('-', 'solid'),
     ('--', 'dashed'),
Index: lib/matplotlib/contour.py
===================================================================
--- lib/matplotlib/contour.py	(revision 5801)
+++ lib/matplotlib/contour.py	(working copy)
@@ -69,6 +69,12 @@
             controls whether the underlying contour is removed or
             not. Default is *True*.
 
+          *inline_spacing*:
+            space in pixels to leave on each side of label when
+            placing inline.  Defaults to 5.  This spacing will be
+            exact for labels at locations where the contour is
+            straight, less so for labels on curved contours.
+
           *fmt*:
             a format string for the label. Default is '%1.3f'
             Alternatively, this can be a dictionary matching contour
@@ -108,11 +114,12 @@
 
         fontsize = kwargs.get('fontsize', None)
         inline = kwargs.get('inline', 1)
-        self.fmt = kwargs.get('fmt', '%1.3f')
+        inline_spacing = kwargs.get('inline_spacing', 5)
+        self.labelFmt = 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)
+        self.labelManual=kwargs.get('manual',False)
 
         if len(args) == 0:
             levels = self.levels
@@ -131,50 +138,50 @@
                 raise ValueError(msg)
         else:
             raise TypeError("Illegal arguments to clabel, see help(clabel)")
-        self.label_levels = levels
-        self.label_indices = indices
+        self.labelLevelList = levels
+        self.labelIndiceList = indices
 
-        self.fp = font_manager.FontProperties()
+        self.labelFontProps = font_manager.FontProperties()
         if fontsize == None:
-            font_size = int(self.fp.get_size_in_points())
+            font_size = int(self.labelFontProps.get_size_in_points())
         else:
             if type(fontsize) not in [int, float, str]:
                 raise TypeError("Font size must be an integer number.")
                 # Can't it be floating point, as indicated in line above?
             else:
                 if type(fontsize) == str:
-                    font_size = int(self.fp.get_size_in_points())
+                    font_size = int(self.labelFontProps.get_size_in_points())
                 else:
-                    self.fp.set_size(fontsize)
+                    self.labelFontProps.set_size(fontsize)
                     font_size = fontsize
-        self.fslist = [font_size] * len(levels)
+        self.labelFontSizeList = [font_size] * len(levels)
 
         if _colors == None:
-            self.label_mappable = self
-            self.label_cvalues = np.take(self.cvalues, self.label_indices)
+            self.labelMappable = self
+            self.labelCValueList = np.take(self.cvalues, self.labelIndiceList)
         else:
-            cmap = colors.ListedColormap(_colors, N=len(self.label_levels))
-            self.label_cvalues = range(len(self.label_levels))
-            self.label_mappable = cm.ScalarMappable(cmap = cmap,
-                                                 norm = colors.NoNorm())
+            cmap = colors.ListedColormap(_colors, N=len(self.labelLevelList))
+            self.labelCValueList = range(len(self.labelLevelList))
+            self.labelMappable = cm.ScalarMappable(cmap = cmap,
+                                                   norm = colors.NoNorm())
 
-        #self.cl = []   # Initialized in ContourSet.__init__
-        #self.cl_cvalues = [] # same
-        self.cl_xy = []
+        #self.labelTexts = []   # Initialized in ContourSet.__init__
+        #self.labelCValues = [] # same
+        self.labelXYs = []
 
-        if self.manual_select:
+        if self.labelManual:
             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.'
 
             blocking_contour_labeler = BlockingContourLabeler(self)
-            blocking_contour_labeler(inline)
+            blocking_contour_labeler(inline,inline_spacing)
         else:
-            self.labels(inline)
+            self.labels(inline,inline_spacing)
 
-        self.label_list =  cbook.silent_list('text.Text', self.cl)
-        return self.label_list
+        self.labelTextsList =  cbook.silent_list('text.Text', self.labelTexts)
+        return self.labelTextsList
 
 
     def print_label(self, linecontour,labelwidth):
@@ -196,9 +203,9 @@
 
     def too_close(self, x,y, lw):
         "if there's a label already nearby, find a better place"
-        if self.cl_xy != []:
+        if self.labelXYs != []:
             dist = [np.sqrt((x-loc[0]) ** 2 + (y-loc[1]) ** 2)
-                    for loc in self.cl_xy]
+                    for loc in self.labelXYs]
             for d in dist:
                 if d < 1.2*lw:
                     return 1
@@ -237,12 +244,35 @@
 
         return lw
 
+    def get_real_label_width( self, lev, fmt, fsize ):
+        """
+        This computes actual onscreen label width.
+        This uses some black magic to determine onscreen extent of non-drawn
+        label.  This magic may not be very robust.
+        """
+        # Find middle of axes
+        xx = np.mean( np.asarray(self.ax.axis()).reshape(2,2), axis=1 )
 
+        # Temporarily create text object
+        t = text.Text( xx[0], xx[1] )
+        self.set_label_props( t, self.get_text(lev,fmt), 'k' )
+
+        # Some black magic to get onscreen extent
+        # NOTE: This will only work for already drawn figures, as the canvas
+        # does not have a renderer otherwise.  This is the reason this function
+        # can't be integrated into the rest of the code.
+        bbox = t.get_window_extent(renderer=self.ax.figure.canvas.renderer)
+
+        # difference in pixel extent of image
+        lw = np.diff(bbox.corners()[0::2,0])[0]
+
+        return np.round(lw)
+
     def set_label_props(self, label, text, color):
         "set the label properties - color, fontsize, text"
         label.set_text(text)
         label.set_color(color)
-        label.set_fontproperties(self.fp)
+        label.set_fontproperties(self.labelFontProps)
         label.set_clip_box(self.ax.bbox)
 
     def get_text(self, lev, fmt):
@@ -255,85 +285,6 @@
             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)
-        hlw = int(labelwidth/2)
-
-        #length of label in screen coords
-        ylabel = abs(hlw * np.sin(rot*np.pi/180))
-        xlabel = abs(hlw * np.cos(rot*np.pi/180))
-
-        trans = self.ax.transData
-
-        slc = trans.transform(linecontour)
-        x,y = slc[ind]
-        xx=slc[:,0].copy()
-        yy=slc[:,1].copy()
-
-        #indices which are under the label
-        inds, = np.nonzero(((xx < x+xlabel) & (xx > x-xlabel)) &
-                            ((yy < y+ylabel) & (yy > y-ylabel)))
-
-        if len(inds) >0:
-            #if the label happens to be over the beginning of the
-            #contour, the entire contour is removed, i.e.
-            #indices to be removed are
-            #inds= [0,1,2,3,305,306,307]
-            #should rewrite this in a better way
-            linds, = np.nonzero(inds[1:]- inds[:-1] != 1)
-            if inds[0] == 0 and len(linds) != 0:
-                ii = inds[linds[0]]
-                lc1 =linecontour[ii+1:inds[ii+1]]
-                lc2 = []
-
-            else:
-                lc1=linecontour[:inds[0]]
-                lc2= linecontour[inds[-1]+1:]
-
-        else:
-            lc1=linecontour[:ind]
-            lc2 = linecontour[ind+1:]
-
-
-        if rot <0:
-            new_x1, new_y1 = x-xlabel, y+ylabel
-            new_x2, new_y2 = x+xlabel, y-ylabel
-        else:
-            new_x1, new_y1 = x-xlabel, y-ylabel
-            new_x2, new_y2 = x+xlabel, y+ylabel
-
-        inverse = trans.inverted()
-        new_x1d, new_y1d = inverse.transform_point((new_x1, new_y1))
-        new_x2d, new_y2d = inverse.transform_point((new_x2, new_y2))
-        new_xy1 = np.array(((new_x1d, new_y1d),))
-        new_xy2 = np.array(((new_x2d, new_y2d),))
-
-
-        if rot > 0:
-            if (len(lc1) > 0 and (lc1[-1][0] <= new_x1d)
-                             and (lc1[-1][1] <= new_y1d)):
-                lc1 = np.concatenate((lc1, new_xy1))
-                #lc1.append((new_x1d, new_y1d))
-
-            if (len(lc2) > 0 and (lc2[0][0] >= new_x2d)
-                             and (lc2[0][1] >= new_y2d)):
-                lc2 = np.concatenate((new_xy2, lc2))
-                #lc2.insert(0, (new_x2d, new_y2d))
-        else:
-            if (len(lc1) > 0 and ((lc1[-1][0] <= new_x1d)
-                             and (lc1[-1][1] >= new_y1d))):
-                lc1 = np.concatenate((lc1, new_xy1))
-                #lc1.append((new_x1d, new_y1d))
-
-            if (len(lc2) > 0 and ((lc2[0][0] >= new_x2d)
-                             and (lc2[0][1] <= new_y2d))):
-                lc2 = np.concatenate((new_xy2, lc2))
-                #lc2.insert(0, (new_x2d, new_y2d))
-
-        return [lc1,lc2]
-
-
     def locate_label(self, linecontour, labelwidth):
         """find a good place to plot a label (relatively flat
         part of the contour) and the angle of rotation for the
@@ -362,12 +313,6 @@
         dist = np.add.reduce(([(abs(s)[i]/L[i]) for i in range(xsize)]),-1)
         x,y,ind = self.get_label_coords(dist, XX, YY, ysize, labelwidth)
         #print 'ind, x, y', ind, x, y
-        angle = np.arctan2(ylast - yfirst, xlast - xfirst).ravel()
-        rotation = angle[ind]*180/np.pi
-        if rotation > 90:
-            rotation = rotation -180
-        if rotation < -90:
-            rotation = 180 + rotation
 
         # There must be a more efficient way...
         lc = [tuple(l) for l in linecontour]
@@ -375,72 +320,176 @@
         #print 'dind', dind
         #dind = list(linecontour).index((x,y))
 
-        return x,y, rotation, dind
+        return x, y, dind
 
+    def calc_label_rot_and_inline( self, slc, ind, lw, lc=[], spacing=5 ):
+        """
+        This function calculates the appropriate label rotation given
+        the linecontour coordinates in screen units, the index of the
+        label location and the label width.
+
+        It will also break contour and calculate inlining if *lc* is
+        not empty.  *spacing* is the space around the label in pixels
+        to leave empty.
+
+        Do both of these tasks at once to avoid calling cbook.path_length
+        multiple times, which is relatively costly.
+
+        The method used here involves calculating the path length
+        along the contour in pixel coordinates and then looking
+        approximately label width / 2 away from central point to
+        determine rotation and then to break contour if desired.
+        """
+
+        # Half the label width
+        hlw = lw/2.0
+
+        # Check if closed and, if so, rotate contour so label is at edge
+        closed = cbook.is_closed_polygon(slc)
+        if closed:
+            slc = np.r_[ slc[ind:-1], slc[:ind+1] ]
+
+            if len(lc): # Rotate lc also if not empty
+                lc = np.r_[ lc[ind:-1], lc[:ind+1] ]
+
+            ind = 0
+
+        # Path length in pixel space
+        pl = cbook.path_length(slc)
+        pl = pl-pl[ind]
+
+        # Use linear interpolation to get points around label
+        xi = np.array( [ -hlw, hlw ] )
+        if closed: # Look at end also for closed contours
+            dp = np.array([pl[-1],0])
+        else:
+            dp = np.zeros_like(xi)
+
+        ll = cbook.less_simple_linear_interpolation( pl, slc, dp+xi,
+                                                     extrap=True )
+
+        # get vector in pixel space coordinates from one point to other
+        dd = np.diff( ll, axis=0 ).ravel()
+
+        # Get angle of vector - must be calculated in pixel space for
+        # text rotation to work correctly
+        if np.all(dd==0): # Must deal with case of zero length label
+            rotation = 0.0
+        else:
+            rotation = np.arctan2(dd[1], dd[0]) * 180.0 / np.pi
+
+        # Fix angle so text is never upside-down
+        if rotation > 90:
+            rotation = rotation - 180.0
+        if rotation < -90:
+            rotation = 180.0 + rotation
+
+        # Break contour if desired
+        nlc = []
+        if len(lc):
+            # Expand range by spacing
+            xi = dp + xi + np.array([-spacing,spacing])
+
+            # Get indices near points of interest
+            I = cbook.less_simple_linear_interpolation(
+                pl, np.arange(len(pl)), xi, extrap=False )
+
+            # If those indices aren't beyond contour edge, find x,y
+            if (not np.isnan(I[0])) and int(I[0])<>I[0]:
+                xy1 = cbook.less_simple_linear_interpolation(
+                    pl, lc, [ xi[0] ] )
+
+            if (not np.isnan(I[1])) and int(I[1])<>I[1]:
+                xy2 = cbook.less_simple_linear_interpolation(
+                    pl, lc, [ xi[1] ] )
+
+            # Make integer
+            I = [ np.floor(I[0]), np.ceil(I[1]) ]
+
+            # Actually break contours
+            if closed:
+                # This will remove contour if shorter than label
+                if np.all(~np.isnan(I)):
+                    nlc.append( np.r_[ xy2, lc[I[1]:I[0]+1], xy1 ] )
+            else:
+                # These will remove pieces of contour if they have length zero
+                if not np.isnan(I[0]):
+                    nlc.append( np.r_[ lc[:I[0]+1], xy1 ] )
+                if not np.isnan(I[1]):
+                    nlc.append( np.r_[ xy2, lc[I[1]:] ] )
+
+        return (rotation,nlc)
+
+
     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)
+        color = self.labelMappable.to_rgba(cvalue,alpha=self.alpha)
 
-        _text = self.get_text(lev,self.fmt)
+        _text = self.get_text(lev,self.labelFmt)
         self.set_label_props(t, _text, color)
-        self.cl.append(t)
-        self.cl_cvalues.append(cvalue)
-        self.cl_xy.append((x,y))
+        self.labelTexts.append(t)
+        self.labelCValues.append(cvalue)
+        self.labelXYs.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)
+        self.labelCValues.pop(index)
+        t = self.labelTexts.pop(index)
         t.remove()
 
-    def labels(self, inline):
+    def labels(self, inline, inline_spacing):
         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 ):
+            self.labelIndiceList, self.labelLevelList, self.labelFontSizeList,
+            self.labelCValueList ):
 
             con = self.collections[icon]
-            lw = self.get_label_width(lev, self.fmt, fsize)
+            lw = self.get_label_width(lev, self.labelFmt, fsize)
             additions = []
             paths = con.get_paths()
             for segNum, linepath in enumerate(paths):
-                linecontour = linepath.vertices
-                # for closed contours add one more point to
-                # avoid division by zero
-                if np.all(linecontour[0] == linecontour[-1]):
-                    linecontour = np.concatenate((linecontour,
-                                                  linecontour[1][np.newaxis,:]))
-                    #linecontour.append(linecontour[1])
-                # transfer all data points to screen coordinates
-                slc = trans.transform(linecontour)
+                lc = linepath.vertices # Line contour
+                slc0 = trans.transform(lc) # Line contour in screen coords
+
+                # For closed polygons, add extra point to avoid division by
+                # zero in print_label and locate_label.  Other than these
+                # functions, this is not necessary and should probably be
+                # eventually removed.
+                if cbook.is_closed_polygon( lc ):
+                    slc = np.r_[ slc0, slc0[1:2,:] ]
+                else:
+                    slc = slc0
+
                 if self.print_label(slc,lw):
-                    x,y, rotation, ind  = self.locate_label(slc, lw)
+                    x,y,ind  = self.locate_label(slc, lw)
 
+                    rotation,new=self.calc_label_rot_and_inline(
+                        slc0, ind, lw, lc if inline else [],
+                        inline_spacing )
+
                     # Actually add the label
                     self.add_label(x,y,rotation,lev,cvalue)
 
-                    # Use break_linecontour to split contours for inlining
+                    # If inline, add new contours
                     if inline:
-                        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]))
+                        for n in new:
+                            # Add path if not empty or single point
+                            if len(n)>1: additions.append( path.Path(n) )
 
-            # After looping over all segments on a contour, append
-            # new paths to existing
-            paths.extend(additions)
+            # After looping over all segments on a contour, remove old
+            # paths and add new ones if inlining
+            if inline:
+                del paths[:]
+                paths.extend(additions)
 
-
 class ContourSet(cm.ScalarMappable, ContourLabeler):
     """
     Create and store a set of contour lines or filled regions.
@@ -512,8 +561,8 @@
         else:
             self.collections = cbook.silent_list('collections.LineCollection')
         # label lists must be initialized here
-        self.cl = []
-        self.cl_cvalues = []
+        self.labelTexts = []
+        self.labelCValues = []
 
         kw = {'cmap': cmap}
         if norm is not None:
@@ -574,9 +623,9 @@
         for color, collection in zip(tcolors, self.collections):
             collection.set_alpha(self.alpha)
             collection.set_color(color)
-        for label, cv in zip(self.cl, self.cl_cvalues):
+        for label, cv in zip(self.labelTexts, self.labelCValues):
             label.set_alpha(self.alpha)
-            label.set_color(self.label_mappable.to_rgba(cv))
+            label.set_color(self.labelMappable.to_rgba(cv))
         # add label colors
         cm.ScalarMappable.changed(self)
 
Index: lib/matplotlib/blocking_input.py
===================================================================
--- lib/matplotlib/blocking_input.py	(revision 5799)
+++ lib/matplotlib/blocking_input.py	(working copy)
@@ -19,8 +19,9 @@
 
 import time
 import numpy as np
+
 from matplotlib import path, verbose
-from cbook import is_sequence_of_strings
+from matplotlib.cbook import is_sequence_of_strings
 
 class BlockingInput(object):
     """
@@ -267,47 +268,39 @@
 
         if event.inaxes == cs.ax:
             conmin,segmin,imin,xmin,ymin = cs.find_nearest_contour(
-                event.x, event.y, cs.label_indices)[:5]
+                event.x, event.y, cs.labelIndiceList)[:5]
 
             # Get index of nearest level in subset of levels used for labeling
-            lmin = cs.label_indices.index(conmin)
+            lmin = cs.labelIndiceList.index(conmin)
 
+            # Coordinates of contour
             paths = cs.collections[conmin].get_paths()
             lc = paths[segmin].vertices
 
-            # Figure out label rotation.  This is very cludgy.
-            # Ideally, there would be one method in ContourLabeler
-            # that would figure out the best rotation for a label at a
-            # point, but the way automatic label rotation is done is
-            # quite mysterious to me and doesn't seem easy to
-            # generalize to non-automatic label placement.  The method
-            # used below is not very robust!  It basically looks one
-            # point before and one point after label location on
-            # contour and takes mean of angles of two vectors formed.
-            # This produces "acceptable" results, but not nearly as
-            # nice as automatic method.
-            ll = lc[max(0,imin-1):imin+2] # Get points around point
-            dd = np.diff(ll,axis=0)
-            rotation = np.mean( np.arctan2(dd[:,1], dd[:,0]) ) * 180 / np.pi
-            if rotation > 90:
-                rotation = rotation -180
-            if rotation < -90:
-                rotation = 180 + rotation
+            # In pixel/screen space
+            slc = cs.ax.transData.transform(lc)
 
-            cs.add_label(xmin,ymin,rotation,cs.label_levels[lmin],
-                         cs.label_cvalues[lmin])
+            # Get label width for rotating labels and breaking contours
+            lw = cs.get_label_width(cs.labelLevelList[lmin],
+                                    cs.labelFmt, cs.labelFontSizeList[lmin])
 
+            # Figure out label rotation.
+            rotation,nlc = cs.calc_label_rot_and_inline(
+                slc, imin, lw, lc if self.inline else [],
+                self.inline_spacing )
+
+            cs.add_label(xmin,ymin,rotation,cs.labelLevelList[lmin],
+                         cs.labelCValueList[lmin])
+
             if self.inline:
-                # Get label width for breaking contours
-                lw = cs.get_label_width(cs.label_levels[lmin],
-                                        cs.fmt, cs.fslist[lmin])
-                # Break contour
-                new=cs.break_linecontour(lc,rotation,lw,imin)
-                if len(new[0]):
-                    paths[segmin] = path.Path(new[0])
-                if len(new[1]):
-                    paths.extend([path.Path(new[1])])
+                # Remove old, not looping over paths so we can do this up front
+                paths.pop(segmin)
 
+                # Add paths if not empty or single point
+                for n in nlc:
+                    if len(n)>1:
+                        paths.append( path.Path(n) )
+
             self.fig.canvas.draw()
         else: # Remove event if not valid
             BlockingInput.pop(self)
@@ -320,14 +313,21 @@
         broken contour - once humpty-dumpty is broken, he can't be put
         back together.  In inline mode, this does nothing.
         """
+        # Remove this last event - not too important for clabel use
+        # since clabel normally doesn't have a maximum number of
+        # events, but best for cleanliness sake.
+        BlockingInput.pop(self)
+
         if self.inline:
             pass
         else:
             self.cs.pop_label()
             self.cs.ax.figure.canvas.draw()
 
-    def __call__(self,inline,n=-1,timeout=-1):
+    def __call__(self,inline,inline_spacing=5,n=-1,timeout=-1):
         self.inline=inline
+        self.inline_spacing=inline_spacing
+
         BlockingMouseInput.__call__(self,n=n,timeout=timeout,
                                     show_clicks=False)
 
-------------------------------------------------------------------------
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-devel mailing list
Matplotlib-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-devel

Reply via email to