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 }