1   
28  package demo.layout.labeling;
29  
30  import demo.view.DemoBase;
31  import demo.view.DemoDefaults;
32  import y.base.DataMap;
33  import y.base.GraphEvent;
34  import y.base.GraphListener;
35  import y.base.Node;
36  import y.base.NodeCursor;
37  import y.layout.AbstractLayoutStage;
38  import y.layout.DiscreteNodeLabelModel;
39  import y.layout.LabelCandidate;
40  import y.layout.LayoutGraph;
41  import y.layout.Layouter;
42  import y.layout.NodeLabelModel;
43  import y.layout.NodeLayout;
44  import y.layout.ProfitModel;
45  import y.layout.labeling.AbstractLabelingAlgorithm;
46  import y.layout.labeling.GreedyMISLabeling;
47  import y.layout.labeling.MISLabelingAlgorithm;
48  import y.layout.NodeLabelLayout;
49  import y.layout.BufferedLayouter;
50  import y.option.EditorFactory;
51  import y.option.OptionGroup;
52  import y.option.OptionHandler;
53  import y.option.DefaultEditorFactory;
54  import y.option.Editor;
55  import y.option.ItemEditor;
56  import y.option.CompoundEditor;
57  import y.util.DataProviderAdapter;
58  import y.util.Maps;
59  import y.view.DefaultBackgroundRenderer;
60  import y.view.EditMode;
61  import y.view.Graph2D;
62  import y.view.Graph2DLayoutExecutor;
63  import y.view.NodeLabel;
64  import y.view.NodeRealizer;
65  import y.view.PopupMode;
66  import y.view.SmartNodeLabelModel;
67  import y.view.YLabel;
68  import y.view.DefaultLabelConfiguration;
69  import y.geom.YPoint;
70  import y.geom.YRectangle;
71  import y.geom.LineSegment;
72  
73  import javax.swing.AbstractAction;
74  import javax.swing.Action;
75  import javax.swing.JPopupMenu;
76  import javax.swing.JToolBar;
77  import javax.swing.JPanel;
78  import javax.swing.JComponent;
79  import java.awt.Color;
80  import java.awt.Dimension;
81  import java.awt.EventQueue;
82  import java.awt.Graphics2D;
83  import java.awt.BorderLayout;
84  import java.awt.geom.Line2D;
85  import java.awt.event.ActionEvent;
86  import java.net.URL;
87  import java.util.Arrays;
88  import java.util.HashMap;
89  import java.util.HashSet;
90  import java.util.List;
91  import java.util.Locale;
92  import java.util.Map;
93  import java.util.ArrayList;
94  import java.util.Iterator;
95  import java.beans.PropertyChangeEvent;
96  import java.beans.PropertyChangeListener;
97  import java.util.Set;
98  
99  
120 public class NodeLabelingDemo extends DemoBase {
121   private static final String LABELING_MODEL_STRING = "Labeling Model";
122   private static final String LABEL_SIZE_STRING = "Font Size";
123   private static final String PROPERTIES_GROUP = "Node Label Properties";
124 
125     private static final String MODEL_CORNERS = "Corners";  
127   private static final String MODEL_SANDWICH = "Sandwich";
128   private static final String MODEL_SIDE = "Side";
129   private static final String MODEL_FREE = "Free";
130   private static final String MODEL_EIGHT_POS = "8 Pos";
131   private static final String[] NODE_LABEL_MODELS = {
132       MODEL_CORNERS, MODEL_SANDWICH, MODEL_SIDE, MODEL_FREE, MODEL_EIGHT_POS
133   };
134 
135   private static final int TOOLS_PANEL_WIDTH = 350;
136 
137   private Map label2Model;   private MyGenericNodeLabelingAlgorithm labelLayouter;
139   private OptionHandler optionHandler;
140 
141   public NodeLabelingDemo() {
142     this(null);
143   }
144 
145   public NodeLabelingDemo(final String helpFilePath) {
146         DefaultBackgroundRenderer renderer = new DefaultBackgroundRenderer(view);
148 
149         URL bgImage = getSharedResource("resource/usamap.png");
151 
152     renderer.setImageResource(bgImage);
153     renderer.setMode(DefaultBackgroundRenderer.DYNAMIC);
154     renderer.setColor(Color.white);
155     view.setBackgroundRenderer(renderer);
156     view.setPreferredSize(new Dimension(650, 400));
157     view.setWorldRect(0, 0, 650, 400);
158 
159     contentPane.add(createToolsPanel(helpFilePath), BorderLayout.EAST);
160 
161     loadGraph("resource/uscities.graphml");
162 
163         view.getGraph2D().addGraphListener(new GraphListener() {
165       public void onGraphEvent(GraphEvent e) {
166         if (e.getType() == GraphEvent.NODE_CREATION) {
167           final Graph2D graph = view.getGraph2D();
168           final Node node = (Node) e.getData();
169           final NodeLabelLayout[] nll = graph.getNodeLabelLayout(node);
170 
171                     final int labelSize = optionHandler.getInt(LABEL_SIZE_STRING);
173           final NodeLabelModel labelingModel = getModel(
174               optionHandler.getEnum(LABELING_MODEL_STRING));           for (int i = 0; i < nll.length; i++) {
176             label2Model.put(nll[i], labelingModel);
177             ((NodeLabel) nll[i]).setFontSize(labelSize);
178           }
179 
180                     final Set newLabels = new HashSet(Arrays.asList(nll));
182           graph.addDataProvider("SELECTED_LABELS", new DataProviderAdapter() {
183             public boolean getBool(Object dataHolder) {
184               return newLabels.contains(dataHolder);
185             }
186           });
187           final Object oldSelectionKey = labelLayouter.getSelectionKey();
188           labelLayouter.setSelection("SELECTED_LABELS");
189           new BufferedLayouter(labelLayouter).doLayout(graph);
190           labelLayouter.setSelection(oldSelectionKey);
191           graph.removeDataProvider("SELECTED_LABELS");
192         }
193       }
194     });
195 
196         doLabelPlacement();
198 
199     getUndoManager().resetQueue();
200   }
201 
202   protected void initialize() {
203     optionHandler = createOptionHandler();
204 
205     labelLayouter = new MyGenericNodeLabelingAlgorithm();
206 
207         label2Model = new HashMap();
209     view.getGraph2D().addDataProvider(AbstractLabelingAlgorithm.LABEL_MODEL_DPKEY, new DataProviderAdapter() {
210       public Object get(Object dataHolder) {
211         return label2Model.get(dataHolder);
212       }
213     });
214   }
215 
216   
220   static class MyGenericNodeLabelingAlgorithm extends AbstractLayoutStage {
221     private final GreedyMISLabeling coreLabeling;
222 
223     public MyGenericNodeLabelingAlgorithm() {
224       this(null);
225     }
226 
227     public MyGenericNodeLabelingAlgorithm(Layouter core) {
228       super(core);
229       this.coreLabeling = new GreedyMISLabeling();
230       coreLabeling.setOptimizationStrategy(MISLabelingAlgorithm.OPTIMIZATION_BALANCED);
231       coreLabeling.setPlaceEdgeLabels(false);
232       coreLabeling.setPlaceNodeLabels(true);
233       coreLabeling.setApplyPostprocessing(true);
234     }
235 
236     
243     public void setSelection(Object key) {
244       coreLabeling.setSelection(key);
245     }
246 
247     
250     public Object getSelectionKey() {
251       return coreLabeling.getSelectionKey();
252     }
253 
254     public boolean canLayout(LayoutGraph graph) {
255       return canLayoutCore(graph);
256     }
257 
258     public void doLayout(LayoutGraph graph) {
259       doLayoutCore(graph);
260 
261             coreLabeling.setProfitModel(new PreferNorthLabelsProfitModel(graph));
263 
264             coreLabeling.doLayout(graph);
266     }
267 
268     
272     private static class PreferNorthLabelsProfitModel implements ProfitModel {
273       private static final double MAX_DISTANCE_WITH_PROFIT = 10;
274       private static final double MAX_HORIZONTAL_CENTER_DISTANCE_WITH_PROFIT = 10;
275       private DataMap label2NodeLayout;
276 
277       public PreferNorthLabelsProfitModel(final LayoutGraph graph) {
278         updateLabel2NodeLayoutMap(graph);
279       }
280 
281       
285       private void updateLabel2NodeLayoutMap(final LayoutGraph graph) {
286         this.label2NodeLayout = Maps.createHashedDataMap();
287         for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
288           final NodeLayout nLayout = graph.getNodeLayout(nc.node());
289           final NodeLabelLayout[] nll = graph.getLabelLayout(nc.node());
290           for (int i = 0; i < nll.length; i++) {
291             label2NodeLayout.set(nll[i], nLayout);
292           }
293         }
294       }
295 
296       private static boolean isLabelAboveNode(final YRectangle labelBox, final NodeLayout nodeLayout) {
297         return nodeLayout.getY() > labelBox.getY() + labelBox.getHeight();
298       }
299 
300       private static double calcHorizontalCenterDistance(final YRectangle labelBox, final NodeLayout nodeLayout) {
301         return Math.abs((nodeLayout.getX() + nodeLayout.getWidth() * 0.5) - (labelBox.getX() + labelBox.getWidth() * 0.5));
302       }
303 
304       private static double calcDistance(YRectangle r1, YRectangle r2) {
305         if (YRectangle.intersects(r1, r2)) {
306           return 0.0;
307         } else {
308           final double distVertical = calculateOrthogonalDifference(r1, r2, false);
309           final double distHoriztonal = calculateOrthogonalDifference(r1, r2, true);
310           return Math.sqrt(distVertical * distVertical + distHoriztonal * distHoriztonal);
311         }
312       }
313 
314       private static double calculateOrthogonalDifference(YRectangle rect1, YRectangle rect2, boolean horizontal) {
315         final double rect1Min = horizontal ? rect1.getX() : rect1.getY();
316         final double rect1Max = horizontal ? rect1.getX() + rect1.getWidth() : rect1.getY() + rect1.getHeight();
317         final double rect2Min = horizontal ? rect2.getX() : rect2.getY();
318         final double rect2Max = horizontal ? rect2.getX() + rect2.getWidth() : rect2.getY() + rect2.getHeight();
319 
320         if (rect2Max < rect1Min) {
321                     return rect2Max - rect1Min;
323         } else if (rect1Max < rect2Min) {
324                     return rect2Min - rect1Max;
326         } else {
327                     return 0.0;
329         }
330       }
331 
332       private static YRectangle getBox(final NodeLayout nodeLayout) {
333         return new YRectangle(nodeLayout.getX(), nodeLayout.getY(), nodeLayout.getWidth(), nodeLayout.getHeight());
334       }
335 
336       
342       private static double calcProfit(final YRectangle labelBox, final NodeLayout nodeLayout) {
343         if (!isLabelAboveNode(labelBox, nodeLayout)) {
344           return 0;
345         }
346 
347         double profit = 0.5;
348         final double dist = calcDistance(labelBox, getBox(nodeLayout));
349         if (dist < MAX_DISTANCE_WITH_PROFIT) {
350                     profit += 0.25 * (MAX_DISTANCE_WITH_PROFIT - dist) / MAX_DISTANCE_WITH_PROFIT;
352         }
353         final double horizontalCenterDist = calcHorizontalCenterDistance(labelBox, nodeLayout);
354         if (horizontalCenterDist < MAX_HORIZONTAL_CENTER_DISTANCE_WITH_PROFIT) {
355                     profit += 0.25 * (MAX_HORIZONTAL_CENTER_DISTANCE_WITH_PROFIT - horizontalCenterDist) / MAX_HORIZONTAL_CENTER_DISTANCE_WITH_PROFIT;
357         }
358         return profit;
359       }
360 
361       public double getProfit(LabelCandidate candidate) {
362         final NodeLayout nodeLayout = (NodeLayout) label2NodeLayout.get(candidate.getOwner());
363         return (nodeLayout == null) ? 0 : calcProfit(candidate.getBoundingBox(), nodeLayout);
364       }
365     }
366   }
367 
368   
372   private void doLabelPlacement() {
373         final Graph2D graph = view.getGraph2D();
375 
376         final int labelSize = optionHandler.getInt(LABEL_SIZE_STRING);
378     final NodeLabelModel labelingModel = getModel(optionHandler.getEnum(LABELING_MODEL_STRING));     label2Model.clear();
380     for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
381       NodeLabelLayout[] nll = graph.getNodeLabelLayout(nc.node());
382       for (int i = 0; i < nll.length; i++) {
383         label2Model.put(nll[i], labelingModel);
384         ((NodeLabel) nll[i]).setFontSize(labelSize);
385       }
386     }
387 
388         final NodeRealizer defaultNodeRealizer = graph.getDefaultNodeRealizer();
390     final NodeLabel nl = defaultNodeRealizer.getLabel();
391     nl.setFontSize(labelSize);
392     final SmartNodeLabelModel model = new SmartNodeLabelModel();
393     nl.setLabelModel(model, model.getDefaultParameter()); 
395     new Graph2DLayoutExecutor().doLayout(view, labelLayouter);
396 
397     view.updateView();
398   }
399 
400   
403   private OptionHandler createOptionHandler() {
404     final OptionHandler oh = new OptionHandler("Options");
405     oh.addEnum(LABELING_MODEL_STRING, NODE_LABEL_MODELS, 2);
406     oh.addInt(LABEL_SIZE_STRING, 12, 10, 25);
407 
408     OptionGroup og = new OptionGroup();
409     og.setAttribute(OptionGroup.ATTRIBUTE_TITLE, PROPERTIES_GROUP);
410     og.addItem(oh.getItem(LABELING_MODEL_STRING));
411     og.addItem(oh.getItem(LABEL_SIZE_STRING));
412 
413     oh.addChildPropertyChangeListener(new PropertyChangeListener() {
414       public void propertyChange(PropertyChangeEvent evt) {
415                 doLabelPlacement();
417       }
418     });
419 
420     return oh;
421   }
422 
423   
426   private static NodeLabelModel getModel(int index) {
427     if (index < 0 || index >= NODE_LABEL_MODELS.length) {
428       return new DiscreteNodeLabelModel(DiscreteNodeLabelModel.SANDWICH_MASK);
429     }
430 
431     final String modelString = NODE_LABEL_MODELS[index];
432     if (MODEL_CORNERS.equals(modelString)) {
433       return new DiscreteNodeLabelModel(DiscreteNodeLabelModel.CORNER_MASK);
434     } else if (MODEL_EIGHT_POS.equals(modelString)) {
435       return new DiscreteNodeLabelModel(DiscreteNodeLabelModel.EIGHT_POS_MASK);
436     } else if (MODEL_FREE.equals(modelString)) {
437       return new SmartNodeLabelModel();
438     } else if (MODEL_SIDE.equals(modelString)) {
439       return new DiscreteNodeLabelModel(DiscreteNodeLabelModel.SIDES_MASK);
440     } else {
441       return new DiscreteNodeLabelModel(DiscreteNodeLabelModel.SANDWICH_MASK);
442     }
443   }
444 
445   
448   private JPanel createToolsPanel(String helpFilePath) {
449     JPanel toolsPanel = new JPanel(new BorderLayout());
450     toolsPanel.add(createOptionHandlerComponent(optionHandler), BorderLayout.NORTH);
451 
452     if (helpFilePath != null) {
453       final URL url = getResource(helpFilePath);
454       if (url == null) {
455         System.err.println("Could not locate help file: " + helpFilePath);
456       } else {
457         JComponent helpPane = createHelpPane(url);
458         if (helpPane != null) {
459           helpPane.setMinimumSize(new Dimension(200, 200));
460           helpPane.setPreferredSize(new Dimension(TOOLS_PANEL_WIDTH, 400));
461           toolsPanel.add(helpPane, BorderLayout.CENTER);
462         }
463       }
464     }
465 
466     return toolsPanel;
467   }
468 
469   protected void configureDefaultRealizers() {
470     super.configureDefaultRealizers();
471 
472         final YLabel.Factory factory = NodeLabel.getFactory();
474     final Map implementationsMap = factory.createDefaultConfigurationMap();
475     implementationsMap.put(YLabel.Painter.class, new MyPainter());
476     factory.addConfiguration("Customized", implementationsMap);
477 
478         NodeRealizer nodeRealizer = view.getGraph2D().getDefaultNodeRealizer();
480     nodeRealizer.setSize(10.0, 10.0);
481     nodeRealizer.getLabel().setText("City");
482     nodeRealizer.getLabel().setConfiguration("Customized");
483   }
484 
485   protected EditMode createEditMode() {
486         final EditMode mode = super.createEditMode();
488     mode.allowEdgeCreation(false);
489     mode.allowMoveSelection(false);
490     mode.setSnappingEnabled(false);
491     mode.allowResizeNodes(false);
492     mode.setPopupMode(new DemoPopupMode());
493     return mode;
494   }
495 
496   protected JToolBar createToolBar() {
497     JToolBar bar = super.createToolBar();
498     bar.addSeparator();
499     bar.add(createActionControl(new LayoutAction()));
500     return bar;
501   }
502 
503   
506   protected void loadGraph(URL resource) {
507     super.loadGraph(resource);
508 
509     final Graph2D graph2D = view.getGraph2D();
510     DemoDefaults.applyRealizerDefaults(graph2D);
511     for (NodeCursor nc = graph2D.nodes(); nc.ok(); nc.next()) {
512       final NodeLabelLayout[] nll = graph2D.getNodeLabelLayout(nc.node());
513       for (int i = 0; i < nll.length; i++) {
514         ((NodeLabel) nll[i]).setConfiguration("Customized");
515       }
516     }
517   }
518 
519   
522   class LayoutAction extends AbstractAction {
523     LayoutAction() {
524       super("Place Labels", SHARED_LAYOUT_ICON);
525       putValue(Action.SHORT_DESCRIPTION, "Place labels");
526     }
527 
528     public void actionPerformed(ActionEvent e) {
529       doLabelPlacement();
530     }
531   }
532 
533   
536   class DemoPopupMode extends PopupMode {
537     
540     public JPopupMenu getNodePopup(Node v) {
541       JPopupMenu pm = new JPopupMenu();
542       NodeRealizer r = this.view.getGraph2D().getRealizer(v);
543       YLabel label = r.getLabel();
544       pm.add(new EditLabel(label));
545       return pm;
546     }
547 
548     
551     public JPopupMenu getNodeLabelPopup(NodeLabel label) {
552       JPopupMenu pm = new JPopupMenu();      
553       pm.add(new EditLabel(label));
554       return pm;
555     }
556   }
557 
558   
561   class EditLabel extends AbstractAction {
562     YLabel label;
563 
564     EditLabel(YLabel l) {
565       super("Edit Label");
566       label = l;
567     }
568 
569     public void actionPerformed(ActionEvent e) {
570       view.openLabelEditor(label, label.getTextLocation().getX(), label.getTextLocation().getY());
571     }
572   }
573 
574   
578   static final class MyPainter extends DefaultLabelConfiguration {
579     
580     public void paintBox(YLabel label, Graphics2D gfx, double x, double y, double width, double height) {
581       super.paintBox(label, gfx, x, y, width, height);
582       if (label instanceof NodeLabel) {
583                 final Node node = ((NodeLabel) label).getNode();
585         final Graph2D graph2D = (Graph2D) node.getGraph();
586         final LineSegment connectingLine = new LineSegment(new YPoint(x + width * 0.5d, y + height * 0.5d),
587             graph2D.getCenter(node));
588 
589                 final YRectangle labelBox = new YRectangle(x, y, width, height);
591         YPoint startPoint = calcBorderIntersectionPoints(labelBox, connectingLine);
592         final YRectangle nodeBox = graph2D.getRectangle(node);
593         YPoint endPoint = calcBorderIntersectionPoints(nodeBox, connectingLine);
594 
595                 if (startPoint != null && endPoint != null) {
597           Line2D line = new Line2D.Double(startPoint.x, startPoint.y, endPoint.x, endPoint.y);
598           gfx.setColor(new Color(0, 0, 0, 150));
599           gfx.draw(line);
600         }
601       }
602     }
603   }
604 
605   
609   private static JComponent createOptionHandlerComponent(OptionHandler oh) {
610     final EditorFactory defaultEditorFactory = new DefaultEditorFactory();
611     final Editor editor = defaultEditorFactory.createEditor(oh);
612 
613         final List stack = new ArrayList();
615     stack.add(editor);
616     while(!stack.isEmpty()) {
617       Object editorObj = stack.remove(stack.size() - 1);
618       if(editorObj instanceof ItemEditor) {
619         ((ItemEditor) editorObj).setAutoAdopt(true);
620         ((ItemEditor) editorObj).setAutoCommit(true);
621       }
622       if(editorObj instanceof CompoundEditor) {
623         for (Iterator iter = ((CompoundEditor) editorObj).editors(); iter.hasNext(); ) {
624           stack.add(iter.next());
625         }
626       }
627     }
628 
629         JComponent optionComponent = editor.getComponent();
631     optionComponent.setMinimumSize(new Dimension(200, 50));
632     return optionComponent;
633   }
634 
635   
639   private static YPoint calcBorderIntersectionPoints(YRectangle r, LineSegment l) {
640     if(!r.contains(l.getFirstEndPoint()) && !r.contains(l.getSecondEndPoint())) {
641       throw new RuntimeException("Input no valid!");
642     }    
643 
644         final YPoint[] rCorners = new YPoint[4]; 
646     rCorners[0] = r.getLocation();
647     rCorners[1] = new YPoint(rCorners[0].x, rCorners[0].y + r.getHeight());
648     rCorners[2] = new YPoint(rCorners[1].x + r.getWidth(), rCorners[1].y);
649     rCorners[3] = new YPoint(rCorners[2].x, rCorners[0].y);
650     for(int i = 0; i < rCorners.length; i++) {
651       final LineSegment rSide = new LineSegment(rCorners[i], rCorners[(i + 1) % 4]);
652       YPoint intersectionPoint = LineSegment.getIntersection(rSide, l);
653       if(intersectionPoint != null) {
654         return intersectionPoint;       }
656     }
657     
658         for(int i = 0; i < rCorners.length; i++) {
660       if(l.intersects(rCorners[i])) {
661         return rCorners[i];
662       }
663     }
664 
665     return null;   }
667 
668   public static void main(String[] args) {
669     EventQueue.invokeLater(new Runnable() {
670       public void run() {
671         Locale.setDefault(Locale.ENGLISH);
672         initLnF();
673         (new NodeLabelingDemo("resource/nodelabelingdemohelp.html")).start("Labeling Demo");
674       }
675     });
676   }
677 }
678 
679 
680       
681