1 | #!/usr/bin/env python |
---|
2 | |
---|
3 | '''A module to draw a graph on the screen given a data file. |
---|
4 | |
---|
5 | The code is written such that it may be run as a program or imported |
---|
6 | and used as a module. |
---|
7 | ''' |
---|
8 | |
---|
9 | import sys |
---|
10 | import os.path |
---|
11 | import types |
---|
12 | import csv |
---|
13 | import time |
---|
14 | import getopt |
---|
15 | import string |
---|
16 | try: |
---|
17 | import cpickle |
---|
18 | except: |
---|
19 | import pickle |
---|
20 | |
---|
21 | try: |
---|
22 | import pylab |
---|
23 | except ImportError: |
---|
24 | import tkinter_error |
---|
25 | msg = ('Sorry, you have to install matplotlib.\n' |
---|
26 | 'Get it from either:\n' |
---|
27 | r' N:\georisk\downloads\matplotlib, or' '\n' |
---|
28 | ' [http://matplotlib.sourceforge.net/].\n\n' |
---|
29 | 'You will also need to have numpy installed:\n' |
---|
30 | r' N:\georisk\downloads\numpy') |
---|
31 | tkinter_error.tkinter_error(msg) |
---|
32 | print msg |
---|
33 | sys.exit(1) |
---|
34 | |
---|
35 | try: |
---|
36 | import wx |
---|
37 | except ImportError: |
---|
38 | import tkinter_error |
---|
39 | msg = ('Sorry, you have to install WxPython.\n' |
---|
40 | 'Get it from either:\n' |
---|
41 | r' N:\georisk\downloads\event_selection, or' '\n' |
---|
42 | ' [http://www.wxpython.org/download.php#binaries].') |
---|
43 | tkinter_error.tkinter_error(msg) |
---|
44 | print msg |
---|
45 | sys.exit(1) |
---|
46 | |
---|
47 | Imported_PyEmbeddedImage = True |
---|
48 | try: |
---|
49 | from wx.lib.embeddedimage import PyEmbeddedImage |
---|
50 | except ImportError: |
---|
51 | Imported_PyEmbeddedImage = False |
---|
52 | |
---|
53 | |
---|
54 | # program name and version |
---|
55 | APP_NAME = 'plotcsv' |
---|
56 | APP_VERSION = '0.5' |
---|
57 | |
---|
58 | # name of the configuration filename |
---|
59 | # GUI values are saved in this file for next time |
---|
60 | ConfigFilename = '%s.cfg' % APP_NAME |
---|
61 | |
---|
62 | # GUI definitions |
---|
63 | BUTTON_WIDTH = 100 |
---|
64 | BUTTON_HEIGHT = 30 |
---|
65 | |
---|
66 | MARGIN = 10 |
---|
67 | |
---|
68 | LAB_CTRL_OFFSET = 5 |
---|
69 | |
---|
70 | BOX_WIDTH = 400 |
---|
71 | BOX_HEIGHT = 360 |
---|
72 | BOX_CSV_WIDTH = 400 |
---|
73 | BOX_CSV_HEIGHT = 265 |
---|
74 | BOX_PLOT_WIDTH = 400 |
---|
75 | BOX_PLOT_HEIGHT = 140 |
---|
76 | |
---|
77 | TXT_CSVFILE_WIDTH = 390 |
---|
78 | TXT_CSVFILE_HEIGHT = 200 |
---|
79 | |
---|
80 | COLLAB_X_OFFSET = 25 |
---|
81 | |
---|
82 | CHBOX_HEIGHT = 30 |
---|
83 | CHBOX_WIDTH = 100 |
---|
84 | |
---|
85 | FORM_WIDTH = BOX_WIDTH + 15 |
---|
86 | FORM_HEIGHT = 450 |
---|
87 | |
---|
88 | START_YOFFSET = 7 |
---|
89 | OUT_TEXT_XOFFSET = 8 |
---|
90 | OUT_TEXT_YOFFSET = 11 |
---|
91 | OUTPUT_BOX_HEIGHT = 45 |
---|
92 | |
---|
93 | GEN_YOFFSET = 15 |
---|
94 | GEN_DELTAY = 15 |
---|
95 | GEN_LABELXOFFSET = 5 |
---|
96 | GEN_TEXTXOFFSET = BOX_WIDTH/2 + GEN_LABELXOFFSET + 10 |
---|
97 | |
---|
98 | TEXTLIST_WIDTH = BOX_WIDTH - 10 |
---|
99 | TEXTLIST_HEIGHT = 200 |
---|
100 | |
---|
101 | CTL_WIDTH = BOX_WIDTH / 2 |
---|
102 | CTL_HEIGHT = 22 |
---|
103 | |
---|
104 | DOUBLE_BUTTON_OFFSET = 8 |
---|
105 | |
---|
106 | COMBO_FUDGE = 2 |
---|
107 | |
---|
108 | # Copies of various parameters |
---|
109 | Region = '' |
---|
110 | GaugeNumber = '' |
---|
111 | MinimumHeight = '' |
---|
112 | MaximumHeight = '' |
---|
113 | |
---|
114 | # Flag strings - keys in the 'options' dictionary |
---|
115 | X_DATACOL = 'x_datacol' |
---|
116 | Y_DATACOL = 'y_datacol' |
---|
117 | X_RANGE = 'x_range' |
---|
118 | Y_RANGE = 'y_range' |
---|
119 | FILENAME = 'filename' |
---|
120 | SIZE = 'size' |
---|
121 | TITLE = 'title' |
---|
122 | X_LABEL = 'x_label' |
---|
123 | Y_LABEL = 'y_label' |
---|
124 | |
---|
125 | CSVWildcard = 'CSV files (*.csv)|*.csv|All files (*.*)|*.*' |
---|
126 | |
---|
127 | # Flag strings - keys in the 'options' dictionary |
---|
128 | X_DATACOL = 'x_datacol' |
---|
129 | Y_DATACOL = 'y_datacol' |
---|
130 | X_RANGE = 'x_range' |
---|
131 | Y_RANGE = 'y_range' |
---|
132 | FILENAME = 'filename' |
---|
133 | SIZE = 'size' |
---|
134 | TITLE = 'title' |
---|
135 | X_LABEL = 'x_label' |
---|
136 | Y_LABEL = 'y_label' |
---|
137 | |
---|
138 | |
---|
139 | ################################################################################ |
---|
140 | # This code was generated by img2py.py |
---|
141 | # Embed the ICON image here, so we don't need an external *.ico file. |
---|
142 | ################################################################################ |
---|
143 | |
---|
144 | if Imported_PyEmbeddedImage: |
---|
145 | def getIcon(): |
---|
146 | return PyEmbeddedImage( |
---|
147 | "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAApxJ" |
---|
148 | "REFUWIXtlr9O40AQh2edWwsKJFAarGtcRaElJWkoo4TGZegocqLME6TOC6CtaWjCFXRUfgJE" |
---|
149 | "xStsiivuKiQ4ND8Kx/bu+k+cUKRhpC0ie+f7sjs7a0FEoB3GDyIiYDcOQgjydkI24lvgW2Bj" |
---|
150 | "gctLolaLaG+P6OiI6PCQSIh89HpEy+VmOdE0lAKI1o8oArSuycMKAQegVQ9qDJeymUCdhGIF" |
---|
151 | "yRLE1FzAhZ+dATc3wN0d8PiYg9z3XAkLbgl0u4DnAePxWvja5XXen0zK4RFHhoA5lNoabs4z" |
---|
152 | "Uw4finDN2hAIw/xtKQGltoanMZut5k4U6K0IB2AIaJ0QVrQPT+LaU1vD0xg+2PDuSwJXCggC" |
---|
153 | "UwAoSLyTxC9SpfAFLxBwAJ99THlavg1uwS0i0LHGcGiurHMKbucav0Uu8V9I/JsrK7FmjRM+" |
---|
154 | "yRL77CPmuBbefUngxeNqCKR7HpDGPUWFmkjhEUf5v1qNkMNMoqzaNWtMJsU+kQmMx/bDq4HG" |
---|
155 | "68CW0LdzC97iFtrctiSmPC2Fpzvc7yenvd9PfmcC+/s56/x8tedGTehjQnQvssSSJRQrxBwj" |
---|
156 | "5LCwIi68KqwV8LykHz0/mxuuoa8GiBZ5YvnhQXFeF2USTeCWQFVo1oheBzn8jaCuPatZpRId" |
---|
157 | "7sBnHyMeJXCtgaen2vNbK+AWnHwXUJNiYZZP1slylt1Sp6eZVKVAAc4S6u/c6hO1Em7JV1yV" |
---|
158 | "pQKl8HTPnWZVKpH14PX3dUGgFm4ub5WEewvNZvZc54KxBBrB6ySmU+DgoHgPu2FIZAIbwask" |
---|
159 | "zNHrrf9oCIxPMrO3N4LXSYQhEMeNpmcCKVywaA43JUYjwPeBTqcx3BIIOYTHHi74YjP4F4OI" |
---|
160 | "IIgI9JOI2kS0JKI/m33TfzU+ASG0bvT61fpzAAAAAElFTkSuQmCC") |
---|
161 | |
---|
162 | |
---|
163 | ################################################################################ |
---|
164 | # An object that behaves like a dictionary but is pickled/unpickled from a file. |
---|
165 | # Used to save state in the application. |
---|
166 | ################################################################################ |
---|
167 | |
---|
168 | class Config(object): |
---|
169 | """An object that behaves like a dictionary but is persistent: |
---|
170 | |
---|
171 | cfg = Config('filename') |
---|
172 | cfg['save'] = 'A saved value' |
---|
173 | cfg[123] = 'value 123' |
---|
174 | print cfg['save'] |
---|
175 | |
---|
176 | The values stored in the file 'filename' will be available to any |
---|
177 | application using that config file. |
---|
178 | """ |
---|
179 | |
---|
180 | def __init__(self, configfile=None): |
---|
181 | """__init__(self, String filename=None) -> Config""" |
---|
182 | |
---|
183 | self.delconf = False |
---|
184 | self.cfgdict = {} |
---|
185 | self.changed = False |
---|
186 | if not configfile: |
---|
187 | self.delconf = True |
---|
188 | configfile =tempfile.mktemp() |
---|
189 | self.configfile = os.path.abspath(configfile) |
---|
190 | if os.path.exists(self.configfile): |
---|
191 | try: |
---|
192 | f = open(configfile, "r") |
---|
193 | u = pickle.Unpickler(f) |
---|
194 | self.cfgdict = u.load() |
---|
195 | except pickle.UnpicklingError, e: |
---|
196 | print e |
---|
197 | except: |
---|
198 | pass |
---|
199 | else: |
---|
200 | f.close() |
---|
201 | |
---|
202 | def __setitem__(self, key, value): |
---|
203 | """Override to allow: cfg[<key>] = <value>""" |
---|
204 | |
---|
205 | self.cfgdict[key] = value |
---|
206 | self.changed = True |
---|
207 | |
---|
208 | def __getitem__(self, key): |
---|
209 | """Override to allow: <var> = cfg[<key>]""" |
---|
210 | |
---|
211 | return self.cfgdict.get(key, None) |
---|
212 | |
---|
213 | def __str__(self): |
---|
214 | """__str__(self) -> String""" |
---|
215 | |
---|
216 | return "<config object at %s>" % hex(id(self)) |
---|
217 | |
---|
218 | def getfilename(self): |
---|
219 | """getfilename(self) -> String filename""" |
---|
220 | |
---|
221 | return self.configfile |
---|
222 | |
---|
223 | def setdeleted(self): |
---|
224 | """setdeleted(self)""" |
---|
225 | |
---|
226 | self.delconf = True |
---|
227 | |
---|
228 | def save(self): |
---|
229 | """save(self)""" |
---|
230 | |
---|
231 | try: |
---|
232 | f = open(self.configfile, "w") |
---|
233 | p = pickle.Pickler(f) |
---|
234 | p.dump(self.cfgdict) |
---|
235 | except pickle.PicklingError, e: |
---|
236 | print e |
---|
237 | else: |
---|
238 | f.close() |
---|
239 | self.changed = False |
---|
240 | |
---|
241 | def close(self): |
---|
242 | """close(self)""" |
---|
243 | |
---|
244 | if self.changed: |
---|
245 | self.save() |
---|
246 | if self.delconf: |
---|
247 | if os.path.exists(self.configfile): |
---|
248 | os.remove(self.configfile) |
---|
249 | |
---|
250 | def __del__(self): |
---|
251 | """__del__(self)""" |
---|
252 | |
---|
253 | self.close() |
---|
254 | |
---|
255 | |
---|
256 | ################################################################################ |
---|
257 | # Plot routines |
---|
258 | ################################################################################ |
---|
259 | |
---|
260 | ## |
---|
261 | # @brief Get CSV data from a file. |
---|
262 | # @param filename Path to the data file to plot. |
---|
263 | # @param x_hdr The X axis title string. |
---|
264 | # @param y_hdr The Y axis title string. |
---|
265 | def getCSVData(filename, x_hdr, y_hdr): |
---|
266 | # get contents of data file |
---|
267 | # after this, 'header' is list of column header strings |
---|
268 | # 'data' is a list of lists of data |
---|
269 | fd = open(filename) |
---|
270 | c = csv.reader(fd) |
---|
271 | data = [] |
---|
272 | for row in c: |
---|
273 | data.append(row) |
---|
274 | fd.close() |
---|
275 | header = data[0] |
---|
276 | del data[0] # get rid of header in dataset |
---|
277 | |
---|
278 | # ensure header strings don't have leading/trailing spaces |
---|
279 | header = map(string.strip, header) |
---|
280 | |
---|
281 | # get int index values for column headers |
---|
282 | try: |
---|
283 | x_index = header.index(x_hdr) |
---|
284 | except ValueError: |
---|
285 | TheFrame.error("Sorry, X column header '%s' isn't in data file '%s'." |
---|
286 | % (x_hdr, filename)) |
---|
287 | return None |
---|
288 | |
---|
289 | try: |
---|
290 | y_index = header.index(y_hdr) |
---|
291 | except ValueError: |
---|
292 | TheFrame.error("Sorry, Y column header '%s' isn't in data file '%s'." |
---|
293 | % (y_hdr, filename)) |
---|
294 | return None |
---|
295 | |
---|
296 | # get appropriate columns from data[] |
---|
297 | x_data = map(lambda x: float(x[x_index]), data) |
---|
298 | if x_hdr == 'time': |
---|
299 | x_data = map(lambda x: float(x)/3600., x_data) |
---|
300 | y_data = map(lambda x: float(x[y_index]), data) |
---|
301 | |
---|
302 | return (x_data, y_data) |
---|
303 | |
---|
304 | |
---|
305 | ## |
---|
306 | # @brief Plot data files. |
---|
307 | # @param filenames List of full pathnames to plot. |
---|
308 | # @param x_hdr The X axis label string. |
---|
309 | # @param y_hdr The Y axis label string. |
---|
310 | # @param title The string used to title the graph. |
---|
311 | # @param legend True if a legend is to be displayed. |
---|
312 | # @param legend_path True if legend is to contain full file paths. |
---|
313 | def plot_files(filenames, x_hdr, y_hdr, title='', legend=False, |
---|
314 | legend_path=False): |
---|
315 | pylab.rc('axes', linewidth=2) |
---|
316 | pylab.xlabel(x_hdr.title()) |
---|
317 | pylab.ylabel(y_hdr.title()) |
---|
318 | pylab.grid(True) |
---|
319 | |
---|
320 | for f in filenames: |
---|
321 | result = getCSVData(f, x_hdr, y_hdr) |
---|
322 | if result is None: # some sort of error |
---|
323 | pylab.close() |
---|
324 | return |
---|
325 | |
---|
326 | (x_data, y_data) = result |
---|
327 | pylab.plot(x_data, y_data) |
---|
328 | |
---|
329 | if title: |
---|
330 | pylab.title(title) |
---|
331 | if legend: |
---|
332 | (min_y, max_y) = pylab.ylim() |
---|
333 | range_y = max_y - min_y |
---|
334 | add_y = float(range_y) / 5 |
---|
335 | pylab.ylim((min_y, max_y+add_y)) |
---|
336 | legend_names = filenames |
---|
337 | if not legend_path: |
---|
338 | legend_names = [os.path.basename(x) for x in filenames] |
---|
339 | pylab.legend(legend_names, 'upper right') |
---|
340 | |
---|
341 | pylab.show() |
---|
342 | pylab.close() |
---|
343 | |
---|
344 | |
---|
345 | ################################################################################ |
---|
346 | # GUI routines |
---|
347 | ################################################################################ |
---|
348 | |
---|
349 | class MyFrame(wx.Frame): |
---|
350 | def __init__(self, parent, id, title, pos=wx.DefaultPosition, |
---|
351 | size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE): |
---|
352 | '''Lay out the GUI form''' |
---|
353 | |
---|
354 | # Make the frame |
---|
355 | wx.Frame.__init__(self, parent, id, title, pos=(50, 50), |
---|
356 | size=(FORM_WIDTH, FORM_HEIGHT), |
---|
357 | style=(wx.DEFAULT_FRAME_STYLE & |
---|
358 | ~ (wx.RESIZE_BOX | wx.MAXIMIZE_BOX | |
---|
359 | wx.RESIZE_BORDER))) |
---|
360 | |
---|
361 | p = self.panel = wx.Panel(self, -1) |
---|
362 | |
---|
363 | if Imported_PyEmbeddedImage: |
---|
364 | tsunami = getIcon() |
---|
365 | icon = tsunami.GetIcon() |
---|
366 | self.SetIcon(icon) |
---|
367 | |
---|
368 | self.Center() |
---|
369 | self.Show(True) |
---|
370 | |
---|
371 | # start laying out controls |
---|
372 | Y_OFFSET = START_YOFFSET |
---|
373 | wx.StaticBox(p, -1, 'CSV files', (3, Y_OFFSET), |
---|
374 | size=(BOX_CSV_WIDTH, BOX_CSV_HEIGHT)) |
---|
375 | Y_OFFSET += GEN_DELTAY |
---|
376 | self.txtCSVFiles = wx.ListBox(p, -1, pos=(8, Y_OFFSET), |
---|
377 | size=(TXT_CSVFILE_WIDTH, |
---|
378 | TXT_CSVFILE_HEIGHT)) |
---|
379 | self.CSVFiles = [] |
---|
380 | Y_OFFSET += TXT_CSVFILE_HEIGHT + MARGIN |
---|
381 | x = FORM_WIDTH/2 - BUTTON_WIDTH - DOUBLE_BUTTON_OFFSET |
---|
382 | self.btnAddCSVFile = wx.Button(p, label="Add", pos=(x, Y_OFFSET), |
---|
383 | size=(100, BUTTON_HEIGHT)) |
---|
384 | x = FORM_WIDTH/2 + DOUBLE_BUTTON_OFFSET |
---|
385 | self.btnDelCSVFile = wx.Button(p, label="Delete", pos=(x, Y_OFFSET), |
---|
386 | size=(100, BUTTON_HEIGHT)) |
---|
387 | Y_OFFSET += BUTTON_HEIGHT + MARGIN |
---|
388 | |
---|
389 | wx.StaticBox(p, -1, 'Plot files', (3, Y_OFFSET), |
---|
390 | size=(BOX_PLOT_WIDTH, BOX_PLOT_HEIGHT)) |
---|
391 | Y_OFFSET += GEN_DELTAY |
---|
392 | wx.StaticText(p, -1, 'X-Column', |
---|
393 | pos=(COLLAB_X_OFFSET, Y_OFFSET+LAB_CTRL_OFFSET), style=wx.ALIGN_LEFT) |
---|
394 | self.cbXColHdr = wx.Choice(p, -1, pos=(COLLAB_X_OFFSET+65, Y_OFFSET), |
---|
395 | size=(80, -1)) |
---|
396 | self.XColHdr = [] |
---|
397 | wx.StaticText(p, -1, 'Y-Column', |
---|
398 | pos=(FORM_WIDTH/2+COLLAB_X_OFFSET, |
---|
399 | Y_OFFSET+LAB_CTRL_OFFSET), style=wx.ALIGN_LEFT) |
---|
400 | x = FORM_WIDTH/2 + COLLAB_X_OFFSET |
---|
401 | self.cbYColHdr = wx.Choice(p, -1, pos=(x+65, Y_OFFSET), size=(80, -1)) |
---|
402 | self.YColHdr = [] |
---|
403 | Y_OFFSET += GEN_DELTAY*2 |
---|
404 | |
---|
405 | self.chkLegend = wx.CheckBox(p, -1, " Show graph legend", |
---|
406 | pos=(COLLAB_X_OFFSET+20, Y_OFFSET)) |
---|
407 | self.chkLegendPath = wx.CheckBox(p, -1, " Full file path in legend", |
---|
408 | pos=(COLLAB_X_OFFSET+180, Y_OFFSET)) |
---|
409 | Y_OFFSET += GEN_DELTAY + 3 |
---|
410 | |
---|
411 | wx.StaticText(p, -1, 'Title', |
---|
412 | pos=(18, Y_OFFSET+LAB_CTRL_OFFSET+2), style=wx.ALIGN_LEFT) |
---|
413 | self.txtTitle = wx.TextCtrl(p, -1, "", size=(350, -1), |
---|
414 | pos=(COLLAB_X_OFFSET+20, |
---|
415 | Y_OFFSET+LAB_CTRL_OFFSET), |
---|
416 | style=wx.ALIGN_LEFT) |
---|
417 | |
---|
418 | Y_OFFSET += BUTTON_HEIGHT + MARGIN |
---|
419 | x = FORM_WIDTH/2 - BUTTON_WIDTH/2 |
---|
420 | self.btnPlot = wx.Button(p, label="Plot", pos=(x, Y_OFFSET), |
---|
421 | size=(100, BUTTON_HEIGHT)) |
---|
422 | |
---|
423 | # bind controls/events to handlers |
---|
424 | self.Bind(wx.EVT_BUTTON, self.AddCSVFile, self.btnAddCSVFile) |
---|
425 | self.Bind(wx.EVT_BUTTON, self.DelCSVFile, self.btnDelCSVFile) |
---|
426 | self.Bind(wx.EVT_BUTTON, self.PlotFiles, self.btnPlot) |
---|
427 | self.Bind(wx.EVT_CHECKBOX, self.ChangeLegend, self.chkLegend) |
---|
428 | |
---|
429 | self.Bind(wx.EVT_CLOSE, self.doStateSave) |
---|
430 | |
---|
431 | # restore saved values |
---|
432 | self.common_headers = None |
---|
433 | self.cfg = Config(ConfigFilename) |
---|
434 | self.doStateRestore() |
---|
435 | self.updateHdrChoices() |
---|
436 | |
---|
437 | def AddCSVFile(self, event): |
---|
438 | """Add a CSV file to the listbox""" |
---|
439 | |
---|
440 | dlg = wx.FileDialog(self, message='Choose a CSV file', |
---|
441 | defaultDir=self.file_dir, |
---|
442 | defaultFile='', |
---|
443 | wildcard=CSVWildcard, |
---|
444 | style=wx.OPEN | wx.CHANGE_DIR) |
---|
445 | |
---|
446 | if dlg.ShowModal() == wx.ID_OK: |
---|
447 | # This returns a list of files that were selected. |
---|
448 | path = dlg.GetPath() |
---|
449 | self.file_dir = dlg.GetDirectory() |
---|
450 | dlg.Destroy() |
---|
451 | if path not in self.CSVFiles: |
---|
452 | headers = self.getHeaders(path) |
---|
453 | if headers: |
---|
454 | common_headers = self.orHeaders(headers) |
---|
455 | if not common_headers or len(common_headers) < 2: |
---|
456 | self.error("Sorry, file '%s' doesn't have enough headers in common with current files" % path) |
---|
457 | else: |
---|
458 | self.headers.append(headers) |
---|
459 | self.common_headers = common_headers |
---|
460 | self.CSVFiles.append(path) |
---|
461 | self.txtCSVFiles.Append(path) |
---|
462 | else: |
---|
463 | self.error("Sorry, file '%s' doesn't appear to be a CSV file" % path) |
---|
464 | self.updateHdrChoices() |
---|
465 | |
---|
466 | def DelCSVFile(self, event): |
---|
467 | """Delete a CSV file from the listbox""" |
---|
468 | |
---|
469 | sel = self.txtCSVFiles.GetSelections() |
---|
470 | if sel: |
---|
471 | sel = sel[0] |
---|
472 | self.txtCSVFiles.Delete(sel) |
---|
473 | del self.CSVFiles[sel] |
---|
474 | del self.headers[sel] |
---|
475 | self.common_headers = self.GenCommonHeaders(self.headers) |
---|
476 | self.updateHdrChoices() |
---|
477 | |
---|
478 | def ChangeLegend(self, event): |
---|
479 | if self.chkLegend.GetValue(): |
---|
480 | self.chkLegendPath.Enable() |
---|
481 | else: |
---|
482 | self.chkLegendPath.Disable() |
---|
483 | |
---|
484 | def doStateSave(self, event): |
---|
485 | """Save state from 'self' variables & controls.""" |
---|
486 | |
---|
487 | self.cfg['CSVFiles'] = self.CSVFiles |
---|
488 | self.cfg['XColHdr'] = self.XColHdr |
---|
489 | self.cfg['XColHdrSelection'] = self.cbXColHdr.GetSelection() |
---|
490 | self.cfg['YColHdr'] = self.YColHdr |
---|
491 | self.cfg['YColHdrSelection'] = self.cbYColHdr.GetSelection() |
---|
492 | self.cfg['file_dir'] = self.file_dir |
---|
493 | self.cfg['GraphTitle'] = self.txtTitle.GetValue() |
---|
494 | self.cfg['GraphLegend'] = self.chkLegend.GetValue() |
---|
495 | self.cfg['LegendPath'] = self.chkLegendPath.GetValue() |
---|
496 | |
---|
497 | self.cfg.save() |
---|
498 | event.Skip(True) |
---|
499 | |
---|
500 | def doStateRestore(self): |
---|
501 | """Restore state to 'self' variables.""" |
---|
502 | |
---|
503 | self.CSVFiles = self.cfg['CSVFiles'] |
---|
504 | if self.CSVFiles is None: |
---|
505 | self.CSVFiles = [] |
---|
506 | self.XColHdr = self.cfg['XColHdr'] |
---|
507 | if self.XColHdr is None: |
---|
508 | self.XColHdr = [] |
---|
509 | self.XColHdrSelection = self.cfg['XColHdrSelection'] |
---|
510 | self.YColHdr = self.cfg['YColHdr'] |
---|
511 | if self.YColHdr is None: |
---|
512 | self.YColHdr = [] |
---|
513 | self.YColHdrSelection = self.cfg['YColHdrSelection'] |
---|
514 | self.file_dir = self.cfg['file_dir'] |
---|
515 | if self.file_dir is None: |
---|
516 | self.file_dir = os.getcwd() |
---|
517 | |
---|
518 | # put data into controls |
---|
519 | self.headers = [] |
---|
520 | for (i, f) in enumerate(self.CSVFiles): |
---|
521 | headers = self.getHeaders(f) |
---|
522 | self.headers.append(headers) |
---|
523 | self.txtCSVFiles.Insert(f, i) |
---|
524 | self.common_headers = self.GenCommonHeaders(self.headers) |
---|
525 | self.updateHdrChoices() |
---|
526 | |
---|
527 | if self.XColHdrSelection >= 0: |
---|
528 | self.cbXColHdr.SetSelection(self.XColHdrSelection) |
---|
529 | if self.YColHdrSelection >= 0: |
---|
530 | self.cbYColHdr.SetSelection(self.YColHdrSelection) |
---|
531 | title = self.cfg['GraphTitle'] |
---|
532 | if title is None: |
---|
533 | title = '' |
---|
534 | self.txtTitle.SetValue(title) |
---|
535 | if self.cfg['GraphLegend']: |
---|
536 | self.chkLegend.SetValue(True) |
---|
537 | if self.cfg['LegendPath']: |
---|
538 | self.chkLegendPath.SetValue(True) |
---|
539 | self.ChangeLegend(None) |
---|
540 | |
---|
541 | def getHeaders(self, filename): |
---|
542 | '''Get header list from a file''' |
---|
543 | |
---|
544 | try: |
---|
545 | fd = open(filename, 'r') |
---|
546 | hdr = fd.readline() |
---|
547 | fd.close() |
---|
548 | except: |
---|
549 | return None |
---|
550 | |
---|
551 | hdr = hdr.split(',') |
---|
552 | hdr = [x.strip() for x in hdr] |
---|
553 | |
---|
554 | return hdr |
---|
555 | |
---|
556 | def updateHdrChoices(self): |
---|
557 | '''Update choice controls with header lists. |
---|
558 | |
---|
559 | Disable controls if no CSV files (ie, no headers). |
---|
560 | Keep selections visible afterwards, if possible. |
---|
561 | ''' |
---|
562 | |
---|
563 | # get current selections, if any |
---|
564 | selected_x = self.cbXColHdr.GetStringSelection() |
---|
565 | selected_y = self.cbYColHdr.GetStringSelection() |
---|
566 | |
---|
567 | self.cbXColHdr.Clear() |
---|
568 | self.cbYColHdr.Clear() |
---|
569 | |
---|
570 | if self.common_headers: |
---|
571 | self.cbXColHdr.Enable() |
---|
572 | self.cbYColHdr.Enable() |
---|
573 | self.btnPlot.Enable() |
---|
574 | for h in self.common_headers: |
---|
575 | self.cbXColHdr.Append(h) |
---|
576 | self.cbYColHdr.Append(h) |
---|
577 | if selected_x in self.common_headers: |
---|
578 | index = self.common_headers.index(selected_x) |
---|
579 | self.cbXColHdr.SetSelection(index) |
---|
580 | if selected_y in self.common_headers: |
---|
581 | index = self.common_headers.index(selected_y) |
---|
582 | self.cbYColHdr.SetSelection(index) |
---|
583 | else: |
---|
584 | self.cbXColHdr.Disable() |
---|
585 | self.cbYColHdr.Disable() |
---|
586 | self.btnPlot.Disable() |
---|
587 | |
---|
588 | def orHeaders(self, new_header): |
---|
589 | '''Update X & Y column header choices. |
---|
590 | |
---|
591 | Return new 'common headers' list from current |
---|
592 | self.common_headers and the supplied new_header list. |
---|
593 | ''' |
---|
594 | |
---|
595 | if self.common_headers: |
---|
596 | result = [x for x in new_header if x in self.common_headers] |
---|
597 | else: |
---|
598 | result = new_header |
---|
599 | return result |
---|
600 | |
---|
601 | def GenCommonHeaders(self, headers): |
---|
602 | '''Get new set of common headers. |
---|
603 | |
---|
604 | Return a new 'common header' list given a |
---|
605 | list of header lists. |
---|
606 | ''' |
---|
607 | |
---|
608 | result = [] |
---|
609 | for header in headers: |
---|
610 | if result: |
---|
611 | result = [x for x in result if x in header] |
---|
612 | else: |
---|
613 | result = header |
---|
614 | return result |
---|
615 | |
---|
616 | def PlotFiles(self, event): |
---|
617 | '''Plot files in the CSV file listbox.''' |
---|
618 | |
---|
619 | selected_x = self.cbXColHdr.GetStringSelection() |
---|
620 | selected_y = self.cbYColHdr.GetStringSelection() |
---|
621 | |
---|
622 | if selected_x and selected_y: |
---|
623 | plot_files(self.CSVFiles, selected_x, selected_y, |
---|
624 | title=self.txtTitle.GetValue(), |
---|
625 | legend=self.chkLegend.GetValue(), |
---|
626 | legend_path=self.chkLegendPath.GetValue()) |
---|
627 | # hide problem with wxPython and matplotlib - close app! |
---|
628 | self.Close(True) |
---|
629 | |
---|
630 | def error(self, msg): |
---|
631 | '''Issue xwPython error message.''' |
---|
632 | |
---|
633 | wx.MessageBox(msg, 'Error') |
---|
634 | |
---|
635 | |
---|
636 | ################################################################################ |
---|
637 | # Mainline code - start the application. |
---|
638 | ################################################################################ |
---|
639 | |
---|
640 | if __name__ == '__main__': |
---|
641 | global TheFrame |
---|
642 | |
---|
643 | # The frame reference |
---|
644 | TheFrame = None |
---|
645 | |
---|
646 | app = wx.App() |
---|
647 | TheFrame = MyFrame(None, -1, '%s %s' % (APP_NAME, APP_VERSION), |
---|
648 | size=(FORM_WIDTH, FORM_HEIGHT)) |
---|
649 | app.SetTopWindow(TheFrame) |
---|
650 | app.MainLoop() |
---|
651 | |
---|