/** * Copyright (c) 2004-2006 Regents of the University of California. * See "license-prefuse.txt" for licensing terms. */ package prefuse.demos; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.geom.Rectangle2D; import java.util.Iterator; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.JFrame; import javax.swing.SwingUtilities; import prefuse.Constants; import prefuse.Display; import prefuse.Visualization; 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.layout.Layout; import prefuse.controls.AnchorUpdateControl; import prefuse.controls.ControlAdapter; import prefuse.data.Schema; import prefuse.data.Table; import prefuse.render.DefaultRendererFactory; import prefuse.render.LabelRenderer; import prefuse.util.ColorLib; import prefuse.visual.VisualItem; /** * <p>A prefuse-based implementation of Fisheye Menus, showcasing the use of * visual distortion to provide access to a large number of data items * without scrolling.</p> * * <p>This implementation is inspired by the Fisheye Menu research conducted * by Ben Bederson at the University of Maryland. See the * <a href="http://www.cs.umd.edu/hcil/fisheyemenu/">Fisheye Menu project * web site</a> for more details.</p> * * @author <a href="http://jheer.org">jeffrey heer</a> */ public class FisheyeMenu extends Display { /** The data group name of menu items. */ public static final String ITEMS = "items"; /** The label data field for menu items. */ public static final String LABEL = "label"; /** The action data field for menu items. */ public static final String ACTION = "action"; /** * This schema holds the data representation for internal storage of * menu items. */ protected static final Schema ITEM_SCHEMA = new Schema(); static { ITEM_SCHEMA.addColumn(LABEL, String.class); ITEM_SCHEMA.addColumn(ACTION, ActionListener.class); } private Table m_items = ITEM_SCHEMA.instantiate(); // table of menu items private double m_maxHeight = 500; // maximum menu height in pixels private double m_scale = 7; // scale parameter for fisheye distortion /** * Create a new, empty FisheyeMenu. * @see #addMenuItem(String, javax.swing.Action) */ public FisheyeMenu() { super(new Visualization()); m_vis.addTable(ITEMS, m_items); // set up the renderer to use LabelRenderer renderer = new LabelRenderer(LABEL); renderer.setHorizontalPadding(0); renderer.setVerticalPadding(1); renderer.setHorizontalAlignment(Constants.LEFT); m_vis.setRendererFactory(new DefaultRendererFactory(renderer)); // set up this display setSize(100,470); setHighQuality(true); setBorder(BorderFactory.createEmptyBorder(10,10,10,5)); addControlListener(new ControlAdapter() { // dispatch an action event to the menu item public void itemClicked(VisualItem item, MouseEvent e) { ActionListener al = (ActionListener)item.get(ACTION); al.actionPerformed(new ActionEvent(item, e.getID(), "click", e.getWhen(), e.getModifiers())); } }); // text color function // items with the mouse over printed in red, otherwise black ColorAction colors = new ColorAction(ITEMS, VisualItem.TEXTCOLOR); colors.setDefaultColor(ColorLib.gray(0)); colors.add("hover()", ColorLib.rgb(255,0,0)); // initial layout and coloring ActionList init = new ActionList(); init.add(new VerticalLineLayout(m_maxHeight)); init.add(colors); init.add(new RepaintAction()); m_vis.putAction("init", init); // fisheye distortion based on the current anchor location ActionList distort = new ActionList(); Distortion feye = new FisheyeDistortion(0,m_scale); distort.add(feye); distort.add(colors); distort.add(new RepaintAction()); m_vis.putAction("distort", distort); // update the distortion anchor position to be the current // location of the mouse pointer addControlListener(new AnchorUpdateControl(feye, "distort")); } /** * Adds a menu item to the fisheye menu. * @param name the menu label to use * @param action the ActionListener to notify when the item is clicked * The prefuse VisualItem corresponding to this menu item will * be returned by the ActionEvent's getSource() method. */ public void addMenuItem(String name, ActionListener listener) { int row = m_items.addRow(); m_items.set(row, LABEL, name); m_items.set(row, ACTION, listener); } /** * Run a demonstration of the FisheyeMenu */ public static final void main(String[] argv) { // only log warnings Logger.getLogger("prefuse").setLevel(Level.WARNING); FisheyeMenu fm = demo(); // create and display application window JFrame f = new JFrame("p r e f u s e | f i s h e y e"); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.getContentPane().add(fm); f.pack(); f.setVisible(true); } public static FisheyeMenu demo() { // create a new fisheye menu and populate it FisheyeMenu fm = new FisheyeMenu(); for ( int i=1; i<=72; ++i ) { // add menu items that simply print their label when clicked fm.addMenuItem(String.valueOf(i), new AbstractAction() { public void actionPerformed(ActionEvent e) { System.out.println("clicked item: "+ ((VisualItem)e.getSource()).get(LABEL)); System.out.flush(); } }); } fm.getVisualization().run("init"); return fm; } /** * Lines up all VisualItems vertically. Also scales the size such that * all items fit within the maximum layout size, and updates the * Display to the final computed size. */ public class VerticalLineLayout extends Layout { private double m_maxHeight = 600; public VerticalLineLayout(double maxHeight) { m_maxHeight = maxHeight; } public void run(double frac) { // first pass double w = 0, h = 0; Iterator iter = m_vis.items(); while ( iter.hasNext() ) { VisualItem item = (VisualItem)iter.next(); item.setSize(1.0); h += item.getBounds().getHeight(); } double scale = h > m_maxHeight ? m_maxHeight/h : 1.0; Display d = m_vis.getDisplay(0); Insets ins = d.getInsets(); // second pass h = ins.top; double ih, y=0, x=ins.left; iter = m_vis.items(); while ( iter.hasNext() ) { VisualItem item = (VisualItem)iter.next(); item.setSize(scale); item.setEndSize(scale); Rectangle2D b = item.getBounds(); w = Math.max(w, b.getWidth()); ih = b.getHeight(); y = h+(ih/2); setX(item, null, x); setY(item, null, y); h += ih; } // set the display size to fit text setSize(d, (int)Math.round(2*m_scale*w + ins.left + ins.right), (int)Math.round(h + ins.bottom)); } private void setSize(final Display d, final int width, final int height) { SwingUtilities.invokeLater(new Runnable() { public void run() { d.setSize(width, height); } }); } } // end of inner class VerticalLineLayout } // end of class FisheyeMenu