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