1
14 package demo.view.layout.tree;
15
16 import demo.view.DemoBase;
17 import y.algo.Bfs;
18 import y.base.Edge;
19 import y.base.EdgeMap;
20 import y.base.Node;
21 import y.base.NodeCursor;
22 import y.base.NodeList;
23 import y.base.NodeMap;
24 import y.geom.YPoint;
25 import y.layout.BufferedLayouter;
26 import y.layout.GraphLayout;
27 import y.layout.PortConstraint;
28 import y.layout.PortConstraintKeys;
29 import y.layout.tree.DefaultNodePlacer;
30 import y.layout.tree.GenericTreeLayouter;
31 import y.layout.tree.NodePlacer;
32 import y.util.DataProviderAdapter;
33 import y.view.Arrow;
34 import y.view.CreateChildEdgeMode;
35 import y.view.EdgeRealizer;
36 import y.view.EditMode;
37 import y.view.Graph2D;
38 import y.view.Graph2DSelectionEvent;
39 import y.view.Graph2DSelectionListener;
40 import y.view.HotSpotMode;
41 import y.view.LayoutMorpher;
42 import y.view.LineType;
43 import y.view.NodeRealizer;
44 import y.view.PolyLineEdgeRealizer;
45 import y.view.PopupMode;
46 import y.view.PortAssignmentMoveSelectionMode;
47
48 import javax.swing.AbstractAction;
49 import javax.swing.JButton;
50 import javax.swing.JLabel;
51 import javax.swing.JMenu;
52 import javax.swing.JPanel;
53 import javax.swing.JPopupMenu;
54 import javax.swing.JSpinner;
55 import javax.swing.JSplitPane;
56 import javax.swing.SpinnerNumberModel;
57 import javax.swing.event.ChangeEvent;
58 import javax.swing.event.ChangeListener;
59 import java.awt.BorderLayout;
60 import java.awt.Color;
61 import java.awt.Cursor;
62 import java.awt.FlowLayout;
63 import java.awt.event.ActionEvent;
64 import java.util.ArrayList;
65 import java.util.List;
66
67
74 public class IncrementalTreeLayouterDemo extends DemoBase
75 {
76 private EdgeMap sourcePortMap;
77 private EdgeMap targetPortMap;
78 private NodeMap portAssignmentMap;
79 private NodeMap nodePlacerMap;
80 private PortAssignmentMoveSelectionMode paMode;
81
82 private double hDistance = 40;
83 private double vDistance = 40;
84
85 private GenericTreeLayouter treeLayouter;
86
87 private DefaultNodePlacerConfigPanel configPanel;
88
89 private Color[] layerColors = new Color[]{Color.red, Color.orange, Color.yellow, Color.cyan, Color.green, Color.blue};
90
91 private List layerStyles = new ArrayList();
92 {
93 layerStyles.add(new DefaultNodePlacer(DefaultNodePlacer.PLACEMENT_HORIZONTAL_DOWNWARD, DefaultNodePlacer.ALIGNMENT_MEDIAN, 40, 40));
94 layerStyles.add(new DefaultNodePlacer(DefaultNodePlacer.PLACEMENT_VERTICAL_TO_RIGHT, DefaultNodePlacer.ALIGNMENT_LEADING_OFFSET, 20, 40));
95 layerStyles.add(new DefaultNodePlacer(DefaultNodePlacer.PLACEMENT_HORIZONTAL_DOWNWARD, DefaultNodePlacer.ALIGNMENT_LEADING_OFFSET, DefaultNodePlacer.ROUTING_FORK_AT_ROOT, 10, 20));
96 layerStyles.add(new DefaultNodePlacer(DefaultNodePlacer.PLACEMENT_HORIZONTAL_DOWNWARD, DefaultNodePlacer.ALIGNMENT_MEDIAN, 40, 40));
97 }
98
99 public IncrementalTreeLayouterDemo()
100 {
101 final Graph2D graph = view.getGraph2D();
102 EdgeRealizer defaultER = graph.getDefaultEdgeRealizer();
103 defaultER.setArrow(Arrow.STANDARD);
104 ((PolyLineEdgeRealizer)defaultER).setSmoothedBends(true);
105 defaultER.setLineType(LineType.LINE_2);
106
107 sourcePortMap = graph.createEdgeMap();
108 targetPortMap = graph.createEdgeMap();
109 portAssignmentMap = graph.createNodeMap();
110 nodePlacerMap = graph.createNodeMap();
111 graph.addDataProvider(GenericTreeLayouter.NODE_PLACER_DPKEY, nodePlacerMap);
112 graph.addDataProvider(GenericTreeLayouter.PORT_ASSIGNMENT_DPKEY, portAssignmentMap);
113 graph.addDataProvider(GenericTreeLayouter.CHILD_COMPARATOR_DPKEY, new ChildEdgeComparatorProvider());
114 graph.addDataProvider(PortConstraintKeys.SOURCE_PORT_CONSTRAINT_KEY, sourcePortMap);
115 graph.addDataProvider(PortConstraintKeys.TARGET_PORT_CONSTRAINT_KEY, targetPortMap);
116
117
118 paMode.setSpc(sourcePortMap);
119 paMode.setTpc(targetPortMap);
120
121 treeLayouter = new GenericTreeLayouter();
122
123 configPanel = new DefaultNodePlacerConfigPanel();
124 configPanel.adoptPlacerValues((NodePlacer)layerStyles.get(0));
125
126 JPanel layerChooserPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
127 layerChooserPanel.add(new JLabel("Layer: "));
128 final JSpinner spinner = new JSpinner(new SpinnerNumberModel(1, 1, 100, 1));
129 spinner.addChangeListener(new ChangeListener()
130 {
131 public void stateChanged(ChangeEvent ce){
132 final int layer = ((Number) spinner.getValue()).intValue() - 1;
133 while (layer >= layerStyles.size()){
134 layerStyles.add(new DefaultNodePlacer(DefaultNodePlacer.PLACEMENT_HORIZONTAL_DOWNWARD, 40, 40));
135 }
136 NodePlacer placer = (NodePlacer) layerStyles.get(layer);
137 configPanel.adoptPlacerValues(placer);
138 }
139 }
140 );
141 layerChooserPanel.add(spinner);
142
143 configPanel.addChangeListener(new ChangeListener()
144 {
145 public void stateChanged(ChangeEvent ce){
146 final int layer = ((Number) spinner.getValue()).intValue() - 1;
147 while (layer >= layerStyles.size()){
148 layerStyles.add(new DefaultNodePlacer(DefaultNodePlacer.PLACEMENT_HORIZONTAL_DOWNWARD, 40, 40));
149 }
150 layerStyles.set(layer, configPanel.createPlacerCopy());
151 }
152 }
153 );
154
155 JButton button = new JButton(new AbstractAction("Apply"){
156 public void actionPerformed(ActionEvent ae){
157 final int layer = ((Number) spinner.getValue()).intValue() - 1;
158 NodePlacer placer = (NodePlacer) layerStyles.get(layer);
159 NodeList[] layers = Bfs.getLayers(graph, new NodeList(graph.firstNode()));
160 if (layer < layers.length){
161 for (NodeCursor nc = layers[layer].nodes(); nc.ok(); nc.next()){
162 nodePlacerMap.set(nc.node(), placer);
163 }
164 calcLayout();
165 }
166 }
167 });
168
169 graph.addGraph2DSelectionListener(new Graph2DSelectionListener()
170 {
171 public void onGraph2DSelectionEvent(Graph2DSelectionEvent ev){
172 if (ev.isNodeSelection() && graph.isSelectionSingleton()){
173 Node n = (Node) ev.getSubject();
174 int depth = 1;
175 while (n.inDegree() > 0){
176 n = n.firstInEdge().source();
177 depth++;
178 }
179 spinner.setValue(new Integer(depth));
180 }
181 }
182 }
183 );
184 layerChooserPanel.add(button);
185
186 JPanel rightPanel = new JPanel(new BorderLayout());
187 rightPanel.add(configPanel, BorderLayout.CENTER);
188 rightPanel.add(layerChooserPanel, BorderLayout.NORTH);
189
190 JSplitPane sp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, rightPanel, view);
191 sp.setOneTouchExpandable(true);
192 sp.setContinuousLayout(false);
193 contentPane.add(sp, BorderLayout.CENTER);
194 createSampleGraph(graph);
195 }
196
197 final class ChildEdgeComparatorProvider extends DataProviderAdapter {
198 public Object get(Object forRootNode){
199 NodePlacer placer = (NodePlacer) nodePlacerMap.get(forRootNode);
200 if (placer instanceof DefaultNodePlacer){
201 return ((DefaultNodePlacer)placer).createComparator();
202 }
203 return null;
204 }
205 }
206
207 private void createSampleGraph(Graph2D graph){
208 graph.clear();
209 Node root = graph.createNode();
210 graph.getRealizer(root).setFillColor(layerColors[0]);
211 nodePlacerMap.set(root, layerStyles.get(0));
212 createChildren(graph, root, 4, 1, 2);
213 calcLayout();
214 }
215
216 private void createChildren(Graph2D graph, Node root, int children, int layer, int layers){
217 for (int i = 0; i < children; i++){
218 Node child = graph.createNode();
219 graph.createEdge(root, child);
220 graph.getRealizer(child).setFillColor(layerColors[layer % layerColors.length]);
221 if (layerStyles.size() > layer){
222 nodePlacerMap.set(child, layerStyles.get(layer));
223 }
224 if (layers > 0){
225 createChildren(graph, child, children, layer+1, layers-1);
226 }
227 }
228 }
229
230 protected boolean isDeletionEnabled(){
231 return false;
232 }
233
234 protected void registerViewModes() {
235 EditMode editMode = new TreeCreateEditMode();
236 view.addViewMode( editMode );
237 }
238
239
240 public void calcLayout(){
241 if (!view.getGraph2D().isEmpty()){
242 if (true) {
243 Cursor oldCursor = view.getCanvasComponent().getCursor();
244 try {
245 view.getCanvasComponent().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
246 GraphLayout result = new BufferedLayouter(treeLayouter).calcLayout(view.getGraph2D());
247 LayoutMorpher morpher = new LayoutMorpher(view, result);
248 morpher.setSmoothViewTransform(true);
249 morpher.setPreferredDuration(300);
250 morpher.execute();
251 } finally {
252 view.getCanvasComponent().setCursor(oldCursor);
253 }
254 } else {
255 new BufferedLayouter(treeLayouter).doLayout(view.getGraph2D());
256 }
257 }
258 view.fitContent();
259 view.updateView();
260 }
261
262 final class TreeCreateChildEdgeMode extends CreateChildEdgeMode {
263 protected void edgeCreated(Edge e){
264 int depth = 1;
265 for (Node n = e.source(); n.inDegree() > 0; n = n.firstInEdge().source()){
266 depth++;
267 }
268 Graph2D g = getGraph2D();
269 g.getRealizer(e.target()).setFillColor(layerColors[depth % layerColors.length]);
270 EdgeRealizer er = g.getRealizer(e);
271 if (nodePlacerMap.get(e.source()) == null){
272 parseNodePlaceMent(g, e, er);
273 }
274 if (layerStyles.size() > depth){
275 nodePlacerMap.set(e.target(), layerStyles.get(depth));
276 }
277 parseTargetPort(g, e, er);
278 g.unselectAll();
279 calcLayout();
280 g.setSelected(e.target(), true);
281 }
282
283 private void parseNodePlaceMent(Graph2D g, Edge e, EdgeRealizer er){
284 YPoint firstPoint = er.bendCount() > 0 ? new YPoint(er.firstBend().getX(), er.firstBend().getY()) :
285 g.getTargetPointAbs(e);
286 NodeRealizer source = g.getRealizer(e.source());
287 double dx = firstPoint.x - source.getCenterX();
288 double dy = firstPoint.y - source.getCenterY();
289 final byte placement;
290 final byte alignment = DefaultNodePlacer.ALIGNMENT_MEDIAN;
291 final byte routing = DefaultNodePlacer.ROUTING_FORK;
292 if (Math.abs(dx) > Math.abs(dy)){
293 if (dx > 0){
294 placement = DefaultNodePlacer.PLACEMENT_VERTICAL_TO_RIGHT;
295 } else {
296 placement = DefaultNodePlacer.PLACEMENT_VERTICAL_TO_LEFT;
297 }
298 } else {
299 if (dy > 0){
300 placement = DefaultNodePlacer.PLACEMENT_HORIZONTAL_DOWNWARD;
301 } else {
302 placement = DefaultNodePlacer.PLACEMENT_HORIZONTAL_UPWARD;
303 }
304 }
305 nodePlacerMap.set(e.source(), new DefaultNodePlacer(placement, alignment, routing, hDistance, vDistance));
306 }
307
308 private void parseTargetPort(Graph2D g, Edge e, EdgeRealizer er){
309 if (er.bendCount() > 0){
310 YPoint lastPoint = new YPoint(er.lastBend().getX(), er.lastBend().getY());
311 NodeRealizer target = g.getRealizer(e.target());
312 double dx = lastPoint.x - target.getCenterX();
313 double dy = lastPoint.y - target.getCenterY();
314 byte side = PortConstraint.ANY_SIDE;
315 if (Math.abs(dx) > Math.abs(dy)){
316 if (dx > 0){
317 side = PortConstraint.EAST;
318 } else {
319 side = PortConstraint.WEST;
320 }
321 } else {
322 if (dy > 0){
323 side = PortConstraint.SOUTH;
324 } else {
325 side = PortConstraint.NORTH;
326 }
327 }
328 targetPortMap.set(e, PortConstraint.create(side));
329 }
330 }
331
332 protected NodeRealizer createChildNodeRealizer()
333 {
334 NodeRealizer retValue;
335 retValue = super.createChildNodeRealizer();
336 retValue.setLabelText("");
337 return retValue;
338 }
339
340 }
341
342
343 final class TreeLayouterPopupMode extends PopupMode {
344 private JPopupMenu nodePlacementMenu;
345
346 public TreeLayouterPopupMode(){
347 nodePlacementMenu = new JPopupMenu();
348 JMenu alignment = new JMenu("Root node Alignment");
349 JMenu placement = new JMenu("Child Placement");
350 JMenu routing = new JMenu("Routing Style");
351
352 nodePlacementMenu.add(placement);
353 nodePlacementMenu.add(alignment);
354 nodePlacementMenu.add(routing);
355
356 placement.add(new PlacementAction("Horizontally Downwards", DefaultNodePlacer.PLACEMENT_HORIZONTAL_DOWNWARD));
357 placement.add(new PlacementAction("Horizontally Upwards", DefaultNodePlacer.PLACEMENT_HORIZONTAL_UPWARD));
358 placement.add(new PlacementAction("Vertically to Left", DefaultNodePlacer.PLACEMENT_VERTICAL_TO_LEFT));
359 placement.add(new PlacementAction("Vertically to right", DefaultNodePlacer.PLACEMENT_VERTICAL_TO_RIGHT));
360
361 alignment.add(new AlignmentAction("Offset Leading", DefaultNodePlacer.ALIGNMENT_LEADING_OFFSET));
362 alignment.add(new AlignmentAction("Leading", DefaultNodePlacer.ALIGNMENT_LEADING));
363 alignment.add(new AlignmentAction("Centered", DefaultNodePlacer.ALIGNMENT_CENTER));
364 alignment.add(new AlignmentAction("Median", DefaultNodePlacer.ALIGNMENT_MEDIAN));
365 alignment.add(new AlignmentAction("Trailing", DefaultNodePlacer.ALIGNMENT_TRAILING));
366 alignment.add(new AlignmentAction("Offset Trailing", DefaultNodePlacer.ALIGNMENT_TRAILING_OFFSET));
367
368 routing.add(new RoutingAction("Fork", DefaultNodePlacer.ROUTING_FORK));
369 routing.add(new RoutingAction("Fork at Root", DefaultNodePlacer.ROUTING_FORK_AT_ROOT));
370 routing.add(new RoutingAction("Poly Line", DefaultNodePlacer.ROUTING_POLY_LINE));
371 routing.add(new RoutingAction("Straight", DefaultNodePlacer.ROUTING_STRAIGHT));
372 }
373
374 public JPopupMenu getNodePopup(final Node v)
375 {
376 return nodePlacementMenu;
377 }
378
379 public JPopupMenu getSelectionPopup(double x, double y)
380 {
381 if (getGraph2D().selectedNodes().ok()){
382 return nodePlacementMenu;
383 } else {
384 return null;
385 }
386 }
387
388 }
389
390 abstract class AssignLayouterAction extends AbstractAction {
391
392 public AssignLayouterAction(String name){
393 super(name);
394 }
395
396 public void actionPerformed(ActionEvent e)
397 {
398 NodeList selectedNodes = new NodeList(IncrementalTreeLayouterDemo.this.view.getGraph2D().selectedNodes());
399
400 NodePlacer placer = (NodePlacer) nodePlacerMap.get(selectedNodes.firstNode());
401 placer = getPlacer(placer);
402
403 DefaultNodePlacer dnp = (DefaultNodePlacer) placer;
404 for (NodeCursor nc = selectedNodes.nodes(); nc.ok(); nc.next()){
405 nodePlacerMap.set(nc.node(), new DefaultNodePlacer(
406 dnp.getChildPlacement(),
407 dnp.getRootAlignment(),
408 dnp.getRoutingStyle(),
409 dnp.getHorizontalDistance(),
410 dnp.getVerticalDistance()));
411 }
412 calcLayout();
413 }
414
415 protected abstract NodePlacer getPlacer(NodePlacer placer);
416 }
417
418 final class PlacementAction extends AssignLayouterAction {
419
420 private byte newPlacement;
421 private Node node;
422
423 public PlacementAction(String name, byte newPlacement){
424 super(name);
425 this.newPlacement = newPlacement;
426 }
427
428 protected NodePlacer getPlacer(NodePlacer placer)
429 {
430 if (placer instanceof DefaultNodePlacer){
431 placer = (DefaultNodePlacer) ((DefaultNodePlacer) placer).clone();
432 ((DefaultNodePlacer)placer).setChildPlacement(newPlacement);
433 } else {
434 placer = new DefaultNodePlacer(newPlacement, DefaultNodePlacer.ALIGNMENT_MEDIAN, hDistance, vDistance);
435 }
436 return placer;
437 }
438 }
439
440 final class AlignmentAction extends AssignLayouterAction {
441 private byte newAlignment;
442
443 public AlignmentAction(String name, byte newAlignment){
444 super(name);
445 this.newAlignment = newAlignment;
446 }
447
448 protected NodePlacer getPlacer(NodePlacer placer)
449 {
450 if (placer instanceof DefaultNodePlacer){
451 placer = (DefaultNodePlacer) ((DefaultNodePlacer) placer).clone();
452 ((DefaultNodePlacer)placer).setRootAlignment(newAlignment);
453 } else {
454 placer = new DefaultNodePlacer(DefaultNodePlacer.PLACEMENT_HORIZONTAL_DOWNWARD, newAlignment, hDistance, vDistance);
455 }
456 return placer;
457 }
458 }
459
460 final class RoutingAction extends AssignLayouterAction {
461 private byte newRouting;
462
463 public RoutingAction(String name, byte newRouting){
464 super(name);
465 this.newRouting = newRouting;
466 }
467
468 protected NodePlacer getPlacer(NodePlacer placer)
469 {
470 if (placer instanceof DefaultNodePlacer){
471 placer = (DefaultNodePlacer) ((DefaultNodePlacer) placer).clone();
472 ((DefaultNodePlacer)placer).setRoutingStyle(newRouting);
473 } else {
474 placer = new DefaultNodePlacer(DefaultNodePlacer.PLACEMENT_HORIZONTAL_DOWNWARD, DefaultNodePlacer.ALIGNMENT_CENTER, newRouting, hDistance, vDistance);
475 }
476 return placer;
477 }
478 }
479
480 final class TreeHotSpotMode extends HotSpotMode {
481 public void mouseReleasedLeft(double x, double y)
482 {
483 super.mouseReleasedLeft(x, y);
484 calcLayout();
485 }
486 }
487
488 final class TreeCreateEditMode extends EditMode {
489 TreeCreateEditMode(){
490 super();
491 setMoveSelectionMode(paMode = new TreePortAssignmentMode());
492 setCreateEdgeMode(new TreeCreateChildEdgeMode());
493 setHotSpotMode(new TreeHotSpotMode());
494 setPopupMode(new TreeLayouterPopupMode());
495 }
496
497 public boolean doAllowNodeCreation()
498 {
499 return getGraph2D().N() == 0;
500 }
501
502 protected void nodeCreated(Node v)
503 {
504 super.nodeCreated(v);
505 nodePlacerMap.set(v, configPanel.createPlacerCopy());
506 }
507
508 }
509
510 final class TreePortAssignmentMode extends PortAssignmentMoveSelectionMode {
511 TreePortAssignmentMode(){
512 super(null, null);
513 }
514
515 protected boolean isPortReassignmentAllowed(Edge edge, boolean source)
516 {
517 return !source;
518 }
519
520
525 protected void selectionMovedAction(double dx, double dy, double x, double y)
526 {
527 super.selectionMovedAction(dx, dy, x, y);
528 calcLayout();
529 }
530
531 }
532
533
536 public static void main(String args[])
537 {
538 initLnF();
539 IncrementalTreeLayouterDemo demo = new IncrementalTreeLayouterDemo();
540 demo.start("Incremental Tree Layouter Demo");
541 }
542 }
543