1 /*
2  * $Id: JGraphExampleGraph.java,v 1.1 2009/09/25 15:17:49 david Exp $
3  * Copyright (c) 2001-2005, Gaudenz Alder
4  *
5  * All rights reserved.
6  *
7  * This file is licensed under the JGraph software license, a copy of which
8  * will have been provided to you in the file LICENSE at the root of your
9  * installation directory. If you are unable to locate this file please
10  * contact JGraph sales for another copy.
11  */
12 package com.jgraph.layout;
13 
14 import java.awt.event.ActionEvent;
15 import java.awt.geom.Rectangle2D;
16 import java.util.HashSet;
17 import java.util.Hashtable;
18 import java.util.Iterator;
19 import java.util.Map;
20 import java.util.Set;
21 
22 import javax.swing.AbstractAction;
23 import javax.swing.Timer;
24 
25 import org.jgraph.example.GraphEd.MyGraph;
26 import org.jgraph.graph.CellView;
27 import org.jgraph.graph.DefaultGraphModel;
28 import org.jgraph.graph.GraphConstants;
29 import org.jgraph.graph.GraphLayoutCache;
30 import org.jgraph.graph.GraphModel;
31 
32 /**
33  * A graph that can animate changes (morph).
34  */
35 public class JGraphExampleGraph extends MyGraph {
36 
37 	/**
38 	 * Specifies the delay between morphing steps
39 	 */
40 	protected int delay = 30;
41 
42 	/**
43 	 * Specified the number of steps in the morphing process
44 	 */
45 	protected int steps = 10;
46 
47 	/**
48 	 * Specified the current morhing step
49 	 */
50 	protected int step = 0;
51 
52 	/**
53 	 * Stores the previous bounds of morphed cells
54 	 */
55 	protected Map oldBounds = new Hashtable();
56 
57 	/**
58 	 * Stores the future bounds of morphed cells
59 	 */
60 	protected Map newBounds = new Hashtable();
61 
62 	/**
63 	 * Stores the previous collective bounds of the morphed cells
64 	 */
65 	protected Rectangle2D oldClipBounds;
66 
67 	/**
68 	 * Stores the new collective bounds of the morphed cells
69 	 */
70 	protected Rectangle2D newClipBounds;
71 
72 	/**
73 	 * Constructs an example graph for the specified graph model.
74 	 *
75 	 * @param model
76 	 */
JGraphExampleGraph(GraphModel model)77 	public JGraphExampleGraph(GraphModel model) {
78 		super(model);
79 		setGraphLayoutCache(new JGraphExampleLayoutCache(this));
80 	}
81 
JGraphExampleGraph(GraphModel model, GraphLayoutCache cache)82 	public JGraphExampleGraph(GraphModel model, GraphLayoutCache cache) {
83 		super(model, cache);
84 	}
85 
morph(final Map nestedMap, Set nomorph)86 	public void morph(final Map nestedMap, Set nomorph) {
87 		Set parents = initMorphing(nestedMap, nomorph);
88 		if (!newBounds.isEmpty()) {
89 			final Object[] cells = parents.toArray();
90 			Object[] edges = DefaultGraphModel.getEdges(getModel(), cells)
91 					.toArray();
92 			final CellView[] edgeViews = getGraphLayoutCache()
93 					.getMapping(edges);
94 
95 			// Execute the morphing. This spawns a timer
96 			// to not block the dispatcher thread (repaint)
97 			Timer timer = new Timer(delay, new AbstractAction() {
98 				public void actionPerformed(ActionEvent e) {
99 					if (step >= steps) {
100 						Timer timer = (Timer) e.getSource();
101 						timer.stop();
102 						restore();
103 						getGraphLayoutCache().edit(nestedMap, null, null, null);
104 					} else {
105 						step++;
106 						Iterator it = newBounds.keySet().iterator();
107 						while (it.hasNext()) {
108 							morphCell(it.next(), step);
109 						}
110 						getGraphLayoutCache().refresh(edgeViews, false);
111 						addOffscreenDirty(oldClipBounds);
112 						addOffscreenDirty(newClipBounds);
113 						oldClipBounds = newClipBounds = null;
114 						repaint();
115 					}
116 				}
117 			});
118 			timer.start();
119 		} else {
120 			getGraphLayoutCache().edit(nestedMap, null, null, null);
121 		}
122 	}
123 
124 	/**
125 	 * Initial step of the morphing process. Analyses the arguments and prepares
126 	 * internal datastructures for the morphing.
127 	 *
128 	 * @return Returns the set of all cells and ancestors to determine the dirty
129 	 *         region and connected edges.
130 	 */
initMorphing(Map nestedMap, Set nomorph)131 	protected Set initMorphing(Map nestedMap, Set nomorph) {
132 		oldBounds.clear();
133 		newBounds.clear();
134 		step = 0;
135 		Iterator it = nestedMap.entrySet().iterator();
136 		while (it.hasNext()) {
137 			Map.Entry entry = (Map.Entry) it.next();
138 			Object cell = entry.getKey();
139 			Map attrs = (Map) entry.getValue();
140 			Rectangle2D rect = GraphConstants.getBounds(attrs);
141 			if (rect != null) {
142 				Rectangle2D old = getCellBounds(cell);
143 				if (old != null && !old.equals(rect)) {
144 					newBounds.put(cell, rect);
145 					oldBounds.put(cell, old.clone());
146 				}
147 			}
148 		}
149 
150 		// Make sure the cells in nomorph are at their future
151 		// locations and fetches the set of all parents.
152 		HashSet parents = new HashSet();
153 		it = oldBounds.keySet().iterator();
154 		while (it.hasNext()) {
155 			Object cell = it.next();
156 			Object parent = getModel().getParent(cell);
157 			if (nomorph != null && nomorph.contains(cell)) {
158 				Rectangle2D rect = (Rectangle2D) newBounds.remove(cell);
159 				setCellBounds(cell, rect);
160 			}
161 			while (parent != null) {
162 				parents.add(parent);
163 				parent = getModel().getParent(parent);
164 			}
165 		}
166 		parents.addAll(oldBounds.keySet());
167 		return parents;
168 	}
169 
170 	/**
171 	 * Restore the old bounds values for all cells. (This is required at the end
172 	 * of the morphing animation and before calling the edit method for the
173 	 * command history to work correctly.)
174 	 *
175 	 */
restore()176 	protected void restore() {
177 		Iterator it = oldBounds.entrySet().iterator();
178 		while (it.hasNext()) {
179 			Map.Entry entry = (Map.Entry) it.next();
180 			setCellBounds(entry.getKey(), (Rectangle2D) entry.getValue());
181 		}
182 	}
183 
184 	/**
185 	 * Performs the morph positionon a cell for a particular step
186 	 *
187 	 * @param cell
188 	 *            the cell being morphed
189 	 * @param step
190 	 *            the number step into morph process
191 	 */
morphCell(Object cell, int step)192 	protected void morphCell(Object cell, int step) {
193 		Rectangle2D old = (Rectangle2D) oldBounds.get(cell);
194 		Rectangle2D rect = (Rectangle2D) newBounds.get(cell);
195 		// Add to total clip bounds
196 		if (old != null) {
197 			if (oldClipBounds == null) {
198 				oldClipBounds = (Rectangle2D)old.clone();
199 			} else {
200 				oldClipBounds.add(old);
201 			}
202 		}
203 		if (rect != null) {
204 			if (newClipBounds == null) {
205 				newClipBounds = (Rectangle2D)rect.clone();
206 			} else {
207 				newClipBounds.add(rect);
208 			}
209 		}		double dx = (rect.getX() - old.getX()) * step / steps;
210 		double dy = (rect.getY() - old.getY()) * step / steps;
211 		Rectangle2D pos = new Rectangle2D.Double(old.getX() + dx, old.getY()
212 				+ dy, old.getWidth(), old.getHeight());
213 		setCellBounds(cell, pos);
214 	}
215 
216 	/**
217 	 * Set the new cell bounds
218 	 *
219 	 * @param cell
220 	 *            the cell whose bounds to set
221 	 * @param bounds
222 	 *            the new bounds of the cell
223 	 */
setCellBounds(Object cell, Rectangle2D bounds)224 	protected void setCellBounds(Object cell, Rectangle2D bounds) {
225 		Rectangle2D rect = getCellBounds(cell);
226 		if (rect != null && bounds != null) {
227 			rect.setFrame(bounds.getX(), bounds.getY(), bounds.getWidth(),
228 					bounds.getHeight());
229 			CellView view = getGraphLayoutCache().getMapping(cell, false);
230 			if (view != null)
231 				view.update(getGraphLayoutCache());
232 		}
233 	}
234 
235 }
236