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