00001 package cz.vutbr.fit.dudka.SGVis.Visual;
00002
00011 import java.net.URL;
00012 import java.util.HashSet;
00013 import java.util.Iterator;
00014
00015 import javax.swing.event.ChangeListener;
00016
00017 import prefuse.Constants;
00018 import prefuse.Visualization;
00019 import prefuse.action.ActionList;
00020 import prefuse.action.GroupAction;
00021 import prefuse.action.ItemAction;
00022 import prefuse.action.RepaintAction;
00023 import prefuse.action.animate.ColorAnimator;
00024 import prefuse.action.animate.PolarLocationAnimator;
00025 import prefuse.action.animate.QualityControlAnimator;
00026 import prefuse.action.animate.VisibilityAnimator;
00027 import prefuse.action.assignment.ColorAction;
00028 import prefuse.action.assignment.FontAction;
00029 import prefuse.action.layout.CollapsedSubtreeLayout;
00030 import prefuse.action.layout.graph.NodeLinkTreeLayout;
00031 import prefuse.activity.SlowInSlowOutPacer;
00032 import prefuse.data.Graph;
00033 import prefuse.data.Node;
00034 import prefuse.data.Table;
00035 import prefuse.data.expression.ColumnExpression;
00036 import prefuse.data.tuple.TupleSet;
00037 import prefuse.render.AbstractShapeRenderer;
00038 import prefuse.render.DefaultRendererFactory;
00039 import prefuse.render.EdgeRenderer;
00040 import prefuse.render.LabelRenderer;
00041 import prefuse.util.ColorLib;
00042 import prefuse.util.FontLib;
00043 import prefuse.util.collections.IntIterator;
00044 import prefuse.visual.VisualItem;
00045 import prefuse.visual.expression.InGroupPredicate;
00046 import cz.vutbr.fit.dudka.SGVis.Config;
00047 import cz.vutbr.fit.dudka.SGVis.Data.Relation;
00048 import cz.vutbr.fit.dudka.SGVis.Data.RelationStorage;
00049
00058 public class GraphView {
00059
00060 private RelationStorage storage;
00061 private Table table;
00062 private Graph graph;
00063 private NodeTable nodeTable;
00064 private Visualization vis;
00065 private StatusManager status;
00066 private HashSet<String> expandedHosts;
00067
00068 public GraphView() {
00069 vis = new Visualization();
00070 status = new StatusManager(this);
00071 storage = new RelationStorage();
00072 nodeTable = new NodeTable();
00073 expandedHosts = new HashSet<String>();
00074
00075 table = new Table();
00076 table.addColumn("text", String.class);
00077 table.addColumn("type", String.class);
00078 graph = new Graph(table,true);
00079
00080
00081 vis.add("tree", graph);
00082 vis.setInteractive("tree.edges", null, false);
00083
00084
00085 LabelRenderer nodeRenderer = new LabelRenderer("text");
00086 nodeRenderer.setRenderType(AbstractShapeRenderer.RENDER_TYPE_FILL);
00087 nodeRenderer.setHorizontalAlignment(Constants.CENTER);
00088 nodeRenderer.setRoundedCorner(8,8);
00089 EdgeRenderer edgeRenderer = new EdgeRenderer(
00090 Constants.EDGE_TYPE_LINE,
00091 Constants.EDGE_ARROW_FORWARD
00092 );
00093 edgeRenderer.setArrowHeadSize(6, 10);
00094 DefaultRendererFactory rf = new DefaultRendererFactory(nodeRenderer);
00095 rf.add(new InGroupPredicate("tree.edges"), edgeRenderer);
00096 vis.setRendererFactory(rf);
00097
00098
00099
00100
00101 ItemAction nodeColor = new NodeColorAction("tree.nodes");
00102 ItemAction textColor = new TextColorAction("tree.nodes");
00103 vis.putAction("textColor", textColor);
00104
00105 ColorAction edgeColor = new ColorAction("tree.edges",
00106 VisualItem.STROKECOLOR, ColorLib.rgb(200,200,200));
00107
00108 ColorAction arrowColor = new ColorAction("tree.edges",
00109 VisualItem.FILLCOLOR, ColorLib.rgb(100,100,100));
00110
00111
00112 FontAction fonts = new FontAction("tree.nodes",
00113 FontLib.getFont("Tahoma", 10));
00114
00115
00116
00117 ActionList recolor = new ActionList();
00118 recolor.add(nodeColor);
00119 recolor.add(textColor);
00120 vis.putAction("recolor", recolor);
00121
00122
00123 ActionList repaint = new ActionList();
00124 repaint.add(recolor);
00125 repaint.add(new RepaintAction());
00126 vis.putAction("repaint", repaint);
00127
00128
00129 ActionList animatePaint = new ActionList(400);
00130 animatePaint.add(new ColorAnimator("tree.nodes"));
00131 animatePaint.add(new RepaintAction());
00132 vis.putAction("animatePaint", animatePaint);
00133
00134
00135 NodeLinkTreeLayout treeLayout = new NodeLinkTreeLayout("tree");
00136 vis.putAction("treeLayout", treeLayout);
00137 CollapsedSubtreeLayout subLayout = new CollapsedSubtreeLayout("tree");
00138 vis.putAction("subLayout", subLayout);
00139
00140
00141 ActionList filter = new ActionList();
00142 filter.add(new TreeRootAction("tree"));
00143 filter.add(fonts);
00144 filter.add(treeLayout);
00145 filter.add(subLayout);
00146 filter.add(textColor);
00147 filter.add(nodeColor);
00148 filter.add(edgeColor);
00149 filter.add(arrowColor);
00150 vis.putAction("filter", filter);
00151
00152
00153 ActionList animate = new ActionList(1250);
00154 animate.setPacingFunction(new SlowInSlowOutPacer());
00155 animate.add(new QualityControlAnimator());
00156 animate.add(new VisibilityAnimator("tree"));
00157 animate.add(new PolarLocationAnimator("tree.nodes", "linear"));
00158 animate.add(new ColorAnimator("tree.nodes"));
00159 animate.add(new RepaintAction());
00160 vis.putAction("animate", animate);
00161 vis.alwaysRunAfter("filter", "animate");
00162 }
00163
00168 public Visualization getVisualization() {
00169 return vis;
00170 }
00171
00176 public IntIterator edgeRows(int row) {
00177 return graph.edgeRows(row);
00178 }
00179
00184 public RelationStorage getStorage() {
00185 return storage;
00186 }
00187
00191 public int getNodeCount() {
00192 return graph.getNodeCount();
00193 }
00194
00198 public int getEdgeCount() {
00199 return graph.getEdgeCount();
00200 }
00201
00206 public void clear() {
00207 LookupWorker.killAll();
00208 vis.cancel("animatePaint");
00209 graph.clear();
00210 table.clear();
00211 storage = new RelationStorage();
00212 nodeTable.clear();
00213 expandedHosts.clear();
00214 vis.run("repaint");
00215 }
00216
00217 private void updateVisual() {
00218 vis.cancel("animatePaint");
00219 final String errText = "Too many nodes opened. Try to collapse some hosts, if you experience long latency.";
00220 if (graph.getNodeCount()>Config.getNodeCountWarn())
00221 status.showErrror(errText);
00222 else if (status.getStatus().equals(errText))
00223 status.showStatus(" ");
00224 vis.run("filter");
00225 }
00226
00232 public void removeNode(int node) {
00233 removeNodePrivate(node);
00234 vis.run("repaint");
00235 }
00236
00237 private void removeNodePrivate(int node) {
00238 nodeTable.remove(node);
00239 graph.removeNode(node);
00240 }
00241
00242 private void removeGroup(String host) {
00243 if (nodeTable.contains(host)) {
00244 this.removeNodePrivate(nodeTable.getId(host));
00245 } else {
00246 for (URL url: storage.getUrls(host)) {
00247 String urlString = url.toString();
00248 if (nodeTable.contains(urlString)) {
00249 this.removeNodePrivate(nodeTable.getId(urlString));
00250 }
00251 }
00252 }
00253 this.expandedHosts.remove(host);
00254 }
00255
00256
00257 private void addGroup(String host, boolean collapsed) {
00258 if (collapsed) {
00259 if (nodeTable.contains(host))
00260 return;
00261
00262 int row = table.addRow();
00263 table.setString(row, 0, host);
00264 table.setString(row, 1, "G");
00265 nodeTable.add(host, row);
00266
00267 for (URL url: storage.getUrls(host)) {
00268 this.addNodeEdges(url);
00269 }
00270 } else {
00271 for (URL url: storage.getUrls(host)) {
00272 String urlString = url.toString();
00273 if (nodeTable.contains(urlString))
00274 continue;
00275
00276 int row = table.addRow();
00277 table.setString(row, 0, urlString);
00278 table.setString(row, 1, "U");
00279 nodeTable.add(urlString, row);
00280
00281 this.addNodeEdges(url);
00282 }
00283 this.expandedHosts.add(host);
00284 }
00285 }
00286
00287 private void addNodeEdges(URL url) {
00288 for (Relation rel: storage.getAllIncidents(url)) {
00289
00290 String fromUrl = rel.from.toString();
00291 String fromHost = RelationStorage.urlToHost(rel.from);
00292 String toUrl = rel.to.toString();
00293 String toHost = RelationStorage.urlToHost(rel.to);
00294
00295
00296 int s;
00297 if (nodeTable.contains(fromUrl))
00298 s = nodeTable.getId(fromUrl);
00299 else if (nodeTable.contains(fromHost))
00300 s = nodeTable.getId(fromHost);
00301 else
00302 continue;
00303
00304
00305 int t;
00306 if (nodeTable.contains(toUrl))
00307 t = nodeTable.getId(toUrl);
00308 else if (nodeTable.contains(toHost))
00309 t = nodeTable.getId(toHost);
00310 else
00311 continue;
00312
00313 if (s!=t) {
00314 graph.addEdge(s,t);
00315 }
00316 }
00317 }
00318
00323 public void collapseHost(String host) {
00324 vis.cancel("animatePaint");
00325 this.removeGroup(host);
00326 this.addGroup(host, true);
00327 updateVisual();
00328 }
00329
00334 public void expandHost(String host) {
00335 vis.cancel("animatePaint");
00336 this.removeGroup(host);
00337 this.addGroup(host, false);
00338 updateVisual();
00339 }
00340
00344 public void zoomToFit() {
00345 vis.cancel("animatePaint");
00346 vis.run("zoomToFit");
00347 }
00348
00353 public void lookup(URL url) {
00354 status.setStatus("Looking for " + url.toString());
00355 LookupWorker.run(url, this);
00356 }
00357
00363 void handleResponse(RelationStorage current, URL url) {
00364
00365 storage.add(current);
00366
00367 int count = graph.getNodeCount();
00368 boolean bEmpty = true;
00369 for(String host: current.getHosts()) {
00370
00371 this.addGroup(host, !expandedHosts.contains(host));
00372 bEmpty = false;
00373 }
00374
00375 if (bEmpty)
00376 status.showStatus("Empty lookup for "+url.toString());
00377 else
00378 status.showStatus("Lookup complete for "+url.toString());
00379
00380 if (count != graph.getNodeCount())
00381 this.updateVisual();
00382 }
00383
00388 public void addStatusListener(ChangeListener listener) {
00389 status.addListener(listener);
00390 }
00391
00396 public void showStatus(String text) {
00397 status.showStatus(text);
00398 }
00399
00403 public String getStatus() {
00404 return status.getStatus();
00405 }
00406
00410 public boolean isStatusError() {
00411 return status.isStatusError();
00412 }
00413
00418 void lookupError(URL url) {
00419 status.showErrror("Lookup failed for "+url.toString());
00420 }
00421
00422
00423
00424
00429 private class TreeRootAction extends GroupAction {
00430 public TreeRootAction(String graphGroup) {
00431 super(graphGroup);
00432 }
00433 public void run(double frac) {
00434 TupleSet focus = m_vis.getGroup(Visualization.FOCUS_ITEMS);
00435 if ( focus==null || focus.getTupleCount() == 0 )
00436 return;
00437 Graph g = (Graph)m_vis.getGroup(m_group);
00438 Node f = null;
00439 Iterator tuples = focus.tuples();
00440 while (tuples.hasNext() && !g.containsTuple(f=(Node)tuples.next()))
00441 f = null;
00442 if ( f == null )
00443 return;
00444 g.getSpanningTree(f);
00445 }
00446 }
00447
00451 private class NodeColorAction extends ColorAction {
00452 public NodeColorAction(String group) {
00453 super(group, VisualItem.FILLCOLOR, ColorLib.rgba(255,255,255,0));
00454 add(new ColumnExpression("_hover"), ColorLib.rgb(210,210,210));
00455 add(new ColumnExpression("_highlight"), ColorLib.rgb(210,210,210));
00456 }
00457
00458 }
00459
00463 private class TextColorAction extends ColorAction {
00464 public TextColorAction(String group) {
00465 super(group, VisualItem.TEXTCOLOR, ColorLib.gray(0));
00466 add(new ColumnExpression("_hover"), ColorLib.rgb(255,0,0));
00467 }
00468 }
00469
00470 }