package charts.builder.spreadsheet;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.concurrent.ExecutionException;
import org.apache.commons.lang3.StringUtils;
import play.Logger;
import charts.ChartType;
import charts.Region;
import charts.builder.DataSource.MissingDataException;
import charts.builder.Value;
import charts.graphics.ProgressTable;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
public abstract class AbstractProgressTableBuilder extends AbstractBuilder {
protected static final ImmutableMap<Region, Integer> ROWS =
new ImmutableMap.Builder<Region, Integer>()
.put(Region.GBR, 3)
.put(Region.CAPE_YORK, 4)
.put(Region.WET_TROPICS, 5)
.put(Region.BURDEKIN, 6)
.put(Region.MACKAY_WHITSUNDAY, 7)
.put(Region.FITZROY, 8)
.put(Region.BURNETT_MARY, 9)
.build();
// Type interpolation would be great here
protected final LoadingCache<
SpreadsheetDataSource,
Map<java.awt.Color, ProgressTable.Condition>
> conditionColorCache = CacheBuilder.newBuilder()
.weakKeys()
.maximumSize(100)
.build(
new CacheLoader<
SpreadsheetDataSource,
Map<java.awt.Color, ProgressTable.Condition>
>() {
@Override
public Map<java.awt.Color, ProgressTable.Condition> load(
SpreadsheetDataSource ds) {
final Map<java.awt.Color, ProgressTable.Condition> m =
Maps.newHashMap();
try {
for (int row = 0;; row++) {
final Value v = ds.select("condition", row, 0);
if (StringUtils.isBlank(v.asString())) {
break;
}
m.put(v.asColor(), getCondition(v.asString()));
}
} catch (MissingDataException e) {}
return m;
}
});
public AbstractProgressTableBuilder(List<ChartType> chartTypes) {
super(chartTypes);
}
@Override
public boolean canHandle(SpreadsheetDataSource datasource) {
try {
return "progress towards targets indicators".equalsIgnoreCase(datasource
.select("A1").getValue());
} catch (MissingDataException e) {
return false;
}
}
protected ProgressTable.Dataset getDataset(SpreadsheetDataSource datasource, Region region)
throws MissingDataException {
List<ProgressTable.Column> columns = getColumns(datasource, region);
List<ProgressTable.Row> rows = getRows(columns, datasource, region);
return new ProgressTable.Dataset(columns, rows);
}
protected List<ProgressTable.Column> getColumns(SpreadsheetDataSource ds, Region region)
throws MissingDataException {
List<ProgressTable.Column> columns = Lists.newArrayList();
int col = 2;
while (StringUtils.isNotBlank(ds.select(0, col).asString())) {
String header = getIndicator(ds.select(0, col).asString(), region).getLabel();
columns.add(new ProgressTable.Column(header, ds.select(1, col).asString(),
ds.select(2, col).asString()));
col++;
}
return columns;
}
private List<ProgressTable.Row> getRows(List<ProgressTable.Column> columns,
SpreadsheetDataSource datasource, Region region) throws MissingDataException {
List<ProgressTable.Row> rows = Lists.newArrayList();
if(region == null) {
for (Region r : Region.values()) {
rows.add(getRow(columns, datasource, r));
}
} else {
rows.add(getRow(columns, datasource, region));
}
return rows;
}
protected ProgressTable.Row getRow(List<ProgressTable.Column> columns,
SpreadsheetDataSource ds, Region region) throws MissingDataException {
Integer row = ROWS.get(region);
if (row == null) {
throw new RuntimeException("row not configured for region "
+ region.getProperName());
}
String r = ds.select(row, 0).asString();
if (!StringUtils.equals(region.getProperName(), r)) {
throw new RuntimeException(String.format(
"expected %s in row %s column 0 but found %s",
region.getProperName(), row, r));
}
List<ProgressTable.Cell> cells = Lists.newArrayList();
for (ProgressTable.Column column : columns) {
Double progress = ds.select(row, columns.indexOf(column) + 2)
.asPercent();
if (progress != null) {
ProgressTable.Indicator indicator = getIndicator(column.header, region);
ProgressTable.Condition condition = getCondition(ds, columns,
indicator, region);
cells.add(new ProgressTable.Cell(indicator, condition,
formatProgress(progress)));
} else {
cells.add(new ProgressTable.Cell());
}
}
return new ProgressTable.Row(region.getProperName(), ds.select(row, 1)
.asString(), cells);
}
private String formatProgress(double value) {
return Math.round(value * 100.0) + "%";
}
protected ProgressTable.Indicator getIndicator(String name, Region region) {
ProgressTable.Indicator i = ProgressTable.Indicator.valueOf(name
.toUpperCase());
if (i == null) {
throw new RuntimeException(String.format("no indicator found for %s",
name));
}
if (region == Region.FITZROY && i == ProgressTable.Indicator.SUGARCANE) {
return ProgressTable.Indicator.GRAIN;
}
return i;
}
private ProgressTable.Condition getCondition(SpreadsheetDataSource ds,
List<ProgressTable.Column> columns, ProgressTable.Indicator indicator,
Region region) throws MissingDataException {
if (indicator == ProgressTable.Indicator.GRAIN) {
indicator = ProgressTable.Indicator.SUGARCANE;
}
return getCondition(ds.select("progress", region.ordinal() + 3,
getConditionColumn(ds, indicator)).asColor(), getConditionColors(ds));
}
private ProgressTable.Condition getCondition(
java.awt.Color c,
Map<java.awt.Color, ProgressTable.Condition> conditions) {
if (c == null) {
return ProgressTable.Condition.UNDECIDED;
}
if (conditions.containsKey(c)) {
return conditions.get(c);
}
// Not an exact match, so pick closest colour
final SortedMap<Double, java.awt.Color> m = Maps.newTreeMap();
for (java.awt.Color otherColor : conditions.keySet()) {
m.put(distance(c, otherColor), otherColor);
}
final java.awt.Color closest = m.get(m.firstKey());
Logger.trace(String.format("Closest match for %s is %s => %s",
c, closest, conditions.get(closest)));
// Get the closest colour
return conditions.get(closest);
}
private ProgressTable.Condition getCondition(String str) {
return ProgressTable.Condition.valueOf(StringUtils.upperCase(StringUtils
.replace(str, " ", "")));
}
private Map<java.awt.Color, ProgressTable.Condition> getConditionColors(
SpreadsheetDataSource ds) {
try {
return conditionColorCache.get(ds);
} catch (ExecutionException e) {
// Shouldn't happen
throw new RuntimeException(e);
}
}
private int getConditionColumn(SpreadsheetDataSource ds,
ProgressTable.Indicator indicator) throws MissingDataException {
for (int col = 2;; col++) {
String s = ds.select("progress", 0, col).asString();
if (StringUtils.equalsIgnoreCase(s, indicator.toString())) {
return col;
} else if (StringUtils.isBlank(s)) {
throw new RuntimeException(String.format("indicator %s not found",
indicator.toString()));
}
}
}
// From: http://stackoverflow.com/a/2103608/701439
private double distance(java.awt.Color c1, java.awt.Color c2){
final double rmean = ( c1.getRed() + c2.getRed() )/2;
final int r = c1.getRed() - c2.getRed();
final int g = c1.getGreen() - c2.getGreen();
final int b = c1.getBlue() - c2.getBlue();
final double weightR = 2 + rmean/256;
final double weightG = 4.0;
final double weightB = 2 + (255-rmean)/256;
return Math.sqrt(weightR*r*r + weightG*g*g + weightB*b*b);
}
}