package charts.builder.spreadsheet; import java.awt.Color; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang3.text.StrSubstitutor; import org.apache.commons.lang3.tuple.Pair; import org.apache.poi.hssf.util.CellReference; import charts.ChartType; import charts.builder.DataSource.MissingDataException; import charts.builder.Value; import charts.graphics.Colors; import charts.jfree.Attribute; import charts.jfree.AttributeMap; import charts.jfree.AttributedDataset; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; public class ChartConfigurator { private static final int SEARCH_DEPTH = 100; private static class Configuration { private final String types; private final AttributeMap attributes; public Configuration(String types, AttributeMap attributes) { this.types = types; this.attributes = attributes; } public boolean isFor(ChartType type) { if(StringUtils.isBlank(types) || (type == null) || StringUtils.equals("*", types)) { return true; } String[] types = StringUtils.split(this.types, ','); for(String s : types) { if(type==ChartType.lookup(StringUtils.strip(s))) { return true; } } return false; } public AttributeMap attributes() { return attributes; } } private final AttributeMap defaults; private final SpreadsheetDataSource ds; private final StrSubstitutor substitutor; public ChartConfigurator(AttributeMap defaults, SpreadsheetDataSource ds, StrSubstitutor subst) { this.defaults = defaults; this.ds = ds; this.substitutor = subst; } private Pair<Integer, Integer> search(SpreadsheetDataSource ds, ChartType type) { for(int i=0;i<SEARCH_DEPTH;i++) { for(Pair<Integer, Integer> p : ImmutableList.of(Pair.of(i,0), Pair.of(0, i), Pair.of(1, i))) { if(isChartConfiguration(ds, p.getLeft(), p.getRight(), type)) { return p; } } } return null; } private boolean isChartConfiguration(SpreadsheetDataSource ds, int row, int col, ChartType type) { try { return isChartConfiguration(ds, row, col) && readConfiguration(row, col).isFor(type); } catch(MissingDataException e) { return false; } } private boolean isChartConfiguration(SpreadsheetDataSource ds, int row, int col) { try { return isChartConfiguration(ds.select(row, col).asString()); } catch(MissingDataException e) { return false; } } private boolean isChartConfiguration(String key) { return (StringUtils.equalsIgnoreCase("type", key) || StringUtils.equalsIgnoreCase("chart configuration", key)); } public void configure(AttributedDataset dataset, ChartType type) { dataset.attrMap().putAll(substitute(defaults)); Configuration cfg = getConfiguration(type); if(cfg != null) { dataset.attrMap().putAll(substitute(cfg.attributes())); } } private AttributeMap substitute(AttributeMap m) { if((substitutor != null) && (m != null)) { for(Attribute<String> a : m.getKeys(String.class)) { m.put(a, substitutor.replace(m.get(a))); } } return m; } public Configuration getConfiguration(ChartType type) { try { Pair<Integer, Integer> p = search(ds, type); if(p != null) { return readConfiguration(p.getLeft(), p.getRight()); } } catch(MissingDataException e) {} return null; } @SuppressWarnings("unchecked") private Configuration readConfiguration(int row, int column) throws MissingDataException { String types = null; AttributeMap.Builder mb = new AttributeMap.Builder(); for(int r = row;true;r++) { if((r-row) >= 100) { throw new RuntimeException("expected less than 100 chart config entries"); } String key = ds.select(r, column).asString(); if(StringUtils.isBlank(key)) { break; } Value val = ds.select(r, column+1); if(isChartConfiguration(key)) { if(types != null) { break; } else { types = val.asString(); } } else { Attribute<?> attribute = Attribute.lookup(key); if(attribute == null) { continue; } if(attribute.getType() == String.class) { mb.put((Attribute<String>)attribute, val.asString()); } else if(attribute.getType().equals(Color.class)) { mb.put((Attribute<Color>)attribute, val.asColor()); } else if(attribute.getType().equals(Color[].class)) { mb.put((Attribute<Color[]>)attribute, getColors(val.asString())); } } } return new Configuration(types, mb.build()); } private Color[] getColors(String s) { if(StringUtils.startsWith(StringUtils.strip(s), "#")) { return fromHex(s); } else if(StringUtils.startsWithIgnoreCase(StringUtils.strip(s), "from")) { return fromCells(s); } else { return new Color[0]; } } private Color[] fromHex(String s) { List<Color> colors = Lists.newArrayList(); for(String color : StringUtils.split(s, ',')) { Color c = Colors.fromHex(StringUtils.strip(color)); if(c != null) { colors.add(c); } } return colors.toArray(new Color[colors.size()]); } private Color[] fromCells(String s) { List<Color> colors = Lists.newArrayList(); Pattern p = Pattern.compile( "from.*([a-z]+)([0-9]+).*([a-z]+)([0-9]+)", Pattern.CASE_INSENSITIVE); Matcher m = p.matcher(s); if(m.matches()) { CellReference cr1 = new CellReference(m.group(1)+m.group(2)); CellReference cr2 = new CellReference(m.group(3)+m.group(4)); for(Value v : ds.rangeSelect(cr1.getRow(), cr1.getCol(), cr2.getRow(), cr2.getCol())) { Color c = v.asColor(); if(c!=null) { colors.add(c); } } } return colors.toArray(new Color[colors.size()]); } }