001: package prefuse.demos;
002:
003: import java.awt.Font;
004: import java.awt.Shape;
005: import java.awt.event.ComponentAdapter;
006: import java.awt.event.ComponentEvent;
007:
008: import javax.swing.BorderFactory;
009: import javax.swing.JFrame;
010:
011: import prefuse.Constants;
012: import prefuse.Display;
013: import prefuse.Visualization;
014: import prefuse.action.Action;
015: import prefuse.action.ActionList;
016: import prefuse.action.RepaintAction;
017: import prefuse.action.animate.ColorAnimator;
018: import prefuse.action.animate.LocationAnimator;
019: import prefuse.action.assignment.ColorAction;
020: import prefuse.action.layout.AxisLayout;
021: import prefuse.activity.Activity;
022: import prefuse.activity.ActivityAdapter;
023: import prefuse.activity.SlowInSlowOutPacer;
024: import prefuse.data.Schema;
025: import prefuse.data.Table;
026: import prefuse.data.Tuple;
027: import prefuse.data.event.TupleSetListener;
028: import prefuse.data.expression.FunctionExpression;
029: import prefuse.data.expression.FunctionTable;
030: import prefuse.data.expression.Predicate;
031: import prefuse.data.expression.parser.ExpressionParser;
032: import prefuse.data.io.DataIOException;
033: import prefuse.data.io.DelimitedTextTableReader;
034: import prefuse.data.query.SearchQueryBinding;
035: import prefuse.data.search.SearchTupleSet;
036: import prefuse.data.tuple.TupleSet;
037: import prefuse.render.DefaultRendererFactory;
038: import prefuse.render.ShapeRenderer;
039: import prefuse.render.LabelRenderer;
040: import prefuse.util.ColorLib;
041: import prefuse.util.FontLib;
042: import prefuse.util.PrefuseLib;
043: import prefuse.util.ui.JSearchPanel;
044: import prefuse.visual.VisualItem;
045: import prefuse.visual.VisualTable;
046:
047: /**
048: * Re-implementation of Ben Fry's Zipdecode. Check out the
049: * original at <a href="http://acg.media.mit.edu/people/fry/zipdecode/">
050: * http://acg.media.mit.edu/people/fry/zipdecode/</a>.
051: *
052: * This demo showcases creating new functions in the prefuse expression
053: * language, creating derived columns, and provides an example of using
054: * a dedicated focus set of items to support more efficient data handling.
055: *
056: * @author <a href="http://jheer.org">jeffrey heer</a>
057: */
058: public class ZipDecode extends Display implements Constants {
059:
060: public static final String ZIPCODES = "/zipcode.txt";
061: public static final String STATES = "/state.txt";
062:
063: // data groups
064: private static final String DATA = "data";
065: private static final String LABELS = "labels";
066: private static final String FOCUS = Visualization.FOCUS_ITEMS;
067:
068: public static class StateLookupFunction extends FunctionExpression {
069: private static Table s_states;
070: static {
071: try {
072: s_states = new DelimitedTextTableReader()
073: .readTable(STATES);
074: } catch (Exception e) {
075: e.printStackTrace();
076: }
077: }
078:
079: public StateLookupFunction() {
080: super (1);
081: }
082:
083: public String getName() {
084: return "STATE";
085: }
086:
087: public Class getType(Schema s) {
088: return String.class;
089: }
090:
091: public Object get(Tuple t) {
092: int code = s_states.index("code").get(param(0).getInt(t));
093: return s_states.getString(code, "alpha");
094: }
095: }
096:
097: // add state function to the FunctionTable
098: static {
099: FunctionTable.addFunction("STATE", StateLookupFunction.class);
100: }
101:
102: public ZipDecode(final Table t) {
103: super (new Visualization());
104:
105: // this predicate makes sure only the continental states are included
106: Predicate filter = (Predicate) ExpressionParser
107: .parse("state >= 1 && state <= 56 && state != 2 && state != 15");
108: VisualTable vt = m_vis.addTable(DATA, t, filter,
109: getDataSchema());
110: // zip codes are loaded in as integers, so lets create a derived
111: // column that has correctly-formatted 5 digit strings
112: vt.addColumn("zipstr", "LPAD(zip,5,'0')");
113: // now add a formatted label to show within the visualization
114: vt.addColumn("label",
115: "CONCAT(CAP(city),', ',STATE(state),' ',zipstr)");
116:
117: // create a filter controlling label appearance
118: Predicate loneResult = (Predicate) ExpressionParser
119: .parse("INGROUP('_search_') AND GROUPSIZE('_search_')=1 AND "
120: + "LENGTH(QUERY('_search_'))=5");
121:
122: // add a table of visible city,state,zip labels
123: // this is a derived table, overriding only the fields that need to
124: // have unique values and inheriting all other data values from the
125: // data table. in particular, we want to inherit the x,y coordinates.
126: m_vis.addDerivedTable(LABELS, DATA, loneResult,
127: getLabelSchema());
128:
129: // -- renderers -------------------------------------------------------
130:
131: DefaultRendererFactory rf = new DefaultRendererFactory();
132: rf.setDefaultRenderer(new ShapeRenderer(1)); // 1 pixel rectangles
133: rf.add("INGROUP('labels')", new LabelRenderer("label") {
134: public Shape getShape(VisualItem item) {
135: // set horizontal alignment based on x-coordinate position
136: setHorizontalAlignment(item.getX() > getWidth() / 2 ? RIGHT
137: : LEFT);
138: // now return shape as usual
139: return super .getShape(item);
140: }
141: });
142: m_vis.setRendererFactory(rf);
143:
144: // -- actions ---------------------------------------------------------
145:
146: ActionList layout = new ActionList();
147: layout.add(new AxisLayout(DATA, "lat", Y_AXIS));
148: layout.add(new AxisLayout(DATA, "lon", X_AXIS));
149: m_vis.putAction("layout", layout);
150:
151: // the update list updates the colors of data points and sets the visual
152: // properties for any labels. Color updating is limited only to the
153: // current focus items, ensuring faster performance.
154: final Action update = new ZipColorAction(FOCUS);
155: m_vis.putAction("update", update);
156:
157: // animate a change in color in the interface. this animation is quite
158: // short, only 200ms, so that it does not impede with interaction.
159: // color animation of data points looks only at the focus items,
160: // ensuring faster performance.
161: ActionList animate = new ActionList(200);
162: animate.add(new ColorAnimator(FOCUS, VisualItem.FILLCOLOR));
163: animate.add(new ColorAnimator(LABELS, VisualItem.TEXTCOLOR));
164: animate.add(new RepaintAction());
165: animate.addActivityListener(new ActivityAdapter() {
166: public void activityCancelled(Activity a) {
167: // if animation is canceled, set colors to final state
168: update.run(1.0);
169: }
170: });
171: m_vis.putAction("animate", animate);
172:
173: // update items after a resize of the display, animating them to their
174: // new locations. this animates all data points, so is noticeably slow.
175: ActionList resize = new ActionList(1500);
176: resize.setPacingFunction(new SlowInSlowOutPacer());
177: resize.add(new LocationAnimator(DATA));
178: resize.add(new LocationAnimator(LABELS));
179: resize.add(new RepaintAction());
180: m_vis.putAction("resize", resize);
181:
182: // -- display ---------------------------------------------------------
183:
184: setSize(720, 360);
185: setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
186: setBackground(ColorLib.getGrayscale(50));
187: setFocusable(false);
188:
189: // -- search ----------------------------------------------------------
190:
191: // zipcode text search is performed using a prefix based search,
192: // provided by a search dynamic query. to make this application run
193: // more efficiently, we optimize data handling by taking all search
194: // results (both added and removed) and shuttling them into a
195: // focus set. we work with this reduced set for updating and
196: // animating color changes in the action definitions above.
197:
198: // create a final reference to the focus set, so that the following
199: // search listener can access it.
200: final TupleSet focus = m_vis.getFocusGroup(FOCUS);
201:
202: // create the search query binding
203: SearchQueryBinding searchQ = new SearchQueryBinding(vt,
204: "zipstr");
205: final SearchTupleSet search = searchQ.getSearchSet();
206:
207: // create the listener that collects search results into a focus set
208: search.addTupleSetListener(new TupleSetListener() {
209: public void tupleSetChanged(TupleSet t, Tuple[] add,
210: Tuple[] rem) {
211: m_vis.cancel("animate");
212:
213: // invalidate changed tuples, add them all to the focus set
214: focus.clear();
215: for (int i = 0; i < add.length; ++i) {
216: ((VisualItem) add[i]).setValidated(false);
217: focus.addTuple(add[i]);
218: }
219: for (int i = 0; i < rem.length; ++i) {
220: ((VisualItem) rem[i]).setValidated(false);
221: focus.addTuple(rem[i]);
222: }
223:
224: m_vis.run("update");
225: m_vis.run("animate");
226: }
227: });
228: m_vis.addFocusGroup(Visualization.SEARCH_ITEMS, search);
229:
230: // create and parameterize a search panel for searching on zip code
231: final JSearchPanel searcher = searchQ.createSearchPanel();
232: searcher.setLabelText("zip>"); // the search box label
233: searcher.setShowCancel(false); // don't show the cancel query button
234: searcher.setShowBorder(false); // don't show the search box border
235: searcher.setFont(FontLib.getFont("Georgia", Font.PLAIN, 22));
236: searcher.setBackground(ColorLib.getGrayscale(50));
237: searcher.setForeground(ColorLib.getColor(100, 100, 75));
238: add(searcher); // add the search box as a sub-component of the display
239: searcher.setBounds(10, getHeight() - 40, 120, 30);
240:
241: addComponentListener(new ComponentAdapter() {
242: public void componentResized(ComponentEvent e) {
243: m_vis.run("layout");
244: m_vis.run("update");
245: m_vis.run("resize");
246: searcher.setBounds(10, getHeight() - 40, 120, 30);
247: invalidate();
248: }
249: });
250:
251: // -- launch ----------------------------------------------------------
252:
253: m_vis.run("layout");
254: m_vis.run("animate");
255: }
256:
257: private static Schema getDataSchema() {
258: Schema s = PrefuseLib.getVisualItemSchema();
259: s.setDefault(VisualItem.INTERACTIVE, false);
260: s.setDefault(VisualItem.FILLCOLOR, ColorLib.rgb(100, 100, 75));
261: return s;
262: }
263:
264: private static Schema getLabelSchema() {
265: Schema s = PrefuseLib.getMinimalVisualSchema();
266: s.setDefault(VisualItem.INTERACTIVE, false);
267:
268: // default font is 16 point Georgia
269: s.addInterpolatedColumn(VisualItem.FONT, Font.class, FontLib
270: .getFont("Georgia", 16));
271:
272: // default fill color should be invisible
273: s.addInterpolatedColumn(VisualItem.FILLCOLOR, int.class);
274: s.setInterpolatedDefault(VisualItem.FILLCOLOR, 0);
275:
276: s.addInterpolatedColumn(VisualItem.TEXTCOLOR, int.class);
277: // default text color is white
278: s.setInterpolatedDefault(VisualItem.TEXTCOLOR, ColorLib
279: .gray(255));
280: // default start text color is fully transparent
281: s.setDefault(VisualItem.STARTTEXTCOLOR, ColorLib.gray(255, 0));
282: return s;
283: }
284:
285: // ------------------------------------------------------------------------
286:
287: public static void main(String[] args) {
288: String datafile = ZIPCODES;
289: if (args.length > 0)
290: datafile = args[0];
291:
292: try {
293: JFrame frame = demo(datafile);
294: frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
295: frame.setVisible(true);
296: } catch (Exception e) {
297: e.printStackTrace();
298: System.exit(1);
299: }
300: }
301:
302: public static JFrame demo() {
303: try {
304: return demo(ZIPCODES);
305: } catch (Exception e) {
306: return null;
307: }
308: }
309:
310: public static JFrame demo(String table) throws DataIOException {
311: DelimitedTextTableReader tr = new DelimitedTextTableReader();
312: Table t = tr.readTable(table);
313: ZipDecode zd = new ZipDecode(t);
314:
315: JFrame frame = new JFrame("p r e f u s e | z i p d e c o d e");
316: frame.getContentPane().add(zd);
317: frame.pack();
318: return frame;
319: }
320:
321: public static class ZipColorAction extends ColorAction {
322: public ZipColorAction(String group) {
323: super (group, VisualItem.FILLCOLOR);
324: }
325:
326: public int getColor(VisualItem item) {
327: if (item.isInGroup(Visualization.SEARCH_ITEMS)) {
328: return ColorLib.gray(255);
329: } else {
330: return ColorLib.rgb(100, 100, 75);
331: }
332: }
333: }
334:
335: } // end of class ZipDecode
|