teichmann@5863: /* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde teichmann@5863: * Software engineering by Intevation GmbH teichmann@5863: * teichmann@5994: * This file is Free Software under the GNU AGPL (>=v3) teichmann@5863: * and comes with ABSOLUTELY NO WARRANTY! Check out the teichmann@5994: * documentation coming with Dive4Elements River for details. teichmann@5863: */ teichmann@5863: teichmann@5831: package org.dive4elements.river.jfree; sascha@3162: sascha@3162: import java.awt.Font; sascha@3162: import java.awt.Graphics2D; sascha@3162: import java.awt.Paint; sascha@3162: import java.awt.Shape; sascha@3162: sascha@3162: import java.awt.geom.Point2D; sascha@3162: import java.awt.geom.Rectangle2D; sascha@3162: sascha@3162: import java.util.ArrayList; sascha@3162: import java.util.List; sascha@3162: import java.util.Map; sascha@3162: sascha@3162: import org.jfree.chart.axis.ValueAxis; sascha@3162: sascha@3162: import org.jfree.chart.labels.ItemLabelPosition; sascha@3162: import org.jfree.chart.labels.XYItemLabelGenerator; sascha@3162: sascha@3162: import org.jfree.chart.plot.CrosshairState; sascha@3162: import org.jfree.chart.plot.PlotOrientation; sascha@3162: import org.jfree.chart.plot.PlotRenderingInfo; sascha@3162: import org.jfree.chart.plot.XYPlot; sascha@3162: sascha@3162: import org.jfree.chart.renderer.xy.StandardXYItemRenderer; sascha@3162: import org.jfree.chart.renderer.xy.XYItemRendererState; sascha@3162: sascha@3162: import org.jfree.data.xy.XYDataset; sascha@3162: sascha@3162: import org.jfree.text.TextUtilities; sascha@3162: sascha@3162: import org.jfree.ui.RectangleEdge; sascha@3162: sascha@3162: public class ShapeRenderer sascha@3162: extends StandardXYItemRenderer { sascha@3162: sascha@3162: public static class Entry { sascha@3162: protected Shape shape; sascha@3162: protected Shape frame; sascha@3162: protected Paint paint; sascha@3162: protected boolean filled; sascha@3162: sascha@3162: public Entry( sascha@3162: Shape shape, sascha@3162: Paint paint, sascha@3162: boolean filled sascha@3162: ) { sascha@3162: this.shape = shape; sascha@3162: this.paint = paint; sascha@3162: this.filled = filled; sascha@3162: } sascha@3162: sascha@3162: public Entry( sascha@3162: Shape shape, sascha@3162: Shape frame, sascha@3162: Paint paint, sascha@3162: boolean filled sascha@3162: ) { sascha@3162: this.shape = shape; sascha@3162: this.frame = frame; sascha@3162: this.paint = paint; sascha@3162: this.filled = filled; sascha@3162: } sascha@3162: sascha@3162: public Shape getShape() { sascha@3162: return shape; sascha@3162: } sascha@3162: sascha@3162: public void setShape(Shape shape) { sascha@3162: this.shape = shape; sascha@3162: } sascha@3162: sascha@3162: sascha@3162: public Paint getPaint() { sascha@3162: return paint; sascha@3162: } sascha@3162: sascha@3162: public void setPaint(Paint paint) { sascha@3162: this.paint = paint; sascha@3162: } sascha@3162: sascha@3162: public boolean getFilled() { sascha@3162: return filled; sascha@3162: } sascha@3162: sascha@3162: public void setFilled(boolean filled) { sascha@3162: this.filled = filled; sascha@3162: } sascha@3162: sascha@3162: public boolean equals(Object other) { sascha@3162: Entry entry = (Entry)other; sascha@3162: return filled == entry.filled sascha@3162: && paint.equals(entry.paint) sascha@3162: && shape.equals(entry.shape); sascha@3162: } sascha@3162: sascha@3162: public int hashCode() { sascha@3162: return sascha@3162: shape.hashCode() ^ sascha@3162: paint.hashCode() ^ sascha@3162: (filled ? 1231 : 1237); sascha@3162: } sascha@3162: } // class Entry sascha@3162: sascha@3173: public interface LabelGenerator { sascha@3173: String createLabel(Entry entry); sascha@3173: } // interface EntryLabelGenerator sascha@3173: sascha@3162: protected Entry [] entries; sascha@3162: sascha@3162: protected List labelBoundingBoxes; sascha@3162: sascha@3162: protected Rectangle2D area; sascha@3162: sascha@3162: public ShapeRenderer() { sascha@3162: this(SHAPES); sascha@3162: } sascha@3162: sascha@3162: public ShapeRenderer(int type) { sascha@3162: super(type); sascha@3162: } sascha@3162: sascha@3162: public ShapeRenderer(Map map) { sascha@3162: super(SHAPES); sascha@3162: setEntries(map); sascha@3162: } sascha@3162: sascha@3162: public void setEntries(Entry [] entries) { sascha@3162: this.entries = entries; sascha@3162: } sascha@3162: sascha@3162: public void setEntries(Map map) { sascha@3162: Entry [] entries = new Entry[map.size()]; sascha@3162: sascha@3162: for (Map.Entry entry: map.entrySet()) { sascha@3162: entries[entry.getValue()] = entry.getKey(); sascha@3162: } sascha@3162: sascha@3162: setEntries(entries); sascha@3162: } sascha@3162: sascha@3162: @Override sascha@3162: public Shape getSeriesShape(int series) { sascha@3162: return entries[series].shape; sascha@3162: } sascha@3162: sascha@3162: public Shape getSeriesFrame(int series) { sascha@3162: return entries[series].frame; sascha@3162: } sascha@3162: sascha@3162: @Override sascha@3162: public Paint getSeriesPaint(int series) { sascha@3162: return entries[series].paint; sascha@3162: } sascha@3162: sascha@3162: @Override sascha@3162: public boolean getItemShapeFilled(int series, int item) { sascha@3162: return entries[series].filled; sascha@3162: } sascha@3162: sascha@3162: @Override sascha@3162: public XYItemRendererState initialise( sascha@3162: Graphics2D g2, sascha@3162: Rectangle2D dataArea, sascha@3162: XYPlot plot, sascha@3162: XYDataset data, sascha@3162: PlotRenderingInfo info sascha@3162: ) { sascha@3162: if (labelBoundingBoxes == null) { sascha@3162: labelBoundingBoxes = new ArrayList(32); sascha@3162: } sascha@3162: else { sascha@3162: labelBoundingBoxes.clear(); sascha@3162: } sascha@3162: sascha@3162: area = dataArea; sascha@3162: sascha@3162: return super.initialise(g2, dataArea, plot, data, info); sascha@3162: } sascha@3162: sascha@3162: @Override sascha@3162: public void drawItem( sascha@3162: Graphics2D g2, sascha@3162: XYItemRendererState state, sascha@3162: Rectangle2D dataArea, sascha@3162: PlotRenderingInfo info, sascha@3162: XYPlot plot, sascha@3162: ValueAxis domainAxis, sascha@3162: ValueAxis rangeAxis, sascha@3162: XYDataset dataset, sascha@3162: int series, sascha@3162: int item, sascha@3162: CrosshairState crosshairState, sascha@3162: int pass sascha@3162: ) { sascha@3162: if (!getItemVisible(series, item)) { sascha@3162: return; sascha@3162: } sascha@3162: sascha@3162: // get the data point... sascha@3162: double x1 = dataset.getXValue(series, item); sascha@3162: double y1 = dataset.getYValue(series, item); sascha@3162: if (Double.isNaN(x1) || Double.isNaN(y1)) { sascha@3162: return; sascha@3162: } sascha@3162: sascha@3162: RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); sascha@3162: RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); sascha@3162: double x = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation); sascha@3162: double y = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation); sascha@3162: sascha@3162: if (dataArea.contains(x, y)) sascha@3162: super.drawItem( sascha@3162: g2, sascha@3162: state, sascha@3162: dataArea, sascha@3162: info, sascha@3162: plot, sascha@3162: domainAxis, sascha@3162: rangeAxis, sascha@3162: dataset, sascha@3162: series, sascha@3162: item, sascha@3162: crosshairState, sascha@3162: pass); sascha@3162: } sascha@3162: sascha@3162: protected Point2D shiftBox(Rectangle2D box) { sascha@3162: sascha@3162: double cx1 = area.getX(); sascha@3162: double cy1 = area.getY(); sascha@3162: double cx2 = cx1 + area.getWidth(); sascha@3162: double cy2 = cy1 + area.getHeight(); sascha@3162: sascha@3162: double bx1 = box.getX(); sascha@3162: double by1 = box.getY(); sascha@3162: double bx2 = bx1 + box.getWidth(); sascha@3162: double by2 = by1 + box.getHeight(); sascha@3162: sascha@3162: double dx; sascha@3162: double dy; sascha@3162: sascha@3162: if (bx1 < cx1) { sascha@3162: dx = cx1 - bx1; sascha@3162: } sascha@3162: else if (bx2 > cx2) { sascha@3162: dx = cx2 - bx2; sascha@3162: } sascha@3162: else { sascha@3162: dx = 0d; sascha@3162: } sascha@3162: sascha@3162: if (by1 < cy1) { sascha@3162: dy = cy1 - by1; sascha@3162: } sascha@3162: else if (by2 > cy2) { sascha@3162: dy = cy2 - by2; sascha@3162: } sascha@3162: else { sascha@3162: dy = 0d; sascha@3162: } sascha@3162: sascha@3162: return new Point2D.Double(dx, dy); sascha@3162: } sascha@3162: sascha@3162: @Override sascha@3162: protected void drawItemLabel( sascha@3162: Graphics2D g2, sascha@3162: PlotOrientation orientation, sascha@3162: XYDataset dataset, sascha@3162: int series, sascha@3162: int item, sascha@3162: double x, sascha@3162: double y, sascha@3162: boolean negative sascha@3162: ) { sascha@3162: XYItemLabelGenerator generator = getItemLabelGenerator(series, item); sascha@3162: if (generator == null) { sascha@3162: return; sascha@3162: } sascha@3162: sascha@3162: Font labelFont = getItemLabelFont(series, item); sascha@3162: sascha@3162: Paint paint = getItemLabelPaint(series, item); sascha@3162: sascha@3162: g2.setFont(labelFont); sascha@3162: g2.setPaint(paint); sascha@3162: sascha@3162: String label = generator.generateLabel(dataset, series, item); sascha@3162: sascha@3164: ATTEMPS: for (int attempt = 0; attempt < 2; ++attempt) { sascha@3164: // get the label position.. sascha@3164: ItemLabelPosition position = null; sascha@3162: sascha@3164: boolean pos; sascha@3164: switch (attempt) { sascha@3164: case 0: pos = negative; break; sascha@3164: case 1: pos = !negative; break; sascha@3164: default: break ATTEMPS; sascha@3164: } sascha@3162: sascha@3164: if (pos) { sascha@3164: position = getNegativeItemLabelPosition(series, item); sascha@3162: } sascha@3164: else { sascha@3164: position = getPositiveItemLabelPosition(series, item); sascha@3164: } sascha@3162: sascha@3164: // work out the label anchor point... sascha@3164: Point2D anchorPoint = calculateLabelAnchorPoint( sascha@3164: position.getItemLabelAnchor(), x, y, orientation); sascha@3164: sascha@3164: Shape labelShape = TextUtilities.calculateRotatedStringBounds( sascha@3164: label, g2, sascha@3164: (float)anchorPoint.getX(), (float)anchorPoint.getY(), sascha@3164: position.getTextAnchor(), position.getAngle(), sascha@3164: position.getRotationAnchor()); sascha@3164: sascha@3164: Rectangle2D bbox = labelShape.getBounds2D(); sascha@3164: sascha@3164: Point2D shift = shiftBox(bbox); sascha@3164: sascha@3164: bbox = new Rectangle2D.Double( sascha@3164: bbox.getX() + shift.getX(), sascha@3164: bbox.getY() + shift.getY(), sascha@3164: bbox.getWidth(), sascha@3164: bbox.getHeight()); sascha@3164: sascha@3164: if (labelBoundingBoxes != null) { sascha@3164: for (Rectangle2D old: labelBoundingBoxes) { sascha@3164: if (old.intersects(bbox)) { sascha@3164: continue ATTEMPS; sascha@3164: } sascha@3164: } sascha@3164: labelBoundingBoxes.add(bbox); sascha@3164: } sascha@3164: sascha@3164: TextUtilities.drawRotatedString( sascha@3164: label, g2, sascha@3164: (float)(anchorPoint.getX() + shift.getX()), sascha@3164: (float)(anchorPoint.getY() + shift.getY()), sascha@3164: position.getTextAnchor(), position.getAngle(), sascha@3164: position.getRotationAnchor()); sascha@3164: break; sascha@3164: } sascha@3162: } sascha@3162: } sascha@3162: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :