package charts.graphics;
import graphics.GraphUtils;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Shape;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.text.NumberFormat;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.LegendItem;
import org.jfree.chart.LegendItemCollection;
import org.jfree.chart.LegendItemSource;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.NumberTickUnit;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.category.CategoryItemRendererState;
import org.jfree.chart.renderer.category.GroupedStackedBarRenderer;
import org.jfree.chart.renderer.category.StandardBarPainter;
import org.jfree.chart.title.LegendTitle;
import org.jfree.data.KeyToGroupMap;
import org.jfree.data.category.CategoryDataset;
import org.jfree.ui.RectangleEdge;
import org.jfree.ui.RectangleInsets;
import charts.Drawable;
import charts.jfree.ADCDataset;
import charts.jfree.Attribute;
import com.google.common.collect.Maps;
public abstract class LandPracticeSystems implements ManagementPracticeSystems {
protected static final Color COLOR_A = new Color(0,118,70);
protected static final Color COLOR_B = new Color(168,198,162);
protected static final Color COLOR_C = new Color(252,203,38);
protected static final Color COLOR_D = new Color(233,44,48);
protected static final Color COLOR_A_TRANS = new Color(0,118,70,90);
protected static final Color COLOR_B_TRANS = new Color(168,198,162,90);
protected static final Color COLOR_C_TRANS = new Color(252,203,38,90);
protected static final Color COLOR_D_TRANS = new Color(233,44,48,90);
protected static final Color COLOR_CD = new Color(215, 136, 70);
protected static final Color COLOR_CD_TRANS = new Color(215, 136, 70, 90);
private static final Color AXIS_LABEL_COLOR = new Color(6, 76, 132);
protected static final Font LEGEND_FONT = new Font(Font.SANS_SERIF, Font.BOLD, 18);
private static final Font AXIS_LABEL_FONT = new Font(Font.SANS_SERIF, Font.BOLD, 18);
private static class BarLabelRenderer extends GroupedStackedBarRenderer {
private boolean isSeriesComplete(CategoryDataset dataset, int row) {
return (dataset.getRowCount()/2-1 == row) || (dataset.getRowCount()-1 == row);
}
private boolean isLastRow(CategoryDataset dataset, int row) {
return dataset.getRowCount()-1 == row;
}
private boolean renderBarLabel(CategoryDataset dataset, int row, int column) {
return dataset.getValue(row, column) != null;
}
@Override
public void drawItem(Graphics2D g2, CategoryItemRendererState state,
Rectangle2D dataArea, CategoryPlot plot,
CategoryAxis domainAxis, ValueAxis rangeAxis,
CategoryDataset dataset, int row, int column, int pass) {
super.drawItem(g2, state, dataArea, plot, domainAxis, rangeAxis, dataset, row,
column, pass);
// after the stacked bar is completely rendered draw the glow text into it.
if((pass == 2) && isSeriesComplete(dataset, row) && renderBarLabel(dataset, row, column)) {
GraphUtils g = new GraphUtils(g2);
String rowKey = dataset.getRowKey(row).toString();
String colKey = dataset.getColumnKey(column).toString();
String label = String.format("%s %s", colKey, getSeries(rowKey));
double barW0 = calculateBarW0(plot, plot.getOrientation(), dataArea, domainAxis,
state, row, column);
// centre the label
double labelx = barW0 + state.getBarWidth()/2;
double labely = (dataArea.getMinY()+dataArea.getHeight() / 2) -
(g.getBounds(label).getWidth() / 2);
AffineTransform saveT = g2.getTransform();
AffineTransform transform = new AffineTransform();
// jfree chart seem to be using the transform on the Graphics2D object
// for scaling when the window gets very small or large
// therefore we can not just overwrite the transform but have to factor it into
// our rotation and translation transformations.
transform.concatenate(saveT);
transform.concatenate(AffineTransform.getRotateInstance(-Math.PI/2, labelx, labely));
g2.setTransform(transform);
g2.setFont(LEGEND_FONT);
g2.setColor(Color.black);
g.drawGlowString(label, Color.white, 6, (int)labelx, (int)labely);
g2.setTransform(saveT);
}
if((pass == 2) && isLastRow(dataset, row)) {
// Workaround: because the dataArea sits on the the Axis the 0% gridline gets drawn
// over the category axis making it gray. To fix this as we draw another black line
// to restore the black axis.
g2.setColor(Color.black);
g2.setStroke(new BasicStroke(2));
g2.drawLine((int)dataArea.getMinX(), (int)dataArea.getMaxY(), (int)dataArea.getMaxX(), (int)dataArea.getMaxY());
g2.drawLine((int)dataArea.getMinX(), (int)dataArea.getMinY(), (int)dataArea.getMinX(), (int)dataArea.getMaxY());
}
}
}
private Paint[] seriesPaint;
public LandPracticeSystems(Paint... seriesPaint) {
this.seriesPaint = seriesPaint;
}
private LegendTitle createLegend() {
final LegendItemCollection legendItems = new LegendItemCollection();
FontRenderContext frc = new FontRenderContext(null, true, true);
GlyphVector gv = LEGEND_FONT.createGlyphVector(frc, new char[] {'X'});
Shape shape = gv.getGlyphVisualBounds(0);
for(Pair<String, Color> p : getLegend()) {
LegendItem li = new LegendItem(p.getLeft(), null, null, null, shape, p.getRight());
li.setLabelFont(LEGEND_FONT);
legendItems.add(li);
}
LegendTitle legend = new LegendTitle(new LegendItemSource() {
@Override
public LegendItemCollection getLegendItems() {
return legendItems;
}});
legend.setPosition(RectangleEdge.BOTTOM);
return legend;
}
protected abstract List<Pair<String, Color>> getLegend();
private NumberFormat percentFormatter() {
NumberFormat percentFormat = NumberFormat.getPercentInstance();
percentFormat.setMaximumFractionDigits(0);
return percentFormat;
}
@Override
public Drawable createChart(ADCDataset dataset, Dimension dimension) {
JFreeChart chart = ChartFactory.createStackedBarChart(
dataset.get(Attribute.TITLE), // chart title
"", // domain axis label
"", // range axis label
dataset, // data
PlotOrientation.VERTICAL, // the plot orientation
false, // legend
false, // tooltips
false // urls
);
chart.addLegend(createLegend());
CategoryPlot plot = (CategoryPlot) chart.getPlot();
plot.setBackgroundPaint(Color.white);
plot.setOutlineVisible(false);
plot.setAxisOffset(new RectangleInsets(0,0,0,0));
plot.setDomainGridlinesVisible(false);
plot.setRangeGridlinesVisible(true);
plot.setRangeGridlinePaint(Color.gray);
plot.setRangeGridlineStroke(new BasicStroke(2));
NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
rangeAxis.setAutoTickUnitSelection(true);
rangeAxis.setTickUnit(new NumberTickUnit(0.2, percentFormatter()));
rangeAxis.setAxisLineVisible(true);
rangeAxis.setLabel(dataset.get(Attribute.Y_AXIS_LABEL));
rangeAxis.setAxisLineStroke(new BasicStroke(2));
rangeAxis.setAxisLinePaint(Color.black);
rangeAxis.setTickMarksVisible(false);
rangeAxis.setLabelPaint(AXIS_LABEL_COLOR);
rangeAxis.setLabelFont(AXIS_LABEL_FONT);
rangeAxis.setLabelInsets(new RectangleInsets(0,0,0,0));
rangeAxis.setUpperMargin(0);
rangeAxis.setRange(0, 1);
CategoryAxis cAxis = plot.getDomainAxis();
cAxis.setTickMarksVisible(false);
cAxis.setAxisLinePaint(Color.black);
cAxis.setAxisLineStroke(new BasicStroke(2));
cAxis.setLabel(dataset.get(Attribute.X_AXIS_LABEL));
cAxis.setTickLabelsVisible(false);
cAxis.setCategoryMargin(0.05);
cAxis.setUpperMargin(0.1);
cAxis.setLowerMargin(0);
GroupedStackedBarRenderer renderer = new BarLabelRenderer();
plot.setRenderer(renderer);
for(int i=0;i<seriesPaint.length;i++) {
renderer.setSeriesPaint(i, seriesPaint[i]);
}
renderer.setRenderAsPercentages(true);
renderer.setDrawBarOutline(false);
renderer.setBaseItemLabelsVisible(false);
renderer.setShadowVisible(false);
renderer.setBarPainter(new StandardBarPainter());
renderer.setItemMargin(0.10);
renderer.setSeriesToGroupMap(createKeyToGroupMap(dataset));
return new JFreeChartDrawable(chart, dimension);
}
private KeyToGroupMap createKeyToGroupMap(CategoryDataset dataset) {
KeyToGroupMap map = new KeyToGroupMap();
Map<String, String> groups = Maps.newHashMap();
int groupCounter = 1;
for(int i=0;i<dataset.getRowCount();i++) {
String key = (String)dataset.getRowKey(i);
String series = getSeries(key);
String group = groups.get(series);
if(group == null) {
group = "G"+groupCounter++;
groups.put(series, group);
}
map.mapKeyToGroup(key, group);
}
return map;
}
private static String getSeries(String rowKey) {
return StringUtils.substringAfter(rowKey, "_");
}
}