package charts.graphics;
import graphics.HatchedRectangle;
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.Rectangle2D;
import java.awt.geom.RectangularShape;
import java.text.NumberFormat;
import java.util.Map;
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.entity.EntityCollection;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.category.BarRenderer;
import org.jfree.chart.renderer.category.CategoryItemRendererState;
import org.jfree.chart.renderer.category.StandardBarPainter;
import org.jfree.chart.title.LegendTitle;
import org.jfree.chart.title.TextTitle;
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.ImmutableMap;
public class GrazingPracticeSystems implements ManagementPracticeSystems {
private static final Color COLOR_A = new Color(0,118,70);
private static final Color COLOR_B = new Color(168,198,162);
private static final Color COLOR_C = new Color(252,203,38);
private static final Color COLOR_D = new Color(233,44,48);
private static final Color COLOR_A_TRANS = new Color(0,118,70,90);
private static final Color COLOR_B_TRANS = new Color(168,198,162,90);
private static final Color COLOR_C_TRANS = new Color(252,203,38,90);
private static final Color COLOR_D_TRANS = new Color(233,44,48,90);
private static final Color AXIS_LABEL_COLOR = new Color(6, 76, 132);
private static final Font TITLE_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 final Font CAXIS_LABEL_FONT = new Font(Font.SANS_SERIF, Font.BOLD, 12);
private static class CustomBarRenderer extends BarRenderer {
private static final Map<Pair<Integer, Integer>, Color> BAR_COLORS =
new ImmutableMap.Builder<Pair<Integer, Integer>, Color>()
.put(Pair.of(0, 0), COLOR_A_TRANS)
.put(Pair.of(1, 0), COLOR_A)
.put(Pair.of(0, 1), COLOR_B_TRANS)
.put(Pair.of(1, 1), COLOR_B)
.put(Pair.of(0, 2), COLOR_C_TRANS)
.put(Pair.of(1, 2), COLOR_C)
.put(Pair.of(0, 3), COLOR_D_TRANS)
.put(Pair.of(1, 3), COLOR_D)
.build();
// copied and modified from jfreechart-1.0.14 org.jfree.chart.renderer.category.BarRenderer
private void drawItemInternal(Graphics2D g2,
CategoryItemRendererState state,
Rectangle2D dataArea,
CategoryPlot plot,
CategoryAxis domainAxis,
ValueAxis rangeAxis,
CategoryDataset dataset,
int row,
int column,
int pass) {
// nothing is drawn if the row index is not included in the list with
// the indices of the visible rows...
int visibleRow = state.getVisibleSeriesIndex(row);
if (visibleRow < 0) {
return;
}
// nothing is drawn for null values...
Number dataValue = dataset.getValue(row, column);
if (dataValue == null) {
return;
}
final double value = dataValue.doubleValue();
PlotOrientation orientation = plot.getOrientation();
double barW0 = calculateBarW0(plot, orientation, dataArea, domainAxis,
state, visibleRow, column);
double[] barL0L1 = calculateBarL0L1(value);
if (barL0L1 == null) {
return; // the bar is not visible
}
RectangleEdge edge = plot.getRangeAxisEdge();
double transL0 = rangeAxis.valueToJava2D(barL0L1[0], dataArea, edge);
double transL1 = rangeAxis.valueToJava2D(barL0L1[1], dataArea, edge);
// in the following code, barL0 is (in Java2D coordinates) the LEFT
// end of the bar for a horizontal bar chart, and the TOP end of the
// bar for a vertical bar chart. Whether this is the BASE of the bar
// or not depends also on (a) whether the data value is 'negative'
// relative to the base value and (b) whether or not the range axis is
// inverted. This only matters if/when we apply the minimumBarLength
// attribute, because we should extend the non-base end of the bar
boolean positive = (value >= this.getBase());
boolean inverted = rangeAxis.isInverted();
double barL0 = Math.min(transL0, transL1);
double barLength = Math.abs(transL1 - transL0);
double barLengthAdj = 0.0;
if (barLength > 0.0 && barLength < getMinimumBarLength()) {
barLengthAdj = getMinimumBarLength() - barLength;
}
double barL0Adj = 0.0;
RectangleEdge barBase;
if (orientation == PlotOrientation.HORIZONTAL) {
if (positive && inverted || !positive && !inverted) {
barL0Adj = barLengthAdj;
barBase = RectangleEdge.RIGHT;
}
else {
barBase = RectangleEdge.LEFT;
}
}
else {
if (positive && !inverted || !positive && inverted) {
barL0Adj = barLengthAdj;
barBase = RectangleEdge.BOTTOM;
}
else {
barBase = RectangleEdge.TOP;
}
}
// draw the bar...
RectangularShape bar = null;
if (orientation == PlotOrientation.HORIZONTAL) {
bar = getBarShape(row, barL0 - barL0Adj, barW0,
barLength + barLengthAdj, state.getBarWidth());
}
else {
bar = getBarShape(row, barW0, barL0 - barL0Adj,
state.getBarWidth(), barLength + barLengthAdj);
}
if (getShadowsVisible()) {
this.getBarPainter().paintBarShadow(g2, this, row, column, bar, barBase,
true);
}
this.getBarPainter().paintBar(g2, this, row, column, bar, barBase);
// FIXME
// CategoryItemLabelGenerator generator = getItemLabelGenerator(row,
// column);
// if (generator != null && isItemLabelVisible(row, column)) {
// drawItemLabel(g2, dataset, row, column, plot, generator, bar,
// (value < 0.0));
// }
// submit the current data point as a crosshair candidate
int datasetIndex = plot.indexOf(dataset);
updateCrosshairValues(state.getCrosshairState(),
dataset.getRowKey(row), dataset.getColumnKey(column), value,
datasetIndex, barW0, barL0, orientation);
// add an item entity, if this information is being collected
EntityCollection entities = state.getEntityCollection();
if (entities != null) {
addItemEntity(entities, dataset, row, column, bar);
}
}
@Override
public void drawItem(Graphics2D g2, CategoryItemRendererState state,
Rectangle2D dataArea, CategoryPlot plot,
CategoryAxis domainAxis, ValueAxis rangeAxis,
CategoryDataset dataset, int row, int column, int pass) {
drawItemInternal(g2, state, dataArea, plot, domainAxis, rangeAxis, dataset, row,
column, pass);
// System.out.println(String.format("row %s, column %s, pass %s", row, column, pass));
if((pass == 0) && (row == 1)&& (column == 3)) {
// 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());
}
}
@Override
public Paint getItemPaint(int row, int column) {
return BAR_COLORS.get(Pair.of(row, column));
}
private RectangularShape getBarShape(int row,
double x, double y, double width, double height) {
if(row == 0) {
return new HatchedRectangle(x, y, width, height, 5, 5);
} else {
return new Rectangle2D.Double(x, y, width, height);
}
}
}
private static LegendTitle createLegend(String legend1Text, String legend2Text) {
final LegendItemCollection legendItems = new LegendItemCollection();
FontRenderContext frc = new FontRenderContext(null, true, true);
Font legenfont = new Font(Font.SANS_SERIF, Font.BOLD, 12);
GlyphVector gv = legenfont.createGlyphVector(frc, new char[] {'X', 'X'});
Shape shape = gv.getVisualBounds();
Rectangle2D bounds = shape.getBounds2D();
HatchedRectangle hatchShape = new HatchedRectangle(bounds.getX(), bounds.getY(),
bounds.getWidth(), bounds.getHeight(), 5, 5);
{
LegendItem li = new LegendItem(legend1Text, null, null, null, hatchShape, Color.black);
li.setLabelFont(legenfont);
legendItems.add(li);
}
{
LegendItem li = new LegendItem(legend2Text, null, null, null, shape, Color.black);
li.setLabelFont(legenfont);
legendItems.add(li);
}
LegendTitle legend = new LegendTitle(new LegendItemSource() {
@Override
public LegendItemCollection getLegendItems() {
return legendItems;
}});
legend.setPosition(RectangleEdge.BOTTOM);
legend.setMargin(new RectangleInsets(0,30,0,0));
legend.setPadding(RectangleInsets.ZERO_INSETS);
legend.setLegendItemGraphicPadding(new RectangleInsets(0,20,0,0));
return legend;
}
private static NumberFormat percentFormatter() {
NumberFormat percentFormat = NumberFormat.getPercentInstance();
percentFormat.setMaximumFractionDigits(0);
return percentFormat;
}
public Drawable createChart(ADCDataset dataset, Dimension dimension) {
JFreeChart chart = ChartFactory.createBarChart(
"", // chart title
"", // domain axis label
"", // range axis label
dataset, // data
PlotOrientation.VERTICAL, // the plot orientation
false, // legend
false, // tooltips
false // urls
);
TextTitle textTitle = new TextTitle(dataset.get(Attribute.TITLE), TITLE_FONT);
textTitle.setPadding(new RectangleInsets(10,0,0,0));
chart.setTitle(textTitle);
chart.addLegend(createLegend(
dataset.getRowKey(0).toString(), dataset.getRowKey(1).toString()));
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.setAutoRange(false);
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(true);
cAxis.setUpperMargin(0.05);
cAxis.setLowerMargin(0.05);
cAxis.setTickLabelFont(CAXIS_LABEL_FONT);
cAxis.setTickLabelPaint(Color.black);
CustomBarRenderer renderer = new CustomBarRenderer();
plot.setRenderer(renderer);
renderer.setDrawBarOutline(false);
renderer.setBaseItemLabelsVisible(false);
renderer.setShadowVisible(false);
renderer.setBarPainter(new StandardBarPainter());
renderer.setMaximumBarWidth(0.08);
renderer.setItemMargin(0.01);
return new JFreeChartDrawable(chart, dimension);
}
}