| EntityRelationshipDemo.java |
1 /****************************************************************************
2 * This demo file is part of yFiles for Java 2.14.
3 * Copyright (c) 2000-2017 by yWorks GmbH, Vor dem Kreuzberg 28,
4 * 72070 Tuebingen, Germany. All rights reserved.
5 *
6 * yFiles demo files exhibit yFiles for Java functionalities. Any redistribution
7 * of demo files in source code or binary form, with or without
8 * modification, is not permitted.
9 *
10 * Owners of a valid software license for a yFiles for Java version that this
11 * demo is shipped with are allowed to use the demo source code as basis
12 * for their own yFiles for Java powered applications. Use of such programs is
13 * governed by the rights and conditions as set out in the yFiles for Java
14 * license agreement.
15 *
16 * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
19 * NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
21 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 *
27 ***************************************************************************/
28 package demo.view.entityrelationship;
29
30 import demo.view.DemoBase;
31 import demo.view.entityrelationship.painters.ErdAttributesNodeLabelModel;
32 import demo.view.entityrelationship.painters.ErdRealizerFactory;
33 import demo.view.flowchart.FlowchartView;
34 import y.base.Edge;
35 import y.io.GraphMLIOHandler;
36 import y.io.graphml.graph2d.Graph2DGraphMLHandler;
37 import y.layout.orthogonal.EdgeLayoutDescriptor;
38 import y.layout.orthogonal.OrthogonalLayouter;
39 import y.module.ModuleEvent;
40 import y.module.ModuleListener;
41 import demo.layout.module.OrthogonalLayoutModule;
42 import y.option.OptionHandler;
43 import y.util.DataProviderAdapter;
44 import y.view.Arrow;
45 import y.view.EdgeRealizer;
46 import y.view.EditMode;
47 import y.view.Graph2D;
48 import y.view.Graph2DView;
49 import y.view.Graph2DViewActions;
50 import y.view.HitInfo;
51 import y.view.NodeLabel;
52 import y.view.NodeRealizer;
53 import y.view.ViewMode;
54
55 import javax.swing.AbstractAction;
56 import javax.swing.Action;
57 import javax.swing.BorderFactory;
58 import javax.swing.JComponent;
59 import javax.swing.JLabel;
60 import javax.swing.JMenu;
61 import javax.swing.JMenuBar;
62 import javax.swing.JMenuItem;
63 import javax.swing.JPanel;
64 import javax.swing.JSplitPane;
65 import javax.swing.JToolBar;
66 import java.awt.BorderLayout;
67 import java.awt.Color;
68 import java.awt.EventQueue;
69 import java.awt.Font;
70 import java.awt.event.ActionEvent;
71 import java.beans.PropertyChangeEvent;
72 import java.beans.PropertyChangeListener;
73 import java.util.Iterator;
74 import java.util.Locale;
75
76 /**
77 * A viewer and editor for entity relationship diagrams (ERD). It shows how to
78 * <ul>
79 * <li>add a palette of ERD symbols, the {@link EntityRelationshipPalette}, to ease the creation of diagrams</li>
80 * <li>implement a {@link y.view.GenericNodeRealizer.Painter} tailored for the drawing of ERD symbols
81 * with two labels</li>
82 * <li>convert the notation of the diagram with a custom class, the {@link ErdNotationConverter}</li>
83 * <li>apply an orthogonal layout with suitable default values</li>
84 * </ul>
85 *
86 * @see <a href="http://docs.yworks.com/yfiles/doc/api/index.html#/dguide/orthogonal_layouter" target="_blank">Section Orthogonal Layoutt</a> in the yFiles for Java Developer's Guide
87 * @see <a href="http://docs.yworks.com/yfiles/doc/api/index.html#/dguide/directed_orthogonal_layouter" target="_blank">Section Directed Orthogonal Layout</a> in the yFiles for Java Developer's Guide
88 * @see <a href="http://docs.yworks.com/yfiles/doc/api/index.html#/dguide/orthogonal_group_layouter" target="_blank">Section Orthogonal Layout of Grouped Graphst</a> in the yFiles for Java Developer's Guide
89 */
90 public class EntityRelationshipDemo extends DemoBase {
91
92 /** Names of the provided example graphs */
93 private static final String[] EXAMPLES_FILE_NAMES = {
94 "chen.graphml",
95 "crows_foot.graphml",
96 "space_database.graphml",
97 "hospital.graphml"
98 };
99
100 /** Component that provides the symbols of ERD diagrams */
101 EntityRelationshipPalette palette;
102 /** Module for execution of orthogonal layout */
103 private OrthogonalLayoutModule module;
104
105 /** Instantiates this demo and builds the GUI. */
106 public EntityRelationshipDemo(){
107 super();
108
109 JPanel panelPalette = createTitledPanel(palette, "ERD Palette");
110 contentPane.add(new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, panelPalette, view), BorderLayout.CENTER);
111
112 loadGraph("resource/graphs/" + EXAMPLES_FILE_NAMES[0]);
113 }
114
115 /** Initializes the Flowchart palette, the clipboard and the layout module */
116 protected void initialize() {
117 palette = new EntityRelationshipPalette(view);
118 palette.setSnapMode(true);
119
120 // orthogonal layout with default settings
121 module = new OrthogonalLayoutModule();
122 OptionHandler defaultSettings = module.getOptionHandler();
123 defaultSettings.set("LAYOUT", "STYLE", "NORMAL_TREE");
124 defaultSettings.set("LAYOUT", "GRID", new Integer(5));
125 defaultSettings.set("LAYOUT", "USE_FACE_MAXIMIZATION", Boolean.TRUE);
126 defaultSettings.set("LABELING", "EDGE_LABELING", "GENERIC");
127 defaultSettings.set("LABELING", "EDGE_LABEL_MODEL", "AS_IS");
128 defaultSettings.getItem("LAYOUT", "MINIMUM_FIRST_SEGMENT_LENGTH").setEnabled(false);
129 defaultSettings.getItem("LAYOUT", "MINIMUM_LAST_SEGMENT_LENGTH").setEnabled(false);
130 defaultSettings.getItem("LAYOUT", "MINIMUM_SEGMENT_LENGTH").setEnabled(false);
131
132 // register module listener in order to assign minimal first/last segment lengths for every
133 // individually before running layout using a DataProvider and remove the DataProvider afterwards
134 module.addModuleListener(new ModuleListener() {
135 public void moduleEventHappened(ModuleEvent moduleEvent) {
136 short type = moduleEvent.getEventType();
137 final Graph2D graph = view.getGraph2D();
138 if (type == ModuleEvent.TYPE_MODULE_INITIALIZING) {
139 // add DataProvider to ensure a minimal first/last segment length if there are arrows
140 graph.addDataProvider(OrthogonalLayouter.EDGE_LAYOUT_DESCRIPTOR_DPKEY,
141 new DataProviderAdapter() {
142 public Object get(Object dataHolder) {
143 EdgeRealizer realizer = graph.getRealizer(
144 ((Edge) dataHolder));
145 Arrow sourceArrow = realizer.getSourceArrow();
146 Arrow targetArrow = realizer.getTargetArrow();
147 EdgeLayoutDescriptor descriptor = new EdgeLayoutDescriptor();
148 descriptor.setMinimumFirstSegmentLength(getArrowLength(sourceArrow) + 10);
149 descriptor.setMinimumLastSegmentLength(getArrowLength(targetArrow) + 10);
150 return descriptor;
151 }
152 });
153 } else if (type == ModuleEvent.TYPE_MODULE_DISPOSED) {
154 // remove DataProvider with information about the minimal first/last segment length
155 graph.removeDataProvider(OrthogonalLayouter.EDGE_LAYOUT_DESCRIPTOR_DPKEY);
156 }
157 }
158
159 private double getArrowLength(Arrow arrow) {
160 switch (arrow.getType()) {
161 case Arrow.CROWS_FOOT_ONE_TYPE:
162 return 10;
163 case Arrow.CROWS_FOOT_ONE_MANDATORY_TYPE:
164 return 15;
165 case Arrow.CROWS_FOOT_ONE_OPTIONAL_TYPE:
166 return 20;
167 case Arrow.CROWS_FOOT_MANY_TYPE:
168 return 10;
169 case Arrow.CROWS_FOOT_MANY_MANDATORY_TYPE:
170 return 15;
171 case Arrow.CROWS_FOOT_MANY_OPTIONAL_TYPE:
172 return 20;
173 default:
174 return arrow.getArrowLength();
175 }
176 }
177 });
178 }
179
180 /** Registers the default view actions and an additional handler that reacts to label changes */
181 protected void registerViewActions() {
182 super.registerViewActions();
183 final Action action = view.getCanvasComponent().getActionMap().get(Graph2DViewActions.EDIT_LABEL);
184 action.putValue("PROPERTY_CHANGE_LISTENER", new LabelChangeHandler());
185 }
186
187 /** Prevents view from registering view modes automatically because the <code>FlowchartView</code> will register its own view modes */
188 protected void registerViewModes() {
189 }
190
191 /**
192 * Creates a <code>GraphMLOIHandler</code> with additionally (de-)serialization
193 * support for the custom label model that is used in big entities.
194 * @return an extended <code>GraphMLOIHandler</code> with support for bit entities
195 * @see ErdAttributesNodeLabelModel
196 */
197 protected GraphMLIOHandler createGraphMLIOHandler() {
198 GraphMLIOHandler graphMLIOHandler = super.createGraphMLIOHandler();
199 Graph2DGraphMLHandler graphMLHandler = graphMLIOHandler.getGraphMLHandler();
200 ErdAttributesNodeLabelModel.Handler handler = new ErdAttributesNodeLabelModel.Handler();
201 graphMLHandler.addSerializationHandler(handler);
202 graphMLHandler.addDeserializationHandler(handler);
203
204 return graphMLIOHandler;
205 }
206
207 /**
208 * Adds menu items for example graphs to the default menu bar.
209 * @return the menu bar for this demo.
210 */
211 protected JMenuBar createMenuBar() {
212 JMenu examplesMenu = new JMenu("Examples");
213 for (int i = 0; i < EXAMPLES_FILE_NAMES.length; i++) {
214 final String fileName = EXAMPLES_FILE_NAMES[i];
215 examplesMenu.add(new JMenuItem(new AbstractAction(fileName) {
216 public void actionPerformed(ActionEvent e) {
217 loadGraph("resource/graphs/" + fileName);
218 }
219 }));
220 }
221
222 JMenuBar menuBar = super.createMenuBar();
223 menuBar.add(examplesMenu);
224 return menuBar;
225 }
226
227 /**
228 * Adds an orthogonal layout editor action
229 * and notation converter actions to the default toolbar.
230 * @return the toolbar for this demo.
231 */
232 protected JToolBar createToolBar() {
233 JToolBar toolBar = super.createToolBar();
234 toolBar.addSeparator();
235 toolBar.add(createActionControl(createOrthogonalLayoutAction()));
236 toolBar.addSeparator();
237 toolBar.add(createCrowsFootNotationAction());
238 toolBar.addSeparator(TOOLBAR_SMALL_SEPARATOR);
239 toolBar.add(createChenNotationAction());
240 return toolBar;
241 }
242
243 /**
244 * Creates an action to trigger a conversion to Crow's Foot notation.
245 * @return the converter action
246 */
247 private Action createCrowsFootNotationAction(){
248 Action action = new AbstractAction("Convert to Crow's Foot") {
249
250 public void actionPerformed(ActionEvent e){
251 Graph2D graph = view.getGraph2D();
252 try{
253 graph.firePreEvent();
254 ErdNotationConverter.convertToCrowFoot(graph);
255 module.start(view.getGraph2D());
256 }finally {
257 graph.firePostEvent();
258 }
259
260 view.fitContent();
261 view.updateView();
262 }
263 };
264
265 return action;
266 }
267
268 /**
269 * Creates an action to trigger a conversion to Chen notation.
270 * @return the converter action
271 */
272 private Action createChenNotationAction(){
273 Action action = new AbstractAction("Convert to Chen") {
274
275 public void actionPerformed(ActionEvent e){
276 Graph2D graph = view.getGraph2D();
277 try{
278 graph.firePreEvent();
279 ErdNotationConverter.convertToChen(graph);
280 module.start(view.getGraph2D());
281 }finally {
282 graph.firePostEvent();
283 }
284
285 view.fitContent();
286 view.updateView();
287 }
288 };
289
290 return action;
291 }
292
293 /**
294 * Creates an action that shows an editor to adjust and execute orthogonal
295 * layout.
296 * @return the orthogonal layout action
297 */
298 private Action createOrthogonalLayoutAction() {
299 Action action = new AbstractAction("Layout") {
300 public void actionPerformed(ActionEvent e) {
301 OptionSupport.showDialog(module, view.getGraph2D(), true, view.getFrame());
302 }
303 };
304 action.putValue(Action.SHORT_DESCRIPTION, "Configure and run the layout algorithm");
305 action.putValue(Action.SMALL_ICON, SHARED_LAYOUT_ICON);
306
307 return action;
308 }
309
310 /**
311 * Creates a panel which contains the specified component and a title on top.
312 * @param content the Component that will be shown in the panel
313 * @param title the text that will be displayed on top of the panel
314 * @return the panel
315 */
316 protected JPanel createTitledPanel(JComponent content, String title) {
317 JLabel label = new JLabel(title);
318 label.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
319 label.setBackground(new Color(231, 219, 182));
320 label.setOpaque(true);
321 label.setForeground(Color.DARK_GRAY);
322 label.setFont(label.getFont().deriveFont(Font.BOLD));
323 label.setFont(label.getFont().deriveFont(13.0f));
324
325 JPanel panel = new JPanel();
326 panel.setLayout(new BorderLayout());
327 panel.add(label, BorderLayout.NORTH);
328 panel.add(content, BorderLayout.CENTER);
329 return panel;
330 }
331
332 /**
333 * Creates a view of the graph that supports label editing on double-click.
334 */
335 protected Graph2DView createGraphView() {
336 Graph2DView view = new FlowchartView();
337 for (Iterator iterator = view.getViewModes(); iterator.hasNext(); ) {
338 final Object next = iterator.next();
339 if (next instanceof EditMode) {
340 final EditMode editMode = (EditMode) next;
341 editMode.setCyclicSelectionEnabled(true);
342 editMode.setPopupMode(new EntityRelationshipPopupMode());
343 }
344 }
345 view.addViewMode(new ViewMode(){
346
347 // Reacts to double-click on nodes/labels by presenting a label editor
348 public void mouseClicked(double x, double y) {
349 if(lastClickEvent != null && lastClickEvent.getClickCount() == 2){
350 final HitInfo hitInfo = getHitInfo(x, y);
351 if (hitInfo.hasHitNodeLabels()) {
352 view.openLabelEditor(hitInfo.getHitNodeLabel(),x,y,new LabelChangeHandler(), true);
353 }else {
354 if (hitInfo.hasHitNodes()){
355 final NodeRealizer realizer = view.getGraph2D().getRealizer(hitInfo.getHitNode());
356 for(int i=realizer.labelCount(); i --> 0;){
357 final NodeLabel label = realizer.getLabel(i);
358 if (label.contains(x,y)) {
359 view.openLabelEditor(label,x,y,new LabelChangeHandler(), true);
360 return;
361 }
362 }
363 }
364 }
365 }
366 }
367 });
368 view.setFitContentOnResize(true);
369 return view;
370 }
371
372 /**
373 * Starts the <code>EntityRelationshipDemo</code>
374 * @param args --
375 */
376 public static void main(String[] args) {
377 EventQueue.invokeLater(new Runnable() {
378
379 public void run() {
380 Locale.setDefault(Locale.ENGLISH);
381 initLnF();
382 final EntityRelationshipDemo demo = new EntityRelationshipDemo();
383 demo.start();
384 }
385 });
386 }
387
388 /**
389 * This handler listens for label changes and adjusts the node size to
390 * the label size.
391 */
392 private class LabelChangeHandler implements PropertyChangeListener {
393 public void propertyChange(PropertyChangeEvent e) {
394 final Object source = e.getSource();
395 if (source instanceof NodeLabel){
396 NodeLabel srcLabel = (NodeLabel) source;
397 NodeRealizer realizer = view.getGraph2D().getRealizer(srcLabel.getNode());
398 if(ErdRealizerFactory.isBigEntityRealizer(realizer)
399 || ErdRealizerFactory.isSmallEntityRealizer(realizer)){
400 double newHeight = 0;
401 double newWidth = 0;
402 for (int i=0; i < realizer.labelCount(); i++){
403 newHeight += realizer.getLabel(i).getBox().getHeight();
404 newWidth = Math.max(newWidth, realizer.getLabel(i).getBox().getWidth());
405 }
406 if(newHeight > realizer.getHeight()) {
407 realizer.setHeight(newHeight + 15);
408 }
409 if(newWidth > realizer.getWidth()) {
410 realizer.setWidth(newWidth + 15);
411 }
412 }
413 }
414 }
415 }
416 }
417