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; ingo@2074: ingo@3227: import java.awt.BasicStroke; ingo@2074: import java.awt.Color; gernotbelger@8910: import java.awt.Paint; ingo@2074: import java.awt.Stroke; gernotbelger@8910: import java.awt.TexturePaint; gernotbelger@8910: import java.awt.geom.Ellipse2D; gernotbelger@8910: import java.awt.geom.Rectangle2D; gernotbelger@8910: import java.awt.image.BufferedImage; ingo@2074: gernotbelger@9496: import org.apache.log4j.Logger; gernotbelger@9555: import org.dive4elements.artifactdatabase.state.Facet; gernotbelger@9496: import org.dive4elements.artifacts.CallMeta; gernotbelger@9496: import org.dive4elements.river.artifacts.resources.Resources; gernotbelger@9555: import org.dive4elements.river.exports.LegendAggregator; gernotbelger@9496: import org.dive4elements.river.java2d.ShapeUtils; gernotbelger@9182: import org.dive4elements.river.themes.ThemeDocument; gernotbelger@9496: import org.dive4elements.river.utils.Formatter; gernotbelger@9496: import org.jfree.chart.LegendItem; gernotbelger@9496: import org.jfree.chart.plot.XYPlot; ingo@3227: import org.jfree.data.xy.XYSeriesCollection; ingo@2074: ingo@2074: /** ingo@2074: * One or more dataseries to draw a polygon (either "open up/downwards", or ingo@2074: * the area between two curves), a theme-document and further display options. ingo@2074: * The theme-document will later "style" the graphical representation. sascha@3076: * The display options can be used to control the z-order and the axis of the ingo@2074: * dataset. ingo@2074: */ gernotbelger@9496: // FIXME: bad abstraction: the only purpose of this derivation is to apply specific styles. This should rather be solved gernotbelger@9496: // similar to the XYSTyle. gernotbelger@9496: public class StyledAreaSeriesCollection extends XYSeriesCollection implements StyledXYDataset { christian@3889: private static final long serialVersionUID = 5274940965666948237L; christian@3889: gernotbelger@9496: private static final Logger log = Logger.getLogger(StyledAreaSeriesCollection.class); gernotbelger@9496: ingo@2074: /** Mode, how to draw/which areas to fill. */ gernotbelger@9496: public enum FILL_MODE { gernotbelger@9496: UNDER, ABOVE, BETWEEN gernotbelger@9496: } ingo@2074: ingo@2074: /** MODE in use. */ gernotbelger@8885: private FILL_MODE mode; ingo@2074: tom@8856: /** Theme-document with attributes about actual visual representation. */ gernotbelger@9182: private final ThemeDocument theme; ingo@2074: ingo@2074: /** gernotbelger@9555: * A 'type' that allows to categorize themes by it. Tyically this is simply the facet-name of the originating gernotbelger@9555: * {@link Facet}. gernotbelger@9555: */ gernotbelger@9555: private final String themeType; gernotbelger@9555: gernotbelger@9555: /** gernotbelger@9496: * @param theme gernotbelger@9496: * the theme-document. gernotbelger@9555: * @param string ingo@2074: */ gernotbelger@9555: public StyledAreaSeriesCollection(final String themeType, final ThemeDocument theme) { gernotbelger@9555: this.themeType = themeType; ingo@2074: this.theme = theme; ingo@2074: this.mode = FILL_MODE.BETWEEN; gernotbelger@9182: } ingo@2074: gernotbelger@9555: public String getThemeType() { gernotbelger@9555: return this.themeType; gernotbelger@9555: } gernotbelger@9555: ingo@2074: /** Gets the Fill mode. */ gernotbelger@9496: private FILL_MODE getMode() { ingo@2074: return this.mode; ingo@2074: } ingo@2074: ingo@2074: /** Sets the Fill mode. */ gernotbelger@9182: public void setMode(final FILL_MODE fMode) { ingo@2074: this.mode = fMode; ingo@2074: } ingo@2074: gernotbelger@9496: @Override gernotbelger@9555: public void applyTheme(final CallMeta callMeta, final XYPlot plot, final LegendAggregator legendBuilder, final int datasetIndex) { gernotbelger@9496: gernotbelger@9496: log.debug("Registering an 'area'renderer at idx: " + datasetIndex); gernotbelger@9496: gernotbelger@9496: final StableXYDifferenceRenderer dRenderer = new StableXYDifferenceRenderer(); gernotbelger@9496: gernotbelger@9496: if (getMode() == StyledAreaSeriesCollection.FILL_MODE.UNDER) { gernotbelger@9496: dRenderer.setPositivePaint(createTransparentPaint()); gernotbelger@9496: } gernotbelger@9496: gernotbelger@9496: plot.setRenderer(datasetIndex, dRenderer); gernotbelger@9496: gernotbelger@9496: applyTheme(dRenderer); gernotbelger@9496: gernotbelger@9496: // i18n gernotbelger@9496: dRenderer.setAreaLabelNumberFormat(Formatter.getFormatter(callMeta, 2, 4)); gernotbelger@9496: gernotbelger@9496: dRenderer.setAreaLabelTemplate(Resources.getMsg(callMeta, "area.label.template", "Area=%sm2")); gernotbelger@9496: gernotbelger@9496: final LegendItem legendItem = dRenderer.getLegendItem(datasetIndex, 0); gernotbelger@9496: if (legendItem != null) { gernotbelger@9555: legendBuilder.addLegendItem(this.themeType, legendItem); gernotbelger@9496: } else { gernotbelger@9555: log.warn("Could not get LegengItem for renderer: " + datasetIndex + ", series-idx " + 0); gernotbelger@9496: } gernotbelger@9496: } gernotbelger@9496: gernotbelger@9496: /** gernotbelger@9496: * Returns a transparently textured paint. gernotbelger@9496: * gernotbelger@9496: * @return a transparently textured paint. gernotbelger@9496: */ gernotbelger@9496: private static Paint createTransparentPaint() { gernotbelger@9496: // TODO why not use a transparent color? gernotbelger@9496: final BufferedImage texture = new BufferedImage(1, 1, BufferedImage.TYPE_4BYTE_ABGR); gernotbelger@9496: gernotbelger@9496: return new TexturePaint(texture, new Rectangle2D.Double(0d, 0d, 0d, 0d)); gernotbelger@9496: } ingo@2074: ingo@2074: /** ingo@2074: * Applies line color, size and type attributes to renderer, also ingo@2074: * whether to draw lines and/or points. gernotbelger@9496: * gernotbelger@9496: * @param renderer gernotbelger@9496: * Renderer to apply theme to. felix@3395: * @return \param renderer ingo@2074: */ gernotbelger@9496: private StableXYDifferenceRenderer applyTheme(final StableXYDifferenceRenderer renderer) { ingo@2074: applyFillColor(renderer); gernotbelger@9182: applyShowBorder(renderer); gernotbelger@9182: applyShowArea(renderer); ingo@2074: applyOutlineColor(renderer); ingo@2074: applyOutlineStyle(renderer); gernotbelger@8885: applyShowLine(renderer); aheinecke@7119: applyShowAreaLabel(renderer); gernotbelger@9186: applyShowLineLabel(renderer); gernotbelger@8910: applyPointStyle(renderer); gernotbelger@9186: applyShowMinimumMaximum(renderer); gernotbelger@9602: applyBaseLineStyle(renderer); gernotbelger@9182: if (this.mode == FILL_MODE.UNDER) { gernotbelger@9496: renderer.setAreaCalculationMode(StableXYDifferenceRenderer.CALCULATE_NEGATIVE_AREA); gernotbelger@9496: } else if (this.mode == FILL_MODE.ABOVE) { gernotbelger@9496: renderer.setAreaCalculationMode(StableXYDifferenceRenderer.CALCULATE_POSITIVE_AREA); gernotbelger@9496: } else { gernotbelger@9496: renderer.setAreaCalculationMode(StableXYDifferenceRenderer.CALCULATE_ALL_AREA); felix@2666: } felix@2667: felix@2667: // Apply text style. gernotbelger@9182: this.theme.parseComplexTextStyle().apply(renderer); ingo@2074: return renderer; ingo@2074: } ingo@2074: gernotbelger@9186: private void applyShowMinimumMaximum(final StableXYDifferenceRenderer renderer) { gernotbelger@9186: gernotbelger@9186: // TODO: nice to have gernotbelger@9186: gernotbelger@9186: // final boolean minimumVisible = this.theme.parseShowMinimum(); gernotbelger@9186: // renderer.setIsMinimumShapeVisible(minimumVisible); gernotbelger@9186: // gernotbelger@9186: // final boolean maximumVisible = this.theme.parseShowMaximum(); gernotbelger@9186: // renderer.setIsMaximumShapeVisible(maximumVisible); gernotbelger@9186: } gernotbelger@9186: gernotbelger@9602: private void applyBaseLineStyle(final StableXYDifferenceRenderer renderer) { gernotbelger@9602: final Color c = this.theme.parseBaseLineColor(); gernotbelger@9602: renderer.setBaseLineColor(c); gernotbelger@9602: } gernotbelger@9602: gernotbelger@8910: private void applyFillColor(final StableXYDifferenceRenderer renderer) { christian@3889: gernotbelger@8910: Paint paint = parseFillPaint(); gernotbelger@9182: ingo@2074: if (paint != null && this.getMode() == FILL_MODE.ABOVE) { ingo@2074: renderer.setPositivePaint(paint); gernotbelger@9496: renderer.setNegativePaint(new Color(0, 0, 0, 0)); gernotbelger@9496: } else if (paint != null && this.getMode() == FILL_MODE.UNDER) { ingo@2074: renderer.setNegativePaint(paint); gernotbelger@9496: renderer.setPositivePaint(new Color(0, 0, 0, 0)); gernotbelger@9496: } else { christian@3889: if (paint == null) christian@3889: paint = new Color(177, 117, 102); gernotbelger@9182: ingo@2074: renderer.setPositivePaint(paint); ingo@2074: renderer.setNegativePaint(paint); ingo@2074: } ingo@2074: } ingo@2074: gernotbelger@8910: private Paint parseFillPaint() { gernotbelger@8910: final Color paint = this.theme.parseAreaBackgroundColor(); gernotbelger@9182: final int transparency = this.theme.parseAreaTransparency(); gernotbelger@9182: gernotbelger@9496: final Color alphaPaint = ShapeUtils.withAlpha(paint, transparency); gernotbelger@9182: gernotbelger@8910: final AreaFillPattern pattern = this.theme.parseAreaBackgroundPattern(); gernotbelger@8910: gernotbelger@9496: if (pattern == null || pattern == AreaFillPattern.patternFill) gernotbelger@8910: return alphaPaint; gernotbelger@9182: gernotbelger@8910: final BufferedImage image = pattern.getImage(alphaPaint); gernotbelger@9182: gernotbelger@9496: final Rectangle2D anchor = new Rectangle2D.Double(0, 0, image.getWidth(), image.getHeight()); gernotbelger@8910: return new TexturePaint(image, anchor); gernotbelger@8910: } gernotbelger@8910: gernotbelger@9182: private void applyShowBorder(final StableXYDifferenceRenderer renderer) { gernotbelger@9182: final boolean show = this.theme.parseAreaShowBorder(); ingo@2074: renderer.setDrawOutline(show); ingo@2074: } ingo@2074: gernotbelger@9182: private void applyShowArea(final StableXYDifferenceRenderer renderer) { felix@3395: gernotbelger@9182: final boolean showArea = this.theme.parseShowArea(); gernotbelger@9182: renderer.setDrawArea(showArea); gernotbelger@9182: } gernotbelger@9182: gernotbelger@9182: private void applyShowLine(final StableXYDifferenceRenderer renderer) { gernotbelger@8910: /* FIXME: strange: this will enable/disable showing the 'point' shapes at each vertex. */ gernotbelger@8910: /* FIXME: this will also now be overridden by the option 'showpoints' */ gernotbelger@9182: final boolean show = this.theme.parseShowLine(); ingo@2074: renderer.setShapesVisible(show); ingo@2074: } ingo@2074: gernotbelger@9182: private void applyOutlineColor(final StableXYDifferenceRenderer renderer) { gernotbelger@9182: final Color c = this.theme.parseLineColorField(); ingo@2074: renderer.setOutlinePaint(c); ingo@2074: } ingo@2074: gernotbelger@9182: private void applyShowAreaLabel(final StableXYDifferenceRenderer renderer) { gernotbelger@9186: renderer.setShowAreaLabel(this.theme.parseShowAreaLabel()); gernotbelger@9186: } gernotbelger@9186: gernotbelger@9186: private void applyShowLineLabel(final StableXYDifferenceRenderer renderer) { gernotbelger@9186: // REMARK: using 'showlinelabel' to activate labeling the line with the title of the theme. This is the same behaviour gernotbelger@9186: // as for line-themes. gernotbelger@9186: final boolean showLabelLine = this.theme.parseShowLineLabel(); gernotbelger@9186: renderer.setShowTitleLabel(showLabelLine); felix@2666: } felix@2666: gernotbelger@9182: private void applyOutlineStyle(final StableXYDifferenceRenderer renderer) { gernotbelger@9182: final float[] dashes = this.theme.parseLineStyle(); gernotbelger@9496: final int size = this.theme.parseLineWidth(); ingo@2074: ingo@2074: Stroke stroke = null; ingo@2074: ingo@2074: if (dashes.length <= 1) { ingo@2074: stroke = new BasicStroke(Integer.valueOf(size)); gernotbelger@9496: } else { gernotbelger@9496: stroke = new BasicStroke(Integer.valueOf(size), BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 1.0f, dashes, 0.0f); ingo@2074: } ingo@2074: ingo@2074: renderer.setOutlineStroke(stroke); ingo@2074: } gernotbelger@8885: gernotbelger@8910: private void applyPointStyle(final StableXYDifferenceRenderer renderer) { gernotbelger@8910: gernotbelger@8910: final boolean showPoints = this.theme.parseShowPoints(); gernotbelger@8910: renderer.setShapesVisible(showPoints); gernotbelger@8910: gernotbelger@9496: if (showPoints) { gernotbelger@9182: final int size = this.theme.parsePointWidth(); gernotbelger@9496: final int dim = 2 * size; gernotbelger@8910: gernotbelger@8910: final Ellipse2D pointShape = new Ellipse2D.Double(-size, -size, dim, dim); gernotbelger@9182: final Color pointColor = this.theme.parsePointColor(); gernotbelger@8910: gernotbelger@8910: renderer.setSeriesPaint(0, pointColor); gernotbelger@8910: renderer.setSeriesPaint(1, pointColor); gernotbelger@8910: gernotbelger@8910: renderer.setSeriesShape(0, pointShape); gernotbelger@8910: renderer.setSeriesShape(1, pointShape); gernotbelger@8910: } gernotbelger@8910: } gernotbelger@8910: gernotbelger@8885: public boolean shouldCalculateRange() { gernotbelger@9182: return this.theme.parseCalculateRange(); gernotbelger@8885: } gernotbelger@9555: gernotbelger@9555: @Override gernotbelger@9555: public void applyAggregatedLegendTheme(final LegendItem item, final ThemeDocument legendTheme) { gernotbelger@9555: /* not implemented */ gernotbelger@9555: } gernotbelger@9555: }