1
14 package demo.view.layout.tree;
15
16 import demo.view.DemoBase;
17 import y.algo.GraphConnectivity;
18 import y.algo.Trees;
19 import y.anim.AnimationFactory;
20 import y.anim.AnimationObject;
21 import y.anim.AnimationPlayer;
22 import y.base.DataMap;
23 import y.base.Edge;
24 import y.base.EdgeCursor;
25 import y.base.EdgeList;
26 import y.base.Node;
27 import y.base.NodeCursor;
28 import y.base.NodeList;
29 import y.base.NodeMap;
30 import y.geom.YPoint;
31 import y.layout.BufferedLayouter;
32 import y.layout.GraphLayout;
33 import y.layout.LayoutOrientation;
34 import y.layout.NodeLayout;
35 import y.layout.hierarchic.IncrementalHierarchicLayouter;
36 import y.layout.hierarchic.incremental.IncrementalHintsFactory;
37 import y.layout.hierarchic.incremental.SimplexNodePlacer;
38 import y.layout.organic.OrganicLayouter;
39 import y.layout.tree.BalloonLayouter;
40 import y.layout.tree.TreeLayouter;
41 import y.layout.tree.XCoordComparator;
42 import y.util.DefaultMutableValue2D;
43 import y.util.Maps;
44 import y.view.EditMode;
45 import y.view.Graph2D;
46 import y.view.LayoutMorpher;
47 import y.view.NodeLabel;
48 import y.view.NodeRealizer;
49 import y.view.ViewAnimationFactory;
50 import y.view.ViewMode;
51 import y.view.hierarchy.GroupNodeRealizer;
52
53 import javax.swing.AbstractAction;
54 import javax.swing.ButtonGroup;
55 import javax.swing.JToggleButton;
56 import javax.swing.JToolBar;
57 import javax.swing.SwingUtilities;
58 import java.awt.Insets;
59 import java.awt.event.ActionEvent;
60 import java.awt.event.MouseEvent;
61 import java.awt.geom.Point2D;
62 import java.util.WeakHashMap;
63
64
69 public class CollapsibleTreeDemo extends DemoBase {
70 public static final byte STYLE_TREE = 1;
71 public static final byte STYLE_BALLOON = 2;
72 private static final byte STYLE_ORGANIC = 3;
73 private static final byte STYLE_HIERARCHIC = 4;
74
75 private byte style = STYLE_TREE;
76 private TreeLayouter treeLayouter;
77 private BalloonLayouter balloonLayouter;
78 private OrganicLayouter organicLayouter;
79 private IncrementalHierarchicLayouter hierarchicLayouter;
80 private CollapsibleTreeDemo.CollapseExpandViewMode viewMode;
81 private DataMap ihlHintMap;
82 private IncrementalHintsFactory hintsFactory;
83
84 public CollapsibleTreeDemo() {
85 Graph2D graph = view.getGraph2D();
86
87 NodeRealizer nr = graph.getDefaultNodeRealizer();
90 NodeLabel nl = nr.createNodeLabel();
91 nr.addLabel(nl);
92 nl.setIcon(GroupNodeRealizer.defaultOpenGroupIcon);
93 nl.setIconTextGap((byte) 0);
94 nl.setPosition(NodeLabel.TOP_RIGHT);
95 nl.setInsets(new Insets(2, 2, 2, 2));
96 nl.setDistance(0);
97
98 createTree(graph);
100
101 viewMode.collapseSubtree(graph, Trees.getRoot(graph));
103 Node root = Trees.getRoot(graph);
104 viewMode.expandSubtree(graph, root, 2);
105
106 treeLayouter = new TreeLayouter();
108 treeLayouter.setComparator(new XCoordComparator()); treeLayouter.setLayoutOrientation(LayoutOrientation.LEFT_TO_RIGHT);
110 treeLayouter.setLayoutStyle(TreeLayouter.ORTHOGONAL_STYLE);
111
112 balloonLayouter = new BalloonLayouter();
113 balloonLayouter.setFromSketchModeEnabled(true);
114 balloonLayouter.setCompactnessFactor(0.1);
115 balloonLayouter.setAllowOverlaps(true);
116
117 organicLayouter = new OrganicLayouter();
118 organicLayouter.setInitialPlacement(OrganicLayouter.AS_IS);
119
120 hierarchicLayouter = new IncrementalHierarchicLayouter();
121 hierarchicLayouter.getEdgeLayoutDescriptor().setOrthogonallyRouted(true);
122 hierarchicLayouter.setLayoutOrientation(LayoutOrientation.TOP_TO_BOTTOM);
123 hierarchicLayouter.setLayoutMode(IncrementalHierarchicLayouter.LAYOUT_MODE_INCREMENTAL);
125 ((SimplexNodePlacer) hierarchicLayouter.getNodePlacer()).setBaryCenterModeEnabled(true);
126
127 ihlHintMap = Maps.createHashedDataMap();
129 graph.addDataProvider(IncrementalHierarchicLayouter.INCREMENTAL_HINTS_DPKEY, ihlHintMap);
130 hintsFactory = hierarchicLayouter.createIncrementalHintsFactory();
132
133 SwingUtilities.invokeLater(new Runnable() {
135 public void run() {
136 layout(view.getGraph2D(), null, true);
137 }
138 });
139 }
140
141
142 protected EditMode createEditMode() {
143 return null;
144 }
145
146
147 protected void registerViewModes() {
148 viewMode = new CollapseExpandViewMode();
149 view.addViewMode(viewMode);
150 }
151
152
157 class CollapseExpandViewMode extends ViewMode {
158 NodeMap collapsedEdges = Maps.createNodeMap(new WeakHashMap());
159 NodeMap collapsedState = Maps.createNodeMap(new WeakHashMap());
160
161 public void mouseClicked(MouseEvent ev) {
162 Node node = getHitInfo(ev).getHitNode();
164
165 if (node != null) {
166 prepareForLayout(view.getGraph2D(), node);
167 if (collapsedState.getBool(node)) {
168 if (ev.isControlDown()) { expandSubtree(getGraph2D(), node, 10000);
170 } else { expandNode(getGraph2D(), node);
172 }
173 } else {
174 if (ev.isControlDown()) { collapseSubtree(getGraph2D(), node);
176 } else { collapseNode(getGraph2D(), node);
178 }
179 }
180 layout(getGraph2D(), node, false);
181 }
182 }
183
184
190 public void collapseSubtree(Graph2D graph, Node root) {
191 NodeList list = GraphConnectivity.getSuccessors(graph, new NodeList(root), graph.N());
192 NodeCursor nodeCursor = list.nodes();
193 for (nodeCursor.toLast(); nodeCursor.ok(); nodeCursor.prev()) {
194 Node node = nodeCursor.node();
195 if (!collapsedState.getBool(node) && node != root) {
196 collapseNode(graph, node);
197 }
198 }
199 collapseNode(graph, root);
200 }
201
202
208 public void collapseNode(Graph2D graph, final Node root) {
209 EdgeList edgeList = collapsedEdges.get(root) != null ? (EdgeList) collapsedEdges.get(root) : new EdgeList();
210 edgeList.addAll(root.outEdges());
211 NodeList collapsedNodes = GraphConnectivity.getSuccessors(graph, new NodeList(root), graph.N());
212
213 for (NodeCursor nc = collapsedNodes.nodes(); nc.ok(); nc.next()) {
214 Node n = nc.node();
215 edgeList.addAll(n.outEdges());
216 double x = graph.getCenterX(n) - graph.getCenterX(root);
217 double y = graph.getCenterY(n) - graph.getCenterY(root);
218
219 graph.getRealizer(n).setLocation(0.01 * x, 0.01 * y);
221
222 graph.hide(n);
224 }
225 collapsedState.setBool(root, true);
226 collapsedEdges.set(root, edgeList);
227
228 if (!edgeList.isEmpty()) {
229 getGraph2D().getRealizer(root).getLabel(1).setIcon(GroupNodeRealizer.defaultClosedGroupIcon);
230 }
231 }
232
233
240 public void expandSubtree(Graph2D graph, Node root, int depth) {
241 if (depth <= 0) {
242 return;
243 }
244 expandNode(graph, root);
246 NodeList list = GraphConnectivity.getSuccessors(graph, new NodeList(root), depth);
247 for (NodeCursor nodeCursor = list.nodes(); nodeCursor.ok(); nodeCursor.next()) {
248 Node node = nodeCursor.node();
249 if (collapsedState.getBool(node)) {
250 expandSubtree(graph, node, depth - 1);
252 }
253 }
254 }
255
256
262 public void expandNode(Graph2D graph, Node root) {
263 final EdgeList edgeList = (EdgeList) collapsedEdges.get(root);
264 if (edgeList != null) {
265 for (EdgeCursor ec = edgeList.edges(); ec.ok(); ec.next()) {
266 Edge e = ec.edge();
267 if (!graph.contains(e.source())) {
268 graph.unhide(e.source());
269 graph.setLocation(e.source(), graph.getX(root) + graph.getX(e.source()),
270 graph.getY(root) + graph.getY(e.source()));
271 }
272 if (!graph.contains(e.target())) {
273 graph.unhide(e.target());
274 graph.setLocation(e.target(), graph.getX(root) + graph.getX(e.target()),
275 graph.getY(root) + graph.getY(e.target()));
276 }
277 graph.unhide(e);
279 graph.getRealizer(e).clearBends();
281 }
282 collapsedEdges.set(root, null);
283 }
284 collapsedState.setBool(root, false);
285
286 if (root.outDegree() > 0) {
287 getGraph2D().getRealizer(root).getLabel(1).setIcon(GroupNodeRealizer.defaultOpenGroupIcon);
288 }
289 }
290 }
291
292
300 void layout(Graph2D graph2D, Node focusNode, boolean fitContent) {
301 GraphLayout gl;
302 switch (style) {
304 case CollapsibleTreeDemo.STYLE_TREE:
305 gl = new BufferedLayouter(treeLayouter).calcLayout(graph2D);
306 break;
307 case CollapsibleTreeDemo.STYLE_BALLOON:
308 gl = new BufferedLayouter(balloonLayouter).calcLayout(graph2D);
309 break;
310 case CollapsibleTreeDemo.STYLE_ORGANIC:
311 gl = new BufferedLayouter(organicLayouter).calcLayout(graph2D);
312 break;
313 case CollapsibleTreeDemo.STYLE_HIERARCHIC:
314 prepareForLayout(graph2D, focusNode);
315 gl = new BufferedLayouter(hierarchicLayouter).calcLayout(graph2D);
316 break;
317 default:
318 gl = new BufferedLayouter(treeLayouter).calcLayout(graph2D);
319 }
320
321 LayoutMorpher lm = new LayoutMorpher(view, gl);
322 if (!fitContent) {
323 lm.setKeepZoomFactor(true); }
325
326 if (focusNode != null) {
328 Point2D cp = view.getCenter();
329 YPoint oldFocus = graph2D.getCenter(focusNode);
330 NodeLayout nl = gl.getNodeLayout(focusNode);
331 YPoint newFocus = new YPoint(nl.getX() + 0.5 * nl.getWidth(), nl.getY() + 0.5 * nl.getHeight());
332 double dx = newFocus.x - oldFocus.x;
333 double dy = newFocus.y - oldFocus.y;
334
335 ViewAnimationFactory af = new ViewAnimationFactory(view);
336 AnimationObject mc = af.moveCamera(DefaultMutableValue2D.create(cp.getX() + dx, cp.getY() + dy), 300);
337 AnimationObject animObject = AnimationFactory.createConcurrency(lm, mc);
338 AnimationPlayer player = af.createConfiguredPlayer();
339 player.setBlocking(true);
340 player.animate(animObject);
341 } else {
342 lm.execute();
343 }
344 }
345
346 private void prepareForLayout(Graph2D graph2D, Node node) {
347 NodeList incrementalNodes = GraphConnectivity.getSuccessors(graph2D, new NodeList(node), graph2D.N());
348 for (NodeCursor nodeCursor = incrementalNodes.nodes(); nodeCursor.ok(); nodeCursor.next()) {
350 ihlHintMap.set(nodeCursor.node(), hintsFactory.createLayerIncrementallyHint(nodeCursor.node()));
351 }
352 }
353
354
355
356 protected JToolBar createToolBar() {
357 JToolBar toolbar = super.createToolBar();
358 ButtonGroup group = new ButtonGroup();
359 JToggleButton b1 = new JToggleButton(new AbstractAction("Tree") {
360 public void actionPerformed(ActionEvent e) {
361 style = CollapsibleTreeDemo.STYLE_TREE;
362 layout(view.getGraph2D(), null, true);
363 }
364 });
365 b1.setSelected(true);
366 group.add(b1);
367 toolbar.add(b1);
368
369
370 JToggleButton b2 = new JToggleButton(new AbstractAction("Balloon") {
371 public void actionPerformed(ActionEvent e) {
372 style = CollapsibleTreeDemo.STYLE_BALLOON;
373 layout(view.getGraph2D(), null, true);
374 }
375 });
376 group.add(b2);
377 toolbar.add(b2);
378
379 JToggleButton b3 = new JToggleButton(new AbstractAction("Organic") {
380 public void actionPerformed(ActionEvent e) {
381 style = CollapsibleTreeDemo.STYLE_ORGANIC;
382 layout(view.getGraph2D(), null, true);
383 }
384 });
385 group.add(b3);
386 toolbar.add(b3);
387
388 JToggleButton b4 = new JToggleButton(new AbstractAction("Hierarchic") {
389 public void actionPerformed(ActionEvent e) {
390 style = CollapsibleTreeDemo.STYLE_HIERARCHIC;
391 Graph2D graph = view.getGraph2D();
392 layout(graph, Trees.getRoot(graph), true);
393 }
394 });
395 group.add(b4);
396 toolbar.add(b4);
397
398 return toolbar;
399 }
400
401 void createTree(Graph2D graph) {
402 NodeList queue = new NodeList();
403 queue.add(graph.createNode(0, 0, 80, 30, "Root"));
404 for (int i = 0; i < 50; i++) {
405 Node root = queue.popNode();
406 Node c1 = graph.createNode(0, 0, 80, 30, String.valueOf(graph.N()));
407 Edge e1 = graph.createEdge(root, c1);
408 Node c2 = graph.createNode(0, 0, 80, 30, String.valueOf(graph.N()));
409 Edge e2 = graph.createEdge(root, c2);
410 queue.add(c2);
411 queue.add(c1);
412 if (i == 25 || i == 40) {
413 for (int j = 0; j < 20; j++) {
414 Node c3 = graph.createNode(0, 0, 80, 30, String.valueOf(graph.N()));
415 Edge e3 = graph.createEdge(root, c3);
416 queue.add(c3);
417 }
418 }
419 }
420 for (NodeCursor nodeCursor = graph.nodes(); nodeCursor.ok(); nodeCursor.next()) {
421 Node node = nodeCursor.node();
422 if (node.outDegree() == 0) {
423 graph.getRealizer(node).getLabel(1).setIcon(null);
424 }
425 }
426 }
427
428
429 public static void main(String[] args) {
430 initLnF();
431 CollapsibleTreeDemo demo = new CollapsibleTreeDemo();
432 demo.start(demo.getClass().getName());
433 }
434 }