Package nltk :: Package draw :: Module plot
[hide private]
[frames] | no frames]

Source Code for Module nltk.draw.plot

  1  # Natural Language Toolkit: Simple Plotting 
  2  # 
  3  # Copyright (C) 2001-2008 NLTK Project 
  4  # Author: Edward Loper <[email protected]> 
  5  # URL: <http://nltk.org> 
  6  # For license information, see LICENSE.TXT 
  7  # 
  8  # $Id: plot.py 6265 2008-07-26 09:25:03Z stevenbird $ 
  9   
 10  """ 
 11  A simple tool for plotting functions.  Each new C{Plot} object opens a 
 12  new window, containing the plot for a sinlge function.  See the 
 13  documentation for L{Plot} for information about creating new plots. 
 14   
 15  Example plots 
 16  ============= 
 17  Plot sin(x) from -10 to 10, with a step of 0.1: 
 18      >>> Plot(math.sin) 
 19   
 20  Plot cos(x) from 0 to 2*pi, with a step of 0.01: 
 21      >>> Plot(math.cos, slice(0, 2*math.pi, 0.01)) 
 22   
 23  Plot a list of points (connected by lines). 
 24      >>> points = ([1,1], [3,8], [5,3], [6,12], [1,24]) 
 25      >>> Plot(points) 
 26   
 27  Plot a list of y-values (connected by lines).  Each value[i] is 
 28  plotted at x=i. 
 29      >>> values = [x**2 for x in range(200)] 
 30      >>> Plot(values) 
 31   
 32  Plot a function with logarithmic axes. 
 33      >>> def f(x): return 5*x**2+2*x+8 
 34      >>> Plot(f, slice(1,10,.1), scale='log') 
 35   
 36  Plot the same function with semi-logarithmic axes. 
 37      >>> Plot(f, slice(1,10,.1), 
 38               scale='log-linear')  # logarithmic x; linear y 
 39      >>> Plot(f, slice(1,10,.1), 
 40               scale='linear-log')  # linear x; logarithmic y 
 41   
 42  BLT 
 43  === 
 44  If U{BLT<http://incrtcl.sourceforge.net/blt/>} and 
 45  U{PMW<http://pmw.sourceforge.net/>} are both installed, then BLT is 
 46  used to plot graphs.  Otherwise, a simple Tkinter-based implementation 
 47  is used.  The Tkinter-based implementation does I{not} display axis 
 48  values. 
 49   
 50  @group Plot Frame Implementations: PlotFrameI, CanvasPlotFrame, 
 51      BLTPlotFrame 
 52  """ 
 53   
 54  # This is used by "from nltk.draw.plot import *". to decide what to 
 55  # import.  It also declares to nltk that only the Plot class is public. 
 56  __all__ = ['Plot'] 
 57   
 58  # Implementation note: 
 59  #   For variable names, I use x,y for pixel coordinates; and i,j for 
 60  #   plot coordinates. 
 61   
 62  # Delegate to BLT? 
 63  #    
 64  #    
 65   
 66  from types import * 
 67  from math import log, log10, ceil, floor 
 68  import Tkinter, sys, time 
 69   
 70  from nltk.draw import ShowText, in_idle 
 71   
