Friday, March 9, 2012

Visualizing Piles of Money

I recently had a need to use the Column Chart 'Piles of Money' in one of my GWT projects.
 
However to be able to use it in my project I needed to create a GWT "wrapper" class as documented here.  See the bottom of my post for the Wrapper Class Code


With my new warpper in hand I am now able to create my data table:

private AbstractDataTable createMoneyTable() {
DataTable data = DataTable.create();

data.addColumn(ColumnType.STRING, "Label");
data.addColumn(ColumnType.NUMBER, "Value");
data.addRows(4);
data.setValue(0, 0, "France");
data.setValue(1, 0, "Germany");
data.setValue(2, 0, "USA");
data.setValue(3, 0, "Poland");
data.setCell(0, 1, 10, "$10,000", null);
data.setCell(1, 1, 30, "$30,000", null);
data.setCell(2, 1, 20, "$20,000", null);
data.setCell(3, 1, 7.5, "$7,500", null);
return data;
}

Once my data table is ready I can also create a select handler:

private SelectHandler createMoneySelectHandler(final PilesOfMoney chart) {
return new SelectHandler() {
@Override
public void onSelect(SelectEvent event) {
String message = "";

// May be multiple selections.
JsArray selections = chart.getSelections();

for (int i = 0; i < selections.length(); i++) {
// add a new line for each selection
message += i == 0 ? "" : "\n";

Selection selection = selections.get(i);

if (selection.isCell()) {
// isCell() returns true if a cell has been selected.

// getRow() returns the row number of the selected cell.
int row = selection.getRow();
// getColumn() returns the column number of the selected
// cell.
int column = selection.getColumn();
message += "cell " + row + ":" + column + " selected";
} else if (selection.isRow()) {
// isRow() returns true if an entire row has been
// selected.

// getRow() returns the row number of the selected row.
int row = selection.getRow();
message += "row " + row + " selected";
} else {
// unreachable
message += "Selections should be either row selections or cell selections.";
message += "  Other visualizations support column selections as well.";
}
}

Window.alert(message);
}
};
}

Create the Options method for this chart:

private PilesOfMoney.Options createMoneyOptions() {
PilesOfMoney.Options options = PilesOfMoney.Options
.create();
options.setTitle("Lots of money");
options.setCurrency("USD");
options.setCanSelect(true);
options.setMin(2);
options.setMax(70);
return options;
}

From my GWT onModuleLoad() method I can load the visualization api:
// Load the visualization api, passing the onLoadCallback to be called
// when loading is done.
VisualizationUtils.loadVisualizationApi(onLoadCallback,
CoreChart.PACKAGE);

Define my onLoadCallback:
// Create a callback to be called when the visualization API
// has been loaded.
Runnable onLoadCallback = new Runnable() {
public void run() {
Panel panel = RootPanel.get();

PilesOfMoney pom = new PilesOfMoney(createMoneyTable(),
createMoneyOptions());

pom.addSelectHandler(createMoneySelectHandler(pom));
panel.add(pom);

}

A couple of other things are still required
Modify your module.gwt.xml file and add the following import to support Visualizations:
  <inherits name='com.google.gwt.visualization.Visualization'>

Modify the GWT applications web page and add the following code:

    

Once you have everything done your visualization should be ready to go:
Stacks of Money Demo











You can also click on the stacks of cash and get a window alert showing the row of data that you clicked:
Stacks of Money with select event
















And finally the all important Wrapper Class for the Piles of Money chart:

/**
 * 
 */
package com.mynumnum.vis.client;


import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.dom.client.Element;
import com.google.gwt.visualization.client.AbstractDataTable;
import com.google.gwt.visualization.client.AbstractDrawOptions;
import com.google.gwt.visualization.client.Selectable;
import com.google.gwt.visualization.client.Selection;
import com.google.gwt.visualization.client.events.Handler;
import com.google.gwt.visualization.client.events.ReadyHandler;
import com.google.gwt.visualization.client.events.SelectHandler;
import com.google.gwt.visualization.client.events.StateChangeHandler;
import com.google.gwt.visualization.client.visualizations.Visualization;


/**
 * @author Jim Hathaway
 * 
 */
public class PilesOfMoney extends Visualization implements
Selectable {


/**
* Options for drawing the chart.
*/
public static class Options extends AbstractDrawOptions {
public static Options create() {
return JavaScriptObject.createObject().cast();
}


protected Options() {
}


public final native void setTitle(String title) /*-{
this.title = title;
}-*/;


/**
* @param currency
*            String USD or EUR
*/
public final native void setCurrency(String currency) /*-{
this.currency = currency;
}-*/;


/**

* @param canSelect
*            boolean, if true (default), users can click on bars
*/
public final native void setCanSelect(boolean canSelect) /*-{
this.canSelect = canSelect;
}-*/;


public final native void setMin(int min) /*-{
this.min = min;
}-*/;


public final native void setMax(int max) /*-{
this.max = max;
}-*/;


}


public static final String PACKAGE = "pilesofmoney";


public PilesOfMoney() {
super();
}


public PilesOfMoney(AbstractDataTable data, Options options) {
super(data, options);
}


public final void addReadyHandler(ReadyHandler handler) {
Handler.addHandler(this, "ready", handler);
}


public final void addStateChangeHandler(StateChangeHandler handler) {
Handler.addHandler(this, "statechange", handler);
}


@Override
protected native JavaScriptObject createJso(Element parent) /*-{
return new $wnd.PilesOfMoney(parent);
}-*/;


public final void addSelectHandler(SelectHandler handler) {
Selection.addSelectHandler(this, handler);
}


public final JsArray getSelections() {
return Selection.getSelections(this);
}


public final void setSelections(JsArray sel) {
Selection.setSelections(this, sel);
}
}