ingo@1115: /* ingo@1115: * Copyright (c) 2010 by Intevation GmbH ingo@1115: * ingo@1115: * This program is free software under the LGPL (>=v2.1) ingo@1115: * Read the file LGPL.txt coming with the software for details ingo@1115: * or visit http://www.gnu.org/licenses/ if it does not exist. ingo@1115: */ ingo@1115: sascha@422: package de.intevation.gnv.jfreechart; sascha@422: sascha@779: import java.awt.BasicStroke; sascha@422: import java.awt.Color; sascha@779: import java.awt.Font; sascha@779: import java.awt.FontMetrics; sascha@422: import java.awt.Graphics2D; sascha@422: import java.awt.Paint; sascha@422: import java.awt.Shape; sascha@447: sascha@422: import java.awt.geom.GeneralPath; sascha@779: sascha@779: import java.awt.geom.Rectangle2D.Double; sascha@779: sascha@422: import java.awt.geom.Rectangle2D; sascha@779: sascha@779: import java.util.ArrayList; sascha@779: sascha@779: import org.apache.log4j.Logger; sascha@779: sascha@779: import org.jfree.chart.axis.ValueAxis; sascha@422: sascha@422: import org.jfree.data.Range; sascha@422: sascha@779: import org.jfree.text.TextUtilities; sascha@450: sascha@450: import org.jfree.ui.RectangleEdge; sascha@450: sascha@422: /** ingo@795: * This renderer is used to draw polygons into a Graphics object. sascha@803: * sascha@780: * @author Ingo Weinzierl sascha@780: * @author Sascha L. Teichmann sascha@422: */ sascha@422: public class PolygonRenderer sascha@422: { sascha@447: private static Logger log = Logger.getLogger( sascha@447: PolygonRenderer.class); sascha@447: ingo@795: /** ingo@795: * This interfaces describes a single method to retrieve a Paint object ingo@795: * for a given index. ingo@795: */ ingo@795: public interface PaintLookup { sascha@422: ingo@795: /** ingo@795: * ingo@795: * @param index Index. ingo@795: * @return Paint ingo@795: */ ingo@795: Paint getPaint(int index); sascha@422: sascha@422: } // interface PaintLookup sascha@422: ingo@795: /** ingo@795: * This class is used to generate labels for a given series. ingo@795: */ ingo@795: public static class DefaultLabelGenerator sascha@450: implements PolygonSeriesLabelGenerator sascha@450: { ingo@795: /** ingo@795: * Construts an empty DefaultLabelGenerator. ingo@795: */ ingo@795: public DefaultLabelGenerator() { sascha@450: } sascha@422: ingo@795: /** ingo@795: * ingo@795: * @param series A PolygonSeries. ingo@795: * @return The label of series. ingo@795: */ ingo@795: public String generateLabel(PolygonSeries series) { sascha@450: Object label = series.getAttribute("label"); sascha@450: return label != null sascha@450: ? toString(label) sascha@450: : null; sascha@450: } sascha@422: ingo@795: /** ingo@795: * ingo@795: * @param label Object ingo@795: * @return String representaton of label. ingo@795: */ ingo@795: protected String toString(Object label) { sascha@450: return label.toString(); sascha@450: } sascha@450: } // class DefaultLabelGenerator sascha@422: ingo@795: /** ingo@795: * Constructor. ingo@795: */ ingo@795: public static final PolygonSeriesLabelGenerator sascha@450: DEFAULT_LABEL_GENERATOR_INSTANCE = new DefaultLabelGenerator(); sascha@450: ingo@795: protected PaintLookup lookup; ingo@815: sascha@450: protected PolygonSeriesLabelGenerator labelGenerator; sascha@422: ingo@815: sascha@422: public PolygonRenderer(PaintLookup lookup) { sascha@450: this(lookup, null); sascha@422: } sascha@422: ingo@815: sascha@450: public PolygonRenderer( sascha@778: PaintLookup lookup, sascha@450: PolygonSeriesLabelGenerator labelGenerator sascha@450: ) { sascha@450: this.lookup = lookup; sascha@450: this.labelGenerator = labelGenerator; sascha@422: } sascha@422: ingo@795: /** ingo@795: * This method draws polygons of each series in dataset into ingo@795: * the given graphics object. If a polygon has no attribute 'fill', we ingo@795: * expect that it is a line, otherwise the polygon is filled. ingo@795: * ingo@795: */ sascha@450: public void drawPolygons( sascha@422: Graphics2D graphics, sascha@451: PolygonPlot plot, sascha@451: Rectangle2D area, sascha@422: PolygonDataset dataset sascha@422: ) { sascha@422: int seriesCount = dataset.getSeriesCount(); sascha@422: for (int i = 0; i < seriesCount; i++) { sascha@422: PolygonSeries series = dataset.getSeries(i); sascha@422: Integer colorIdx = (Integer)series.getAttribute("fill"); sascha@422: sascha@422: if (colorIdx != null) { sascha@422: Paint paint = lookup.getPaint(colorIdx.intValue()); sascha@422: graphics.setPaint(paint != null ? paint : Color.black); sascha@451: graphics.fill(constructShape(plot, area, series, true)); sascha@422: } sascha@422: else { sascha@447: Number lineWidth = (Number)series.getAttribute("line.width"); sascha@447: BasicStroke stroke = new BasicStroke( sascha@447: lineWidth != null ? lineWidth.floatValue() : 1f); sascha@447: graphics.setStroke(stroke); sascha@422: graphics.setPaint(Color.black); sascha@451: graphics.draw(constructShape(plot, area, series, false)); sascha@422: } sascha@422: } sascha@450: } sascha@450: ingo@795: /** ingo@795: * Draw labels at each item of a series in the given dataset. If the series ingo@795: * has no label attritue, no label is drawn. sascha@803: * ingo@795: */ sascha@450: public void drawLabels( sascha@450: final Graphics2D graphics, sascha@450: final PolygonPlot plot, sascha@450: final Rectangle2D area, sascha@450: PolygonDataset dataset sascha@450: ) { sascha@450: if (labelGenerator == null) { sascha@450: return; sascha@450: } sascha@450: sascha@450: final ArrayList bboxes = new ArrayList(); sascha@450: sascha@450: Font font = graphics.getFont(); sascha@450: font = font.deriveFont(Font.PLAIN, Math.max(8, font.getSize()-3)); sascha@450: graphics.setFont(font); sascha@450: FontMetrics metrics = graphics.getFontMetrics(font); sascha@450: sascha@450: for (int i = dataset.getSeriesCount()-1; i >= 0; --i) { sascha@450: PolygonSeries series = dataset.getSeries(i); sascha@450: sascha@450: String label = labelGenerator.generateLabel(series); sascha@450: if (label == null) { sascha@450: continue; sascha@450: } sascha@450: sascha@450: final Rectangle2D box = TextUtilities.getTextBounds( sascha@450: label, graphics, metrics); sascha@450: sascha@450: for (int j = series.getItemCount()-1; j >= 0; --j) { sascha@450: final CompactXYItems ring = series.getItem(j); sascha@450: LevelOrderIndices loi = new LevelOrderIndices(ring.size()-1); sascha@450: Rectangle2D r = (Rectangle2D)loi.visit( sascha@778: new LevelOrderIndices.Visitor() sascha@450: { sascha@450: ValueAxis da = plot.getDomainAxis(); sascha@450: ValueAxis ra = plot.getRangeAxis(); sascha@450: RectangleEdge de = plot.getDomainAxisEdge(); sascha@450: RectangleEdge re = plot.getRangeAxisEdge(); sascha@450: Rectangle2D.Double r = new Rectangle2D.Double( sascha@450: 0d, 0d, box.getWidth(), box.getHeight()); sascha@450: sascha@450: public Object visit(int index) { sascha@778: r.x = da.valueToJava2D(ring.getX(index), area, de) sascha@450: - 0.5*box.getWidth(); sascha@450: r.y = ra.valueToJava2D(ring.getY(index), area, re) sascha@450: + 0.5*box.getHeight(); sascha@450: sascha@450: for (Rectangle2D b: bboxes) { sascha@450: if (b.intersects(r)) { sascha@450: return null; sascha@450: } sascha@450: } sascha@450: return r; sascha@450: } sascha@450: }); sascha@450: sascha@450: if (r != null) { sascha@450: bboxes.add(r); sascha@450: graphics.drawString( sascha@450: label, (float)r.getX(), (float)r.getY()); sascha@450: } sascha@450: } // for all items in series sascha@450: } // for all series sascha@422: } sascha@422: ingo@795: /** ingo@795: * Creates a shape made up of the CompactXYItems object stored in the given ingo@795: * series. ingo@795: * ingo@795: * @param plot The plot. ingo@795: * @param area The boundary. ingo@795: * @param series The series storing the items. ingo@795: * @param close Specifies if the polygon should be closed or not. ingo@815: * @return the constructed shape. ingo@795: */ sascha@451: protected Shape constructShape( sascha@451: PolygonPlot plot, sascha@451: Rectangle2D area, sascha@778: PolygonSeries series, sascha@451: boolean close sascha@451: ) { sascha@451: ValueAxis da = plot.getDomainAxis(); sascha@451: ValueAxis ra = plot.getRangeAxis(); sascha@451: RectangleEdge de = plot.getDomainAxisEdge(); sascha@451: RectangleEdge re = plot.getRangeAxisEdge(); sascha@451: sascha@422: CompactXYItems [] rings = series.getRings(); sascha@451: GeneralPath path = new GeneralPath(); sascha@451: sascha@422: for (int i = 0; i < rings.length; ++i) { sascha@451: sascha@422: CompactXYItems ring = rings[i]; sascha@451: sascha@422: double [] data = ring.getData(); sascha@451: sascha@422: if (data.length >= 2) { sascha@451: path.moveTo( sascha@451: (float)da.valueToJava2D(data[0], area, de), sascha@451: (float)ra.valueToJava2D(data[1], area, re)); sascha@422: } sascha@422: for (int j = 2; j < data.length;) { sascha@451: path.lineTo( sascha@451: (float)da.valueToJava2D(data[j++], area, de), sascha@451: (float)ra.valueToJava2D(data[j++], area, re)); sascha@422: } sascha@437: if (close) { sascha@437: path.closePath(); sascha@437: } sascha@422: } sascha@422: return path; sascha@422: } sascha@422: ingo@795: /** ingo@795: * Retrieves the bounding box of a dataset. ingo@795: */ sascha@422: public Rectangle2D getBoundingBox(PolygonDataset dataset) { sascha@422: Rectangle2D bbox = null; sascha@422: sascha@422: for (int i = 0, N = dataset.getSeriesCount(); i < N; i++) { sascha@422: Range domain = dataset.getSeries(i).getDomainBounds(); sascha@422: Range range = dataset.getSeries(i).getRangeBounds(); sascha@422: sascha@422: double x = domain.getLowerBound(); sascha@422: double y = range.getLowerBound(); sascha@422: double w = Math.abs(domain.getUpperBound() - x); sascha@422: double h = Math.abs(range.getUpperBound() - y); sascha@422: sascha@422: if (bbox == null) { sascha@422: bbox = new Rectangle2D.Double(x, y, w, h); sascha@422: } sascha@422: else { sascha@422: bbox.add(new Rectangle2D.Double(x, y, w, h)); sascha@422: } sascha@422: } sascha@422: sascha@422: return bbox; sascha@422: } sascha@422: ingo@795: /** ingo@795: * ingo@795: * @return the bounds of a series. ingo@795: */ sascha@422: public Rectangle2D getBounds(PolygonSeries series) { sascha@422: sascha@422: Range domain = series.getDomainBounds(); sascha@422: Range range = series.getRangeBounds(); sascha@422: sascha@422: return new Rectangle2D.Double( sascha@422: domain.getLowerBound(), range.getLowerBound(), sascha@422: domain.getUpperBound(), range.getUpperBound() sascha@422: ); sascha@422: } sascha@422: } sascha@422: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :