Changeset 6344 for misc/tools/plotcsv/plotcsv.py
- Timestamp:
- Feb 16, 2009, 2:12:14 PM (16 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
misc/tools/plotcsv/plotcsv.py
r6341 r6344 14 14 import getopt 15 15 import pylab 16 try: 17 import cpickle 18 except: 19 import pickle 16 20 17 21 try: … … 93 97 MinimumHeight = '' 94 98 MaximumHeight = '' 99 100 # Flag strings - keys in the 'options' dictionary 101 X_DATACOL = 'x_datacol' 102 Y_DATACOL = 'y_datacol' 103 X_RANGE = 'x_range' 104 Y_RANGE = 'y_range' 105 FILENAME = 'filename' 106 SIZE = 'size' 107 TITLE = 'title' 108 X_LABEL = 'x_label' 109 Y_LABEL = 'y_label' 110 111 CSVWildcard = 'CSV files (*.csv)|*.csv|All files (*.*)|*.*' 95 112 96 113 # Flag strings - keys in the 'options' dictionary … … 130 147 131 148 132 ## 133 # @brief Issue an error message. 134 # @param msg The message. 135 def error(msg): 136 print >>sys.stderr, msg 137 sys.exit(10) 138 139 149 ################################################################################ 150 # An object that behaves like a dictionary but is pickled/unpickled from a file. 151 ################################################################################ 152 153 class Config(object): 154 """ 155 An object that behaves like a dictionary but is persistent: 156 157 cfg = Config('filename') 158 cfg['save'] = 'A saved value' 159 cfg[123] = 'value 123' 160 print cfg['save'] 161 162 The values stored in the file 'filename' will be available to any 163 application using that config file. 164 """ 165 166 def __init__(self, configfile=None): 167 """ 168 __init__(self, String filename=None) -> Config 169 """ 170 171 self.delconf = False 172 self.cfgdict = {} 173 self.changed = False 174 if not configfile: 175 self.delconf = True 176 configfile =tempfile.mktemp() 177 self.configfile = os.path.abspath(configfile) 178 if os.path.exists(self.configfile): 179 try: 180 f = open(configfile, "r") 181 u = pickle.Unpickler(f) 182 self.cfgdict = u.load() 183 except pickle.UnpicklingError, e: 184 print e 185 except: 186 pass 187 else: 188 f.close() 189 190 def __setitem__(self, key, value): 191 """ 192 Override to allow: cfg[<key>] = <value> 193 """ 194 self.cfgdict[key] = value 195 self.changed = True 196 197 def __getitem__(self, key): 198 """ 199 Override to allow: <var> = cfg[<key>] 200 """ 201 return self.cfgdict.get(key, None) 202 203 def __str__(self): 204 """ 205 __str__(self) -> String 206 """ 207 return "<config object at %s>" % hex(id(self)) 208 209 def getfilename(self): 210 """ 211 getfilename(self) -> String filename 212 """ 213 return self.configfile 214 215 def setdeleted(self): 216 """ 217 setdeleted(self) 218 """ 219 self.delconf = True 220 221 def save(self): 222 """ 223 save(self) 224 """ 225 try: 226 f = open(self.configfile, "w") 227 p = pickle.Pickler(f) 228 p.dump(self.cfgdict) 229 except pickle.PicklingError, e: 230 print e 231 else: 232 f.close() 233 self.changed = False 234 235 def close(self): 236 """ 237 close(self) 238 """ 239 if self.changed: 240 self.save() 241 if self.delconf: 242 if os.path.exists(self.configfile): 243 os.remove(self.configfile) 244 245 def __del__(self): 246 """ 247 __del__(self) 248 """ 249 self.close() 250 251 ################################################################################ 252 # Plot routines 253 ################################################################################ 254 140 255 ## 141 256 # @brief Plot a sequence of data. 142 257 # @param x_data The X data sequence to plot. 143 # @param y_data A list of one or more Y data sequencesto plot.258 # @param y_data The Y data sequence to plot. 144 259 # @param options A dictionary of plot options. 145 260 def plot_data(x_data, y_data, options): 261 print 'x_data=%s' % str(x_data) 262 print 'y_data=%s' % str(y_data) 146 263 pylab.plot(x_data, y_data) 147 pylab.title(options.get(TITLE, '')) 148 pylab.grid(True) 149 150 # if user request a particular Y range 264 265 # if user requested a particular Y range 151 266 if not options[Y_RANGE] is None: 152 267 try: … … 158 273 pylab.ylim(ymin=minimum, ymax=maximum) 159 274 160 pylab.xlabel(options.get(X_LABEL, ''))161 pylab.ylabel(options.get(Y_LABEL, ''))162 163 pylab.show()164 165 275 166 276 ## 167 # @brief Plot a data file.277 # @brief Get CSV data from a file. 168 278 # @param filename Path to the data file to plot. 279 # @param x_hdr The X axis title string (or index). 280 # @param y_hdr The Y axis title string (or index). 169 281 # @param options A dictionary of options. 170 def plot_file(filename, options=None): 171 # set options defaults 172 opts = options 173 if opts is None: 174 opts = {} 175 176 opts[X_DATACOL] = opts.get(X_DATACOL, '0') 177 opts[Y_DATACOL] = opts.get(Y_DATACOL, '1') 178 opts[SIZE] = opts.get(SIZE, '800,600') 179 opts[Y_RANGE] = opts.get(Y_RANGE, None) 180 282 def getCSVData(filename, x_hdr, y_hdr, options): 181 283 # get contents of data file 182 284 # after this, 'header' is list of column header strings … … 189 291 fd.close() 190 292 header = data[0] 191 del data[0] 293 del data[0] # get rid of header in dataset 192 294 193 295 # convert column specifiers to 'int' if required 194 296 try: 195 index = int( opts[X_DATACOL])297 index = int(x_hdr) 196 298 except: 197 299 try: 198 index = header.index( opts[X_DATACOL])300 index = header.index(x_hdr) 199 301 except ValueError: 200 error("Sorry, X column header '%s' isn't in the data file." % opts[X_DATACOL])201 opt s[X_DATACOL] = index302 error("Sorry, X column header '%s' isn't in the data file." % x_hdr) 303 options[X_DATACOL] = index 202 304 203 305 try: 204 index = int( opts[Y_DATACOL])306 index = int(y_hdr) 205 307 except: 206 308 try: 207 index = header.index( opts[Y_DATACOL])309 index = header.index(y_hdr) 208 310 except ValueError: 209 error("Sorry, Y column header '%s' isn't in the data file." % opts[Y_DATACOL])210 opt s[Y_DATACOL] = index311 error("Sorry, Y column header '%s' isn't in the data file." % y_hdr) 312 options[Y_DATACOL] = index 211 313 212 # extract required columns from the data 213 x_col = opt s[X_DATACOL]214 y_col = opt s[Y_DATACOL]314 # extract required columns from the data (int at this point) 315 x_col = options[X_DATACOL] 316 y_col = options[Y_DATACOL] 215 317 216 318 # get max column number, check requested columns … … 227 329 y_label = header[y_col].title() 228 330 229 opts[TITLE] = 'File: %s' % filename 230 opts[X_LABEL] = x_label 231 opts[Y_LABEL] = y_label 232 233 plot_data(x_data, y_data, opts) 331 options[X_LABEL] = x_label 332 options[Y_LABEL] = y_label 333 334 return (x_data, y_data) 335 336 337 ## 338 # @brief Plot data files. 339 # @param filenames List of full pathnames to plot. 340 # @param options A dictionary of options. 341 def plot_files(filenames, options=None): 342 # set options defaults 343 opts = options 344 if opts is None: 345 opts = {} 346 347 ## pylab.title(options.get(TITLE, '')) 348 ## pylab.xlabel(options.get(X_LABEL, '')) 349 ## pylab.ylabel(options.get(Y_LABEL, '')) 350 ## pylab.grid(True) 351 ## 352 for f in filenames: 353 (x_data, y_data) = getCSVData(f, opts[X_LABEL], opts[Y_LABEL], options) 354 print 'x_data=%s' % str(x_data) 355 print 'y_data=%s' % str(y_data) 356 pylab.plot(x_data, y_data) 357 358 pylab.show() 234 359 235 360 … … 258 383 wx.StaticBox(p, -1, 'CSV files', (3, Y_OFFSET), 259 384 size=(BOX_CSV_WIDTH, BOX_CSV_HEIGHT)) 260 261 ## wx.StaticText(p, -1, 'Minimum wave height',262 ## pos=(GEN_LABELXOFFSET, Y_OFFSET),263 ## size=(CTL_WIDTH, CTL_HEIGHT), style=wx.ALIGN_RIGHT)264 ## self.txtMinH = wx.TextCtrl(p, -1, size=(100, 20),265 ## pos=(GEN_TEXTXOFFSET, Y_OFFSET))266 385 Y_OFFSET += GEN_DELTAY 267 268 ## self.lstGenerate = wx.ListCtrl(p, -1, pos=(8, Y_OFFSET), 269 ## size=(TEXTLIST_WIDTH,TEXTLIST_HEIGHT), 270 ## style=wx.LC_SINGLE_SEL) 271 ## self.txtCSVFiles = wx.TextCtrl(p, -1, 272 ## "Here is a looooooooooooooong line of text set in the control.\n\n" 273 ## "The quick brown fox jumped over the lazy dog...", 274 ## pos=(8, Y_OFFSET), size=(TXT_CSVFILE_WIDTH,TXT_CSVFILE_HEIGHT), style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER) 275 self.txtCSVFiles = wx.ListBox(p, -1, pos=(8, Y_OFFSET), size=(TXT_CSVFILE_WIDTH,TXT_CSVFILE_HEIGHT)) 386 self.txtCSVFiles = wx.ListBox(p, -1, pos=(8, Y_OFFSET), 387 size=(TXT_CSVFILE_WIDTH, 388 TXT_CSVFILE_HEIGHT)) 389 self.CSVFiles = [] 276 390 Y_OFFSET += TXT_CSVFILE_HEIGHT + MARGIN 277 391 x = FORM_WIDTH/2 - BUTTON_WIDTH - DOUBLE_BUTTON_OFFSET … … 288 402 wx.StaticText(p, -1, 'X-Column', 289 403 pos=(COLLAB_X_OFFSET, Y_OFFSET+LAB_CTRL_OFFSET), style=wx.ALIGN_LEFT) 290 self.cbXColHdr = wx.ComboBox(p, -1, pos=(COLLAB_X_OFFSET+50, Y_OFFSET), 291 size=(80, -1), style=wx.CB_DROPDOWN) 404 self.cbXColHdr = wx.Choice(p, -1, pos=(COLLAB_X_OFFSET+50, Y_OFFSET), 405 size=(80, -1)) 406 self.XColHdr = [] 292 407 wx.StaticText(p, -1, 'Y-Column', 293 408 pos=(FORM_WIDTH/2+COLLAB_X_OFFSET, 294 409 Y_OFFSET+LAB_CTRL_OFFSET), style=wx.ALIGN_LEFT) 295 410 x = FORM_WIDTH/2 + COLLAB_X_OFFSET 296 self.cbYColHdr = wx.C omboBox(p, -1, pos=(x+50, Y_OFFSET),297 size=(80, -1), style=wx.CB_DROPDOWN)411 self.cbYColHdr = wx.Choice(p, -1, pos=(x+50, Y_OFFSET), size=(80, -1)) 412 self.YColHdr = [] 298 413 299 414 Y_OFFSET += BUTTON_HEIGHT + MARGIN 300 415 x = FORM_WIDTH/2 - BUTTON_WIDTH/2 301 self.btnPlot e= wx.Button(p, label="Plot", pos=(x, Y_OFFSET),416 self.btnPlot = wx.Button(p, label="Plot", pos=(x, Y_OFFSET), 302 417 size=(100, BUTTON_HEIGHT)) 303 418 304 305 306 307 308 ## wx.StaticText(p, -1, 'Maximum wave height', 309 ## pos=(GEN_LABELXOFFSET, Y_OFFSET), 310 ## size=(CTL_WIDTH, CTL_HEIGHT), style=wx.ALIGN_RIGHT) 311 ## self.chXColHdr = wx.Choice(p, -1, size=(CHBOX_WIDTH, CHBOX_HEIGHT), 312 ## pos=(10,Y_OFFSET)) 313 314 315 316 317 318 319 ## wx.StaticText(p, -1, 'Maximum wave height', 320 ## pos=(GEN_LABELXOFFSET, Y_OFFSET), 321 ## size=(CTL_WIDTH, CTL_HEIGHT), style=wx.ALIGN_RIGHT) 322 ## self.txtMaxH = wx.TextCtrl(p, -1, size=(100, 20), 323 ## pos=(GEN_TEXTXOFFSET, Y_OFFSET)) 324 ## Y_OFFSET += GEN_DELTAY 325 ## x = FORM_WIDTH/2 - BUTTON_WIDTH/2 326 ## self.btnGenerate = wx.Button(p, label="List", 327 ## pos=(x, Y_OFFSET), 328 ## size=(100, BUTTON_HEIGHT)) 329 ## Y_OFFSET += GEN_DELTAY + BUTTON_HEIGHT/2 330 ## 331 ## self.lstGenerate = wx.ListCtrl(p, -1, pos=(8, Y_OFFSET), 332 ## size=(TEXTLIST_WIDTH,TEXTLIST_HEIGHT), 333 ## style=wx.LC_REPORT|wx.LC_SINGLE_SEL) 334 ## 335 ## Y_OFFSET += TEXTLIST_HEIGHT + 20 336 ## self.Bind(wx.EVT_BUTTON, self.GenerateClick, self.btnGenerate) 337 ## self.lstGenerate.Bind(wx.EVT_LIST_ITEM_SELECTED, 338 ## self.OnItemSelected, self.lstGenerate) 339 ## 340 ## # start laying out get_multimux controls 341 ## wx.StaticBox(p, -1, 'multimux_grid', (3, Y_OFFSET), 342 ## size=(BOX_WIDTH, BOX2_HEIGHT)) 343 ## Y_OFFSET += 18 344 ## wx.StaticText(p, -1, 'Quake ID', pos=(GEN_LABELXOFFSET, Y_OFFSET), 345 ## size=(CTL_WIDTH, CTL_HEIGHT), style=wx.ALIGN_RIGHT) 346 ## self.txtQuakeID = wx.TextCtrl(p, -1, size=(100, 20), 347 ## pos=(GEN_TEXTXOFFSET, Y_OFFSET)) 348 ## Y_OFFSET += GEN_DELTAY 349 ## x = FORM_WIDTH/2 - BUTTON_WIDTH - DOUBLE_BUTTON_OFFSET 350 ## self.btnMultimux = wx.Button(p, label="Multimux", pos=(x, Y_OFFSET), 351 ## size=(100, BUTTON_HEIGHT)) 352 ## x = FORM_WIDTH/2 + DOUBLE_BUTTON_OFFSET 353 ## self.btnGrid = wx.Button(p, label="Grid", pos=(x, Y_OFFSET), 354 ## size=(100, BUTTON_HEIGHT)) 355 ## Y_OFFSET += GEN_DELTAY + BUTTON_HEIGHT/2 356 ## self.lstMultimux = wx.ListCtrl(p, -1, pos=(8, Y_OFFSET), 357 ## size=(TEXTLIST_WIDTH,TEXTLIST_HEIGHT), 358 ## style=wx.LC_REPORT|wx.LC_SINGLE_SEL) 359 ## Y_OFFSET += TEXTLIST_HEIGHT + 20 360 ## self.Bind(wx.EVT_BUTTON, self.MultimuxClick, self.btnMultimux) 361 ## self.Bind(wx.EVT_BUTTON, self.GridClick, self.btnGrid) 362 419 self.Bind(wx.EVT_BUTTON, self.AddCSVFile, self.btnAddCSVFile) 420 self.Bind(wx.EVT_BUTTON, self.DelCSVFile, self.btnDelCSVFile) 421 self.Bind(wx.EVT_BUTTON, self.PlotFiles, self.btnPlot) 363 422 self.Bind(wx.EVT_CLOSE, self.doStateSave) 423 self.Bind(wx.EVT_CLOSE, self.doStateSave) 424 self.cfg = Config(ConfigFilename) 425 self.common_headers = None 364 426 self.doStateRestore() 427 self.updateHdrChoices() 428 429 def AddCSVFile(self, event): 430 """Add a CSV file to the listbox""" 431 432 dlg = wx.FileDialog(self, message='Choose a CSV file', 433 defaultDir=self.file_dir, 434 defaultFile='', 435 wildcard=CSVWildcard, 436 style=wx.OPEN | wx.CHANGE_DIR) 437 438 if dlg.ShowModal() == wx.ID_OK: 439 # This returns a list of files that were selected. 440 path = dlg.GetPath() 441 self.file_dir = dlg.GetDirectory() 442 dlg.Destroy() 443 if path not in self.CSVFiles: 444 headers = self.getHeaders(path) 445 if headers: 446 common_headers = self.orHeaders(headers) 447 if not common_headers or len(common_headers) < 2: 448 self.error("Sorry, file '%s' doesn't have enough headers in common with current files" % path) 449 else: 450 self.headers.append(headers) 451 self.common_headers = common_headers 452 self.CSVFiles.append(path) 453 self.txtCSVFiles.Append(path) 454 else: 455 self.error("Sorry, file '%s' doesn't appear to be a CSV file" % path) 456 self.updateHdrChoices() 457 458 def DelCSVFile(self, event): 459 """Add a CSV file to the listbox""" 460 461 sel = self.txtCSVFiles.GetSelections() 462 if sel: 463 sel = sel[0] 464 self.txtCSVFiles.Delete(sel) 465 del self.CSVFiles[sel] 466 del self.headers[sel] 467 self.common_headers = self.GenCommonHeaders(self.headers) 468 self.updateHdrChoices() 365 469 366 470 def doStateSave(self, event): 367 """ 368 Save state here. 369 """ 370 ## self.cfg['OutputDirectory'] = self.txtOutputDir.GetValue() 371 ## self.cfg['Region'] = self.cbRegion.GetValue() 372 ## self.cfg['GaugeNumber'] = self.txtTGN.GetValue() 373 ## self.cfg['MinimumHeight'] = self.txtMinH.GetValue() 374 ## self.cfg['MaximumHeight'] = self.txtMaxH.GetValue() 375 ## self.cfg.save() 471 """Save state from 'self' variables.""" 472 473 self.cfg['CSVFiles'] = self.CSVFiles 474 self.cfg['XColHdr'] = self.XColHdr 475 self.cfg['XColHdrSelection'] = self.cbXColHdr.GetSelection() 476 self.cfg['YColHdr'] = self.YColHdr 477 self.cfg['YColHdrSelection'] = self.cbYColHdr.GetSelection() 478 self.cfg['file_dir'] = self.file_dir 479 480 self.cfg.save() 481 event.Skip(True) 482 483 def doStateRestore(self): 484 """Restore state to 'self' variables.""" 485 486 self.CSVFiles = self.cfg['CSVFiles'] 487 if self.CSVFiles is None: 488 self.CSVFiles = [] 489 self.XColHdr = self.cfg['XColHdr'] 490 if self.XColHdr is None: 491 self.XColHdr = [] 492 # -1 here means 'no selection' 493 self.XColHdrSelection = self.cfg['XColHdrSelection', -1] 494 self.YColHdr = self.cfg['YColHdr'] 495 if self.YColHdr is None: 496 self.YColHdr = [] 497 # -1 here means 'no selection' 498 self.YColHdrSelection = self.cfg['YColHdrSelection', -1] 499 self.file_dir = self.cfg['file_dir'] 500 if self.file_dir is None: 501 self.file_dir = os.getcwd() 502 503 # put data into controls 504 self.headers = [] 505 for (i, f) in enumerate(self.CSVFiles): 506 headers = self.getHeaders(f) 507 self.headers.append(headers) 508 self.txtCSVFiles.Insert(f, i) 509 510 def getHeaders(self, filename): 511 '''Get header list from a file''' 512 513 try: 514 fd = open(filename, 'r') 515 hdr = fd.readline() 516 fd.close() 517 except: 518 return None 519 520 hdr = hdr.split(',') 521 hdr = [x.strip() for x in hdr] 522 523 return hdr 376 524 377 event.Skip(True) 378 379 def doStateRestore(self): 380 """ 381 Restore state here - globals have been set and tested sane 382 """ 383 ## global Region, GaugeNumber, MinimumHeight, MaximumHeight 384 ## 385 ## cfg = Config(ConfigFilename) 386 ## output_dir = cfg['OutputDirectory'] 387 ## Region = cfg['Region'] 388 ## GaugeNumber = cfg['GaugeNumber'] 389 ## if GaugeNumber: 390 ## GaugeNumber = int(GaugeNumber) 391 ## MinimumHeight = cfg['MinimumHeight'] 392 ## if MinimumHeight: 393 ## MinimumHeight = float(MinimumHeight) 394 ## MaximumHeight = cfg['MaximumHeight'] 395 ## if MaximumHeight: 396 ## MaximumHeight = float(MaximumHeight) 397 ## 398 ## self.cfg = cfg 399 ## 400 ## region_name = None 401 ## for (name, dir) in Regions: 402 ## if name == Region: 403 ## region_name = name 404 ## if not region_name: 405 ## Region = '' 406 ## 407 ## if output_dir: 408 ## self.txtOutputDir.WriteText(output_dir) 409 ## self.cbRegion.SetStringSelection(Region) 410 ## if GaugeNumber: 411 ## self.txtTGN.WriteText(str(GaugeNumber)) 412 ## if MinimumHeight: 413 ## self.txtMinH.WriteText(str(MinimumHeight)) 414 ## if MaximumHeight: 415 ## self.txtMaxH.WriteText(str(MaximumHeight)) 525 def updateHdrChoices(self): 526 '''Update choice controls with header lists. 527 528 Keep initially visible strings visible afterwards, if posssible. 529 ''' 530 531 selected_x = self.cbXColHdr.GetStringSelection() 532 selected_y = self.cbYColHdr.GetStringSelection() 416 533 534 self.cbXColHdr.Clear() 535 self.cbYColHdr.Clear() 536 537 if self.common_headers: 538 self.cbXColHdr.Enable() 539 self.cbYColHdr.Enable() 540 self.btnPlot.Enable() 541 for h in self.common_headers: 542 self.cbXColHdr.Append(h) 543 self.cbYColHdr.Append(h) 544 if selected_x in self.common_headers: 545 index = self.common_headers.index(selected_x) 546 self.cbXColHdr.SetSelection(index) 547 if selected_y in self.common_headers: 548 index = self.common_headers.index(selected_y) 549 self.cbYColHdr.SetSelection(index) 550 else: 551 self.cbXColHdr.Disable() 552 self.cbYColHdr.Disable() 553 self.btnPlot.Disable() 554 555 def orHeaders(self, new_header): 556 '''Update X & Y column header choices''' 557 558 if self.common_headers: 559 result = [x for x in new_header if x in self.common_headers] 560 else: 561 result = new_header 562 return result 563 564 def GenCommonHeaders(self, headers): 565 '''Get new set of common headers''' 566 567 result = [] 568 for header in headers: 569 if result: 570 result = [x for x in result if x in header] 571 else: 572 result = header 573 return result 574 575 def PlotFiles(self, event): 576 selected_x = self.cbXColHdr.GetStringSelection() 577 selected_y = self.cbYColHdr.GetStringSelection() 578 579 if selected_x and selected_y: 580 options = {} 581 # set X and Y axis labels 582 options[X_LABEL] = selected_x 583 options[Y_LABEL] = selected_y 584 options[SIZE] = '800,600' 585 options[TITLE] = 'TITLE' 586 587 plot_files(self.CSVFiles, options) 588 417 589 def error(self, msg): 418 590 dlg = wx.MessageDialog(self, msg, 'Error',
Note: See TracChangeset
for help on using the changeset viewer.