1
28 package demo.layout.tree;
29
30 import demo.view.DemoBase;
31 import y.algo.GraphConnectivity;
32 import y.algo.Trees;
33 import y.base.DataMap;
34 import y.base.DataProvider;
35 import y.base.Edge;
36 import y.base.EdgeCursor;
37 import y.base.EdgeList;
38 import y.base.Node;
39 import y.base.NodeCursor;
40 import y.base.NodeList;
41 import y.base.NodeMap;
42 import y.geom.YPoint;
43 import y.layout.AbstractLayoutStage;
44 import y.layout.LayoutGraph;
45 import y.layout.LayoutOrientation;
46 import y.layout.LayoutTool;
47 import y.layout.Layouter;
48 import y.layout.NodeLayout;
49 import y.layout.hierarchic.IncrementalHierarchicLayouter;
50 import y.layout.hierarchic.incremental.IncrementalHintsFactory;
51 import y.layout.hierarchic.incremental.SimplexNodePlacer;
52 import y.layout.organic.SmartOrganicLayouter;
53 import y.layout.tree.BalloonLayouter;
54 import y.layout.tree.TreeLayouter;
55 import y.layout.tree.XCoordComparator;
56 import y.util.DataProviderAdapter;
57 import y.util.DataProviders;
58 import y.util.Maps;
59 import y.view.EdgeRealizer;
60 import y.view.EditMode;
61 import y.view.Graph2D;
62 import y.view.Graph2DLayoutExecutor;
63 import y.view.LineType;
64 import y.view.NavigationMode;
65 import y.view.NodeLabel;
66 import y.view.NodeRealizer;
67 import y.view.SmartNodeLabelModel;
68 import y.view.ViewMode;
69
70 import javax.swing.AbstractAction;
71 import javax.swing.ButtonGroup;
72 import javax.swing.Icon;
73 import javax.swing.JLabel;
74 import javax.swing.JMenu;
75 import javax.swing.JMenuBar;
76 import javax.swing.JToggleButton;
77 import javax.swing.JToolBar;
78 import java.awt.Color;
79 import java.awt.Component;
80 import java.awt.Dimension;
81 import java.awt.EventQueue;
82 import java.awt.Graphics;
83 import java.awt.Insets;
84 import java.awt.event.ActionEvent;
85 import java.awt.event.MouseEvent;
86 import java.net.URL;
87 import java.util.HashSet;
88 import java.util.Locale;
89 import java.util.WeakHashMap;
90
91
99 public class CollapsibleTreeDemo extends DemoBase {
100 public static final byte STYLE_TREE = 1;
101 public static final byte STYLE_BALLOON = 2;
102 private static final byte STYLE_ORGANIC = 3;
103 private static final byte STYLE_HIERARCHIC = 4;
104
105 private static final Color LEAF_COLOR = new Color(154, 205, 54);
106 private static final Color COLLAPSIBLE_COLOR = new Color(154, 205, 255);
107 private static final Color EXPANDABLE_COLOR = new Color(255, 154, 0);
108
109 static final Icon expandableIcon;
110 static final Icon collapsibleIcon;
111
112 static {
113 collapsibleIcon = new Icon(){
114 public void paintIcon(Component c, Graphics g, int x, int y) {
115 Color col = g.getColor();
116 g.setColor(Color.white);
117 g.fillRect(x + 1, y + 1, 19, 9);
118 g.setColor(Color.darkGray);
119 g.fillRect(x + 3, y + 3, 15, 5);
120 g.setColor(Color.gray);
121 g.setColor(col);
122 }
123
124 public int getIconWidth() {
125 return 18;
126 }
127
128 public int getIconHeight() {
129 return 9;
130 }
131 };
132
133 expandableIcon = new Icon(){
134 public void paintIcon(Component c, Graphics g, int x, int y) {
135 Color col = g.getColor();
136 g.setColor(Color.white);
137 g.fillRect(x + 6, y + 1, 9, 19);
138 g.fillRect(x + 1, y + 6, 19, 9);
139 g.setColor(Color.darkGray);
140 g.fillRect(x + 3, y + 8, 15, 5);
141 g.fillRect(x + 8, y + 3, 5, 15);
142 g.setColor(Color.gray);
143 g.setColor(col);
144 }
145
146 public int getIconWidth() {
147 return 18;
148 }
149
150 public int getIconHeight() {
151 return 18;
152 }
153 };
154 }
155
156 private byte style = STYLE_TREE;
157 private TreeLayouter treeLayouter;
158 private BalloonLayouter balloonLayouter;
159 private SmartOrganicLayouter organicLayouter;
160 private IncrementalHierarchicLayouter hierarchicLayouter;
161 private CollapsibleTreeDemo.CollapseExpandViewMode viewMode;
162 private DataMap ihlHintMap;
163 private IncrementalHintsFactory hintsFactory;
164
165 public CollapsibleTreeDemo() {
166 Graph2D graph = view.getGraph2D();
167
168 createTree(graph);
170
171 viewMode.collapseSubtree(graph, Trees.getRoot(graph));
173 Node root = Trees.getRoot(graph);
174 viewMode.expandSubtree(graph, root, 2);
175
176 treeLayouter = new TreeLayouter();
178 treeLayouter.setComparator(new XCoordComparator()); treeLayouter.setLayoutOrientation(LayoutOrientation.LEFT_TO_RIGHT);
180 treeLayouter.setLayoutStyle(TreeLayouter.ORTHOGONAL_STYLE);
181
182 balloonLayouter = new BalloonLayouter();
183 balloonLayouter.setFromSketchModeEnabled(true);
184 balloonLayouter.setCompactnessFactor(0.1);
185 balloonLayouter.setAllowOverlaps(true);
186
187 organicLayouter = new SmartOrganicLayouter();
188 organicLayouter.setScope(SmartOrganicLayouter.SCOPE_MAINLY_SUBSET);
189 organicLayouter.setMinimalNodeDistance(20);
190 organicLayouter.setMultiThreadingAllowed(true);
191
192 hierarchicLayouter = new IncrementalHierarchicLayouter();
193 hierarchicLayouter.setOrthogonallyRouted(true);
194 hierarchicLayouter.setLayoutOrientation(LayoutOrientation.TOP_TO_BOTTOM);
195 hierarchicLayouter.setLayoutMode(IncrementalHierarchicLayouter.LAYOUT_MODE_INCREMENTAL);
197 ((SimplexNodePlacer) hierarchicLayouter.getNodePlacer()).setBaryCenterModeEnabled(true);
198
199 ihlHintMap = Maps.createHashedDataMap();
201 graph.addDataProvider(IncrementalHierarchicLayouter.INCREMENTAL_HINTS_DPKEY, ihlHintMap);
202 hintsFactory = hierarchicLayouter.createIncrementalHintsFactory();
204
205 layout(view.getGraph2D(), null, true);
207 }
208
209 protected void configureDefaultRealizers() {
210 super.configureDefaultRealizers();
211 NodeRealizer nr = view.getGraph2D().getDefaultNodeRealizer();
212 nr.setSize(80, 30);
213 NodeLabel nl = nr.createNodeLabel();
214 nr.addLabel(nl);
215 nr.setLineColor(null);
216 nl.setIcon(collapsibleIcon);
217 nl.setIconTextGap((byte) 0);
218 SmartNodeLabelModel model = new SmartNodeLabelModel();
219 nl.setLabelModel(model,
220 model.createDiscreteModelParameter(SmartNodeLabelModel.POSITION_CENTER));
221 nl.setInsets(new Insets(4, 4, 4, 4));
222 nl.setDistance(0);
223 EdgeRealizer er = view.getGraph2D().getDefaultEdgeRealizer();
224 er.setLineType(LineType.LINE_2);
225 er.setLineColor(Color.gray);
226 }
227
228 protected void loadGraph(URL resource) {
229 super.loadGraph(resource);
230 Graph2D graph = view.getGraph2D();
231 viewMode.collapseSubtree(graph, Trees.getRoot(graph));
232 Node root = Trees.getRoot(graph);
233 viewMode.expandSubtree(graph, root, 2);
234 layout(graph, null, true);
235 }
236
237 protected void initialize() {
238 super.initialize();
239 view.setPreferredSize(new Dimension(900,600));
240 }
241
242
243 protected EditMode createEditMode() {
244 return null;
245 }
246
247
248 protected void registerViewModes() {
249 viewMode = new CollapseExpandViewMode();
250 view.addViewMode(viewMode);
251 NavigationMode navigationMode = new NavigationMode();
252 view.addViewMode(navigationMode);
253 }
254
255
256 protected JMenuBar createMenuBar() {
257 JMenuBar menuBar = new JMenuBar();
258 JMenu menu = new JMenu("File");
259 menu.add(new PrintAction());
260 menu.addSeparator();
261 menu.add(new ExitAction());
262 menuBar.add(menu);
263 return menuBar;
264 }
265
266
269 protected boolean isUndoRedoEnabled() {
270 return false;
271 }
272
273
276 protected boolean isClipboardEnabled() {
277 return false;
278 }
279
280
285 class CollapseExpandViewMode extends ViewMode {
286 NodeMap collapsedEdges = Maps.createNodeMap(new WeakHashMap());
287 NodeMap collapsedState = Maps.createNodeMap(new WeakHashMap());
288
289 public void mouseClicked(MouseEvent ev) {
290 Node node = getHitInfo(ev).getHitNode();
292
293 if (node != null) {
294 prepareForLayout(view.getGraph2D(), node);
295 if (collapsedState.getBool(node)) {
296 if (ev.isControlDown()) { expandSubtree(getGraph2D(), node, 10000);
298 } else { expandNode(getGraph2D(), node);
300 }
301 } else {
302 if (ev.isControlDown()) { collapseSubtree(getGraph2D(), node);
304 } else { collapseNode(getGraph2D(), node);
306 }
307 }
308 layout(getGraph2D(), node, false);
309 }
310 }
311
312
318 public void collapseSubtree(Graph2D graph, Node root) {
319 NodeList list = GraphConnectivity.getSuccessors(graph, new NodeList(root), graph.N());
320 NodeCursor nodeCursor = list.nodes();
321 for (nodeCursor.toLast(); nodeCursor.ok(); nodeCursor.prev()) {
322 Node node = nodeCursor.node();
323 if (!collapsedState.getBool(node) && node != root) {
324 collapseNode(graph, node);
325 }
326 }
327 collapseNode(graph, root);
328 }
329
330
336 public void collapseNode(Graph2D graph, final Node root) {
337 EdgeList edgeList = collapsedEdges.get(root) != null ? (EdgeList) collapsedEdges.get(root) : new EdgeList();
338 edgeList.addAll(root.outEdges());
339 NodeList collapsedNodes = GraphConnectivity.getSuccessors(graph, new NodeList(root), graph.N());
340
341 for (NodeCursor nc = collapsedNodes.nodes(); nc.ok(); nc.next()) {
342 Node n = nc.node();
343 edgeList.addAll(n.outEdges());
344 double x = graph.getCenterX(n) - graph.getCenterX(root);
345 double y = graph.getCenterY(n) - graph.getCenterY(root);
346
347 graph.getRealizer(n).setLocation(0.01 * x, 0.01 * y);
349
350 graph.hide(n);
352 }
353 collapsedState.setBool(root, true);
354 collapsedEdges.set(root, edgeList);
355
356 if (!edgeList.isEmpty()) {
357 NodeRealizer rootR = getGraph2D().getRealizer(root);
358 if(rootR.labelCount() > 1) {
359 getGraph2D().getRealizer(root).getLabel(1).setIcon(expandableIcon);
360 }
361 rootR.setFillColor(EXPANDABLE_COLOR);
362 }
363 }
364
365
372 public void expandSubtree(Graph2D graph, Node root, int depth) {
373 if (depth <= 0) {
374 return;
375 }
376 expandNode(graph, root);
378 NodeList list = GraphConnectivity.getSuccessors(graph, new NodeList(root), depth);
379 for (NodeCursor nodeCursor = list.nodes(); nodeCursor.ok(); nodeCursor.next()) {
380 Node node = nodeCursor.node();
381 if (collapsedState.getBool(node)) {
382 expandSubtree(graph, node, depth - 1);
384 }
385 }
386 }
387
388
394 public void expandNode(Graph2D graph, Node root) {
395 final EdgeList edgeList = (EdgeList) collapsedEdges.get(root);
396 if (edgeList != null) {
397 for (EdgeCursor ec = edgeList.edges(); ec.ok(); ec.next()) {
398 Edge e = ec.edge();
399 if (!graph.contains(e.source())) {
400 graph.unhide(e.source());
401 graph.setLocation(e.source(), graph.getX(root) + graph.getX(e.source()),
402 graph.getY(root) + graph.getY(e.source()));
403 }
404 if (!graph.contains(e.target())) {
405 graph.unhide(e.target());
406 graph.setLocation(e.target(), graph.getX(root) + graph.getX(e.target()),
407 graph.getY(root) + graph.getY(e.target()));
408 }
409 graph.unhide(e);
411 graph.getRealizer(e).clearBends();
413 }
414 collapsedEdges.set(root, null);
415 }
416 collapsedState.setBool(root, false);
417
418 if (root.outDegree() > 0) {
419 NodeRealizer rootR = getGraph2D().getRealizer(root);
420 if(rootR.labelCount() > 1) {
421 rootR.getLabel(1).setIcon(collapsibleIcon);
422 }
423 rootR.setFillColor(COLLAPSIBLE_COLOR);
424 }
425 }
426 }
427
428
436 void layout(Graph2D graph2D, final Node focusNode, boolean fitContent) {
437 Layouter layouter = null;
439 switch (style) {
440 case CollapsibleTreeDemo.STYLE_TREE:
441 layouter = treeLayouter;
442 break;
443 case CollapsibleTreeDemo.STYLE_BALLOON:
444 layouter = balloonLayouter;
445 break;
446 case CollapsibleTreeDemo.STYLE_ORGANIC:
447 prepareForLayout(graph2D, focusNode);
448 layouter = organicLayouter;
449 break;
450 case CollapsibleTreeDemo.STYLE_HIERARCHIC:
451 prepareForLayout(graph2D, focusNode);
452 layouter = hierarchicLayouter;
453 break;
454 default:
455 layouter = treeLayouter;
456 }
457
458 graph2D.addDataProvider(FocusNodeLayoutStage.FOCUS_NODE_DPKEY, FocusNodeLayoutStage.createFocusNodeDataProvider(focusNode));
459
460 final Graph2DLayoutExecutor layoutExecutor = new Graph2DLayoutExecutor();
461 if (fitContent) {
462 layoutExecutor.getLayoutMorpher().setSmoothViewTransform(true);
463 } else {
464 layoutExecutor.getLayoutMorpher().setKeepZoomFactor(true);
465 }
466 layoutExecutor.getLayoutMorpher().setEasedExecution(true);
467 layoutExecutor.doLayout(view, new FocusNodeLayoutStage(layouter));
468 }
469
470 public static class FocusNodeLayoutStage extends AbstractLayoutStage {
471
472 public static final Object FOCUS_NODE_DPKEY = "FocusNodeStage#FOCUS_NODE_DPKEY";
473
474 public FocusNodeLayoutStage(Layouter coreLayouter) {
475 super(coreLayouter);
476 }
477
478 public boolean canLayout(LayoutGraph graph) {
479 return canLayoutCore(graph);
480 }
481
482 public void doLayout(LayoutGraph graph) {
483 DataProvider dp = graph.getDataProvider(FOCUS_NODE_DPKEY);
484 if(dp != null) {
485 Node focusNode = null;
486 for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
487 Node n = nc.node();
488 if(dp.getBool(n)) {
489 focusNode = n;
490 break;
491 }
492 }
493 YPoint oldFocus = null;
494 if(focusNode != null) {
495 oldFocus = graph.getCenter(focusNode);
496 doLayoutCore(graph);
497 NodeLayout nl = graph.getNodeLayout(focusNode);
498 YPoint newFocus = new YPoint(nl.getX() + 0.5 * nl.getWidth(), nl.getY() + 0.5 * nl.getHeight());
499 double dx = newFocus.x - oldFocus.x;
500 double dy = newFocus.y - oldFocus.y;
501 LayoutTool.moveSubgraph(graph, graph.nodes(), -dx, -dy);
502 }
503 else {
504 doLayoutCore(graph);
505 }
506 }
507 else {
508 doLayoutCore(graph);
509 }
510 }
511
512 public static DataProvider createFocusNodeDataProvider(final Node focusNode) {
513 return new DataProviderAdapter() {
514 public boolean getBool(Object obj) {
515 return obj == focusNode;
516 }
517 };
518 }
519 }
520
521 private void prepareForLayout(Graph2D graph2D, Node node) {
522 if (node != null){
523 NodeList incrementalNodes = GraphConnectivity.getSuccessors(graph2D, new NodeList(node), graph2D.N());
524 final HashSet incrementalNodesSet = new HashSet(incrementalNodes);
525 for (NodeCursor nodeCursor = incrementalNodes.nodes(); nodeCursor.ok(); nodeCursor.next()) {
527 ihlHintMap.set(nodeCursor.node(), hintsFactory.createLayerIncrementallyHint(nodeCursor.node()));
528 }
529 graph2D.addDataProvider(SmartOrganicLayouter.NODE_SUBSET_DATA, new DataProviderAdapter() {
530 public boolean getBool(Object dataHolder) {
531 return incrementalNodesSet.contains(dataHolder);
532 }
533 });
534 graph2D.addDataProvider(IncrementalHierarchicLayouter.INCREMENTAL_HINTS_DPKEY, ihlHintMap);
535 organicLayouter.setScope(SmartOrganicLayouter.SCOPE_MAINLY_SUBSET);
536 } else {
537 graph2D.removeDataProvider(IncrementalHierarchicLayouter.INCREMENTAL_HINTS_DPKEY);
538 graph2D.addDataProvider(SmartOrganicLayouter.NODE_SUBSET_DATA, DataProviders.createConstantDataProvider(Boolean.FALSE));
539 organicLayouter.setScope(SmartOrganicLayouter.SCOPE_ALL);
540 }
541 }
542
543
544
545 protected JToolBar createToolBar() {
546 JToolBar toolbar = super.createToolBar();
547 toolbar.addSeparator();
548 toolbar.add(new JLabel("Layout:"));
549 toolbar.addSeparator(TOOLBAR_SMALL_SEPARATOR);
550
551 ButtonGroup group = new ButtonGroup();
552 JToggleButton b1 = new JToggleButton(new AbstractAction(
553 "Tree", SHARED_LAYOUT_ICON) {
554 public void actionPerformed(ActionEvent e) {
555 style = CollapsibleTreeDemo.STYLE_TREE;
556 layout(view.getGraph2D(), null, true);
557 }
558 });
559 b1.setSelected(true);
560 group.add(b1);
561 toolbar.add(b1);
562
563
564 JToggleButton b2 = new JToggleButton(new AbstractAction(
565 "Balloon", SHARED_LAYOUT_ICON) {
566 public void actionPerformed(ActionEvent e) {
567 style = CollapsibleTreeDemo.STYLE_BALLOON;
568 layout(view.getGraph2D(), null, true);
569 }
570 });
571 group.add(b2);
572 toolbar.add(b2);
573
574 JToggleButton b3 = new JToggleButton(new AbstractAction(
575 "Organic", SHARED_LAYOUT_ICON) {
576 public void actionPerformed(ActionEvent e) {
577 style = CollapsibleTreeDemo.STYLE_ORGANIC;
578 layout(view.getGraph2D(), null, true);
579 }
580 });
581 group.add(b3);
582 toolbar.add(b3);
583
584 JToggleButton b4 = new JToggleButton(new AbstractAction(
585 "Hierarchic", SHARED_LAYOUT_ICON) {
586 public void actionPerformed(ActionEvent e) {
587 style = CollapsibleTreeDemo.STYLE_HIERARCHIC;
588 Graph2D graph = view.getGraph2D();
589 layout(graph, Trees.getRoot(graph), true);
590 }
591 });
592 group.add(b4);
593 toolbar.add(b4);
594
595 return toolbar;
596 }
597
598 void createTree(Graph2D graph) {
599 NodeList queue = new NodeList();
600 queue.add(graph.createNode());
601 for (int i = 0; i < 50; i++) {
602 Node root = queue.popNode();
603 Node c1 = graph.createNode();
604 Edge e1 = graph.createEdge(root, c1);
605 Node c2 = graph.createNode();
606 Edge e2 = graph.createEdge(root, c2);
607 queue.add(c2);
608 queue.add(c1);
609 if (i == 25 || i == 40) {
610 for (int j = 0; j < 20; j++) {
611 Node c3 = graph.createNode();
612 Edge e3 = graph.createEdge(root, c3);
613 queue.add(c3);
614 }
615 }
616 }
617 for (NodeCursor nodeCursor = graph.nodes(); nodeCursor.ok(); nodeCursor.next()) {
618 Node node = nodeCursor.node();
619 if (node.outDegree() == 0) {
620 graph.getRealizer(node).getLabel(1).setIcon(null);
621 graph.getRealizer(node).setFillColor(LEAF_COLOR);
622 }
623 }
624 }
625
626
627 public static void main(String[] args) {
628 EventQueue.invokeLater(new Runnable() {
629 public void run() {
630 Locale.setDefault(Locale.ENGLISH);
631 initLnF();
632 (new CollapsibleTreeDemo()).start();
633 }
634 });
635 }
636 }