view flys-artifacts/src/main/java/de/intevation/flys/jfree/StickyAxisAnnotation.java @ 2072:4cdd9c4896f6

#393 Added a new Renderer and Options in Themes that allow displaying minimum and maximum of a chart series. flys-artifacts/trunk@3581 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Ingo Weinzierl <ingo.weinzierl@intevation.de>
date Wed, 04 Jan 2012 12:24:35 +0000
parents 59622ba800c8
children c68f4f227c09
line wrap: on
line source
package de.intevation.flys.jfree;

import org.apache.log4j.Logger;

import java.util.Iterator;

import java.awt.Shape;
import java.awt.geom.Rectangle2D;
import java.awt.geom.Line2D;
import java.awt.Color;
import java.awt.Font;
import java.awt.BasicStroke;

import org.jfree.chart.annotations.XYTextAnnotation;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.util.LineUtilities;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.entity.XYAnnotationEntity;
import org.jfree.chart.plot.PlotRenderingInfo;
import org.jfree.chart.ChartRenderingInfo;
import org.jfree.chart.plot.Plot;

import org.jfree.data.Range;

import org.jfree.text.TextUtilities;

import org.jfree.ui.RectangleEdge;
import org.jfree.ui.TextAnchor;

import de.intevation.flys.utils.ThemeAccess;

/**
 * Custom Annotations class that is drawn only if no collisions with other
 * already drawn CustomAnnotations in current plot are found. A mark on
 * the axis is shown in all cases, though.
 * Draws a given text and a line to it from either axis.
 */
public class StickyAxisAnnotation extends XYTextAnnotation {

    /** Logger for this class. */    
    private static Logger logger =
        Logger.getLogger(StickyAxisAnnotation.class);

    /** Simplified view on axes. */
    public static enum SimpleAxis {
        X_AXIS, /** Usually "horizontal". */
        Y_AXIS  /** Usually "vertical". */
    }

    /** Which axis to stick to. */
    protected SimpleAxis stickyAxis = SimpleAxis.X_AXIS;

    /** The 1-dimensional position of this annotation. */
    protected float pos;

    /** Theme attributes. */
    protected Color textColor;
    protected Color lineColor;
    protected Font font;
    protected int lineWidth;
    protected String textOrientation = "vertical";
    protected Color textBackground;
    protected boolean showBackground;


    /**
     * Trivial constructor.
     *
     * @param text Text to display.
     * @param x    X-position in dataspace (typical horizontal, in km).
     * @param y    Y-position in dataspace (typical vertical, in m).
     * @deprecated
     */
    public StickyAxisAnnotation(String text, float x, float y) {
        super(text, x, y);
        setStickyAxis(SimpleAxis.X_AXIS);
    }


    /**
     * Constructor with implicit sticky x-axis.
     * @param text       the text to display.
     * @param pos        the position at which to draw the text and mark.
     */
    public StickyAxisAnnotation(String text, float pos) {
        this(text, pos, pos, SimpleAxis.X_AXIS);
    }


    /**
     * Constructor with given explicit axis.
     * @param text       the text to display.
     * @param pos        the position at which to draw the text and mark.
     * @param stickyAxis the axis at which to stick (and to which 'pos' is
     *                   relative).
     */
    public StickyAxisAnnotation(String text, float pos, SimpleAxis stickAxis) {
        super(text, pos, pos);
        setStickyAxis(stickAxis);
        this.pos = pos;
    }


    /**
     * Legacy-Constructor.
     * @deprecated
     */
    public StickyAxisAnnotation(String text, float x, float y,
            SimpleAxis stickAxis) {
        super(text, x, y);
        setStickyAxis(stickAxis);
        this.pos = x;
    }


    /**
     * Sets the "sticky axis" (whether to draw annotations at the
     * X- or the Y-Axis.
     *
     * @param stickyAxis axis to stick to.
     */
    public void setStickyAxis(SimpleAxis stickyAxis) {
        this.stickyAxis = stickyAxis;
        if (stickyAxis == SimpleAxis.X_AXIS){
            float o = 270f;
            if(textOrientation.equals("horizontal")) {
                o = 0f;
            }
            this.setRotationAngle(o * (Math.PI / 180f));
            this.setRotationAnchor(TextAnchor.CENTER_LEFT);
            this.setTextAnchor(TextAnchor.CENTER_LEFT);
        } else {
            this.setRotationAngle(0f * (Math.PI / 180f));
            this.setRotationAnchor(TextAnchor.CENTER_LEFT);
            this.setTextAnchor(TextAnchor.CENTER_LEFT);
        }
    }


