Added TODOs lists to the README.
[pyplotsuite.git] / plotfile2.py
blob0a148089724f3b20354ddd9fb8a821b80301d83d
1 #!/usr/bin/env python
2 # -*- coding: UTF8 -*-
4 # PlotFile2 -- A tool to make quick plots from data series.
5 # Version: 0.1-alpha (see README)
7 # Copyright (C) 2007 Antonio Ingargiola <tritemio@gmail.com>
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 import sys
15 rootdir = sys.path[0] + '/'
16 sourcedir = rootdir + 'plotfile/'
18 import os.path
19 import gtk
20 from matplotlib.lines import Line2D
21 from common.gtk_generic_windows import \
22 GenericMainPlotWindow, DialogWindowWithCancel,\
23 GenericOpenFileDialog, GenericSecondaryWindow
24 from common.filedata import xyLabelFromFile
25 from numpy import arange, array, min
27 # XML GUI Description file
28 gladefile = sourcedir + 'plotfile2.glade'
30 # Minimum default axis value in log scale when the original value was <= 0
31 LOGMIN = 1e-3
33 # How many centimeter is an inch
34 CM_PER_INCHES = 2.54
36 class DataSet:
37 """An object describing data series with a shared X axis."""
38 def __init__(self, x=None, y1=None, name=None):
39 # self.x is the X axis shared for all y series
40 self.x = x
41 self.len = len(x)
42 self.name = name
44 # self.y is a list of series of data
45 self.y = []
46 if y1 != None: self.add(y1)
48 # self.yline is a list of plotted lines from the current DataSet
49 self.yline = []
51 # Set the plot style, maker and linestyle attributes
52 # Marker and linestyle are separate variables so we can switch them off
53 # and then recover the style information when we turn them on
54 self.marker = '.'
55 self.linestyle = '-'
56 ## TO BE REMOVED
57 ##self.plot_kwargs = dict(
58 ## linestyle = self.linestyle,
59 ## linewidth = 1.5,
60 ## marker = self.marker,
61 ## markersize = 5,
62 ## alpha = 1
63 ## )
65 def add(self, yi):
66 if len(yi) != self.len:
67 raise IndexError, 'Y data length (%d) must be == len(x) (%d)'\
68 % (len(yi), self.len)
69 else:
70 self.y.append(yi)
72 class AxesProperty:
73 """Simple struct to store any properties that has different values for X
74 and Y axes"""
75 x = None
76 y = None
79 # Main window class
81 class PlotFileApp(GenericMainPlotWindow):
82 """
83 This class implements an interactive plot windows with various options.
84 """
85 def __init__(self, x=None, y=None, title='Title', debug=False):
86 GenericMainPlotWindow.__init__(self, 'PlotFileWindow', gladefile)
88 self.set_defaults()
89 self.title = title
90 self.debug = debug
91 self.negative = False
93 self.setup_gui_widgets()
95 # Data: self.data is a list of DataSet() instances
96 if x != None:
97 self.data.append( DataSet(x, y, 'Plot 1') )
98 if min(y) <= 0:
99 self.negative = True
101 # Try to load the file passed as parameter
102 if x == None and y == None:
103 try:
104 self.load(sys.argv[1])
105 except:
106 pass
107 self.plot()
109 def set_defaults(self):
110 # Clear data
111 self.data = []
113 # Plot Defaults
114 self.title = 'Title'
115 self.xlabel = 'X Axis'
116 self.ylabel = 'Y Axis'
117 self.xscale = 'linear'
118 self.yscale = 'linear'
119 self.showPoints = True
120 self.showLines = True
121 self.grid = True
123 # Reset ranges
124 self.xmin, self.xmax, self.ymin, self.ymax = None, None, None, None
125 self.axes_limits = AxesProperty()
126 self.axes_limits.x = dict(linear=None, log=None)
127 self.axes_limits.y = dict(linear=None, log=None)
129 def reset_scale_buttons(self):
130 self.setup = True
131 if self.xscale == 'log':
132 self.xscaleCheckB.set_active(True)
133 else:
134 self.xscaleCheckB.set_active(False)
135 if self.yscale == 'log':
136 self.yscaleCheckB.set_active(True)
137 else:
138 self.yscaleCheckB.set_active(False)
139 self.setup = False
141 def setup_gui_widgets(self):
142 # Create the text entry handler
143 self.xminEntry = self.widgetTree.get_widget('xmin_entry')
144 self.xmaxEntry = self.widgetTree.get_widget('xmax_entry')
145 self.yminEntry = self.widgetTree.get_widget('ymin_entry')
146 self.ymaxEntry = self.widgetTree.get_widget('ymax_entry')
148 # Initialize the check buttons to the correct value
149 self.pointsCheckB = self.widgetTree.get_widget('points_chk_butt')
150 self.linesCheckB = self.widgetTree.get_widget('lines_chk_butt')
151 self.xscaleCheckB = self.widgetTree.get_widget('xlog_chk_butt')
152 self.yscaleCheckB = self.widgetTree.get_widget('ylog_chk_butt')
153 self.reset_scale_buttons()
155 self.cooPickerCheckB = self.widgetTree.get_widget('coordinatePicker')
156 self.moreGridCheckB = self.widgetTree.get_widget('moreGrid')
158 self.window.show_all()
159 if self.debug: print 'end of setup_gui_widgets()'
161 def is_ydata_positive(self):
162 for d in self.data:
163 for yi in d.y:
164 if (yi <= 0).any(): return False
165 return True
167 def plot_data(self, n=0):
168 """Plot the n-th data from the self.data list of DataSet()."""
170 if self.debug: print 'plot_data method'
172 d = self.data[n]
173 d.yline = range(len(d.y))
174 for i, yi in enumerate(d.y):
175 d.yline[i], = self.axis.plot(d.x, yi, ls=d.linestyle,
176 marker=d.marker)
178 self.axis.set_title(self.title)
179 self.axis.set_xlabel(self.xlabel)
180 self.axis.set_ylabel(self.ylabel)
182 self.set_xlim()
183 self.set_ylim()
184 self.canvas.draw()
186 #self.update_axis_limits_entry()
188 def update_axis_limits_entry(self):
190 xmin, xmax = self.axis.get_xlim()
191 self.xminEntry.set_text(str(xmin))
192 self.xmaxEntry.set_text(str(xmax))
194 ymin, ymax = self.axis.get_ylim()
195 self.yminEntry.set_text(str(ymin))
196 self.ymaxEntry.set_text(str(ymax))
200 def plot(self):
201 if self.debug: print 'plot method'
203 if self.yscale == 'log' and not self.is_ydata_positive():
204 self.writeStatusBar('WARNING: Negative Y values in log scale.')
206 self.axis.clear()
208 self.axis.set_yscale('linear')
209 self.axis.set_xscale('linear')
211 for d in self.data:
212 d.yline = range(len(d.y))
213 for i, yi in enumerate(d.y):
214 d.yline[i], = self.axis.plot(d.x, yi, ls=d.linestyle,
215 marker=d.marker)
217 self.axis.set_title(self.title)
218 self.axis.set_xlabel(self.xlabel)
219 self.axis.set_ylabel(self.ylabel)
220 self.axis.grid(self.grid)
222 print 'x', self.xscale, 'y', self.yscale
223 self.axis.set_yscale(self.yscale)
224 self.axis.set_xscale(self.xscale)
226 if len(self.data) > 0:
227 self.set_xlim()
228 if len(self.data[0].y) > 0:
229 self.set_ylim()
231 self.canvas.draw()
233 def set_xlim(self):
234 if self.axes_limits.x[self.xscale] == None:
235 if self.debug: print 'autoscaling...'
236 self.axis.autoscale_view(scalex=True, scaley=False)
237 self.xmin, self.xmax = self.axis.get_xlim()
238 else:
239 if self.debug: print 'using old axis limit', self.axes_limits.x
240 self.xmin, self.xmax = self.axes_limits.x[self.xscale]
241 if self.xscale == 'log':
242 if self.xmin <= 0: self.xmin = LOGMIN
243 if self.xmax <= self.xmin: self.xmax = self.xmin+1
244 self.axis.set_xlim(xmin=self.xmin, xmax=self.xmax)
246 if self.debug: print 'xmin:', self.xmin, 'xmax:', self.xmax
247 self.xminEntry.set_text(str(self.xmin))
248 self.xmaxEntry.set_text(str(self.xmax))
250 def set_ylim(self):
251 if self.axes_limits.y[self.yscale] == None:
252 if self.debug: print 'autoscaling...'
253 self.axis.autoscale_view(scaley=True, scalex=False)
254 self.ymin, self.ymax = self.axis.get_ylim()
255 else:
256 if self.debug: print 'using old axis limit'
257 self.ymin, self.ymax = self.axes_limits.y[self.yscale]
258 if self.yscale == 'log':
259 if self.ymin <= 0: self.ymin = LOGMIN
260 if self.ymax <= self.ymin: self.ymax = self.ymin+1
261 self.axis.set_ylim(ymin=self.ymin, ymax=self.ymax)
263 if self.debug: print 'ymin:', self.ymin, 'ymax:', self.ymax
264 self.yminEntry.set_text(str(self.ymin))
265 self.ymaxEntry.set_text(str(self.ymax))
267 def load(self, filename):
268 try:
269 x, y , xlab, ylab = xyLabelFromFile(filename)
270 except IOError:
271 self.writeStatusBar("Can't open file '%s'" % filename)
272 return False
273 except:
274 self.writeStatusBar("File '%s': file format unknown." % filename)
275 return False
276 else:
277 filename = os.path.basename(filename)
278 self.data.append(DataSet(x,y, filename))
279 self.title = filename
280 if xlab is not None: self.xlabel = xlab
281 if ylab is not None: self.ylabel = ylab
282 self.axes_limits.x = dict(linear=None, log=None)
283 self.axes_limits.y = dict(linear=None, log=None)
284 self.writeStatusBar("File '%s' loaded." % filename)
285 if y.min() <= 0:
286 self.negative = True
287 return True
289 def warn(self, msg):
290 self.writeStatusBar('Warning: '+msg)
293 # The following are menu callback methods
295 def on_title_and_axes_labels_activate(self, widget, *args):
296 TitleAndAxesWindow(self)
299 # The following are GUI callback methods
301 def on_xscale_toggled(self, widget, *args):
302 if not self.setup:
303 self.axes_limits.x[self.xscale] = self.axis.get_xlim()
304 if self.xscaleCheckB.get_active(): self.xscale = 'log'
305 else: self.xscale = 'linear'
306 if self.debug: print "XScale:", self.xscale
307 self.axis.set_xscale(self.xscale)
308 self.set_xlim()
309 self.canvas.draw()
310 self.writeStatusBar('X Axis Scale: %s.' % self.xscale)
313 def on_yscale_toggled(self, widget, *args):
314 if not self.setup:
315 self.axes_limits.y[self.yscale] = self.axis.get_ylim()
316 if self.yscaleCheckB.get_active(): self.yscale = 'log'
317 else: self.yscale = 'linear'
318 if self.debug: print "YScale:", self.yscale
319 self.axis.set_yscale(self.yscale)
320 self.set_ylim()
321 self.canvas.draw()
323 if self.negative and self.yscale == 'log':
324 self.warn('Negative values not displayed in log-scale, lines '+\
325 'may appear discontinuos!')
326 else:
327 self.writeStatusBar('Y Axis Scale: %s.' % self.yscale)
330 def on_points_toggled(self, widget, *args):
331 self.showPoints = not self.showPoints
332 if self.debug: print "Show Points:", self.showPoints
333 for d in self.data:
334 if self.showPoints:
335 # Restore the previous marker style
336 d.yline[0].set_marker(d.marker)
337 else:
338 # Turn off the marker visualization
339 d.yline[0].set_marker('')
340 self.canvas.draw()
342 def on_lines_toggled(self, widget, *args):
343 self.showLines = not self.showLines
344 if self.debug: print "Show Lines:", self.showLines
345 for d in self.data:
346 if self.showLines:
347 # Restore the previous linestyle
348 d.yline[0].set_linestyle(d.linestyle)
349 else:
350 # Turn off the line visualization
351 d.yline[0].set_linestyle('')
352 self.canvas.draw()
354 def on_grid_toggled(self, widget, *args):
355 self.grid = not self.grid
356 if self.debug: print "Show Grid:", self.grid
357 self.axis.grid(self.grid)
358 self.canvas.draw()
360 def on_xmin_entry_activate(self, widget, *args):
361 if self.debug: print 'X Min Entry Activated'
362 s = self.xminEntry.get_text()
363 try:
364 val = float(s)
365 except ValueError:
366 self.statusBar.push(self.context, 'Wrong X axis min limit.')
367 self.xminEntry.set_text('')
368 else:
369 if self.xscale == 'log' and val <= 0:
370 val = LOGMIN
371 self.warn('Only values > 0 are allowed in log scale.')
372 self.xminEntry.set_text(str(val))
373 self.xmin = val
374 self.axis.set_xlim(xmin=val)
375 self.canvas.draw()
377 def on_xmax_entry_activate(self, widget, *args):
378 if self.debug: print 'X Max Entry Activated'
379 s = self.xmaxEntry.get_text()
380 try:
381 val = float(s)
382 except ValueError:
383 self.statusBar.push(self.context, 'Wrong X axis max limit.')
384 self.xmaxEntry.set_text('')
385 else:
386 if self.xscale == 'log' and val <= 0:
387 val = self.xmin*10.0
388 self.warn('Only values > 0 are allowed in log scale.')
389 self.xmaxEntry.set_text(str(val))
390 self.xmax = val
391 self.axis.set_xlim(xmax=val)
392 self.canvas.draw()
394 def on_ymin_entry_activate(self, widget, *args):
395 if self.debug: print 'Y Min Entry Activated'
396 s = self.yminEntry.get_text()
397 try:
398 val = float(s)
399 except ValueError:
400 self.statusBar.push(self.context, 'Wrong Y axis min limit.')
401 self.yminEntry.set_text('')
402 else:
403 if self.yscale == 'log' and val <= 0:
404 val = LOGMIN
405 self.warn('Only values > 0 are allowed in log scale.')
406 self.yminEntry.set_text(str(val))
407 self.ymin = val
408 self.axis.set_ylim(ymin=val)
409 self.canvas.draw()
411 def on_ymax_entry_activate(self, widget, *args):
412 if self.debug: print 'Y Max Entry Activated'
413 s = self.ymaxEntry.get_text()
414 try:
415 val = float(s)
416 except ValueError:
417 self.statusBar.push(self.context, 'Wrong Y axis max limit.')
418 self.ymaxEntry.set_text('')
419 else:
420 if self.yscale == 'log' and val <= 0:
421 val = self.ymin*10.0
422 self.warn('Only values > 0 are allowed in log scale.')
423 self.ymaxEntry.set_text(str(val))
424 self.ymax = val
425 self.axis.set_ylim(ymax=val)
426 self.canvas.draw()
428 def on_apply_button_clicked(self, widget, *args):
429 sxmin = self.xminEntry.get_text()
430 sxmax = self.xmaxEntry.get_text()
431 symin = self.yminEntry.get_text()
432 symax = self.ymaxEntry.get_text()
433 try:
434 xmin_val = float(sxmin)
435 xmax_val = float(sxmax)
436 ymin_val = float(symin)
437 ymax_val = float(symax)
438 except ValueError:
439 self.statusBar.push(self.context, 'Wrong axis limit')
440 else:
441 if self.xscale == 'log':
442 if xmin_val <= 0: xmin_val = LOGMIN
443 if xmax_val <= 0: xmax_val = xmin_val*10
444 if ymin_val <= 0: ymin_val = LOGMIN
445 if ymax_val <= 0: ymax_val = ymin_val*10
446 self.xmin, self.xmax, self.ymin, self.ymax = \
447 xmin_val, xmax_val, ymin_val, ymax_val
448 self.axis.set_xlim(xmin=xmin_val)
449 self.axis.set_xlim(xmax=xmax_val)
450 self.axis.set_ylim(ymin=ymin_val)
451 self.axis.set_ylim(ymax=ymax_val)
452 self.canvas.draw()
455 # The following are MENU callback methods
457 def on_addDataSet_activate(self, widget, *args):
458 OpenFileDialog(self)
459 def on_new_activate(self, widget, *args):
460 self.set_defaults()
461 self.reset_scale_buttons()
462 self.plot()
463 def on_dimensionsResolution_activate(self, widget, *args):
464 DimentionAndResolution(self)
465 def on_autoscale_activate(self, widget, *args):
466 self.axis.autoscale_view()
467 self.canvas.draw()
468 def on_coordinatePicker_activate(self, widget, *args):
469 if self.cooPickerCheckB.get_active():
470 self.cid = self.canvas.mpl_connect('button_press_event',
471 get_coordinates_cb)
472 self.writeStatusBar('Click on the plot to log coordinates on '+\
473 'the terminal.')
474 elif self.cid != None:
475 self.canvas.mpl_disconnect(self.cid)
476 self.writeStatusBar('Coordinate picker disabled.')
477 else:
478 print "Error: tried to disconnect an unexistent event."
479 def on_moreGrid_activate(self, widget, *args):
480 if self.moreGridCheckB.get_active():
481 self.axis.xaxis.grid(True, which='minor', ls='--', alpha=0.5)
482 self.axis.yaxis.grid(True, which='minor', ls='--', alpha=0.5)
483 self.axis.xaxis.grid(True, which='major', ls='-', alpha=0.5)
484 self.axis.yaxis.grid(True, which='major', ls='-', alpha=0.5)
485 else:
486 self.axis.xaxis.grid(False, which='minor')
487 self.axis.yaxis.grid(False, which='minor')
488 self.axis.xaxis.grid(True, which='major', ls=':')
489 self.axis.yaxis.grid(True, which='major', ls=':')
490 self.canvas.draw()
491 def on_info_activate(self, widget, *args):
492 AboutDialog()
493 def on_plot_properties_activate(self, widget, *args):
494 PlotPropertiesDialog(self)
495 def on_makeInterpolation_activate(self, widget, *args):
496 InterpolationDialog(self)
499 def get_coordinates_cb(event):
501 Callback for the coordinate picker tool. This function is not a class
502 method because I don't know how to connect an MPL event to a method instead
503 of a function.
505 if not event.inaxes: return
506 print "%3.4f\t%3.4f" % (event.xdata, event.ydata)
509 # Abstract dialogs classes
511 class DialogWithPlotList(DialogWindowWithCancel):
512 """Abstract class for dialogs with a list of current plots in them."""
514 def __init__(self, dialogName, gladefile, callerApp):
515 DialogWindowWithCancel.__init__(self, dialogName, gladefile, callerApp)
517 self.treeView = self.widgetTree.get_widget('treeview')
518 self.create_plotlist()
520 def create_plotlist(self):
521 # Add a column to the treeView
522 self.addListColumn('Plot List', 0)
524 # Create the listStore Model to use with the treeView
525 self.plotList = gtk.ListStore(str)
527 # Populate the list
528 self.insertPlotList()
530 # Attatch the model to the treeView
531 self.treeView.set_model(self.plotList)
533 def addListColumn(self, title, columnId):
534 """This function adds a column to the list view.
535 First it create the gtk.TreeViewColumn and then set
536 some needed properties"""
538 column = gtk.TreeViewColumn(title, gtk.CellRendererText(), text=0)
539 column.set_resizable(True)
540 column.set_sort_column_id(columnId)
541 self.treeView.append_column(column)
543 def insertPlotList(self):
544 for data in self.callerApp.data:
545 self.plotList.append([data.name])
547 def on_applyButton_clicked(self, widget, *args):
548 self.window.destroy()
550 def on_cancelButton_clicked(self, widget, *args):
551 self.window.destroy()
553 def on_okButton_clicked(self, widget, *args):
554 self.window.destroy()
557 # Dialogs classes
559 class InterpolationDialog(DialogWithPlotList):
560 def __init__(self, callerApp):
561 DialogWithPlotList.__init__(self, 'InterpolationDialog',
562 gladefile, callerApp)
563 # To be implemented ...
566 class PlotPropertiesDialog(DialogWithPlotList):
567 def __init__(self, callerApp):
568 DialogWithPlotList.__init__(self, 'PlotPropertiesDialog',
569 gladefile, callerApp)
571 self.load_widgets()
573 # Retrive the data list
574 self.data = self.callerApp.data
576 # Save all the lines properties in case of Cancell-button
577 self.orig_lines = range(len(self.data))
578 for i,d in enumerate(self.data):
579 self.orig_lines[i] = [Line2D([0], [0]), d.linestyle, d.marker]
580 self.orig_lines[i][0].update_from(d.yline[0])
582 self.selected_data = None
583 self.selected_line = None
584 self.allowReplot = False
586 def load_widgets(self):
587 self.lineWidthSpinButt = \
588 self.widgetTree.get_widget('lineWidthSpinButton')
589 self.lineStyleComboB = \
590 self.widgetTree.get_widget('lineStyleComboBox')
591 self.lineColorComboB = \
592 self.widgetTree.get_widget('lineColorComboBox')
593 self.markerTypeComboB = \
594 self.widgetTree.get_widget('markerTypeComboBox')
595 self.markerSizeSpinButt = \
596 self.widgetTree.get_widget('markerSizeSpinButton')
597 self.markerEdgeColorComboB = \
598 self.widgetTree.get_widget('markerEdgeColComboBox')
599 self.markerFaceColorComboB = \
600 self.widgetTree.get_widget('markerFaceColComboBox')
601 self.markerEdgeWidthSpinButt = \
602 self.widgetTree.get_widget('markerEdgeWidthSpinButton')
604 def update_properties(self):
605 # Set the Spin Buttons with the values for the selected data
606 self.lineWidthSpinButt.set_value(self.selected_line.get_linewidth())
607 self.markerSizeSpinButt.set_value(self.selected_line.get_markersize())
608 mew = self.selected_line.get_markeredgewidth()
609 self.markerEdgeWidthSpinButt.set_value(mew)
611 # Set the Combo Boxes with the values for the selected data
612 self.set_current_linestyle(self.selected_data)
613 self.set_current_marker(self.selected_data)
614 self.set_current_lineColor(self.selected_line)
615 self.set_current_markerEdgeColor(self.selected_line)
616 self.set_current_markerFaceColor(self.selected_line)
618 def set_current_lineColor(self, data_line):
619 lc = data_line.get_color()
620 colors = 'bgrcmyk'
621 if lc in colors:
622 ic = colors.index(lc)
623 self.lineColorComboB.set_active(ic)
624 else:
625 print "WARNING: Unsupported color '%s'." % lc
627 def set_current_markerEdgeColor(self, data_line):
628 mec = data_line.get_mec()
629 colors = 'bgrcmyk'
630 if mec in colors:
631 ic = colors.index(mec)
632 self.markerEdgeColorComboB.set_active(ic)
633 else:
634 print "WARNING: Unsupported color '%s'." % mec
636 def set_current_markerFaceColor(self, data_line):
637 mfc = data_line.get_mfc()
638 colors = 'bgrcmyk'
639 if mfc in colors:
640 ic = colors.index(mfc)
641 self.markerFaceColorComboB.set_active(ic)
642 else:
643 print "WARNING: Unsupported color '%s'." % mfc
645 def set_current_linestyle(self, data):
646 ls = data.linestyle
647 line_styles = ['-', '--', ':', '-.']
649 if ls in line_styles:
650 il = line_styles.index(ls)
651 self.lineStyleComboB.set_active(il)
652 else:
653 print "WARNING: Linestyle '%s' not supported." % ls
655 def set_current_marker(self, data):
656 m = data.marker
657 markers = 'os^v<>.+xdDhHp'
658 if m in markers:
659 im = markers.index(m)
660 self.markerTypeComboB.set_active(im)
661 else:
662 print "WARNING: Marker type '%s' not supported." % m
666 # GUI Callbacks
668 def on_treeview_cursor_changed(self, *args):
669 # Retrive the current plot/line reference
670 plot_index = self.treeView.get_cursor()[0][0]
671 self.selected_line = self.data[plot_index].yline[0]
672 self.selected_data = self.callerApp.data[plot_index]
674 # Update the plot with the current line/plot properties
675 self.allowReplot = False
676 self.update_properties()
677 self.allowReplot = True
679 # Why self.allowReplot ?
680 # Changing combo box or spin button state will emit the corresponding
681 # signal caught by their GUI callback. The callback updates the plot.
682 # We use the self.allowReplot flag to inibit the CB from updating the
683 # plot while we are setting up the properties for the current line.
685 def on_lineWidth_value_changed(self, *args):
686 if self.allowReplot and self.selected_line is not None:
687 prop = 'linewidth'
688 lw = self.lineWidthSpinButt.get_value()
690 self.selected_line.set_linewidth(lw)
691 self.callerApp.canvas.draw()
693 def on_lineStyle_changed(self, *args):
694 if self.allowReplot and self.selected_line is not None:
695 # Switch ON the "show lines" check button on the main windows if
696 # we change line style to prevent inconsistent state.
697 if not self.callerApp.linesCheckB.get_active():
698 self.callerApp.linesCheckB.set_active(True)
700 prop = 'linestyle'
701 ls = self.lineStyleComboB.get_active_text()
703 line_styles = {'Continuous': '-', 'Dashed': '--',
704 'Dotted': ':', 'Dot-Line': '-.'}
706 if ls in line_styles.keys():
707 self.selected_line.set_linestyle(line_styles[ls])
708 self.callerApp.canvas.draw()
709 self.selected_data.linestyle = line_styles[ls]
710 else:
711 print "WARNING: Unsupported line style '%s'." % ls
713 def on_lineColor_changed(self, *args):
714 if self.allowReplot and self.selected_line is not None:
715 prop = 'color'
716 text = self.lineColorComboB.get_active_text()
717 color = text[text.find('(') + 1]
719 self.selected_line.set_color(color)
720 self.callerApp.canvas.draw()
722 def on_markerType_changed(self, *args):
723 if self.allowReplot and self.selected_line is not None:
724 # Switch ON the "show points" check button on the main windows if
725 # we change marker properties to prevent inconsistent state.
726 if not self.callerApp.pointsCheckB.get_active():
727 self.callerApp.pointsCheckB.set_active(True)
729 prop = 'marker'
730 text = self.markerTypeComboB.get_active_text()
731 m = text[text.find('(') + 1]
733 self.selected_line.set_marker(m)
734 self.callerApp.canvas.draw()
735 self.selected_data.marker = m
737 def on_markerSize_value_changed(self, *args):
738 if self.allowReplot and self.selected_line is not None:
739 prop = 'markersize'
740 ms = self.markerSizeSpinButt.get_value()
742 self.selected_line.set_markersize(ms)
743 self.callerApp.canvas.draw()
745 def on_markerEdgeWidth_value_changed(self, *args):
746 if self.allowReplot and self.selected_line is not None:
747 prop = 'markeredgewidth'
748 mew = self.markerEdgeWidthSpinButt.get_value()
750 self.selected_line.set_markeredgewidth(mew)
751 self.callerApp.canvas.draw()
753 def on_markerEdgeColor_changed(self, *args):
754 if self.allowReplot and self.selected_data is not None:
755 prop = 'markeredgecolor'
756 text = self.markerEdgeColorComboB.get_active_text()
757 color = text[text.find('(') + 1]
759 self.selected_line.set_markeredgecolor(color)
760 self.callerApp.canvas.draw()
762 def on_markerFaceColor_changed(self, *args):
763 if self.allowReplot and self.selected_data is not None:
764 prop = 'markerfacecolor'
765 text = self.markerFaceColorComboB.get_active_text()
766 color = text[text.find('(') + 1]
768 self.selected_line.set_markerfacecolor(color)
769 self.callerApp.canvas.draw()
771 def on_cancelButton_clicked(self, widget, *args):
772 # Restore the old style
773 for orig_line, d in zip(self.orig_lines, self.data):
774 d.yline[0].update_from(orig_line[0])
775 d.linestyle = orig_line[1]
776 d.marker = orig_line[2]
778 self.callerApp.canvas.draw()
779 self.window.destroy()
781 def on_okButton_clicked(self, widget, *args):
782 self.window.destroy()
785 class TitleAndAxesWindow(DialogWindowWithCancel):
787 Dialog for setting Title and axes labels.
789 def __init__(self, callerApp):
790 DialogWindowWithCancel.__init__(self, 'TitleAndAxesWindow', gladefile,
791 callerApp)
793 self.titleEntry = self.widgetTree.get_widget('titleEntry')
794 self.xlabelEntry = self.widgetTree.get_widget('xlabelEntry')
795 self.ylabelEntry = self.widgetTree.get_widget('ylabelEntry')
797 # Update the entries with the current values
798 def sync(field, entry):
799 if field != None: entry.set_text(field)
801 sync(self.callerApp.title, self.titleEntry)
802 sync(self.callerApp.xlabel, self.xlabelEntry)
803 sync(self.callerApp.ylabel, self.ylabelEntry)
805 def on_okButton_clicked(self, widget, *args):
806 print "OK Clicked"
807 self.callerApp.axis.set_title(self.titleEntry.get_text())
808 self.callerApp.axis.set_xlabel(self.xlabelEntry.get_text())
809 self.callerApp.axis.set_ylabel(self.ylabelEntry.get_text())
810 self.callerApp.canvas.draw()
812 self.callerApp.title = self.titleEntry.get_text()
813 self.callerApp.xlabel = self.ylabelEntry.get_text()
814 self.callerApp.ylabel = self.xlabelEntry.get_text()
816 self.window.destroy()
818 class DimentionAndResolution(DialogWindowWithCancel):
820 Dialog for setting Figure dimentions and resolution.
822 def __init__(self, callerApp):
823 DialogWindowWithCancel.__init__(self, 'DimentionsDialog', gladefile,
824 callerApp)
825 self.xdimSpinButton = self.widgetTree.get_widget('xdimSpinButton')
826 self.ydimSpinButton = self.widgetTree.get_widget('ydimSpinButton')
827 self.resolutionSpinButton = self.widgetTree.get_widget(
828 'resolutionSpinButton')
829 self.inchesRadioB = self.widgetTree.get_widget('inRadioButton')
831 self.xdim_o, self.ydim_o = self.callerApp.figure.get_size_inches()
832 self.dpi_o = self.callerApp.figure.get_dpi()
834 self.set_initial_values()
836 def set_values(self, xdim, ydim, dpi):
837 if not self.inchesRadioB.get_active():
838 xdim *= CM_PER_INCHES
839 ydim *= CM_PER_INCHES
840 self.xdimSpinButton.set_value(xdim)
841 self.ydimSpinButton.set_value(ydim)
842 self.resolutionSpinButton.set_value(dpi)
844 def set_initial_values(self):
845 self.set_values(self.xdim_o, self.ydim_o, self.dpi_o)
847 def set_default_values(self):
848 xdim, ydim = rcParams['figure.figsize']
849 dpi = rcParams['figure.dpi']
850 self.set_values(xdim, ydim, dpi)
852 def get_values(self):
853 self.xdim = self.xdimSpinButton.get_value()
854 self.ydim = self.ydimSpinButton.get_value()
855 self.resolution = self.resolutionSpinButton.get_value()
856 if not self.inchesRadioB.get_active():
857 self.xdim /= CM_PER_INCHES
858 self.ydim /= CM_PER_INCHES
860 def set_figsize(self):
861 self.callerApp.figure.set_size_inches(self.xdim, self.ydim)
862 self.callerApp.figure.set_dpi(self.resolution)
864 def on_unity_toggled(self, widget, *args):
865 xdim = self.xdimSpinButton.get_value()
866 ydim = self.ydimSpinButton.get_value()
867 if self.inchesRadioB.get_active():
868 xdim /= CM_PER_INCHES
869 ydim /= CM_PER_INCHES
870 else:
871 xdim *= CM_PER_INCHES
872 ydim *= CM_PER_INCHES
873 self.xdimSpinButton.set_value(xdim)
874 self.ydimSpinButton.set_value(ydim)
876 def on_okButton_clicked(self, widget, *args):
877 print "OK"
878 self.get_values()
879 self.set_figsize()
880 self.callerApp.canvas.draw()
881 self.window.destroy()
883 def on_restoreButton_clicked(self, widget, *args):
884 self.set_default_values()
887 class OpenFileDialog(GenericOpenFileDialog):
889 This class implements the "Open File" dialog.
891 def __init__(self, callerApp):
892 GenericOpenFileDialog.__init__(self, 'OpenFileDialog', gladefile,
893 callerApp)
895 def openSelectedFile(self):
896 loaded = self.callerApp.load( self.filename )
897 if loaded:
898 self.callerApp.plot_data(-1)
899 self.window.destroy()
902 class AboutDialog(GenericSecondaryWindow):
904 Object for the "About Dialog".
906 def __init__(self):
907 GenericSecondaryWindow.__init__(self, 'AboutDialog', gladefile)
908 self.window.set_version(read_version())
911 # Functions
913 def read_version():
914 """ Extract version from README file. """
915 file = open(rootdir+'README')
916 ver = None
917 for line in file:
918 if line.strip().startswith('*Latest Version*'):
919 s = line.split()
920 ver = s[2]
921 break
922 file.close()
923 return ver
925 def test():
926 x = arange(100)
927 y = sin(x/10.0)
928 p = PlotFileApp(x, y, title='Random Sequence', debug=True)
929 p.start()
931 def main():
932 p = PlotFileApp(debug=True)
933 # Try to open a sample file
934 if len(sys.argv) < 2 and os.path.isfile(sourcedir+'samples/data.txt'):
935 p.load(sourcedir+'samples/data.txt')
936 p.plot()
937 p.start()
939 if __name__ == '__main__': main()