package charts.builder.spreadsheet;
import static com.google.common.collect.Lists.newLinkedList;
import static java.lang.String.format;
import static org.apache.commons.lang3.StringUtils.equalsIgnoreCase;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.strip;
import java.awt.Color;
import java.awt.Dimension;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.apache.commons.lang3.StringUtils;
import org.jfree.data.general.Dataset;
import org.jfree.data.statistics.StatisticalCategoryDataset;
import org.supercsv.io.CsvListWriter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import charts.ChartType;
import charts.Drawable;
import charts.Region;
import charts.builder.DataSource.MissingDataException;
import charts.builder.csv.Csv;
import charts.builder.csv.CsvWriter;
import charts.graphics.TrendsSeagrassAbundance;
import charts.jfree.ADSCDataset;
import charts.jfree.Attribute;
import charts.jfree.AttributeMap;
public class TrendsSeagrassAbundanceBuilder extends JFreeBuilder {
private static class Entry {
final String name;
final Date date;
final Double mean;
final Double deviation;
public Entry(String name, Date date, Double mean, Double deviation) {
this.name = name;
this.date = date;
this.mean = mean;
this.deviation = deviation;
}
}
private static class DList {
final List<Entry> entries;
public DList(List<Entry> entries) {
this.entries = entries;
}
}
private final SubstitutionKey S_SUBREGION = new SubstitutionKey("subregion",
"Subregion e.g. Archer Point", new SubstitutionKey.Val() {
@Override
public String value(Context ctx) {
return getSubregion(ctx.datasource(), ctx.parameters()).getLabel();
}
});
private final SubstitutionKey S_HABITAT = new SubstitutionKey("habitat",
"The habitat type of the location (fringing reef, coastal, reef and estuarine)",
new SubstitutionKey.Val() {
@Override
public String value(Context ctx) {
return getSubregion(ctx.datasource(), ctx.parameters()).getHabitat().getLabel();
}
});
private final SubstitutionKey S_SUBTIDAL = new SubstitutionKey("subtidal",
"Used to show that this chart is also showing subtidal data in the title",
new SubstitutionKey.Val() {
@Override
public String value(Context ctx) {
Dataset d = ((JFreeContext)ctx).dataset();
ADSCDataset ad = (ADSCDataset)d;
return ad.getRowCount() == 1?"":"and subtidal ";
}
});
public static final String SUBREGION = "subregion";
public static enum Habitat {
REEF, COASTAL, ESTUARINE;
public String getLabel() {
return name().toLowerCase();
}
}
public static enum Subregion {
AP(Region.CAPE_YORK, "Archer Point", Habitat.REEF),
YP(Region.WET_TROPICS, "Yule Point", Habitat. COASTAL),
LB(Region.WET_TROPICS, "Lugger Bay", Habitat.COASTAL),
GI(Region.WET_TROPICS, "Green Island", Habitat.REEF),
DI(Region.WET_TROPICS, "Dunk Island", Habitat.REEF),
TSV(Region.BURDEKIN, "Bushland and Shelly Beaches", Habitat.COASTAL),
MI(Region.BURDEKIN, "Magnetic Island", Habitat.REEF),
SI(Region.MACKAY_WHITSUNDAY, "Sarina Inlet", Habitat.COASTAL),
PI(Region.MACKAY_WHITSUNDAY, "Pioneer Bay", Habitat.COASTAL),
HM(Region.MACKAY_WHITSUNDAY, "Hamilton Island", Habitat.REEF),
GH(Region.FITZROY, "Gladstone Harbour", Habitat.ESTUARINE),
GK(Region.FITZROY, "Great Keppel Island", Habitat.REEF),
SWB(Region.FITZROY, "Shoalwater Bay", Habitat.COASTAL),
UG(Region.BURNETT_MARY, "Urangan", Habitat.ESTUARINE),
RD(Region.BURNETT_MARY, "Rodds Bay", Habitat.ESTUARINE),
SR(Region.CAPE_YORK, "Shelbourne Bay", Habitat.COASTAL),
BY(Region.CAPE_YORK, "Bathurst Head", Habitat.COASTAL),
FR(Region.CAPE_YORK, "Farmer Island", Habitat.REEF),
ST(Region.CAPE_YORK, "Stanley Island", Habitat.COASTAL),
LI(Region.WET_TROPICS, "Low Island", Habitat.REEF),
JR(Region.BURDEKIN, "Jerona", Habitat.COASTAL),
YY(Region.CAPE_YORK, "Yum Yum Beach", Habitat.REEF),
MP(Region.MACKAY_WHITSUNDAY, "Midge Point", Habitat.COASTAL),
HB(Region.MACKAY_WHITSUNDAY, "Hydeaway Bay", Habitat.REEF),
BH(Region.BURNETT_MARY, "Burrum Heads", Habitat.COASTAL),
;
private final Region region;
private final String label;
private final Habitat habitat;
private Subregion(Region region, String label, Habitat habitat) {
this.region = region;
this.label = label;
this.habitat = habitat;
}
public Region getRegion() {
return region;
}
public String getLabel() {
return label;
}
public Habitat getHabitat() {
return habitat;
}
public static Subregion fromName(String name) {
try {
return valueOf(name);
} catch(IllegalArgumentException e) {
return null;
}
}
}
public TrendsSeagrassAbundanceBuilder() {
super(ChartType.TSA);
}
@Override
public boolean canHandle(SpreadsheetDataSource ds) {
try {
if(stripEqualsIgnoreCase(ds, "site", "A1") &&
stripEqualsIgnoreCase(ds, "date", "B1") &&
stripEqualsIgnoreCase(ds, "mean", "C1") &&
stripEqualsIgnoreCase(ds, "se", "D1")) {
return true;
}
} catch(MissingDataException e) {}
return false;
}
private boolean stripEqualsIgnoreCase(SpreadsheetDataSource ds, String s,
String cellref) throws MissingDataException {
return equalsIgnoreCase(s, strip(ds.select(cellref).asString()));
}
@Override
protected Map<String, List<String>> getParameters(SpreadsheetDataSource datasource, ChartType type) {
return Collections.singletonMap(SUBREGION, getSubregions(datasource));
}
private List<String> getSubregions(SpreadsheetDataSource ds) {
return StreamSupport.stream(ds.rangeRowSelect(0, 1,
ds.getRows()).spliterator(), false).map(v -> v!=null?v.asString():null).filter(
StringUtils::isNotBlank).map(StringUtils::upperCase).distinct().collect(
Collectors.toList());
}
private Subregion getSubregion(final SpreadsheetDataSource ds, Map<String, ?> parameters) {
String subregion = (String)parameters.get(SUBREGION);
return (isNotBlank(subregion) && getSubregions(ds).contains(subregion.toUpperCase()))?
Subregion.fromName(subregion.toUpperCase()):null;
}
private int getSubregionRowStart(SpreadsheetDataSource ds, String name) {
for (int row = 1; row <= ds.getRows(); row++) {
try {
String s = strip(ds.select(row, 0).asString());
if (equalsIgnoreCase(s, name)) {
return row;
}
} catch (MissingDataException e) {
}
}
return -1;
}
@Override
protected ADSCDataset createDataset(Context ctx) {
SpreadsheetDataSource ds = ctx.datasource();
final Subregion subregion = getSubregion(ds, ctx.parameters());
if (subregion == null) {
return null;
}
if (subregion.getRegion() == ctx.region()) {
try {
List<Entry> intertidal = read(ctx, subregion.name());
List<Entry> subtidal = read(ctx, subregion.name()+"_sub");
final ADSCDataset d = toDataset(merge(intertidal, subtidal));
return d;
} catch (MissingDataException e) {
throw new RuntimeException("while building trends in seagrass abundance dataset "
+ ctx.toString(), e);
}
} else {
return null;
}
}
private List<DList> merge(List<Entry> l1, List<Entry> l2) {
List<Entry> combined = Lists.newArrayList(l1);
combined.addAll(l2);
List<DList> dates = Lists.newArrayList();
for(Date date : dates(combined)) {
dates.add(new DList(entries(date, combined)));
}
return dates;
}
private List<Date> dates(List<Entry> l) {
Set<Date> dates = Sets.newHashSet();
for(Entry entry : l) {
dates.add(entry.date);
}
List<Date> result = Lists.newArrayList(dates);
Collections.sort(result);
return result;
}
private List<Entry> entries(Date date, List<Entry> l) {
List<Entry> entries = Lists.newArrayList();
for(Entry entry : l) {
if(entry.date.equals(date)) {
entries.add(entry);
}
}
return entries;
}
private ADSCDataset toDataset(List<DList> dates) {
final ADSCDataset d = new ADSCDataset();
final SimpleDateFormat sdf = new SimpleDateFormat("MMMMM yyyy");
for(DList dlist : dates) {
for(Entry entry : dlist.entries) {
d.add(entry.mean, entry.deviation, entry.name, sdf.format(entry.date));
}
}
return d;
}
private List<Entry> read(Context ctx, String name) throws MissingDataException {
final List<Entry> entries = Lists.newArrayList();
final SpreadsheetDataSource ds = ctx.datasource();
int row = getSubregionRowStart(ds, name);
if (row == -1) {
return entries;
}
for (; true; row++) {
String subr = strip(ds.select(row, 0).asString());
if (!equalsIgnoreCase(subr, name)) {
break;
}
Date date = ds.select(row, 1).asDate();
Double mean = ds.select(row, 2).asDouble();
Double deviation = ds.select(row, 3).asDouble();
if (mean != null && deviation != null) {
entries.add(new Entry(name, date, mean, deviation));
}
}
return entries;
}
@Override
public AttributeMap defaults(ChartType type) {
return new AttributeMap.Builder().
put(Attribute.TITLE, "Seagrass abundance at inshore intertidal ${subtidal}${habitat} habitat"
+ "\nat ${subregion} in the ${region} region").
put(Attribute.X_AXIS_LABEL, "").
put(Attribute.Y_AXIS_LABEL, "Seagrass abundance").
put(Attribute.SERIES_COLOR, new Color(30, 172, 226)).
build();
}
@Override
protected Drawable getDrawable(JFreeContext ctx) {
return TrendsSeagrassAbundance.createChart(
(ADSCDataset)ctx.dataset(), new Dimension(750, 500));
}
@Override
protected String getCsv(final JFreeContext ctx) {
final StatisticalCategoryDataset dataset = (StatisticalCategoryDataset)ctx.dataset();
return Csv.write(new CsvWriter() {
@Override
public void write(CsvListWriter csv) throws IOException {
@SuppressWarnings("unchecked")
List<String> columnKeys = dataset.getColumnKeys();
@SuppressWarnings("unchecked")
List<String> rowKeys = dataset.getRowKeys();
final List<String> heading = ImmutableList.<String> builder()
.add(format("%s %s %s", ctx.type(), ctx.region(),
rowKeys.get(0)))
.add("Mean")
.add("Std Dev")
.build();
csv.write(heading);
for (String col : columnKeys) {
List<String> line = newLinkedList();
line.add(col);
if(dataset.getMeanValue(rowKeys.get(0), col) != null) {
line.add(format("%.3f",
dataset.getMeanValue(rowKeys.get(0), col).doubleValue()));
line.add(format("%.3f",
dataset.getStdDevValue(rowKeys.get(0), col).doubleValue()));
}
csv.write(line);
}
}});
}
@Override
protected String title(JFreeContext ctx) {
return "TSA at "+getSubregion(ctx.datasource(), ctx.parameters()).getLabel();
}
@Override
public Set<SubstitutionKey> substitutionKeys() {
return ImmutableSet.<SubstitutionKey>builder().
addAll(super.substitutionKeys()).add(S_SUBREGION).add(S_HABITAT).add(S_SUBTIDAL).build();
}
}