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.Font;
008: import java.awt.Shape;
009: import java.awt.event.MouseEvent;
010: import java.awt.geom.Rectangle2D;
011: import java.util.Iterator;
012:
013: import javax.swing.BorderFactory;
014: import javax.swing.Box;
015: import javax.swing.JComponent;
016: import javax.swing.JFrame;
017: import javax.swing.JPanel;
018: import javax.swing.SwingConstants;
019:
020: import prefuse.Display;
021: import prefuse.Visualization;
022: import prefuse.action.ActionList;
023: import prefuse.action.RepaintAction;
024: import prefuse.action.animate.ColorAnimator;
025: import prefuse.action.assignment.ColorAction;
026: import prefuse.action.layout.Layout;
027: import prefuse.action.layout.graph.SquarifiedTreeMapLayout;
028: import prefuse.controls.ControlAdapter;
029: import prefuse.data.Schema;
030: import prefuse.data.Tree;
031: import prefuse.data.expression.Predicate;
032: import prefuse.data.expression.parser.ExpressionParser;
033: import prefuse.data.io.TreeMLReader;
034: import prefuse.data.query.SearchQueryBinding;
035: import prefuse.render.AbstractShapeRenderer;
036: import prefuse.render.DefaultRendererFactory;
037: import prefuse.render.LabelRenderer;
038: import prefuse.util.ColorLib;
039: import prefuse.util.ColorMap;
040: import prefuse.util.FontLib;
041: import prefuse.util.PrefuseLib;
042: import prefuse.util.UpdateListener;
043: import prefuse.util.ui.JFastLabel;
044: import prefuse.util.ui.JSearchPanel;
045: import prefuse.util.ui.UILib;
046: import prefuse.visual.DecoratorItem;
047: import prefuse.visual.NodeItem;
048: import prefuse.visual.VisualItem;
049: import prefuse.visual.VisualTree;
050: import prefuse.visual.expression.InGroupPredicate;
051: import prefuse.visual.sort.TreeDepthItemSorter;
052:
053: /**
054: * Demonstration showcasing a TreeMap layout of a hierarchical data
055: * set and the use of dynamic query binding for text search. Animation
056: * is used to highlight changing search results.
057: *
058: * @author <a href="http://jheer.org">jeffrey heer</a>
059: */
060: public class TreeMap extends Display {
061:
062: public static final String TREE_CHI = "/chi-ontology.xml.gz";
063:
064: // create data description of labels, setting colors, fonts ahead of time
065: private static final Schema LABEL_SCHEMA = PrefuseLib
066: .getVisualItemSchema();
067: static {
068: LABEL_SCHEMA.setDefault(VisualItem.INTERACTIVE, false);
069: LABEL_SCHEMA.setDefault(VisualItem.TEXTCOLOR, ColorLib
070: .gray(200));
071: LABEL_SCHEMA.setDefault(VisualItem.FONT, FontLib.getFont(
072: "Tahoma", 16));
073: }
074:
075: private static final String tree = "tree";
076: private static final String treeNodes = "tree.nodes";
077: private static final String treeEdges = "tree.edges";
078: private static final String labels = "labels";
079:
080: private SearchQueryBinding searchQ;
081:
082: public TreeMap(Tree t, String label) {
083: super (new Visualization());
084:
085: // add the tree to the visualization
086: VisualTree vt = m_vis.addTree(tree, t);
087: m_vis.setVisible(treeEdges, null, false);
088:
089: // ensure that only leaf nodes are interactive
090: Predicate noLeaf = (Predicate) ExpressionParser
091: .parse("childcount()>0");
092: m_vis.setInteractive(treeNodes, noLeaf, false);
093:
094: // add labels to the visualization
095: // first create a filter to show labels only at top-level nodes
096: Predicate labelP = (Predicate) ExpressionParser
097: .parse("treedepth()=1");
098: // now create the labels as decorators of the nodes
099: m_vis.addDecorators(labels, treeNodes, labelP, LABEL_SCHEMA);
100:
101: // set up the renderers - one for nodes and one for labels
102: DefaultRendererFactory rf = new DefaultRendererFactory();
103: rf.add(new InGroupPredicate(treeNodes), new NodeRenderer());
104: rf.add(new InGroupPredicate(labels), new LabelRenderer(label));
105: m_vis.setRendererFactory(rf);
106:
107: // border colors
108: final ColorAction borderColor = new BorderColorAction(treeNodes);
109: final ColorAction fillColor = new FillColorAction(treeNodes);
110:
111: // color settings
112: ActionList colors = new ActionList();
113: colors.add(fillColor);
114: colors.add(borderColor);
115: m_vis.putAction("colors", colors);
116:
117: // animate paint change
118: ActionList animatePaint = new ActionList(400);
119: animatePaint.add(new ColorAnimator(treeNodes));
120: animatePaint.add(new RepaintAction());
121: m_vis.putAction("animatePaint", animatePaint);
122:
123: // create the single filtering and layout action list
124: ActionList layout = new ActionList();
125: layout.add(new SquarifiedTreeMapLayout(tree));
126: layout.add(new LabelLayout(labels));
127: layout.add(colors);
128: layout.add(new RepaintAction());
129: m_vis.putAction("layout", layout);
130:
131: // initialize our display
132: setSize(700, 600);
133: setItemSorter(new TreeDepthItemSorter());
134: addControlListener(new ControlAdapter() {
135: public void itemEntered(VisualItem item, MouseEvent e) {
136: item.setStrokeColor(borderColor.getColor(item));
137: item.getVisualization().repaint();
138: }
139:
140: public void itemExited(VisualItem item, MouseEvent e) {
141: item.setStrokeColor(item.getEndStrokeColor());
142: item.getVisualization().repaint();
143: }
144: });
145:
146: searchQ = new SearchQueryBinding(vt.getNodeTable(), label);
147: m_vis.addFocusGroup(Visualization.SEARCH_ITEMS, searchQ
148: .getSearchSet());
149: searchQ.getPredicate().addExpressionListener(
150: new UpdateListener() {
151: public void update(Object src) {
152: m_vis.cancel("animatePaint");
153: m_vis.run("colors");
154: m_vis.run("animatePaint");
155: }
156: });
157:
158: // perform layout
159: m_vis.run("layout");
160: }
161:
162: public SearchQueryBinding getSearchQuery() {
163: return searchQ;
164: }
165:
166: public static void main(String argv[]) {
167: UILib.setPlatformLookAndFeel();
168:
169: String infile = TREE_CHI;
170: String label = "name";
171: if (argv.length > 1) {
172: infile = argv[0];
173: label = argv[1];
174: }
175: JComponent treemap = demo(infile, label);
176:
177: JFrame frame = new JFrame("p r e f u s e | t r e e m a p");
178: frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
179: frame.setContentPane(treemap);
180: frame.pack();
181: frame.setVisible(true);
182: }
183:
184: public static JComponent demo() {
185: return demo(TREE_CHI, "name");
186: }
187:
188: public static JComponent demo(String datafile, final String label) {
189: Tree t = null;
190: try {
191: t = (Tree) new TreeMLReader().readGraph(datafile);
192: } catch (Exception e) {
193: e.printStackTrace();
194: System.exit(1);
195: }
196:
197: // create a new treemap
198: final TreeMap treemap = new TreeMap(t, label);
199:
200: // create a search panel for the tree map
201: JSearchPanel search = treemap.getSearchQuery()
202: .createSearchPanel();
203: search.setShowResultCount(true);
204: search.setBorder(BorderFactory.createEmptyBorder(5, 5, 4, 0));
205: search.setFont(FontLib.getFont("Tahoma", Font.PLAIN, 11));
206:
207: final JFastLabel title = new JFastLabel(" ");
208: title.setPreferredSize(new Dimension(350, 20));
209: title.setVerticalAlignment(SwingConstants.BOTTOM);
210: title.setBorder(BorderFactory.createEmptyBorder(3, 0, 0, 0));
211: title.setFont(FontLib.getFont("Tahoma", Font.PLAIN, 16));
212:
213: treemap.addControlListener(new ControlAdapter() {
214: public void itemEntered(VisualItem item, MouseEvent e) {
215: title.setText(item.getString(label));
216: }
217:
218: public void itemExited(VisualItem item, MouseEvent e) {
219: title.setText(null);
220: }
221: });
222:
223: Box box = UILib.getBox(new Component[] { title, search }, true,
224: 10, 3, 0);
225:
226: JPanel panel = new JPanel(new BorderLayout());
227: panel.add(treemap, BorderLayout.CENTER);
228: panel.add(box, BorderLayout.SOUTH);
229: UILib.setColor(panel, Color.BLACK, Color.GRAY);
230: return panel;
231: }
232:
233: // ------------------------------------------------------------------------
234:
235: /**
236: * Set the stroke color for drawing treemap node outlines. A graded
237: * grayscale ramp is used, with higer nodes in the tree drawn in
238: * lighter shades of gray.
239: */
240: public static class BorderColorAction extends ColorAction {
241:
242: public BorderColorAction(String group) {
243: super (group, VisualItem.STROKECOLOR);
244: }
245:
246: public int getColor(VisualItem item) {
247: NodeItem nitem = (NodeItem) item;
248: if (nitem.isHover())
249: return ColorLib.rgb(99, 130, 191);
250:
251: int depth = nitem.getDepth();
252: if (depth < 2) {
253: return ColorLib.gray(100);
254: } else if (depth < 4) {
255: return ColorLib.gray(75);
256: } else {
257: return ColorLib.gray(50);
258: }
259: }
260: }
261:
262: /**
263: * Set fill colors for treemap nodes. Search items are colored
264: * in pink, while normal nodes are shaded according to their
265: * depth in the tree.
266: */
267: public static class FillColorAction extends ColorAction {
268: private ColorMap cmap = new ColorMap(ColorLib
269: .getInterpolatedPalette(10, ColorLib.rgb(85, 85, 85),
270: ColorLib.rgb(0, 0, 0)), 0, 9);
271:
272: public FillColorAction(String group) {
273: super (group, VisualItem.FILLCOLOR);
274: }
275:
276: public int getColor(VisualItem item) {
277: if (item instanceof NodeItem) {
278: NodeItem nitem = (NodeItem) item;
279: if (nitem.getChildCount() > 0) {
280: return 0; // no fill for parent nodes
281: } else {
282: if (m_vis.isInGroup(item,
283: Visualization.SEARCH_ITEMS))
284: return ColorLib.rgb(191, 99, 130);
285: else
286: return cmap.getColor(nitem.getDepth());
287: }
288: } else {
289: return cmap.getColor(0);
290: }
291: }
292:
293: } // end of inner class TreeMapColorAction
294:
295: /**
296: * Set label positions. Labels are assumed to be DecoratorItem instances,
297: * decorating their respective nodes. The layout simply gets the bounds
298: * of the decorated node and assigns the label coordinates to the center
299: * of those bounds.
300: */
301: public static class LabelLayout extends Layout {
302: public LabelLayout(String group) {
303: super (group);
304: }
305:
306: public void run(double frac) {
307: Iterator iter = m_vis.items(m_group);
308: while (iter.hasNext()) {
309: DecoratorItem item = (DecoratorItem) iter.next();
310: VisualItem node = item.getDecoratedItem();
311: Rectangle2D bounds = node.getBounds();
312: setX(item, null, bounds.getCenterX());
313: setY(item, null, bounds.getCenterY());
314: }
315: }
316: } // end of inner class LabelLayout
317:
318: /**
319: * A renderer for treemap nodes. Draws simple rectangles, but defers
320: * the bounds management to the layout.
321: */
322: public static class NodeRenderer extends AbstractShapeRenderer {
323: private Rectangle2D m_bounds = new Rectangle2D.Double();
324:
325: public NodeRenderer() {
326: m_manageBounds = false;
327: }
328:
329: protected Shape getRawShape(VisualItem item) {
330: m_bounds.setRect(item.getBounds());
331: return m_bounds;
332: }
333: } // end of inner class NodeRenderer
334:
335: } // end of class TreeMap
|