| Home | Trees | Indices | Help |
|
|---|
|
|
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
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 """
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
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
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
155
158
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
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
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
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
198
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
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
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
252 if self._horizontal:
253 return self._maintain_order_horizontal(child)
254 else:
255 return self._maintain_order_vertical(child)
256
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
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
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
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
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
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
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
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 """
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
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
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
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
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
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
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
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
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
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
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
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
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
717
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
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
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
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
837
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
857
867
877
878 ##//////////////////////////////////////////////////////
879 ## Demo Code
880 ##//////////////////////////////////////////////////////
881
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
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0beta1 on Wed Aug 27 15:09:18 2008 | http://epydoc.sourceforge.net |