--- /dev/null
+#!/usr/bin/env python
+"""
+Pick data-points with a cursor. The cursor snaps to the nearest
+datapoint. Developed from Matplotlib's cursor_demo.py.
+
+Faster cursoring is possible using native GUI drawing, as in
+wxcursor_demo.py. You could also write up a more efficient
+Cursor._get_xy() method.
+"""
+
+from pylab import subplot, connect, show, draw
+from numpy import array, fromfile, double
+
+class Cursor:
+ """
+ Like Cursor but the crosshair snaps to the nearest x,y point
+ For simplicity, I'm assuming x is sorted
+ """
+ def __init__(self, ax, x, y):
+ self.ax = ax
+ self.lx, = ax.plot( (0,0), (0,0), 'k-' ) # the horiz line
+ self.ly, = ax.plot( (0,0), (0,0), 'k-' ) # the vert line
+ self.x = x
+ self.y = y
+ self.xscale = max(self.x) - min(self.x)
+ self.yscale = max(self.y) - min(self.y)
+ self._sort()
+ # text location in axes coords
+ #self.txt = ax.text( 0.6, 0.9, '', transform=ax.transAxes)
+ self.txt = self.ax.title
+ def _sort(self):
+ "ideally, here is where you build the Voronoi lookup tree"
+ pass
+ def _get_xy(self, x, y):
+ """terrible hack. Should compute Voronoi diagram with
+ some sort of lookup tree. Work for my free time...
+ http://en.wikipedia.org/wiki/Voronoi_diagram
+ http://en.wikipedia.org/wiki/Point_location#Triangulation_refinement
+ http://www.cs.cmu.edu/~quake/triangle.html
+ """
+ dist = float("infinity")
+ indx = -1
+ xs = self.xscale
+ ys = self.yscale
+ for xp,yp,i in zip(self.x, self.y,range(len(self.x))):
+ d = (((x-xp)/xs)**2 + ((y-yp)/ys)**2)**0.5
+ if d < dist:
+ dist = d
+ xpm = xp
+ ypm = yp
+ indx = i
+ return (xpm,ypm,indx)
+ def mouse_move(self, event):
+ if not event.inaxes: return
+ ax = event.inaxes
+ minx, maxx = ax.get_xlim()
+ miny, maxy = ax.get_ylim()
+ x, y, i = self._get_xy(event.xdata, event.ydata)
+ # update the line positions
+ self.lx.set_data( (minx, maxx), (y, y) )
+ self.ly.set_data( (x, x), (miny, maxy) )
+ # update the label
+ self.txt.set_text( 'x=%1.2g, y=%1.2g, indx=%d'%(x,y,i) )
+ draw()
+ def mouse_click(self, event):
+ if not event.inaxes: return
+ x, y, i = self._get_xy(event.xdata, event.ydata)
+ if event.button != 3: return # ignore non-button-3 clicks
+ print '%g\t%g\t%d'%(x,y,i)
+
+def readFile(fid, cols=2, sep='\t'):
+ """This is the lower level reader. It works on all file types, but
+ you need to know the number of columns in the file ahead of time."""
+ data = fromfile(file=fid, dtype=double, sep=sep)
+ rows = len(data)/cols
+ data = data.reshape((rows,cols))
+ x = data[:,0]
+ y = data[:,1]
+ return x,y
+
+def readFilename(filename, sep='\t'):
+ """Requires rewinding file if first line is not a comment.
+ Therefore only useful on
+ * seekable files (e.g. read from disk)
+ * FIFOs/piped-data with headers"""
+ fid = file(filename,'r')
+ headline = fid.readline()
+ if headline[0] != "#": fid.seek(0) # rewind if headline was data
+ cols = headline.count(sep)+1
+ x,y = readFile(fid, cols=cols, sep=sep)
+ fid.close()
+ return x,y
+
+if __name__ == "__main__" :
+ import sys
+
+ if len(sys.argv) > 2 :
+ print "usage: plotpick.py [datafile]"
+ print """
+Reads in ASCII data from datafile, or, if datafile is not given, from
+stdin. Data files define an array of (x,y) points and consist of two
+columns (x&y) seperated by TABs, with ENDLINEs between the points:
+x0 y0
+x1 y1
+...
+The points are plotted in a window, and mousing over the plot displays
+a cursor that snaps to the nearest point. Right-clicking (button 3)
+will print the TAB seperated coordinates (x, y, point-index) to stdout.
+"""
+ sys.exit(1)
+ elif len(sys.argv) == 1:
+ x,y = readFile(sys.stdin)
+ else: # len(sys.argv) == 2
+ datafile = file(sys.argv[1], 'r')
+ x,y = readFile(datafile)
+ datafile.close
+
+ ax = subplot(111)
+ cursor = Cursor(ax, x, y)
+ connect('motion_notify_event', cursor.mouse_move)
+ connect('button_press_event', cursor.mouse_click)
+ ax.plot(x, y, '.')
+ ax.axis([min(x), max(x), min(y), max(y)])
+
+ print '#x\ty\tindex'
+ show()