source: misc/tools/plotcsv/plotcsv.py @ 7214

Last change on this file since 7214 was 7214, checked in by rwilson, 15 years ago

Back- (and forward)-merge, Numeric to numpy.

File size: 25.1 KB
Line 
1#!/usr/bin/env python
2
3'''A module to draw a graph on the screen given a data file.
4
5The code is written such that it may be run as a program or imported
6and used as a module.
7'''
8
9import sys
10import os.path
11import types
12import csv
13import time
14import getopt
15import string
16try:
17    import cpickle as pickle
18except:
19    import pickle
20
21try:
22    import pylab
23except 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
35try:
36    import wx
37except 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
47Imported_PyEmbeddedImage = True
48try:
49    from wx.lib.embeddedimage import PyEmbeddedImage
50except ImportError:
51    Imported_PyEmbeddedImage = False
52
53
54# program name and version
55APP_NAME = 'plotcsv'
56APP_VERSION = '0.7'
57
58# name of the configuration filename
59# GUI values are saved in this file for next time
60ConfigFilename = '%s.cfg' % APP_NAME
61
62# GUI definitions
63BUTTON_WIDTH = 100
64BUTTON_HEIGHT = 30
65
66MARGIN = 10
67
68LAB_CTRL_OFFSET = 5
69
70BOX_WIDTH = 400
71BOX_HEIGHT = 360
72BOX_CSV_WIDTH = 400
73BOX_CSV_HEIGHT = 265
74BOX_PLOT_WIDTH = 400
75BOX_PLOT_HEIGHT = 140+25+25+30
76
77TXT_CSVFILE_WIDTH = 390
78TXT_CSVFILE_HEIGHT = 200
79
80COLLAB_X_OFFSET = 25
81
82CHBOX_HEIGHT = 30
83CHBOX_WIDTH = 100
84
85FORM_WIDTH = BOX_WIDTH + 15
86FORM_HEIGHT = 450+25+25+30
87
88START_YOFFSET = 7
89OUT_TEXT_XOFFSET = 8
90OUT_TEXT_YOFFSET = 11
91OUTPUT_BOX_HEIGHT = 45
92
93GEN_YOFFSET = 15
94GEN_DELTAY = 15
95GEN_LABELXOFFSET = 5
96GEN_TEXTXOFFSET = BOX_WIDTH/2 + GEN_LABELXOFFSET + 10
97
98TEXTLIST_WIDTH = BOX_WIDTH - 10
99TEXTLIST_HEIGHT = 200
100
101CTL_WIDTH = BOX_WIDTH / 2
102CTL_HEIGHT = 22
103
104DOUBLE_BUTTON_OFFSET = 8
105
106COMBO_FUDGE = 2
107
108# Copies of various parameters
109Region = ''
110GaugeNumber = ''
111MinimumHeight = ''
112MaximumHeight = ''
113
114# Flag strings - keys in the 'options' dictionary
115X_DATACOL = 'x_datacol'
116Y_DATACOL = 'y_datacol'
117X_RANGE = 'x_range'
118Y_RANGE = 'y_range'
119FILENAME = 'filename'
120SIZE = 'size'
121TITLE = 'title'
122X_LABEL = 'x_label'
123Y_LABEL = 'y_label'
124
125CSVWildcard = 'CSV files (*.csv)|*.csv|All files (*.*)|*.*'
126
127# Flag strings - keys in the 'options' dictionary
128X_DATACOL = 'x_datacol'
129Y_DATACOL = 'y_datacol'
130X_RANGE = 'x_range'
131Y_RANGE = 'y_range'
132FILENAME = 'filename'
133SIZE = 'size'
134TITLE = 'title'
135X_LABEL = 'x_label'
136Y_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
144if 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
168class 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 get(self, key, default=None):
209        """Override to allow: <var> = cfg.get(<key>, default=None)"""
210       
211        return self.cfgdict.get(key, default)
212       
213    def __getitem__(self, key):
214        """Override to allow: <var> = cfg[<key>]"""
215       
216        return self.cfgdict.get(key, None)
217
218    def __str__(self):
219        """__str__(self) -> String"""
220       
221        return "<config object at %s>" % hex(id(self))
222
223    def getfilename(self):
224        """getfilename(self) -> String filename"""
225       
226        return self.configfile
227
228    def setdeleted(self):
229        """setdeleted(self)"""
230       
231        self.delconf = True
232
233    def save(self):
234        """save(self)"""
235       
236        try:
237            f = open(self.configfile, "w")
238            p = pickle.Pickler(f)
239            p.dump(self.cfgdict)
240        except pickle.PicklingError, e:
241            print e
242        else:
243            f.close()
244        self.changed = False
245
246    def close(self):
247        """close(self)"""
248       
249        if self.changed:
250            self.save()
251        if self.delconf:
252            if os.path.exists(self.configfile):
253                os.remove(self.configfile)
254
255    def __del__(self):
256        """__del__(self)"""
257       
258        self.close()
259       
260
261################################################################################
262# Plot routines
263################################################################################
264
265##
266# @brief Get CSV data from a file.
267# @param filename Path to the data file to plot.
268# @param x_hdr The X axis title string.
269# @param y_hdr The Y axis title string.
270def getCSVData(filename, x_hdr, y_hdr):
271    # get contents of data file
272    # after this, 'header' is list of column header strings
273    #             'data' is a list of lists of data
274    fd = open(filename)
275    c = csv.reader(fd)
276    data = []
277    for row in c:
278        data.append(row)
279    fd.close()
280    header = data[0]
281    del data[0]         # get rid of header in dataset
282
283    # ensure header strings don't have leading/trailing spaces
284    header = map(string.strip, header)
285
286    # get int index values for column headers
287    try:
288        x_index = header.index(x_hdr)
289    except ValueError:
290        TheFrame.error("Sorry, X column header '%s' isn't in data file '%s'."
291                     % (x_hdr, filename))
292        return None
293
294    try:
295        y_index = header.index(y_hdr)
296    except ValueError:
297        TheFrame.error("Sorry, Y column header '%s' isn't in data file '%s'."
298                       % (y_hdr, filename))
299        return None
300
301    # get appropriate columns from data[]
302    x_data = map(lambda x: float(x[x_index]), data)
303    if x_hdr == 'time':
304        x_data = map(lambda x: float(x)/3600., x_data)
305    y_data = map(lambda x: float(x[y_index]), data)
306
307    return (x_data, y_data)
308
309
310##
311# @brief Plot data files.
312# @param filenames List of full pathnames to plot.
313# @param x_hdr The X axis data header string.
314# @param y_hdr The Y axis data header string.
315# @param title The string used to title the graph.
316# @param legend True if a legend is to be displayed.
317# @param legend_path True if legend is to contain full file paths.
318# @param grid True if grid is to be displayed.
319# @param fontsize Point size of font.
320# @param plot_width Width of plot lines.
321# @param x_label Override X axis label.
322# @param y_label Override Y axis label.
323def plot_files(filenames, x_hdr, y_hdr, title='', legend=False,
324               legend_path=False, grid=False, fontsize=10,
325               plot_width=2, x_label=None, y_label=None):
326    if x_label is None:
327        x_label = x_hdr
328    if y_label is None:
329        y_label = y_hdr
330       
331    pylab.rc('axes', linewidth=2)
332    pylab.rc('lines', linewidth=float(plot_width))
333    pylab.rc(('xtick', 'ytick'), labelsize=float(fontsize)*0.75,)
334    pylab.rc(('xtick.major', 'ytick.major'), size=5, pad=5)
335    pylab.rc('axes', labelsize=float(fontsize))
336    pylab.xlabel(x_label)
337    pylab.ylabel(y_label)
338
339    pylab.grid(grid)
340
341    for f in filenames:
342        result = getCSVData(f, x_hdr, y_hdr)
343        if result is None:          # some sort of error
344            pylab.close()
345            return
346
347        (x_data, y_data) = result
348        pylab.plot(x_data, y_data)
349
350    if title:
351        pylab.title(title, fontsize=float(fontsize)*1.5)
352       
353    if legend:
354        pylab.rc('legend', fancybox=True)
355        (min_y, max_y) = pylab.ylim()
356        range_y = max_y - min_y
357        add_y = float(range_y) / 5
358        pylab.ylim((min_y, max_y+add_y))
359        legend_names = filenames
360        if not legend_path:
361            legend_names = [os.path.basename(x) for x in filenames]
362        pylab.legend(legend_names, 'upper right')
363
364    pylab.show()
365    pylab.close()
366
367
368################################################################################
369# GUI routines
370################################################################################
371
372class MyFrame(wx.Frame):
373    def __init__(self, parent, id, title, pos=wx.DefaultPosition,
374                    size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE):
375        '''Lay out the GUI form'''
376       
377        # Make the frame
378        wx.Frame.__init__(self, parent, id, title, pos=(50, 50),
379                          size=(FORM_WIDTH, FORM_HEIGHT),
380                          style=(wx.DEFAULT_FRAME_STYLE &
381                                 ~ (wx.RESIZE_BOX | wx.MAXIMIZE_BOX |
382                                    wx.RESIZE_BORDER)))
383
384        p = self.panel = wx.Panel(self, -1)
385
386        if Imported_PyEmbeddedImage:
387            tsunami = getIcon()
388            icon = tsunami.GetIcon()
389            self.SetIcon(icon)
390       
391        self.Center()
392        self.Show(True)
393
394        # start laying out controls
395        Y_OFFSET = START_YOFFSET
396        wx.StaticBox(p, -1, 'CSV files', (3, Y_OFFSET),
397                     size=(BOX_CSV_WIDTH, BOX_CSV_HEIGHT))
398        Y_OFFSET += GEN_DELTAY
399        self.txtCSVFiles = wx.ListBox(p, -1, pos=(8, Y_OFFSET),
400                                      size=(TXT_CSVFILE_WIDTH,
401                                            TXT_CSVFILE_HEIGHT))
402        self.CSVFiles = []
403        Y_OFFSET += TXT_CSVFILE_HEIGHT + MARGIN
404        x = FORM_WIDTH/2 - BUTTON_WIDTH - DOUBLE_BUTTON_OFFSET
405        self.btnAddCSVFile = wx.Button(p, label="Add", pos=(x, Y_OFFSET),
406                                     size=(100, BUTTON_HEIGHT))
407        x = FORM_WIDTH/2 + DOUBLE_BUTTON_OFFSET
408        self.btnDelCSVFile = wx.Button(p, label="Delete All", pos=(x, Y_OFFSET),
409                                     size=(100, BUTTON_HEIGHT))
410        Y_OFFSET += BUTTON_HEIGHT + MARGIN
411
412        wx.StaticBox(p, -1, 'Plot files', (3, Y_OFFSET),
413                     size=(BOX_PLOT_WIDTH, BOX_PLOT_HEIGHT))
414        Y_OFFSET += GEN_DELTAY
415
416        wx.StaticText(p, -1, 'X-Column',
417                      pos=(COLLAB_X_OFFSET, Y_OFFSET+LAB_CTRL_OFFSET), style=wx.ALIGN_LEFT)
418        self.cbXColHdr = wx.Choice(p, -1, pos=(COLLAB_X_OFFSET+50, Y_OFFSET),
419                                   size=(80, -1))
420        self.XColHdr = []
421        wx.StaticText(p, -1, 'Y-Column',
422                      pos=(FORM_WIDTH/2,
423                           Y_OFFSET+LAB_CTRL_OFFSET), style=wx.ALIGN_LEFT)
424        x = FORM_WIDTH/2+50
425        self.cbYColHdr = wx.Choice(p, -1, pos=(x, Y_OFFSET), size=(80, -1))
426        self.YColHdr = []
427        Y_OFFSET += GEN_DELTAY*1.5
428
429        wx.StaticText(p, -1, 'X-Label',
430                      pos=(COLLAB_X_OFFSET, Y_OFFSET+LAB_CTRL_OFFSET), style=wx.ALIGN_LEFT)
431        self.txtXLabel = wx.TextCtrl(p, -1, "", size=(100, -1),
432                                     pos=(COLLAB_X_OFFSET+50,
433                                          Y_OFFSET+LAB_CTRL_OFFSET),
434                                     style=wx.ALIGN_LEFT)
435        wx.StaticText(p, -1, 'Y-Label',
436                      pos=(FORM_WIDTH/2,
437                           Y_OFFSET+LAB_CTRL_OFFSET), style=wx.ALIGN_LEFT)
438        x = FORM_WIDTH/2 + 50
439        self.txtYLabel = wx.TextCtrl(p, -1, "", size=(100, -1),
440                                     pos=(x,
441                                          Y_OFFSET+LAB_CTRL_OFFSET),
442                                     style=wx.ALIGN_LEFT)
443        Y_OFFSET += GEN_DELTAY*2.5
444
445        self.chkLegend = wx.CheckBox(p, -1, " Show graph legend",
446                                     pos=(COLLAB_X_OFFSET+20, Y_OFFSET))       
447        self.chkLegendPath = wx.CheckBox(p, -1, " Full file path in legend",
448                                         pos=(COLLAB_X_OFFSET+180, Y_OFFSET))       
449        Y_OFFSET += GEN_DELTAY*2
450
451        font_sizes = ['10', '12', '14', '16', '18', '20',
452                      '22', '24', '26', '28', '30', '32']
453        self.cbLabFontSize = wx.ComboBox(p, -1, "10",
454                                         (COLLAB_X_OFFSET+20, Y_OFFSET-5), 
455                                         (47, -1), font_sizes,
456                                        wx.CB_DROPDOWN)
457        wx.StaticText(p, -1, 'Label fontsize',
458                      pos=(COLLAB_X_OFFSET+72, Y_OFFSET),
459                      style=wx.ALIGN_LEFT)
460
461        self.chkShowGrid = wx.CheckBox(p, -1, " Show grid",
462                                       pos=(COLLAB_X_OFFSET+180, Y_OFFSET))       
463       
464        Y_OFFSET += GEN_DELTAY*2
465
466        plot_widths = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']
467        self.cbPlotWidth = wx.ComboBox(p, -1, "2",
468                                       (COLLAB_X_OFFSET+20, Y_OFFSET-5), 
469                                       (47, -1), plot_widths,
470                                       wx.CB_DROPDOWN)
471        wx.StaticText(p, -1, 'Plot width',
472                      pos=(COLLAB_X_OFFSET+72, Y_OFFSET),
473                      style=wx.ALIGN_LEFT)
474        Y_OFFSET += GEN_DELTAY + 3
475       
476        wx.StaticText(p, -1, 'Title',
477                      pos=(18, Y_OFFSET+LAB_CTRL_OFFSET+2), style=wx.ALIGN_LEFT)
478        self.txtTitle = wx.TextCtrl(p, -1, "", size=(350, -1),
479                                    pos=(COLLAB_X_OFFSET+20,
480                                         Y_OFFSET+LAB_CTRL_OFFSET),
481                                    style=wx.ALIGN_LEFT)
482        Y_OFFSET += BUTTON_HEIGHT # + MARGIN
483        x = FORM_WIDTH/2 - BUTTON_WIDTH/2
484        self.btnPlot = wx.Button(p, label="Plot", pos=(x, Y_OFFSET),
485                                     size=(100, BUTTON_HEIGHT))
486
487        # bind controls/events to handlers
488        self.Bind(wx.EVT_BUTTON, self.AddCSVFile, self.btnAddCSVFile)
489        self.Bind(wx.EVT_BUTTON, self.DelCSVFile, self.btnDelCSVFile)
490        self.Bind(wx.EVT_BUTTON, self.PlotFiles, self.btnPlot)
491        self.Bind(wx.EVT_CHECKBOX, self.ChangeLegend, self.chkLegend)
492        self.Bind(wx.EVT_CHOICE, self.ChangeXLabel, self.cbXColHdr)
493        self.Bind(wx.EVT_CHOICE, self.ChangeYLabel, self.cbYColHdr)
494       
495        self.Bind(wx.EVT_CLOSE, self.doStateSave)
496
497        # restore saved values
498        self.common_headers = None
499        self.cfg = Config(ConfigFilename)
500        self.doStateRestore()
501        self.updateHdrChoices()
502
503    def ChangeXLabel(self, event):
504        sel = self.cbXColHdr.GetCurrentSelection()
505        self.txtXLabel.SetValue(self.cbXColHdr.GetItems()[sel].title())
506   
507    def ChangeYLabel(self, event):
508        sel = self.cbYColHdr.GetCurrentSelection()
509        self.txtYLabel.SetValue(self.cbYColHdr.GetItems()[sel].title())
510   
511    def AddCSVFile(self, event):
512        """Add a CSV file to the listbox"""
513
514        dlg = wx.FileDialog(self, message='Choose a CSV file',
515                            defaultDir=self.file_dir, 
516                            defaultFile='',
517                            wildcard=CSVWildcard,
518                            style=wx.OPEN | wx.MULTIPLE | wx.CHANGE_DIR)
519
520        if dlg.ShowModal() == wx.ID_OK:
521            # This returns a list of  files that were selected.
522            files = dlg.GetFilenames()
523            self.file_dir = dlg.GetDirectory()
524            dlg.Destroy()
525            for f in files:
526                path = os.path.join(self.file_dir, f)
527                if path not in self.CSVFiles:
528                    headers = self.getHeaders(path)
529                    if headers:
530                        common_headers = self.orHeaders(headers)
531                        if not common_headers or len(common_headers) < 2:
532                            self.error("Sorry, file '%s' doesn't have enough headers in common with current files" % path)
533                        else:
534                            self.headers.append(headers)
535                            self.common_headers = common_headers
536                            self.CSVFiles.append(path)
537                            self.txtCSVFiles.Append(path)
538                    else:
539                        self.error("Sorry, file '%s' doesn't appear to be a CSV file" % path)
540                    self.updateHdrChoices()
541
542    def DelCSVFile(self, event):
543        """Delete all CSV files from the listbox"""
544       
545        self.txtCSVFiles.Clear()
546        self.CSVFiles = []
547        self.common_headers = None
548        self.updateHdrChoices()
549       
550    def ChangeLegend(self, event):
551        if self.chkLegend.GetValue():
552            self.chkLegendPath.Enable()
553        else:
554            self.chkLegendPath.Disable()
555
556    def doStateSave(self, event):
557        """Save state from 'self' variables & controls."""
558
559        self.cfg['CSVFiles'] = self.CSVFiles
560        self.cfg['XColHdr'] = self.XColHdr
561        self.cfg['XColHdrSelection'] = self.cbXColHdr.GetSelection()
562        self.cfg['YColHdr'] = self.YColHdr
563        self.cfg['YColHdrSelection'] = self.cbYColHdr.GetSelection()
564        self.cfg['file_dir'] = self.file_dir
565        self.cfg['GraphTitle'] = self.txtTitle.GetValue()
566        self.cfg['GraphLegend'] = self.chkLegend.GetValue()
567        self.cfg['LegendPath'] = self.chkLegendPath.GetValue()
568        self.cfg['ShowGrid'] = self.chkShowGrid.GetValue()
569        self.cfg['FontSize'] = self.cbLabFontSize.GetValue()
570        self.cfg['PlotWidth'] = self.cbPlotWidth.GetValue()
571        self.cfg['XLabel'] = self.txtXLabel.GetValue()
572        self.cfg['YLabel'] = self.txtYLabel.GetValue()
573
574        self.cfg.save()
575        event.Skip(True)
576
577    def doStateRestore(self):
578        """Restore state to 'self' variables."""
579
580        self.CSVFiles = self.cfg['CSVFiles']
581        if self.CSVFiles is None:
582            self.CSVFiles = []
583        self.XColHdr = self.cfg['XColHdr']
584        if self.XColHdr is None:
585            self.XColHdr = []
586        self.XColHdrSelection = self.cfg['XColHdrSelection']
587        self.YColHdr = self.cfg['YColHdr']
588        if self.YColHdr is None:
589            self.YColHdr = []
590        self.YColHdrSelection = self.cfg['YColHdrSelection']
591        self.file_dir = self.cfg['file_dir']
592        if self.file_dir is None:
593            self.file_dir = os.getcwd()
594
595        # put data into controls
596        self.headers = []
597        for (i, f) in enumerate(self.CSVFiles):
598            headers = self.getHeaders(f)
599            self.headers.append(headers)
600            self.txtCSVFiles.Insert(f, i)
601        self.common_headers = self.GenCommonHeaders(self.headers)
602        self.updateHdrChoices()
603
604        if self.XColHdrSelection >= 0:
605            self.cbXColHdr.SetSelection(self.XColHdrSelection)
606        if self.YColHdrSelection >= 0:
607            self.cbYColHdr.SetSelection(self.YColHdrSelection)
608        self.txtTitle.SetValue(self.cfg.get('GraphTitle', ''))
609        self.chkLegend.SetValue(self.cfg.get('GraphLegend', False))
610        self.chkLegendPath.SetValue(self.cfg.get('LegendPath', False))
611        self.cbLabFontSize.SetValue(self.cfg.get('FontSize', '14'))
612        self.cbPlotWidth.SetValue(self.cfg.get('PlotWidth', '1'))
613        self.chkShowGrid.SetValue(self.cfg.get('ShowGrid', True))
614        self.txtXLabel.SetValue(self.cfg.get('XLabel', ''))
615        self.txtYLabel.SetValue(self.cfg.get('YLabel', ''))
616        self.ChangeLegend(None)
617       
618    def getHeaders(self, filename):
619        '''Get header list from a file'''
620
621        try:
622            fd = open(filename, 'r')
623            hdr = fd.readline()
624            fd.close()
625        except:
626            return None
627
628        hdr = hdr.split(',')
629        hdr = [x.strip() for x in hdr]
630
631        return hdr
632       
633    def updateHdrChoices(self):
634        '''Update choice controls with header lists.
635
636        Disable controls if no CSV files (ie, no headers).
637        Keep selections visible afterwards, if possible.
638        '''
639
640        # get current selections, if any
641        selected_x = self.cbXColHdr.GetStringSelection()
642        selected_y = self.cbYColHdr.GetStringSelection()
643       
644        self.cbXColHdr.Clear()
645        self.cbYColHdr.Clear()
646
647        if self.common_headers:
648            self.cbXColHdr.Enable()
649            self.cbYColHdr.Enable()
650            self.btnPlot.Enable()
651            for h in self.common_headers:
652                self.cbXColHdr.Append(h)
653                self.cbYColHdr.Append(h)
654            if selected_x in self.common_headers:
655                index = self.common_headers.index(selected_x)
656                self.cbXColHdr.SetSelection(index)
657            if selected_y in self.common_headers:
658                index = self.common_headers.index(selected_y)
659                self.cbYColHdr.SetSelection(index)
660            self.txtXLabel.Enable()
661            self.txtYLabel.Enable()
662        else:
663            self.cbXColHdr.Disable()
664            self.cbYColHdr.Disable()
665            self.txtXLabel.Disable()
666            self.txtYLabel.Disable()
667            self.btnPlot.Disable()
668
669    def orHeaders(self, new_header):
670        '''Update X & Y column header choices.
671
672        Return new 'common headers' list from current
673        self.common_headers and the supplied new_header list.
674        '''
675
676        if self.common_headers:
677            result = [x for x in new_header if x in self.common_headers]
678        else:
679            result = new_header
680        return result
681
682    def GenCommonHeaders(self, headers):
683        '''Get new set of common headers.
684
685        Return a new 'common header' list given a
686        list of header lists.
687        '''
688
689        result = []
690        for header in headers:
691            if result:
692                result = [x for x in result if x in header]
693            else:
694                result = header
695        return result
696
697    def PlotFiles(self, event):
698        '''Plot files in the CSV file listbox.'''
699       
700        selected_x = self.cbXColHdr.GetStringSelection()
701        selected_y = self.cbYColHdr.GetStringSelection()
702
703        x_label = self.txtXLabel.GetValue()
704        y_label = self.txtYLabel.GetValue()
705
706        if selected_x and selected_y:
707            grid_on = self.chkShowGrid.GetValue()
708            fontsize = self.cbLabFontSize.GetValue()
709            plot_width = self.cbPlotWidth.GetValue()
710            plot_files(self.CSVFiles, selected_x, selected_y,
711                       x_label=x_label, y_label=y_label,
712                       title=self.txtTitle.GetValue(),
713                       legend=self.chkLegend.GetValue(),
714                       legend_path=self.chkLegendPath.GetValue(),
715                       grid=grid_on, fontsize=fontsize,
716                       plot_width = plot_width)
717
718            # hide problem with wxPython and matplotlib - close app!
719            self.Close(True)
720        else:
721            self.error('Sorry, you must select X- and Y-column data values')
722           
723    def error(self, msg):
724        '''Issue xwPython error message.'''
725       
726        wx.MessageBox(msg, 'Error')
727
728       
729################################################################################
730# Mainline code - start the application.
731################################################################################
732
733if __name__ == '__main__':
734    global TheFrame
735
736    # The frame reference
737    TheFrame = None
738
739    app = wx.App()
740    TheFrame = MyFrame(None, -1, '%s %s' % (APP_NAME, APP_VERSION),
741                       size=(FORM_WIDTH, FORM_HEIGHT))
742    app.SetTopWindow(TheFrame)
743    app.MainLoop()
744
Note: See TracBrowser for help on using the repository browser.