#!/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()