001: package prefuse.demos;
002:
003: import java.awt.BorderLayout;
004: import java.awt.Color;
005: import java.awt.Component;
006: import java.awt.Dimension;
007: import java.awt.event.ActionEvent;
008: import java.awt.event.ActionListener;
009: import java.awt.event.WindowAdapter;
010: import java.awt.event.WindowEvent;
011: import java.awt.geom.Rectangle2D;
012:
013: import javax.swing.AbstractAction;
014: import javax.swing.BorderFactory;
015: import javax.swing.Box;
016: import javax.swing.BoxLayout;
017: import javax.swing.JButton;
018: import javax.swing.JDialog;
019: import javax.swing.JFrame;
020: import javax.swing.JLabel;
021: import javax.swing.JList;
022: import javax.swing.JMenu;
023: import javax.swing.JMenuBar;
024: import javax.swing.JPanel;
025: import javax.swing.JScrollPane;
026: import javax.swing.JSplitPane;
027: import javax.swing.KeyStroke;
028: import javax.swing.ListSelectionModel;
029: import javax.swing.event.ChangeEvent;
030: import javax.swing.event.ChangeListener;
031: import javax.swing.event.ListSelectionEvent;
032: import javax.swing.event.ListSelectionListener;
033:
034: import prefuse.Display;
035: import prefuse.Visualization;
036: import prefuse.action.ActionList;
037: import prefuse.action.RepaintAction;
038: import prefuse.action.assignment.ColorAction;
039: import prefuse.action.filter.GraphDistanceFilter;
040: import prefuse.action.layout.graph.ForceDirectedLayout;
041: import prefuse.activity.Activity;
042: import prefuse.controls.DragControl;
043: import prefuse.controls.FocusControl;
044: import prefuse.controls.NeighborHighlightControl;
045: import prefuse.controls.PanControl;
046: import prefuse.controls.WheelZoomControl;
047: import prefuse.controls.ZoomControl;
048: import prefuse.controls.ZoomToFitControl;
049: import prefuse.data.Graph;
050: import prefuse.data.Table;
051: import prefuse.data.Tuple;
052: import prefuse.data.event.TupleSetListener;
053: import prefuse.data.io.GraphMLReader;
054: import prefuse.data.tuple.TupleSet;
055: import prefuse.render.DefaultRendererFactory;
056: import prefuse.render.LabelRenderer;
057: import prefuse.util.ColorLib;
058: import prefuse.util.GraphLib;
059: import prefuse.util.GraphicsLib;
060: import prefuse.util.display.DisplayLib;
061: import prefuse.util.display.ItemBoundsListener;
062: import prefuse.util.force.ForceSimulator;
063: import prefuse.util.io.IOLib;
064: import prefuse.util.ui.JForcePanel;
065: import prefuse.util.ui.JValueSlider;
066: import prefuse.util.ui.UILib;
067: import prefuse.visual.VisualGraph;
068: import prefuse.visual.VisualItem;
069:
070: /**
071: * @author <a href="http://jheer.org">jeffrey heer</a>
072: */
073: public class GraphView extends JPanel {
074:
075: private static final String graph = "graph";
076: private static final String nodes = "graph.nodes";
077: private static final String edges = "graph.edges";
078:
079: private Visualization m_vis;
080:
081: public GraphView(Graph g, String label) {
082: super (new BorderLayout());
083:
084: // create a new, empty visualization for our data
085: m_vis = new Visualization();
086:
087: // --------------------------------------------------------------------
088: // set up the renderers
089:
090: LabelRenderer tr = new LabelRenderer();
091: tr.setRoundedCorner(8, 8);
092: m_vis.setRendererFactory(new DefaultRendererFactory(tr));
093:
094: // --------------------------------------------------------------------
095: // register the data with a visualization
096:
097: // adds graph to visualization and sets renderer label field
098: setGraph(g, label);
099:
100: // fix selected focus nodes
101: TupleSet focusGroup = m_vis.getGroup(Visualization.FOCUS_ITEMS);
102: focusGroup.addTupleSetListener(new TupleSetListener() {
103: public void tupleSetChanged(TupleSet ts, Tuple[] add,
104: Tuple[] rem) {
105: for (int i = 0; i < rem.length; ++i)
106: ((VisualItem) rem[i]).setFixed(false);
107: for (int i = 0; i < add.length; ++i) {
108: ((VisualItem) add[i]).setFixed(false);
109: ((VisualItem) add[i]).setFixed(true);
110: }
111: if (ts.getTupleCount() == 0) {
112: ts.addTuple(rem[0]);
113: ((VisualItem) rem[0]).setFixed(false);
114: }
115: m_vis.run("draw");
116: }
117: });
118:
119: // --------------------------------------------------------------------
120: // create actions to process the visual data
121:
122: int hops = 30;
123: final GraphDistanceFilter filter = new GraphDistanceFilter(
124: graph, hops);
125:
126: ColorAction fill = new ColorAction(nodes, VisualItem.FILLCOLOR,
127: ColorLib.rgb(200, 200, 255));
128: fill.add(VisualItem.FIXED, ColorLib.rgb(255, 100, 100));
129: fill.add(VisualItem.HIGHLIGHT, ColorLib.rgb(255, 200, 125));
130:
131: ActionList draw = new ActionList();
132: draw.add(filter);
133: draw.add(fill);
134: draw.add(new ColorAction(nodes, VisualItem.STROKECOLOR, 0));
135: draw.add(new ColorAction(nodes, VisualItem.TEXTCOLOR, ColorLib
136: .rgb(0, 0, 0)));
137: draw.add(new ColorAction(edges, VisualItem.FILLCOLOR, ColorLib
138: .gray(200)));
139: draw.add(new ColorAction(edges, VisualItem.STROKECOLOR,
140: ColorLib.gray(200)));
141:
142: ActionList animate = new ActionList(Activity.INFINITY);
143: animate.add(new ForceDirectedLayout(graph));
144: animate.add(fill);
145: animate.add(new RepaintAction());
146:
147: // finally, we register our ActionList with the Visualization.
148: // we can later execute our Actions by invoking a method on our
149: // Visualization, using the name we've chosen below.
150: m_vis.putAction("draw", draw);
151: m_vis.putAction("layout", animate);
152:
153: m_vis.runAfter("draw", "layout");
154:
155: // --------------------------------------------------------------------
156: // set up a display to show the visualization
157:
158: Display display = new Display(m_vis);
159: display.setSize(700, 700);
160: display.pan(350, 350);
161: display.setForeground(Color.GRAY);
162: display.setBackground(Color.WHITE);
163:
164: // main display controls
165: display.addControlListener(new FocusControl(1));
166: display.addControlListener(new DragControl());
167: display.addControlListener(new PanControl());
168: display.addControlListener(new ZoomControl());
169: display.addControlListener(new WheelZoomControl());
170: display.addControlListener(new ZoomToFitControl());
171: display.addControlListener(new NeighborHighlightControl());
172:
173: // overview display
174: // Display overview = new Display(vis);
175: // overview.setSize(290,290);
176: // overview.addItemBoundsListener(new FitOverviewListener());
177:
178: display.setForeground(Color.GRAY);
179: display.setBackground(Color.WHITE);
180:
181: // --------------------------------------------------------------------
182: // launch the visualization
183:
184: // create a panel for editing force values
185: ForceSimulator fsim = ((ForceDirectedLayout) animate.get(0))
186: .getForceSimulator();
187: JForcePanel fpanel = new JForcePanel(fsim);
188:
189: // JPanel opanel = new JPanel();
190: // opanel.setBorder(BorderFactory.createTitledBorder("Overview"));
191: // opanel.setBackground(Color.WHITE);
192: // opanel.add(overview);
193:
194: final JValueSlider slider = new JValueSlider("Distance", 0,
195: hops, hops);
196: slider.addChangeListener(new ChangeListener() {
197: public void stateChanged(ChangeEvent e) {
198: filter.setDistance(slider.getValue().intValue());
199: m_vis.run("draw");
200: }
201: });
202: slider.setBackground(Color.WHITE);
203: slider.setPreferredSize(new Dimension(300, 30));
204: slider.setMaximumSize(new Dimension(300, 30));
205:
206: Box cf = new Box(BoxLayout.Y_AXIS);
207: cf.add(slider);
208: cf.setBorder(BorderFactory
209: .createTitledBorder("Connectivity Filter"));
210: fpanel.add(cf);
211:
212: //fpanel.add(opanel);
213:
214: fpanel.add(Box.createVerticalGlue());
215:
216: // create a new JSplitPane to present the interface
217: JSplitPane split = new JSplitPane();
218: split.setLeftComponent(display);
219: split.setRightComponent(fpanel);
220: split.setOneTouchExpandable(true);
221: split.setContinuousLayout(false);
222: split.setDividerLocation(700);
223:
224: // now we run our action list
225: m_vis.run("draw");
226:
227: add(split);
228: }
229:
230: public void setGraph(Graph g, String label) {
231: // update labeling
232: DefaultRendererFactory drf = (DefaultRendererFactory) m_vis
233: .getRendererFactory();
234: ((LabelRenderer) drf.getDefaultRenderer()).setTextField(label);
235:
236: // update graph
237: m_vis.removeGroup(graph);
238: VisualGraph vg = m_vis.addGraph(graph, g);
239: m_vis.setValue(edges, null, VisualItem.INTERACTIVE,
240: Boolean.FALSE);
241: VisualItem f = (VisualItem) vg.getNode(0);
242: m_vis.getGroup(Visualization.FOCUS_ITEMS).setTuple(f);
243: f.setFixed(false);
244: }
245:
246: // ------------------------------------------------------------------------
247: // Main and demo methods
248:
249: public static void main(String[] args) {
250: UILib.setPlatformLookAndFeel();
251:
252: // create graphview
253: String datafile = null;
254: String label = "label";
255: if (args.length > 1) {
256: datafile = args[0];
257: label = args[1];
258: }
259:
260: JFrame frame = demo(datafile, label);
261: frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
262: }
263:
264: public static JFrame demo() {
265: return demo((String) null, "label");
266: }
267:
268: public static JFrame demo(String datafile, String label) {
269: Graph g = null;
270: if (datafile == null) {
271: g = GraphLib.getGrid(15, 15);
272: label = "label";
273: } else {
274: try {
275: g = new GraphMLReader().readGraph(datafile);
276: } catch (Exception e) {
277: e.printStackTrace();
278: System.exit(1);
279: }
280: }
281: return demo(g, label);
282: }
283:
284: public static JFrame demo(Graph g, String label) {
285: final GraphView view = new GraphView(g, label);
286:
287: // set up menu
288: JMenu dataMenu = new JMenu("Data");
289: dataMenu.add(new OpenGraphAction(view));
290: dataMenu.add(new GraphMenuAction("Grid", "ctrl 1", view) {
291: protected Graph getGraph() {
292: return GraphLib.getGrid(15, 15);
293: }
294: });
295: dataMenu.add(new GraphMenuAction("Clique", "ctrl 2", view) {
296: protected Graph getGraph() {
297: return GraphLib.getClique(10);
298: }
299: });
300: dataMenu.add(new GraphMenuAction("Honeycomb", "ctrl 3", view) {
301: protected Graph getGraph() {
302: return GraphLib.getHoneycomb(5);
303: }
304: });
305: dataMenu.add(new GraphMenuAction("Balanced Tree", "ctrl 4",
306: view) {
307: protected Graph getGraph() {
308: return GraphLib.getBalancedTree(3, 5);
309: }
310: });
311: dataMenu
312: .add(new GraphMenuAction("Diamond Tree", "ctrl 5", view) {
313: protected Graph getGraph() {
314: return GraphLib.getDiamondTree(3, 3, 3);
315: }
316: });
317: JMenuBar menubar = new JMenuBar();
318: menubar.add(dataMenu);
319:
320: // launch window
321: JFrame frame = new JFrame("p r e f u s e | g r a p h v i e w");
322: frame.setJMenuBar(menubar);
323: frame.setContentPane(view);
324: frame.pack();
325: frame.setVisible(true);
326:
327: frame.addWindowListener(new WindowAdapter() {
328: public void windowActivated(WindowEvent e) {
329: view.m_vis.run("layout");
330: }
331:
332: public void windowDeactivated(WindowEvent e) {
333: view.m_vis.cancel("layout");
334: }
335: });
336:
337: return frame;
338: }
339:
340: // ------------------------------------------------------------------------
341:
342: /**
343: * Swing menu action that loads a graph into the graph viewer.
344: */
345: public abstract static class GraphMenuAction extends AbstractAction {
346: private GraphView m_view;
347:
348: public GraphMenuAction(String name, String accel, GraphView view) {
349: m_view = view;
350: this .putValue(AbstractAction.NAME, name);
351: this .putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke
352: .getKeyStroke(accel));
353: }
354:
355: public void actionPerformed(ActionEvent e) {
356: m_view.setGraph(getGraph(), "label");
357: }
358:
359: protected abstract Graph getGraph();
360: }
361:
362: public static class OpenGraphAction extends AbstractAction {
363: private GraphView m_view;
364:
365: public OpenGraphAction(GraphView view) {
366: m_view = view;
367: this .putValue(AbstractAction.NAME, "Open File...");
368: this .putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke
369: .getKeyStroke("ctrl O"));
370: }
371:
372: public void actionPerformed(ActionEvent e) {
373: Graph g = IOLib.getGraphFile(m_view);
374: if (g == null)
375: return;
376: String label = getLabel(m_view, g);
377: if (label != null) {
378: m_view.setGraph(g, label);
379: }
380: }
381:
382: public static String getLabel(Component c, Graph g) {
383: // get the column names
384: Table t = g.getNodeTable();
385: int cc = t.getColumnCount();
386: String[] names = new String[cc];
387: for (int i = 0; i < cc; ++i)
388: names[i] = t.getColumnName(i);
389:
390: // where to store the result
391: final String[] label = new String[1];
392:
393: // -- build the dialog -----
394: // we need to get the enclosing frame first
395: while (c != null && !(c instanceof JFrame)) {
396: c = c.getParent();
397: }
398: final JDialog dialog = new JDialog((JFrame) c,
399: "Choose Label Field", true);
400:
401: // create the ok/cancel buttons
402: final JButton ok = new JButton("OK");
403: ok.setEnabled(false);
404: ok.addActionListener(new ActionListener() {
405: public void actionPerformed(ActionEvent e) {
406: dialog.setVisible(false);
407: }
408: });
409: JButton cancel = new JButton("Cancel");
410: cancel.addActionListener(new ActionListener() {
411: public void actionPerformed(ActionEvent e) {
412: label[0] = null;
413: dialog.setVisible(false);
414: }
415: });
416:
417: // build the selection list
418: final JList list = new JList(names);
419: list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
420: list.getSelectionModel().addListSelectionListener(
421: new ListSelectionListener() {
422: public void valueChanged(ListSelectionEvent e) {
423: int sel = list.getSelectedIndex();
424: if (sel >= 0) {
425: ok.setEnabled(true);
426: label[0] = (String) list.getModel()
427: .getElementAt(sel);
428: } else {
429: ok.setEnabled(false);
430: label[0] = null;
431: }
432: }
433: });
434: JScrollPane scrollList = new JScrollPane(list);
435:
436: JLabel title = new JLabel(
437: "Choose a field to use for node labels:");
438:
439: // layout the buttons
440: Box bbox = new Box(BoxLayout.X_AXIS);
441: bbox.add(Box.createHorizontalStrut(5));
442: bbox.add(Box.createHorizontalGlue());
443: bbox.add(ok);
444: bbox.add(Box.createHorizontalStrut(5));
445: bbox.add(cancel);
446: bbox.add(Box.createHorizontalStrut(5));
447:
448: // put everything into a panel
449: JPanel panel = new JPanel(new BorderLayout());
450: panel.add(title, BorderLayout.NORTH);
451: panel.add(scrollList, BorderLayout.CENTER);
452: panel.add(bbox, BorderLayout.SOUTH);
453: panel
454: .setBorder(BorderFactory.createEmptyBorder(5, 2, 2,
455: 2));
456:
457: // show the dialog
458: dialog.setContentPane(panel);
459: dialog.pack();
460: dialog.setLocationRelativeTo(c);
461: dialog.setVisible(true);
462: dialog.dispose();
463:
464: // return the label field selection
465: return label[0];
466: }
467: }
468:
469: public static class FitOverviewListener implements
470: ItemBoundsListener {
471: private Rectangle2D m_bounds = new Rectangle2D.Double();
472: private Rectangle2D m_temp = new Rectangle2D.Double();
473: private double m_d = 15;
474:
475: public void itemBoundsChanged(Display d) {
476: d.getItemBounds(m_temp);
477: GraphicsLib.expand(m_temp, 25 / d.getScale());
478:
479: double dd = m_d / d.getScale();
480: double xd = Math.abs(m_temp.getMinX() - m_bounds.getMinX());
481: double yd = Math.abs(m_temp.getMinY() - m_bounds.getMinY());
482: double wd = Math.abs(m_temp.getWidth()
483: - m_bounds.getWidth());
484: double hd = Math.abs(m_temp.getHeight()
485: - m_bounds.getHeight());
486: if (xd > dd || yd > dd || wd > dd || hd > dd) {
487: m_bounds.setFrame(m_temp);
488: DisplayLib.fitViewToBounds(d, m_bounds, 0);
489: }
490: }
491: }
492:
493: } // end of class GraphView
|