view artifacts/src/main/java/org/dive4elements/river/jfree/StyledAreaSeriesCollection.java @ 9555:ef5754ba5573

Implemented legend aggregation based on type of themes. Added theme-editor style configuration for aggregated legend entries. Only configured themes get aggregated.
author gernotbelger
date Tue, 23 Oct 2018 16:26:48 +0200
parents d8e753d0fdb9
children 6b2496d71936
line wrap: on
line source
/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
 * Software engineering by Intevation GmbH
 *
 * This file is Free Software under the GNU AGPL (>=v3)
 * and comes with ABSOLUTELY NO WARRANTY! Check out the
 * documentation coming with Dive4Elements River for details.
 */

package org.dive4elements.river.jfree;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Paint;
import java.awt.Stroke;
import java.awt.TexturePaint;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;

import org.apache.log4j.Logger;
import org.dive4elements.artifactdatabase.state.Facet;
import org.dive4elements.artifacts.CallMeta;
import org.dive4elements.river.artifacts.resources.Resources;
import org.dive4elements.river.exports.LegendAggregator;
import org.dive4elements.river.java2d.ShapeUtils;
import org.dive4elements.river.themes.ThemeDocument;
import org.dive4elements.river.utils.Formatter;
import org.jfree.chart.LegendItem;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.xy.XYSeriesCollection;

/**
 * One or more dataseries to draw a polygon (either "open up/downwards", or
 * the area between two curves), a theme-document and further display options.
 * The theme-document will later "style" the graphical representation.
 * The display options can be used to control the z-order and the axis of the
 * dataset.
 */
// FIXME: bad abstraction: the only purpose of this derivation is to apply specific styles. This should rather be solved
// similar to the XYSTyle.
public class StyledAreaSeriesCollection extends XYSeriesCollection implements StyledXYDataset {
    private static final long serialVersionUID = 5274940965666948237L;

    private static final Logger log = Logger.getLogger(StyledAreaSeriesCollection.class);

    /** Mode, how to draw/which areas to fill. */
    public enum FILL_MODE {
        UNDER, ABOVE, BETWEEN
    }

    /** MODE in use. */
    private FILL_MODE mode;

    /** Theme-document with attributes about actual visual representation. */
    private final ThemeDocument theme;

    /**
     * A 'type' that allows to categorize themes by it. Tyically this is simply the facet-name of the originating
     * {@link Facet}.
     */
    private final String themeType;

    /**
     * @param theme
     *            the theme-document.
     * @param string
     */
    public StyledAreaSeriesCollection(final String themeType, final ThemeDocument theme) {
        this.themeType = themeType;
        this.theme = theme;
        this.mode = FILL_MODE.BETWEEN;
    }

    public String getThemeType() {
        return this.themeType;
    }

    /** Gets the Fill mode. */
    private FILL_MODE getMode() {
        return this.mode;
    }

    /** Sets the Fill mode. */
    public void setMode(final FILL_MODE fMode) {
        this.mode = fMode;
    }

    @Override
    public void applyTheme(final CallMeta callMeta, final XYPlot plot, final LegendAggregator legendBuilder, final int datasetIndex) {

        log.debug("Registering an 'area'renderer at idx: " + datasetIndex);

        final StableXYDifferenceRenderer dRenderer = new StableXYDifferenceRenderer();

        if (getMode() == StyledAreaSeriesCollection.FILL_MODE.UNDER) {
            dRenderer.setPositivePaint(createTransparentPaint());
        }

        plot.setRenderer(datasetIndex, dRenderer);

        applyTheme(dRenderer);

        // i18n
        dRenderer.setAreaLabelNumberFormat(Formatter.getFormatter(callMeta, 2, 4));

        dRenderer.setAreaLabelTemplate(Resources.getMsg(callMeta, "area.label.template", "Area=%sm2"));

        final LegendItem legendItem = dRenderer.getLegendItem(datasetIndex, 0);
        if (legendItem != null) {
            legendBuilder.addLegendItem(this.themeType, legendItem);
        } else {
            log.warn("Could not get LegengItem for renderer: " + datasetIndex + ", series-idx " + 0);
        }
    }

    /**
     * Returns a transparently textured paint.
     *
     * @return a transparently textured paint.
     */
    private static Paint createTransparentPaint() {
        // TODO why not use a transparent color?
        final BufferedImage texture = new BufferedImage(1, 1, BufferedImage.TYPE_4BYTE_ABGR);

        return new TexturePaint(texture, new Rectangle2D.Double(0d, 0d, 0d, 0d));
    }

    /**
     * Applies line color, size and type attributes to renderer, also
     * whether to draw lines and/or points.
     *
     * @param renderer
     *            Renderer to apply theme to.
     * @return \param renderer
     */
    private StableXYDifferenceRenderer applyTheme(final StableXYDifferenceRenderer renderer) {
        applyFillColor(renderer);
        applyShowBorder(renderer);
        applyShowArea(renderer);
        applyOutlineColor(renderer);
        applyOutlineStyle(renderer);
        applyShowLine(renderer);
        applyShowAreaLabel(renderer);
        applyShowLineLabel(renderer);
        applyPointStyle(renderer);
        applyShowMinimumMaximum(renderer);
        if (this.mode == FILL_MODE.UNDER) {
            renderer.setAreaCalculationMode(StableXYDifferenceRenderer.CALCULATE_NEGATIVE_AREA);
        } else if (this.mode == FILL_MODE.ABOVE) {
            renderer.setAreaCalculationMode(StableXYDifferenceRenderer.CALCULATE_POSITIVE_AREA);
        } else {
            renderer.setAreaCalculationMode(StableXYDifferenceRenderer.CALCULATE_ALL_AREA);
        }

        // Apply text style.
        this.theme.parseComplexTextStyle().apply(renderer);
        return renderer;
    }

