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