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