source: trunk/python/asaplot.py@ 119

Last change on this file since 119 was 119, checked in by cal103, 21 years ago

Added a histogram plotting function hist(); modified subplot() so that an
increment may be specified.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 12.6 KB
Line 
1"""
2ASAP plotting class based on matplotlib.
3"""
4
5import sys
6from re import match
7import Tkinter as Tk
8
9print "Importing matplotlib with TkAgg backend."
10import matplotlib
11matplotlib.use("TkAgg")
12
13from matplotlib.backends import new_figure_manager, show
14from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, \
15 FigureManagerTkAgg
16from matplotlib.figure import Figure, Text
17from matplotlib.numerix import sqrt
18
19# Force use of the newfangled toolbar.
20matplotlib.rcParams['toolbar'] = 'toolbar2'
21
22# Colour dictionary.
23colours = {}
24
25class ASAPlot:
26 """
27 ASAP plotting class based on matplotlib.
28 """
29
30 def __init__(self, rows=1, cols=0, title='', size=(8,6), buffering=False):
31 """
32 Create a new instance of the ASAPlot plotting class.
33
34 If rows < 1 then a separate call to set_panels() is required to define
35 the panel layout; refer to the doctext for set_panels().
36 """
37 self.window = Tk.Tk()
38
39 self.frame1 = Tk.Frame(self.window, relief=Tk.RIDGE, borderwidth=4)
40 self.frame1.pack(fill=Tk.BOTH)
41
42 self.figure = Figure(figsize=size, facecolor='#ddddee')
43 self.canvas = FigureCanvasTkAgg(self.figure, master=self.frame1)
44 self.canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
45
46 # Simply instantiating this is enough to get a working toolbar.
47 self.figmgr = FigureManagerTkAgg(self.canvas, 1, self.window)
48 self.window.wm_title('ASAPlot graphics window')
49
50 self.set_title(title)
51 self.subplots = []
52 if rows > 0:
53 self.set_panels(rows, cols)
54
55
56 # Set matplotlib default colour sequence.
57 self.colours = [1, 'b', 'g', 'r', 'c', 'm', 'y', 'k', 'w']
58 self.attributes = {}
59 self.loc = 1
60
61 matplotlib.interactive = True
62 self.buffering = buffering
63
64 self.canvas.show()
65
66
67 def clear(self):
68 """
69 Delete all lines from the plot. Line numbering will restart from 1.
70 """
71
72 for i in range(1,len(self.lines)+1):
73 self.delete(i)
74
75 self.axes.clear()
76 self.colours[0] = 1
77 self.lines = []
78
79
80 def delete(self, numbers=None):
81 """
82 Delete the 0-relative line number, default is to delete the last.
83 The remaining lines are NOT renumbered.
84 """
85
86 if numbers is None: numbers = [len(self.lines)-1]
87
88 if not hasattr(numbers, '__iter__'):
89 numbers = [numbers]
90
91 for number in numbers:
92 if 0 <= number < len(self.lines):
93 if self.lines[number] is not None:
94 for line in self.lines[number]:
95 line.set_linestyle('None')
96 self.lines[number] = None
97
98 self.show()
99
100
101 def get_line(self):
102 """
103 Get the current default line attributes.
104 """
105 return self.attributes
106
107
108 def hist(self, x=None, y=None, fmt=None):
109 """
110 Plot a histogram. N.B. the x values refer to the start of the
111 histogram bin.
112
113 fmt is the line style as in plot().
114 """
115
116 if x is None:
117 if y is None: return
118 x = range(0,len(y))
119
120 if len(x) != len(y):
121 return
122
123 l2 = 2*len(x)
124 x2 = range(0,l2)
125 y2 = range(0,l2)
126
127 for i in range(0,l2):
128 x2[i] = x[i/2]
129
130 y2[0] = 0
131 for i in range(1,l2):
132 y2[i] = y[(i-1)/2]
133
134 self.plot(x2, y2, fmt)
135
136
137 def hold(self, hold=True):
138 """
139 Buffer graphics until subsequently released.
140 """
141 self.buffering = hold
142
143
144 def legend(self, loc=1):
145 """
146 Add a legend to the plot.
147
148 Any other value for loc else disables the legend:
149 1: upper right
150 2: upper left
151 3: lower left
152 4: lower right
153 5: right
154 6: center left
155 7: center right
156 8: lower center
157 9: upper center
158 10: center
159
160 """
161 if 1 > loc > 10: loc = 0
162 self.loc = loc
163 self.show()
164
165
166 def map(self):
167 """
168 Reveal the ASAPlot graphics window and bring it to the top of the
169 window stack.
170 """
171 self.window.wm_deiconify()
172 self.window.lift()
173
174
175 def palette(self, pen=None, colours=None):
176 """
177 Redefine the colour sequence.
178
179 pen is the pen number to use for the next plot; this will be auto-
180 incremented.
181
182 colours is the list of pen colours. Colour may be specified via
183 the single letter values understood by matplotlib:
184
185 b: blue
186 g: green
187 r: red
188 c: cyan
189 m: magenta
190 y: yellow
191 k: black
192 w: white
193
194 or via the full name as listed in the colour dictionary which is
195 loaded by default by load_colours() from rgb.txt and listed by
196 list_colours().
197 """
198
199 if pen is None and colours is None:
200 self.colours = []
201 return
202
203 if pen is None:
204 if not len(self.colours):
205 self.colours = [1]
206 else:
207 self.colours[0] = pen
208
209 if colours is None:
210 return
211
212 cols = []
213 for col in colours:
214 cols.append(get_colour(col))
215
216 self.colours[1:] = cols
217
218 if 0 > self.colours[0] > len(self.colours):
219 self.colours[0] = 1
220
221
222 def plot(self, x=None, y=None, mask=None, fmt=None, add=None):
223 """
224 Plot the next line in the current frame using the current line
225 attributes. The ASAPlot graphics window will be mapped and raised.
226
227 The argument list works a bit like the matlab plot() function.
228 """
229
230 if x is None:
231 if y is None: return
232 x = range(len(y))
233
234 elif y is None:
235 y = x
236 x = range(len(y))
237
238 if mask is None:
239 if fmt is None:
240 line = self.axes.plot(x, y)
241 else:
242 line = self.axes.plot(x, y, fmt)
243 else:
244 segments = []
245
246 mask = list(mask)
247 i = 0
248 while mask[i:].count(1):
249 i += mask[i:].index(1)
250 if mask[i:].count(0):
251 j = i + mask[i:].index(0)
252 else:
253 j = len(mask)
254
255 segments.append(x[i:j])
256 segments.append(y[i:j])
257
258 i = j
259
260 line = self.axes.plot(*segments)
261
262 # Add to an existing line?
263 if add is None or len(self.lines) < add < 0:
264 # Don't add.
265 self.lines.append(line)
266 i = len(self.lines) - 1
267 else:
268 if add == 0: add = len(self.lines)
269 i = add - 1
270 self.lines[i].extend(line)
271
272 # Set/reset attributes for the line.
273 gotcolour = False
274 for k, v in self.attributes.iteritems():
275 if k == 'color': gotcolour = True
276 for segment in self.lines[i]:
277 getattr(segment, "set_%s"%k)(v)
278
279 if not gotcolour and len(self.colours):
280 for segment in self.lines[i]:
281 getattr(segment, "set_color")(self.colours[self.colours[0]])
282
283 self.colours[0] += 1
284 if self.colours[0] >= len(self.colours):
285 self.colours[0] = 1
286
287 self.show()
288
289
290 def quit(self):
291 """
292 Destroy the ASAPlot graphics window.
293 """
294 self.window.destroy()
295
296
297 def release(self):
298 """
299 Release buffered graphics.
300 """
301 self.buffering = False
302 self.show()
303
304
305 def set_axes(self, what=None, *args, **kwargs):
306 """
307 Set attributes for the axes by calling the relevant Axes.set_*()
308 method. Colour translation is done as described in the doctext
309 for palette().
310 """
311
312 if what is None: return
313 if what[-6:] == 'colour': what = what[:-6] + 'color'
314
315 newargs = {}
316 for k, v in kwargs.iteritems():
317 k = k.lower()
318 if k == 'colour': k = 'color'
319
320 if k == 'color':
321 v = get_colour(v)
322
323 newargs[k] = v
324
325 getattr(self.axes, "set_%s"%what)(*args, **newargs)
326 self.show()
327
328
329 def set_figure(self, what=None, *args, **kwargs):
330 """
331 Set attributes for the figure by calling the relevant Figure.set_*()
332 method. Colour translation is done as described in the doctext
333 for palette().
334 """
335
336 if what is None: return
337 if what[-6:] == 'colour': what = what[:-6] + 'color'
338 if what[-5:] == 'color' and len(args):
339 args = (get_colour(args[0]),)
340
341 newargs = {}
342 for k, v in kwargs.iteritems():
343 k = k.lower()
344 if k == 'colour': k = 'color'
345
346 if k == 'color':
347 v = get_colour(v)
348
349 newargs[k] = v
350
351 getattr(self.figure, "set_%s"%what)(*args, **newargs)
352 self.show()
353
354
355 def set_line(self, number=None, **kwargs):
356 """
357 Set attributes for the specified line, or else the next line(s)
358 to be plotted.
359
360 number is the 0-relative number of a line that has already been
361 plotted. If no such line exists, attributes are recorded and used
362 for the next line(s) to be plotted.
363
364 Keyword arguments specify Line2D attributes, e.g. color='r'. Do
365
366 import matplotlib
367 help(matplotlib.lines)
368
369 The set_* methods of class Line2D define the attribute names and
370 values. For non-US usage, "colour" is recognized as synonymous with
371 "color".
372
373 Set the value to None to delete an attribute.
374
375 Colour translation is done as described in the doctext for palette().
376 """
377
378 redraw = False
379 for k, v in kwargs.iteritems():
380 k = k.lower()
381 if k == 'colour': k = 'color'
382
383 if k == 'color':
384 v = get_colour(v)
385
386 if 0 <= number < len(self.lines):
387 if self.lines[number] is not None:
388 for line in self.lines[number]:
389 getattr(line, "set_%s"%k)(v)
390 redraw = True
391 else:
392 if v is None:
393 del self.attributes[k]
394 else:
395 self.attributes[k] = v
396
397 if redraw: self.show()
398
399
400 def set_panels(self, rows=1, cols=0, n=-1):
401 """
402 Set the panel layout.
403
404 rows and cols, if cols != 0, specify the number of rows and columns in
405 a regular layout. (Indexing of these panels in matplotlib is row-
406 major, i.e. column varies fastest.)
407
408 cols == 0 is interpreted as a retangular layout that accomodates
409 'rows' panels, e.g. rows == 6, cols == 0 is equivalent to
410 rows == 2, cols == 3.
411
412 0 <= n < rows*cols is interpreted as the 0-relative panel number in
413 the configuration specified by rows and cols to be added to the
414 current figure as its next 0-relative panel number (i). This allows
415 non-regular panel layouts to be constructed via multiple calls. Any
416 other value of n clears the plot and produces a rectangular array of
417 empty panels.
418 """
419 if n < 0 and len(self.subplots):
420 self.figure.clear()
421 self.set_title()
422
423 if rows < 1:
424 rows = 1
425
426 if cols == 0:
427 i = int(sqrt(rows))
428 if i*i < rows: i += 1
429 cols = i
430
431 if i*(i-1) >= rows: i -= 1
432 rows = i
433
434 if 0 <= n < rows*cols:
435 i = len(self.subplots)
436 self.subplots.append({})
437 self.subplots[i]['axes'] = self.figure.add_subplot(rows,
438 cols, n+1)
439 self.subplots[i]['lines'] = []
440
441 if i == 0: self.subplot(0)
442
443 else:
444 self.subplots = []
445 for i in range(0,rows*cols):
446 self.subplots.append({})
447 self.subplots[i]['axes'] = self.figure.add_subplot(rows,
448 cols, i+1)
449 self.subplots[i]['lines'] = []
450
451 self.subplot(0)
452
453
454 def set_title(self, title=None):
455 """
456 Set the title of the plot window. Use the previous title if title is
457 omitted.
458 """
459 if title is not None:
460 self.title = title
461
462 self.figure.text(0.5, 0.95, self.title, horizontalalignment='center')
463
464
465 def show(self):
466 """
467 Show graphics dependent on the current buffering state.
468 """
469 if not self.buffering:
470 if self.loc:
471 lines = []
472 labels = []
473 i = 0
474 for line in self.lines:
475 i += 1
476 if line is not None:
477 lines.append(line[0])
478 lbl = line[0].get_label()
479 if lbl == '':
480 lbl = str(i)
481 labels.append(lbl)
482
483 if len(lines):
484 self.axes.legend(tuple(lines), tuple(labels), self.loc)
485 else:
486 self.axes.legend((' '))
487
488 self.window.wm_deiconify()
489 self.canvas.show()
490
491
492 def subplot(self, i=None, inc=None):
493 """
494 Set the subplot to the 0-relative panel number as defined by one or
495 more invokations of set_panels().
496 """
497 l = len(self.subplots)
498 if l:
499 if i is not None:
500 self.i = i
501
502 if inc is not None:
503 self.i += inc
504
505 self.i %= l
506 self.axes = self.subplots[self.i]['axes']
507 self.lines = self.subplots[self.i]['lines']
508
509
510 def terminate(self):
511 """
512 Clear the figure.
513 """
514 self.window.destroy()
515
516
517 def text(self, *args, **kwargs):
518 """
519 Add text to the figure.
520 """
521 self.figure.text(*args, **kwargs)
522 self.show()
523
524
525 def unmap(self):
526 """
527 Hide the ASAPlot graphics window.
528 """
529 self.window.wm_withdraw()
530
531
532def get_colour(colour='black'):
533 """
534 Look up a colour by name in the colour dictionary. Matches are
535 case-insensitive, insensitive to blanks, and 'gray' matches 'grey'.
536 """
537
538 if colour is None: return None
539
540 if match('[rgbcmykw]$', colour): return colour
541 if match('#[\da-fA-F]{6}$', colour): return colour
542
543 if len(colours) == 0: load_colours()
544
545 # Try a quick match.
546 if colours.has_key(colour): return colours[colour]
547
548 colour = colour.replace(' ','').lower()
549 colour = colour.replace('gray','grey')
550 for name in colours.keys():
551 if name.lower() == colour:
552 return colours[name]
553
554 return '#000000'
555
556
557def list_colours():
558 """
559 List the contents of the colour dictionary sorted by name.
560 """
561
562 if len(colours) == 0: load_colours()
563
564 names = colours.keys()
565 names.sort()
566 for name in names:
567 print colours[name], name
568
569
570def load_colours(file='/usr/local/lib/rgb.txt'):
571 """
572 Load the colour dictionary from the specified file.
573 """
574 print 'Loading colour dictionary from', file
575 rgb = open(file, 'r')
576
577 while True:
578 line = rgb.readline()
579 if line == '': break
580 tmp = line.split()
581
582 if len(tmp) == 4:
583 if tmp[3][:4] == 'gray': continue
584 if tmp[3].lower().find('gray') != -1: continue
585
586 name = tmp[3][0].upper() + tmp[3][1:]
587 r, g, b = int(tmp[0]), int(tmp[1]), int(tmp[2])
588 colours[name] = '#%2.2x%2.2x%2.2x' % (r, g, b)
Note: See TracBrowser for help on using the repository browser.