    /**
     * Draws a small line at axis where this annotation resides.
     *
     * @param g2          the graphics device.
     * @param dataArea    the data area.
     * @param domainAxis  the domain axis.
     * @param rangeAxis   the range axis.
     * @param domainEdge  the domain edge.
     * @param rangeEdge   the range edge.
     * @param orientation the plot orientation.
     */
    protected void drawAxisMark(
            java.awt.Graphics2D g2,
            java.awt.geom.Rectangle2D dataArea,
            ValueAxis domainAxis,
            ValueAxis rangeAxis,
            RectangleEdge domainEdge,
            RectangleEdge rangeEdge,
            PlotOrientation orientation) {
        float j2DX1 = 0.0f;
        float j2DX2 = 0.0f;
        float j2DY1 = 0.0f;
        float j2DY2 = 0.0f;
        float x = (float) getX();
        float y = (float) getY();
        /* When dependent on X/Y-Axis and orientation, following
           can be used as a base:
        if (orientation == PlotOrientation.VERTICAL) {
            j2DX1 = (float) domainAxis.valueToJava2D(x, dataArea,
                        domainEdge);
            j2DY1 = (float) rangeAxis.valueToJava2D(y, dataArea,
                        rangeEdge);
            j2DX2 = (float) domainAxis.valueToJava2D(x, dataArea,
                        domainEdge);
            j2DY2 = (float) rangeAxis.valueToJava2D(y, dataArea,
                        rangeEdge);
            }
        else if (orientation == PlotOrientation.HORIZONTAL) {
            j2DY1 = (float) domainAxis.valueToJava2D(x, dataArea,
                    domainEdge);
            j2DX1 = (float) rangeAxis.valueToJava2D(y, dataArea,
                    rangeEdge);
            j2DY2 = (float) domainAxis.valueToJava2D(x, dataArea,
                    domainEdge);
            j2DX2 = (float) rangeAxis.valueToJava2D(y, dataArea,
                    rangeEdge);
        }

        g2.setPaint(this.paint);
        g2.setStroke(this.stroke);
        */
        if (this.stickyAxis == SimpleAxis.X_AXIS) {
            j2DY1 = (float) RectangleEdge.coordinate(dataArea, domainEdge);
            double rangeLow = rangeAxis.getRange().getLowerBound();
            // Line ends at 1.5% of full distance.
            j2DY2 = (float) rangeAxis.valueToJava2D(
                      (1f - 0.015f) * rangeLow + 0.015f * 
                      rangeAxis.getRange().getUpperBound(),
                     dataArea, rangeEdge);
            j2DX1 = (float) domainAxis.valueToJava2D(x, dataArea, domainEdge);
            j2DX2 = j2DX1;
        } else {
            j2DX1 = (float) RectangleEdge.coordinate(dataArea, rangeEdge);
            Range domainRange = domainAxis.getRange();
            double rangeLow = domainRange.getLowerBound();
            // Line ends at 1.5% of full distance.
            j2DX2 = (float) domainAxis.valueToJava2D(
                      (1f - 0.015f) * rangeLow + 0.015f * 
                      domainRange.getUpperBound(),
                     dataArea, domainEdge);
            j2DY1 = (float) rangeAxis.valueToJava2D(pos, dataArea, rangeEdge);
            j2DY2 = j2DY1;
        }

        Line2D line = new Line2D.Float(j2DX1, j2DY1, j2DX2, j2DY2);

        // line is clipped to avoid JRE bug 6574155, for more info
        // see JFreeChart bug 2221495
        boolean visible = LineUtilities.clipLine(line, dataArea);
        if (visible) {
            setOutlineStroke(new BasicStroke((float) lineWidth));
            g2.setStroke(getOutlineStroke());
            g2.setPaint(lineColor);
            g2.draw(line);
        }
    }


