001 package nl.cwi.sen1.gui.plugin;
002
003 import java.awt.BorderLayout;
004 import java.awt.Color;
005 import java.awt.Point;
006 import java.awt.event.MouseEvent;
007 import java.awt.geom.AffineTransform;
008 import java.awt.geom.NoninvertibleTransformException;
009 import java.util.HashSet;
010 import java.util.Iterator;
011 import java.util.Set;
012
013 import javax.swing.AbstractAction;
014 import javax.swing.JPanel;
015
016 import nl.cwi.sen1.graph.types.Attribute;
017 import nl.cwi.sen1.util.Preferences;
018 import prefuse.Constants;
019 import prefuse.Display;
020 import prefuse.Visualization;
021 import prefuse.action.Action;
022 import prefuse.action.ActionList;
023 import prefuse.action.RepaintAction;
024 import prefuse.action.animate.LocationAnimator;
025 import prefuse.action.animate.PolarLocationAnimator;
026 import prefuse.action.assignment.ColorAction;
027 import prefuse.action.assignment.FontAction;
028 import prefuse.action.layout.CircleLayout;
029 import prefuse.action.layout.RandomLayout;
030 import prefuse.action.layout.graph.ForceDirectedLayout;
031 import prefuse.action.layout.graph.FruchtermanReingoldLayout;
032 import prefuse.action.layout.graph.NodeLinkTreeLayout;
033 import prefuse.action.layout.graph.RadialTreeLayout;
034 import prefuse.activity.Activity;
035 import prefuse.activity.SlowInSlowOutPacer;
036 import prefuse.controls.Control;
037 import prefuse.controls.ControlAdapter;
038 import prefuse.controls.DragControl;
039 import prefuse.controls.FocusControl;
040 import prefuse.controls.PanControl;
041 import prefuse.controls.ToolTipControl;
042 import prefuse.controls.ZoomControl;
043 import prefuse.controls.ZoomToFitControl;
044 import prefuse.data.Graph;
045 import prefuse.data.Tuple;
046 import prefuse.data.event.TupleSetListener;
047 import prefuse.data.tuple.TupleSet;
048 import prefuse.render.DefaultRendererFactory;
049 import prefuse.render.EdgeRenderer;
050 import prefuse.render.LabelRenderer;
051 import prefuse.util.ColorLib;
052 import prefuse.util.display.ExportDisplayAction;
053 import prefuse.util.force.DragForce;
054 import prefuse.util.force.ForceSimulator;
055 import prefuse.util.force.NBodyForce;
056 import prefuse.util.force.SpringForce;
057 import prefuse.visual.NodeItem;
058 import prefuse.visual.VisualItem;
059 import prefuse.visual.expression.InGroupPredicate;
060
061 public class GraphPanel extends JPanel {
062 private String type;
063
064 private String id;
065
066 private Display display;
067
068 private Visualization vis;
069
070 private GraphPanelListener listener;
071
072 private Activity currentLayout;
073
074 private Activity currentAnimation;
075
076 private Set<String> layouts;
077
078 private EdgeRenderer edgeRenderer;
079
080 private ForceSimulator forceSimulator;
081
082 private Boolean closable;
083
084 public void setClosable(Boolean closable) {
085 this.closable = closable;
086 }
087
088 public Boolean isClosable() {
089 return closable;
090 }
091
092 public GraphPanel(String type, String id, final Preferences prefs,
093 boolean closable) {
094 this.id = id;
095 this.type = type;
096 this.closable = closable;
097 setLayout(new BorderLayout());
098
099 vis = new Visualization();
100
101 LabelRenderer lr = new GraphNodeRenderer(GraphConstants.LABEL);
102 lr.setHorizontalPadding(prefs.getInt(GraphConstants.NODE_BORDER_WIDTH));
103 lr.setVerticalPadding(prefs.getInt(GraphConstants.NODE_BORDER_HEIGHT));
104 edgeRenderer = new GraphEdgeRenderer(Constants.EDGE_TYPE_LINE,
105 Constants.EDGE_ARROW_FORWARD);
106 vis.setRendererFactory(new DefaultRendererFactory(lr, edgeRenderer));
107
108 ColorAction nStroke = new ColorAction(GraphConstants.NODES,
109 VisualItem.STROKECOLOR);
110 nStroke.setDefaultColor(prefs.getColor(GraphConstants.NODE_FOREGROUND)
111 .getRGB());
112 nStroke.add(new InGroupPredicate(Visualization.FOCUS_ITEMS), prefs
113 .getColor(GraphConstants.NODE_SELECTED_FOREGROUND).getRGB());
114
115 ColorAction nFill = new GraphColorAction(GraphConstants.NODES,
116 GraphConstants.FILLCOLOR, VisualItem.FILLCOLOR);
117 nFill.setDefaultColor(prefs.getColor(GraphConstants.NODE_BACKGROUND)
118 .getRGB());
119 nFill.add(new InGroupPredicate(Visualization.FOCUS_ITEMS), prefs
120 .getColor(GraphConstants.NODE_SELECTED_BACKGROUND).getRGB());
121
122 ColorAction eStroke = new ColorAction(GraphConstants.EDGES,
123 VisualItem.STROKECOLOR);
124 eStroke.setDefaultColor(Color.BLACK.getRGB());
125
126 ColorAction eFill = new ColorAction(GraphConstants.EDGES,
127 VisualItem.FILLCOLOR);
128 eStroke.setDefaultColor(Color.BLACK.getRGB());
129
130 ColorAction text = new ColorAction(GraphConstants.NODES,
131 VisualItem.TEXTCOLOR);
132 text.setDefaultColor(ColorLib.gray(50));
133
134 FontAction font = new FontAction(GraphConstants.NODES, prefs
135 .getFont(GraphConstants.NODE_FONT));
136
137 ActionList draw = new ActionList();
138 draw.add(text);
139 draw.add(nStroke);
140 draw.add(nFill);
141 draw.add(eStroke);
142 draw.add(eFill);
143 draw.add(font);
144 draw.add(new RepaintAction());
145
146 vis.putAction("draw", draw);
147
148 display = new Display(vis);
149 display.setHighQuality(true);
150 display.addControlListener(new DragControl());
151 display.addControlListener(new ZoomControl());
152 ZoomToFitControl ztf = new ZoomToFitControl();
153 ztf.setZoomOverItem(false);
154 display.addControlListener(ztf);
155 display.addControlListener(new PanControl());
156 display.addControlListener(new FocusControl());
157 display.addControlListener(new ControlAdapter() {
158 public void itemEntered(VisualItem item, MouseEvent e) {
159 if (item.isInGroup(GraphConstants.NODES)) {
160 item.setFillColor(prefs.getColor(
161 GraphConstants.NODE_HOVERED_BACKGROUND).getRGB());
162 item.setStrokeColor(prefs.getColor(
163 GraphConstants.NODE_HOVERED_FOREGROUND).getRGB());
164 item.getVisualization().repaint();
165 }
166 }
167
168 public void itemExited(VisualItem item, MouseEvent e) {
169 if (item.isInGroup(GraphConstants.NODES)) {
170 if (item.isInGroup(Visualization.FOCUS_ITEMS)) {
171 item.setFillColor(prefs.getColor(
172 GraphConstants.NODE_SELECTED_BACKGROUND)
173 .getRGB());
174 item.setStrokeColor(prefs.getColor(
175 GraphConstants.NODE_SELECTED_FOREGROUND)
176 .getRGB());
177 } else {
178 item
179 .setFillColor(item
180 .getInt(GraphConstants.FILLCOLOR));
181 item.setStrokeColor(prefs.getColor(
182 GraphConstants.NODE_FOREGROUND).getRGB());
183 }
184 item.getVisualization().repaint();
185 }
186 }
187
188 /*
189 * public void itemPressed(VisualItem item, MouseEvent e) { if
190 * (e.isPopupTrigger()) { String id =
191 * item.getString(GraphConstants.ID); if (id != null) {
192 * firePopupRequested(id, e); } } } }
193 */
194 });
195 display.addControlListener(new ToolTipControl(GraphConstants.TOOLTIP));
196
197 TupleSet focusGroup = vis.getGroup(Visualization.FOCUS_ITEMS);
198 focusGroup.addTupleSetListener(new TupleSetListener() {
199 public void tupleSetChanged(TupleSet ts, Tuple[] add, Tuple[] rem) {
200 for (Tuple t : add) {
201 VisualItem item = (VisualItem) t;
202 if (item instanceof NodeItem) {
203 fireNodeSelected(item.getString(GraphConstants.ID));
204 vis.run("draw");
205 vis.repaint();
206 }
207 }
208 }
209 });
210
211 createLayouts();
212 setPolarAnimation();
213
214 add(display, BorderLayout.CENTER);
215 }
216
217 public void addControlListener(Control cl) {
218 display.addControlListener(cl);
219 }
220
221 public AbstractAction getSaveImageAction() {
222 return new ExportDisplayAction(display);
223 }
224
225 public void setCurvedEdges() {
226 edgeRenderer.setEdgeType(Constants.EDGE_TYPE_CURVE);
227 runNow();
228 }
229
230 public void setStraightEdges() {
231 edgeRenderer.setEdgeType(Constants.EDGE_TYPE_LINE);
232 runNow();
233 }
234
235 public void setNoAnimation() {
236 ActionList animation = new ActionList(vis);
237 animation.add(new RepaintAction());
238 vis.putAction("no-animation", animation);
239 currentAnimation = animation;
240 }
241
242 public void setPolarAnimation() {
243 ActionList animation = new ActionList(vis, 1500);
244 animation.setPacingFunction(new SlowInSlowOutPacer());
245 animation.add(new PolarLocationAnimator());
246 animation.add(new RepaintAction());
247 vis.putAction("polar-animation", animation);
248 currentAnimation = animation;
249 }
250
251 public void setLinearAnimation() {
252 ActionList animation = new ActionList(vis, 1500);
253 animation.setPacingFunction(new SlowInSlowOutPacer());
254 animation.add(new LocationAnimator());
255 animation.add(new RepaintAction());
256 vis.putAction("linear-animation", animation);
257 currentAnimation = animation;
258 }
259
260 private void createLayouts() {
261 layouts = new HashSet<String>();
262
263 ActionList dot = new ActionList();
264 dot.add(new GraphDotLayout(GraphConstants.GRAPH));
265 dot.add(new RepaintAction());
266 vis.putAction("Dot", dot);
267 layouts.add("Dot");
268
269 forceSimulator = new ForceSimulator();
270 forceSimulator.addForce(new NBodyForce());
271 forceSimulator.addForce(new SpringForce());
272 forceSimulator.addForce(new DragForce());
273
274 ActionList contforce = new ActionList(Action.INFINITY);
275 contforce.add(new ForceDirectedLayout(GraphConstants.GRAPH,
276 forceSimulator, false));
277 contforce.add(new RepaintAction());
278 vis.putAction("Force", contforce);
279 layouts.add("Force");
280
281 ActionList random = new ActionList();
282 random.add(new RandomLayout());
283 random.add(new RepaintAction());
284 vis.putAction("Random", random);
285 layouts.add("Random");
286
287 ActionList circle = new ActionList();
288 circle.add(new CircleLayout(GraphConstants.NODES));
289 circle.add(new RepaintAction());
290 vis.putAction("Circle", circle);
291 layouts.add("Circle");
292
293 ActionList nodeLink = new ActionList();
294 nodeLink.add(new NodeLinkTreeLayout(GraphConstants.GRAPH));
295 nodeLink.add(new RepaintAction());
296 vis.putAction("NodeLink Horizontal", nodeLink);
297 layouts.add("NodeLink Horizontal");
298
299 ActionList nodeLinkV = new ActionList();
300 NodeLinkTreeLayout nodeLinkVlayout = new NodeLinkTreeLayout(
301 GraphConstants.GRAPH);
302 nodeLinkVlayout.setOrientation(Constants.ORIENT_TOP_BOTTOM);
303 nodeLinkV.add(nodeLinkVlayout);
304 nodeLinkV.add(new RepaintAction());
305 vis.putAction("NodeLink Vertical", nodeLinkV);
306 layouts.add("NodeLink Vertical");
307
308 ActionList radialTree = new ActionList();
309 radialTree.add(new RadialTreeLayout(GraphConstants.GRAPH));
310 radialTree.add(new RepaintAction());
311 vis.putAction("Radial", radialTree);
312 layouts.add("Radial");
313
314 ActionList funny = new ActionList();
315 funny.add(new FruchtermanReingoldLayout(GraphConstants.GRAPH));
316 funny.add(new RepaintAction());
317 vis.putAction("Funny", funny);
318 layouts.add("Funny");
319 }
320
321 public ForceSimulator getForceSimulator() {
322 return forceSimulator;
323 }
324
325 public Object[] getLayouts() {
326 return layouts.toArray();
327 }
328
329 public void setLayout(String name) {
330 Action newLayout = vis.getAction(name);
331
332 if (newLayout != null) {
333 cancel();
334 currentLayout = newLayout;
335 runNow();
336 } else {
337 throw new UnsupportedOperationException("Layout " + name
338 + " does not exist");
339 }
340 }
341
342 private void runNow() {
343 if (currentLayout != null) {
344 vis.run("draw");
345 currentLayout.run();
346 vis.repaint();
347 currentAnimation.run();
348 } else {
349 System.err.println("No current layout!");
350 }
351 }
352
353 private void cancel() {
354 if (currentLayout != null) {
355 currentLayout.cancel();
356 }
357 }
358
359 protected void resize() {
360 setSize(getParent().getWidth(), getParent().getHeight());
361 }
362
363 void setGraphPanelListener(GraphPanelListener l) {
364 this.listener = l;
365 }
366
367 protected void fireNodeSelected(String selectedNodeId) {
368 listener.nodeSelected(selectedNodeId);
369 }
370
371 void setGraph(Graph g) {
372 vis.removeGroup(GraphConstants.GRAPH);
373 vis.addGraph(GraphConstants.GRAPH, g);
374 Activity oldAnimation = currentAnimation;
375 setNoAnimation();
376 runNow();
377 currentAnimation = oldAnimation;
378 }
379
380 public void updateNode(String nodeId, Attribute attr) {
381 VisualItem node = findNode(nodeId);
382 if (node != null) {
383 GraphAdapter.updateNode(node, attr);
384 node.setValidated(false);
385 vis.run("draw");
386 vis.repaint();
387 }
388 }
389
390 public void setSelectedNode(String nodeId) {
391 VisualItem node = findNode(nodeId);
392 if (node != null) {
393 TupleSet focusGroup = vis.getGroup(Visualization.FOCUS_ITEMS);
394 focusGroup.setTuple(node);
395 vis.run("draw");
396 vis.repaint();
397 Point p = new Point((int)node.getX(), (int)node.getY());
398 display.animatePanToAbs(p, 2000);
399 }
400
401 }
402
403 private VisualItem findNode(String nodeId) {
404 Graph g = (Graph) vis.getGroup(GraphConstants.GRAPH);
405 if (g != null) {
406 Iterator<?> nodeIter = g.nodes();
407 while (nodeIter.hasNext()) {
408 VisualItem node = (VisualItem) nodeIter.next();
409 if (node.getString(GraphConstants.ID).equals(nodeId)) {
410 return node;
411 }
412 }
413 }
414
415 return null;
416 }
417
418 public void restoreZoomAndPan() {
419 try {
420 display.setTransform(new AffineTransform());
421 display.repaint();
422 } catch (NoninvertibleTransformException e) {
423 // this does not happen
424 }
425 }
426
427 public String getId() {
428 return id;
429 }
430
431 public String getType() {
432 return type;
433 }
434 }