Changeset 708
- Timestamp:
- 11/02/05 13:20:12 (19 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/python/asaplot.py
r702 r708 3 3 """ 4 4 5 import sys 6 from re import match 7 import Tkinter as Tk 5 from asap.asaplotbase import * 8 6 9 import matplotlib 10 matplotlib.use("TkAgg") 7 from matplotlib.backends.backend_agg import FigureCanvasAgg 11 8 12 from matplotlib.backends import new_figure_manager, show 13 from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, \ 14 FigureManagerTkAgg 15 from matplotlib.figure import Figure, Text 16 from matplotlib.font_manager import FontProperties 17 from matplotlib.numerix import sqrt 18 from matplotlib import rc, rcParams 19 from asap import rcParams as asaprcParams 20 21 # Force use of the newfangled toolbar. 22 matplotlib.rcParams['toolbar'] = 'toolbar2' 23 24 class ASAPlot: 9 class asaplot(asaplotbase): 25 10 """ 26 11 ASAP plotting class based on matplotlib. 27 12 """ 28 29 def __init__(self, rows=1, cols=0, title='', size=(8,6), buffering=False): 13 def __init__(self, rows=1, cols=0, title='', size=(8,4), buffering=False): 30 14 """ 31 15 Create a new instance of the ASAPlot plotting class. … … 34 18 the panel layout; refer to the doctext for set_panels(). 35 19 """ 36 self.window = Tk.Tk() 37 self.is_dead = False 38 def dest_callback(): 39 self.is_dead = True 40 self.window.destroy() 41 42 self.window.protocol("WM_DELETE_WINDOW", dest_callback) 43 44 self.figure = Figure(figsize=size, facecolor='#ddddee') 45 self.canvas = FigureCanvasTkAgg(self.figure, master=self.window) 46 self.canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1) 47 48 # Simply instantiating this is enough to get a working toolbar. 49 self.figmgr = FigureManagerTkAgg(self.canvas, 1, self.window) 50 self.window.wm_title('ASAPlot graphics window') 51 52 self.events = {'button_press':None, 53 'button_release':None, 54 'motion_notify':None} 55 56 self.set_title(title) 57 self.subplots = [] 58 if rows > 0: 59 self.set_panels(rows, cols) 60 61 # Set matplotlib default colour sequence. 62 self.colormap = "green red black cyan magenta orange blue purple yellow pink".split() 63 64 c = asaprcParams['plotter.colours'] 65 if isinstance(c,str) and len(c) > 0: 66 self.colormap = c.split() 67 68 self.lsalias = {"line": [1,0], 69 "dashdot": [4,2,1,2], 70 "dashed" : [4,2,4,2], 71 "dotted" : [1,2], 72 "dashdotdot": [4,2,1,2,1,2], 73 "dashdashdot": [4,2,4,2,1,2] 74 } 75 76 styles = "line dashed dotted dashdot".split() 77 c = asaprcParams['plotter.linestyles'] 78 if isinstance(c,str) and len(c) > 0: 79 styles = c.split() 80 s = [] 81 for ls in styles: 82 if self.lsalias.has_key(ls): 83 s.append(self.lsalias.get(ls)) 84 else: 85 s.append('-') 86 self.linestyles = s 87 88 self.color = 0; 89 self.linestyle = 0; 90 self.attributes = {} 91 self.loc = 0 92 93 matplotlib.interactive = True 94 self.buffering = buffering 95 96 self.canvas.show() 97 98 99 def clear(self): 100 """ 101 Delete all lines from the plot. Line numbering will restart from 1. 102 """ 103 104 for i in range(len(self.lines)): 105 self.delete(i) 106 self.axes.clear() 107 self.color = 0 108 self.linestyle = 0 109 self.lines = [] 110 111 def palette(self, color, colormap=None, linestyle=0, linestyles=None): 112 if colormap: 113 if isinstance(colormap,list): 114 self.colormap = colormap 115 elif isinstance(colormap,str): 116 self.colormap = colormap.split() 117 if 0 <= color < len(self.colormap): 118 self.color = color 119 if linestyles: 120 self.linestyles = [] 121 if isinstance(linestyles,list): 122 styles = linestyles 123 elif isinstance(linestyles,str): 124 styles = linestyles.split() 125 for ls in styles: 126 if self.lsalias.has_key(ls): 127 self.linestyles.append(self.lsalias.get(ls)) 128 else: 129 self.linestyles.append(self.lsalias.get('line')) 130 if 0 <= linestyle < len(self.linestyles): 131 self.linestyle = linestyle 132 133 def delete(self, numbers=None): 134 """ 135 Delete the 0-relative line number, default is to delete the last. 136 The remaining lines are NOT renumbered. 137 """ 138 139 if numbers is None: numbers = [len(self.lines)-1] 140 141 if not hasattr(numbers, '__iter__'): 142 numbers = [numbers] 143 144 for number in numbers: 145 if 0 <= number < len(self.lines): 146 if self.lines[number] is not None: 147 for line in self.lines[number]: 148 line.set_linestyle('None') 149 self.lines[number] = None 150 self.show() 151 152 def get_line(self): 153 """ 154 Get the current default line attributes. 155 """ 156 return self.attributes 157 158 159 def hist(self, x=None, y=None, fmt=None): 160 """ 161 Plot a histogram. N.B. the x values refer to the start of the 162 histogram bin. 163 164 fmt is the line style as in plot(). 165 """ 166 167 if x is None: 168 if y is None: return 169 x = range(0,len(y)) 170 171 if len(x) != len(y): 172 return 173 174 l2 = 2*len(x) 175 x2 = range(0,l2) 176 y2 = range(0,l2) 177 178 for i in range(0,l2): 179 x2[i] = x[i/2] 180 181 y2[0] = 0 182 for i in range(1,l2): 183 y2[i] = y[(i-1)/2] 184 185 self.plot(x2, y2, fmt) 186 187 188 def hold(self, hold=True): 189 """ 190 Buffer graphics until subsequently released. 191 """ 192 self.buffering = hold 193 194 195 def legend(self, loc=None): 196 """ 197 Add a legend to the plot. 198 199 Any other value for loc else disables the legend: 200 1: upper right 201 2: upper left 202 3: lower left 203 4: lower right 204 5: right 205 6: center left 206 7: center right 207 8: lower center 208 9: upper center 209 10: center 210 211 """ 212 if isinstance(loc,int): 213 if 0 > loc > 10: loc = 0 214 self.loc = loc 215 self.show() 216 217 218 def map(self): 219 """ 220 Reveal the ASAPlot graphics window and bring it to the top of the 221 window stack. 222 """ 223 self.window.wm_deiconify() 224 self.window.lift() 225 226 227 228 def plot(self, x=None, y=None, mask=None, fmt=None, add=None): 229 """ 230 Plot the next line in the current frame using the current line 231 attributes. The ASAPlot graphics window will be mapped and raised. 232 233 The argument list works a bit like the matlab plot() function. 234 """ 235 236 if x is None: 237 if y is None: return 238 x = range(len(y)) 239 240 elif y is None: 241 y = x 242 x = range(len(y)) 243 244 if mask is None: 245 if fmt is None: 246 line = self.axes.plot(x, y) 247 else: 248 line = self.axes.plot(x, y, fmt) 249 else: 250 segments = [] 251 252 mask = list(mask) 253 i = 0 254 while mask[i:].count(1): 255 i += mask[i:].index(1) 256 if mask[i:].count(0): 257 j = i + mask[i:].index(0) 258 else: 259 j = len(mask) 260 261 segments.append(x[i:j]) 262 segments.append(y[i:j]) 263 264 i = j 265 266 line = self.axes.plot(*segments) 267 268 # Add to an existing line? 269 if add is None or len(self.lines) < add < 0: 270 # Don't add. 271 self.lines.append(line) 272 i = len(self.lines) - 1 273 else: 274 if add == 0: add = len(self.lines) 275 i = add - 1 276 self.lines[i].extend(line) 277 278 # Set/reset attributes for the line. 279 gotcolour = False 280 for k, v in self.attributes.iteritems(): 281 if k == 'color': gotcolour = True 282 for segment in self.lines[i]: 283 getattr(segment, "set_%s"%k)(v) 284 285 if not gotcolour and len(self.colormap): 286 for segment in self.lines[i]: 287 getattr(segment, "set_color")(self.colormap[self.color]) 288 if len(self.colormap) == 1: 289 getattr(segment, "set_dashes")(self.linestyles[self.linestyle]) 290 self.color += 1 291 if self.color >= len(self.colormap): 292 self.color = 0 293 294 if len(self.colormap) == 1: 295 self.linestyle += 1 296 if self.linestyle >= len(self.linestyles): 297 self.linestyle = 0 298 299 self.show() 300 301 302 def position(self): 303 """ 304 Use the mouse to get a position from a graph. 305 """ 306 307 def position_disable(event): 308 self.register('button_press', None) 309 print '%.4f, %.4f' % (event.xdata, event.ydata) 310 311 print 'Press any mouse button...' 312 self.register('button_press', position_disable) 313 314 315 def quit(self): 316 """ 317 Destroy the ASAPlot graphics window. 318 """ 319 self.window.destroy() 320 321 322 def region(self): 323 """ 324 Use the mouse to get a rectangular region from a plot. 325 326 The return value is [x0, y0, x1, y1] in world coordinates. 327 """ 328 329 def region_start(event): 330 height = self.canvas.figure.bbox.height() 331 self.rect = {'fig': None, 'height': height, 332 'x': event.x, 'y': height - event.y, 333 'world': [event.xdata, event.ydata, 334 event.xdata, event.ydata]} 335 self.register('button_press', None) 336 self.register('motion_notify', region_draw) 337 self.register('button_release', region_disable) 338 339 def region_draw(event): 340 self.canvas._tkcanvas.delete(self.rect['fig']) 341 self.rect['fig'] = self.canvas._tkcanvas.create_rectangle( 342 self.rect['x'], self.rect['y'], 343 event.x, self.rect['height'] - event.y) 344 345 def region_disable(event): 346 self.register('motion_notify', None) 347 self.register('button_release', None) 348 349 self.canvas._tkcanvas.delete(self.rect['fig']) 350 351 self.rect['world'][2:4] = [event.xdata, event.ydata] 352 print '(%.2f, %.2f) (%.2f, %.2f)' % (self.rect['world'][0], 353 self.rect['world'][1], self.rect['world'][2], 354 self.rect['world'][3]) 355 356 self.register('button_press', region_start) 357 358 # This has to be modified to block and return the result (currently 359 # printed by region_disable) when that becomes possible in matplotlib. 360 361 return [0.0, 0.0, 0.0, 0.0] 362 363 364 def register(self, type=None, func=None): 365 """ 366 Register, reregister, or deregister events of type 'button_press', 367 'button_release', or 'motion_notify'. 368 369 The specified callback function should have the following signature: 370 371 def func(event) 372 373 where event is an MplEvent instance containing the following data: 374 375 name # Event name. 376 canvas # FigureCanvas instance generating the event. 377 x = None # x position - pixels from left of canvas. 378 y = None # y position - pixels from bottom of canvas. 379 button = None # Button pressed: None, 1, 2, 3. 380 key = None # Key pressed: None, chr(range(255)), shift, 381 win, or control 382 inaxes = None # Axes instance if cursor within axes. 383 xdata = None # x world coordinate. 384 ydata = None # y world coordinate. 385 386 For example: 387 388 def mouse_move(event): 389 print event.xdata, event.ydata 390 391 a = asaplot() 392 a.register('motion_notify', mouse_move) 393 394 If func is None, the event is deregistered. 395 396 Note that in TkAgg keyboard button presses don't generate an event. 397 """ 398 399 if not self.events.has_key(type): return 400 401 if func is None: 402 if self.events[type] is not None: 403 # It's not clear that this does anything. 404 self.canvas.mpl_disconnect(self.events[type]) 405 self.events[type] = None 406 407 # It seems to be necessary to return events to the toolbar. 408 if type == 'motion_notify': 409 self.canvas.mpl_connect(type + '_event', 410 self.figmgr.toolbar.mouse_move) 411 elif type == 'button_press': 412 self.canvas.mpl_connect(type + '_event', 413 self.figmgr.toolbar.press) 414 elif type == 'button_release': 415 self.canvas.mpl_connect(type + '_event', 416 self.figmgr.toolbar.release) 417 418 else: 419 self.events[type] = self.canvas.mpl_connect(type + '_event', func) 420 421 422 def release(self): 423 """ 424 Release buffered graphics. 425 """ 426 self.buffering = False 427 self.show() 428 429 430 def save(self, fname=None, orientation=None): 431 """ 432 Save the plot to a file. 433 434 fname is the name of the output file. The image format is determined 435 from the file suffix; 'png', 'ps', and 'eps' are recognized. If no 436 file name is specified 'yyyymmdd_hhmmss.png' is created in the current 437 directory. 438 """ 439 if fname is None: 440 from datetime import datetime 441 dstr = datetime.now().strftime('%Y%m%d_%H%M%S') 442 fname = 'asap'+dstr+'.png' 443 444 d = ['png','.ps','eps'] 445 446 from os.path import expandvars 447 fname = expandvars(fname) 448 449 if fname[-3:].lower() in d: 450 try: 451 if fname[-3:].lower() == ".ps": 452 w = self.figure.figwidth.get() 453 h = self.figure.figheight.get() 454 a4w = 8.25 455 a4h = 11.25 456 457 if orientation is None: 458 # auto oriented 459 if w > h: 460 orientation = 'landscape' 461 else: 462 orientation = 'portrait' 463 ds = None 464 if orientation == 'landscape': 465 ds = min(a4h/w,a4w/h) 466 #self.figure.set_figsize_inches((a4h,a4w)) 467 else: 468 ds = min(a4w/w,a4h/h) 469 ow = ds * w 470 oh = ds * h 471 self.figure.set_figsize_inches((ow,oh)) 472 self.canvas.print_figure(fname,orientation=orientation) 473 print 'Written file %s' % (fname) 474 else: 475 self.canvas.print_figure(fname) 476 print 'Written file %s' % (fname) 477 except IOError, msg: 478 print 'Failed to save %s: Error msg was\n\n%s' % (fname, err) 479 return 480 else: 481 print "Invalid image type. Valid types are:" 482 print "'ps', 'eps', 'png'" 483 484 485 def set_axes(self, what=None, *args, **kwargs): 486 """ 487 Set attributes for the axes by calling the relevant Axes.set_*() 488 method. Colour translation is done as described in the doctext 489 for palette(). 490 """ 491 492 if what is None: return 493 if what[-6:] == 'colour': what = what[:-6] + 'color' 494 495 newargs = {} 496 497 for k, v in kwargs.iteritems(): 498 k = k.lower() 499 if k == 'colour': k = 'color' 500 newargs[k] = v 501 502 getattr(self.axes, "set_%s"%what)(*args, **newargs) 503 self.show() 504 505 506 def set_figure(self, what=None, *args, **kwargs): 507 """ 508 Set attributes for the figure by calling the relevant Figure.set_*() 509 method. Colour translation is done as described in the doctext 510 for palette(). 511 """ 512 513 if what is None: return 514 if what[-6:] == 'colour': what = what[:-6] + 'color' 515 #if what[-5:] == 'color' and len(args): 516 # args = (get_colour(args[0]),) 517 518 newargs = {} 519 for k, v in kwargs.iteritems(): 520 k = k.lower() 521 if k == 'colour': k = 'color' 522 newargs[k] = v 523 524 getattr(self.figure, "set_%s"%what)(*args, **newargs) 525 self.show() 526 527 528 def set_limits(self, xlim=None, ylim=None): 529 """ 530 Set x-, and y-limits for each subplot. 531 532 xlim = [xmin, xmax] as in axes.set_xlim(). 533 ylim = [ymin, ymax] as in axes.set_ylim(). 534 """ 535 for s in self.subplots: 536 self.axes = s['axes'] 537 self.lines = s['lines'] 538 oldxlim = list(self.axes.get_xlim()) 539 oldylim = list(self.axes.get_ylim()) 540 if xlim is not None: 541 for i in range(len(xlim)): 542 if xlim[i] is not None: 543 oldxlim[i] = xlim[i] 544 if ylim is not None: 545 for i in range(len(ylim)): 546 if ylim[i] is not None: 547 oldylim[i] = ylim[i] 548 self.axes.set_xlim(oldxlim) 549 self.axes.set_ylim(oldylim) 550 return 551 552 553 def set_line(self, number=None, **kwargs): 554 """ 555 Set attributes for the specified line, or else the next line(s) 556 to be plotted. 557 558 number is the 0-relative number of a line that has already been 559 plotted. If no such line exists, attributes are recorded and used 560 for the next line(s) to be plotted. 561 562 Keyword arguments specify Line2D attributes, e.g. color='r'. Do 563 564 import matplotlib 565 help(matplotlib.lines) 566 567 The set_* methods of class Line2D define the attribute names and 568 values. For non-US usage, "colour" is recognized as synonymous with 569 "color". 570 571 Set the value to None to delete an attribute. 572 573 Colour translation is done as described in the doctext for palette(). 574 """ 575 576 redraw = False 577 for k, v in kwargs.iteritems(): 578 k = k.lower() 579 if k == 'colour': k = 'color' 580 581 if 0 <= number < len(self.lines): 582 if self.lines[number] is not None: 583 for line in self.lines[number]: 584 getattr(line, "set_%s"%k)(v) 585 redraw = True 586 else: 587 if v is None: 588 del self.attributes[k] 589 else: 590 self.attributes[k] = v 591 592 if redraw: self.show() 593 594 595 def set_panels(self, rows=1, cols=0, n=-1, nplots=-1, ganged=True): 596 """ 597 Set the panel layout. 598 599 rows and cols, if cols != 0, specify the number of rows and columns in 600 a regular layout. (Indexing of these panels in matplotlib is row- 601 major, i.e. column varies fastest.) 602 603 cols == 0 is interpreted as a retangular layout that accomodates 604 'rows' panels, e.g. rows == 6, cols == 0 is equivalent to 605 rows == 2, cols == 3. 606 607 0 <= n < rows*cols is interpreted as the 0-relative panel number in 608 the configuration specified by rows and cols to be added to the 609 current figure as its next 0-relative panel number (i). This allows 610 non-regular panel layouts to be constructed via multiple calls. Any 611 other value of n clears the plot and produces a rectangular array of 612 empty panels. The number of these may be limited by nplots. 613 """ 614 if n < 0 and len(self.subplots): 615 self.figure.clear() 616 self.set_title() 617 618 if rows < 1: rows = 1 619 620 if cols <= 0: 621 i = int(sqrt(rows)) 622 if i*i < rows: i += 1 623 cols = i 624 625 if i*(i-1) >= rows: i -= 1 626 rows = i 627 628 if 0 <= n < rows*cols: 629 i = len(self.subplots) 630 self.subplots.append({}) 631 632 self.subplots[i]['axes'] = self.figure.add_subplot(rows, 633 cols, n+1) 634 self.subplots[i]['lines'] = [] 635 636 if i == 0: self.subplot(0) 637 638 self.rows = 0 639 self.cols = 0 640 641 else: 642 self.subplots = [] 643 644 if nplots < 1 or rows*cols < nplots: 645 nplots = rows*cols 646 647 for i in range(nplots): 648 self.subplots.append({}) 649 650 self.subplots[i]['axes'] = self.figure.add_subplot(rows, 651 cols, i+1) 652 self.subplots[i]['lines'] = [] 653 xfsize = self.subplots[i]['axes'].xaxis.label.get_size()-cols/2 654 yfsize = self.subplots[i]['axes'].yaxis.label.get_size()-rows/2 655 self.subplots[i]['axes'].xaxis.label.set_size(xfsize) 656 self.subplots[i]['axes'].yaxis.label.set_size(yfsize) 657 658 if ganged: 659 if rows > 1 or cols > 1: 660 # Squeeze the plots together. 661 pos = self.subplots[i]['axes'].get_position() 662 if cols > 1: pos[2] *= 1.2 663 if rows > 1: pos[3] *= 1.2 664 self.subplots[i]['axes'].set_position(pos) 665 666 # Suppress tick labelling for interior subplots. 667 if i <= (rows-1)*cols - 1: 668 if i+cols < nplots: 669 # Suppress x-labels for frames width 670 # adjacent frames 671 for tick in \ 672 self.subplots[i]['axes'].xaxis.majorTicks: 673 tick.label1On = False 674 self.subplots[i]['axes'].xaxis.label.set_visible(False) 675 if i%cols: 676 # Suppress y-labels for frames not in the left column. 677 for tick in self.subplots[i]['axes'].yaxis.majorTicks: 678 tick.label1On = False 679 self.subplots[i]['axes'].yaxis.label.set_visible(False) 680 681 682 self.rows = rows 683 self.cols = cols 684 685 self.subplot(0) 686 687 def set_title(self, title=None): 688 """ 689 Set the title of the plot window. Use the previous title if title is 690 omitted. 691 """ 692 if title is not None: 693 self.title = title 694 695 self.figure.text(0.5, 0.95, self.title, horizontalalignment='center') 696 697 698 def show(self): 699 """ 700 Show graphics dependent on the current buffering state. 701 """ 702 if not self.buffering: 703 if self.loc is not None: 704 for j in range(len(self.subplots)): 705 lines = [] 706 labels = [] 707 i = 0 708 for line in self.subplots[j]['lines']: 709 i += 1 710 if line is not None: 711 lines.append(line[0]) 712 lbl = line[0].get_label() 713 if lbl == '': 714 lbl = str(i) 715 labels.append(lbl) 716 717 if len(lines): 718 self.subplots[j]['axes'].legend(tuple(lines), 719 tuple(labels), 720 self.loc) 721 else: 722 self.subplots[j]['axes'].legend((' ')) 723 724 self.window.wm_deiconify() 725 self.canvas.show() 726 727 def subplot(self, i=None, inc=None): 728 """ 729 Set the subplot to the 0-relative panel number as defined by one or 730 more invokations of set_panels(). 731 """ 732 l = len(self.subplots) 733 if l: 734 if i is not None: 735 self.i = i 736 737 if inc is not None: 738 self.i += inc 739 740 self.i %= l 741 self.axes = self.subplots[self.i]['axes'] 742 self.lines = self.subplots[self.i]['lines'] 743 744 745 def terminate(self): 746 """ 747 Clear the figure. 748 """ 749 self.window.destroy() 750 751 752 def text(self, *args, **kwargs): 753 """ 754 Add text to the figure. 755 """ 756 self.figure.text(*args, **kwargs) 757 self.show() 758 759 760 def unmap(self): 761 """ 762 Hide the ASAPlot graphics window. 763 """ 764 self.window.wm_withdraw() 20 v = vars() 21 del v['self'] 22 asaplotbase.__init__(self,**v) 23 self.canvas = FigureCanvasAgg(self.figure)
Note:
See TracChangeset
for help on using the changeset viewer.