package org.onecmdb.rest.graph.prefuse.view; import java.awt.BorderLayout; import java.awt.Color; import java.awt.geom.Rectangle2D; import java.util.Iterator; import javax.swing.JPanel; import org.onecmdb.rest.graph.prefuse.action.VisabilityActivity; import org.onecmdb.rest.graph.prefuse.action.distortion.ZoomDistortion; import org.onecmdb.rest.graph.prefuse.controls.FlyInOutZoomControl; import org.onecmdb.rest.graph.prefuse.layout.MyForcedDirectedLayout; import org.onecmdb.rest.graph.prefuse.render.MyEdgeRenderer; import org.onecmdb.rest.graph.utils.applet.AppletProperties; 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.distortion.Distortion; import prefuse.action.distortion.FisheyeDistortion; import prefuse.action.filter.GraphDistanceFilter; import prefuse.action.layout.Layout; import prefuse.activity.Activity; import prefuse.controls.AnchorUpdateControl; import prefuse.controls.DragControl; import prefuse.controls.FocusControl; import prefuse.controls.NeighborHighlightControl; import prefuse.controls.PanControl; import prefuse.controls.WheelZoomControl; import prefuse.controls.ZoomControl; import prefuse.controls.ZoomToFitControl; import prefuse.data.Graph; import prefuse.data.Node; import prefuse.data.Schema; import prefuse.data.Tuple; import prefuse.data.event.TupleSetListener; import prefuse.data.search.PrefixSearchTupleSet; import prefuse.data.search.SearchTupleSet; import prefuse.data.tuple.TupleSet; import prefuse.render.DefaultRendererFactory; import prefuse.render.EdgeRenderer; import prefuse.render.LabelRenderer; import prefuse.util.ColorLib; import prefuse.util.GraphicsLib; import prefuse.util.PrefuseLib; import prefuse.util.display.DisplayLib; import prefuse.util.display.ItemBoundsListener; import prefuse.util.force.DragForce; import prefuse.util.force.ForceSimulator; import prefuse.util.force.NBodyForce; import prefuse.util.force.SpringForce; import prefuse.visual.DecoratorItem; import prefuse.visual.EdgeItem; import prefuse.visual.VisualGraph; import prefuse.visual.VisualItem; import prefuse.visual.expression.InGroupPredicate; /** * @author <a href="http://jheer.org">jeffrey heer</a> */ public class GraphView extends JPanel { private static final String graph = "graph"; private static final String nodes = "graph.nodes"; private static final String edges = "graph.edges"; public static final String EDGE_DECORATORS = "edgeDeco"; private Visualization m_vis; private double m_scale_x = 2; private double m_scale_y = 2; private AnchorUpdateControl zoomDistortControl; private AnchorUpdateControl fisheyeDistortControl; private VisualGraph vg; private GraphDistanceFilter distanceFilter; private MyForcedDirectedLayout layout; private static final Schema DECORATOR_SCHEMA = PrefuseLib.getVisualItemSchema(); static { DECORATOR_SCHEMA.setDefault(VisualItem.INTERACTIVE, false); DECORATOR_SCHEMA.setDefault(VisualItem.TEXTCOLOR, ColorLib.blue(128)); } public Visualization getVisualization() { return(m_vis); } public void magnify(boolean enable) { Display d = m_vis.getDisplay(0); if (enable) { //animate(false); d.addControlListener(zoomDistortControl); } else { d.removeControlListener(zoomDistortControl); //animate(true); } } public void fisheye(boolean enable) { Display d = m_vis.getDisplay(0); if (enable) { animate(false); d.addControlListener(fisheyeDistortControl); } else { d.removeControlListener(fisheyeDistortControl); animate(true); } } public void setEnforceBounds(boolean value) { layout.setEnforceBounds(value); } public void animate(boolean enable) { Action a = m_vis.getAction("layout"); if (a != null) { a.setEnabled(enable); } if (!enable) { try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } public GraphView(Graph g, String label, String edgeLabel) { super(new BorderLayout()); // create a new, empty visualization for our data m_vis = new Visualization(); // -------------------------------------------------------------------- // set up the renderers EdgeRenderer m_edgeRenderer = new MyEdgeRenderer(Constants.EDGE_TYPE_CURVE, Constants.EDGE_ARROW_FORWARD); EdgeRenderer m_edgeRendererNoArrow = new EdgeRenderer(Constants.EDGE_TYPE_LINE, Constants.EDGE_ARROW_NONE); EdgeRenderer m_edgeRenderer2 = new EdgeRenderer(Constants.EDGE_TYPE_CURVE, Constants.EDGE_ARROW_NONE); m_edgeRenderer.setArrowHeadSize(4,12); LabelRenderer tr = new LabelRenderer(); tr.setRoundedCorner(8, 8); tr.setImageField("image"); tr.setTextField(label); //tr.setMaxImageDimensions(32, 32); tr.setImagePosition(Constants.TOP); DefaultRendererFactory rf = new DefaultRendererFactory(tr, m_edgeRenderer); //rf.add(new InGroupPredicate(edges), m_edgeRenderer); if (edgeLabel != null) { rf.add(new InGroupPredicate(EDGE_DECORATORS), new LabelRenderer(edgeLabel)); } //rf.add(new EdgeTypeGroupPredicate(edges, "Inheritance"), m_edgeRenderer); //rf.add(new EdgeTypeGroupPredicate(edges, "Relation") , m_edgeRenderer2); m_vis.setRendererFactory(rf); // -------------------------------------------------------------------- // register the data with a visualization // adds graph to visualization and sets renderer label field setGraph(g, label); if (edgeLabel != null) { m_vis.addDecorators(EDGE_DECORATORS, edges, DECORATOR_SCHEMA); m_vis.setValue(EDGE_DECORATORS, null, VisualItem.INTERACTIVE, Boolean.FALSE); } // fix selected focus nodes TupleSet focusGroup = m_vis.getGroup(Visualization.FOCUS_ITEMS); focusGroup.addTupleSetListener(new TupleSetListener() { public void tupleSetChanged(TupleSet ts, Tuple[] add, Tuple[] rem) { if (add.length == 1 && (add[0] instanceof EdgeItem)) { return; } for ( int i=0; i<rem.length; ++i ) { VisualItem vi = (VisualItem)rem[i]; if (vi.getRow() >= 0) { vi.setFixed(false); } } for ( int i=0; i<add.length; ++i ) { ((VisualItem)add[i]).setFixed(false); ((VisualItem)add[i]).setFixed(true); } if ( ts.getTupleCount() == 0 ) { /* ts.addTuple(rem[0]); ((VisualItem)rem[0]).setFixed(false); */ } m_vis.run("draw"); } }); // -------------------------------------------------------------------- // create actions to process the visual data VisabilityActivity visable = new VisabilityActivity(); m_vis.putAction("allVisable", visable); distanceFilter = new GraphDistanceFilter(graph, 30); distanceFilter.setEnabled(false); ColorAction fill = new ColorAction(nodes, VisualItem.FILLCOLOR, ColorLib.rgb(200,200,255)); fill.add(VisualItem.FIXED, ColorLib.rgb(255,100,100)); fill.add(VisualItem.HIGHLIGHT, ColorLib.rgb(255,200,125)); fill.add("ingroup('" + Visualization.SEARCH_ITEMS + "')", ColorLib.rgb(255,190,190)); ActionList draw = new ActionList(); draw.add(distanceFilter); draw.add(fill); draw.add(new ColorAction(nodes, VisualItem.STROKECOLOR, 0)); draw.add(new ColorAction(nodes, VisualItem.TEXTCOLOR, ColorLib.rgb(0,0,0))); draw.add(new ColorAction(edges, VisualItem.FILLCOLOR, ColorLib.gray(200))); draw.add(new ColorAction(edges, VisualItem.STROKECOLOR, ColorLib.gray(200))); draw.add(new ColorAction(EDGE_DECORATORS, VisualItem.TEXTCOLOR, ColorLib.rgb(200,200,200))); ActionList animate = new ActionList(Activity.INFINITY); ForceSimulator fsim = new ForceSimulator(); fsim.addForce(new NBodyForce()); fsim.addForce(new SpringForce(1E-5f, 150f)); fsim.addForce(new DragForce()); layout = new MyForcedDirectedLayout(graph, fsim); if (edgeLabel != null) { animate.add(new LabelLayout2(EDGE_DECORATORS)); } animate.add(layout); animate.add(fill); animate.add(new RepaintAction()); // finally, we register our ActionList with the Visualization. // we can later execute our Actions by invoking a method on our // Visualization, using the name we've chosen below. m_vis.putAction("draw", draw); m_vis.putAction("layout", animate); m_vis.runAfter("draw", "layout"); // Zoom to Fit action. m_vis.putAction("zoomToFit", getZoomToFitAction()); // -------------------------------------------------------------------- // set up a display to show the visualization Display display = new Display(m_vis); /* display.setSize(700,700); display.pan(350, 350); */ // main display controls display.addControlListener(new FocusControl(1)); display.addControlListener(new DragControl()); display.addControlListener(new PanControl()); display.addControlListener(new ZoomControl()); display.addControlListener(new WheelZoomControl()); display.addControlListener(new ZoomToFitControl()); display.addControlListener(new NeighborHighlightControl()); display.addControlListener(new FlyInOutZoomControl()); // overview display // Display overview = new Display(vis); // overview.setSize(290,290); // overview.addItemBoundsListener(new FitOverviewListener()); display.setForeground(Color.GRAY); Color bgColor = new Color(0x45, 0x45, 0x45); String bgColorValue = AppletProperties.get("graphBackgroundColor"); if (bgColorValue != null) { try { if (bgColorValue.startsWith("0x")) { bgColorValue = bgColorValue.substring(2); } int color = Integer.parseInt(bgColorValue, 16); bgColor = new Color(color); } catch (Throwable e) { } } display.setBackground(bgColor); /* // fisheye distortion based on the current anchor location Distortion feye = new FisheyeDistortion(0, m_scale_y); */ { ActionList distort = new ActionList(); Distortion mag = new ZoomDistortion(); mag.setGroup(nodes); distort.add(mag); //distort.add(new RepaintAction()); m_vis.putAction("distortZoom", distort); zoomDistortControl = new AnchorUpdateControl(mag, "distortZoom"); } { ActionList distort = new ActionList(); Distortion feye = new FisheyeDistortion(3, 3); feye.setGroup(nodes); distort.add(feye); distort.add(new RepaintAction()); m_vis.putAction("distortfEye", distort); fisheyeDistortControl = new AnchorUpdateControl(feye, "distortfEye"); } // update the distortion anchor position to be the current // location of the mouse pointer //display.addControlListener(new ToolTipControl(new String[] {"name", "image"})); // Handle Search SearchTupleSet search = new PrefixSearchTupleSet(); m_vis.addFocusGroup(Visualization.SEARCH_ITEMS, search); search.addTupleSetListener(new TupleSetListener() { public void tupleSetChanged(TupleSet t, Tuple[] add, Tuple[] rem) { //m_vis.cancel("animatePaint"); //m_vis.run("recolor"); //m_vis.run("animatePaint"); } }); // now we run our action list m_vis.run("draw"); add(display); } private Action getZoomToFitAction() { return(new Action() { @Override public void run(double frac) { Rectangle2D bounds = m_vis.getBounds(Visualization.ALL_ITEMS); Display display = m_vis.getDisplay(0); GraphicsLib.expand(bounds, 50 + (int)(1/display.getScale())); DisplayLib.fitViewToBounds(display, bounds, 2000); } }); } /** * Set label positions. Labels are assumed to be DecoratorItem instances, * decorating their respective nodes. The layout simply gets the bounds * of the decorated node and assigns the label coordinates to the center * of those bounds. */ class LabelLayout2 extends Layout { public LabelLayout2(String group) { super(group); } public void run(double frac) { Iterator iter = m_vis.items(m_group); int count = 0; while ( iter.hasNext() ) { DecoratorItem item = (DecoratorItem)iter.next(); VisualItem node = item.getDecoratedItem(); if (node.isVisible()) { item.setVisible(true); count++; Rectangle2D bounds = node.getBounds(); setX(item, null, bounds.getCenterX()); setY(item, null, bounds.getCenterY()); } else { item.setVisible(false); } } } } // end of inner class LabelLayout /** * Set node fill colors */ public static class NodeColorAction extends ColorAction { public NodeColorAction(String group) { super(group, VisualItem.FILLCOLOR, ColorLib.rgba(255,255,255,0)); add("_hover", ColorLib.gray(220,230)); add("ingroup('_search_')", ColorLib.rgb(255,190,190)); add("ingroup('_focus_')", ColorLib.rgb(198,229,229)); } } // end of inner class NodeColorAction public VisualGraph getVisaulGraph() { return(vg); } public void setGraph(Graph g, String label) { // update labeling DefaultRendererFactory drf = (DefaultRendererFactory) m_vis.getRendererFactory(); ((LabelRenderer)drf.getDefaultRenderer()).setTextField(label); // update graph m_vis.removeGroup(graph); vg = m_vis.addGraph(graph, g); m_vis.setValue(edges, null, VisualItem.INTERACTIVE, Boolean.FALSE); /* VisualItem f = (VisualItem)vg.getNode(0); m_vis.getGroup(Visualization.FOCUS_ITEMS).setTuple(f); f.setFixed(false); */ } // ------------------------------------------------------------------------ class FlyZoomAction extends Action { @Override public void run(double frac) { } } // ------------------------------------------------------------------------ public static class FitOverviewListener implements ItemBoundsListener { private Rectangle2D m_bounds = new Rectangle2D.Double(); private Rectangle2D m_temp = new Rectangle2D.Double(); private double m_d = 15; public void itemBoundsChanged(Display d) { d.getItemBounds(m_temp); GraphicsLib.expand(m_temp, 25/d.getScale()); double dd = m_d/d.getScale(); double xd = Math.abs(m_temp.getMinX()-m_bounds.getMinX()); double yd = Math.abs(m_temp.getMinY()-m_bounds.getMinY()); double wd = Math.abs(m_temp.getWidth()-m_bounds.getWidth()); double hd = Math.abs(m_temp.getHeight()-m_bounds.getHeight()); if ( xd>dd || yd>dd || wd>dd || hd>dd ) { m_bounds.setFrame(m_temp); DisplayLib.fitViewToBounds(d, m_bounds, 0); } } } public void setFocus(Node n) { TupleSet ts = m_vis.getFocusGroup(Visualization.FOCUS_ITEMS); ts.clear(); VisualItem item = m_vis.getVisualItem(nodes, n); if (item != null) { ts.setTuple(item); } } public void redrawAndZoomToFit() { /* VisualItem f = (VisualItem)vg.getNode(0); if (f != null) { m_vis.getGroup(Visualization.FOCUS_ITEMS).setTuple(f); //f.set("_focus_", true); } */ m_vis.run("draw"); // Center focus node. //m_vis.run("zoomToFit"); //zoomToFit(); new Thread(new Runnable() { public void run() { try { Thread.sleep(300); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } zoomToFit(); } }).start(); } public void zoomToFit() { Rectangle2D bounds = m_vis.getBounds(Visualization.ALL_ITEMS); Display display = m_vis.getDisplay(0); GraphicsLib.expand(bounds, 50 + (int)(1/display.getScale())); DisplayLib.fitViewToBounds(display, bounds, 2000); } public void setDistance(int intValue) { distanceFilter.setDistance(intValue); m_vis.run("draw"); } public boolean isDistanceEnabled() { return(distanceFilter.isEnabled()); } public void setEnableDistanceFilter(boolean enable) { distanceFilter.setEnabled(enable); if (!enable) { m_vis.run("allVisable"); } m_vis.run("draw"); } } // end of class GraphView