001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2020, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025 * Other names may be trademarks of their respective owners.]
026 *
027 * ---------------------
028 * XYTextAnnotation.java
029 * ---------------------
030 * (C) Copyright 2002-2020, by Object Refinery Limited and Contributors.
031 *
032 * Original Author:  David Gilbert (for Object Refinery Limited);
033 * Contributor(s):   Peter Kolb (patch 2809117);
034 *
035 */
036
037package org.jfree.chart.annotations;
038
039import java.awt.BasicStroke;
040import java.awt.Color;
041import java.awt.Font;
042import java.awt.Graphics2D;
043import java.awt.Paint;
044import java.awt.Shape;
045import java.awt.Stroke;
046import java.awt.geom.Rectangle2D;
047import java.io.IOException;
048import java.io.ObjectInputStream;
049import java.io.ObjectOutputStream;
050import java.io.Serializable;
051
052import org.jfree.chart.HashUtils;
053import org.jfree.chart.axis.ValueAxis;
054import org.jfree.chart.event.AnnotationChangeEvent;
055import org.jfree.chart.plot.Plot;
056import org.jfree.chart.plot.PlotOrientation;
057import org.jfree.chart.plot.PlotRenderingInfo;
058import org.jfree.chart.plot.XYPlot;
059import org.jfree.chart.text.TextUtils;
060import org.jfree.chart.ui.RectangleEdge;
061import org.jfree.chart.ui.TextAnchor;
062import org.jfree.chart.util.PaintUtils;
063import org.jfree.chart.util.Args;
064import org.jfree.chart.util.PublicCloneable;
065import org.jfree.chart.util.SerialUtils;
066
067/**
068 * A text annotation that can be placed at a particular (x, y) location on an
069 * {@link XYPlot}.
070 */
071public class XYTextAnnotation extends AbstractXYAnnotation
072        implements Cloneable, PublicCloneable, Serializable {
073
074    /** For serialization. */
075    private static final long serialVersionUID = -2946063342782506328L;
076
077    /** The default font. */
078    public static final Font DEFAULT_FONT = new Font("SansSerif", Font.PLAIN,
079            10);
080
081    /** The default paint. */
082    public static final Paint DEFAULT_PAINT = Color.BLACK;
083
084    /** The default text anchor. */
085    public static final TextAnchor DEFAULT_TEXT_ANCHOR = TextAnchor.CENTER;
086
087    /** The default rotation anchor. */
088    public static final TextAnchor DEFAULT_ROTATION_ANCHOR = TextAnchor.CENTER;
089
090    /** The default rotation angle. */
091    public static final double DEFAULT_ROTATION_ANGLE = 0.0;
092
093    /** The text. */
094    private String text;
095
096    /** The font. */
097    private Font font;
098
099    /** The paint. */
100    private transient Paint paint;
101
102    /** The x-coordinate. */
103    private double x;
104
105    /** The y-coordinate. */
106    private double y;
107
108    /** The text anchor (to be aligned with (x, y)). */
109    private TextAnchor textAnchor;
110
111    /** The rotation anchor. */
112    private TextAnchor rotationAnchor;
113
114    /** The rotation angle. */
115    private double rotationAngle;
116
117    /**
118     * The background paint (possibly null).
119     *
120     * @since 1.0.13
121     */
122    private transient Paint backgroundPaint;
123
124    /**
125     * The flag that controls the visibility of the outline.
126     *
127     * @since 1.0.13
128     */
129    private boolean outlineVisible;
130
131    /**
132     * The outline paint (never null).
133     *
134     * @since 1.0.13
135     */
136    private transient Paint outlinePaint;
137
138    /**
139     * The outline stroke (never null).
140     *
141     * @since 1.0.13
142     */
143    private transient Stroke outlineStroke;
144
145    /**
146     * Creates a new annotation to be displayed at the given coordinates.  The
147     * coordinates are specified in data space (they will be converted to
148     * Java2D space for display).
149     *
150     * @param text  the text ({@code null} not permitted).
151     * @param x  the x-coordinate (in data space).
152     * @param y  the y-coordinate (in data space).
153     */
154    public XYTextAnnotation(String text, double x, double y) {
155        super();
156        Args.nullNotPermitted(text, "text");
157        this.text = text;
158        this.font = DEFAULT_FONT;
159        this.paint = DEFAULT_PAINT;
160        this.x = x;
161        this.y = y;
162        this.textAnchor = DEFAULT_TEXT_ANCHOR;
163        this.rotationAnchor = DEFAULT_ROTATION_ANCHOR;
164        this.rotationAngle = DEFAULT_ROTATION_ANGLE;
165
166        // by default the outline and background won't be visible
167        this.backgroundPaint = null;
168        this.outlineVisible = false;
169        this.outlinePaint = Color.BLACK;
170        this.outlineStroke = new BasicStroke(0.5f);
171    }
172
173    /**
174     * Returns the text for the annotation.
175     *
176     * @return The text (never {@code null}).
177     *
178     * @see #setText(String)
179     */
180    public String getText() {
181        return this.text;
182    }
183
184    /**
185     * Sets the text for the annotation.
186     *
187     * @param text  the text ({@code null} not permitted).
188     *
189     * @see #getText()
190     */
191    public void setText(String text) {
192        Args.nullNotPermitted(text, "text");
193        this.text = text;
194        fireAnnotationChanged();
195    }
196
197    /**
198     * Returns the font for the annotation.
199     *
200     * @return The font (never {@code null}).
201     *
202     * @see #setFont(Font)
203     */
204    public Font getFont() {
205        return this.font;
206    }
207
208    /**
209     * Sets the font for the annotation and sends an
210     * {@link AnnotationChangeEvent} to all registered listeners.
211     *
212     * @param font  the font ({@code null} not permitted).
213     *
214     * @see #getFont()
215     */
216    public void setFont(Font font) {
217        Args.nullNotPermitted(font, "font");
218        this.font = font;
219        fireAnnotationChanged();
220    }
221
222    /**
223     * Returns the paint for the annotation.
224     *
225     * @return The paint (never {@code null}).
226     *
227     * @see #setPaint(Paint)
228     */
229    public Paint getPaint() {
230        return this.paint;
231    }
232
233    /**
234     * Sets the paint for the annotation and sends an
235     * {@link AnnotationChangeEvent} to all registered listeners.
236     *
237     * @param paint  the paint ({@code null} not permitted).
238     *
239     * @see #getPaint()
240     */
241    public void setPaint(Paint paint) {
242        Args.nullNotPermitted(paint, "paint");
243        this.paint = paint;
244        fireAnnotationChanged();
245    }
246
247    /**
248     * Returns the text anchor.
249     *
250     * @return The text anchor (never {@code null}).
251     *
252     * @see #setTextAnchor(TextAnchor)
253     */
254    public TextAnchor getTextAnchor() {
255        return this.textAnchor;
256    }
257
258    /**
259     * Sets the text anchor (the point on the text bounding rectangle that is
260     * aligned to the (x, y) coordinate of the annotation) and sends an
261     * {@link AnnotationChangeEvent} to all registered listeners.
262     *
263     * @param anchor  the anchor point ({@code null} not permitted).
264     *
265     * @see #getTextAnchor()
266     */
267    public void setTextAnchor(TextAnchor anchor) {
268        Args.nullNotPermitted(anchor, "anchor");
269        this.textAnchor = anchor;
270        fireAnnotationChanged();
271    }
272
273    /**
274     * Returns the rotation anchor.
275     *
276     * @return The rotation anchor point (never {@code null}).
277     *
278     * @see #setRotationAnchor(TextAnchor)
279     */
280    public TextAnchor getRotationAnchor() {
281        return this.rotationAnchor;
282    }
283
284    /**
285     * Sets the rotation anchor point and sends an
286     * {@link AnnotationChangeEvent} to all registered listeners.
287     *
288     * @param anchor  the anchor ({@code null} not permitted).
289     *
290     * @see #getRotationAnchor()
291     */
292    public void setRotationAnchor(TextAnchor anchor) {
293        Args.nullNotPermitted(anchor, "anchor");
294        this.rotationAnchor = anchor;
295        fireAnnotationChanged();
296    }
297
298    /**
299     * Returns the rotation angle.
300     *
301     * @return The rotation angle.
302     *
303     * @see #setRotationAngle(double)
304     */
305    public double getRotationAngle() {
306        return this.rotationAngle;
307    }
308
309    /**
310     * Sets the rotation angle and sends an {@link AnnotationChangeEvent} to
311     * all registered listeners.  The angle is measured clockwise in radians.
312     *
313     * @param angle  the angle (in radians).
314     *
315     * @see #getRotationAngle()
316     */
317    public void setRotationAngle(double angle) {
318        this.rotationAngle = angle;
319        fireAnnotationChanged();
320    }
321
322    /**
323     * Returns the x coordinate for the text anchor point (measured against the
324     * domain axis).
325     *
326     * @return The x coordinate (in data space).
327     *
328     * @see #setX(double)
329     */
330    public double getX() {
331        return this.x;
332    }
333
334    /**
335     * Sets the x coordinate for the text anchor point (measured against the
336     * domain axis) and sends an {@link AnnotationChangeEvent} to all
337     * registered listeners.
338     *
339     * @param x  the x coordinate (in data space).
340     *
341     * @see #getX()
342     */
343    public void setX(double x) {
344        this.x = x;
345        fireAnnotationChanged();
346    }
347
348    /**
349     * Returns the y coordinate for the text anchor point (measured against the
350     * range axis).
351     *
352     * @return The y coordinate (in data space).
353     *
354     * @see #setY(double)
355     */
356    public double getY() {
357        return this.y;
358    }
359
360    /**
361     * Sets the y coordinate for the text anchor point (measured against the
362     * range axis) and sends an {@link AnnotationChangeEvent} to all registered
363     * listeners.
364     *
365     * @param y  the y coordinate.
366     *
367     * @see #getY()
368     */
369    public void setY(double y) {
370        this.y = y;
371        fireAnnotationChanged();
372    }
373
374    /**
375     * Returns the background paint for the annotation.
376     *
377     * @return The background paint (possibly {@code null}).
378     *
379     * @see #setBackgroundPaint(Paint)
380     *
381     * @since 1.0.13
382     */
383    public Paint getBackgroundPaint() {
384        return this.backgroundPaint;
385    }
386
387    /**
388     * Sets the background paint for the annotation and sends an
389     * {@link AnnotationChangeEvent} to all registered listeners.
390     *
391     * @param paint  the paint ({@code null} permitted).
392     *
393     * @see #getBackgroundPaint()
394     *
395     * @since 1.0.13
396     */
397    public void setBackgroundPaint(Paint paint) {
398        this.backgroundPaint = paint;
399        fireAnnotationChanged();
400    }
401
402    /**
403     * Returns the outline paint for the annotation.
404     *
405     * @return The outline paint (never {@code null}).
406     *
407     * @see #setOutlinePaint(Paint)
408     *
409     * @since 1.0.13
410     */
411    public Paint getOutlinePaint() {
412        return this.outlinePaint;
413    }
414
415    /**
416     * Sets the outline paint for the annotation and sends an
417     * {@link AnnotationChangeEvent} to all registered listeners.
418     *
419     * @param paint  the paint ({@code null} not permitted).
420     *
421     * @see #getOutlinePaint()
422     *
423     * @since 1.0.13
424     */
425    public void setOutlinePaint(Paint paint) {
426        Args.nullNotPermitted(paint, "paint");
427        this.outlinePaint = paint;
428        fireAnnotationChanged();
429    }
430
431    /**
432     * Returns the outline stroke for the annotation.
433     *
434     * @return The outline stroke (never {@code null}).
435     *
436     * @see #setOutlineStroke(Stroke)
437     *
438     * @since 1.0.13
439     */
440    public Stroke getOutlineStroke() {
441        return this.outlineStroke;
442    }
443
444    /**
445     * Sets the outline stroke for the annotation and sends an
446     * {@link AnnotationChangeEvent} to all registered listeners.
447     *
448     * @param stroke  the stroke ({@code null} not permitted).
449     *
450     * @see #getOutlineStroke()
451     *
452     * @since 1.0.13
453     */
454    public void setOutlineStroke(Stroke stroke) {
455        Args.nullNotPermitted(stroke, "stroke");
456        this.outlineStroke = stroke;
457        fireAnnotationChanged();
458    }
459
460    /**
461     * Returns the flag that controls whether or not the outline is drawn.
462     *
463     * @return A boolean.
464     *
465     * @since 1.0.13
466     */
467    public boolean isOutlineVisible() {
468        return this.outlineVisible;
469    }
470
471    /**
472     * Sets the flag that controls whether or not the outline is drawn and
473     * sends an {@link AnnotationChangeEvent} to all registered listeners.
474     *
475     * @param visible  the new flag value.
476     *
477     * @since 1.0.13
478     */
479    public void setOutlineVisible(boolean visible) {
480        this.outlineVisible = visible;
481        fireAnnotationChanged();
482    }
483
484    /**
485     * Draws the annotation.
486     *
487     * @param g2  the graphics device.
488     * @param plot  the plot.
489     * @param dataArea  the data area.
490     * @param domainAxis  the domain axis.
491     * @param rangeAxis  the range axis.
492     * @param rendererIndex  the renderer index.
493     * @param info  an optional info object that will be populated with
494     *              entity information.
495     */
496    @Override
497    public void draw(Graphics2D g2, XYPlot plot, Rectangle2D dataArea,
498                     ValueAxis domainAxis, ValueAxis rangeAxis,
499                     int rendererIndex, PlotRenderingInfo info) {
500
501        PlotOrientation orientation = plot.getOrientation();
502        RectangleEdge domainEdge = Plot.resolveDomainAxisLocation(
503                plot.getDomainAxisLocation(), orientation);
504        RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation(
505                plot.getRangeAxisLocation(), orientation);
506
507        float anchorX = (float) domainAxis.valueToJava2D(
508                this.x, dataArea, domainEdge);
509        float anchorY = (float) rangeAxis.valueToJava2D(
510                this.y, dataArea, rangeEdge);
511
512        if (orientation == PlotOrientation.HORIZONTAL) {
513            float tempAnchor = anchorX;
514            anchorX = anchorY;
515            anchorY = tempAnchor;
516        }
517
518        g2.setFont(getFont());
519        Shape hotspot = TextUtils.calculateRotatedStringBounds(
520                getText(), g2, anchorX, anchorY, getTextAnchor(),
521                getRotationAngle(), getRotationAnchor());
522        if (this.backgroundPaint != null) {
523            g2.setPaint(this.backgroundPaint);
524            g2.fill(hotspot);
525        }
526        g2.setPaint(getPaint());
527        TextUtils.drawRotatedString(getText(), g2, anchorX, anchorY,
528                getTextAnchor(), getRotationAngle(), getRotationAnchor());
529        if (this.outlineVisible) {
530            g2.setStroke(this.outlineStroke);
531            g2.setPaint(this.outlinePaint);
532            g2.draw(hotspot);
533        }
534
535        String toolTip = getToolTipText();
536        String url = getURL();
537        if (toolTip != null || url != null) {
538            addEntity(info, hotspot, rendererIndex, toolTip, url);
539        }
540
541    }
542
543    /**
544     * Tests this annotation for equality with an arbitrary object.
545     *
546     * @param obj  the object ({@code null} permitted).
547     *
548     * @return A boolean.
549     */
550    @Override
551    public boolean equals(Object obj) {
552        if (obj == this) {
553            return true;
554        }
555        if (!(obj instanceof XYTextAnnotation)) {
556            return false;
557        }
558        XYTextAnnotation that = (XYTextAnnotation) obj;
559        if (!this.text.equals(that.text)) {
560            return false;
561        }
562        if (this.x != that.x) {
563            return false;
564        }
565        if (this.y != that.y) {
566            return false;
567        }
568        if (!this.font.equals(that.font)) {
569            return false;
570        }
571        if (!PaintUtils.equal(this.paint, that.paint)) {
572            return false;
573        }
574        if (!this.rotationAnchor.equals(that.rotationAnchor)) {
575            return false;
576        }
577        if (this.rotationAngle != that.rotationAngle) {
578            return false;
579        }
580        if (!this.textAnchor.equals(that.textAnchor)) {
581            return false;
582        }
583        if (this.outlineVisible != that.outlineVisible) {
584            return false;
585        }
586        if (!PaintUtils.equal(this.backgroundPaint, that.backgroundPaint)) {
587            return false;
588        }
589        if (!PaintUtils.equal(this.outlinePaint, that.outlinePaint)) {
590            return false;
591        }
592        if (!(this.outlineStroke.equals(that.outlineStroke))) {
593            return false;
594        }
595        return super.equals(obj);
596    }
597
598    /**
599     * Returns a hash code for the object.
600     *
601     * @return A hash code.
602     */
603    @Override
604    public int hashCode() {
605        int result = 193;
606        result = 37 * result + this.text.hashCode();
607        result = 37 * result + this.font.hashCode();
608        result = 37 * result + HashUtils.hashCodeForPaint(this.paint);
609        long temp = Double.doubleToLongBits(this.x);
610        result = 37 * result + (int) (temp ^ (temp >>> 32));
611        temp = Double.doubleToLongBits(this.y);
612        result = 37 * result + (int) (temp ^ (temp >>> 32));
613        result = 37 * result + this.textAnchor.hashCode();
614        result = 37 * result + this.rotationAnchor.hashCode();
615        temp = Double.doubleToLongBits(this.rotationAngle);
616        result = 37 * result + (int) (temp ^ (temp >>> 32));
617        return result;
618    }
619
620    /**
621     * Returns a clone of the annotation.
622     *
623     * @return A clone.
624     *
625     * @throws CloneNotSupportedException  if the annotation can't be cloned.
626     */
627    @Override
628    public Object clone() throws CloneNotSupportedException {
629        return super.clone();
630    }
631
632    /**
633     * Provides serialization support.
634     *
635     * @param stream  the output stream.
636     *
637     * @throws IOException  if there is an I/O error.
638     */
639    private void writeObject(ObjectOutputStream stream) throws IOException {
640        stream.defaultWriteObject();
641        SerialUtils.writePaint(this.paint, stream);
642        SerialUtils.writePaint(this.backgroundPaint, stream);
643        SerialUtils.writePaint(this.outlinePaint, stream);
644        SerialUtils.writeStroke(this.outlineStroke, stream);
645    }
646
647    /**
648     * Provides serialization support.
649     *
650     * @param stream  the input stream.
651     *
652     * @throws IOException  if there is an I/O error.
653     * @throws ClassNotFoundException  if there is a classpath problem.
654     */
655    private void readObject(ObjectInputStream stream)
656        throws IOException, ClassNotFoundException {
657        stream.defaultReadObject();
658        this.paint = SerialUtils.readPaint(stream);
659        this.backgroundPaint = SerialUtils.readPaint(stream);
660        this.outlinePaint = SerialUtils.readPaint(stream);
661        this.outlineStroke = SerialUtils.readStroke(stream);
662    }
663
664}