source: trunk/python/asaplot.py@ 118

Last change on this file since 118 was 118, checked in by cal103, 20 years ago

Added general control of the panel layout.

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