source: trunk/python/asaplot.py@ 118

Last change on this file since 118 was 118, checked in by cal103, 21 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
RevLine 
[111]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, \
[117]15 FigureManagerTkAgg
[111]16from matplotlib.figure import Figure, Text
[118]17from matplotlib.numerix import sqrt
[111]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
[118]30 def __init__(self, rows=1, cols=0, title='', size=(8,6), buffering=False):
[117]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)
[111]38
[117]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)
[111]42
[117]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')
[111]46
[118]47 self.set_title(title)
[117]48 self.subplots = []
[118]49 if rows > 0:
50 self.set_panels(rows, cols)
[111]51
52
[117]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
[111]57
[117]58 matplotlib.interactive = True
59 self.buffering = buffering
[111]60
[117]61 self.canvas.show()
[111]62
63
[117]64 def clear(self):
65 """
66 Delete all lines from the plot. Line numbering will restart from 1.
67 """
[111]68
[117]69 for i in range(1,len(self.lines)+1):
70 self.delete(i)
[111]71
[117]72 self.axes.clear()
73 self.colours[0] = 1
74 self.lines = []
75
[111]76
77 def delete(self, numbers=None):
[117]78 """
79 Delete the 0-relative line number, default is to delete the last.
80 The remaining lines are NOT renumbered.
81 """
[111]82
[117]83 if numbers is None: numbers = [len(self.lines)-1]
[111]84
[117]85 if not hasattr(numbers, '__iter__'):
86 numbers = [numbers]
[111]87
[117]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
[111]94
[117]95 self.show()
[111]96
97
98 def get_line(self):
[117]99 """
100 Get the current default line attributes.
101 """
102 return self.attributes
[111]103
104
105 def hold(self, hold=True):
[117]106 """
107 Buffer graphics until subsequently released.
108 """
109 self.buffering = hold
[111]110
111
112 def legend(self, loc=1):
[117]113 """
114 Add a legend to the plot.
[111]115
[117]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
[111]127
[117]128 """
129 if 1 > loc > 10: loc = 0
130 self.loc = loc
131 self.show()
[111]132
133
134 def map(self):
[117]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()
[111]141
[117]142
[111]143 def palette(self, pen=None, colours=None):
[117]144 """
145 Redefine the colour sequence.
[111]146
[117]147 pen is the pen number to use for the next plot; this will be auto-
148 incremented.
[111]149
[117]150 colours is the list of pen colours. Colour may be specified via
151 the single letter values understood by matplotlib:
[111]152
[117]153 b: blue
154 g: green
155 r: red
156 c: cyan
157 m: magenta
158 y: yellow
159 k: black
160 w: white
[111]161
[117]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 """
[111]166
[117]167 if pen is None and colours is None:
168 self.colours = []
169 return
[111]170
[117]171 if pen is None:
172 if not len(self.colours):
173 self.colours = [1]
174 else:
175 self.colours[0] = pen
[111]176
[117]177 if colours is None:
178 return
[111]179
[117]180 cols = []
181 for col in colours:
182 cols.append(get_colour(col))
[111]183
[117]184 self.colours[1:] = cols
[111]185
[117]186 if 0 > self.colours[0] > len(self.colours):
187 self.colours[0] = 1
[111]188
189
190 def plot(self, x=None, y=None, mask=None, fmt=None, add=None):
[117]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.
[111]194
[117]195 The argument list works a bit like the matlab plot() function.
196 """
[111]197
[117]198 if x is None:
199 if y is None: return
200 x = range(len(y))
[111]201
[117]202 elif y is None:
203 y = x
204 x = range(len(y))
[111]205
[117]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 = []
[111]213
[117]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)
[111]222
[117]223 segments.append(x[i:j])
224 segments.append(y[i:j])
[111]225
[117]226 i = j
[111]227
[117]228 line = self.axes.plot(*segments)
[111]229
[117]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)
[111]238
[117]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)
[111]245
[117]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]])
[111]249
[117]250 self.colours[0] += 1
251 if self.colours[0] >= len(self.colours):
252 self.colours[0] = 1
[111]253
[117]254 self.show()
[111]255
256
257 def quit(self):
[117]258 """
259 Destroy the ASAPlot graphics window.
260 """
261 self.window.destroy()
[111]262
263
264 def release(self):
[117]265 """
266 Release buffered graphics.
267 """
268 self.buffering = False
269 self.show()
[111]270
271
272 def set_axes(self, what=None, *args, **kwargs):
[117]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 """
[111]278
[117]279 if what is None: return
280 if what[-6:] == 'colour': what = what[:-6] + 'color'
[111]281
[117]282 newargs = {}
283 for k, v in kwargs.iteritems():
284 k = k.lower()
285 if k == 'colour': k = 'color'
[111]286
[117]287 if k == 'color':
288 v = get_colour(v)
[111]289
[117]290 newargs[k] = v
[111]291
[117]292 getattr(self.axes, "set_%s"%what)(*args, **newargs)
293 self.show()
[111]294
295
296 def set_figure(self, what=None, *args, **kwargs):
[117]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 """
[111]302
[117]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]),)
[111]307
[117]308 newargs = {}
309 for k, v in kwargs.iteritems():
310 k = k.lower()
311 if k == 'colour': k = 'color'
[111]312
[117]313 if k == 'color':
314 v = get_colour(v)
[111]315
[117]316 newargs[k] = v
[111]317
[117]318 getattr(self.figure, "set_%s"%what)(*args, **newargs)
319 self.show()
[111]320
321
322 def set_line(self, number=None, **kwargs):
[117]323 """
324 Set attributes for the specified line, or else the next line(s)
325 to be plotted.
[111]326
[117]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.
[111]330
[117]331 Keyword arguments specify Line2D attributes, e.g. color='r'. Do
[111]332
[117]333 import matplotlib
334 help(matplotlib.lines)
[111]335
[117]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".
[111]339
[117]340 Set the value to None to delete an attribute.
[111]341
[117]342 Colour translation is done as described in the doctext for palette().
343 """
[111]344
[117]345 redraw = False
346 for k, v in kwargs.iteritems():
347 k = k.lower()
348 if k == 'colour': k = 'color'
[111]349
[117]350 if k == 'color':
351 v = get_colour(v)
[111]352
[117]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
[111]363
[117]364 if redraw: self.show()
[111]365
366
[118]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
[111]432 def show(self):
[117]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)
[111]449
[117]450 if len(lines):
451 self.axes.legend(tuple(lines), tuple(labels), self.loc)
452 else:
453 self.axes.legend((' '))
[111]454
[117]455 self.window.wm_deiconify()
456 self.canvas.show()
[111]457
458
[118]459 def subplot(self, i=0):
[117]460 """
[118]461 Set the subplot to the 0-relative panel number as defined by one or
462 more invokations of set_panels().
[117]463 """
[118]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']
[111]469
[117]470
[111]471 def terminate(self):
[117]472 """
473 Clear the figure.
474 """
475 self.window.destroy()
[111]476
477
478 def text(self, *args, **kwargs):
[117]479 """
480 Add text to the figure.
481 """
482 self.figure.text(*args, **kwargs)
483 self.show()
[111]484
485
486 def unmap(self):
[117]487 """
488 Hide the ASAPlot graphics window.
489 """
490 self.window.wm_withdraw()
[111]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():
[117]512 if name.lower() == colour:
513 return colours[name]
[111]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:
[117]528 print colours[name], name
[111]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:
[117]539 line = rgb.readline()
540 if line == '': break
541 tmp = line.split()
[111]542
[117]543 if len(tmp) == 4:
544 if tmp[3][:4] == 'gray': continue
545 if tmp[3].lower().find('gray') != -1: continue
[111]546
[117]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.