72 -class PlotFrameI(object):
73 """ 74 A frame for plotting graphs. If BLT is present, then we use 75 BLTPlotFrame, since it's nicer. But we fall back on 76 CanvasPlotFrame if BLTPlotFrame is unavaibale. 77 """
78 - def postscript(self, filename):
79 'Print the contents of the plot to the given file' 80 raise AssertionError, 'PlotFrameI is an interface'
81 - def config_axes(self, xlog, ylog):
82 'Set the scale for the axes (linear/logarithmic)' 83 raise AssertionError, 'PlotFrameI is an interface'
84 - def invtransform(self, x, y):
85 'Transform pixel coordinates to plot coordinates' 86 raise AssertionError, 'PlotFrameI is an interface'
87 - def zoom(self, i1, j1, i2, j2):
88 'Zoom to the given range' 89 raise AssertionError, 'PlotFrameI is an interface'
90 - def visible_area(self):
91 'Return the visible area rect (in plot coordinates)' 92 raise AssertionError, 'PlotFrameI is an interface'
93 - def create_zoom_marker(self):
94 'mark the zoom region, for drag-zooming' 95 raise AssertionError, 'PlotFrameI is an interface'
96 - def adjust_zoom_marker(self, x0, y0, x1, y1):
97 'adjust the zoom region marker, for drag-zooming' 98 raise AssertionError, 'PlotFrameI is an interface'
99 - def delete_zoom_marker(self):
100 'delete the zoom region marker (for drag-zooming)' 101 raise AssertionError, 'PlotFrameI is an interface'
102 - def bind(self, *args):
103 'bind an event to a function' 104 raise AssertionError, 'PlotFrameI is an interface'
105 - def unbind(self, *args):
106 'unbind an event' 107 raise AssertionError, 'PlotFrameI is an interface' 108
109 -class CanvasPlotFrame(PlotFrameI):
110 - def __init__(self, root, vals, rng):
111 self._root = root 112 self._original_rng = rng 113 self._original_vals = vals 114 115 self._frame = Tkinter.Frame(root) 116 self._frame.pack(expand=1, fill='both') 117 118 # Set up the canvas 119 self._canvas = Tkinter.Canvas(self._frame, background='white') 120 self._canvas['scrollregion'] = (0,0,200,200) 121 122 # Give the canvas scrollbars. 123 sb1 = Tkinter.Scrollbar(self._frame, orient='vertical') 124 sb1.pack(side='right', fill='y') 125 sb2 = Tkinter.Scrollbar(self._frame, orient='horizontal') 126 sb2.pack(side='bottom', fill='x') 127 self._canvas.pack(side='left', fill='both', expand=1) 128 129 # Connect the scrollbars to the canvas. 130 sb1.config(command=self._canvas.yview) 131 sb2['command']=self._canvas.xview 132 self._canvas['yscrollcommand'] = sb1.set 133 self._canvas['xscrollcommand'] = sb2.set 134 135 self._width = self._height = -1 136 self._canvas.bind('<Configure>', self._configure) 137 138 # Start out with linear coordinates. 139 self.config_axes(0, 0)
140
141 - def _configure(self, event):
142 if self._width != event.width or self._height != event.height: 143 self._width = event.width 144 self._height = event.height 145 (i1, j1, i2, j2) = self.visible_area() 146 self.zoom(i1, j1, i2, j2)
147
148 - def postscript(self, filename):
149 (x0, y0, w, h) = self._canvas['scrollregion'].split() 150 self._canvas.postscript(file=filename, x=float(x0), y=float(y0), 151 width=float(w)+2, height=float(h)+2)
152
153 - def _plot(self, *args):
154 self._canvas.delete('all') 155 (i1, j1, i2, j2) = self.visible_area() 156 157 # Draw the Axes 158 xzero = -self._imin*self._dx 159 yzero = self._ymax+self._jmin*self._dy 160 neginf = min(self._imin, self._jmin, -1000)*1000 161 posinf = max(self._imax, self._jmax, 1000)*1000 162 self._canvas.create_line(neginf,yzero,posinf,yzero, 163 fill='gray', width=2) 164 self._canvas.create_line(xzero,neginf,xzero,posinf, 165 fill='gray', width=2) 166 167 # Draw the X grid. 168 if self._xlog: 169 (i1, i2) = (10**(i1), 10**(i2)) 170 (imin, imax) = (10**(self._imin), 10**(self._imax)) 171 # Grid step size. 172 di = (i2-i1)/1000.0 173 # round to a power of 10 174 di = 10.0**(int(log10(di))) 175 # grid start location 176 i = ceil(imin/di)*di 177 while i <= imax: 178 if i > 10*di: di *= 10 179 x = log10(i)*self._dx - log10(imin)*self._dx 180 self._canvas.create_line(x, neginf, x, posinf, fill='gray') 181 i += di 182 else: 183 # Grid step size. 184 di = max((i2-i1)/10.0, (self._imax-self._imin)/100) 185 # round to a power of 2 186 di = 2.0**(int(log(di)/log(2))) 187 # grid start location 188 i = int(self._imin/di)*di 189 # Draw the grid. 190 while i <= self._imax: 191 x = (i-self._imin)*self._dx 192 self._canvas.create_line(x, neginf, x, posinf, fill='gray') 193 i += di 194 195 # Draw the Y grid 196 if self._ylog: 197 (j1, j2) = (10**(j1), 10**(j2)) 198 (jmin, jmax) = (10**(self._jmin), 10**(self._jmax)) 199 # Grid step size. 200 dj = (j2-j1)/1000.0 201 # round to a power of 10 202 dj = 10.0**(int(log10(dj))) 203 # grid start locatjon 204 j = ceil(jmin/dj)*dj 205 while j <= jmax: 206 if j > 10*dj: dj *= 10 207 y = log10(jmax)*self._dy - log10(j)*self._dy 208 self._canvas.create_line(neginf, y, posinf, y, fill='gray') 209 j += dj 210 else: 211 # Grid step size. 212 dj = max((j2-j1)/10.0, (self._jmax-self._jmin)/100) 213 # round to a power of 2 214 dj = 2.0**(int(log(dj)/log(2))) 215 # grid start location 216 j = int(self._jmin/dj)*dj 217 # Draw the grid 218 while j <= self._jmax: 219 y = (j-self._jmin)*self._dy 220 self._canvas.create_line(neginf, y, posinf, y, fill='gray') 221 j += dj 222 223 # The plot 224 line = [] 225 for (i,j) in zip(self._rng, self._vals): 226 x = (i-self._imin) * self._dx 227 y = self._ymax-((j-self._jmin) * self._dy) 228 line.append( (x,y) ) 229 if len(line) == 1: line.append(line[0]) 230 self._canvas.create_line(line, fill='black')
231
232 - def config_axes(self, xlog, ylog):
233 if hasattr(self, '_rng'): 234 (i1, j1, i2, j2) = self.visible_area() 235 zoomed=1 236 else: 237 zoomed=0 238 239 self._xlog = xlog 240 self._ylog = ylog 241 if xlog: self._rng = [log10(x) for x in self._original_rng] 242 else: self._rng = self._original_rng 243 if ylog: self._vals = [log10(x) for x in self._original_vals] 244 else: self._vals = self._original_vals 245 246 self._imin = min(self._rng) 247 self._imax = max(self._rng) 248 if self._imax == self._imin: 249 self._imin -= 1 250 self._imax += 1 251 self._jmin = min(self._vals) 252 self._jmax = max(self._vals) 253 if self._jmax == self._jmin: 254 self._jmin -= 1 255 self._jmax += 1 256 257 if zoomed: 258 self.zoom(i1, j1, i2, j2) 259 else: 260 self.zoom(self._imin, self._jmin, self._imax, self._jmax)
261
262 - def invtransform(self, x, y):
263 x = self._canvas.canvasx(x) 264 y = self._canvas.canvasy(y) 265 return (self._imin+x/self._dx, self._jmin+(self._ymax-y)/self._dy)
266
267 - def zoom(self, i1, j1, i2, j2):
268 w = self._width 269 h = self._height 270 self._xmax = (self._imax-self._imin)/(i2-i1) * w 271 self._ymax = (self._jmax-self._jmin)/(j2-j1) * h 272 self._canvas['scrollregion'] = (0, 0, self._xmax, self._ymax) 273 self._dx = self._xmax/(self._imax-self._imin) 274 self._dy = self._ymax/(self._jmax-self._jmin) 275 self._plot() 276 277 # Pan to the correct place 278 self._canvas.xview('moveto', (i1-self._imin)/(self._imax-self._imin)) 279 self._canvas.yview('moveto', (self._jmax-j2)/(self._jmax-self._jmin))
280
281 - def visible_area(self):
282 xview = self._canvas.xview() 283 yview = self._canvas.yview() 284 i1 = self._imin + xview[0] * (self._imax-self._imin) 285 i2 = self._imin + xview[1] * (self._imax-self._imin) 286 j1 = self._jmax - yview[1] * (self._jmax-self._jmin) 287 j2 = self._jmax - yview[0] * (self._jmax-self._jmin) 288 return (i1, j1, i2, j2)
289
290 - def create_zoom_marker(self):
291 self._canvas.create_rectangle(0,0,0,0, tag='zoom')
292
293 - def adjust_zoom_marker(self, x0, y0, x1, y1):
294 x0 = self._canvas.canvasx(x0) 295 y0 = self._canvas.canvasy(y0) 296 x1 = self._canvas.canvasx(x1) 297 y1 = self._canvas.canvasy(y1) 298 self._canvas.coords('zoom', x0, y0, x1, y1)
299
300 - def delete_zoom_marker(self):
301 self._canvas.delete('zoom')
302
303 - def bind(self, *args): self._canvas.bind(*args)
304 - def unbind(self, *args): self._canvas.unbind(*args)
305
306 -class BLTPlotFrame(PlotFrameI):
307 - def __init__(self, root, vals, rng):
308 #raise ImportError # for debugging CanvasPlotFrame 309 310 # Find ranges 311 self._imin = min(rng) 312 self._imax = max(rng) 313 if self._imax == self._imin: 314 self._imin -= 1 315 self._imax += 1 316 self._jmin = min(vals) 317 self._jmax = max(vals) 318 if self._jmax == self._jmin: 319 self._jmin -= 1 320 self._jmax += 1 321 322 # Create top-level frame. 323 self._root = root 324 self._frame = Tkinter.Frame(root) 325 self._frame.pack(expand=1, fill='both') 326 327 # Create the graph. 328 try: 329 import Pmw 330 # This reload is needed to prevent an error if we create 331 # more than 1 graph in the same interactive session: 332 reload(Pmw.Blt) 333 334 Pmw.initialise() 335 self._graph = Pmw.Blt.Graph(self._frame) 336 except: 337 raise ImportError('Pmw not installed!') 338 339 # Add scrollbars. 340 sb1 = Tkinter.Scrollbar(self._frame, orient='vertical') 341 sb1.pack(side='right', fill='y') 342 sb2 = Tkinter.Scrollbar(self._frame, orient='horizontal') 343 sb2.pack(side='bottom', fill='x') 344 self._graph.pack(side='left', fill='both', expand='yes') 345 self._yscroll = sb1 346 self._xscroll = sb2 347 348 # Connect the scrollbars to the canvas. 349 sb1['command'] = self._yview 350 sb2['command'] = self._xview 351 352 # Create the plot. 353 self._graph.line_create('plot', xdata=tuple(rng), 354 ydata=tuple(vals), symbol='') 355 self._graph.legend_configure(hide=1) 356 self._graph.grid_configure(hide=0) 357 self._set_scrollbars()
358
359 - def _set_scrollbars(self):
360 (i1, j1, i2, j2) = self.visible_area() 361 (imin, imax) = (self._imin, self._imax) 362 (jmin, jmax) = (self._jmin, self._jmax) 363 364 self._xscroll.set((i1-imin)/(imax-imin), (i2-imin)/(imax-imin)) 365 self._yscroll.set(1-(j2-jmin)/(jmax-jmin), 1-(j1-jmin)/(jmax-jmin))
366
367 - def _xview(self, *command):
368 (i1, j1, i2, j2) = self.visible_area() 369 (imin, imax) = (self._imin, self._imax) 370 (jmin, jmax) = (self._jmin, self._jmax) 371 372 if command[0] == 'moveto': 373 f = float(command[1]) 374 elif command[0] == 'scroll': 375 dir = int(command[1]) 376 if command[2] == 'pages': 377 f = (i1-imin)/(imax-imin) + dir*(i2-i1)/(imax-imin) 378 elif command[2] == 'units': 379 f = (i1-imin)/(imax-imin) + dir*(i2-i1)/(10*(imax-imin)) 380 else: return 381 else: return 382 383 f = max(f, 0) 384 f = min(f, 1-(i2-i1)/(imax-imin)) 385 self.zoom(imin + f*(imax-imin), j1, 386 imin + f*(imax-imin)+(i2-i1), j2) 387 self._set_scrollbars()
388
389 - def _yview(self, *command):
390 (i1, j1, i2, j2) = self.visible_area() 391 (imin, imax) = (self._imin, self._imax) 392 (jmin, jmax) = (self._jmin, self._jmax) 393 394 if command[0] == 'moveto': 395 f = 1.0-float(command[1]) - (j2-j1)/(jmax-jmin) 396 elif command[0] == 'scroll': 397 dir = -int(command[1]) 398 if command[2] == 'pages': 399 f = (j1-jmin)/(jmax-jmin) + dir*(j2-j1)/(jmax-jmin) 400 elif command[2] == 'units': 401 f = (j1-jmin)/(jmax-jmin) + dir*(j2-j1)/(10*(jmax-jmin)) 402 else: return 403 else: return 404 405 f = max(f, 0) 406 f = min(f, 1-(j2-j1)/(jmax-jmin)) 407 self.zoom(i1, jmin + f*(jmax-jmin), 408 i2, jmin + f*(jmax-jmin)+(j2-j1)) 409 self._set_scrollbars()
410
411 - def config_axes(self, xlog, ylog):
412 self._graph.xaxis_configure(logscale=xlog) 413 self._graph.yaxis_configure(logscale=ylog)
414
415 - def invtransform(self, x, y):
416 return self._graph.invtransform(x, y)
417
418 - def zoom(self, i1, j1, i2, j2):
419 self._graph.xaxis_configure(min=i1, max=i2) 420 self._graph.yaxis_configure(min=j1, max=j2) 421 self._set_scrollbars()
422
423 - def visible_area(self):
424 (i1, i2) = self._graph.xaxis_limits() 425 (j1, j2) = self._graph.yaxis_limits() 426 return (i1, j1, i2, j2)
427
428 - def create_zoom_marker(self):
429 self._graph.marker_create("line", name="zoom", dashes=(2, 2))
430
431 - def adjust_zoom_marker(self, press_x, press_y, release_x, release_y):
432 (i1, j1) = self._graph.invtransform(press_x, press_y) 433 (i2, j2) = self._graph.invtransform(release_x, release_y) 434 coords = (i1, j1, i2, j1, i2, j2, i1, j2, i1, j1) 435 self._graph.marker_configure("zoom", coords=coords)
436
437 - def delete_zoom_marker(self):
438 self._graph.marker_delete("zoom")
439
440 - def bind(self, *args): self._graph.bind(*args)
441 - def unbind(self, *args): self._graph.unbind(*args)
442
443 - def postscript(self, filename):
444 self._graph.postscript_output(filename)
445 446
447 -class Plot(object):
448 """ 449 A simple graphical tool for plotting functions. Each new C{Plot} 450 object opens a new window, containing the plot for a sinlge 451 function. Multiple plots in the same window are not (yet) 452 supported. The C{Plot} constructor supports several mechanisms 453 for defining the set of points to plot. 454 455 Example plots 456 ============= 457 Plot the math.sin function over the range [-10:10:.1]: 458 >>> import math 459 >>> Plot(math.sin) 460 461 Plot the math.sin function over the range [0:1:.001]: 462 >>> Plot(math.sin, slice(0, 1, .001)) 463 464 Plot a list of points: 465 >>> points = ([1,1], [3,8], [5,3], [6,12], [1,24]) 466 >>> Plot(points) 467 468 Plot a list of values, at x=0, x=1, x=2, ..., x=n: 469 >>> Plot(x**2 for x in range(20)) 470 """
471 - def __init__(self, vals, rng=None, **kwargs):
472 """ 473 Create a new C{Plot}. 474 475 @param vals: The set of values to plot. C{vals} can be a list 476 of y-values; a list of points; or a function. 477 @param rng: The range over which to plot. C{rng} can be a 478 list of x-values, or a slice object. If no range is 479 specified, a default range will be used. Note that C{rng} 480 may I{not} be specified if C{vals} is a list of points. 481 @keyword scale: The scales that should be used for the axes. 482 Possible values are: 483 - C{'linear'}: both axes are linear. 484 - C{'log-linear'}: The x axis is logarithmic; and the y 485 axis is linear. 486 - C{'linear-log'}: The x axis is linear; and the y axis 487 is logarithmic. 488 - C{'log'}: Both axes are logarithmic. 489 By default, C{scale} is C{'linear'}. 490 """ 491 # If range is a slice, then expand it to a list. 492 if type(rng) is SliceType: 493 (start, stop, step) = (rng.start, rng.stop, rng.step) 494 if step>0 and stop>start: 495 rng = [start] 496 i = 0 497 while rng[-1] < stop: 498 rng.append(start+i*step) 499 i += 1 500 elif step<0 and stop<start: 501 rng = [start] 502 i = 0 503 while rng[-1] > stop: 504 rng.append(start+i*step) 505 i += 1 506 else: 507 rng = [] 508 509 # If vals is a function, evaluate it over range. 510 if type(vals) in (FunctionType, BuiltinFunctionType, 511 MethodType): 512 if rng is None: rng = [x*0.1 for x in range(-100, 100)] 513 try: vals = [vals(i) for i in rng] 514 except TypeError: 515 raise TypeError, 'Bad range type: %s' % type(rng) 516 517 # If vals isn't a function, make sure it's a sequence: 518 elif type(vals) not in (ListType, TupleType): 519 raise ValueError, 'Bad values type: %s' % type(vals) 520 521 # If vals is a list of points, unzip it. 522 elif len(vals) > 0 and type(vals[0]) in (ListType, TupleType): 523 if rng is not None: 524 estr = "Can't specify a range when vals is a list of points." 525 raise ValueError, estr 526 (rng, vals) = zip(*vals) 527 528 # If vals & rng are both lists, make sure their lengths match. 529 elif type(rng) in (ListType, TupleType): 530 if len(rng) != len(vals): 531 estr = 'Range list and value list have different lengths.' 532 raise ValueError, estr 533 534 # If rng is unspecified, take it to be integers starting at zero 535 elif rng is None: 536 rng = range(len(vals)) 537 538 # If it's an unknown range type, then fail. 539 else: 540 raise TypeError, 'Bad range type: %s' % type(rng) 541 542 # Check that we have something to plot 543 if len(vals) == 0: 544 raise ValueError, 'Nothing to plot!' 545 546 # Set _rng/_vals 547 self._rng = rng 548 self._vals = vals 549 550 # Find max/min's. 551 self._imin = min(rng) 552 self._imax = max(rng) 553 if self._imax == self._imin: 554 self._imin -= 1 555 self._imax += 1 556 self._jmin = min(vals) 557 self._jmax = max(vals) 558 if self._jmax == self._jmin: 559 self._jmin -= 1 560 self._jmax += 1 561 562 # Do some basic error checking. 563 if len(self._rng) != len(self._vals): 564 raise ValueError("Rng and vals have different lengths") 565 if len(self._rng) == 0: 566 raise ValueError("Nothing to plot") 567 568 # Set up the tk window 569 self._root = Tkinter.Tk() 570 self._init_bindings(self._root) 571 572 # Create the actual plot frame 573 try: 574 self._plot = BLTPlotFrame(self._root, vals, rng) 575 except ImportError: 576 self._plot = CanvasPlotFrame(self._root, vals, rng) 577 578 # Set up the axes 579 self._ilog = Tkinter.IntVar(self._root); self._ilog.set(0) 580 self._jlog = Tkinter.IntVar(self._root); self._jlog.set(0) 581 scale = kwargs.get('scale', 'linear') 582 if scale in ('log-linear', 'log_linear', 'log'): self._ilog.set(1) 583 if scale in ('linear-log', 'linear_log', 'log'): self._jlog.set(1) 584 self._plot.config_axes(self._ilog.get(), self._jlog.get()) 585 586 ## Set up zooming 587 self._plot.bind("<ButtonPress-1>", self._zoom_in_buttonpress) 588 self._plot.bind("<ButtonRelease-1>", self._zoom_in_buttonrelease) 589 self._plot.bind("<ButtonPress-2>", self._zoom_out) 590 self._plot.bind("<ButtonPress-3>", self._zoom_out) 591 592 self._init_menubar(self._root)
593
594 - def _init_bindings(self, parent):
595 self._root.bind('<Control-q>', self.destroy) 596 self._root.bind('<Control-x>', self.destroy) 597 self._root.bind('<Control-p>', self.postscript) 598 self._root.bind('<Control-a>', self._zoom_all) 599 self._root.bind('<F1>', self.help)
600
601 - def _init_menubar(self, parent):
602 menubar = Tkinter.Menu(parent) 603 604 filemenu = Tkinter.Menu(menubar, tearoff=0) 605 filemenu.add_command(label='Print to Postscript', underline=0, 606 command=self.postscript, accelerator='Ctrl-p') 607 filemenu.add_command(label='Exit', underline=1, 608 command=self.destroy, accelerator='Ctrl-x') 609 menubar.add_cascade(label='File', underline=0, menu=filemenu) 610 611 zoommenu = Tkinter.Menu(menubar, tearoff=0) 612 zoommenu.add_command(label='Zoom in', underline=5, 613 command=self._zoom_in, accelerator='left click') 614 zoommenu.add_command(label='Zoom out', underline=5, 615 command=self._zoom_out, accelerator='right click') 616 zoommenu.add_command(label='View 100%', command=self._zoom_all, 617 accelerator='Ctrl-a') 618 menubar.add_cascade(label='Zoom', underline=0, menu=zoommenu) 619 620 axismenu = Tkinter.Menu(menubar, tearoff=0) 621 if self._imin > 0: xstate = 'normal' 622 else: xstate = 'disabled' 623 if self._jmin > 0: ystate = 'normal' 624 else: ystate = 'disabled' 625 axismenu.add_checkbutton(label='Log X axis', underline=4, 626 variable=self._ilog, state=xstate, 627 command=self._log) 628 axismenu.add_checkbutton(label='Log Y axis', underline=4, 629 variable=self._jlog, state=ystate, 630 command=self._log) 631 menubar.add_cascade(label='Axes', underline=0, menu=axismenu) 632 633 helpmenu = Tkinter.Menu(menubar, tearoff=0) 634 helpmenu.add_command(label='About', underline=0, 635 command=self.about) 636 helpmenu.add_command(label='Instructions', underline=0, 637 command=self.help, accelerator='F1') 638 menubar.add_cascade(label='Help', underline=0, menu=helpmenu) 639 640 parent.config(menu=menubar)
641
642 - def _log(self, *e):
643 self._plot.config_axes(self._ilog.get(), self._jlog.get())
644
645 - def about(self, *e):
646 """ 647 Dispaly an 'about' dialog window for the NLTK plot tool. 648 """ 649 ABOUT = ("NLTK Plot Tool\n" 650 "<http://nltk.sourceforge.net>") 651 TITLE = 'About: Plot Tool' 652 if isinstance(self._plot, BLTPlotFrame): 653 ABOUT += '\n\nBased on the BLT Widget' 654 try: 655 from tkMessageBox import Message 656 Message(message=ABOUT, title=TITLE).show() 657 except: 658 ShowText(self._root, TITLE, ABOUT)
659
660 - def help(self, *e):
661 """ 662 Display a help window. 663 """ 664 doc = __doc__.split('\n@', 1)[0].strip() 665 import re 666 doc = re.sub(r'[A-Z]{([^}<]*)(<[^>}]*>)?}', r'\1', doc) 667 self._autostep = 0 668 # The default font's not very legible; try using 'fixed' instead. 669 try: 670 ShowText(self._root, 'Help: Plot Tool', doc, 671 width=75, font='fixed') 672 except: 673 ShowText(self._root, 'Help: Plot Tool', doc, width=75)
674 675
676 - def postscript(self, *e):
677 """ 678 Print the (currently visible) contents of the plot window to a 679 postscript file. 680 """ 681 from tkFileDialog import asksaveasfilename 682 ftypes = [('Postscript files', '.ps'), 683 ('All files', '*')] 684 filename = asksaveasfilename(filetypes=ftypes, defaultextension='.ps') 685 if not filename: return 686 self._plot.postscript(filename)
687
688 - def destroy(self, *args):
689 """ 690 Cloase the plot window. 691 """ 692 if self._root is None: return 693 self._root.destroy() 694 self._root = None
695
696 - def mainloop(self, *varargs, **kwargs):
697 """ 698 Enter the mainloop for the window. This method must be called 699 if a Plot is constructed from a non-interactive Python program 700 (e.g., from a script); otherwise, the plot window will close 701 as soon se the script completes. 702 """ 703 if in_idle(): return 704 self._root.mainloop(*varargs, **kwargs)
705
706 - def _zoom(self, i1, j1, i2, j2):
707 # Make sure they're ordered correctly. 708 if i1 > i2: (i1,i2) = (i2,i1) 709 if j1 > j2: (j1,j2) = (j2,j1) 710 711 # Bounds checking: x 712 if i1 < self._imin: 713 i2 = min(self._imax, i2 + (self._imin - i1)) 714 i1 = self._imin 715 if i2 > self._imax: 716 i1 = max(self._imin, i1 - (i2 - self._imax)) 717 i2 = self._imax 718 719 # Bounds checking: y 720 if j1 < self._jmin: 721 j2 = min(self._jmax, j2 + self._jmin - j1) 722 j1 = self._jmin 723 if j2 > self._jmax: 724 j1 = max(self._jmin, j1 - (j2 - self._jmax)) 725 j2 = self._jmax 726 727 # Range size checking: 728 if i1 == i2: i2 += 1 729 if j1 == j2: j2 += 1 730 731 if self._ilog.get(): i1 = max(1e-100, i1) 732 if self._jlog.get(): j1 = max(1e-100, j1) 733 734 # Do the actual zooming. 735 self._plot.zoom(i1, j1, i2, j2)
736
737 - def _zoom_in_buttonpress(self, event):
738 self._press_x = event.x 739 self._press_y = event.y 740 self._press_time = time.time() 741 self._plot.create_zoom_marker() 742 self._bind_id = self._plot.bind("<Motion>", self._zoom_in_drag)
743
744 - def _zoom_in_drag(self, event):
745 self._plot.adjust_zoom_marker(self._press_x, self._press_y, 746 event.x, event.y)
747
748 - def _zoom_in_buttonrelease(self, event):
749 self._plot.delete_zoom_marker() 750 self._plot.unbind("<Motion>", self._bind_id) 751 if ((time.time() - self._press_time > 0.1) and 752 abs(self._press_x-event.x) + abs(self._press_y-event.y) > 5): 753 (i1, j1) = self._plot.invtransform(self._press_x, self._press_y) 754 (i2, j2) = self._plot.invtransform(event.x, event.y) 755 self._zoom(i1, j1, i2, j2) 756 else: 757 self._zoom_in()
758
759 - def _zoom_in(self, *e):
760 (i1, j1, i2, j2) = self._plot.visible_area() 761 di = (i2-i1)*0.1 762 dj = (j2-j1)*0.1 763 self._zoom(i1+di, j1+dj, i2-di, j2-dj)
764
765 - def _zoom_out(self, *e):
766 (i1, j1, i2, j2) = self._plot.visible_area() 767 di = -(i2-i1)*0.1 768 dj = -(j2-j1)*0.1 769 self._zoom(i1+di, j1+dj, i2-di, j2-dj)
770
771 - def _zoom_all(self, *e):
772 self._zoom(self._imin, self._jmin, self._imax, self._jmax)
773 774 775 if __name__ == '__main__': 776 from math import sin 777 #Plot(lambda v: sin(v)**2+0.01) 778 Plot(lambda x:abs(x**2-sin(20*x**3))+.1, 779 [0.01*x for x in range(1,100)], scale='log').mainloop() 780