#!/usr/bin/env python
-"""
-Pick data-points with a cursor. The cursor snaps to the nearest
-datapoint. Developed from Matplotlib's cursor_demo.py.
+
+"""Pick data-points with a cursor.
+
+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.
+
+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.
"""
+import numpy as _numpy
+import pylab as _pylab
+
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
+
+__version__ = '0.2'
+
+
+class Cursor (object):
+ """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
+ def __init__(self, axes, x, y, selected=None, highlight=False):
+ self.axes = axes
+ hold = self.axes.ishold()
+ self.axes.hold(True)
+ self.crossx, = axes.plot(
+ (0,0), (0,0), 'k-', zorder=4) # the horiz crosshair
+ self.crossy, = axes.plot(
+ (0,0), (0,0), 'k-', zorder=4) # the vert crosshair
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
+ if highlight:
+ if selected is None:
+ selected = []
+ sx = [self.x[i] for i in selected]
+ sy = [self.y[i] for i in selected]
+ self.highlight_line, = axes.plot(sx, sy, 'r.', zorder=5)
+ self.selected = selected
+ self.highlight = highlight
+ self.txt = self.axes.title
+ self.axes.hold(hold)
+
def _sort(self):
- "ideally, here is where you build the Voronoi lookup tree"
+ "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...
+ """Return `(x_p, y_p, i_p)` for the point nearest `(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
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)
+ 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) )
+ self.crossx.set_data((minx, maxx), (y, y))
+ self.crossy.set_data((x, x), (miny, maxy))
# update the label
- self.txt.set_text( 'x=%1.2g, y=%1.2g, indx=%d'%(x,y,i) )
+ self.txt.set_text('x={:1.2g}, y={:1.2g}, indx={}'.format(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)
+ 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
+ if self.highlight:
+ if i in self.selected:
+ self.selected.remove(i)
+ else:
+ self.selected.append(i)
+ sx = [self.x[i] for i in self.selected]
+ sy = [self.y[i] for i in self.selected]
+ self.highlight_line.set_data(sx, sy)
+ else:
+ print('{}\t{}\t{}'.format(x, y, i))
+
-def readFile(fid, cols=2, sep='\t'):
+def read_file(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)
y = data[:,1]
return x,y
-def readFilename(filename, sep='\t'):
+def read_filename(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
+ with open(filename,'r') as f:
+ headline = f.readline()
+ if headline[0] != "#":
+ f.seek(0) # rewind if headline was data
+ cols = headline.count(sep)+1
+ x,y = read_file(f, cols=cols, sep=sep)
+ return (x, y)
+
if __name__ == "__main__" :
- import sys
+ from argparse import ArgumentParser as _ArgumentParser
+ import sys as _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'
+ parser = _ArgumentParser(
+ description=__doc__, version=__version__)
+ parser.add_argument(
+ '--highlight', action='store_const', const=True, default=False,
+ help=('Highlight selected points and print them afterwards, instead '
+ 'of printing them as they are clicked'))
+ parser.add_argument(
+ 'datafile', nargs='?', default=None,
+ help='Path to the datafile. Defaults to stdin')
+
+ args = parser.parse_args()
+
+ if args.datafile:
+ x,y = read_filename(args.datafile)
+ else:
+ x,y = read_file(_sys.stdin)
+
+ axes = _pylab.subplot(1, 1, 1)
+ cursor = Cursor(axes, x, y, highlight=args.highlight)
+ _pylab.connect('motion_notify_event', cursor.mouse_move)
+ _pylab.connect('button_press_event', cursor.mouse_click)
+ axes.plot(x, y, '.')
+ axes.set_xlim(min(x), max(x))
+ axes.set_ylim(min(y), max(y))
+
+ print('#x\ty\tindex')
show()
+ if args.highlight:
+ for i in cursor.selected:
+ point = (x[i], y[i], i)
+ print('\t'.join(str(x) for x in point))