from __future__ import print_function # Python 3 does not have "unicode" because all strings are unicode try: unicode("a") except NameError as e: def unicode(s, **kw): return s class TSV: """A TSV instance holds the contents of a tab-separated-values file""" def __init__(self, filename): with open(filename) as f: self._readTitle(f) self._readData(f) def _readTitle(self, f): # Read titles from first line in file line = f.readline() self._titles = [ s.strip() for s in line.split('\t') ] def _readData(self, f): # Read data fields from each line # If there is a mismatch in number of fields, ignore line self._data = [] for line in f: fields = [ unicode(s.strip(), errors="ignore") for s in line.split('\t') ] if len(fields) != len(self._titles): continue self._data.append(fields) def getColumn(self, title): """Return data for column with given title""" n = self._titles.index(title) return [ fields[n] for fields in self._data ] def titles(self): """Return column titles (for readonly use)""" return self._titles def data(self): """Return all data rows (for readonly use)""" return self._data # # Test TSV code # testTSV = False if testTSV: def testTSV(filename): tsv = TSV(filename) print("File %s: %d rows" % (filename, len(tsv.data()))) print("Columns:") import pprint pprint.pprint(tsv.titles()) print() testTSV("Random_SNP.txt") testTSV("Table42.txt") raise SystemExit(0) class Plot: """User interface for plotting columns from TSV data file""" def __init__(self): # Default to no data self.tsv = None self._columnCache = {} # Setup GUI top level frame/application try: import Tkinter except ImportError: import tkinter as Tkinter app = Tkinter.Frame() app.grid(row=0, column=0, sticky="nsew") top = app.winfo_toplevel() top.columnconfigure(0, weight=1) top.rowconfigure(0, weight=1) self.app = app # Fill in rest of GUI self._setupGUI() def run(self): """Enter event loop""" self.app.mainloop() def _setupGUI(self): # GUI consists of three parts: option menus for # what to display, plot area, and action buttons import Pmw row = 0 row = self._setupMenus(row) row = self._setupPlot(row) bbox = Pmw.ButtonBox(self.app) bbox.grid(row=row, column=0, sticky="ew") bbox.add("Open...", command=self._openCB) bbox.add("Plot", command=self._plotCB) bbox.add("Quit", command=self._quitCB) self.buttonbox = bbox def _setupMenus(self, row): import Pmw self.menus = [] # "Type" menu for type of plot to display self.menuType = Pmw.OptionMenu(self.app, labelpos="w", label_text="Plot Type:", initialitem=0, items=["Scatter", "Line"]) self.menuType.grid(row=row, column=0, sticky="w") self.menus.append(self.menuType) row += 1 # Menu for x-axis data self.menuX = Pmw.OptionMenu(self.app, labelpos="w", label_text="X-axis Column:", initialitem=0, items=["No columns"]) self.menuX.grid(row=row, column=0, sticky="w") self.menus.append(self.menuX) row += 1 # Menu for y-axis data self.menuY = Pmw.OptionMenu(self.app, labelpos="w", label_text="Y-axis Column:", initialitem=0, items=["No columns"]) self.menuY.grid(row=row, column=0, sticky="w") self.menus.append(self.menuY) row += 1 # Menu for label data self.menuLabel = Pmw.OptionMenu(self.app, labelpos="w", label_text="Label Column:", items=["No columns"]) self.menuLabel.grid(row=row, column=0, sticky="w") self.menus.append(self.menuLabel) row += 1 Pmw.alignlabels(self.menus, sticky="e") return row def _setupPlot(self, row): # Set up matplotlib figure instance # We also need figureCanvas for redraws from matplotlib.figure import Figure from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg self.figure = Figure() fc = FigureCanvasTkAgg(self.figure, master=self.app) fc.get_tk_widget().grid(row=row, column=0, sticky="nsew") self.app.rowconfigure(row, weight=1) self.app.columnconfigure(0, weight=1) self.figureCanvas = fc self.plotax = None return row + 1 def _getPlot(self): # Add plot to figure if self.plotax is None: self.plotax = self.figure.add_subplot(111) return self.plotax def _clear(self): # Remove plot from figure if self.plotax is not None: self.figure.delaxes(self.plotax) self.plotax = None def _redraw(self): self.figureCanvas.draw() def _openCB(self): # Callback invoked when user presses "Open..." # Ask for data file name and replace any existing data try: from tkFileDialog import askopenfilename except ImportError: from tkinter.filedialog import askopenfilename filename = askopenfilename(parent=self.app, title="Select TSV file", filetypes=[ ("Tab-delimited text", "*.txt") ]) if not filename: return self._clear() # Clear plot self._redraw() self._columnCache = {} # Flush column cache self.tsv = TSV(unicode(filename)) # Replace column data # Update menu options titles = [ title for title in self.tsv.titles() if title ] titles.sort() titles.insert(0, "Select column...") self.menuX.setitems(titles) self.menuY.setitems(titles) self.menuLabel.setitems(titles) _plotFormat = { "Line": "-", "Scatter": ".", } def _plotCB(self): # Callback invoked when user presses "Plot" # Get x and y data. Only points with valid numeric # values for both x and y are actually displayed. xColName = self.menuX.getvalue() yColName = self.menuY.getvalue() try: columnX = self._getColumn(xColName, "x-axis") columnY = self._getColumn(yColName, "y-axis") except ValueError: return keep = [ i for i in range(len(columnX)) if columnX[i] is not None and columnY[i] is not None ] # Get label data if user selected a column labelColName = self.menuLabel.getvalue() try: columnLabel = self.tsv.getColumn(labelColName) except ValueError: # No labels wanted columnLabel = None # Create matplotlib "Axes" instance if not already present self._clear() plot = self._getPlot() plot.set_xlabel(xColName) plot.set_ylabel(yColName) # Make x-y plot plotFormat = self._plotFormat[self.menuType.getvalue()] cX = [ columnX[i] for i in keep ] cY = [ columnY[i] for i in keep ] plot.plot(cX, cY, plotFormat) # Display labels if selected if columnLabel: for i in keep: plot.text(columnX[i], columnY[i], columnLabel[i]) # Redraw figure self._redraw() def _getColumn(self, colName, desc): # Return a numeric version of requested column, replacing # any non-numeric values with None # Since the computation is non-trivial, we cache the results # so that we will not need to repeat it try: # Already in cache return self._columnCache[colName] except KeyError: pass try: # Fetch raw data col = self.tsv.getColumn(colName) except ValueError: from tkMessageBox import showerror showerror(title="Column Selection Error", message="Please select valid column" " for %s" % desc) raise ValueError("bad column selection") numericCol = [] for s in col: keep = [] for c in s: if c in [ '"', "'" ]: # Skip quotes continue if c in "0123456789.,": # Keep characters found in numbers if c not in ",": keep.append(c) else: # Fields with non-numeric characters # always generate None numericCol.append(None) break else: try: # Convert to floating point value = float(''.join(keep)) except ValueError: # Make value None if illegal value = None numericCol.append(value) # Store in cache and return column data self._columnCache[colName] = numericCol return numericCol def _quitCB(self): # Callback invoked when user presses "Quit" self.app.quit() if __name__ == "__main__": Plot().run()