package prefuse.demos;
import java.awt.Font;
import java.awt.Shape;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import prefuse.Constants;
import prefuse.Display;
import prefuse.Visualization;
import prefuse.action.Action;
import prefuse.action.ActionList;
import prefuse.action.RepaintAction;
import prefuse.action.animate.ColorAnimator;
import prefuse.action.animate.LocationAnimator;
import prefuse.action.assignment.ColorAction;
import prefuse.action.layout.AxisLayout;
import prefuse.activity.Activity;
import prefuse.activity.ActivityAdapter;
import prefuse.activity.SlowInSlowOutPacer;
import prefuse.data.Schema;
import prefuse.data.Table;
import prefuse.data.Tuple;
import prefuse.data.event.TupleSetListener;
import prefuse.data.expression.FunctionExpression;
import prefuse.data.expression.FunctionTable;
import prefuse.data.expression.Predicate;
import prefuse.data.expression.parser.ExpressionParser;
import prefuse.data.io.DataIOException;
import prefuse.data.io.DelimitedTextTableReader;
import prefuse.data.query.SearchQueryBinding;
import prefuse.data.search.SearchTupleSet;
import prefuse.data.tuple.TupleSet;
import prefuse.render.DefaultRendererFactory;
import prefuse.render.ShapeRenderer;
import prefuse.render.LabelRenderer;
import prefuse.util.ColorLib;
import prefuse.util.FontLib;
import prefuse.util.PrefuseLib;
import prefuse.util.ui.JSearchPanel;
import prefuse.visual.VisualItem;
import prefuse.visual.VisualTable;
/**
* Re-implementation of Ben Fry's Zipdecode. Check out the
* original at <a href="http://acg.media.mit.edu/people/fry/zipdecode/">
* http://acg.media.mit.edu/people/fry/zipdecode/</a>.
*
* This demo showcases creating new functions in the prefuse expression
* language, creating derived columns, and provides an example of using
* a dedicated focus set of items to support more efficient data handling.
*
* @author <a href="http://jheer.org">jeffrey heer</a>
*/
public class ZipDecode extends Display implements Constants {
public static final String ZIPCODES = "/zipcode.txt";
public static final String STATES = "/state.txt";
// data groups
private static final String DATA = "data";
private static final String LABELS = "labels";
private static final String FOCUS = Visualization.FOCUS_ITEMS;
public static class StateLookupFunction extends FunctionExpression {
private static Table s_states;
static {
try {
s_states = new DelimitedTextTableReader().readTable(STATES);
} catch ( Exception e ) { e.printStackTrace(); }
}
public StateLookupFunction() { super(1); }
public String getName() { return "STATE"; }
public Class getType(Schema s) { return String.class; }
public Object get(Tuple t) {
int code = s_states.index("code").get(param(0).getInt(t));
return s_states.getString(code, "alpha");
}
}
// add state function to the FunctionTable
static { FunctionTable.addFunction("STATE", StateLookupFunction.class); }
public ZipDecode(final Table t) {
super(new Visualization());
// this predicate makes sure only the continental states are included
Predicate filter = (Predicate)ExpressionParser.parse(
"state >= 1 && state <= 56 && state != 2 && state != 15");
VisualTable vt = m_vis.addTable(DATA, t, filter, getDataSchema());
// zip codes are loaded in as integers, so lets create a derived
// column that has correctly-formatted 5 digit strings
vt.addColumn("zipstr", "LPAD(zip,5,'0')");
// now add a formatted label to show within the visualization
vt.addColumn("label", "CONCAT(CAP(city),', ',STATE(state),' ',zipstr)");
// create a filter controlling label appearance
Predicate loneResult = (Predicate)ExpressionParser.parse(
"INGROUP('_search_') AND GROUPSIZE('_search_')=1 AND " +
"LENGTH(QUERY('_search_'))=5");
// add a table of visible city,state,zip labels
// this is a derived table, overriding only the fields that need to
// have unique values and inheriting all other data values from the
// data table. in particular, we want to inherit the x,y coordinates.
m_vis.addDerivedTable(LABELS, DATA, loneResult, getLabelSchema());
// -- renderers -------------------------------------------------------
DefaultRendererFactory rf = new DefaultRendererFactory();
rf.setDefaultRenderer(new ShapeRenderer(1)); // 1 pixel rectangles
rf.add("INGROUP('labels')", new LabelRenderer("label") {
public Shape getShape(VisualItem item) {
// set horizontal alignment based on x-coordinate position
setHorizontalAlignment(item.getX()>getWidth()/2 ? RIGHT:LEFT);
// now return shape as usual
return super.getShape(item);
}
});
m_vis.setRendererFactory(rf);
// -- actions ---------------------------------------------------------
ActionList layout = new ActionList();
layout.add(new AxisLayout(DATA, "lat", Y_AXIS));
layout.add(new AxisLayout(DATA, "lon", X_AXIS));
m_vis.putAction("layout", layout);
// the update list updates the colors of data points and sets the visual
// properties for any labels. Color updating is limited only to the
// current focus items, ensuring faster performance.
final Action update = new ZipColorAction(FOCUS);
m_vis.putAction("update", update);
// animate a change in color in the interface. this animation is quite
// short, only 200ms, so that it does not impede with interaction.
// color animation of data points looks only at the focus items,
// ensuring faster performance.
ActionList animate = new ActionList(200);
animate.add(new ColorAnimator(FOCUS, VisualItem.FILLCOLOR));
animate.add(new ColorAnimator(LABELS, VisualItem.TEXTCOLOR));
animate.add(new RepaintAction());
animate.addActivityListener(new ActivityAdapter() {
public void activityCancelled(Activity a) {
// if animation is canceled, set colors to final state
update.run(1.0);
}
});
m_vis.putAction("animate", animate);
// update items after a resize of the display, animating them to their
// new locations. this animates all data points, so is noticeably slow.
ActionList resize = new ActionList(1500);
resize.setPacingFunction(new SlowInSlowOutPacer());
resize.add(new LocationAnimator(DATA));
resize.add(new LocationAnimator(LABELS));
resize.add(new RepaintAction());
m_vis.putAction("resize", resize);
// -- display ---------------------------------------------------------
setSize(720, 360);
setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
setBackground(ColorLib.getGrayscale(50));
setFocusable(false);
// -- search ----------------------------------------------------------
// zipcode text search is performed using a prefix based search,
// provided by a search dynamic query. to make this application run
// more efficiently, we optimize data handling by taking all search
// results (both added and removed) and shuttling them into a
// focus set. we work with this reduced set for updating and
// animating color changes in the action definitions above.
// create a final reference to the focus set, so that the following
// search listener can access it.
final TupleSet focus = m_vis.getFocusGroup(FOCUS);
// create the search query binding
SearchQueryBinding searchQ = new SearchQueryBinding(vt, "zipstr");
final SearchTupleSet search = searchQ.getSearchSet();
// create the listener that collects search results into a focus set
search.addTupleSetListener(new TupleSetListener() {
public void tupleSetChanged(TupleSet t, Tuple[] add, Tuple[] rem) {
m_vis.cancel("animate");
// invalidate changed tuples, add them all to the focus set
focus.clear();
for ( int i=0; i<add.length; ++i ) {
((VisualItem)add[i]).setValidated(false);
focus.addTuple(add[i]);
}
for ( int i=0; i<rem.length; ++i ) {
((VisualItem)rem[i]).setValidated(false);
focus.addTuple(rem[i]);
}
m_vis.run("update");
m_vis.run("animate");
}
});
m_vis.addFocusGroup(Visualization.SEARCH_ITEMS, search);
// create and parameterize a search panel for searching on zip code
final JSearchPanel searcher = searchQ.createSearchPanel();
searcher.setLabelText("zip>"); // the search box label
searcher.setShowCancel(false); // don't show the cancel query button
searcher.setShowBorder(false); // don't show the search box border
searcher.setFont(FontLib.getFont("Georgia", Font.PLAIN, 22));
searcher.setBackground(ColorLib.getGrayscale(50));
searcher.setForeground(ColorLib.getColor(100,100,75));
add(searcher); // add the search box as a sub-component of the display
searcher.setBounds(10, getHeight()-40, 120, 30);
addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent e) {
m_vis.run("layout");
m_vis.run("update");
m_vis.run("resize");
searcher.setBounds(10, getHeight()-40, 120, 30);
invalidate();
}
});
// -- launch ----------------------------------------------------------
m_vis.run("layout");
m_vis.run("animate");
}
private static Schema getDataSchema() {
Schema s = PrefuseLib.getVisualItemSchema();
s.setDefault(VisualItem.INTERACTIVE, false);
s.setDefault(VisualItem.FILLCOLOR, ColorLib.rgb(100,100,75));
return s;
}
private static Schema getLabelSchema() {
Schema s = PrefuseLib.getMinimalVisualSchema();
s.setDefault(VisualItem.INTERACTIVE, false);
// default font is 16 point Georgia
s.addInterpolatedColumn(
VisualItem.FONT, Font.class, FontLib.getFont("Georgia",16));
// default fill color should be invisible
s.addInterpolatedColumn(VisualItem.FILLCOLOR, int.class);
s.setInterpolatedDefault(VisualItem.FILLCOLOR, 0);
s.addInterpolatedColumn(VisualItem.TEXTCOLOR, int.class);
// default text color is white
s.setInterpolatedDefault(VisualItem.TEXTCOLOR, ColorLib.gray(255));
// default start text color is fully transparent
s.setDefault(VisualItem.STARTTEXTCOLOR, ColorLib.gray(255,0));
return s;
}
// ------------------------------------------------------------------------
public static void main(String[] args) {
String datafile = ZIPCODES;
if ( args.length > 0 )
datafile = args[0];
try {
JFrame frame = demo(datafile);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
} catch ( Exception e ) {
e.printStackTrace();
System.exit(1);
}
}
public static JFrame demo() {
try {
return demo(ZIPCODES);
} catch ( Exception e ) {
return null;
}
}
public static JFrame demo(String table) throws DataIOException {
DelimitedTextTableReader tr = new DelimitedTextTableReader();
Table t = tr.readTable(table);
ZipDecode zd = new ZipDecode(t);
JFrame frame = new JFrame("p r e f u s e | z i p d e c o d e");
frame.getContentPane().add(zd);
frame.pack();
return frame;
}
public static class ZipColorAction extends ColorAction {
public ZipColorAction(String group) {
super(group, VisualItem.FILLCOLOR);
}
public int getColor(VisualItem item) {
if ( item.isInGroup(Visualization.SEARCH_ITEMS) ) {
return ColorLib.gray(255);
} else {
return ColorLib.rgb(100,100,75);
}
}
}
} // end of class ZipDecode