package prefuse.demos; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.geom.Rectangle2D; import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.KeyStroke; import javax.swing.ListSelectionModel; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import prefuse.Display; import prefuse.Visualization; import prefuse.action.ActionList; import prefuse.action.RepaintAction; import prefuse.action.assignment.ColorAction; import prefuse.action.filter.GraphDistanceFilter; import prefuse.action.layout.graph.ForceDirectedLayout; import prefuse.activity.Activity; 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.Table; import prefuse.data.Tuple; import prefuse.data.event.TupleSetListener; import prefuse.data.io.GraphMLReader; import prefuse.data.tuple.TupleSet; import prefuse.render.DefaultRendererFactory; import prefuse.render.LabelRenderer; import prefuse.util.ColorLib; import prefuse.util.GraphLib; import prefuse.util.GraphicsLib; import prefuse.util.display.DisplayLib; import prefuse.util.display.ItemBoundsListener; import prefuse.util.force.ForceSimulator; import prefuse.util.io.IOLib; import prefuse.util.ui.JForcePanel; import prefuse.util.ui.JValueSlider; import prefuse.util.ui.UILib; import prefuse.visual.VisualGraph; import prefuse.visual.VisualItem; /** * @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"; private Visualization m_vis; public GraphView(Graph g, String label) { super(new BorderLayout()); // create a new, empty visualization for our data m_vis = new Visualization(); // -------------------------------------------------------------------- // set up the renderers LabelRenderer tr = new LabelRenderer(); tr.setRoundedCorner(8, 8); m_vis.setRendererFactory(new DefaultRendererFactory(tr)); // -------------------------------------------------------------------- // register the data with a visualization // adds graph to visualization and sets renderer label field setGraph(g, label); // 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) { for ( int i=0; i<rem.length; ++i ) ((VisualItem)rem[i]).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 int hops = 30; final GraphDistanceFilter filter = new GraphDistanceFilter(graph, hops); 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)); ActionList draw = new ActionList(); draw.add(filter); 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))); ActionList animate = new ActionList(Activity.INFINITY); animate.add(new ForceDirectedLayout(graph)); 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"); // -------------------------------------------------------------------- // set up a display to show the visualization Display display = new Display(m_vis); display.setSize(700,700); display.pan(350, 350); display.setForeground(Color.GRAY); display.setBackground(Color.WHITE); // 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()); // overview display // Display overview = new Display(vis); // overview.setSize(290,290); // overview.addItemBoundsListener(new FitOverviewListener()); display.setForeground(Color.GRAY); display.setBackground(Color.WHITE); // -------------------------------------------------------------------- // launch the visualization // create a panel for editing force values ForceSimulator fsim = ((ForceDirectedLayout)animate.get(0)).getForceSimulator(); JForcePanel fpanel = new JForcePanel(fsim); // JPanel opanel = new JPanel(); // opanel.setBorder(BorderFactory.createTitledBorder("Overview")); // opanel.setBackground(Color.WHITE); // opanel.add(overview); final JValueSlider slider = new JValueSlider("Distance", 0, hops, hops); slider.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { filter.setDistance(slider.getValue().intValue()); m_vis.run("draw"); } }); slider.setBackground(Color.WHITE); slider.setPreferredSize(new Dimension(300,30)); slider.setMaximumSize(new Dimension(300,30)); Box cf = new Box(BoxLayout.Y_AXIS); cf.add(slider); cf.setBorder(BorderFactory.createTitledBorder("Connectivity Filter")); fpanel.add(cf); //fpanel.add(opanel); fpanel.add(Box.createVerticalGlue()); // create a new JSplitPane to present the interface JSplitPane split = new JSplitPane(); split.setLeftComponent(display); split.setRightComponent(fpanel); split.setOneTouchExpandable(true); split.setContinuousLayout(false); split.setDividerLocation(700); // now we run our action list m_vis.run("draw"); add(split); } 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); VisualGraph 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); } // ------------------------------------------------------------------------ // Main and demo methods public static void main(String[] args) { UILib.setPlatformLookAndFeel(); // create graphview String datafile = null; String label = "label"; if ( args.length > 1 ) { datafile = args[0]; label = args[1]; } JFrame frame = demo(datafile, label); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static JFrame demo() { return demo((String)null, "label"); } public static JFrame demo(String datafile, String label) { Graph g = null; if ( datafile == null ) { g = GraphLib.getGrid(15,15); label = "label"; } else { try { g = new GraphMLReader().readGraph(datafile); } catch ( Exception e ) { e.printStackTrace(); System.exit(1); } } return demo(g, label); } public static JFrame demo(Graph g, String label) { final GraphView view = new GraphView(g, label); // set up menu JMenu dataMenu = new JMenu("Data"); dataMenu.add(new OpenGraphAction(view)); dataMenu.add(new GraphMenuAction("Grid","ctrl 1",view) { protected Graph getGraph() { return GraphLib.getGrid(15,15); } }); dataMenu.add(new GraphMenuAction("Clique","ctrl 2",view) { protected Graph getGraph() { return GraphLib.getClique(10); } }); dataMenu.add(new GraphMenuAction("Honeycomb","ctrl 3",view) { protected Graph getGraph() { return GraphLib.getHoneycomb(5); } }); dataMenu.add(new GraphMenuAction("Balanced Tree","ctrl 4",view) { protected Graph getGraph() { return GraphLib.getBalancedTree(3,5); } }); dataMenu.add(new GraphMenuAction("Diamond Tree","ctrl 5",view) { protected Graph getGraph() { return GraphLib.getDiamondTree(3,3,3); } }); JMenuBar menubar = new JMenuBar(); menubar.add(dataMenu); // launch window JFrame frame = new JFrame("p r e f u s e | g r a p h v i e w"); frame.setJMenuBar(menubar); frame.setContentPane(view); frame.pack(); frame.setVisible(true); frame.addWindowListener(new WindowAdapter() { public void windowActivated(WindowEvent e) { view.m_vis.run("layout"); } public void windowDeactivated(WindowEvent e) { view.m_vis.cancel("layout"); } }); return frame; } // ------------------------------------------------------------------------ /** * Swing menu action that loads a graph into the graph viewer. */ public abstract static class GraphMenuAction extends AbstractAction { private GraphView m_view; public GraphMenuAction(String name, String accel, GraphView view) { m_view = view; this.putValue(AbstractAction.NAME, name); this.putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke.getKeyStroke(accel)); } public void actionPerformed(ActionEvent e) { m_view.setGraph(getGraph(), "label"); } protected abstract Graph getGraph(); } public static class OpenGraphAction extends AbstractAction { private GraphView m_view; public OpenGraphAction(GraphView view) { m_view = view; this.putValue(AbstractAction.NAME, "Open File..."); this.putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke.getKeyStroke("ctrl O")); } public void actionPerformed(ActionEvent e) { Graph g = IOLib.getGraphFile(m_view); if ( g == null ) return; String label = getLabel(m_view, g); if ( label != null ) { m_view.setGraph(g, label); } } public static String getLabel(Component c, Graph g) { // get the column names Table t = g.getNodeTable(); int cc = t.getColumnCount(); String[] names = new String[cc]; for ( int i=0; i<cc; ++i ) names[i] = t.getColumnName(i); // where to store the result final String[] label = new String[1]; // -- build the dialog ----- // we need to get the enclosing frame first while ( c != null && !(c instanceof JFrame) ) { c = c.getParent(); } final JDialog dialog = new JDialog( (JFrame)c, "Choose Label Field", true); // create the ok/cancel buttons final JButton ok = new JButton("OK"); ok.setEnabled(false); ok.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { dialog.setVisible(false); } }); JButton cancel = new JButton("Cancel"); cancel.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { label[0] = null; dialog.setVisible(false); } }); // build the selection list final JList list = new JList(names); list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); list.getSelectionModel().addListSelectionListener( new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { int sel = list.getSelectedIndex(); if ( sel >= 0 ) { ok.setEnabled(true); label[0] = (String)list.getModel().getElementAt(sel); } else { ok.setEnabled(false); label[0] = null; } } }); JScrollPane scrollList = new JScrollPane(list); JLabel title = new JLabel("Choose a field to use for node labels:"); // layout the buttons Box bbox = new Box(BoxLayout.X_AXIS); bbox.add(Box.createHorizontalStrut(5)); bbox.add(Box.createHorizontalGlue()); bbox.add(ok); bbox.add(Box.createHorizontalStrut(5)); bbox.add(cancel); bbox.add(Box.createHorizontalStrut(5)); // put everything into a panel JPanel panel = new JPanel(new BorderLayout()); panel.add(title, BorderLayout.NORTH); panel.add(scrollList, BorderLayout.CENTER); panel.add(bbox, BorderLayout.SOUTH); panel.setBorder(BorderFactory.createEmptyBorder(5,2,2,2)); // show the dialog dialog.setContentPane(panel); dialog.pack(); dialog.setLocationRelativeTo(c); dialog.setVisible(true); dialog.dispose(); // return the label field selection return label[0]; } } 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); } } } } // end of class GraphView