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

Source Code for Module nltk.draw.tree

  1  # Natural Language Toolkit: Graphical Representations for Trees 
  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: tree.py 6265 2008-07-26 09:25:03Z stevenbird $ 
  9   
 10  """ 
 11  Graphically display a C{Tree}. 
 12  """ 
 13   
 14  from Tkinter import * 
 15  import sys 
 16   
 17  from nltk import tree 
 18   
 19  from nltk.draw import * 
 20   
 21  ##////////////////////////////////////////////////////// 
 22  ##  Tree Segment 
 23  ##////////////////////////////////////////////////////// 
 24   
25 -class TreeSegmentWidget(CanvasWidget):
26 """ 27 A canvas widget that displays a single segment of a hierarchical 28 tree. Each C{TreeSegmentWidget} connects a single X{node widget} 29 to a sequence of zero or more X{subtree widgets}. By default, the 30 bottom of the node is connected to the top of each subtree by a 31 single line. However, if the C{roof} attribute is set, then a 32 single triangular "roof" will connect the node to all of its 33 children. 34 35 Attributes: 36 - C{roof}: What sort of connection to draw between the node and 37 its subtrees. If C{roof} is true, draw a single triangular 38 "roof" over the subtrees. If C{roof} is false, draw a line 39 between each subtree and the node. Default value is false. 40 - C{xspace}: The amount of horizontal space to leave between 41 subtrees when managing this widget. Default value is 10. 42 - C{yspace}: The amount of space to place between the node and 43 its children when managing this widget. Default value is 15. 44 - C{color}: The color of the lines connecting the node to its 45 subtrees; and of the outline of the triangular roof. Default 46 value is C{'#006060'}. 47 - C{fill}: The fill color for the triangular roof. Default 48 value is C{''} (no fill). 49 - C{width}: The width of the lines connecting the node to its 50 subtrees; and of the outline of the triangular roof. Default 51 value is 1. 52 - C{orientation}: Determines whether the tree branches downwards 53 or rightwards. Possible values are C{'horizontal'} and 54 C{'vertical'}. The default value is C{'vertical'} (i.e., 55 branch downwards). 56 - C{draggable}: whether the widget can be dragged by the user. 57 58 The following attributes may also be added in the near future: 59 - C{lineM{n}_color}: The color of the line connecting the node 60 to its C{M{n}}th subtree. 61 - C{lineM{n}_color}: The width of the line connecting the node 62 to its C{M{n}}th subtree. 63 - C{lineM{n}_color}: The dash pattern of the line connecting the 64 node to its C{M{n}}th subtree. 65 66 """
67 - def __init__(self, canvas, node, subtrees, **attribs):
68 """ 69 @type node: 70 @type subtrees: C{list} of C{CanvasWidgetI} 71 """ 72 self._node = node 73 self._subtrees = subtrees 74 75 # Attributes 76 self._horizontal = 0 77 self._roof = 0 78 self._xspace = 10 79 self._yspace = 15 80 self._ordered = False 81 82 # Create canvas objects. 83 self._lines = [canvas.create_line(0,0,0,0, fill='#006060') 84 for c in subtrees] 85 self._polygon = canvas.create_polygon(0,0, fill='', state='hidden', 86 outline='#006060') 87 88 # Register child widgets (node + subtrees) 89 self._add_child_widget(node) 90 for subtree in subtrees: 91 self._add_child_widget(subtree) 92 93 # Are we currently managing? 94 self._managing = False 95 96 CanvasWidget.__init__(self, canvas, **attribs)
97
98 - def __setitem__(self, attr, value):
99 canvas = self.canvas() 100 if attr is 'roof': 101 self._roof = value 102 if self._roof: 103 for l in self._lines: canvas.itemconfig(l, state='hidden') 104 canvas.itemconfig(self._polygon, state='normal') 105 else: 106 for l in self._lines: canvas.itemconfig(l, state='normal') 107 canvas.itemconfig(self._polygon, state='hidden') 108 elif attr == 'orientation': 109 if value == 'horizontal': self._horizontal = 1 110 elif value == 'vertical': self._horizontal = 0 111 else: 112 raise ValueError('orientation must be horizontal or vertical') 113 elif attr == 'color': 114 for l in self._lines: canvas.itemconfig(l, fill=value) 115 canvas.itemconfig(self._polygon, outline=value) 116 elif isinstance(attr, tuple) and attr[0] == 'color': 117 # Set the color of an individual line. 118 l = self._lines[int(attr[1])] 119 canvas.itemconfig(l, fill=value) 120 elif attr == 'fill': 121 canvas.itemconfig(self._polygon, fill=value) 122 elif attr == 'width': 123 canvas.itemconfig(self._polygon, {attr:value}) 124 for l in self._lines: canvas.itemconfig(l, {attr:value}) 125 elif attr in ('xspace', 'yspace'): 126 if attr == 'xspace': self._xspace = value 127 elif attr == 'yspace': self._yspace = value 128 self.update(self._node) 129 elif attr == 'ordered': 130 self._ordered = value 131 else: 132 CanvasWidget.__setitem__(self, attr, value)
133
134 - def __getitem__(self, attr):
135 if attr == 'roof': return self._roof 136 elif attr == 'width': 137 return self.canvas().itemcget(self._polygon, attr) 138 elif attr == 'color': 139 return self.canvas().itemcget(self._polygon, 'outline') 140 elif isinstance(attr, tuple) and attr[0] == 'color': 141 l = self._lines[int(attr[1])] 142 return self.canvas().itemcget(l, 'fill') 143 elif attr == 'xspace': return self._xspace 144 elif attr == 'yspace': return self._yspace 145 elif attr == 'orientation': 146 if self._horizontal: return 'horizontal' 147 else: return 'vertical' 148 elif attr == 'ordered': 149 return self._ordered 150 else: 151 return CanvasWidget.__getitem__(self, attr)
152
153 - def node(self):
154 return self._node
155
156 - def subtrees(self):
157 return self._subtrees[:]
158
159 - def set_node(self, node):
160 """ 161 Set the node to C{node}. 162 """ 163 self._remove_child_widget(self._node) 164 self._add_child_widget(node) 165 self._node = node 166 self.update(self._node)
167
168 - def replace_child(self, oldchild, newchild):
169 """ 170 Replace the child C{oldchild} with C{newchild}. 171 """ 172 index = self._subtrees.index(oldchild) 173 self._subtrees[index] = newchild 174 self._remove_child_widget(oldchild) 175 self._add_child_widget(newchild) 176 self.update(newchild)
177
178 - def remove_child(self, child):
179 index = self._subtrees.index(child) 180 del self._subtrees[index] 181 self._remove_child_widget(child) 182 self.canvas().delete(self._lines.pop()) 183 self.update(self._node)
184
185 - def insert_child(self, index, child):
186 self._subtrees.insert(index, child) 187 self._add_child_widget(child) 188 self._lines.append(canvas.create_line(0,0,0,0, fill='#006060')) 189 self.update(self._node)
190 191 # but.. lines??? 192
193 - def _tags(self):
194 if self._roof: 195 return [self._polygon] 196 else: 197 return self._lines
198
199 - def _subtree_top(self, child):
200 if isinstance(child, TreeSegmentWidget): 201 bbox = child.node().bbox() 202 else: 203 bbox = child.bbox() 204 if self._horizontal: 205 return (bbox[0], (bbox[1]+bbox[3])/2.0) 206 else: 207 return ((bbox[0]+bbox[2])/2.0, bbox[1])
208
209 - def _node_bottom(self):
210 bbox = self._node.bbox() 211 if self._horizontal: 212 return (bbox[2], (bbox[1]+bbox[3])/2.0) 213 else: 214 return ((bbox[0]+bbox[2])/2.0, bbox[3])
215
216 - def _update(self, child):
217 if len(self._subtrees) == 0: return 218 if self._node.bbox() is None: return # [XX] ??? 219 220 # Which lines need to be redrawn? 221 if child is self._node: need_update = self._subtrees 222 else: need_update = [child] 223 224 if self._ordered and not self._managing: 225 need_update = self._maintain_order(child) 226 227 # Update the polygon. 228 (nodex, nodey) = self._node_bottom() 229 (xmin, ymin, xmax, ymax) = self._subtrees[0].bbox() 230 for subtree in self._subtrees[1:]: 231 bbox = subtree.bbox() 232 xmin = min(xmin, bbox[0]) 233 ymin = min(ymin, bbox[1]) 234 xmax = max(xmax, bbox[2]) 235 ymax = max(ymax, bbox[3]) 236 237 if self._horizontal: 238 self.canvas().coords(self._polygon, nodex, nodey, xmin, 239 ymin, xmin, ymax, nodex, nodey) 240 else: 241 self.canvas().coords(self._polygon, nodex, nodey, xmin, 242 ymin, xmax, ymin, nodex, nodey) 243 244 # Redraw all lines that need it. 245 for subtree in need_update: 246 (nodex, nodey) = self._node_bottom() 247 line = self._lines[self._subtrees.index(subtree)] 248 (subtreex, subtreey) = self._subtree_top(subtree) 249 self.canvas().coords(line, nodex, nodey, subtreex, subtreey)
250
251 - def _maintain_order(self, child):
252 if self._horizontal: 253 return self._maintain_order_horizontal(child) 254 else: 255 return self._maintain_order_vertical(child)
256
257 - def _maintain_order_vertical(self, child):
258 (left, top, right, bot) = child.bbox() 259 260 if child is self._node: 261 # Check all the leaves 262 for subtree in self._subtrees: 263 (x1, y1, x2, y2) = subtree.bbox() 264 if bot+self._yspace > y1: 265 subtree.move(0,bot+self._yspace-y1) 266 267 return self._subtrees 268 else: 269 moved = [child] 270 index = self._subtrees.index(child) 271 272 # Check leaves to our right. 273 x = right + self._xspace 274 for i in range(index+1, len(self._subtrees)): 275 (x1, y1, x2, y2) = self._subtrees[i].bbox() 276 if x > x1: 277 self._subtrees[i].move(x-x1, 0) 278 x += x2-x1 + self._xspace 279 moved.append(self._subtrees[i]) 280 281 # Check leaves to our left. 282 x = left - self._xspace 283 for i in range(index-1, -1, -1): 284 (x1, y1, x2, y2) = self._subtrees[i].bbox() 285 if x < x2: 286 self._subtrees[i].move(x-x2, 0) 287 x -= x2-x1 + self._xspace 288 moved.append(self._subtrees[i]) 289 290 # Check the node 291 (x1, y1, x2, y2) = self._node.bbox() 292 if y2 > top-self._yspace: 293 self._node.move(0, top-self._yspace-y2) 294 moved = self._subtrees 295 296 # Return a list of the nodes we moved 297 return moved
298
299 - def _maintain_order_horizontal(self, child):
300 (left, top, right, bot) = child.bbox() 301 302 if child is self._node: 303 # Check all the leaves 304 for subtree in self._subtrees: 305 (x1, y1, x2, y2) = subtree.bbox() 306 if right+self._xspace > x1: 307 subtree.move(right+self._xspace-x1) 308 309 return self._subtrees 310 else: 311 moved = [child] 312 index = self._subtrees.index(child) 313 314 # Check leaves below us. 315 y = bot + self._yspace 316 for i in range(index+1, len(self._subtrees)): 317 (x1, y1, x2, y2) = self._subtrees[i].bbox() 318 if y > y1: 319 self._subtrees[i].move(0, y-y1) 320 y += y2-y1 + self._yspace 321 moved.append(self._subtrees[i]) 322 323 # Check leaves above us 324 y = top - self._yspace 325 for i in range(index-1, -1, -1): 326 (x1, y1, x2, y2) = self._subtrees[i].bbox() 327 if y < y2: 328 self._subtrees[i].move(0, y-y2) 329 y -= y2-y1 + self._yspace 330 moved.append(self._subtrees[i]) 331 332 # Check the node 333 (x1, y1, x2, y2) = self._node.bbox() 334 if x2 > left-self._xspace: 335 self._node.move(left-self._xspace-x2, 0) 336 moved = self._subtrees 337 338 # Return a list of the nodes we moved 339 return moved
340
341 - def _manage_horizontal(self):
342 (nodex, nodey) = self._node_bottom() 343 344 # Put the subtrees in a line. 345 y = 20 346 for subtree in self._subtrees: 347 subtree_bbox = subtree.bbox() 348 dx = nodex - subtree_bbox[0] + self._xspace 349 dy = y - subtree_bbox[1] 350 subtree.move(dx, dy) 351 y += subtree_bbox[3] - subtree_bbox[1] + self._yspace 352 353 # Find the center of their tops. 354 center = 0.0 355 for subtree in self._subtrees: 356 center += self._subtree_top(subtree)[1] 357 center /= len(self._subtrees) 358 359 # Center the subtrees with the node. 360 for subtree in self._subtrees: 361 subtree.move(0, nodey-center)
362
363 - def _manage_vertical(self):
364 (nodex, nodey) = self._node_bottom() 365 366 # Put the subtrees in a line. 367 x = 0 368 for subtree in self._subtrees: 369 subtree_bbox = subtree.bbox() 370 dy = nodey - subtree_bbox[1] + self._yspace 371 dx = x - subtree_bbox[0] 372 subtree.move(dx, dy) 373 x += subtree_bbox[2] - subtree_bbox[0] + self._xspace 374 375 # Find the center of their tops. 376 center = 0.0 377 for subtree in self._subtrees: 378 center += self._subtree_top(subtree)[0]/len(self._subtrees) 379 380 # Center the subtrees with the node. 381 for subtree in self._subtrees: 382 subtree.move(nodex-center, 0)
383
384 - def _manage(self):
385 self._managing = True 386 (nodex, nodey) = self._node_bottom() 387 if len(self._subtrees) == 0: return 388 389 if self._horizontal: self._manage_horizontal() 390 else: self._manage_vertical() 391 392 # Update lines to subtrees. 393 for subtree in self._subtrees: 394 self._update(subtree) 395 396 self._managing = False
397
398 - def __repr__(self):
399 return '[TreeSeg %s: %s]' % (self._node, self._subtrees)
400
401 -def _tree_to_treeseg(canvas, t, make_node, make_leaf, 402 tree_attribs, node_attribs, 403 leaf_attribs, loc_attribs):
404 if isinstance(t, tree.Tree): 405 node = make_node(canvas, t.node, **node_attribs) 406 subtrees = [_tree_to_treeseg(canvas, child, make_node, make_leaf, 407 tree_attribs, node_attribs, 408 leaf_attribs, loc_attribs) 409 for child in t] 410 return TreeSegmentWidget(canvas, node, subtrees, **tree_attribs) 411 else: 412 return make_leaf(canvas, t, **leaf_attribs)
413
414 -def tree_to_treesegment(canvas, t, make_node=TextWidget, 415 make_leaf=TextWidget, **attribs):
416 """ 417 Convert a C{Tree} into a C{TreeSegmentWidget}. 418 419 @param make_node: A C{CanvasWidget} constructor or a function that 420 creates C{CanvasWidgets}. C{make_node} is used to convert 421 the C{Tree}'s nodes into C{CanvasWidgets}. If no constructor 422 is specified, then C{TextWidget} will be used. 423 @param make_leaf: A C{CanvasWidget} constructor or a function that 424 creates C{CanvasWidgets}. C{make_leaf} is used to convert 425 the C{Tree}'s leafs into C{CanvasWidgets}. If no constructor 426 is specified, then C{TextWidget} will be used. 427 @param attribs: Attributes for the canvas widgets that make up the 428 returned C{TreeSegmentWidget}. Any attribute beginning with 429 C{'tree_'} will be passed to all C{TreeSegmentWidget}s (with 430 the C{'tree_'} prefix removed. Any attribute beginning with 431 C{'node_'} will be passed to all nodes. Any attribute 432 beginning with C{'leaf_'} will be passed to all leaves. And 433 any attribute beginning with C{'loc_'} will be passed to all 434 text locations (for C{Tree}s). 435 """ 436 # Process attribs. 437 tree_attribs = {} 438 node_attribs = {} 439 leaf_attribs = {} 440 loc_attribs = {} 441 442 for (key, value) in attribs.items(): 443 if key[:5] == 'tree_': tree_attribs[key[5:]] = value 444 elif key[:5] == 'node_': node_attribs[key[5:]] = value 445 elif key[:5] == 'leaf_': leaf_attribs[key[5:]] = value 446 elif key[:4] == 'loc_': loc_attribs[key[4:]] = value 447 else: raise ValueError('Bad attribute: %s' % key) 448 return _tree_to_treeseg(canvas, t, make_node, make_leaf, 449 tree_attribs, node_attribs, 450 leaf_attribs, loc_attribs)
451 452 ##////////////////////////////////////////////////////// 453 ## Tree Widget 454 ##////////////////////////////////////////////////////// 455
456 -class TreeWidget(CanvasWidget):
457 """ 458 A canvas widget that displays a single C{Tree}. 459 C{TreeWidget} manages a group of C{TreeSegmentWidget}s that are 460 used to display a C{Tree}. 461 462 Attributes: 463 464 - C{node_M{attr}}: Sets the attribute C{M{attr}} on all of the 465 node widgets for this C{TreeWidget}. 466 - C{node_M{attr}}: Sets the attribute C{M{attr}} on all of the 467 leaf widgets for this C{TreeWidget}. 468 - C{loc_M{attr}}: Sets the attribute C{M{attr}} on all of the 469 location widgets for this C{TreeWidget} (if it was built from 470 a C{Tree}). Note that location widgets are 471 C{TextWidget}s. 472 473 - C{xspace}: The amount of horizontal space to leave between 474 subtrees when managing this widget. Default value is 10. 475 - C{yspace}: The amount of space to place between the node and 476 its children when managing this widget. Default value is 15. 477 478 - C{line_color}: The color of the lines connecting each expanded 479 node to its subtrees. 480 - C{roof_color}: The color of the outline of the triangular roof 481 for collapsed trees. 482 - C{roof_fill}: The fill color for the triangular roof for 483 collapsed trees. 484 - C{width} 485 486 - C{orientation}: Determines whether the tree branches downwards 487 or rightwards. Possible values are C{'horizontal'} and 488 C{'vertical'}. The default value is C{'vertical'} (i.e., 489 branch downwards). 490 491 - C{shapeable}: whether the subtrees can be independantly 492 dragged by the user. THIS property simply sets the 493 C{DRAGGABLE} property on all of the C{TreeWidget}'s tree 494 segments. 495 - C{draggable}: whether the widget can be dragged by the user. 496 """
497 - def __init__(self, canvas, t, make_node=TextWidget, 498 make_leaf=TextWidget, **attribs):
499 # Node & leaf canvas widget constructors 500 self._make_node = make_node 501 self._make_leaf = make_leaf 502 self._tree = t 503 504 # Attributes. 505 self._nodeattribs = {} 506 self._leafattribs = {} 507 self._locattribs = {'color': '#008000'} 508 self._line_color = '#008080' 509 self._line_width = 1 510 self._roof_color = '#008080' 511 self._roof_fill = '#c0c0c0' 512 self._shapeable = False 513 self._xspace = 10 514 self._yspace = 10 515 self._orientation = 'vertical' 516 self._ordered = False 517 518 # Build trees. 519 self._keys = {} # treeseg -> key 520 self._expanded_trees = {} 521 self._collapsed_trees = {} 522 self._nodes = [] 523 self._leaves = [] 524 #self._locs = [] 525 self._make_collapsed_trees(canvas, t, ()) 526 self._treeseg = self._make_expanded_tree(canvas, t, ()) 527 self._add_child_widget(self._treeseg) 528 529 CanvasWidget.__init__(self, canvas, **attribs)
530
531 - def expanded_tree(self, *path_to_tree):
532 """ 533 Return the C{TreeSegmentWidget} for the specified subtree. 534 535 @param path_to_tree: A list of indices i1, i2, ..., in, where 536 the desired widget is the widget corresponding to 537 C{tree.children()[i1].children()[i2]....children()[in]}. 538 For the root, the path is C{()}. 539 """ 540 return self._expanded_trees[path_to_tree]
541
542 - def collapsed_tree(self, *path_to_tree):
543 """ 544 Return the C{TreeSegmentWidget} for the specified subtree. 545 546 @param path_to_tree: A list of indices i1, i2, ..., in, where 547 the desired widget is the widget corresponding to 548 C{tree.children()[i1].children()[i2]....children()[in]}. 549 For the root, the path is C{()}. 550 """ 551 return self._collapsed_trees[path_to_tree]
552
553 - def bind_click_trees(self, callback, button=1):
554 """ 555 Add a binding to all tree segments. 556 """ 557 for tseg in self._expanded_trees.values(): 558 tseg.bind_click(callback, button) 559 for tseg in self._collapsed_trees.values(): 560 tseg.bind_click(callback, button)
561
562 - def bind_drag_trees(self, callback, button=1):
563 """ 564 Add a binding to all tree segments. 565 """ 566 for tseg in self._expanded_trees.values(): 567 tseg.bind_drag(callback, button) 568 for tseg in self._collapsed_trees.values(): 569 tseg.bind_drag(callback, button)
570
571 - def bind_click_leaves(self, callback, button=1):
572 """ 573 Add a binding to all leaves. 574 """ 575 for leaf in self._leaves: leaf.bind_click(callback, button) 576 for leaf in self._leaves: leaf.bind_click(callback, button)
577
578 - def bind_drag_leaves(self, callback, button=1):
579 """ 580 Add a binding to all leaves. 581 """ 582 for leaf in self._leaves: leaf.bind_drag(callback, button) 583 for leaf in self._leaves: leaf.bind_drag(callback, button)
584
585 - def bind_click_nodes(self, callback, button=1):
586 """ 587 Add a binding to all nodes. 588 """ 589 for node in self._nodes: node.bind_click(callback, button) 590 for node in self._nodes: node.bind_click(callback, button)
591
592 - def bind_drag_nodes(self, callback, button=1):
593 """ 594 Add a binding to all nodes. 595 """ 596 for node in self._nodes: node.bind_drag(callback, button) 597 for node in self._nodes: node.bind_drag(callback, button)
598
599 - def _make_collapsed_trees(self, canvas, t, key):
600 if not isinstance(t, tree.Tree): return 601 make_node = self._make_node 602 make_leaf = self._make_leaf 603 604 node = make_node(canvas, t.node, **self._nodeattribs) 605 self._nodes.append(node) 606 leaves = [make_leaf(canvas, l, **self._leafattribs) 607 for l in t.leaves()] 608 self._leaves += leaves 609 treeseg = TreeSegmentWidget(canvas, node, leaves, roof=1, 610 color=self._roof_color, 611 fill=self._roof_fill, 612 width=self._line_width) 613 614 self._collapsed_trees[key] = treeseg 615 self._keys[treeseg] = key 616 #self._add_child_widget(treeseg) 617 treeseg.hide() 618 619 # Build trees for children. 620 for i in range(len(t)): 621 child = t[i] 622 self._make_collapsed_trees(canvas, child, key + (i,))
623
624 - def _make_expanded_tree(self, canvas, t, key):
625 make_node = self._make_node 626 make_leaf = self._make_leaf 627 628 if isinstance(t, tree.Tree): 629 node = make_node(canvas, t.node, **self._nodeattribs) 630 self._nodes.append(node) 631 children = t 632 subtrees = [self._make_expanded_tree(canvas, children[i], key+(i,)) 633 for i in range(len(children))] 634 treeseg = TreeSegmentWidget(canvas, node, subtrees, 635 color=self._line_color, 636 width=self._line_width) 637 self._expanded_trees[key] = treeseg 638 self._keys[treeseg] = key 639 return treeseg 640 else: 641 leaf = make_leaf(canvas, t, **self._leafattribs) 642 self._leaves.append(leaf) 643 return leaf
644
645 - def __setitem__(self, attr, value):
646 if attr[:5] == 'node_': 647 for node in self._nodes: node[attr[5:]] = value 648 elif attr[:5] == 'leaf_': 649 for leaf in self._leaves: leaf[attr[5:]] = value 650 elif attr == 'line_color': 651 self._line_color = value 652 for tseg in self._expanded_trees.values(): tseg['color'] = value 653 elif attr == 'line_width': 654 self._line_width = value 655 for tseg in self._expanded_trees.values(): tseg['width'] = value 656 for tseg in self._collapsed_trees.values(): tseg['width'] = value 657 elif attr == 'roof_color': 658 self._roof_color = value 659 for tseg in self._collapsed_trees.values(): tseg['color'] = value 660 elif attr == 'roof_fill': 661 self._roof_fill = value 662 for tseg in self._collapsed_trees.values(): tseg['fill'] = value 663 elif attr == 'shapeable': 664 self._shapeable = value 665 for tseg in self._expanded_trees.values(): 666 tseg['draggable'] = value 667 for tseg in self._collapsed_trees.values(): 668 tseg['draggable'] = value 669 for leaf in self._leaves: leaf['draggable'] = value 670 elif attr == 'xspace': 671 self._xspace = value 672 for tseg in self._expanded_trees.values(): 673 tseg['xspace'] = value 674 for tseg in self._collapsed_trees.values(): 675 tseg['xspace'] = value 676 self.manage() 677 elif attr == 'yspace': 678 self._yspace = value 679 for tseg in self._expanded_trees.values(): 680 tseg['yspace'] = value 681 for tseg in self._collapsed_trees.values(): 682 tseg['yspace'] = value 683 self.manage() 684 elif attr == 'orientation': 685 self._orientation = value 686 for tseg in self._expanded_trees.values(): 687 tseg['orientation'] = value 688 for tseg in self._collapsed_trees.values(): 689 tseg['orientation'] = value 690 self.manage() 691 elif attr == 'ordered': 692 self._ordered = value 693 for tseg in self._expanded_trees.values(): 694 tseg['ordered'] = value 695 for tseg in self._collapsed_trees.values(): 696 tseg['ordered'] = value 697 else: CanvasWidget.__setitem__(self, attr, value)
698
699 - def __getitem__(self, attr):
700 if attr[:5] == 'node_': 701 return self._nodeattribs.get(attr[5:], None) 702 elif attr[:5] == 'leaf_': 703 return self._leafattribs.get(attr[5:], None) 704 elif attr[:4] == 'loc_': 705 return self._locattribs.get(attr[4:], None) 706 elif attr == 'line_color': return self._line_color 707 elif attr == 'line_width': return self._line_width 708 elif attr == 'roof_color': return self._roof_color 709 elif attr == 'roof_fill': return self._roof_fill 710 elif attr == 'shapeable': return self._shapeable 711 elif attr == 'xspace': return self._xspace 712 elif attr == 'yspace': return self._yspace 713 elif attr == 'orientation': return self._orientation 714 else: return CanvasWidget.__getitem__(self, attr)
715
716 - def _tags(self): return []
717
718 - def _manage(self):
719 segs = self._expanded_trees.values() + self._collapsed_trees.values() 720 for tseg in segs: 721 if tseg.hidden(): 722 tseg.show() 723 tseg.manage() 724 tseg.hide()
725
726 - def toggle_collapsed(self, treeseg):
727 """ 728 Collapse/expand a tree. 729 """ 730 old_treeseg = treeseg 731 if old_treeseg['roof']: 732 new_treeseg = self._expanded_trees[self._keys[old_treeseg]] 733 else: 734 new_treeseg = self._collapsed_trees[self._keys[old_treeseg]] 735 736 # Replace the old tree with the new tree. 737 if old_treeseg.parent() is self: 738 self._remove_child_widget(old_treeseg) 739 self._add_child_widget(new_treeseg) 740 self._treeseg = new_treeseg 741 else: 742 old_treeseg.parent().replace_child(old_treeseg, new_treeseg) 743 744 # Move the new tree to where the old tree was. Show it first, 745 # so we can find its bounding box. 746 new_treeseg.show() 747 (newx, newy) = new_treeseg.node().bbox()[:2] 748 (oldx, oldy) = old_treeseg.node().bbox()[:2] 749 new_treeseg.move(oldx-newx, oldy-newy) 750 751 # Hide the old tree 752 old_treeseg.hide() 753 754 # We could do parent.manage() here instead, if we wanted. 755 new_treeseg.parent().update(new_treeseg)
756 757 ##////////////////////////////////////////////////////// 758 ## draw_trees 759 ##////////////////////////////////////////////////////// 760
761 -class TreeView(object):
762 - def __init__(self, *trees):
763 from nltk.draw import CanvasFrame 764 from math import sqrt, ceil 765 766 self._trees = trees 767 768 self._top = Tk() 769 self._top.title('NLTK') 770 self._top.bind('<Control-x>', self.destroy) 771 self._top.bind('<Control-q>', self.destroy) 772 773 cf = self._cframe = CanvasFrame(self._top) 774 self._top.bind('<Control-p>', self._cframe.print_to_file) 775 776 # Size is variable. 777 self._size = IntVar(self._top) 778 self._size.set(12) 779 bold = ('helvetica', -self._size.get(), 'bold') 780 helv = ('helvetica', -self._size.get()) 781 782 # Lay the trees out in a square. 783 self._width = int(ceil(sqrt(len(trees)))) 784 self._widgets = [] 785 for i in range(len(trees)): 786 widget = TreeWidget(cf.canvas(), trees[i], node_font=bold, 787 leaf_color='#008040', node_color='#004080', 788 roof_color='#004040', roof_fill='white', 789 line_color='#004040', draggable=1, 790 leaf_font=helv) 791 widget.bind_click_trees(widget.toggle_collapsed) 792 self._widgets.append(widget) 793 cf.add_widget(widget, 0, 0) 794 795 self._layout() 796 self._cframe.pack(expand=1, fill='both') 797 self._init_menubar()
798
799 - def _layout(self):
800 i = x = y = ymax = 0 801 width = self._width 802 for i in range(len(self._widgets)): 803 widget = self._widgets[i] 804 (oldx, oldy) = widget.bbox()[:2] 805 if i % width == 0: 806 y = ymax 807 x = 0 808 widget.move(x-oldx, y-oldy) 809 x = widget.bbox()[2] + 10 810 ymax = max(ymax, widget.bbox()[3] + 10)
811
812 - def _init_menubar(self):
813 menubar = Menu(self._top) 814 815 filemenu = Menu(menubar, tearoff=0) 816 filemenu.add_command(label='Print to Postscript', underline=0, 817 command=self._cframe.print_to_file, 818 accelerator='Ctrl-p') 819 filemenu.add_command(label='Exit', underline=1, 820 command=self.destroy, accelerator='Ctrl-x') 821 menubar.add_cascade(label='File', underline=0, menu=filemenu) 822 823 zoommenu = Menu(menubar, tearoff=0) 824 zoommenu.add_radiobutton(label='Tiny', variable=self._size, 825 underline=0, value=10, command=self.resize) 826 zoommenu.add_radiobutton(label='Small', variable=self._size, 827 underline=0, value=12, command=self.resize) 828 zoommenu.add_radiobutton(label='Medium', variable=self._size, 829 underline=0, value=14, command=self.resize) 830 zoommenu.add_radiobutton(label='Large', variable=self._size, 831 underline=0, value=28, command=self.resize) 832 zoommenu.add_radiobutton(label='Huge', variable=self._size, 833 underline=0, value=50, command=self.resize) 834 menubar.add_cascade(label='Zoom', underline=0, menu=zoommenu) 835 836 self._top.config(menu=menubar)
837
838 - def resize(self, *e):
839 bold = ('helvetica', -self._size.get(), 'bold') 840 helv = ('helvetica', -self._size.get()) 841 xspace = self._size.get() 842 yspace = self._size.get() 843 for widget in self._widgets: 844 widget['node_font'] = bold 845 widget['leaf_font'] = helv 846 widget['xspace'] = xspace 847 widget['yspace'] = yspace 848 if self._size.get() < 20: widget['line_width'] = 1 849 elif self._size.get() < 30: widget['line_width'] = 2 850 else: widget['line_width'] = 3 851 self._layout()
852
853 - def destroy(self, *e):
854 if self._top is None: return 855 self._top.destroy() 856 self._top = None
857
858 - def mainloop(self, *args, **kwargs):
859 """ 860 Enter the Tkinter mainloop. This function must be called if 861 this demo is created from a non-interactive program (e.g. 862 from a secript); otherwise, the demo will close as soon as 863 the script completes. 864 """ 865 if in_idle(): return 866 self._top.mainloop(*args, **kwargs)
867
868 -def draw_trees(*trees):
869 """ 870 Open a new window containing a graphical diagram of the given 871 trees. 872 873 @rtype: None 874 """ 875 TreeView(*trees).mainloop() 876 return
877 878 ##////////////////////////////////////////////////////// 879 ## Demo Code 880 ##////////////////////////////////////////////////////// 881
882 -def demo():
883 import random 884 def fill(cw): 885 cw['fill'] = '#%06d' % random.randint(0,999999)
886 887 cf = CanvasFrame(width=550, height=450, closeenough=2) 888 889 t = tree.bracket_parse(''' 890 (S (NP the very big cat) 891 (VP (Adv sorta) (V saw) (NP (Det the) (N dog))))''') 892 893 tc = TreeWidget(cf.canvas(), t, draggable=1, 894 node_font=('helvetica', -14, 'bold'), 895 leaf_font=('helvetica', -12, 'italic'), 896 roof_fill='white', roof_color='black', 897 leaf_color='green4', node_color='blue2') 898 cf.add_widget(tc,10,10) 899 900 def boxit(canvas, text): 901 big = ('helvetica', -16, 'bold') 902 return BoxWidget(canvas, TextWidget(canvas, text, 903 font=big), fill='green') 904 def ovalit(canvas, text): 905 return OvalWidget(canvas, TextWidget(canvas, text), 906 fill='cyan') 907 908 treetok = tree.bracket_parse('(S (NP this tree) (VP (V is) (AdjP shapeable)))') 909 tc2 = TreeWidget(cf.canvas(), treetok, boxit, ovalit, shapeable=1) 910 911 def color(node): 912 node['color'] = '#%04d00' % random.randint(0,9999) 913 def color2(treeseg): 914 treeseg.node()['fill'] = '#%06d' % random.randint(0,9999) 915 treeseg.node().child()['color'] = 'white' 916 917 tc.bind_click_trees(tc.toggle_collapsed) 918 tc2.bind_click_trees(tc2.toggle_collapsed) 919 tc.bind_click_nodes(color, 3) 920 tc2.expanded_tree(1).bind_click(color2, 3) 921 tc2.expanded_tree().bind_click(color2, 3) 922 923 paren = ParenWidget(cf.canvas(), tc2) 924 cf.add_widget(paren, tc.bbox()[2]+10, 10) 925 926 tree3 = tree.bracket_parse(''' 927 (S (NP this tree) (AUX was) 928 (VP (V built) (PP (P with) (NP (N tree_to_treesegment)))))''') 929 tc3 = tree_to_treesegment(cf.canvas(), tree3, tree_color='green4', 930 tree_xspace=2, tree_width=2) 931 tc3['draggable'] = 1 932 cf.add_widget(tc3, 10, tc.bbox()[3]+10) 933 934 def orientswitch(treewidget): 935 if treewidget['orientation'] == 'horizontal': 936 treewidget.expanded_tree(1,1).subtrees()[0].set_text('vertical') 937 treewidget.collapsed_tree(1,1).subtrees()[0].set_text('vertical') 938 treewidget.collapsed_tree(1).subtrees()[1].set_text('vertical') 939 treewidget.collapsed_tree().subtrees()[3].set_text('vertical') 940 treewidget['orientation'] = 'vertical' 941 else: 942 treewidget.expanded_tree(1,1).subtrees()[0].set_text('horizontal') 943 treewidget.collapsed_tree(1,1).subtrees()[0].set_text('horizontal') 944 treewidget.collapsed_tree(1).subtrees()[1].set_text('horizontal') 945 treewidget.collapsed_tree().subtrees()[3].set_text('horizontal') 946 treewidget['orientation'] = 'horizontal' 947 948 text = """ 949 Try clicking, right clicking, and dragging 950 different elements of each of the trees. 951 The top-left tree is a TreeWidget built from 952 a Tree. The top-right is a TreeWidget built 953 from a Tree, using non-default widget 954 constructors for the nodes & leaves (BoxWidget 955 and OvalWidget). The bottom-left tree is 956 built from tree_to_treesegment.""" 957 twidget = TextWidget(cf.canvas(), text.strip()) 958 textbox = BoxWidget(cf.canvas(), twidget, fill='white', draggable=1) 959 cf.add_widget(textbox, tc3.bbox()[2]+10, tc2.bbox()[3]+10) 960 961 tree4 = tree.bracket_parse('(S (NP this tree) (VP (V is) (Adj horizontal)))') 962 tc4 = TreeWidget(cf.canvas(), tree4, draggable=1, 963 line_color='brown2', roof_color='brown2', 964 node_font=('helvetica', -12, 'bold'), 965 node_color='brown4', orientation='horizontal') 966 tc4.manage() 967 cf.add_widget(tc4, tc3.bbox()[2]+10, textbox.bbox()[3]+10) 968 tc4.bind_click(orientswitch) 969 tc4.bind_click_trees(tc4.toggle_collapsed, 3) 970 971 # Run mainloop 972 cf.mainloop() 973 974 if __name__ == '__main__': 975 demo() 976