001: /**
002: * Copyright (c) 2004-2006 Regents of the University of California.
003: * See "license-prefuse.txt" for licensing terms.
004: */package prefuse.demos;
005:
006: import java.awt.Cursor;
007: import java.awt.event.MouseEvent;
008: import java.awt.geom.Point2D;
009: import java.awt.geom.Rectangle2D;
010: import java.util.Iterator;
011:
012: import javax.swing.JFrame;
013: import javax.swing.SwingUtilities;
014:
015: import prefuse.Constants;
016: import prefuse.Display;
017: import prefuse.Visualization;
018: import prefuse.action.ActionList;
019: import prefuse.action.RepaintAction;
020: import prefuse.action.assignment.ColorAction;
021: import prefuse.action.assignment.DataColorAction;
022: import prefuse.action.layout.Layout;
023: import prefuse.action.layout.graph.ForceDirectedLayout;
024: import prefuse.activity.Activity;
025: import prefuse.controls.ControlAdapter;
026: import prefuse.controls.PanControl;
027: import prefuse.controls.ZoomControl;
028: import prefuse.data.Graph;
029: import prefuse.data.Node;
030: import prefuse.render.DefaultRendererFactory;
031: import prefuse.render.PolygonRenderer;
032: import prefuse.render.Renderer;
033: import prefuse.render.ShapeRenderer;
034: import prefuse.util.ColorLib;
035: import prefuse.util.GraphicsLib;
036: import prefuse.visual.AggregateItem;
037: import prefuse.visual.AggregateTable;
038: import prefuse.visual.VisualGraph;
039: import prefuse.visual.VisualItem;
040:
041: /**
042: * Demo application showcasing the use of AggregateItems to
043: * visualize groupings of nodes with in a graph visualization.
044: *
045: * This class uses the AggregateLayout class to compute bounding
046: * polygons for each aggregate and the AggregateDragControl to
047: * enable drags of both nodes and node aggregates.
048: *
049: * @author <a href="http://jheer.org">jeffrey heer</a>
050: */
051: public class AggregateDemo extends Display {
052:
053: public static final String GRAPH = "graph";
054: public static final String NODES = "graph.nodes";
055: public static final String EDGES = "graph.edges";
056: public static final String AGGR = "aggregates";
057:
058: public AggregateDemo() {
059: // initialize display and data
060: super (new Visualization());
061: initDataGroups();
062:
063: // set up the renderers
064: // draw the nodes as basic shapes
065: Renderer nodeR = new ShapeRenderer(20);
066: // draw aggregates as polygons with curved edges
067: Renderer polyR = new PolygonRenderer(Constants.POLY_TYPE_CURVE);
068: ((PolygonRenderer) polyR).setCurveSlack(0.15f);
069:
070: DefaultRendererFactory drf = new DefaultRendererFactory();
071: drf.setDefaultRenderer(nodeR);
072: drf.add("ingroup('aggregates')", polyR);
073: m_vis.setRendererFactory(drf);
074:
075: // set up the visual operators
076: // first set up all the color actions
077: ColorAction nStroke = new ColorAction(NODES,
078: VisualItem.STROKECOLOR);
079: nStroke.setDefaultColor(ColorLib.gray(100));
080: nStroke.add("_hover", ColorLib.gray(50));
081:
082: ColorAction nFill = new ColorAction(NODES, VisualItem.FILLCOLOR);
083: nFill.setDefaultColor(ColorLib.gray(255));
084: nFill.add("_hover", ColorLib.gray(200));
085:
086: ColorAction nEdges = new ColorAction(EDGES,
087: VisualItem.STROKECOLOR);
088: nEdges.setDefaultColor(ColorLib.gray(100));
089:
090: ColorAction aStroke = new ColorAction(AGGR,
091: VisualItem.STROKECOLOR);
092: aStroke.setDefaultColor(ColorLib.gray(200));
093: aStroke.add("_hover", ColorLib.rgb(255, 100, 100));
094:
095: int[] palette = new int[] { ColorLib.rgba(255, 200, 200, 150),
096: ColorLib.rgba(200, 255, 200, 150),
097: ColorLib.rgba(200, 200, 255, 150) };
098: ColorAction aFill = new DataColorAction(AGGR, "id",
099: Constants.NOMINAL, VisualItem.FILLCOLOR, palette);
100:
101: // bundle the color actions
102: ActionList colors = new ActionList();
103: colors.add(nStroke);
104: colors.add(nFill);
105: colors.add(nEdges);
106: colors.add(aStroke);
107: colors.add(aFill);
108:
109: // now create the main layout routine
110: ActionList layout = new ActionList(Activity.INFINITY);
111: layout.add(colors);
112: layout.add(new ForceDirectedLayout(GRAPH, true));
113: layout.add(new AggregateLayout(AGGR));
114: layout.add(new RepaintAction());
115: m_vis.putAction("layout", layout);
116:
117: // set up the display
118: setSize(500, 500);
119: pan(250, 250);
120: setHighQuality(true);
121: addControlListener(new AggregateDragControl());
122: addControlListener(new ZoomControl());
123: addControlListener(new PanControl());
124:
125: // set things running
126: m_vis.run("layout");
127: }
128:
129: private void initDataGroups() {
130: // create sample graph
131: // 9 nodes broken up into 3 interconnected cliques
132: Graph g = new Graph();
133: for (int i = 0; i < 3; ++i) {
134: Node n1 = g.addNode();
135: Node n2 = g.addNode();
136: Node n3 = g.addNode();
137: g.addEdge(n1, n2);
138: g.addEdge(n1, n3);
139: g.addEdge(n2, n3);
140: }
141: g.addEdge(0, 3);
142: g.addEdge(3, 6);
143: g.addEdge(6, 0);
144:
145: // add visual data groups
146: VisualGraph vg = m_vis.addGraph(GRAPH, g);
147: m_vis.setInteractive(EDGES, null, false);
148: m_vis.setValue(NODES, null, VisualItem.SHAPE, new Integer(
149: Constants.SHAPE_ELLIPSE));
150:
151: AggregateTable at = m_vis.addAggregates(AGGR);
152: at.addColumn(VisualItem.POLYGON, float[].class);
153: at.addColumn("id", int.class);
154:
155: // add nodes to aggregates
156: // create an aggregate for each 3-clique of nodes
157: Iterator nodes = vg.nodes();
158: for (int i = 0; i < 3; ++i) {
159: AggregateItem aitem = (AggregateItem) at.addItem();
160: aitem.setInt("id", i);
161: for (int j = 0; j < 3; ++j) {
162: aitem.addItem((VisualItem) nodes.next());
163: }
164: }
165: }
166:
167: public static void main(String[] argv) {
168: JFrame frame = demo();
169: frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
170: frame.setVisible(true);
171: }
172:
173: public static JFrame demo() {
174: AggregateDemo ad = new AggregateDemo();
175: JFrame frame = new JFrame(
176: "p r e f u s e | a g g r e g a t e d");
177: frame.getContentPane().add(ad);
178: frame.pack();
179: return frame;
180: }
181:
182: } // end of class AggregateDemo
183:
184: /**
185: * Layout algorithm that computes a convex hull surrounding
186: * aggregate items and saves it in the "_polygon" field.
187: */
188: class AggregateLayout extends Layout {
189:
190: private int m_margin = 5; // convex hull pixel margin
191: private double[] m_pts; // buffer for computing convex hulls
192:
193: public AggregateLayout(String aggrGroup) {
194: super (aggrGroup);
195: }
196:
197: /**
198: * @see edu.berkeley.guir.prefuse.action.Action#run(edu.berkeley.guir.prefuse.ItemRegistry, double)
199: */
200: public void run(double frac) {
201:
202: AggregateTable aggr = (AggregateTable) m_vis.getGroup(m_group);
203: // do we have any to process?
204: int num = aggr.getTupleCount();
205: if (num == 0)
206: return;
207:
208: // update buffers
209: int maxsz = 0;
210: for (Iterator aggrs = aggr.tuples(); aggrs.hasNext();)
211: maxsz = Math.max(maxsz, 4 * 2 * ((AggregateItem) aggrs
212: .next()).getAggregateSize());
213: if (m_pts == null || maxsz > m_pts.length) {
214: m_pts = new double[maxsz];
215: }
216:
217: // compute and assign convex hull for each aggregate
218: Iterator aggrs = m_vis.visibleItems(m_group);
219: while (aggrs.hasNext()) {
220: AggregateItem aitem = (AggregateItem) aggrs.next();
221:
222: int idx = 0;
223: if (aitem.getAggregateSize() == 0)
224: continue;
225: VisualItem item = null;
226: Iterator iter = aitem.items();
227: while (iter.hasNext()) {
228: item = (VisualItem) iter.next();
229: if (item.isVisible()) {
230: addPoint(m_pts, idx, item, m_margin);
231: idx += 2 * 4;
232: }
233: }
234: // if no aggregates are visible, do nothing
235: if (idx == 0)
236: continue;
237:
238: // compute convex hull
239: double[] nhull = GraphicsLib.convexHull(m_pts, idx);
240:
241: // prepare viz attribute array
242: float[] fhull = (float[]) aitem.get(VisualItem.POLYGON);
243: if (fhull == null || fhull.length < nhull.length)
244: fhull = new float[nhull.length];
245: else if (fhull.length > nhull.length)
246: fhull[nhull.length] = Float.NaN;
247:
248: // copy hull values
249: for (int j = 0; j < nhull.length; j++)
250: fhull[j] = (float) nhull[j];
251: aitem.set(VisualItem.POLYGON, fhull);
252: aitem.setValidated(false); // force invalidation
253: }
254: }
255:
256: private static void addPoint(double[] pts, int idx,
257: VisualItem item, int growth) {
258: Rectangle2D b = item.getBounds();
259: double minX = (b.getMinX()) - growth, minY = (b.getMinY())
260: - growth;
261: double maxX = (b.getMaxX()) + growth, maxY = (b.getMaxY())
262: + growth;
263: pts[idx] = minX;
264: pts[idx + 1] = minY;
265: pts[idx + 2] = minX;
266: pts[idx + 3] = maxY;
267: pts[idx + 4] = maxX;
268: pts[idx + 5] = minY;
269: pts[idx + 6] = maxX;
270: pts[idx + 7] = maxY;
271: }
272:
273: } // end of class AggregateLayout
274:
275: /**
276: * Interactive drag control that is "aggregate-aware"
277: */
278: class AggregateDragControl extends ControlAdapter {
279:
280: private VisualItem activeItem;
281: protected Point2D down = new Point2D.Double();
282: protected Point2D temp = new Point2D.Double();
283: protected boolean dragged;
284:
285: /**
286: * Creates a new drag control that issues repaint requests as an item
287: * is dragged.
288: */
289: public AggregateDragControl() {
290: }
291:
292: /**
293: * @see prefuse.controls.Control#itemEntered(prefuse.visual.VisualItem, java.awt.event.MouseEvent)
294: */
295: public void itemEntered(VisualItem item, MouseEvent e) {
296: Display d = (Display) e.getSource();
297: d.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
298: activeItem = item;
299: if (!(item instanceof AggregateItem))
300: setFixed(item, true);
301: }
302:
303: /**
304: * @see prefuse.controls.Control#itemExited(prefuse.visual.VisualItem, java.awt.event.MouseEvent)
305: */
306: public void itemExited(VisualItem item, MouseEvent e) {
307: if (activeItem == item) {
308: activeItem = null;
309: setFixed(item, false);
310: }
311: Display d = (Display) e.getSource();
312: d.setCursor(Cursor.getDefaultCursor());
313: }
314:
315: /**
316: * @see prefuse.controls.Control#itemPressed(prefuse.visual.VisualItem, java.awt.event.MouseEvent)
317: */
318: public void itemPressed(VisualItem item, MouseEvent e) {
319: if (!SwingUtilities.isLeftMouseButton(e))
320: return;
321: dragged = false;
322: Display d = (Display) e.getComponent();
323: d.getAbsoluteCoordinate(e.getPoint(), down);
324: if (item instanceof AggregateItem)
325: setFixed(item, true);
326: }
327:
328: /**
329: * @see prefuse.controls.Control#itemReleased(prefuse.visual.VisualItem, java.awt.event.MouseEvent)
330: */
331: public void itemReleased(VisualItem item, MouseEvent e) {
332: if (!SwingUtilities.isLeftMouseButton(e))
333: return;
334: if (dragged) {
335: activeItem = null;
336: setFixed(item, false);
337: dragged = false;
338: }
339: }
340:
341: /**
342: * @see prefuse.controls.Control#itemDragged(prefuse.visual.VisualItem, java.awt.event.MouseEvent)
343: */
344: public void itemDragged(VisualItem item, MouseEvent e) {
345: if (!SwingUtilities.isLeftMouseButton(e))
346: return;
347: dragged = true;
348: Display d = (Display) e.getComponent();
349: d.getAbsoluteCoordinate(e.getPoint(), temp);
350: double dx = temp.getX() - down.getX();
351: double dy = temp.getY() - down.getY();
352:
353: move(item, dx, dy);
354:
355: down.setLocation(temp);
356: }
357:
358: protected static void setFixed(VisualItem item, boolean fixed) {
359: if (item instanceof AggregateItem) {
360: Iterator items = ((AggregateItem) item).items();
361: while (items.hasNext()) {
362: setFixed((VisualItem) items.next(), fixed);
363: }
364: } else {
365: item.setFixed(fixed);
366: }
367: }
368:
369: protected static void move(VisualItem item, double dx, double dy) {
370: if (item instanceof AggregateItem) {
371: Iterator items = ((AggregateItem) item).items();
372: while (items.hasNext()) {
373: move((VisualItem) items.next(), dx, dy);
374: }
375: } else {
376: double x = item.getX();
377: double y = item.getY();
378: item.setStartX(x);
379: item.setStartY(y);
380: item.setX(x + dx);
381: item.setY(y + dy);
382: item.setEndX(x + dx);
383: item.setEndY(y + dy);
384: }
385: }
386:
387: } // end of class AggregateDragControl
|