1
2 """
3 Module for basic plotting inside the TableCanvas. Uses matplotlib.
4 Created August 2008
5 Copyright (C) Damien Farrell
6
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License
9 as published by the Free Software Foundation; either version 2
10 of the License, or (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 """
21
22 import sys, os
23 import copy
24 from Tkinter import *
25 from math import *
26 try:
27 import numpy
28 except:
29 print 'you need numpy to do statistics'
30
31 import matplotlib
32 matplotlib.use('TkAgg')
33 from matplotlib.font_manager import FontProperties
34 import pylab
35
36
38 """An interface to matplotlib for general plotting and stats, using tk backend"""
39
40 colors = ['#0049B4','#C90B11','#437C17','#AFC7C7','#E9AB17','#7F525D','#F6358A',
41 '#52D017','#FFFC17','#F76541','#F62217' ]
42 linestyles = ['-','--']
43 shapes = ['o','-','--',':','.' ,'p','^','<','s','+','x','D','1','4','h']
44 legend_positions = ['best', 'upper left','upper center','upper right',
45 'center left','center','center right'
46 'lower left','lower center','lower right']
47
48 graphtypes = ['XY', 'hist', 'bar', 'pie']
49 fonts = ['serif', 'sans-serif', 'cursive', 'fantasy', 'monospace']
50
51
53
54 self.shape = 'o'
55 self.grid = 0
56 self.xscale = 0
57 self.yscale = 0
58 self.showlegend = 0
59 self.legendloc = 'best'
60 self.legendlines = []
61 self.legendnames = []
62 self.graphtype = 'XY'
63 self.datacolors = self.colors
64 self.dpi = 300
65 self.linewidth = 1.5
66 self.font = 'sans-serif'
67 self.fontsize = 12
68 try:
69 self.setupPlotVars()
70 except:
71 print 'no tk running'
72 self.currdata = None
73
74 self.plottitle = ''
75 self.plotxlabel = ''
76 self.plotylabel = ''
77 return
78
79 - def plotXY(self, x, y, title='', xlabel=None, ylabel=None, shape=None,
80 clr=None, lw=1):
81 """Do x-y plot of 2 lists"""
82 if shape == None:
83 shape = self.shape
84 if clr == None:
85 clr = 'b'
86 if self.xscale == 1:
87 if self.yscale == 1:
88 line, = pylab.loglog(x, y, shape, color=clr, linewidth=lw)
89 else:
90 line, = pylab.semilogx(x, y, shape, color=clr, linewidth=lw)
91 elif self.yscale == 1:
92 line, = pylab.semilogy(x, y, shape, color=clr, linewidth=lw)
93 else:
94 line, = pylab.plot(x, y, shape, color=clr, linewidth=lw)
95 return line
96
98 """Do a pylab histogram of 1 or more lists"""
99 if len(data) == 1:
100 ydim=1
101 else:
102 ydim=2
103 dim=int(ceil(len(data)/2.0))
104 i=1
105
106 for r in data:
107 if len(r)==0:
108 continue
109 ax = pylab.subplot(ydim,dim,i)
110 print r
111 for j in range(len(r)):
112 r[j] = float(r[j])
113 pylab.hist(r,bins=bins)
114 i=i+1
115 return ax
116
118 """Do a pylab bar chart"""
119
120 for i in range(len(x)):
121 x[i] = float(x[i]);y[i] = float(y[i])
122 plotfig = pylab.bar(x, y, color=clr, alpha=0.6)
123
124 return plotfig
125
127 """Do a pylab bar chart"""
128 if len(data) == 1:
129 ydim=1
130 else:
131 ydim=2
132 dim=int(ceil(len(data)/2.0))
133 i=1
134 for r in data:
135 if len(r)==0:
136 continue
137 fig = pylab.subplot(ydim,dim,i)
138 print r
139 for j in range(len(r)):
140 r[j] = float(r[j])
141 pylab.pie(r)
142 i=i+1
143
144 return
145
147 """Set the current plot data, useful for re-plotting without re-calling
148 explicit functions from the parent"""
149
150 self.currdata = data
151 return
152
154 """Is there some plot data?"""
155 if self.currdata != None and len(self.currdata) > 0:
156 return True
157 else:
158 return False
159
161 """Set the series names, for use in legend"""
162 self.dataseriesvars=[]
163 for i in range(start,len(names)):
164 s=StringVar()
165 s.set(names[i])
166 self.dataseriesvars.append(s)
167
168 return
169
174
175 - def plotCurrent(self, data=None, graphtype='bar', show=True, guiopts=False,title=None):
176 """Re-do the plot with the current options and data"""
177 if guiopts == True:
178 self.applyOptions()
179 if title != None:
180 self.settitle(title)
181 self.clear()
182 currfig = pylab.figure(1)
183
184 if data == None:
185 try:
186 data = self.currdata
187 except:
188 print 'no data to plot'
189 return
190 else:
191 self.setData(data)
192
193 seriesnames = []
194 legendlines = []
195 for d in self.dataseriesvars:
196 seriesnames.append(d.get())
197
198 self.graphtype = graphtype
199
200 if self.graphtype == 'bar' or len(data) == 1:
201 i=0
202 pdata = copy.deepcopy(data)
203 if len(pdata)>1:
204 x = pdata[0]
205 pdata.remove(x)
206 for y in pdata:
207 if i >= len(self.colors):
208 i = 0
209 c = self.colors[i]
210 self.doBarChart(x, y, clr=c)
211 i+=1
212 else:
213 y = pdata[0]
214 x = range(len(y))
215 self.doBarChart(x, y, clr='b')
216
217 elif self.graphtype == 'XY':
218 pdata = copy.deepcopy(data)
219 x = pdata[0]
220 pdata.remove(x)
221 i=0
222 for y in pdata:
223 if i >= len(self.colors):
224 i = 0
225 c = self.colors[i]
226 line = self.plotXY(x, y, clr=c, lw=self.linewidth)
227 legendlines.append(line)
228 i+=1
229
230 elif self.graphtype == 'hist':
231 self.doHistogram(data)
232 elif self.graphtype == 'pie':
233 self.doPieChart(data)
234
235 pylab.title(self.plottitle)
236 pylab.xlabel(self.plotxlabel)
237 pylab.ylabel(self.plotylabel)
238
239 if self.showlegend == 1:
240 print legendlines
241 pylab.legend(legendlines,seriesnames,
242 loc=self.legendloc)
243 if self.grid == 1:
244 pylab.grid(True)
245
246 if show == True:
247 self.show()
248 return currfig
249
251 """clear plot"""
252 pylab.clf()
253 self.legendlines = []
254 self.legendnames = []
255 return
256
258 pylab.show()
259 return
260
262 import tkFileDialog, os
263 filename=tkFileDialog.asksaveasfilename(parent=self.plotprefswin,
264 defaultextension='.png',
265 filetypes=[("Png file","*.png"),
266 ("All files","*.*")])
267 if not filename:
268 return
269 fig = self.plotCurrent(show=False)
270 fig.savefig(filename, dpi=self.dpi)
271 return
272
274 self.plottitle = title
275
277 self.plotxlabel = label
278
280 self.plotylabel = label
281
282 - def setOptions(self, shape=None, grid=None, xscale=None, yscale=None,
283 showlegend=None, legendloc=None, linewidth=None,
284 graphtype=None, font=None, fontsize=None):
285 """Set the options before plotting"""
286 if shape != None:
287 self.shape = shape
288 if grid != None:
289 self.grid = grid
290 if xscale != None:
291 self.xscale = xscale
292 if yscale != None:
293 self.yscale = yscale
294 if showlegend != None:
295 self.showlegend = showlegend
296 if legendloc != None:
297 self.legendloc = legendloc
298 if linewidth != None:
299 self.linewidth = linewidth
300 if graphtype !=None:
301 self.graphtype = graphtype
302 if font != None:
303 self.font = font
304 if fontsize != None:
305 self.fontsize = fontsize
306 pylab.rc("font", family=self.font, size=self.fontsize)
307 return
308
310 """Plot Vars """
311 self.pltgrid = IntVar()
312 self.pltlegend = IntVar()
313 self.pltsymbol = StringVar()
314 self.pltsymbol.set(self.shape)
315 self.legendlocvar = StringVar()
316 self.legendlocvar.set(self.legendloc)
317 self.xscalevar = IntVar()
318 self.yscalevar = IntVar()
319 self.xscalevar.set(0)
320 self.yscalevar.set(0)
321 self.graphtypevar = StringVar()
322 self.graphtypevar.set(self.graphtype)
323 self.linewidthvar = DoubleVar()
324 self.linewidthvar.set(self.linewidth)
325 self.fontvar = StringVar()
326 self.fontvar.set(self.font)
327 self.fontsizevar = DoubleVar()
328 self.fontsizevar.set(self.fontsize)
329
330 self.plottitlevar = StringVar()
331 self.plottitlevar.set('')
332 self.plotxlabelvar = StringVar()
333 self.plotxlabelvar.set('')
334 self.plotylabelvar = StringVar()
335 self.plotylabelvar.set('')
336 self.dataseriesvars=[]
337 return
338
339
341 """Apply the gui option vars to the plotter options"""
342 self.setOptions(shape=self.pltsymbol.get(), grid=self.pltgrid.get(),
343 xscale=self.xscalevar.get(), yscale=self.yscalevar.get(),
344 showlegend = self.pltlegend.get(),
345 legendloc = self.legendlocvar.get(),
346 linewidth = self.linewidthvar.get(),
347 graphtype = self.graphtypevar.get(),
348 font = self.fontvar.get(),
349 fontsize = self.fontsizevar.get())
350 self.setTitle(self.plottitlevar.get())
351 self.setxlabel(self.plotxlabelvar.get())
352 self.setylabel(self.plotylabelvar.get())
353 return
354
356 """Plot options dialog"""
357
358 if data != None:
359 self.setData(data)
360 self.plotprefswin=Toplevel()
361 self.plotprefswin.geometry('+300+450')
362 self.plotprefswin.title('Plot Preferences')
363 row=0
364 frame1=LabelFrame(self.plotprefswin, text='General')
365 frame1.grid(row=row,column=0,sticky='news',padx=2,pady=2)
366 def close_prefsdialog():
367 self.plotprefswin.destroy()
368
369 def choosecolor(x):
370 """Choose color for data series"""
371 d=x[0]
372 c=x[1]
373 print 'passed', 'd',d, 'c',c
374 import tkColorChooser
375 colour,colour_string = tkColorChooser.askcolor(c,parent=self.plotprefswin)
376 if colour != None:
377 self.datacolors[d] = str(colour_string)
378 cbuttons[d].configure(bg=colour_string)
379
380 return
381
382 Checkbutton(frame1, text="Grid lines", variable=self.pltgrid,
383 onvalue=1, offvalue=0).grid(row=0,column=0, columnspan=2, sticky='news')
384 Checkbutton(frame1, text="Legend", variable=self.pltlegend,
385 onvalue=1, offvalue=0).grid(row=1,column=0, columnspan=2, sticky='news')
386
387 Label(frame1,text='Symbol:').grid(row=2,column=0,padx=2,pady=2)
388 symbolbutton = Menubutton(frame1,textvariable=self.pltsymbol,
389 relief=GROOVE, width=16, bg='lightblue')
390 symbol_menu = Menu(symbolbutton, tearoff=0)
391 symbolbutton['menu'] = symbol_menu
392 for text in self.shapes:
393 symbol_menu.add_radiobutton(label=text,
394 variable=self.pltsymbol,
395 value=text,
396 indicatoron=1)
397 symbolbutton.grid(row=2,column=1, sticky='news',padx=2,pady=2)
398 row=row+1
399
400 Label(frame1,text='Legend pos:').grid(row=3,column=0,padx=2,pady=2)
401 legendposbutton = Menubutton(frame1,textvariable=self.legendlocvar,
402 relief=GROOVE, width=16, bg='lightblue')
403 legendpos_menu = Menu(legendposbutton, tearoff=0)
404 legendposbutton['menu'] = legendpos_menu
405 i=0
406 for p in self.legend_positions:
407 legendpos_menu.add_radiobutton(label=p,
408 variable=self.legendlocvar,
409 value=p,
410 indicatoron=1)
411 i+=1
412 legendposbutton.grid(row=3,column=1, sticky='news',padx=2,pady=2)
413
414 Label(frame1,text='Font:').grid(row=4,column=0,padx=2,pady=2)
415 fontbutton = Menubutton(frame1,textvariable=self.fontvar,
416 relief=GROOVE, width=16, bg='lightblue')
417 font_menu = Menu(fontbutton, tearoff=0)
418 fontbutton['menu'] = font_menu
419 for f in self.fonts:
420 font_menu.add_radiobutton(label=f,
421 variable=self.fontvar,
422 value=f,
423 indicatoron=1)
424 fontbutton.grid(row=4,column=1, sticky='news',padx=2,pady=2)
425 row=row+1
426 Label(frame1,text='Font size:').grid(row=5,column=0,padx=2,pady=2)
427 Scale(frame1,from_=8,to=26,resolution=0.5,orient='horizontal',
428 relief=GROOVE,variable=self.fontsizevar).grid(row=5,column=1,padx=2,pady=2)
429
430 Label(frame1,text='linewidth:').grid(row=6,column=0,padx=2,pady=2)
431 Scale(frame1,from_=1,to=10,resolution=0.5,orient='horizontal',
432 relief=GROOVE,variable=self.linewidthvar).grid(row=6,column=1,padx=2,pady=2)
433 row=0
434 scalesframe = LabelFrame(self.plotprefswin, text="Axes Scales")
435 scales={0:'norm',1:'log'}
436 for i in range(0,2):
437 Radiobutton(scalesframe,text='x-'+scales[i],variable=self.xscalevar,
438 value=i).grid(row=0,column=i,pady=2)
439 Radiobutton(scalesframe,text='y-'+scales[i],variable=self.yscalevar,
440 value=i).grid(row=1,column=i,pady=2)
441 scalesframe.grid(row=row,column=1,sticky='news',padx=2,pady=2)
442
443 row=row+1
444 frame=LabelFrame(self.plotprefswin, text='Graph type')
445 frame.grid(row=row,column=0,columnspan=2,sticky='news',padx=2,pady=2)
446 for i in range(len(self.graphtypes)):
447 Radiobutton(frame,text=self.graphtypes[i],variable=self.graphtypevar,
448 value=self.graphtypes[i]).grid(row=0,column=i,pady=2)
449
450 row=row+1
451 labelsframe = LabelFrame(self.plotprefswin,text='Labels')
452 labelsframe.grid(row=row,column=0,columnspan=2,sticky='news',padx=2,pady=2)
453 Label(labelsframe,text='Title:').grid(row=0,column=0,padx=2,pady=2)
454 Entry(labelsframe,textvariable=self.plottitlevar,bg='white',relief=GROOVE).grid(row=0,column=1,padx=2,pady=2)
455 Label(labelsframe,text='X-axis label:').grid(row=1,column=0,padx=2,pady=2)
456 Entry(labelsframe,textvariable=self.plotxlabelvar,bg='white',relief=GROOVE).grid(row=1,column=1,padx=2,pady=2)
457 Label(labelsframe,text='Y-axis label:').grid(row=2,column=0,padx=2,pady=2)
458 Entry(labelsframe,textvariable=self.plotylabelvar,bg='white',relief=GROOVE).grid(row=2,column=1,padx=2,pady=2)
459 print self.currdata
460 if self.currdata != None:
461
462 row=row+1
463 seriesframe = LabelFrame(self.plotprefswin, text="Data Series Labels")
464 seriesframe.grid(row=row,column=0,columnspan=2,sticky='news',padx=2,pady=2)
465
466 if len(self.dataseriesvars) == 0:
467 self.setDataSeries(range(len(self.currdata)))
468 r=1
469 sr=1
470 cl=0
471 for s in self.dataseriesvars:
472 Label(seriesframe,text='Series '+str(r)).grid(row=r,column=cl,padx=2,pady=2)
473 Entry(seriesframe,textvariable=s,bg='white',
474 relief=GROOVE).grid(row=r,column=cl+1,padx=2,pady=2)
475 r+=1
476 if r > 8:
477 r=1
478 cl+=2
479 row=row+1
480 cbuttons = {}
481 frame = LabelFrame(self.plotprefswin, text="Dataset Colors")
482 r=1
483 cl=0
484 sr=1
485 ci=0
486 for d in range(len(self.dataseriesvars)):
487 if d >= len(self.datacolors):
488 self.datacolors.append(self.colors[ci])
489 ci+=1
490 c = self.datacolors[d]
491 action = lambda x =(d,c): choosecolor(x)
492 cbuttons[d]=Button(frame,text='Series '+str(sr),bg=c,command=action)
493 cbuttons[d].grid(row=r,column=cl,sticky='news',padx=2,pady=2)
494 r+=1
495 sr+=1
496 if r > 8:
497 r=1
498 cl+=1
499 frame.grid(row=row,column=0,columnspan=2,sticky='news',padx=2,pady=2)
500
501 row=row+1
502 frame=Frame(self.plotprefswin)
503 frame.grid(row=row,column=0,columnspan=2,sticky='news',padx=2,pady=2)
504 replotb = Button(frame, text="Replot",
505 command=lambda:self.plotCurrent(graphtype=self.graphtype,guiopts=True),
506 relief=GROOVE, bg='#99ccff')
507 replotb.pack(side=LEFT,fill=X,padx=2,pady=2)
508 b = Button(frame, text="Apply", command=self.applyOptions, relief=GROOVE, bg='#99ccff')
509 b.pack(side=LEFT,fill=X,padx=2,pady=2)
510 b = Button(frame, text="Save", command=self.saveCurrent, relief=GROOVE, bg='#99ccff')
511 b.pack(side=LEFT,fill=X,padx=2,pady=2)
512 c=Button(frame,text='Close', command=close_prefsdialog, relief=GROOVE, bg='#99ccff')
513 c.pack(side=LEFT,fill=X,padx=2,pady=2)
514 if self.currdata == None:
515 replotb.configure(state=DISABLED)
516
517 self.plotprefswin.focus_set()
518 self.plotprefswin.grab_set()
519
520 return
521