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.Insets;
007: import java.awt.event.ActionEvent;
008: import java.awt.event.ActionListener;
009: import java.awt.event.MouseEvent;
010: import java.awt.geom.Rectangle2D;
011: import java.util.Iterator;
012: import java.util.logging.Level;
013: import java.util.logging.Logger;
014:
015: import javax.swing.AbstractAction;
016: import javax.swing.BorderFactory;
017: import javax.swing.JFrame;
018: import javax.swing.SwingUtilities;
019:
020: import prefuse.Constants;
021: import prefuse.Display;
022: import prefuse.Visualization;
023: import prefuse.action.ActionList;
024: import prefuse.action.RepaintAction;
025: import prefuse.action.assignment.ColorAction;
026: import prefuse.action.distortion.Distortion;
027: import prefuse.action.distortion.FisheyeDistortion;
028: import prefuse.action.layout.Layout;
029: import prefuse.controls.AnchorUpdateControl;
030: import prefuse.controls.ControlAdapter;
031: import prefuse.data.Schema;
032: import prefuse.data.Table;
033: import prefuse.render.DefaultRendererFactory;
034: import prefuse.render.LabelRenderer;
035: import prefuse.util.ColorLib;
036: import prefuse.visual.VisualItem;
037:
038: /**
039: * <p>A prefuse-based implementation of Fisheye Menus, showcasing the use of
040: * visual distortion to provide access to a large number of data items
041: * without scrolling.</p>
042: *
043: * <p>This implementation is inspired by the Fisheye Menu research conducted
044: * by Ben Bederson at the University of Maryland. See the
045: * <a href="http://www.cs.umd.edu/hcil/fisheyemenu/">Fisheye Menu project
046: * web site</a> for more details.</p>
047: *
048: * @author <a href="http://jheer.org">jeffrey heer</a>
049: */
050: public class FisheyeMenu extends Display {
051:
052: /** The data group name of menu items. */
053: public static final String ITEMS = "items";
054: /** The label data field for menu items. */
055: public static final String LABEL = "label";
056: /** The action data field for menu items. */
057: public static final String ACTION = "action";
058:
059: /**
060: * This schema holds the data representation for internal storage of
061: * menu items.
062: */
063: protected static final Schema ITEM_SCHEMA = new Schema();
064: static {
065: ITEM_SCHEMA.addColumn(LABEL, String.class);
066: ITEM_SCHEMA.addColumn(ACTION, ActionListener.class);
067: }
068:
069: private Table m_items = ITEM_SCHEMA.instantiate(); // table of menu items
070:
071: private double m_maxHeight = 500; // maximum menu height in pixels
072: private double m_scale = 7; // scale parameter for fisheye distortion
073:
074: /**
075: * Create a new, empty FisheyeMenu.
076: * @see #addMenuItem(String, javax.swing.Action)
077: */
078: public FisheyeMenu() {
079: super (new Visualization());
080: m_vis.addTable(ITEMS, m_items);
081:
082: // set up the renderer to use
083: LabelRenderer renderer = new LabelRenderer(LABEL);
084: renderer.setHorizontalPadding(0);
085: renderer.setVerticalPadding(1);
086: renderer.setHorizontalAlignment(Constants.LEFT);
087: m_vis.setRendererFactory(new DefaultRendererFactory(renderer));
088:
089: // set up this display
090: setSize(100, 470);
091: setHighQuality(true);
092: setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 5));
093: addControlListener(new ControlAdapter() {
094: // dispatch an action event to the menu item
095: public void itemClicked(VisualItem item, MouseEvent e) {
096: ActionListener al = (ActionListener) item.get(ACTION);
097: al.actionPerformed(new ActionEvent(item, e.getID(),
098: "click", e.getWhen(), e.getModifiers()));
099: }
100: });
101:
102: // text color function
103: // items with the mouse over printed in red, otherwise black
104: ColorAction colors = new ColorAction(ITEMS,
105: VisualItem.TEXTCOLOR);
106: colors.setDefaultColor(ColorLib.gray(0));
107: colors.add("hover()", ColorLib.rgb(255, 0, 0));
108:
109: // initial layout and coloring
110: ActionList init = new ActionList();
111: init.add(new VerticalLineLayout(m_maxHeight));
112: init.add(colors);
113: init.add(new RepaintAction());
114: m_vis.putAction("init", init);
115:
116: // fisheye distortion based on the current anchor location
117: ActionList distort = new ActionList();
118: Distortion feye = new FisheyeDistortion(0, m_scale);
119: distort.add(feye);
120: distort.add(colors);
121: distort.add(new RepaintAction());
122: m_vis.putAction("distort", distort);
123:
124: // update the distortion anchor position to be the current
125: // location of the mouse pointer
126: addControlListener(new AnchorUpdateControl(feye, "distort"));
127: }
128:
129: /**
130: * Adds a menu item to the fisheye menu.
131: * @param name the menu label to use
132: * @param action the ActionListener to notify when the item is clicked
133: * The prefuse VisualItem corresponding to this menu item will
134: * be returned by the ActionEvent's getSource() method.
135: */
136: public void addMenuItem(String name, ActionListener listener) {
137: int row = m_items.addRow();
138: m_items.set(row, LABEL, name);
139: m_items.set(row, ACTION, listener);
140: }
141:
142: /**
143: * Run a demonstration of the FisheyeMenu
144: */
145: public static final void main(String[] argv) {
146: // only log warnings
147: Logger.getLogger("prefuse").setLevel(Level.WARNING);
148:
149: FisheyeMenu fm = demo();
150:
151: // create and display application window
152: JFrame f = new JFrame("p r e f u s e | f i s h e y e");
153: f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
154: f.getContentPane().add(fm);
155: f.pack();
156: f.setVisible(true);
157: }
158:
159: public static FisheyeMenu demo() {
160: // create a new fisheye menu and populate it
161: FisheyeMenu fm = new FisheyeMenu();
162: for (int i = 1; i <= 72; ++i) {
163: // add menu items that simply print their label when clicked
164: fm.addMenuItem(String.valueOf(i), new AbstractAction() {
165: public void actionPerformed(ActionEvent e) {
166: System.out.println("clicked item: "
167: + ((VisualItem) e.getSource()).get(LABEL));
168: System.out.flush();
169: }
170: });
171: }
172: fm.getVisualization().run("init");
173: return fm;
174: }
175:
176: /**
177: * Lines up all VisualItems vertically. Also scales the size such that
178: * all items fit within the maximum layout size, and updates the
179: * Display to the final computed size.
180: */
181: public class VerticalLineLayout extends Layout {
182: private double m_maxHeight = 600;
183:
184: public VerticalLineLayout(double maxHeight) {
185: m_maxHeight = maxHeight;
186: }
187:
188: public void run(double frac) {
189: // first pass
190: double w = 0, h = 0;
191: Iterator iter = m_vis.items();
192: while (iter.hasNext()) {
193: VisualItem item = (VisualItem) iter.next();
194: item.setSize(1.0);
195: h += item.getBounds().getHeight();
196: }
197: double scale = h > m_maxHeight ? m_maxHeight / h : 1.0;
198:
199: Display d = m_vis.getDisplay(0);
200: Insets ins = d.getInsets();
201:
202: // second pass
203: h = ins.top;
204: double ih, y = 0, x = ins.left;
205: iter = m_vis.items();
206: while (iter.hasNext()) {
207: VisualItem item = (VisualItem) iter.next();
208: item.setSize(scale);
209: item.setEndSize(scale);
210: Rectangle2D b = item.getBounds();
211:
212: w = Math.max(w, b.getWidth());
213: ih = b.getHeight();
214: y = h + (ih / 2);
215: setX(item, null, x);
216: setY(item, null, y);
217: h += ih;
218: }
219:
220: // set the display size to fit text
221: setSize(d, (int) Math.round(2 * m_scale * w + ins.left
222: + ins.right), (int) Math.round(h + ins.bottom));
223: }
224:
225: private void setSize(final Display d, final int width,
226: final int height) {
227: SwingUtilities.invokeLater(new Runnable() {
228: public void run() {
229: d.setSize(width, height);
230: }
231: });
232: }
233: } // end of inner class VerticalLineLayout
234:
235: } // end of class FisheyeMenu
|