package prefuse.demos; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Font; import java.awt.Shape; import java.awt.event.MouseEvent; import java.awt.geom.Rectangle2D; import java.util.Iterator; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.SwingConstants; import prefuse.Display; import prefuse.Visualization; import prefuse.action.ActionList; import prefuse.action.RepaintAction; import prefuse.action.animate.ColorAnimator; import prefuse.action.assignment.ColorAction; import prefuse.action.layout.Layout; import prefuse.action.layout.graph.SquarifiedTreeMapLayout; import prefuse.controls.ControlAdapter; import prefuse.data.Schema; import prefuse.data.Tree; import prefuse.data.expression.Predicate; import prefuse.data.expression.parser.ExpressionParser; import prefuse.data.io.TreeMLReader; import prefuse.data.query.SearchQueryBinding; import prefuse.render.AbstractShapeRenderer; import prefuse.render.DefaultRendererFactory; import prefuse.render.LabelRenderer; import prefuse.util.ColorLib; import prefuse.util.ColorMap; import prefuse.util.FontLib; import prefuse.util.PrefuseLib; import prefuse.util.UpdateListener; import prefuse.util.ui.JFastLabel; import prefuse.util.ui.JSearchPanel; import prefuse.util.ui.UILib; import prefuse.visual.DecoratorItem; import prefuse.visual.NodeItem; import prefuse.visual.VisualItem; import prefuse.visual.VisualTree; import prefuse.visual.expression.InGroupPredicate; import prefuse.visual.sort.TreeDepthItemSorter; /** * Demonstration showcasing a TreeMap layout of a hierarchical data * set and the use of dynamic query binding for text search. Animation * is used to highlight changing search results. * * @author <a href="http://jheer.org">jeffrey heer</a> */ public class TreeMap extends Display { public static final String TREE_CHI = "/chi-ontology.xml.gz"; // create data description of labels, setting colors, fonts ahead of time private static final Schema LABEL_SCHEMA = PrefuseLib.getVisualItemSchema(); static { LABEL_SCHEMA.setDefault(VisualItem.INTERACTIVE, false); LABEL_SCHEMA.setDefault(VisualItem.TEXTCOLOR, ColorLib.gray(200)); LABEL_SCHEMA.setDefault(VisualItem.FONT, FontLib.getFont("Tahoma",16)); } private static final String tree = "tree"; private static final String treeNodes = "tree.nodes"; private static final String treeEdges = "tree.edges"; private static final String labels = "labels"; private SearchQueryBinding searchQ; public TreeMap(Tree t, String label) { super(new Visualization()); // add the tree to the visualization VisualTree vt = m_vis.addTree(tree, t); m_vis.setVisible(treeEdges, null, false); // ensure that only leaf nodes are interactive Predicate noLeaf = (Predicate)ExpressionParser.parse("childcount()>0"); m_vis.setInteractive(treeNodes, noLeaf, false); // add labels to the visualization // first create a filter to show labels only at top-level nodes Predicate labelP = (Predicate)ExpressionParser.parse("treedepth()=1"); // now create the labels as decorators of the nodes m_vis.addDecorators(labels, treeNodes, labelP, LABEL_SCHEMA); // set up the renderers - one for nodes and one for labels DefaultRendererFactory rf = new DefaultRendererFactory(); rf.add(new InGroupPredicate(treeNodes), new NodeRenderer()); rf.add(new InGroupPredicate(labels), new LabelRenderer(label)); m_vis.setRendererFactory(rf); // border colors final ColorAction borderColor = new BorderColorAction(treeNodes); final ColorAction fillColor = new FillColorAction(treeNodes); // color settings ActionList colors = new ActionList(); colors.add(fillColor); colors.add(borderColor); m_vis.putAction("colors", colors); // animate paint change ActionList animatePaint = new ActionList(400); animatePaint.add(new ColorAnimator(treeNodes)); animatePaint.add(new RepaintAction()); m_vis.putAction("animatePaint", animatePaint); // create the single filtering and layout action list ActionList layout = new ActionList(); layout.add(new SquarifiedTreeMapLayout(tree)); layout.add(new LabelLayout(labels)); layout.add(colors); layout.add(new RepaintAction()); m_vis.putAction("layout", layout); // initialize our display setSize(700,600); setItemSorter(new TreeDepthItemSorter()); addControlListener(new ControlAdapter() { public void itemEntered(VisualItem item, MouseEvent e) { item.setStrokeColor(borderColor.getColor(item)); item.getVisualization().repaint(); } public void itemExited(VisualItem item, MouseEvent e) { item.setStrokeColor(item.getEndStrokeColor()); item.getVisualization().repaint(); } }); searchQ = new SearchQueryBinding(vt.getNodeTable(), label); m_vis.addFocusGroup(Visualization.SEARCH_ITEMS, searchQ.getSearchSet()); searchQ.getPredicate().addExpressionListener(new UpdateListener() { public void update(Object src) { m_vis.cancel("animatePaint"); m_vis.run("colors"); m_vis.run("animatePaint"); } }); // perform layout m_vis.run("layout"); } public SearchQueryBinding getSearchQuery() { return searchQ; } public static void main(String argv[]) { UILib.setPlatformLookAndFeel(); String infile = TREE_CHI; String label = "name"; if ( argv.length > 1 ) { infile = argv[0]; label = argv[1]; } JComponent treemap = demo(infile, label); JFrame frame = new JFrame("p r e f u s e | t r e e m a p"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setContentPane(treemap); frame.pack(); frame.setVisible(true); } public static JComponent demo() { return demo(TREE_CHI, "name"); } public static JComponent demo(String datafile, final String label) { Tree t = null; try { t = (Tree)new TreeMLReader().readGraph(datafile); } catch ( Exception e ) { e.printStackTrace(); System.exit(1); } // create a new treemap final TreeMap treemap = new TreeMap(t, label); // create a search panel for the tree map JSearchPanel search = treemap.getSearchQuery().createSearchPanel(); search.setShowResultCount(true); search.setBorder(BorderFactory.createEmptyBorder(5,5,4,0)); search.setFont(FontLib.getFont("Tahoma", Font.PLAIN, 11)); final JFastLabel title = new JFastLabel(" "); title.setPreferredSize(new Dimension(350, 20)); title.setVerticalAlignment(SwingConstants.BOTTOM); title.setBorder(BorderFactory.createEmptyBorder(3,0,0,0)); title.setFont(FontLib.getFont("Tahoma", Font.PLAIN, 16)); treemap.addControlListener(new ControlAdapter() { public void itemEntered(VisualItem item, MouseEvent e) { title.setText(item.getString(label)); } public void itemExited(VisualItem item, MouseEvent e) { title.setText(null); } }); Box box = UILib.getBox(new Component[]{title,search}, true, 10, 3, 0); JPanel panel = new JPanel(new BorderLayout()); panel.add(treemap, BorderLayout.CENTER); panel.add(box, BorderLayout.SOUTH); UILib.setColor(panel, Color.BLACK, Color.GRAY); return panel; } // ------------------------------------------------------------------------ /** * Set the stroke color for drawing treemap node outlines. A graded * grayscale ramp is used, with higer nodes in the tree drawn in * lighter shades of gray. */ public static class BorderColorAction extends ColorAction { public BorderColorAction(String group) { super(group, VisualItem.STROKECOLOR); } public int getColor(VisualItem item) { NodeItem nitem = (NodeItem)item; if ( nitem.isHover() ) return ColorLib.rgb(99,130,191); int depth = nitem.getDepth(); if ( depth < 2 ) { return ColorLib.gray(100); } else if ( depth < 4 ) { return ColorLib.gray(75); } else { return ColorLib.gray(50); } } } /** * Set fill colors for treemap nodes. Search items are colored * in pink, while normal nodes are shaded according to their * depth in the tree. */ public static class FillColorAction extends ColorAction { private ColorMap cmap = new ColorMap( ColorLib.getInterpolatedPalette(10, ColorLib.rgb(85,85,85), ColorLib.rgb(0,0,0)), 0, 9); public FillColorAction(String group) { super(group, VisualItem.FILLCOLOR); } public int getColor(VisualItem item) { if ( item instanceof NodeItem ) { NodeItem nitem = (NodeItem)item; if ( nitem.getChildCount() > 0 ) { return 0; // no fill for parent nodes } else { if ( m_vis.isInGroup(item, Visualization.SEARCH_ITEMS) ) return ColorLib.rgb(191,99,130); else return cmap.getColor(nitem.getDepth()); } } else { return cmap.getColor(0); } } } // end of inner class TreeMapColorAction /** * Set label positions. Labels are assumed to be DecoratorItem instances, * decorating their respective nodes. The layout simply gets the bounds * of the decorated node and assigns the label coordinates to the center * of those bounds. */ public static class LabelLayout extends Layout { public LabelLayout(String group) { super(group); } public void run(double frac) { Iterator iter = m_vis.items(m_group); while ( iter.hasNext() ) { DecoratorItem item = (DecoratorItem)iter.next(); VisualItem node = item.getDecoratedItem(); Rectangle2D bounds = node.getBounds(); setX(item, null, bounds.getCenterX()); setY(item, null, bounds.getCenterY()); } } } // end of inner class LabelLayout /** * A renderer for treemap nodes. Draws simple rectangles, but defers * the bounds management to the layout. */ public static class NodeRenderer extends AbstractShapeRenderer { private Rectangle2D m_bounds = new Rectangle2D.Double(); public NodeRenderer() { m_manageBounds = false; } protected Shape getRawShape(VisualItem item) { m_bounds.setRect(item.getBounds()); return m_bounds; } } // end of inner class NodeRenderer } // end of class TreeMap