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 * XYTextAnnotation.java
029 * ---------------------
030 * (C) Copyright 2002-2007, by Object Refinery Limited.
031 *
032 * Original Author: David Gilbert (for Object Refinery Limited);
033 * Contributor(s): -;
034 *
035 * $Id: XYTextAnnotation.java,v 1.5.2.4 2007/03/06 16:12:19 mungady Exp $
036 *
037 * Changes:
038 * --------
039 * 28-Aug-2002 : Version 1 (DG);
040 * 07-Nov-2002 : Fixed errors reported by Checkstyle (DG);
041 * 13-Jan-2003 : Reviewed Javadocs (DG);
042 * 26-Mar-2003 : Implemented Serializable (DG);
043 * 02-Jul-2003 : Added new text alignment and rotation options (DG);
044 * 19-Aug-2003 : Implemented Cloneable (DG);
045 * 17-Jan-2003 : Added fix for bug 878706, where the annotation is placed
046 * incorrectly for a plot with horizontal orientation (thanks to
047 * Ed Yu for the fix) (DG);
048 * 21-Jan-2004 : Update for renamed method in ValueAxis (DG);
049 * ------------- JFREECHART 1.0.x ---------------------------------------------
050 * 26-Jan-2006 : Fixed equals() method (bug 1415480) (DG);
051 * 06-Mar-2007 : Added argument checks, re-implemented hashCode() method (DG);
052 *
053 */
054
055 package org.jfree.chart.annotations;
056
057 import java.awt.Color;
058 import java.awt.Font;
059 import java.awt.Graphics2D;
060 import java.awt.Paint;
061 import java.awt.Shape;
062 import java.awt.geom.Rectangle2D;
063 import java.io.IOException;
064 import java.io.ObjectInputStream;
065 import java.io.ObjectOutputStream;
066 import java.io.Serializable;
067
068 import org.jfree.chart.HashUtilities;
069 import org.jfree.chart.axis.ValueAxis;
070 import org.jfree.chart.plot.Plot;
071 import org.jfree.chart.plot.PlotOrientation;
072 import org.jfree.chart.plot.PlotRenderingInfo;
073 import org.jfree.chart.plot.XYPlot;
074 import org.jfree.io.SerialUtilities;
075 import org.jfree.text.TextUtilities;
076 import org.jfree.ui.RectangleEdge;
077 import org.jfree.ui.TextAnchor;
078 import org.jfree.util.PaintUtilities;
079 import org.jfree.util.PublicCloneable;
080
081 /**
082 * A text annotation that can be placed at a particular (x, y) location on an
083 * {@link XYPlot}.
084 */
085 public class XYTextAnnotation extends AbstractXYAnnotation
086 implements Cloneable, PublicCloneable,
087 Serializable {
088
089 /** For serialization. */
090 private static final long serialVersionUID = -2946063342782506328L;
091
092 /** The default font. */
093 public static final Font DEFAULT_FONT = new Font("SansSerif", Font.PLAIN,
094 10);
095
096 /** The default paint. */
097 public static final Paint DEFAULT_PAINT = Color.black;
098
099 /** The default text anchor. */
100 public static final TextAnchor DEFAULT_TEXT_ANCHOR = TextAnchor.CENTER;
101
102 /** The default rotation anchor. */
103 public static final TextAnchor DEFAULT_ROTATION_ANCHOR = TextAnchor.CENTER;
104
105 /** The default rotation angle. */
106 public static final double DEFAULT_ROTATION_ANGLE = 0.0;
107
108 /** The text. */
109 private String text;
110
111 /** The font. */
112 private Font font;
113
114 /** The paint. */
115 private transient Paint paint;
116
117 /** The x-coordinate. */
118 private double x;
119
120 /** The y-coordinate. */
121 private double y;
122
123 /** The text anchor (to be aligned with (x, y)). */
124 private TextAnchor textAnchor;
125
126 /** The rotation anchor. */
127 private TextAnchor rotationAnchor;
128
129 /** The rotation angle. */
130 private double rotationAngle;
131
132 /**
133 * Creates a new annotation to be displayed at the given coordinates. The
134 * coordinates are specified in data space (they will be converted to
135 * Java2D space for display).
136 *
137 * @param text the text (<code>null</code> not permitted).
138 * @param x the x-coordinate (in data space).
139 * @param y the y-coordinate (in data space).
140 */
141 public XYTextAnnotation(String text, double x, double y) {
142 if (text == null) {
143 throw new IllegalArgumentException("Null 'text' argument.");
144 }
145 this.text = text;
146 this.font = DEFAULT_FONT;
147 this.paint = DEFAULT_PAINT;
148 this.x = x;
149 this.y = y;
150 this.textAnchor = DEFAULT_TEXT_ANCHOR;
151 this.rotationAnchor = DEFAULT_ROTATION_ANCHOR;
152 this.rotationAngle = DEFAULT_ROTATION_ANGLE;
153 }
154
155 /**
156 * Returns the text for the annotation.
157 *
158 * @return The text (never <code>null</code>).
159 *
160 * @see #setText(String)
161 */
162 public String getText() {
163 return this.text;
164 }
165
166 /**
167 * Sets the text for the annotation.
168 *
169 * @param text the text (<code>null</code> not permitted).
170 *
171 * @see #getText()
172 */
173 public void setText(String text) {
174 if (text == null) {
175 throw new IllegalArgumentException("Null 'text' argument.");
176 }
177 this.text = text;
178 }
179
180 /**
181 * Returns the font for the annotation.
182 *
183 * @return The font (never <code>null</code>).
184 *
185 * @see #setFont(Font)
186 */
187 public Font getFont() {
188 return this.font;
189 }
190
191 /**
192 * Sets the font for the annotation.
193 *
194 * @param font the font (<code>null</code> not permitted).
195 *
196 * @see #getFont()
197 */
198 public void setFont(Font font) {
199 if (font == null) {
200 throw new IllegalArgumentException("Null 'font' argument.");
201 }
202 this.font = font;
203 }
204
205 /**
206 * Returns the paint for the annotation.
207 *
208 * @return The paint (never <code>null</code>).
209 *
210 * @see #setPaint(Paint)
211 */
212 public Paint getPaint() {
213 return this.paint;
214 }
215
216 /**
217 * Sets the paint for the annotation.
218 *
219 * @param paint the paint (<code>null</code> not permitted).
220 *
221 * @see #getPaint()
222 */
223 public void setPaint(Paint paint) {
224 if (paint == null) {
225 throw new IllegalArgumentException("Null 'paint' argument.");
226 }
227 this.paint = paint;
228 }
229
230 /**
231 * Returns the text anchor.
232 *
233 * @return The text anchor (never <code>null</code>).
234 *
235 * @see #setTextAnchor(TextAnchor)
236 */
237 public TextAnchor getTextAnchor() {
238 return this.textAnchor;
239 }
240
241 /**
242 * Sets the text anchor (the point on the text bounding rectangle that is
243 * aligned to the (x, y) coordinate of the annotation).
244 *
245 * @param anchor the anchor point (<code>null</code> not permitted).
246 *
247 * @see #getTextAnchor()
248 */
249 public void setTextAnchor(TextAnchor anchor) {
250 if (anchor == null) {
251 throw new IllegalArgumentException("Null 'anchor' argument.");
252 }
253 this.textAnchor = anchor;
254 }
255
256 /**
257 * Returns the rotation anchor.
258 *
259 * @return The rotation anchor point (never <code>null</code>).
260 *
261 * @see #setRotationAnchor(TextAnchor)
262 */
263 public TextAnchor getRotationAnchor() {
264 return this.rotationAnchor;
265 }
266
267 /**
268 * Sets the rotation anchor point.
269 *
270 * @param anchor the anchor (<code>null</code> not permitted).
271 *
272 * @see #getRotationAnchor()
273 */
274 public void setRotationAnchor(TextAnchor anchor) {
275 if (anchor == null) {
276 throw new IllegalArgumentException("Null 'anchor' argument.");
277 }
278 this.rotationAnchor = anchor;
279 }
280
281 /**
282 * Returns the rotation angle.
283 *
284 * @return The rotation angle.
285 *
286 * @see #setRotationAngle(double)
287 */
288 public double getRotationAngle() {
289 return this.rotationAngle;
290 }
291
292 /**
293 * Sets the rotation angle. The angle is measured clockwise in radians.
294 *
295 * @param angle the angle (in radians).
296 *
297 * @see #getRotationAngle()
298 */
299 public void setRotationAngle(double angle) {
300 this.rotationAngle = angle;
301 }
302
303 /**
304 * Returns the x coordinate for the text anchor point (measured against the
305 * domain axis).
306 *
307 * @return The x coordinate (in data space).
308 *
309 * @see #setX(double)
310 */
311 public double getX() {
312 return this.x;
313 }
314
315 /**
316 * Sets the x coordinate for the text anchor point (measured against the
317 * domain axis).
318 *
319 * @param x the x coordinate (in data space).
320 *
321 * @see #getX()
322 */
323 public void setX(double x) {
324 this.x = x;
325 }
326
327 /**
328 * Returns the y coordinate for the text anchor point (measured against the
329 * range axis).
330 *
331 * @return The y coordinate (in data space).
332 *
333 * @see #setY(double)
334 */
335 public double getY() {
336 return this.y;
337 }
338
339 /**
340 * Sets the y coordinate for the text anchor point (measured against the
341 * range axis).
342 *
343 * @param y the y coordinate.
344 *
345 * @see #getY()
346 */
347 public void setY(double y) {
348 this.y = y;
349 }
350
351 /**
352 * Draws the annotation.
353 *
354 * @param g2 the graphics device.
355 * @param plot the plot.
356 * @param dataArea the data area.
357 * @param domainAxis the domain axis.
358 * @param rangeAxis the range axis.
359 * @param rendererIndex the renderer index.
360 * @param info an optional info object that will be populated with
361 * entity information.
362 */
363 public void draw(Graphics2D g2, XYPlot plot, Rectangle2D dataArea,
364 ValueAxis domainAxis, ValueAxis rangeAxis,
365 int rendererIndex,
366 PlotRenderingInfo info) {
367
368 PlotOrientation orientation = plot.getOrientation();
369 RectangleEdge domainEdge = Plot.resolveDomainAxisLocation(
370 plot.getDomainAxisLocation(), orientation);
371 RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation(
372 plot.getRangeAxisLocation(), orientation);
373
374 float anchorX = (float) domainAxis.valueToJava2D(
375 this.x, dataArea, domainEdge);
376 float anchorY = (float) rangeAxis.valueToJava2D(
377 this.y, dataArea, rangeEdge);
378
379 if (orientation == PlotOrientation.HORIZONTAL) {
380 float tempAnchor = anchorX;
381 anchorX = anchorY;
382 anchorY = tempAnchor;
383 }
384
385 g2.setFont(getFont());
386 g2.setPaint(getPaint());
387 TextUtilities.drawRotatedString(getText(), g2, anchorX, anchorY,
388 getTextAnchor(), getRotationAngle(), getRotationAnchor());
389 Shape hotspot = TextUtilities.calculateRotatedStringBounds(
390 getText(), g2, anchorX, anchorY, getTextAnchor(),
391 getRotationAngle(), getRotationAnchor());
392
393 String toolTip = getToolTipText();
394 String url = getURL();
395 if (toolTip != null || url != null) {
396 addEntity(info, hotspot, rendererIndex, toolTip, url);
397 }
398
399 }
400
401 /**
402 * Tests this annotation for equality with an arbitrary object.
403 *
404 * @param obj the object (<code>null</code> permitted).
405 *
406 * @return A boolean.
407 */
408 public boolean equals(Object obj) {
409 if (obj == this) {
410 return true;
411 }
412 if (!(obj instanceof XYTextAnnotation)) {
413 return false;
414 }
415 if (!super.equals(obj)) {
416 return false;
417 }
418 XYTextAnnotation that = (XYTextAnnotation) obj;
419 if (!this.text.equals(that.text)) {
420 return false;
421 }
422 if (this.x != that.x) {
423 return false;
424 }
425 if (this.y != that.y) {
426 return false;
427 }
428 if (!this.font.equals(that.font)) {
429 return false;
430 }
431 if (!PaintUtilities.equal(this.paint, that.paint)) {
432 return false;
433 }
434 if (!this.rotationAnchor.equals(that.rotationAnchor)) {
435 return false;
436 }
437 if (this.rotationAngle != that.rotationAngle) {
438 return false;
439 }
440 if (!this.textAnchor.equals(that.textAnchor)) {
441 return false;
442 }
443 return true;
444 }
445
446 /**
447 * Returns a hash code for the object.
448 *
449 * @return A hash code.
450 */
451 public int hashCode() {
452 int result = 193;
453 result = 37 * this.text.hashCode();
454 result = 37 * this.font.hashCode();
455 result = 37 * result + HashUtilities.hashCodeForPaint(this.paint);
456 long temp = Double.doubleToLongBits(this.x);
457 result = 37 * result + (int) (temp ^ (temp >>> 32));
458 temp = Double.doubleToLongBits(this.y);
459 result = 37 * result + (int) (temp ^ (temp >>> 32));
460 result = 37 * result + this.textAnchor.hashCode();
461 result = 37 * result + this.rotationAnchor.hashCode();
462 temp = Double.doubleToLongBits(this.rotationAngle);
463 result = 37 * result + (int) (temp ^ (temp >>> 32));
464 return result;
465 }
466
467 /**
468 * Returns a clone of the annotation.
469 *
470 * @return A clone.
471 *
472 * @throws CloneNotSupportedException if the annotation can't be cloned.
473 */
474 public Object clone() throws CloneNotSupportedException {
475 return super.clone();
476 }
477
478 /**
479 * Provides serialization support.
480 *
481 * @param stream the output stream.
482 *
483 * @throws IOException if there is an I/O error.
484 */
485 private void writeObject(ObjectOutputStream stream) throws IOException {
486 stream.defaultWriteObject();
487 SerialUtilities.writePaint(this.paint, stream);
488 }
489
490 /**
491 * Provides serialization support.
492 *
493 * @param stream the input stream.
494 *
495 * @throws IOException if there is an I/O error.
496 * @throws ClassNotFoundException if there is a classpath problem.
497 */
498 private void readObject(ObjectInputStream stream)
499 throws IOException, ClassNotFoundException {
500 stream.defaultReadObject();
501 this.paint = SerialUtilities.readPaint(stream);
502 }
503
504 }