source: trunk/python/asaplot.py@ 144

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

Added function register() to handle mouse events.

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