001: /* ===========================================================
002: * JFreeChart : a free chart library for the Java(tm) platform
003: * ===========================================================
004: *
005: * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
006: *
007: * Project Info: http://www.jfree.org/jfreechart/index.html
008: *
009: * This library is free software; you can redistribute it and/or modify it
010: * under the terms of the GNU Lesser General Public License as published by
011: * the Free Software Foundation; either version 2.1 of the License, or
012: * (at your option) any later version.
013: *
014: * This library is distributed in the hope that it will be useful, but
015: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017: * License for more details.
018: *
019: * You should have received a copy of the GNU Lesser General Public
020: * License along with this library; if not, write to the Free Software
021: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
022: * USA.
023: *
024: * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025: * in the United States and other countries.]
026: *
027: * ------------------------------
028: * CategoryPointerAnnotation.java
029: * ------------------------------
030: * (C) Copyright 2006, 2007, by Object Refinery Limited.
031: *
032: * Original Author: David Gilbert (for Object Refinery Limited);
033: * Contributor(s): -;
034: *
035: * $Id: CategoryPointerAnnotation.java,v 1.1.2.3 2007/03/06 16:12:18 mungady Exp $
036: *
037: * Changes:
038: * --------
039: * 02-Oct-2006 : Version 1 (DG);
040: * 06-Mar-2007 : Implemented hashCode() (DG);
041: *
042: */
043:
044: package org.jfree.chart.annotations;
045:
046: import java.awt.BasicStroke;
047: import java.awt.Color;
048: import java.awt.Graphics2D;
049: import java.awt.Paint;
050: import java.awt.Stroke;
051: import java.awt.geom.GeneralPath;
052: import java.awt.geom.Line2D;
053: import java.awt.geom.Rectangle2D;
054: import java.io.IOException;
055: import java.io.ObjectInputStream;
056: import java.io.ObjectOutputStream;
057: import java.io.Serializable;
058:
059: import org.jfree.chart.HashUtilities;
060: import org.jfree.chart.axis.CategoryAxis;
061: import org.jfree.chart.axis.ValueAxis;
062: import org.jfree.chart.plot.CategoryPlot;
063: import org.jfree.chart.plot.Plot;
064: import org.jfree.chart.plot.PlotOrientation;
065: import org.jfree.data.category.CategoryDataset;
066: import org.jfree.io.SerialUtilities;
067: import org.jfree.text.TextUtilities;
068: import org.jfree.ui.RectangleEdge;
069: import org.jfree.util.ObjectUtilities;
070: import org.jfree.util.PublicCloneable;
071:
072: /**
073: * An arrow and label that can be placed on a {@link CategoryPlot}. The arrow
074: * is drawn at a user-definable angle so that it points towards the (category,
075: * value) location for the annotation.
076: * <p>
077: * The arrow length (and its offset from the (category, value) location) is
078: * controlled by the tip radius and the base radius attributes. Imagine two
079: * circles around the (category, value) coordinate: the inner circle defined by
080: * the tip radius, and the outer circle defined by the base radius. Now, draw
081: * the arrow starting at some point on the outer circle (the point is
082: * determined by the angle), with the arrow tip being drawn at a corresponding
083: * point on the inner circle.
084: *
085: * @since 1.0.3
086: */
087: public class CategoryPointerAnnotation extends CategoryTextAnnotation
088: implements Cloneable, PublicCloneable, Serializable {
089:
090: /** For serialization. */
091: private static final long serialVersionUID = -4031161445009858551L;
092:
093: /** The default tip radius (in Java2D units). */
094: public static final double DEFAULT_TIP_RADIUS = 10.0;
095:
096: /** The default base radius (in Java2D units). */
097: public static final double DEFAULT_BASE_RADIUS = 30.0;
098:
099: /** The default label offset (in Java2D units). */
100: public static final double DEFAULT_LABEL_OFFSET = 3.0;
101:
102: /** The default arrow length (in Java2D units). */
103: public static final double DEFAULT_ARROW_LENGTH = 5.0;
104:
105: /** The default arrow width (in Java2D units). */
106: public static final double DEFAULT_ARROW_WIDTH = 3.0;
107:
108: /** The angle of the arrow's line (in radians). */
109: private double angle;
110:
111: /**
112: * The radius from the (x, y) point to the tip of the arrow (in Java2D
113: * units).
114: */
115: private double tipRadius;
116:
117: /**
118: * The radius from the (x, y) point to the start of the arrow line (in
119: * Java2D units).
120: */
121: private double baseRadius;
122:
123: /** The length of the arrow head (in Java2D units). */
124: private double arrowLength;
125:
126: /** The arrow width (in Java2D units, per side). */
127: private double arrowWidth;
128:
129: /** The arrow stroke. */
130: private transient Stroke arrowStroke;
131:
132: /** The arrow paint. */
133: private transient Paint arrowPaint;
134:
135: /** The radius from the base point to the anchor point for the label. */
136: private double labelOffset;
137:
138: /**
139: * Creates a new label and arrow annotation.
140: *
141: * @param label the label (<code>null</code> permitted).
142: * @param key the category key.
143: * @param value the y-value (measured against the chart's range axis).
144: * @param angle the angle of the arrow's line (in radians).
145: */
146: public CategoryPointerAnnotation(String label, Comparable key,
147: double value, double angle) {
148:
149: super (label, key, value);
150: this .angle = angle;
151: this .tipRadius = DEFAULT_TIP_RADIUS;
152: this .baseRadius = DEFAULT_BASE_RADIUS;
153: this .arrowLength = DEFAULT_ARROW_LENGTH;
154: this .arrowWidth = DEFAULT_ARROW_WIDTH;
155: this .labelOffset = DEFAULT_LABEL_OFFSET;
156: this .arrowStroke = new BasicStroke(1.0f);
157: this .arrowPaint = Color.black;
158:
159: }
160:
161: /**
162: * Returns the angle of the arrow.
163: *
164: * @return The angle (in radians).
165: *
166: * @see #setAngle(double)
167: */
168: public double getAngle() {
169: return this .angle;
170: }
171:
172: /**
173: * Sets the angle of the arrow.
174: *
175: * @param angle the angle (in radians).
176: *
177: * @see #getAngle()
178: */
179: public void setAngle(double angle) {
180: this .angle = angle;
181: }
182:
183: /**
184: * Returns the tip radius.
185: *
186: * @return The tip radius (in Java2D units).
187: *
188: * @see #setTipRadius(double)
189: */
190: public double getTipRadius() {
191: return this .tipRadius;
192: }
193:
194: /**
195: * Sets the tip radius.
196: *
197: * @param radius the radius (in Java2D units).
198: *
199: * @see #getTipRadius()
200: */
201: public void setTipRadius(double radius) {
202: this .tipRadius = radius;
203: }
204:
205: /**
206: * Returns the base radius.
207: *
208: * @return The base radius (in Java2D units).
209: *
210: * @see #setBaseRadius(double)
211: */
212: public double getBaseRadius() {
213: return this .baseRadius;
214: }
215:
216: /**
217: * Sets the base radius.
218: *
219: * @param radius the radius (in Java2D units).
220: *
221: * @see #getBaseRadius()
222: */
223: public void setBaseRadius(double radius) {
224: this .baseRadius = radius;
225: }
226:
227: /**
228: * Returns the label offset.
229: *
230: * @return The label offset (in Java2D units).
231: *
232: * @see #setLabelOffset(double)
233: */
234: public double getLabelOffset() {
235: return this .labelOffset;
236: }
237:
238: /**
239: * Sets the label offset (from the arrow base, continuing in a straight
240: * line, in Java2D units).
241: *
242: * @param offset the offset (in Java2D units).
243: *
244: * @see #getLabelOffset()
245: */
246: public void setLabelOffset(double offset) {
247: this .labelOffset = offset;
248: }
249:
250: /**
251: * Returns the arrow length.
252: *
253: * @return The arrow length.
254: *
255: * @see #setArrowLength(double)
256: */
257: public double getArrowLength() {
258: return this .arrowLength;
259: }
260:
261: /**
262: * Sets the arrow length.
263: *
264: * @param length the length.
265: *
266: * @see #getArrowLength()
267: */
268: public void setArrowLength(double length) {
269: this .arrowLength = length;
270: }
271:
272: /**
273: * Returns the arrow width.
274: *
275: * @return The arrow width (in Java2D units).
276: *
277: * @see #setArrowWidth(double)
278: */
279: public double getArrowWidth() {
280: return this .arrowWidth;
281: }
282:
283: /**
284: * Sets the arrow width.
285: *
286: * @param width the width (in Java2D units).
287: *
288: * @see #getArrowWidth()
289: */
290: public void setArrowWidth(double width) {
291: this .arrowWidth = width;
292: }
293:
294: /**
295: * Returns the stroke used to draw the arrow line.
296: *
297: * @return The arrow stroke (never <code>null</code>).
298: *
299: * @see #setArrowStroke(Stroke)
300: */
301: public Stroke getArrowStroke() {
302: return this .arrowStroke;
303: }
304:
305: /**
306: * Sets the stroke used to draw the arrow line.
307: *
308: * @param stroke the stroke (<code>null</code> not permitted).
309: *
310: * @see #getArrowStroke()
311: */
312: public void setArrowStroke(Stroke stroke) {
313: if (stroke == null) {
314: throw new IllegalArgumentException(
315: "Null 'stroke' not permitted.");
316: }
317: this .arrowStroke = stroke;
318: }
319:
320: /**
321: * Returns the paint used for the arrow.
322: *
323: * @return The arrow paint (never <code>null</code>).
324: *
325: * @see #setArrowPaint(Paint)
326: */
327: public Paint getArrowPaint() {
328: return this .arrowPaint;
329: }
330:
331: /**
332: * Sets the paint used for the arrow.
333: *
334: * @param paint the arrow paint (<code>null</code> not permitted).
335: *
336: * @see #getArrowPaint()
337: */
338: public void setArrowPaint(Paint paint) {
339: if (paint == null) {
340: throw new IllegalArgumentException("Null 'paint' argument.");
341: }
342: this .arrowPaint = paint;
343: }
344:
345: /**
346: * Draws the annotation.
347: *
348: * @param g2 the graphics device.
349: * @param plot the plot.
350: * @param dataArea the data area.
351: * @param domainAxis the domain axis.
352: * @param rangeAxis the range axis.
353: */
354: public void draw(Graphics2D g2, CategoryPlot plot,
355: Rectangle2D dataArea, CategoryAxis domainAxis,
356: ValueAxis rangeAxis) {
357:
358: PlotOrientation orientation = plot.getOrientation();
359: RectangleEdge domainEdge = Plot.resolveDomainAxisLocation(plot
360: .getDomainAxisLocation(), orientation);
361: RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation(plot
362: .getRangeAxisLocation(), orientation);
363: CategoryDataset dataset = plot.getDataset();
364: int catIndex = dataset.getColumnIndex(getCategory());
365: int catCount = dataset.getColumnCount();
366: double j2DX = domainAxis.getCategoryMiddle(catIndex, catCount,
367: dataArea, domainEdge);
368: double j2DY = rangeAxis.valueToJava2D(getValue(), dataArea,
369: rangeEdge);
370: if (orientation == PlotOrientation.HORIZONTAL) {
371: double temp = j2DX;
372: j2DX = j2DY;
373: j2DY = temp;
374: }
375: double startX = j2DX + Math.cos(this .angle) * this .baseRadius;
376: double startY = j2DY + Math.sin(this .angle) * this .baseRadius;
377:
378: double endX = j2DX + Math.cos(this .angle) * this .tipRadius;
379: double endY = j2DY + Math.sin(this .angle) * this .tipRadius;
380:
381: double arrowBaseX = endX + Math.cos(this .angle)
382: * this .arrowLength;
383: double arrowBaseY = endY + Math.sin(this .angle)
384: * this .arrowLength;
385:
386: double arrowLeftX = arrowBaseX
387: + Math.cos(this .angle + Math.PI / 2.0)
388: * this .arrowWidth;
389: double arrowLeftY = arrowBaseY
390: + Math.sin(this .angle + Math.PI / 2.0)
391: * this .arrowWidth;
392:
393: double arrowRightX = arrowBaseX
394: - Math.cos(this .angle + Math.PI / 2.0)
395: * this .arrowWidth;
396: double arrowRightY = arrowBaseY
397: - Math.sin(this .angle + Math.PI / 2.0)
398: * this .arrowWidth;
399:
400: GeneralPath arrow = new GeneralPath();
401: arrow.moveTo((float) endX, (float) endY);
402: arrow.lineTo((float) arrowLeftX, (float) arrowLeftY);
403: arrow.lineTo((float) arrowRightX, (float) arrowRightY);
404: arrow.closePath();
405:
406: g2.setStroke(this .arrowStroke);
407: g2.setPaint(this .arrowPaint);
408: Line2D line = new Line2D.Double(startX, startY, endX, endY);
409: g2.draw(line);
410: g2.fill(arrow);
411:
412: // draw the label
413: g2.setFont(getFont());
414: g2.setPaint(getPaint());
415: double labelX = j2DX + Math.cos(this .angle)
416: * (this .baseRadius + this .labelOffset);
417: double labelY = j2DY + Math.sin(this .angle)
418: * (this .baseRadius + this .labelOffset);
419: /* Rectangle2D hotspot = */TextUtilities.drawAlignedString(
420: getText(), g2, (float) labelX, (float) labelY,
421: getTextAnchor());
422: // TODO: implement the entity for the annotation
423:
424: }
425:
426: /**
427: * Tests this annotation for equality with an arbitrary object.
428: *
429: * @param obj the object (<code>null</code> permitted).
430: *
431: * @return <code>true</code> or <code>false</code>.
432: */
433: public boolean equals(Object obj) {
434:
435: if (obj == this ) {
436: return true;
437: }
438: if (!(obj instanceof CategoryPointerAnnotation)) {
439: return false;
440: }
441: if (!super .equals(obj)) {
442: return false;
443: }
444: CategoryPointerAnnotation that = (CategoryPointerAnnotation) obj;
445: if (this .angle != that.angle) {
446: return false;
447: }
448: if (this .tipRadius != that.tipRadius) {
449: return false;
450: }
451: if (this .baseRadius != that.baseRadius) {
452: return false;
453: }
454: if (this .arrowLength != that.arrowLength) {
455: return false;
456: }
457: if (this .arrowWidth != that.arrowWidth) {
458: return false;
459: }
460: if (!this .arrowPaint.equals(that.arrowPaint)) {
461: return false;
462: }
463: if (!ObjectUtilities.equal(this .arrowStroke, that.arrowStroke)) {
464: return false;
465: }
466: if (this .labelOffset != that.labelOffset) {
467: return false;
468: }
469: return true;
470: }
471:
472: /**
473: * Returns a hash code for this instance.
474: *
475: * @return A hash code.
476: */
477: public int hashCode() {
478: int result = 193;
479: long temp = Double.doubleToLongBits(this .angle);
480: result = 37 * result + (int) (temp ^ (temp >>> 32));
481: temp = Double.doubleToLongBits(this .tipRadius);
482: result = 37 * result + (int) (temp ^ (temp >>> 32));
483: temp = Double.doubleToLongBits(this .baseRadius);
484: result = 37 * result + (int) (temp ^ (temp >>> 32));
485: temp = Double.doubleToLongBits(this .arrowLength);
486: result = 37 * result + (int) (temp ^ (temp >>> 32));
487: temp = Double.doubleToLongBits(this .arrowWidth);
488: result = 37 * result + (int) (temp ^ (temp >>> 32));
489: result = 37 * result
490: + HashUtilities.hashCodeForPaint(this .arrowPaint);
491: result = 37 * result + this .arrowStroke.hashCode();
492: temp = Double.doubleToLongBits(this .labelOffset);
493: result = 37 * result + (int) (temp ^ (temp >>> 32));
494: return result;
495: }
496:
497: /**
498: * Returns a clone of the annotation.
499: *
500: * @return A clone.
501: *
502: * @throws CloneNotSupportedException if the annotation can't be cloned.
503: */
504: public Object clone() throws CloneNotSupportedException {
505: return super .clone();
506: }
507:
508: /**
509: * Provides serialization support.
510: *
511: * @param stream the output stream.
512: *
513: * @throws IOException if there is an I/O error.
514: */
515: private void writeObject(ObjectOutputStream stream)
516: throws IOException {
517: stream.defaultWriteObject();
518: SerialUtilities.writePaint(this .arrowPaint, stream);
519: SerialUtilities.writeStroke(this .arrowStroke, stream);
520: }
521:
522: /**
523: * Provides serialization support.
524: *
525: * @param stream the input stream.
526: *
527: * @throws IOException if there is an I/O error.
528: * @throws ClassNotFoundException if there is a classpath problem.
529: */
530: private void readObject(ObjectInputStream stream)
531: throws IOException, ClassNotFoundException {
532: stream.defaultReadObject();
533: this.arrowPaint = SerialUtilities.readPaint(stream);
534: this.arrowStroke = SerialUtilities.readStroke(stream);
535: }
536:
537: }
|