package net.techreadiness.plugin.action.reports;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.Lists;
/**
* Wrapper class around the Google Visualization GeoChart.
*
* For this Bean to be used, you must include the following in the <head> tag on your page:
* {@code <script type='text/javascript' src='https://www.google.com/jsapi'></script>}
*
* You will also need to add a div to the page where the chart will go, like
* this: <div id='mapDiv'></div>
*
* There is an option onSelect event that can be captured. You can set the
* onSelectMethodName in this bean. Then you would define that method on the
* page. Use code similar to this to view the selection: var selection =
* geoChart.getSelection(); alert('You selected ' +
* geoData.getRowProperty(selection[0].row, 'stateCode'));
*
* geoChart is the variable name of the chart object and geoData is the name of
* the DataTable used in the chart.
*
*/
public class GeoChartBean {
private static final String GOOGLE_URL = "http://chart.apis.google.com/chart?";
protected String chartDiv;
protected int width;
protected int height;
protected String hoverLabel;
protected String backgroundColor;
protected List<String> colors;
protected String datalessRegionColor;
protected List<Data> data;
protected String onSelectMethodName;
protected boolean legend;
private static final List<String> ST_WHITE_LIST = Lists.newArrayList("AL",
"AK", "AZ", "AR", "CA", "CO", "CT", "DE", "FL", "GA", "HI", "ID",
"IL", "IN", "IA", "KS", "KY", "LA", "ME", "MD", "MA", "MI", "MN",
"MS", "MO", "MT", "NE", "NV", "NH", "NJ", "NM", "NY", "NC", "ND",
"OH", "OK", "OR", "PA", "RI", "SC", "SD", "TN", "TX", "UT", "VT",
"VA", "WA", "WV", "WI", "WY");
public GeoChartBean() {
width = 1060;
height = 650;
chartDiv = "mapDiv";
backgroundColor = "#FFFFFF";
colors = new ArrayList<>();
addColor("#b81f4b", "#f6851f", "#fcb913", "#00a15e", "#9a9a9a");
datalessRegionColor = "#E5E5E5";
data = new ArrayList<>();
onSelectMethodName = "geoChartSelectHandler";
}
public String getChartDiv() {
return chartDiv;
}
/**
* This is the id of the element the chart will be placed in. Defaults to
* "mapDiv"
*
* @param chartDiv
* HTML id attribute of the element the map should be created in.
*
*/
public void setChartDiv(String chartDiv) {
this.chartDiv = chartDiv;
}
public int getWidth() {
return width;
}
/**
* The width of the chart in px
*
* @param width
* Width of the chart in pixels.
*
*/
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
/**
* The height of the chart in px
*
* @param height
* Height of the chart in pixels.
*
*/
public void setHeight(int height) {
this.height = height;
}
public String getBackgroundColor() {
return backgroundColor;
}
/**
* The background color for the chart. Must be in the format "#RRGGBB"
* Defaults to "#FFFFFF"
*
* @param backgroundColor
* Hex value of the background color.
*
*/
public void setBackgroundColor(String backgroundColor) {
this.backgroundColor = backgroundColor;
}
public List<String> getColors() {
return colors;
}
/**
* Adds colors to be used in the gradient. Calling this method also resets
* old color assignment. Colors must be in the format "#RRGGBB"
*
* @param colors
* Hex color values to be used for the chart.
*
*/
public void addColor(String... colors) {
this.colors = new ArrayList<>();
for (String color : colors) {
this.colors.add(color);
}
}
public void setColors(List<String> colors) {
this.colors = colors;
}
public String getDatalessRegionColor() {
return datalessRegionColor;
}
public String getHoverLabel() {
return hoverLabel;
}
/**
* Used to set the label on the hover-over
*
* @param hoverLabel
* Label the chart should display.
*
*/
public void setHoverLabel(String hoverLabel) {
this.hoverLabel = hoverLabel;
}
/**
* The color that the chart assigns to the regions not specified in the
* data. Must be in the format "#RRGGBB" Defaults to "#E5E5E5"
*
* @param datalessRegionColor
* Hex color value for regions that do not have data.
*
*/
public void setDatalessRegionColor(String datalessRegionColor) {
this.datalessRegionColor = datalessRegionColor;
}
public List<Data> getData() {
return data;
}
public void addData(String id, String state, String code, int data) {
Data d = new Data();
d.id = id;
d.state = state;
d.code = code;
d.data = data;
this.data.add(d);
}
public void addBooleanData(String id, String state, String code,
String label) {
Data d = new Data();
d.id = id;
d.state = state;
d.code = code;
d.data = 1;
d.formattedValue = label;
data.add(d);
}
/**
* Adds data to be used in the chart.
*
* @param id
* Identifier for the piece of data.
*
* @param state
* Name of the state, ex. Iowa, Ohio, Texas
* @param code
* Name of the state's code, ex. IA, OH, TX
* @param data
* The data that you are trying to plot
*/
public void addPercentData(String id, String state, String code, String data) {
try {
Data d = new Data();
d.id = id;
d.code = code;
d.state = state;
if ("TBD".equalsIgnoreCase(data)) {
d.data = getData(-1);
d.formattedValue = "TBD";
} else {
if ("".equalsIgnoreCase(data)) {
d.data = getData(-1);
d.formattedValue = "";
} else {
d.data = getData(Long
.valueOf(StringUtils.remove(data, '%')));
d.formattedValue = data;
}
}
this.data.add(d);
} catch (Exception e) {
// Ignore error
}
}
/**
* Adds data to be used in the chart.
*
* @param id
* Id of the organization for the data
* @param state
* Name of the state, ex. Iowa, Ohio, Texas
* @param code
* Name of the state's code, ex. IA, OH, TX
* @param data
* The data that you are trying to plot
*/
public void addSurveyData(String id, String state, String code, String data) {
try {
Data d = new Data();
d.id = id;
d.code = code;
d.state = state;
if ("TBD".equals(data)) {
d.data = -1;
d.formattedValue = "TBD";
} else if ("(missing)".equals(data)) {
d.data = getSurveyData(11);
d.formattedValue = "(missing)";
} else {
d.data = getSurveyData(Double.parseDouble(data));
d.formattedValue = data;
}
this.data.add(d);
} catch (Exception e) {
// Ignore error
}
}
public void setData(List<Data> data) {
this.data = data;
}
public String getOnSelectMethodName() {
return onSelectMethodName;
}
/**
* The name of the function called when a region is selected. To disable
* this functionality call onSelectMethodName(null). This method name
* defaults to "geoChartSelectHandler"
* @param onSelectMethodName JavaScript function name that will be called when a region is clicked.
*
*/
public void setOnSelectMethodName(String onSelectMethodName) {
this.onSelectMethodName = onSelectMethodName;
}
public boolean isLegend() {
return legend;
}
/**
* If true a legend is rendered with the chart. Default is true.
*
* @param legend
* New value for the legend property.
*/
public void setLegend(boolean legend) {
this.legend = legend;
}
/**
* You will call this method on the page. It builds the JavaScript needed
* for the chart. It will create its own <script> tags.
*
* @return JavaScript that loads the Google GeoChart.
*/
public String getChart() {
final String LB = "\r\n";
StringBuilder sb = new StringBuilder(512);
sb.append("var geoChart, geoData;").append(LB);
sb.append(
"google.load('visualization', '1', {'packages': ['geochart']});")
.append(LB);
sb.append("google.setOnLoadCallback(drawMap);").append(LB).append(LB);
sb.append("function drawMap() {").append(LB);
sb.append("geoData = new google.visualization.DataTable();").append(LB);
sb.append("geoData.addRows(").append(data.size()).append(");")
.append(LB);
sb.append("geoData.addColumn('string', 'State');").append(LB);
sb.append(
"geoData.addColumn('number', '"
+ (hoverLabel == null ? "Percent" : hoverLabel) + "');")
.append(LB);
int count = 0;
for (Data data : this.data) {
sb.append("geoData.setValue(").append(count).append(", 0, '")
.append(data.state).append("');").append(LB);
sb.append("geoData.setValue(").append(count).append(", 1, ")
.append(data.data).append(");").append(LB);
sb.append("geoData.setFormattedValue(").append(count)
.append(", 1, '").append(data.formattedValue).append("');")
.append(LB);
sb.append("geoData.setRowProperty(").append(count)
.append(", 'stateCode', '").append(data.id).append("');")
.append(LB);
count++;
}
sb.append("var options = {};").append(LB);
sb.append("options['region'] = 'US';").append(LB);
sb.append("options['resolution'] = 'provinces';").append(LB);
sb.append("options['colorAxis'] = { minValue : 0, maxValue : ")
.append(colors.size() - 1).append(", colors : [");
for (String color : colors) {
sb.append("'").append(color).append("',");
}
if (colors.size() > 0) {
sb.setLength(sb.length() - 1);
}
sb.append("]};").append(LB);
sb.append("options['backgroundColor'] = '").append(backgroundColor)
.append("';").append(LB);
sb.append("options['datalessRegionColor'] = '")
.append(datalessRegionColor).append("';").append(LB);
sb.append("options['width'] = ").append(width).append(";").append(LB);
sb.append("options['height'] = ").append(height).append(";").append(LB);
if (!legend) {
sb.append("options['legend'] = 'none';").append(LB);
}
sb.append(
"geoChart = new google.visualization.GeoChart(document.getElementById('")
.append(chartDiv).append("'));").append(LB);
sb.append("geoChart.draw(geoData, options);").append(LB);
if (onSelectMethodName != null && !onSelectMethodName.equals("")) {
sb.append(
"google.visualization.events.addListener(geoChart, 'select', ")
.append(onSelectMethodName).append("); ").append(LB);
}
sb.append("}");
return sb.toString();
}
public String getUrl() {
StringBuilder sb = new StringBuilder(GOOGLE_URL);
// append size
sb.append("chs=").append(350).append("x").append(215);
sb.append("&cht=t");
// append colors
if (colors != null && !colors.isEmpty()) {
sb.append("&chco=")
.append(datalessRegionColor.startsWith("#") ? datalessRegionColor
.substring(1) : datalessRegionColor).append(",");
for (String color : colors) {
sb.append(color.startsWith("#") ? color.substring(1) : color)
.append(",");
}
sb.setLength(sb.length() - 1);
}
// append data
if (data != null && !data.isEmpty()) {
sb.append("&chld=");
for (Data data : this.data) {
if (ST_WHITE_LIST.contains(data.code)) {
sb.append(data.code);
}
}
sb.append("&chd=t:");
for (Data data : this.data) {
if (ST_WHITE_LIST.contains(data.code)) {
sb.append(data.data).append(",");
}
}
sb.setLength(sb.length() - 1);
if (colors.size() > 1) {
sb.append("&chds=0,").append(colors.size() - 1);
}
}
sb.append("&chtm=usa");
return sb.toString();
}
public String getDCUrl() {
String color = getDatalessRegionColor();
for (Data d : data) {
if (d.id.equalsIgnoreCase("DC")) {
color = colors.get(getData(d.data));
break;
}
}
return GOOGLE_URL + "cht=map&chld=US-DC&chs=75x75&chco=FFFFFF|"
+ (color.startsWith("#") ? color.substring(1) : color);
}
private static int getData(double data) {
if (data < 0) {
return 4;
}
if (data < 26) {
return 0;
}
if (data < 51) {
return 1;
}
if (data < 76) {
return 2;
}
return 3;
}
private static int getSurveyData(double data) {
if (data < 0) {
return 4;
}
if (data <= 3) {
return 3;
}
if (data <= 5) {
return 2;
}
if (data <= 7) {
return 1;
}
return 0;
}
private class Data {
public String id;
public String state;
public String code;
public int data;
public String formattedValue;
}
}