package charts.graphics; import static charts.graphics.Colors.BLACK; import static charts.graphics.Colors.BLUE; import static charts.graphics.Colors.BROWN; import static charts.graphics.Colors.GREEN; import static charts.graphics.Colors.LIGHT_BLUE; import static charts.graphics.Colors.LIGHT_BROWN; import static charts.graphics.Colors.LIGHT_PURPLE; import static charts.graphics.Colors.ORANGE; import static charts.graphics.Colors.PINK; import static charts.graphics.Colors.RED; import static charts.graphics.Colors.YELLOW; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Paint; import java.awt.geom.Rectangle2D; import org.jfree.chart.JFreeChart; import org.jfree.chart.LegendItem; import org.jfree.chart.LegendItemCollection; import org.jfree.chart.LegendItemSource; import org.jfree.chart.StandardChartTheme; import org.jfree.chart.axis.CategoryAxis; import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.block.GridArrangement; import org.jfree.chart.entity.EntityCollection; import org.jfree.chart.labels.CategoryItemLabelGenerator; 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.StackedBarRenderer; import org.jfree.chart.renderer.category.StandardBarPainter; import org.jfree.chart.title.LegendTitle; import org.jfree.data.DataUtilities; import org.jfree.data.Range; 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 svg.AorraSvgGraphics2D; public class PSIITrends { public static final String SEPARATOR = AutoSubCategoryAxis.DEFAULT_SEPARATOR; public static final Color[] SERIES_PAINT = new Color[] { PINK, LIGHT_BROWN, BLUE, LIGHT_PURPLE, YELLOW, BROWN, ORANGE, LIGHT_BLUE, GREEN, RED, BLACK }; private static class Renderer extends StackedBarRenderer { public Renderer() { setShadowVisible(false); setDrawBarOutline(false); setShadowVisible(false); } // copied from superclass and added another pass that adds markers where the bar crosses // partition boundaries @Override public void drawItem(Graphics2D g2, CategoryItemRendererState state, Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis, ValueAxis rangeAxis, CategoryDataset dataset, int row, int column, int pass) { if (!isSeriesVisible(row)) { return; } // nothing is drawn for null values... Number dataValue = dataset.getValue(row, column); if (dataValue == null) { return; } double value = dataValue.doubleValue(); double total = 0.0; // only needed if calculating percentages if (this.getRenderAsPercentages()) { total = DataUtilities.calculateColumnTotal(dataset, column, state.getVisibleSeriesArray()); value = value / total; } PlotOrientation orientation = plot.getOrientation(); double barW0 = domainAxis.getCategoryMiddle(column, getColumnCount(), dataArea, plot.getDomainAxisEdge()) - state.getBarWidth() / 2.0; double positiveBase = getBase(); double negativeBase = positiveBase; for (int i = 0; i < row; i++) { Number v = dataset.getValue(i, column); if (v != null && isSeriesVisible(i)) { double d = v.doubleValue(); if (this.getRenderAsPercentages()) { d = d / total; } if (d > 0) { positiveBase = positiveBase + d; } else { negativeBase = negativeBase + d; } } } double translatedBase; double translatedValue; boolean positive = (value > 0.0); boolean inverted = rangeAxis.isInverted(); RectangleEdge barBase; if (orientation == PlotOrientation.HORIZONTAL) { if (positive && inverted || !positive && !inverted) { barBase = RectangleEdge.RIGHT; } else { barBase = RectangleEdge.LEFT; } } else { if (positive && !inverted || !positive && inverted) { barBase = RectangleEdge.BOTTOM; } else { barBase = RectangleEdge.TOP; } } RectangleEdge location = plot.getRangeAxisEdge(); if (positive) { translatedBase = rangeAxis.valueToJava2D(positiveBase, dataArea, location); translatedValue = rangeAxis.valueToJava2D(positiveBase + value, dataArea, location); } else { translatedBase = rangeAxis.valueToJava2D(negativeBase, dataArea, location); translatedValue = rangeAxis.valueToJava2D(negativeBase + value, dataArea, location); } double barL0 = Math.min(translatedBase, translatedValue); double barLength = Math.max(Math.abs(translatedValue - translatedBase), getMinimumBarLength()); Rectangle2D bar = null; if (orientation == PlotOrientation.HORIZONTAL) { bar = new Rectangle2D.Double(barL0, barW0, barLength, state.getBarWidth()); } else { bar = new Rectangle2D.Double(barW0, barL0, state.getBarWidth(), barLength); } if (pass == 0) { if (getShadowsVisible()) { boolean pegToBase = (positive && (positiveBase == getBase())) || (!positive && (negativeBase == getBase())); getBarPainter().paintBarShadow(g2, this, row, column, bar, barBase, pegToBase); } } else if (pass == 1) { if(g2 instanceof AorraSvgGraphics2D) { g2.setPaint(getItemPaint(row, column)); ((AorraSvgGraphics2D) g2).fillWithTooltip(bar, String.format("%s %s ng/L", dataset.getRowKey(row).toString(), value)); } else { getBarPainter().paintBar(g2, this, row, column, bar, barBase); } // add an item entity, if this information is being collected EntityCollection entities = state.getEntityCollection(); if (entities != null) { addItemEntity(entities, dataset, row, column, bar); } } else if (pass == 2) { CategoryItemLabelGenerator generator = getItemLabelGenerator(row, column); if (generator != null && isItemLabelVisible(row, column)) { drawItemLabel(g2, dataset, row, column, plot, generator, bar, (value < 0.0)); } } else if(pass == 3) { if(rangeAxis instanceof PartitionedNumberAxis) { Range r = new Range(positiveBase, positiveBase+value); ((PartitionedNumberAxis)rangeAxis).drawPatitionBoundaries( g2, r, dataArea, state.getBarWidth(), barW0); } } } @Override public int getPassCount() { return 4; } @Override protected void calculateBarWidth(CategoryPlot plot, Rectangle2D dataArea, int rendererIndex, CategoryItemRendererState state) { CategoryAxis xAxis = plot.getDomainAxisForDataset(rendererIndex); if(xAxis instanceof AutoSubCategoryAxis) { state.setBarWidth(((AutoSubCategoryAxis)xAxis).calculateCategorySize(dataArea)); } else { super.calculateBarWidth(plot, dataArea, rendererIndex, state); } } } public static Drawable createChart(ADCDataset dataset, Dimension dimension) { JFreeChart chart = createStackBarChart(dataset, dataset.get(Attribute.TITLE)); CategoryPlot plot = (CategoryPlot)chart.getPlot(); { plot.setBackgroundPaint(Color.white); plot.setRangeGridlinesVisible(true); plot.setRangeGridlinePaint(Color.lightGray); plot.setRangeGridlineStroke(new BasicStroke(1.0f)); } { // TODO the partition range should be determined automatically! if(plot.getRangeAxis() instanceof PartitionedNumberAxis) { PartitionedNumberAxis vAxis = (PartitionedNumberAxis)plot.getRangeAxis(); vAxis.addPartition(new PartitionedNumberAxis.Partition(new Range(0,110.0),1.0/3.0*2.0)); vAxis.addPartition(new PartitionedNumberAxis.Partition(new Range(120.0,750.0),1.0/3.0)); } } { StackedBarRenderer renderer = (StackedBarRenderer)plot.getRenderer(); Paint[] sp = seriesPaint(dataset); for(int i=0;i<sp.length;i++) { if(sp[i] != null) { renderer.setSeriesPaint(i, sp[i]); } } renderer.setBarPainter(new StandardBarPainter()); } { new PSIICommon().configureSubCategoryAxis((AutoSubCategoryAxis)plot.getDomainAxis(), plot.getRangeAxis().getTickLabelFont()); } { final LegendItemCollection items = new LegendItemCollection(); int i = 0; for(Object p : dataset.getRowKeys()) { items.add(new LegendItem(p.toString(), null, null, null, new Rectangle2D.Double(-6.0, -6.0, 10.0,10.0), plot.getRenderer().getSeriesPaint(i++))); } LegendTitle legend = new LegendTitle(new LegendItemSource() { @Override public LegendItemCollection getLegendItems() { return items; }}, new GridArrangement(dataset.getRowCount()/6+1,6), null); legend.setMargin(new RectangleInsets(1.0, 50.0, 1.0, 1.0)); legend.setBackgroundPaint(Color.white); legend.setPosition(RectangleEdge.BOTTOM); chart.addLegend(legend); } return new JFreeChartDrawable(chart, dimension); } private static Paint[] seriesPaint(ADCDataset dataset) { Color[] colors = dataset.get(Attribute.SERIES_COLORS); if(colors == null) { return SERIES_PAINT; } Paint[] p = new Paint[SERIES_PAINT.length]; for(int i=0;i<p.length;i++) { if(i<colors.length && (colors[i] != null)) { p[i] = colors[i]; } else { p[i] = SERIES_PAINT[i]; } } return p; } private static JFreeChart createStackBarChart(ADCDataset dataset,String title) { AutoSubCategoryAxis dAxis = new AutoSubCategoryAxis(dataset); dAxis.setCategoryLabelPositionOffset(0); dAxis.setLabel(dataset.get(Attribute.X_AXIS_LABEL)); PartitionedNumberAxis vAxis = new PartitionedNumberAxis( dataset.get(Attribute.Y_AXIS_LABEL)); CategoryPlot plot = new CategoryPlot(dAxis.getFixedDataset(), dAxis, vAxis, new Renderer()); plot.setOrientation(PlotOrientation.VERTICAL); JFreeChart chart = new JFreeChart(title, JFreeChart.DEFAULT_TITLE_FONT, plot, false); new StandardChartTheme("JFree").apply(chart); return chart; } }