1
14 package demo.layout.labeling;
15
16 import demo.view.DemoBase;
17 import demo.view.DemoDefaults;
18 import y.base.GraphEvent;
19 import y.base.GraphListener;
20 import y.base.Node;
21 import y.base.NodeCursor;
22 import y.layout.DiscreteNodeLabelModel;
23 import y.layout.NodeLabelModel;
24 import y.layout.labeling.AbstractLabelingAlgorithm;
25 import y.layout.labeling.GreedyMISLabeling;
26 import y.layout.labeling.MISLabelingAlgorithm;
27 import y.layout.NodeLabelLayout;
28 import y.layout.BufferedLayouter;
29 import y.option.EditorFactory;
30 import y.option.OptionGroup;
31 import y.option.OptionHandler;
32 import y.option.DefaultEditorFactory;
33 import y.option.Editor;
34 import y.option.ItemEditor;
35 import y.option.CompoundEditor;
36 import y.util.DataProviderAdapter;
37 import y.view.DefaultBackgroundRenderer;
38 import y.view.EditMode;
39 import y.view.Graph2D;
40 import y.view.NodeLabel;
41 import y.view.NodeRealizer;
42 import y.view.PopupMode;
43 import y.view.SmartNodeLabelModel;
44 import y.view.YLabel;
45 import y.view.DefaultLabelConfiguration;
46 import y.geom.YPoint;
47 import y.geom.YRectangle;
48 import y.geom.LineSegment;
49
50 import javax.swing.AbstractAction;
51 import javax.swing.Action;
52 import javax.swing.JPopupMenu;
53 import javax.swing.JToolBar;
54 import javax.swing.JPanel;
55 import javax.swing.JComponent;
56 import java.awt.Color;
57 import java.awt.Dimension;
58 import java.awt.EventQueue;
59 import java.awt.Graphics2D;
60 import java.awt.BorderLayout;
61 import java.awt.Shape;
62 import java.awt.geom.Line2D;
63 import java.awt.event.ActionEvent;
64 import java.net.URL;
65 import java.util.Arrays;
66 import java.util.HashMap;
67 import java.util.HashSet;
68 import java.util.List;
69 import java.util.Locale;
70 import java.util.Map;
71 import java.util.ArrayList;
72 import java.util.Iterator;
73 import java.beans.PropertyChangeEvent;
74 import java.beans.PropertyChangeListener;
75 import java.util.Set;
76
77
91 public class NodeLabelingDemo extends DemoBase {
92 private static final String LABELING_MODEL_STRING = "Labeling Model";
93 private static final String LABEL_SIZE_STRING = "Font Size";
94 private static final String PROPERTIES_GROUP = "Node Label Properties";
95
96 private static final String MODEL_CORNERS = "Corners";
98 private static final String MODEL_SANDWICH = "Sandwich";
99 private static final String MODEL_SIDE = "Side";
100 private static final String MODEL_FREE = "Free";
101 private static final String MODEL_EIGHT_POS = "8 Pos";
102 private static final String[] NODE_LABEL_MODELS = {
103 MODEL_CORNERS, MODEL_SANDWICH, MODEL_SIDE, MODEL_FREE, MODEL_EIGHT_POS
104 };
105
106 private static final int TOOLS_PANEL_WIDTH = 350;
107
108 private Map label2Model; private GreedyMISLabeling labelLayouter;
110 private OptionHandler optionHandler;
111
112 public NodeLabelingDemo() {
113 this(null);
114 }
115
116 public NodeLabelingDemo(final String helpFilePath) {
117 DefaultBackgroundRenderer renderer = new DefaultBackgroundRenderer(view);
119 URL bgImage = getClass().getResource("resource/usamap.gif");
120 renderer.setImageResource(bgImage);
121 renderer.setMode(DefaultBackgroundRenderer.DYNAMIC);
122 renderer.setColor(Color.white);
123 view.setBackgroundRenderer(renderer);
124 view.setPreferredSize(new Dimension(650, 400));
125 view.setWorldRect(0, 0, 650, 400);
126
127 contentPane.add(createToolsPanel(helpFilePath), BorderLayout.EAST);
128
129 loadGraph("resource/uscities.graphml");
130
131 view.getGraph2D().addGraphListener(new GraphListener() {
133 public void onGraphEvent(GraphEvent e) {
134 if (e.getType() == GraphEvent.NODE_CREATION) {
135 final Graph2D graph = view.getGraph2D();
136 final Node node = (Node) e.getData();
137 final NodeLabelLayout[] nll = graph.getNodeLabelLayout(node);
138
139 final int labelSize = optionHandler.getInt(LABEL_SIZE_STRING);
141 final NodeLabelModel labelingModel = getModel(
142 optionHandler.getEnum(LABELING_MODEL_STRING)); for (int i = 0; i < nll.length; i++) {
144 label2Model.put(nll[i], labelingModel);
145 ((NodeLabel) nll[i]).setFontSize(labelSize);
146 }
147
148 final Set newLabels = new HashSet(Arrays.asList(nll));
150 graph.addDataProvider("SELECTED_LABELS", new DataProviderAdapter() {
151 public boolean getBool(Object dataHolder) {
152 return newLabels.contains(dataHolder);
153 }
154 });
155 final Object oldSelectionKey = labelLayouter.getSelectionKey();
156 labelLayouter.setSelection("SELECTED_LABELS");
157 new BufferedLayouter(labelLayouter).doLayout(graph);
158 labelLayouter.setSelection(oldSelectionKey);
159 graph.removeDataProvider("SELECTED_LABELS");
160 }
161 }
162 });
163
164 doLabelPlacement();
166 }
167
168 protected void initialize() {
169 optionHandler = createOptionHandler();
170
171 labelLayouter = new GreedyMISLabeling();
172 labelLayouter.setOptimizationStrategy(MISLabelingAlgorithm.OPTIMIZATION_BALANCED);
173 labelLayouter.setPlaceEdgeLabels(false);
174 labelLayouter.setPlaceNodeLabels(true);
175 labelLayouter.setApplyPostprocessing(true);
176
177 label2Model = new HashMap();
179 view.getGraph2D().addDataProvider(AbstractLabelingAlgorithm.LABEL_MODEL_DPKEY, new DataProviderAdapter() {
180 public Object get(Object dataHolder) {
181 return label2Model.get(dataHolder);
182 }
183 });
184 }
185
186
190 private void doLabelPlacement() {
191 final Graph2D graph = view.getGraph2D();
193
194 final int labelSize = optionHandler.getInt(LABEL_SIZE_STRING);
196 final NodeLabelModel labelingModel = getModel(optionHandler.getEnum(LABELING_MODEL_STRING)); label2Model.clear();
198 for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
199 NodeLabelLayout[] nll = graph.getNodeLabelLayout(nc.node());
200 for (int i = 0; i < nll.length; i++) {
201 label2Model.put(nll[i], labelingModel);
202 ((NodeLabel) nll[i]).setFontSize(labelSize);
203 }
204 }
205
206 final NodeRealizer defaultNodeRealizer = graph.getDefaultNodeRealizer();
208 defaultNodeRealizer.getLabel().setFontSize(labelSize);
209 defaultNodeRealizer.getLabel().setLabelModel(new SmartNodeLabelModel());
211 new BufferedLayouter(labelLayouter).doLayout(view.getGraph2D());
212
213 view.updateView();
214 }
215
216
219 private OptionHandler createOptionHandler() {
220 final OptionHandler oh = new OptionHandler("Options");
221 oh.addEnum(LABELING_MODEL_STRING, NODE_LABEL_MODELS, 2);
222 oh.addInt(LABEL_SIZE_STRING, 12, 10, 25);
223
224 OptionGroup og = new OptionGroup();
225 og.setAttribute(OptionGroup.ATTRIBUTE_TITLE, PROPERTIES_GROUP);
226 og.addItem(oh.getItem(LABELING_MODEL_STRING));
227 og.addItem(oh.getItem(LABEL_SIZE_STRING));
228
229 oh.addChildPropertyChangeListener(new PropertyChangeListener() {
230 public void propertyChange(PropertyChangeEvent evt) {
231 doLabelPlacement();
233 }
234 });
235
236 return oh;
237 }
238
239
242 private static NodeLabelModel getModel(int index) {
243 if (index < 0 || index >= NODE_LABEL_MODELS.length) {
244 return new DiscreteNodeLabelModel(DiscreteNodeLabelModel.SANDWICH_MASK);
245 }
246
247 final String modelString = NODE_LABEL_MODELS[index];
248 if (MODEL_CORNERS.equals(modelString)) {
249 return new DiscreteNodeLabelModel(DiscreteNodeLabelModel.CORNER_MASK);
250 } else if (MODEL_EIGHT_POS.equals(modelString)) {
251 return new DiscreteNodeLabelModel(DiscreteNodeLabelModel.EIGHT_POS_MASK);
252 } else if (MODEL_FREE.equals(modelString)) {
253 return new SmartNodeLabelModel();
254 } else if (MODEL_SIDE.equals(modelString)) {
255 return new DiscreteNodeLabelModel(DiscreteNodeLabelModel.SIDES_MASK);
256 } else {
257 return new DiscreteNodeLabelModel(DiscreteNodeLabelModel.SANDWICH_MASK);
258 }
259 }
260
261
264 private JPanel createToolsPanel(String helpFilePath) {
265 JPanel toolsPanel = new JPanel(new BorderLayout());
266 toolsPanel.add(createOptionHandlerComponent(optionHandler), BorderLayout.NORTH);
267
268 if (helpFilePath != null) {
269 final URL url = getClass().getResource(helpFilePath);
270 if (url == null) {
271 System.err.println("Could not locate help file: " + helpFilePath);
272 } else {
273 JComponent helpPane = createHelpPane(url);
274 if (helpPane != null) {
275 helpPane.setMinimumSize(new Dimension(200, 200));
276 helpPane.setPreferredSize(new Dimension(TOOLS_PANEL_WIDTH, 400));
277 toolsPanel.add(helpPane, BorderLayout.CENTER);
278 }
279 }
280 }
281
282 return toolsPanel;
283 }
284
285 protected void configureDefaultRealizers() {
286 super.configureDefaultRealizers();
287
288 final YLabel.Factory factory = NodeLabel.getFactory();
290 final Map implementationsMap = factory.createDefaultConfigurationMap();
291 implementationsMap.put(YLabel.Painter.class, new MyPainter());
292 factory.addConfiguration("Customized", implementationsMap);
293
294 NodeRealizer nodeRealizer = view.getGraph2D().getDefaultNodeRealizer();
296 nodeRealizer.setSize(10.0, 10.0);
297 nodeRealizer.getLabel().setText("City");
298 nodeRealizer.getLabel().setConfiguration("Customized");
299 }
300
301 protected EditMode createEditMode() {
302 final EditMode mode = super.createEditMode();
304 mode.allowEdgeCreation(false);
305 mode.allowMoveSelection(false);
306 mode.setSnappingEnabled(false);
307 mode.allowResizeNodes(false);
308 mode.setPopupMode(new DemoPopupMode());
309 return mode;
310 }
311
312 protected JToolBar createToolBar() {
313 JToolBar bar = super.createToolBar();
314 bar.addSeparator();
315 bar.add(createActionControl(new LayoutAction()));
316 return bar;
317 }
318
319
322 protected void loadGraph(URL resource) {
323 super.loadGraph(resource);
324
325 final Graph2D graph2D = view.getGraph2D();
326 DemoDefaults.applyRealizerDefaults(graph2D);
327 for (NodeCursor nc = graph2D.nodes(); nc.ok(); nc.next()) {
328 final NodeLabelLayout[] nll = graph2D.getNodeLabelLayout(nc.node());
329 for (int i = 0; i < nll.length; i++) {
330 ((NodeLabel) nll[i]).setConfiguration("Customized");
331 }
332 }
333 }
334
335
338 class LayoutAction extends AbstractAction {
339 LayoutAction() {
340 super("Place Labels", SHARED_LAYOUT_ICON);
341 putValue(Action.SHORT_DESCRIPTION, "Place labels");
342 }
343
344 public void actionPerformed(ActionEvent e) {
345 doLabelPlacement();
346 }
347 }
348
349
352 class DemoPopupMode extends PopupMode {
353
356 public JPopupMenu getNodePopup(Node v) {
357 JPopupMenu pm = new JPopupMenu();
358 NodeRealizer r = this.view.getGraph2D().getRealizer(v);
359 YLabel label = r.getLabel();
360 pm.add(new EditLabel(label));
361 return pm;
362 }
363
364
367 public JPopupMenu getNodeLabelPopup(NodeLabel label) {
368 JPopupMenu pm = new JPopupMenu();
369 pm.add(new EditLabel(label));
370 return pm;
371 }
372 }
373
374
377 class EditLabel extends AbstractAction {
378 YLabel label;
379
380 EditLabel(YLabel l) {
381 super("Edit Label");
382 label = l;
383 }
384
385 public void actionPerformed(ActionEvent e) {
386 view.openLabelEditor(label, label.getTextLocation().getX(), label.getTextLocation().getY());
387 }
388 }
389
390
394 static final class MyPainter extends DefaultLabelConfiguration {
395
396 public void paintBox(YLabel label, Graphics2D gfx, double x, double y, double width, double height) {
397 super.paintBox(label, gfx, x, y, width, height);
398 if (label instanceof NodeLabel) {
399 final Node node = ((NodeLabel) label).getNode();
401 final Graph2D graph2D = (Graph2D) node.getGraph();
402 final LineSegment connectingLine = new LineSegment(new YPoint(x + width * 0.5d, y + height * 0.5d),
403 graph2D.getCenter(node));
404
405 final YRectangle labelBox = new YRectangle(x, y, width, height);
407 YPoint startPoint = calcBorderIntersectionPoints(labelBox, connectingLine);
408 final YRectangle nodeBox = graph2D.getRectangle(node);
409 YPoint endPoint = calcBorderIntersectionPoints(nodeBox, connectingLine);
410
411 if (startPoint != null && endPoint != null) {
413 Line2D line = new Line2D.Double(startPoint.x, startPoint.y, endPoint.x, endPoint.y);
414 gfx.setColor(new Color(0, 0, 0, 150));
415 gfx.draw(line);
416 }
417 }
418 }
419 }
420
421
425 private static JComponent createOptionHandlerComponent(OptionHandler oh) {
426 final EditorFactory defaultEditorFactory = new DefaultEditorFactory();
427 final Editor editor = defaultEditorFactory.createEditor(oh);
428
429 final List stack = new ArrayList();
431 stack.add(editor);
432 while(!stack.isEmpty()) {
433 Object editorObj = stack.remove(stack.size() - 1);
434 if(editorObj instanceof ItemEditor) {
435 ((ItemEditor) editorObj).setAutoAdopt(true);
436 ((ItemEditor) editorObj).setAutoCommit(true);
437 }
438 if(editorObj instanceof CompoundEditor) {
439 for (Iterator iter = ((CompoundEditor) editorObj).editors(); iter.hasNext(); ) {
440 stack.add(iter.next());
441 }
442 }
443 }
444
445 JComponent optionComponent = editor.getComponent();
447 optionComponent.setMinimumSize(new Dimension(200, 50));
448 return optionComponent;
449 }
450
451
455 private static YPoint calcBorderIntersectionPoints(YRectangle r, LineSegment l) {
456 if(!r.contains(l.getFirstEndPoint()) && !r.contains(l.getSecondEndPoint())) {
457 throw new RuntimeException("Input no valid!");
458 }
459
460 final YPoint[] rCorners = new YPoint[4];
462 rCorners[0] = r.getLocation();
463 rCorners[1] = new YPoint(rCorners[0].x, rCorners[0].y + r.getHeight());
464 rCorners[2] = new YPoint(rCorners[1].x + r.getWidth(), rCorners[1].y);
465 rCorners[3] = new YPoint(rCorners[2].x, rCorners[0].y);
466 for(int i = 0; i < rCorners.length; i++) {
467 final LineSegment rSide = new LineSegment(rCorners[i], rCorners[(i + 1) % 4]);
468 YPoint intersectionPoint = LineSegment.getIntersection(rSide, l);
469 if(intersectionPoint != null) {
470 return intersectionPoint; }
472 }
473
474 for(int i = 0; i < rCorners.length; i++) {
476 if(l.intersects(rCorners[i])) {
477 return rCorners[i];
478 }
479 }
480
481 return null; }
483
484 public static void main(String[] args) {
485 EventQueue.invokeLater(new Runnable() {
486 public void run() {
487 Locale.setDefault(Locale.ENGLISH);
488 initLnF();
489 (new NodeLabelingDemo("resource/nodelabelingdemohelp.html")).start("Labeling Demo");
490 }
491 });
492 }
493 }
494
495
496
497