    /**
     * Draw the Annotation; the text only if it does not collide with other
     * already drawn Annotations- texts.
     *
     * @param g2            the graphics device.
     * @param plot          the plot.
     * @param dataArea      the data area.
     * @param domainAxis    the domain axis.
     * @param rangeAxis     the range axis.
     * @param rendererIndex the render index.
     * @param info          state information, escpecially collects info about
     *                      already drawn shapes (and thus annotations), used
     *                      for collision detection.
     */
    @Override
    public void draw(
       java.awt.Graphics2D g2,
       XYPlot plot,
       java.awt.geom.Rectangle2D dataArea,
       ValueAxis domainAxis,
       ValueAxis rangeAxis,
       int rendererIndex,
       PlotRenderingInfo info) {

        if (info == null)
            return;

        if (domainAxis == null || rangeAxis == null
                || domainAxis.getRange()    == null
                || rangeAxis.getRange()     == null
                ) {
            logger.error("Annotation cannot be drawn (missing axis).");
            return;
        }

        // Calculate the bounding box.
        ChartRenderingInfo chartInfo = info.getOwner();

        PlotOrientation orientation = plot.getOrientation();
        RectangleEdge domainEdge = Plot.resolveDomainAxisLocation(
            plot.getDomainAxisLocation(), orientation);
        RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation(
            plot.getRangeAxisLocation(), orientation);
        float anchorX = 0f;
        float anchorY = 0.0f;
        if (this.stickyAxis == SimpleAxis.X_AXIS) {
            // Text starts at 2% of full distance.
            float rangeLow = (float) rangeAxis.getRange().getLowerBound();
            float y = rangeLow + 0.02f * ((float)
                rangeAxis.getRange().getUpperBound() - rangeLow);
            setY(y);

            anchorX = (float) domainAxis.valueToJava2D(
                getX(), dataArea, domainEdge);
            anchorY = (float) rangeAxis.valueToJava2D(
                getY(), dataArea, rangeEdge);
        } else {
            float rangeLow = (float) domainAxis.getRange().getLowerBound();
            float x = rangeLow + 0.02f * ((float)
                domainAxis.getRange().getUpperBound() - rangeLow);
            setX(x);
            anchorX = (float) domainAxis.valueToJava2D(
                getX(), dataArea, domainEdge);
            anchorY = (float) rangeAxis.valueToJava2D(
                getY(), dataArea, rangeEdge);
        }
        if (orientation == PlotOrientation.HORIZONTAL) {
            float tempAnchor = anchorX;
            anchorX = anchorY;
            anchorY = tempAnchor;
        }

        //Call to apply orientation.
        setStickyAxis(stickyAxis);

        // Always draw the small line at axis.
        drawAxisMark(g2, dataArea, domainAxis, rangeAxis, domainEdge,
            rangeEdge, orientation);

        Shape hotspot = TextUtilities.calculateRotatedStringBounds(
            getText(), g2, anchorX, anchorY, getTextAnchor(),
            getRotationAngle(), getRotationAnchor());
        Rectangle2D hotspotBox = hotspot.getBounds2D();
        // Check for collisions with other XYAnnotations.
        for (Iterator i = chartInfo.getEntityCollection().iterator();
                i.hasNext(); ) {
            Object next = i.next();
            // Collision with other stuff than XYAnnotations are okay.
            if (next instanceof XYAnnotationEntity) {
                XYAnnotationEntity drawnShape = (XYAnnotationEntity) next;
                if (drawnShape.getArea().intersects(hotspotBox)) {
                    // Found collision, early stop.
                    return;
                }
            }
        }

        // Draw the background.
        if (showBackground) {
            g2.setStroke(new BasicStroke ((float) 1));
            g2.setBackground(textBackground);
            g2.setPaint(textBackground);
            hotspot = TextUtilities.calculateRotatedStringBounds(
                getText(), g2, anchorX, anchorY, getTextAnchor(),
                getRotationAngle(), getRotationAnchor());
            g2.fill(hotspot);
            g2.draw(hotspot);
        }

        // Draw the text.
        g2.setPaint(textColor);
        g2.setFont(font);
        TextUtilities.drawRotatedString(getText(), g2, anchorX, anchorY,
            getTextAnchor(), getRotationAngle(), getRotationAnchor());

        // Add info that we have drawn this Annotation.
        addEntity(info, hotspot, rendererIndex, getToolTipText(), getURL());
    }


    public void applyTheme(ThemeAccess ta) {
        lineWidth       = ta.parseLineWidth();
        lineColor       = ta.parseLineColorField();
        textColor       = ta.parseTextColor();
        font            = ta.parseTextFont();
        textOrientation = ta.parseTextOrientation();
        textBackground  = ta.parseTextBackground();
        showBackground  = ta.parseShowTextBackground();
    }
}
// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org