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 matplotlib.pyplot as _pyplot
import numpy as _numpy
-import pylab as _pylab
-
-from pylab import subplot, connect, show, draw
-from numpy import array, fromfile, double
-__version__ = '0.2'
+__version__ = '0.3'
-class Cursor (object):
- """Like Cursor but the crosshair snaps to the nearest x,y point
-
- For simplicity, I'm assuming x is sorted.
+class Picker (object):
+ """Pick points from a plot
"""
- def __init__(self, axes, x, y, selected=None, highlight=False):
+ def __init__(self, axes, x=None, y=None, 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()
if highlight:
if selected is None:
selected = []
- sx = [self.x[i] for i in selected]
- sy = [self.y[i] for i in selected]
+ sx = [x[i] for i in selected]
+ sy = [y[i] for i in selected]
+ hold = self.axes.ishold()
+ axes.hold(True)
self.highlight_line, = axes.plot(sx, sy, 'r.', zorder=5)
+ self.axes.hold(hold)
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"
- pass
-
- def _get_xy(self, x, y):
- """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
- """
- 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.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={}'.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
- if self.highlight:
- if i in self.selected:
- self.selected.remove(i)
+
+ def onpick(self, event):
+ thisline = event.artist
+ xdata = thisline.get_xdata()
+ ydata = thisline.get_ydata()
+ for i in event.ind:
+ if self.highlight:
+ if i in self.selected:
+ self.selected.remove(i)
+ else:
+ self.selected.append(i)
+ sx = [xdata[i] for i in self.selected]
+ sy = [ydata[i] for i in self.selected]
+ self.highlight_line.set_data(sx, sy)
+ _pyplot.draw()
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)
- draw()
- else:
- print('{}\t{}\t{}'.format(x, y, i))
-
-
-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)
- rows = len(data)/cols
- data = data.reshape((rows,cols))
- x = data[:,0]
- y = data[:,1]
- return x,y
-
-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"""
- 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)
+ print('{}\t{}\t{}'.format(xdata[i], ydata[i], i))
+ break # only deal with the first picked point
if __name__ == "__main__" :
args = parser.parse_args()
if args.datafile:
- x,y = read_filename(args.datafile)
+ data = _numpy.genfromtxt(args.datafile)
else:
- x,y = read_file(_sys.stdin)
+ data = _numpy.genfromtxt(_sys.stdin)
+ x = data[:,0].squeeze()
+ y = data[:,1].squeeze()
- 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))
+ figure = _pyplot.figure()
+ axes = figure.add_subplot(1, 1, 1)
+ axes.plot(x, y, '.', picker=2)
+ axes.autoscale(tight=True)
+ picker = Picker(axes=axes, highlight=args.highlight)
+ figure.canvas.mpl_connect('pick_event', picker.onpick)
print('#x\ty\tindex')
- show()
+ _pyplot.show()
if args.highlight:
- for i in cursor.selected:
+ for i in picker.selected:
point = (x[i], y[i], i)
print('\t'.join(str(x) for x in point))