package charts.builder.spreadsheet; import static java.lang.String.format; import java.awt.Dimension; import java.io.IOException; import java.text.NumberFormat; import java.util.List; import java.util.Set; import org.apache.commons.lang3.StringUtils; import org.jfree.data.category.CategoryDataset; import org.supercsv.io.CsvListWriter; 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.TrackingTowardsTargets; import charts.jfree.ADCDataset; import charts.jfree.Attribute; import charts.jfree.AttributeMap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; public class TrackingTowardsTargetsBuilder extends JFreeBuilder { private static final SubstitutionKey S_TARGET = new SubstitutionKey("target", "the target in %", new SubstitutionKey.Val() { @Override public String value(Context ctx) { return percentFormatter().format(getTarget(ctx.datasource(), ctx.type())); } }); private static final SubstitutionKey S_BY = new SubstitutionKey("by", "year to reach the target", new SubstitutionKey.Val() { @Override public String value(Context ctx) { return getTargetBy(ctx.datasource(), ctx.type()); } }); private static final String TITLE_TYPO = "tracking towards tagets"; private static final String TITLE = "tracking towards targets"; private static final String POLLUTANT_REDUCTION = "% reduction in pollutant load"; private static final String TARGET_ADOPTION = " target (${target} adoption by ${by})"; private static final String TARGET_REDUCTION = " target (${target} reduction by ${by})"; private static class Title { private String title; private String valueAxisLabel; public Title(String title, String valueAxisLabel) { super(); this.title = title; this.valueAxisLabel = valueAxisLabel; } public String getTitle() { return title; } public String getValueAxisLabel() { return valueAxisLabel; } } private static NumberFormat percentFormatter() { NumberFormat percentFormat = NumberFormat.getPercentInstance(); percentFormat.setMaximumFractionDigits(0); return percentFormat; } private static final ImmutableMap<ChartType, Title> TITLES = new ImmutableMap.Builder<ChartType, Title>() .put(ChartType.TTT_CANE_AND_HORT, new Title( "Cane and horticulture"+TARGET_ADOPTION, "% of farmers adopting improved practices")) .put(ChartType.TTT_GRAZING, new Title( "Grazing"+TARGET_ADOPTION, "% of graziers adopting improved practices")) .put(ChartType.TTT_NITRO_AND_PEST, new Title( "Total nitrogen and pesticide"+TARGET_REDUCTION, POLLUTANT_REDUCTION)) .put(ChartType.TTT_SEDIMENT, new Title( "Sediment"+TARGET_REDUCTION, POLLUTANT_REDUCTION)) .build(); private static enum Series { CANE("Cane"), HORTICULTURE("Horticulture"), GRAZING("Grazing"), SEDIMENT( "Sediment"), TOTAL_NITROGEN("Total nitrogen"), PESTICIDES("Pesticides"); private String name; private Series(String name) { this.name = name; } @Override public String toString() { return name; } } public TrackingTowardsTargetsBuilder() { super(Lists.newArrayList(ChartType.TTT_CANE_AND_HORT, ChartType.TTT_GRAZING, ChartType.TTT_NITRO_AND_PEST, ChartType.TTT_SEDIMENT)); } private static Integer row(SpreadsheetDataSource datasource, Series series) { try { for(int i = 1;i<10;i++) { if(StringUtils.equalsIgnoreCase(datasource.select(i, 0).asString(), series.toString())) { return i; } } } catch(MissingDataException e) {} return null; } @Override public boolean canHandle(SpreadsheetDataSource datasource) { try { String title = datasource.select("A1").asString(); return TITLE.equalsIgnoreCase(title) || TITLE_TYPO.equalsIgnoreCase(title); } catch (MissingDataException e) { return false; } } @Override protected ADCDataset createDataset(Context ctx) { if (ctx.region() == Region.GBR) { SpreadsheetDataSource ds = ctx.datasource(); final ADCDataset dataset = new ADCDataset(); try { switch (ctx.type()) { case TTT_CANE_AND_HORT: addSeries(ds, dataset, Series.CANE); addSeries(ds, dataset, Series.HORTICULTURE); break; case TTT_GRAZING: addSeries(ds, dataset, Series.GRAZING); break; case TTT_NITRO_AND_PEST: addSeries(ds, dataset, Series.TOTAL_NITROGEN); addSeries(ds, dataset, Series.PESTICIDES); break; case TTT_SEDIMENT: addSeries(ds, dataset, Series.SEDIMENT); break; //$CASES-OMITTED$ default: throw new RuntimeException("chart type not supported " + ctx.type().toString()); } } catch (MissingDataException e) { throw new RuntimeException(e); } if(dataset.getRowCount() > 0) { return dataset; } else { return null; } } else { return null; } } protected static Series getTargetSeries(ChartType chartType) { switch (chartType) { case TTT_CANE_AND_HORT: return Series.CANE; case TTT_GRAZING: return Series.GRAZING; case TTT_NITRO_AND_PEST: return Series.TOTAL_NITROGEN; case TTT_SEDIMENT: return Series.SEDIMENT; //$CASES-OMITTED$ default: throw new RuntimeException("chart type not supported "+chartType); } } private void addSeries(SpreadsheetDataSource ds, ADCDataset dataset, Series series) throws MissingDataException { Integer row = row(ds, series); if (row != null) { List<String> columns = getColumns(ds); for (int col = 0; col < columns.size(); col++) { String s = ds.select(row, col + 3).asString(); try { Double value = new Double(s); dataset.addValue(value, series.toString(), columns.get(col)); } catch (Exception e) { dataset.addValue(null, series.toString(), columns.get(col)); } } } } private List<String> getColumns(SpreadsheetDataSource ds) throws MissingDataException { List<String> columns = Lists.newArrayList(); for (int col = 3; true; col++) { String s = ds.select(0, col).asString(); if (StringUtils.isBlank(s)) { break; } columns.add(s); } return columns; } private static double getTarget(SpreadsheetDataSource ds, ChartType type) { try { Series series = getTargetSeries(type); return ds.select(row(ds, series), 1).asDouble(); } catch(MissingDataException e) { throw new RuntimeException(e); } } private static String getTargetBy(SpreadsheetDataSource ds, ChartType type) { try { Series series = getTargetSeries(type); return ds.select(row(ds, series), 2).asInteger().toString(); } catch (MissingDataException e) { throw new RuntimeException(e); } } public String getDefaultTitle(ChartType type) { return TITLES.get(type).getTitle(); } public String getDefaultRangeAxisTitle(ChartType type) { return TITLES.get(type).getValueAxisLabel(); } @Override public AttributeMap defaults(ChartType type) { return new AttributeMap.Builder(). put(Attribute.TITLE, getDefaultTitle(type)). put(Attribute.X_AXIS_LABEL, ""). put(Attribute.Y_AXIS_LABEL, getDefaultRangeAxisTitle(type)). build(); } @Override protected Drawable getDrawable(JFreeContext ctx) { return new TrackingTowardsTargets().createChart( ctx.type(), getTarget(ctx.datasource(), ctx.type()), (ADCDataset)ctx.dataset(), new Dimension(750, 500)); } @Override protected String getCsv(final JFreeContext ctx) { final CategoryDataset dataset = (CategoryDataset)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", ctx.region(), ctx.type())) .add(format("%% Target by " + getTargetBy(ctx.datasource(), ctx.type()))) .addAll(columnKeys) .build(); csv.write(heading); final double target = getTarget(ctx.datasource(), ctx.type()); for (String row : rowKeys) { List<String> line = Lists.newLinkedList(); line.add(row); line.add(format("%.0f", target * 100)); for (String col : columnKeys) { Number n = dataset.getValue(row, col); line.add(n == null ? "" : format("%.0f", n.doubleValue()*100)); } csv.write(line); } }}); } @Override public Set<SubstitutionKey> substitutionKeys() { return ImmutableSet.<SubstitutionKey>builder(). addAll(super.substitutionKeys()).add(S_TARGET).add(S_BY).build(); } }