    private void applyShowMinimumMaximum(final StableXYDifferenceRenderer renderer) {

        // TODO: nice to have

        // final boolean minimumVisible = this.theme.parseShowMinimum();
        // renderer.setIsMinimumShapeVisible(minimumVisible);
        //
        // final boolean maximumVisible = this.theme.parseShowMaximum();
        // renderer.setIsMaximumShapeVisible(maximumVisible);
    }

    private void applyFillColor(final StableXYDifferenceRenderer renderer) {

        Paint paint = parseFillPaint();

        if (paint != null && this.getMode() == FILL_MODE.ABOVE) {
            renderer.setPositivePaint(paint);
            renderer.setNegativePaint(new Color(0, 0, 0, 0));
        } else if (paint != null && this.getMode() == FILL_MODE.UNDER) {
            renderer.setNegativePaint(paint);
            renderer.setPositivePaint(new Color(0, 0, 0, 0));
        } else {
            if (paint == null)
                paint = new Color(177, 117, 102);

            renderer.setPositivePaint(paint);
            renderer.setNegativePaint(paint);
        }
    }

    private Paint parseFillPaint() {
        final Color paint = this.theme.parseAreaBackgroundColor();
        final int transparency = this.theme.parseAreaTransparency();

        final Color alphaPaint = ShapeUtils.withAlpha(paint, transparency);

        final AreaFillPattern pattern = this.theme.parseAreaBackgroundPattern();

        if (pattern == null || pattern == AreaFillPattern.patternFill)
            return alphaPaint;

        final BufferedImage image = pattern.getImage(alphaPaint);

        final Rectangle2D anchor = new Rectangle2D.Double(0, 0, image.getWidth(), image.getHeight());
        return new TexturePaint(image, anchor);
    }

    private void applyShowBorder(final StableXYDifferenceRenderer renderer) {
        final boolean show = this.theme.parseAreaShowBorder();
        renderer.setDrawOutline(show);
    }

    private void applyShowArea(final StableXYDifferenceRenderer renderer) {

        final boolean showArea = this.theme.parseShowArea();
        renderer.setDrawArea(showArea);
    }

    private void applyShowLine(final StableXYDifferenceRenderer renderer) {
        /* FIXME: strange: this will enable/disable showing the 'point' shapes at each vertex. */
        /* FIXME: this will also now be overridden by the option 'showpoints' */
        final boolean show = this.theme.parseShowLine();
        renderer.setShapesVisible(show);
    }

    private void applyOutlineColor(final StableXYDifferenceRenderer renderer) {
        final Color c = this.theme.parseLineColorField();
        renderer.setOutlinePaint(c);
    }

    private void applyShowAreaLabel(final StableXYDifferenceRenderer renderer) {
        renderer.setShowAreaLabel(this.theme.parseShowAreaLabel());
    }

    private void applyShowLineLabel(final StableXYDifferenceRenderer renderer) {
        // REMARK: using 'showlinelabel' to activate labeling the line with the title of the theme. This is the same behaviour
        // as for line-themes.
        final boolean showLabelLine = this.theme.parseShowLineLabel();
        renderer.setShowTitleLabel(showLabelLine);
    }

    private void applyOutlineStyle(final StableXYDifferenceRenderer renderer) {
        final float[] dashes = this.theme.parseLineStyle();
        final int size = this.theme.parseLineWidth();

        Stroke stroke = null;

        if (dashes.length <= 1) {
            stroke = new BasicStroke(Integer.valueOf(size));
        } else {
            stroke = new BasicStroke(Integer.valueOf(size), BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 1.0f, dashes, 0.0f);
        }

        renderer.setOutlineStroke(stroke);
    }

    private void applyPointStyle(final StableXYDifferenceRenderer renderer) {

        final boolean showPoints = this.theme.parseShowPoints();
        renderer.setShapesVisible(showPoints);

        if (showPoints) {
            final int size = this.theme.parsePointWidth();
            final int dim = 2 * size;

            final Ellipse2D pointShape = new Ellipse2D.Double(-size, -size, dim, dim);
            final Color pointColor = this.theme.parsePointColor();

            renderer.setSeriesPaint(0, pointColor);
            renderer.setSeriesPaint(1, pointColor);

            renderer.setSeriesShape(0, pointShape);
            renderer.setSeriesShape(1, pointShape);
        }
    }

    public boolean shouldCalculateRange() {
        return this.theme.parseCalculateRange();
    }

    @Override
    public void applyAggregatedLegendTheme(final LegendItem item, final ThemeDocument legendTheme) {
        /* not implemented */
    }
}

http://dive4elements.wald.intevation.org