felix@1039: package de.intevation.flys.jfree;
felix@1039: 
felix@1039: import org.apache.log4j.Logger;
felix@1039: 
felix@1039: import java.util.Iterator;
felix@1039: 
felix@1039: import java.awt.Shape;
felix@1039: import java.awt.geom.Rectangle2D;
felix@1039: import java.awt.geom.Line2D;
felix@1039: 
felix@1039: import org.jfree.chart.annotations.XYTextAnnotation;
felix@1039: import org.jfree.chart.axis.ValueAxis;
felix@1039: import org.jfree.chart.util.LineUtilities;
felix@1039: import org.jfree.chart.plot.PlotOrientation;
felix@1039: import org.jfree.chart.plot.XYPlot;
felix@1039: import org.jfree.chart.entity.XYAnnotationEntity;
felix@1039: import org.jfree.chart.plot.PlotRenderingInfo;
felix@1039: import org.jfree.chart.ChartRenderingInfo;
felix@1039: import org.jfree.chart.plot.Plot;
felix@1039: 
felix@1085: import org.jfree.data.Range;
felix@1085: 
felix@1085: import org.jfree.text.TextUtilities;
felix@1085: 
felix@1039: import org.jfree.ui.RectangleEdge;
felix@1082: import org.jfree.ui.TextAnchor;
felix@1039: 
felix@1039: 
felix@1039: /**
felix@1039:  * Custom Annotations class that is drawn only if no collisions with other
felix@1039:  * already drawn CustomAnnotations in current plot are found.
felix@1039:  * Draws a given text and a line to it from either axis.
felix@1039:  */
felix@1039: public class StickyAxisAnnotation extends XYTextAnnotation {
felix@1039: 
felix@1039:     /** Logger for this class. */    
felix@1039:     private static Logger logger =
felix@1039:         Logger.getLogger(StickyAxisAnnotation.class);
felix@1039: 
felix@1039:     /** Simplified view on axes. */
felix@1039:     public static enum SimpleAxis {
felix@1039:         X_AXIS, /** Usually "horizontal". */
felix@1039:         Y_AXIS  /** Usually "vertical". */
felix@1039:     }
felix@1039: 
felix@1039:     /** Which axis to stick to. */
felix@1039:     protected SimpleAxis stickyAxis = SimpleAxis.X_AXIS;
felix@1039: 
felix@1085:     /** The 1-dimensional position of this annotation. */
felix@1085:     protected float pos;
felix@1085: 
felix@1039: 
felix@1039:     /**
felix@1039:      * Trivial constructor.
felix@1039:      *
felix@1039:      * @param text Text to display.
felix@1039:      * @param x    X-position in dataspace (typical horizontal, in km).
felix@1039:      * @param y    Y-position in dataspace (typical vertical, in m).
felix@1085:      * @deprecated
felix@1039:      */
felix@1039:     public StickyAxisAnnotation(String text, float x, float y) {
felix@1039:         super(text, x, y);
felix@1082:         setStickyAxis(SimpleAxis.X_AXIS);
felix@1039:     }
felix@1039: 
felix@1085: 
felix@1085:     /**
felix@1089:      * Constructor with implicit sticky x-axis.
felix@1089:      * @param text       the text to display.
felix@1089:      * @param pos        the position at which to draw the text and mark.
felix@1089:      */
felix@1089:     public StickyAxisAnnotation(String text, float pos) {
felix@1089:         this(text, pos, pos, SimpleAxis.X_AXIS);
felix@1089:     }
felix@1089: 
felix@1089: 
felix@1089:     /**
felix@1085:      * Constructor with given explicit axis.
felix@1085:      * @param text       the text to display.
felix@1085:      * @param pos        the position at which to draw the text and mark.
felix@1085:      * @param stickyAxis the axis at which to stick (and to which 'pos' is
felix@1085:      *                   relative).
felix@1085:      */
felix@1085:     public StickyAxisAnnotation(String text, float pos, SimpleAxis stickAxis) {
felix@1085:         super(text, pos, pos);
felix@1085:         setStickyAxis(stickAxis);
felix@1085:         this.pos = pos;
felix@1085:     }
felix@1085: 
felix@1085:     /**
felix@1085:      * Legacy-Constructor.
felix@1085:      * @deprecated
felix@1085:      */
felix@1082:     public StickyAxisAnnotation(String text, float x, float y,
felix@1082:             SimpleAxis stickAxis) {
felix@1082:         super(text, x, y);
felix@1082:         setStickyAxis(stickAxis);
felix@1085:         this.pos = x;
felix@1082:     }
felix@1082: 
felix@1082: 
felix@1039:     /**
felix@1039:      * Sets the "sticky axis" (whether to draw annotations at the
felix@1039:      * X- or the Y-Axis.
felix@1039:      *
felix@1039:      * @param stickyAxis axis to stick to.
felix@1039:      */
felix@1039:     public void setStickyAxis(SimpleAxis stickyAxis) {
felix@1039:         this.stickyAxis = stickyAxis;
felix@1082:         if (stickyAxis == SimpleAxis.X_AXIS) {
felix@1082:             this.setRotationAngle(270f * (Math.PI / 180f));
felix@1082:             this.setRotationAnchor(TextAnchor.CENTER_LEFT);
felix@1082:             this.setTextAnchor(TextAnchor.CENTER_LEFT);
felix@1085:         } else {
felix@1085:             this.setRotationAngle(0f * (Math.PI / 180f));
felix@1085:             this.setRotationAnchor(TextAnchor.CENTER_LEFT);
felix@1085:             this.setTextAnchor(TextAnchor.CENTER_LEFT);
felix@1082:         }
felix@1039:     }
felix@1039: 
felix@1039: 
felix@1039:     /**
felix@1039:      * Draws a small line at axis where this annotation resides.
felix@1039:      *
felix@1039:      * @param g2          the graphics device.
felix@1039:      * @param dataArea    the data area.
felix@1039:      * @param domainAxis  the domain axis.
felix@1039:      * @param rangeAxis   the range axis.
felix@1039:      * @param domainEdge  the domain edge.
felix@1039:      * @param rangeEdge   the range edge.
felix@1039:      * @param orientation the plot orientation.
felix@1039:      */
felix@1039:     protected void drawAxisMark(
felix@1039:             java.awt.Graphics2D g2,
felix@1039:             java.awt.geom.Rectangle2D dataArea,
felix@1039:             ValueAxis domainAxis,
felix@1039:             ValueAxis rangeAxis,
felix@1039:             RectangleEdge domainEdge,
felix@1039:             RectangleEdge rangeEdge,
felix@1039:             PlotOrientation orientation) {
felix@1039:         float j2DX1 = 0.0f;
felix@1039:         float j2DX2 = 0.0f;
felix@1039:         float j2DY1 = 0.0f;
felix@1039:         float j2DY2 = 0.0f;
felix@1039:         float x = (float) getX();
felix@1039:         float y = (float) getY();
felix@1039:         /* When dependent on X/Y-Axis and orientation, following
felix@1039:            can be used as a base:
felix@1039:         if (orientation == PlotOrientation.VERTICAL) {
felix@1039:             j2DX1 = (float) domainAxis.valueToJava2D(x, dataArea,
felix@1039:                         domainEdge);
felix@1039:             j2DY1 = (float) rangeAxis.valueToJava2D(y, dataArea,
felix@1039:                         rangeEdge);
felix@1039:             j2DX2 = (float) domainAxis.valueToJava2D(x, dataArea,
felix@1039:                         domainEdge);
felix@1039:             j2DY2 = (float) rangeAxis.valueToJava2D(y, dataArea,
felix@1039:                         rangeEdge);
felix@1039:             }
felix@1039:         else if (orientation == PlotOrientation.HORIZONTAL) {
felix@1039:             j2DY1 = (float) domainAxis.valueToJava2D(x, dataArea,
felix@1039:                     domainEdge);
felix@1039:             j2DX1 = (float) rangeAxis.valueToJava2D(y, dataArea,
felix@1039:                     rangeEdge);
felix@1039:             j2DY2 = (float) domainAxis.valueToJava2D(x, dataArea,
felix@1039:                     domainEdge);
felix@1039:             j2DX2 = (float) rangeAxis.valueToJava2D(y, dataArea,
felix@1039:                     rangeEdge);
felix@1039:         }
felix@1039: 
felix@1039:         g2.setPaint(this.paint);
felix@1039:         g2.setStroke(this.stroke);
felix@1039:         */
felix@1085:         if (this.stickyAxis == SimpleAxis.X_AXIS) {
felix@1085:             j2DY1 = (float) RectangleEdge.coordinate(dataArea, domainEdge);
felix@1085:             double rangeLow = rangeAxis.getRange().getLowerBound();
felix@1085:             // Line ends at 1.5% of full distance.
felix@1085:             j2DY2 = (float) rangeAxis.valueToJava2D(
felix@1085:                       (1f - 0.015f) * rangeLow + 0.015f * 
felix@1085:                       rangeAxis.getRange().getUpperBound(),
felix@1085:                      dataArea, rangeEdge);
felix@1085:             j2DX1 = (float) domainAxis.valueToJava2D(x, dataArea, domainEdge);
felix@1085:             j2DX2 = j2DX1;
felix@1085:         } else {
felix@1085:             j2DX1 = (float) RectangleEdge.coordinate(dataArea, rangeEdge);
felix@1085:             Range domainRange = domainAxis.getRange();
felix@1085:             double rangeLow = domainRange.getLowerBound();
felix@1085:             // Line ends at 1.5% of full distance.
felix@1085:             j2DX2 = (float) domainAxis.valueToJava2D(
felix@1085:                       (1f - 0.015f) * rangeLow + 0.015f * 
felix@1085:                       domainRange.getUpperBound(),
felix@1085:                      dataArea, domainEdge);
felix@1085:             j2DY1 = (float) rangeAxis.valueToJava2D(pos, dataArea, rangeEdge);
felix@1085:             j2DY2 = j2DY1;
felix@1085:         }
felix@1039: 
felix@1039:         Line2D line = new Line2D.Float(j2DX1, j2DY1, j2DX2, j2DY2);
felix@1039: 
felix@1039:         // line is clipped to avoid JRE bug 6574155, for more info
felix@1039:         // see JFreeChart bug 2221495
felix@1039:         boolean visible = LineUtilities.clipLine(line, dataArea);
felix@1039:         if (visible) {
felix@1039:             g2.draw(line);
felix@1039:         }
felix@1039:     }
felix@1039: 
felix@1039: 
felix@1039:     /**
felix@1039:      * Draw the Annotiation if it does not collide with other already drawn
felix@1039:      * Annotations.
felix@1039:      *
felix@1039:      * @param g2            the graphics device.
felix@1039:      * @param plot          the plot.
felix@1039:      * @param dataArea      the data area.
felix@1039:      * @param domainAxis    the domain axis.
felix@1039:      * @param rangeAxis     the range axis.
felix@1039:      * @param rendererIndex the render index.
felix@1039:      * @param info          state information, escpecially collects info about
felix@1039:      *                      already drawn shapes (and thus annotations), used
felix@1039:      *                      for collision detection.
felix@1039:      */
felix@1039:     @Override
felix@1039:     public void draw(
felix@1039:        java.awt.Graphics2D g2,
felix@1039:        XYPlot plot,
felix@1039:        java.awt.geom.Rectangle2D dataArea,
felix@1039:        ValueAxis domainAxis,
felix@1039:        ValueAxis rangeAxis,
felix@1039:        int rendererIndex,
felix@1039:        PlotRenderingInfo info) {
felix@1039: 
felix@1039:         if (info == null)
felix@1039:             return;
felix@1039: 
felix@1167:         if (domainAxis == null || rangeAxis == null
felix@1167:                 || domainAxis.getRange()    == null
felix@1167:                 || rangeAxis.getRange()     == null
felix@1167:                 ) {
felix@1167:             logger.error("Annotation cannot be drawn (missing axis).");
felix@1167:             return;
felix@1167:         }
felix@1167: 
felix@1039:         // Calculate the bounding box.
felix@1039:         ChartRenderingInfo chartInfo = info.getOwner();
felix@1039: 
felix@1039:         PlotOrientation orientation = plot.getOrientation();
felix@1039:         RectangleEdge domainEdge = Plot.resolveDomainAxisLocation(
felix@1039:             plot.getDomainAxisLocation(), orientation);
felix@1039:         RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation(
felix@1039:             plot.getRangeAxisLocation(), orientation);
felix@1085:         float anchorX = 0f;
felix@1085:         float anchorY = 0.0f;
felix@1085:         if (this.stickyAxis == SimpleAxis.X_AXIS) {
felix@1085:             // Text starts at 1.5% of full distance.
felix@1085:             float rangeLow = (float) rangeAxis.getRange().getLowerBound();
felix@1085:             float y = rangeLow + 0.02f * ((float)
felix@1085:                     rangeAxis.getRange().getUpperBound() - rangeLow);
felix@1085:             setY(y);
felix@1085: 
felix@1085:             anchorX = (float) domainAxis.valueToJava2D(
felix@1085:                 getX(), dataArea, domainEdge);
felix@1085:             anchorY = (float) rangeAxis.valueToJava2D(
felix@1085:                 getY(), dataArea, rangeEdge);
felix@1085:         } else {
felix@1085:             float rangeLow = (float) domainAxis.getRange().getLowerBound();
felix@1085:             float x = rangeLow + 0.02f * ((float)
felix@1085:                     domainAxis.getRange().getUpperBound() - rangeLow);
felix@1085:             setX(x);
felix@1085:             anchorX = (float) domainAxis.valueToJava2D(
felix@1085:                 getX(), dataArea, domainEdge);
felix@1085:             anchorY = (float) rangeAxis.valueToJava2D(
felix@1085:                 getY(), dataArea, rangeEdge);
felix@1085:         }
felix@1039:         if (orientation == PlotOrientation.HORIZONTAL) {
felix@1039:             float tempAnchor = anchorX;
felix@1039:             anchorX = anchorY;
felix@1039:             anchorY = tempAnchor;
felix@1039:         }
felix@1039: 
felix@1039:         // Always draw the small line at axis.
felix@1711:         g2.setStroke(getOutlineStroke());
felix@1711:         g2.setPaint(getPaint());
felix@1039:         drawAxisMark(g2, dataArea, domainAxis, rangeAxis, domainEdge,
felix@1039:                 rangeEdge, orientation);
felix@1039: 
felix@1039:         g2.setFont(getFont());
felix@1039:         Shape hotspot = TextUtilities.calculateRotatedStringBounds(
felix@1039:            getText(), g2, anchorX, anchorY, getTextAnchor(),
felix@1039:            getRotationAngle(), getRotationAnchor());
felix@1039:         Rectangle2D hotspotBox = hotspot.getBounds2D();
felix@1039:         // Check for collisions with other XYAnnotations.
felix@1039:         for (Iterator i = chartInfo.getEntityCollection().iterator();
felix@1039:                 i.hasNext(); ) {
felix@1039:             Object next = i.next();
felix@1039:             // Collision with other stuff than XYAnnotations are okay.
felix@1039:             if (next instanceof XYAnnotationEntity) {
felix@1039:                 XYAnnotationEntity drawnShape = (XYAnnotationEntity) next;
felix@1039:                 if (drawnShape.getArea().intersects(hotspotBox)) {
felix@1039:                     // Found collision, early stop.
felix@1039:                     return;
felix@1039:                 }
felix@1039:             }
felix@1039:         }
felix@1039: 
felix@1039:         // Actuall drawing.
felix@1039:         if (getBackgroundPaint() != null) {
felix@1711:             g2.setPaint(getBackgroundPaint());
felix@1039:             g2.fill(hotspot);
felix@1039:         }
felix@1039:         g2.setPaint(getPaint());
felix@1039:         TextUtilities.drawRotatedString(getText(), g2, anchorX, anchorY,
felix@1039:                 getTextAnchor(), getRotationAngle(), getRotationAnchor());
felix@1039:         // Draw outline.
felix@1156:         if (false) {
felix@1039:             g2.setStroke(getOutlineStroke());
felix@1039:             g2.setPaint(getOutlinePaint());
felix@1039:             g2.draw(hotspot);
felix@1039:         }
felix@1039: 
felix@1039:         // Add info that we have drawn this Annotation.
felix@1039:         addEntity(info, hotspot, rendererIndex, getToolTipText(), getURL());
felix@1039:     }
felix@1039: }
felix@1711: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :