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

Source Code for Module nltk_lite.draw.plot

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