/* * (C) Copyright IBM Corp. 2009 * * LICENSE: Eclipse Public License v1.0 * http://www.eclipse.org/legal/epl-v10.html */ package com.ibm.gaiandb.apps.dashboard; import java.awt.Color; import java.awt.Graphics2D; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import prefuse.Constants; import prefuse.Display; import prefuse.Visualization; import prefuse.action.Action; import prefuse.action.ActionList; import prefuse.action.RepaintAction; import prefuse.action.assignment.ColorAction; import prefuse.action.assignment.ShapeAction; import prefuse.action.assignment.SizeAction; import prefuse.action.layout.graph.ForceDirectedLayout; import prefuse.activity.Activity; import prefuse.controls.Control; import prefuse.controls.ControlAdapter; import prefuse.controls.DragControl; import prefuse.controls.FocusControl; import prefuse.controls.PanControl; import prefuse.controls.WheelZoomControl; import prefuse.controls.ZoomControl; import prefuse.controls.ZoomToFitControl; import prefuse.data.Graph; import prefuse.data.Schema; import prefuse.data.Table; import prefuse.data.tuple.TupleSet; import prefuse.render.DefaultRendererFactory; import prefuse.render.EdgeRenderer; import prefuse.render.LabelRenderer; import prefuse.render.RendererFactory; import prefuse.render.ShapeRenderer; import prefuse.util.GraphicsLib; import prefuse.util.display.DisplayLib; import prefuse.util.force.DragForce; import prefuse.util.force.ForceSimulator; import prefuse.util.force.NBodyForce; import prefuse.util.force.SpringForce; import prefuse.visual.EdgeItem; import prefuse.visual.VisualItem; import com.ibm.gaiandb.tools.NetworkLinksAnalyser; public class LtAndDsGraph extends Display { // Use PROPRIETARY notice if class contains a main() method, otherwise use // COPYRIGHT notice. public static final String COPYRIGHT_NOTICE = "(c) Copyright IBM Corp. 2011"; private static final long serialVersionUID = 0L; private static final String GROUP = "data"; private static final String NODE_GROUP = GROUP + ".nodes"; private static final String EDGE_GROUP = GROUP + ".edges"; private static final String NODE_ID_FIELD = "node"; private static final String SOURCE_ID_FIELD = "source"; private static final String TARGET_ID_FIELD = "target"; private static final String NODE_NAME_FIELD = "name"; private static final Color TEXT_COLOR = Color.WHITE; private static final Color DEFAULT_NODE_COLOR = Color.BLACK; private static final Color DEFAULT_LT_COLOR = Color.LIGHT_GRAY; private static final Color EDGE_COLOR = Color.LIGHT_GRAY; private static final Color SELECTED_EDGE_COLOR = Color.BLACK; private static final int EDGE_THICKNESS = 1; private static final int SPRING_LENGTH_MULTIPLIER = 100; NetworkLinksAnalyser nla = new NetworkLinksAnalyser(); public static class Node implements Comparable<Node> { private static final Map<String, Node> INSTANCES = new HashMap<String, Node>(); private final String name; private Color color = null; private Node(String name) { this.name = name; } public static synchronized Node getInstance(String name) { Node instance = INSTANCES.get(name); if (null == instance) { instance = new Node(name); INSTANCES.put(name, instance); } return instance; } public static Set<String> getAllNodeNames() { return INSTANCES.keySet(); } public static void removeInstance(String name) { INSTANCES.remove(name); } public String getName() { return name; } public Color getColor() { return color; } public void setColor(Color color) { this.color = color; } public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof Node)) { return false; } return name.equals(((Node) o).name); } public int hashCode() { return name.hashCode(); } public int compareTo(Node o) { return name.compareToIgnoreCase(o.name); } public String toString() { return name; } } public static class Edge implements Comparable<Edge> { public final Node source; public final Node target; public Edge(String sourceName, String targetName) { Node source = Node.getInstance(sourceName); Node target = Node.getInstance(targetName); int comparison = source.compareTo(target); if (comparison <= 0) { this.source = source; this.target = target; } else { this.source = target; this.target = source; } } public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof Edge)) { return false; } Edge other = (Edge) o; return (source.equals(other.source) && target.equals(other.target)); } public int hashCode() { return source.hashCode() ^ target.hashCode(); } public int compareTo(Edge o) { int ret; ret = source.compareTo(o.source); if (0 != ret) { return ret; } ret = target.compareTo(o.target); if (0 != ret) { return ret; } return 0; } public String toString() { return source.name + " <-> " + target.name; } } private static LtAndDsTab parentTab = null; public Graph data; public Table nodeTable; public Table edgeTable; private Map<Node, Integer> nodes = new TreeMap<Node, Integer>(); private Map<Edge, Integer> edges = new TreeMap<Edge, Integer>(); private Map<Node, Color> newNodeColors = new TreeMap<Node, Color>(); private Set<Edge> newEdges = new TreeSet<Edge>(); private MonitorInfo monitor = null; // private JPopupMenu popup = new JPopupMenu("POPUP!"); private String localNode = null; private String currentHoverItemName = null; private String lastClickedNode = null; private RendererFactory rendererWithoutLabels; private RendererFactory rendererWithLabels; private boolean isBackgroundSelected = false; public synchronized void recenter() { if (null == m_vis || isTranformInProgress() || null != currentHoverItemName || isBackgroundSelected) return; try { long m_duration = 500; int m_margin = 50; Rectangle2D bounds = m_vis.getBounds(Visualization.ALL_ITEMS); // System.out.println( // "Display size: width: " + this.getWidth() + ", height: " + // this.getHeight() // "\nVis bounds: center: " + bounds.getCenterX() + ", " + // bounds.getCenterY() + // ", bounds X: " + bounds.getMinX() + ", " + bounds.getMaxX() + // ", bounds Y: " + bounds.getMinY() + ", " + bounds.getMaxY() // ); if (bounds.getMinX() == bounds.getMaxX() || bounds.getMinY() == bounds.getMaxY()) return; GraphicsLib.expand(bounds, m_margin + (int) (1 / getScale())); DisplayLib.fitViewToBounds(this, bounds, m_duration); } catch (Exception e) { System.out.println("Exception in recenter(): " + e); } } private SpringForce springForce = new SpringForce(1e-5f, SpringForce.DEFAULT_SPRING_LENGTH); public MonitorInfo getMonitor() { return monitor; } public void setMonitor(MonitorInfo monitor) { this.monitor = monitor; } public int getNodeCount() { return null != data ? data.getNodeCount() : 0; } public String getLocalNode() { return localNode; } public void setLocalNode(String localNode) { this.localNode = localNode; } public String getCurrentItemName() { return currentHoverItemName; } public String getAndUnsetLastClickedNode() { String lcn = lastClickedNode; System.out.println("clicked " + lcn); lastClickedNode = null; return lcn; } public void setNodeRenderer(boolean withLabels) { if (withLabels) { m_vis.setRendererFactory(rendererWithLabels); } else { m_vis.setRendererFactory(rendererWithoutLabels); } } private static final Object lock = new Object(); private static LtAndDsGraph graph = null; public static LtAndDsGraph getSingleton(LtAndDsTab context) { synchronized (lock) { if (null == graph) graph = new LtAndDsGraph(); } parentTab = context; return graph; } private LtAndDsGraph() { super(new Visualization()); String[] nodeColumns = { NODE_ID_FIELD, NODE_NAME_FIELD }; Class<?>[] nodeColumnTypes = { int.class, String.class }; nodeTable = new Schema(nodeColumns, nodeColumnTypes).instantiate(); data = new Graph(nodeTable, false, NODE_ID_FIELD, SOURCE_ID_FIELD, TARGET_ID_FIELD); edgeTable = data.getEdgeTable(); m_vis.addGraph(GROUP, data); ShapeRenderer nodeRendererWithoutLabels = new ShapeRenderer(); LabelRenderer nodeRendererWithLabels = new LabelRenderer( NODE_NAME_FIELD); nodeRendererWithLabels.setRoundedCorner(10, 10); EdgeRenderer edgeRenderer = new EdgeRenderer() { public void render(Graphics2D g, VisualItem item) { EdgeItem edgeItem = (EdgeItem) item; if (null != currentHoverItemName && (currentHoverItemName.equals(edgeItem .getSourceItem().getString(NODE_NAME_FIELD)) || currentHoverItemName .equals(edgeItem.getTargetItem().getString( NODE_NAME_FIELD)))) { item.setSize(EDGE_THICKNESS * 2); item.setStrokeColor(SELECTED_EDGE_COLOR.getRGB()); } else { item.setSize(EDGE_THICKNESS); item.setStrokeColor(EDGE_COLOR.getRGB()); } super.render(g, item); } }; rendererWithoutLabels = new DefaultRendererFactory( nodeRendererWithoutLabels, edgeRenderer); rendererWithLabels = new DefaultRendererFactory(nodeRendererWithLabels, edgeRenderer); m_vis.setRendererFactory(rendererWithoutLabels); // Set up the drawing actions. ActionList draw = new ActionList(); draw.add(new ShapeAction(NODE_GROUP, Constants.SHAPE_ELLIPSE) { public int getShape(VisualItem item) { if (null != localNode && localNode.equals(item.getString(NODE_NAME_FIELD))) { return Constants.SHAPE_STAR; } else { return super.getShape(item); } } }); draw.add(new SizeAction(NODE_GROUP) { public double getSize(VisualItem item) { if (null != localNode && localNode.equals(item.getString(NODE_NAME_FIELD))) { return super.getSize(item) * 2; } else { return super.getSize(item); } } }); // draw.add(new ColorAction(NODE_GROUP, VisualItem.TEXTCOLOR, // TEXT_COLOR.getRGB())); draw.add(new ColorAction(EDGE_GROUP, VisualItem.STROKECOLOR, EDGE_COLOR .getRGB())); draw.add(new RepaintAction()); m_vis.putAction("draw", draw); Action selectNode = new ColorAction(NODE_GROUP, VisualItem.STROKECOLOR, DEFAULT_NODE_COLOR.getRGB()) { public int getColor(VisualItem item) { if (!item.isInGroup(Visualization.FOCUS_ITEMS)) return item.getFillColor(); return Color.CYAN.getRGB(); } }; m_vis.putAction("selectNode", selectNode); ActionList nodeColor = new ActionList(); nodeColor.add(new ColorAction(NODE_GROUP, VisualItem.FILLCOLOR, Color.LIGHT_GRAY.getRGB()) { public int getColor(VisualItem item) { if (null == monitor) return super.getColor(item); String nodeName = item.getString(NODE_NAME_FIELD); Color color = Node.getInstance(nodeName).getColor(); if (null == color) return super.getColor(item); return color.getRGB(); } }); nodeColor.add(new ColorAction(NODE_GROUP, VisualItem.TEXTCOLOR, TEXT_COLOR.getRGB()) { public int getColor(VisualItem item) { String nodeName = item.getString(NODE_NAME_FIELD); Color color = Node.getInstance(nodeName).getColor(); if (null == color || !color.equals(Color.YELLOW)) return Color.WHITE.getRGB(); return Color.BLACK.getRGB(); } }); nodeColor.add(selectNode); m_vis.putAction("nodeColor", nodeColor); // Set up the positioning actions. ForceSimulator forces = new ForceSimulator(); forces.addForce(new NBodyForce()); forces.addForce(springForce); forces.addForce(new DragForce()); ActionList position = new ActionList(Activity.INFINITY); position.add(new ForceDirectedLayout(GROUP, forces, false)); position.add(new RepaintAction()); m_vis.putAction("position", position); // for ( String s : new String[] { "Kill", "Execute Last Query" } ) { // JMenuItem jm = new JMenuItem(s); // // jm.addActionListener(this); // popup.add(jm); // } // Set up movement and zooming for windows and nodes. addControlListener(new DragControl()); // { public void // mouseClicked(MouseEvent e) { // graph.requestFocus(); // super.mouseClicked(e); } } ); addControlListener(new PanControl()); addControlListener(new ZoomControl()); addControlListener(new WheelZoomControl()); addControlListener(new FocusControl(1, "selectNode") { public void mouseClicked(MouseEvent e) { // System.out.println("button clicked: " + e.getButton() + // ", LEFT=" + Control.LEFT_MOUSE_BUTTON + // ", fo: " + graph.isFocusOwner() + ", fr: " + // graph.isRequestFocusEnabled() + ", froot: " + // graph.isFocusCycleRoot()); if (!graph.isFocusOwner()) graph.requestFocus(); else if (MouseEvent.BUTTON1 == e.getButton() && !e.isControlDown()) { TupleSet ts = m_vis .getFocusGroup(Visualization.FOCUS_ITEMS); ts.clear(); curFocus = null; m_vis.run(activity); } super.mouseClicked(e); } public void itemClicked(VisualItem item, MouseEvent e) { String nodeName = item.getString(NODE_NAME_FIELD); // If we are editing a cell, we do not want to update the table // information, // so we do nothing. Otherwise... if (parentTab.filterText.isEnabled()) { if (nodeName.equals(lastClickedNode)) { lastClickedNode = null; parentTab.savedFilter = null; parentTab.filterDisplay(lastClickedNode); } else { lastClickedNode = item.getString(NODE_NAME_FIELD); if (!lastClickedNode.equals(localNode)) { parentTab.savedFilter = lastClickedNode; parentTab.filterDisplay(lastClickedNode); } } } super.itemClicked(item, e); if (!e.isControlDown()) { TupleSet ts = m_vis .getFocusGroup(Visualization.FOCUS_ITEMS); ts.clear(); curFocus = null; m_vis.run(activity); } } }); addControlListener(new ZoomToFitControl(Control.MIDDLE_MOUSE_BUTTON)); addControlListener(new ControlAdapter() { public void itemEntered(VisualItem item, MouseEvent event) { if (item.isInGroup(NODE_GROUP)) { currentHoverItemName = item.getString(NODE_NAME_FIELD); } } public void itemExited(VisualItem item, MouseEvent event) { currentHoverItemName = null; } public void mousePressed(MouseEvent event) { isBackgroundSelected = true; } public void mouseReleased(MouseEvent event) { isBackgroundSelected = false; } public void itemClicked(VisualItem item, MouseEvent event) { if (event.isShiftDown() && item.isInGroup(NODE_GROUP)) { // 2 == // event.getClickCount() // ) // { // DRV - 22/10/2011 - commented out node clicking action. // Security credentials are not to be entered this way // anymore. // this should be customer/policy-plugin specific now // lastClickedNode = item.getString(NODE_NAME_FIELD); return; } // if ( MouseEvent.BUTTON3 == event.getButton() ) { // // System.out.println("comp: " + event.getComponent() + // ", X: " + event.getX() + ", Y: " + event.getY() ); // // System.out.println("comp: " + event.getComponent() + // ", X: " + event.getXOnScreen() + ", Y: " + // event.getYOnScreen()); // popup.show(event.getComponent(), event.getX(), event.getY()); // } } public void keyTyped(KeyEvent event) { if (event.isControlDown() && event.getKeyChar() == 1) { // == // Ctrl-A // => // toggle // select/unselect // all // nodes TupleSet ts = m_vis .getFocusGroup(Visualization.FOCUS_ITEMS); int tupleCount = ts.getTupleCount(); ts.clear(); if (nodeTable.getRowCount() != tupleCount) { @SuppressWarnings("unchecked") Iterator<VisualItem> it = m_vis.getGroup(NODE_GROUP) .tuples(); while (it.hasNext()) ts.addTuple(it.next()); } m_vis.run("selectNode"); } } }); } public Set<String> getSelectedNodes() { TupleSet ts = (TupleSet) m_vis.getFocusGroup(Visualization.FOCUS_ITEMS); Set<String> nodes = new HashSet<String>(); @SuppressWarnings("unchecked") Iterator<VisualItem> it = ts.tuples(); try { while (it.hasNext()) nodes.add(it.next().getString(NODE_NAME_FIELD)); } catch (Exception e) { return null; } return nodes; } public synchronized void update() { synchronized (newEdges) { m_vis.cancel("position"); processNewEdges(); if (0 == data.getNodeCount()) { return; } springForce .setMaxValue( SpringForce.SPRING_LENGTH, (float) (Math.log10(data.getNodeCount()) * SPRING_LENGTH_MULTIPLIER)); m_vis.run("position"); m_vis.run("draw"); m_vis.run("nodeColor"); } processNewNodeColors(); } // public synchronized void updateValues() { // processNewNodeColors(); // } public void setNodeColor(String nodeName, Color color) { if (null != color) { synchronized (newNodeColors) { newNodeColors.put(Node.getInstance(nodeName), color); } } } public void setEdge(String sourceName, String targetName) { synchronized (newEdges) { newEdges.add(new Edge(sourceName, targetName)); } } private void processNewNodeColors() { for (Node node : nodes.keySet()) { if (node.name.equals(localNode)) { node.setColor(DEFAULT_NODE_COLOR); } else { node.setColor(DEFAULT_LT_COLOR); } } for (Entry<Node, Color> nodeValue : newNodeColors.entrySet()) { nodeValue.getKey().setColor(nodeValue.getValue()); } newNodeColors.clear(); m_vis.run("nodeColor"); } private void processNewEdges() { boolean isGraphChanged = false; Set<Node> newNodes = new HashSet<Node>(); for (Edge edge : newEdges) { newNodes.add(edge.source); newNodes.add(edge.target); } Iterator<Edge> edgeIterator = edges.keySet().iterator(); while (edgeIterator.hasNext()) { Edge edge = edgeIterator.next(); if (!newEdges.contains(edge)) { Integer row = edges.get(edge); if (null != row) { edgeTable.removeRow(row); } edgeIterator.remove(); } } Iterator<Node> nodeIterator = nodes.keySet().iterator(); while (nodeIterator.hasNext()) { Node node = nodeIterator.next(); if (!newNodes.contains(node)) { isGraphChanged = true; nodeTable.removeRow(nodes.get(node)); nodeIterator.remove(); Node.removeInstance(node.name); } } for (Edge edge : newEdges) { if (!edges.containsKey(edge)) { isGraphChanged = true; Node source = Node.getInstance(edge.source.name); Integer sourceId = nodes.get(edge.source); if (null == sourceId) { sourceId = nodeTable.addRow(); nodeTable.setInt(sourceId, NODE_ID_FIELD, sourceId); nodeTable.setString(sourceId, NODE_NAME_FIELD, edge.source.name); nodes.put(source, sourceId); } Node target = Node.getInstance(edge.target.name); Integer targetId = nodes.get(edge.target); if (null == targetId) { targetId = nodeTable.addRow(); nodeTable.setInt(targetId, NODE_ID_FIELD, targetId); nodeTable.setString(targetId, NODE_NAME_FIELD, edge.target.name); nodes.put(target, targetId); } if (sourceId != targetId) { Integer edgeId = edgeTable.addRow(); edgeTable.setInt(edgeId, SOURCE_ID_FIELD, sourceId); edgeTable.setInt(edgeId, TARGET_ID_FIELD, targetId); edges.put(edge, edgeId); } else { edges.put(edge, null); } } } newEdges.clear(); if (isGraphChanged) { // nla.computeStats(edges.keySet()); hasChanged = true; } } private boolean hasChanged = false; boolean hasChanged() { boolean hasChanged = this.hasChanged; this.hasChanged = false; return hasChanged; } Set<String> getAllNodes() { // Set<String> nodeNames = new HashSet<String>(); // for ( Node node : nodes.keySet() ) nodeNames.add(node.getName()); // return nodeNames; return Node.getAllNodeNames(); } int getConnectivity(String node) { return nla.getNumConnections(node); } int getEccentricity(String node) { return nla.getEccentricity(node); } Set<String> getFurthestNodes(String node) { return nla.getFurthestNodes(node); } ArrayList<Integer> getStepCardinalities(String node) { return nla.getStepCardinalities(node); } int getDiameter() { return nla.getDiameter(); } int getRadius() { return nla.getRadius(); } String getNodesPerEccentricity() { return nla.getNodesPerEccentricity(); } String getNodesPerConnectivity() { return nla.getNodesPerConnectivity(); } }