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