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; raimund@1738: import java.awt.Color; raimund@1738: import java.awt.Font; raimund@1738: import java.awt.BasicStroke; 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: sascha@1754: import de.intevation.flys.utils.ThemeAccess; 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: raimund@1738: /** Theme attributes. */ raimund@1738: protected Color textColor; raimund@1738: protected Color lineColor; raimund@1738: protected Font font; raimund@1738: protected int lineWidth; raimund@1751: protected String textOrientation = "vertical"; raimund@1751: protected Color textBackground; raimund@1751: protected boolean showBackground; raimund@1738: 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; raimund@1751: if (stickyAxis == SimpleAxis.X_AXIS){ raimund@1751: float o = 270f; raimund@1751: if(textOrientation.equals("horizontal")) { raimund@1751: o = 0f; raimund@1751: } raimund@1751: this.setRotationAngle(o * (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: raimund@1751: //Call to apply orientation. raimund@1751: setStickyAxis(stickyAxis); felix@1039: 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: raimund@1751: // Draw the background. raimund@1751: if (showBackground) { raimund@1751: g2.setStroke(new BasicStroke ((float) 1)); raimund@1751: g2.setBackground(textBackground); raimund@1751: g2.setPaint(textBackground); raimund@1751: hotspot = TextUtilities.calculateRotatedStringBounds( raimund@1751: getText(), g2, anchorX, anchorY, getTextAnchor(), raimund@1751: getRotationAngle(), getRotationAnchor()); felix@1039: g2.fill(hotspot); raimund@1751: g2.draw(hotspot); felix@1039: } raimund@1751: raimund@1751: // Always draw the small line at axis. raimund@1751: setOutlineStroke(new BasicStroke((float) lineWidth)); raimund@1751: g2.setStroke(getOutlineStroke()); raimund@1751: g2.setPaint(lineColor); raimund@1751: drawAxisMark(g2, dataArea, domainAxis, rangeAxis, domainEdge, raimund@1751: rangeEdge, orientation); raimund@1751: raimund@1751: // Draw the text. raimund@1751: g2.setPaint(textColor); raimund@1751: g2.setFont(font); felix@1039: TextUtilities.drawRotatedString(getText(), g2, anchorX, anchorY, felix@1039: getTextAnchor(), getRotationAngle(), getRotationAnchor()); felix@1039: felix@1039: // Add info that we have drawn this Annotation. felix@1039: addEntity(info, hotspot, rendererIndex, getToolTipText(), getURL()); felix@1039: } raimund@1738: raimund@1738: sascha@1754: public void applyTheme(ThemeAccess ta) { sascha@1754: lineWidth = ta.parseLineWidth(); sascha@1754: lineColor = ta.parseLineColorField(); sascha@1754: textColor = ta.parseTextColor(); sascha@1754: font = ta.parseTextFont(); sascha@1754: textOrientation = ta.parseTextOrientation(); sascha@1754: textBackground = ta.parseTextBackground(); sascha@1754: showBackground = ta.parseShowTextBackground(); raimund@1738: } felix@1039: } felix@1711: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :