001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2011, 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 * Plot.java
029 * ---------
030 * (C) Copyright 2000-2011, by Object Refinery Limited and Contributors.
031 *
032 * Original Author:  David Gilbert (for Object Refinery Limited);
033 * Contributor(s):   Sylvain Vieujot;
034 *                   Jeremy Bowman;
035 *                   Andreas Schneider;
036 *                   Gideon Krause;
037 *                   Nicolas Brodu;
038 *                   Michal Krause;
039 *                   Richard West, Advanced Micro Devices, Inc.;
040 *                   Peter Kolb - patches 2603321, 2809117;
041 *
042 * Changes
043 * -------
044 * 21-Jun-2001 : Removed redundant JFreeChart parameter from constructors (DG);
045 * 18-Sep-2001 : Updated header info and fixed DOS encoding problem (DG);
046 * 19-Oct-2001 : Moved series paint and stroke methods from JFreeChart
047 *               class (DG);
048 * 23-Oct-2001 : Created renderer for LinePlot class (DG);
049 * 07-Nov-2001 : Changed type names for ChartChangeEvent (DG);
050 *               Tidied up some Javadoc comments (DG);
051 * 13-Nov-2001 : Changes to allow for null axes on plots such as PiePlot (DG);
052 *               Added plot/axis compatibility checks (DG);
053 * 12-Dec-2001 : Changed constructors to protected, and removed unnecessary
054 *               'throws' clauses (DG);
055 * 13-Dec-2001 : Added tooltips (DG);
056 * 22-Jan-2002 : Added handleClick() method, as part of implementation for
057 *               crosshairs (DG);
058 *               Moved tooltips reference into ChartInfo class (DG);
059 * 23-Jan-2002 : Added test for null axes in chartChanged() method, thanks
060 *               to Barry Evans for the bug report (number 506979 on
061 *               SourceForge) (DG);
062 *               Added a zoom() method (DG);
063 * 05-Feb-2002 : Updated setBackgroundPaint(), setOutlineStroke() and
064 *               setOutlinePaint() to better handle null values, as suggested
065 *               by Sylvain Vieujot (DG);
066 * 06-Feb-2002 : Added background image, plus alpha transparency for background
067 *               and foreground (DG);
068 * 06-Mar-2002 : Added AxisConstants interface (DG);
069 * 26-Mar-2002 : Changed zoom method from empty to abstract (DG);
070 * 23-Apr-2002 : Moved dataset from JFreeChart class (DG);
071 * 11-May-2002 : Added ShapeFactory interface for getShape() methods,
072 *               contributed by Jeremy Bowman (DG);
073 * 28-May-2002 : Fixed bug in setSeriesPaint(int, Paint) for subplots (AS);
074 * 25-Jun-2002 : Removed redundant imports (DG);
075 * 30-Jul-2002 : Added 'no data' message for charts with null or empty
076 *               datasets (DG);
077 * 21-Aug-2002 : Added code to extend series array if necessary (refer to
078 *               SourceForge bug id 594547 for details) (DG);
079 * 17-Sep-2002 : Fixed bug in getSeriesOutlineStroke() method, reported by
080 *               Andreas Schroeder (DG);
081 * 23-Sep-2002 : Added getLegendItems() abstract method (DG);
082 * 24-Sep-2002 : Removed firstSeriesIndex, subplots now use their own paint
083 *               settings, there is a new mechanism for the legend to collect
084 *               the legend items (DG);
085 * 27-Sep-2002 : Added dataset group (DG);
086 * 14-Oct-2002 : Moved listener storage into EventListenerList.  Changed some
087 *               abstract methods to empty implementations (DG);
088 * 28-Oct-2002 : Added a getBackgroundImage() method (DG);
089 * 21-Nov-2002 : Added a plot index for identifying subplots in combined and
090 *               overlaid charts (DG);
091 * 22-Nov-2002 : Changed all attributes from 'protected' to 'private'.  Added
092 *               dataAreaRatio attribute from David M O'Donnell's code (DG);
093 * 09-Jan-2003 : Integrated fix for plot border contributed by Gideon
094 *               Krause (DG);
095 * 17-Jan-2003 : Moved to com.jrefinery.chart.plot (DG);
096 * 23-Jan-2003 : Removed one constructor (DG);
097 * 26-Mar-2003 : Implemented Serializable (DG);
098 * 14-Jul-2003 : Moved the dataset and secondaryDataset attributes to the
099 *               CategoryPlot and XYPlot classes (DG);
100 * 21-Jul-2003 : Moved DrawingSupplier from CategoryPlot and XYPlot up to this
101 *               class (DG);
102 * 20-Aug-2003 : Implemented Cloneable (DG);
103 * 11-Sep-2003 : Listeners and clone (NB);
104 * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG);
105 * 03-Dec-2003 : Modified draw method to accept anchor (DG);
106 * 12-Mar-2004 : Fixed clipping bug in drawNoDataMessage() method (DG);
107 * 07-Apr-2004 : Modified string bounds calculation (DG);
108 * 04-Nov-2004 : Added default shapes for legend items (DG);
109 * 25-Nov-2004 : Some changes to the clone() method implementation (DG);
110 * 23-Feb-2005 : Implemented new LegendItemSource interface (and also
111 *               PublicCloneable) (DG);
112 * 21-Apr-2005 : Replaced Insets with RectangleInsets (DG);
113 * 05-May-2005 : Removed unused draw() method (DG);
114 * 06-Jun-2005 : Fixed bugs in equals() method (DG);
115 * 01-Sep-2005 : Moved dataAreaRatio from here to ContourPlot (DG);
116 * ------------- JFREECHART 1.0.x ---------------------------------------------
117 * 30-Jun-2006 : Added background image alpha - see bug report 1514904 (DG);
118 * 05-Sep-2006 : Implemented the MarkerChangeListener interface (DG);
119 * 11-Jan-2007 : Added some argument checks, event notifications, and many
120 *               API doc updates (DG);
121 * 03-Apr-2007 : Made drawBackgroundImage() public (DG);
122 * 07-Jun-2007 : Added new fillBackground() method to handle GradientPaint
123 *               taking into account orientation (DG);
124 * 25-Mar-2008 : Added fireChangeEvent() method - see patch 1914411 (DG);
125 * 15-Aug-2008 : Added setDrawingSupplier() method with notify flag (DG);
126 * 13-Jan-2009 : Added notify flag (DG);
127 * 19-Mar-2009 : Added entity support - see patch 2603321 by Peter Kolb (DG);
128 * 24-Jun-2009 : Implemented AnnotationChangeListener (see patch 2809117 by
129 *               PK) (DG);
130 * 13-Jul-2009 : Plot background image should be clipped if necessary (DG);
131 *
132 */
133
134package org.jfree.chart.plot;
135
136import java.awt.AlphaComposite;
137import java.awt.BasicStroke;
138import java.awt.Color;
139import java.awt.Composite;
140import java.awt.Font;
141import java.awt.GradientPaint;
142import java.awt.Graphics2D;
143import java.awt.Image;
144import java.awt.Paint;
145import java.awt.Shape;
146import java.awt.Stroke;
147import java.awt.geom.Ellipse2D;
148import java.awt.geom.Point2D;
149import java.awt.geom.Rectangle2D;
150import java.io.IOException;
151import java.io.ObjectInputStream;
152import java.io.ObjectOutputStream;
153import java.io.Serializable;
154
155import javax.swing.event.EventListenerList;
156
157import org.jfree.chart.JFreeChart;
158import org.jfree.chart.LegendItemCollection;
159import org.jfree.chart.LegendItemSource;
160import org.jfree.chart.annotations.Annotation;
161import org.jfree.chart.axis.AxisLocation;
162import org.jfree.chart.entity.EntityCollection;
163import org.jfree.chart.entity.PlotEntity;
164import org.jfree.chart.event.AnnotationChangeEvent;
165import org.jfree.chart.event.AnnotationChangeListener;
166import org.jfree.chart.event.AxisChangeEvent;
167import org.jfree.chart.event.AxisChangeListener;
168import org.jfree.chart.event.ChartChangeEventType;
169import org.jfree.chart.event.MarkerChangeEvent;
170import org.jfree.chart.event.MarkerChangeListener;
171import org.jfree.chart.event.PlotChangeEvent;
172import org.jfree.chart.event.PlotChangeListener;
173import org.jfree.data.general.DatasetChangeEvent;
174import org.jfree.data.general.DatasetChangeListener;
175import org.jfree.data.general.DatasetGroup;
176import org.jfree.io.SerialUtilities;
177import org.jfree.text.G2TextMeasurer;
178import org.jfree.text.TextBlock;
179import org.jfree.text.TextBlockAnchor;
180import org.jfree.text.TextUtilities;
181import org.jfree.ui.Align;
182import org.jfree.ui.RectangleEdge;
183import org.jfree.ui.RectangleInsets;
184import org.jfree.util.ObjectUtilities;
185import org.jfree.util.PaintUtilities;
186import org.jfree.util.PublicCloneable;
187
188/**
189 * The base class for all plots in JFreeChart.  The {@link JFreeChart} class
190 * delegates the drawing of axes and data to the plot.  This base class
191 * provides facilities common to most plot types.
192 */
193public abstract class Plot implements AxisChangeListener,
194        DatasetChangeListener, AnnotationChangeListener, MarkerChangeListener,
195        LegendItemSource, PublicCloneable, Cloneable, Serializable {
196
197    /** For serialization. */
198    private static final long serialVersionUID = -8831571430103671324L;
199
200    /** Useful constant representing zero. */
201    public static final Number ZERO = new Integer(0);
202
203    /** The default insets. */
204    public static final RectangleInsets DEFAULT_INSETS
205            = new RectangleInsets(4.0, 8.0, 4.0, 8.0);
206
207    /** The default outline stroke. */
208    public static final Stroke DEFAULT_OUTLINE_STROKE = new BasicStroke(0.5f,
209            BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
210
211    /** The default outline color. */
212    public static final Paint DEFAULT_OUTLINE_PAINT = Color.gray;
213
214    /** The default foreground alpha transparency. */
215    public static final float DEFAULT_FOREGROUND_ALPHA = 1.0f;
216
217    /** The default background alpha transparency. */
218    public static final float DEFAULT_BACKGROUND_ALPHA = 1.0f;
219
220    /** The default background color. */
221    public static final Paint DEFAULT_BACKGROUND_PAINT = Color.white;
222
223    /** The minimum width at which the plot should be drawn. */
224    public static final int MINIMUM_WIDTH_TO_DRAW = 10;
225
226    /** The minimum height at which the plot should be drawn. */
227    public static final int MINIMUM_HEIGHT_TO_DRAW = 10;
228
229    /** A default box shape for legend items. */
230    public static final Shape DEFAULT_LEGEND_ITEM_BOX
231            = new Rectangle2D.Double(-4.0, -4.0, 8.0, 8.0);
232
233    /** A default circle shape for legend items. */
234    public static final Shape DEFAULT_LEGEND_ITEM_CIRCLE
235            = new Ellipse2D.Double(-4.0, -4.0, 8.0, 8.0);
236
237    /** The parent plot (<code>null</code> if this is the root plot). */
238    private Plot parent;
239
240    /** The dataset group (to be used for thread synchronisation). */
241    private DatasetGroup datasetGroup;
242
243    /** The message to display if no data is available. */
244    private String noDataMessage;
245
246    /** The font used to display the 'no data' message. */
247    private Font noDataMessageFont;
248
249    /** The paint used to draw the 'no data' message. */
250    private transient Paint noDataMessagePaint;
251
252    /** Amount of blank space around the plot area. */
253    private RectangleInsets insets;
254
255    /**
256     * A flag that controls whether or not the plot outline is drawn.
257     *
258     * @since 1.0.6
259     */
260    private boolean outlineVisible;
261
262    /** The Stroke used to draw an outline around the plot. */
263    private transient Stroke outlineStroke;
264
265    /** The Paint used to draw an outline around the plot. */
266    private transient Paint outlinePaint;
267
268    /** An optional color used to fill the plot background. */
269    private transient Paint backgroundPaint;
270
271    /** An optional image for the plot background. */
272    private transient Image backgroundImage;  // not currently serialized
273
274    /** The alignment for the background image. */
275    private int backgroundImageAlignment = Align.FIT;
276
277    /** The alpha value used to draw the background image. */
278    private float backgroundImageAlpha = 0.5f;
279
280    /** The alpha-transparency for the plot. */
281    private float foregroundAlpha;
282
283    /** The alpha transparency for the background paint. */
284    private float backgroundAlpha;
285
286    /** The drawing supplier. */
287    private DrawingSupplier drawingSupplier;
288
289    /** Storage for registered change listeners. */
290    private transient EventListenerList listenerList;
291
292    /**
293     * A flag that controls whether or not the plot will notify listeners
294     * of changes (defaults to true, but sometimes it is useful to disable
295     * this).
296     *
297     * @since 1.0.13
298     */
299    private boolean notify;
300
301    /**
302     * Creates a new plot.
303     */
304    protected Plot() {
305
306        this.parent = null;
307        this.insets = DEFAULT_INSETS;
308        this.backgroundPaint = DEFAULT_BACKGROUND_PAINT;
309        this.backgroundAlpha = DEFAULT_BACKGROUND_ALPHA;
310        this.backgroundImage = null;
311        this.outlineVisible = true;
312        this.outlineStroke = DEFAULT_OUTLINE_STROKE;
313        this.outlinePaint = DEFAULT_OUTLINE_PAINT;
314        this.foregroundAlpha = DEFAULT_FOREGROUND_ALPHA;
315
316        this.noDataMessage = null;
317        this.noDataMessageFont = new Font("SansSerif", Font.PLAIN, 12);
318        this.noDataMessagePaint = Color.black;
319
320        this.drawingSupplier = new DefaultDrawingSupplier();
321
322        this.notify = true;
323        this.listenerList = new EventListenerList();
324
325    }
326
327    /**
328     * Returns the dataset group for the plot (not currently used).
329     *
330     * @return The dataset group.
331     *
332     * @see #setDatasetGroup(DatasetGroup)
333     */
334    public DatasetGroup getDatasetGroup() {
335        return this.datasetGroup;
336    }
337
338    /**
339     * Sets the dataset group (not currently used).
340     *
341     * @param group  the dataset group (<code>null</code> permitted).
342     *
343     * @see #getDatasetGroup()
344     */
345    protected void setDatasetGroup(DatasetGroup group) {
346        this.datasetGroup = group;
347    }
348
349    /**
350     * Returns the string that is displayed when the dataset is empty or
351     * <code>null</code>.
352     *
353     * @return The 'no data' message (<code>null</code> possible).
354     *
355     * @see #setNoDataMessage(String)
356     * @see #getNoDataMessageFont()
357     * @see #getNoDataMessagePaint()
358     */
359    public String getNoDataMessage() {
360        return this.noDataMessage;
361    }
362
363    /**
364     * Sets the message that is displayed when the dataset is empty or
365     * <code>null</code>, and sends a {@link PlotChangeEvent} to all registered
366     * listeners.
367     *
368     * @param message  the message (<code>null</code> permitted).
369     *
370     * @see #getNoDataMessage()
371     */
372    public void setNoDataMessage(String message) {
373        this.noDataMessage = message;
374        fireChangeEvent();
375    }
376
377    /**
378     * Returns the font used to display the 'no data' message.
379     *
380     * @return The font (never <code>null</code>).
381     *
382     * @see #setNoDataMessageFont(Font)
383     * @see #getNoDataMessage()
384     */
385    public Font getNoDataMessageFont() {
386        return this.noDataMessageFont;
387    }
388
389    /**
390     * Sets the font used to display the 'no data' message and sends a
391     * {@link PlotChangeEvent} to all registered listeners.
392     *
393     * @param font  the font (<code>null</code> not permitted).
394     *
395     * @see #getNoDataMessageFont()
396     */
397    public void setNoDataMessageFont(Font font) {
398        if (font == null) {
399            throw new IllegalArgumentException("Null 'font' argument.");
400        }
401        this.noDataMessageFont = font;
402        fireChangeEvent();
403    }
404
405    /**
406     * Returns the paint used to display the 'no data' message.
407     *
408     * @return The paint (never <code>null</code>).
409     *
410     * @see #setNoDataMessagePaint(Paint)
411     * @see #getNoDataMessage()
412     */
413    public Paint getNoDataMessagePaint() {
414        return this.noDataMessagePaint;
415    }
416
417    /**
418     * Sets the paint used to display the 'no data' message and sends a
419     * {@link PlotChangeEvent} to all registered listeners.
420     *
421     * @param paint  the paint (<code>null</code> not permitted).
422     *
423     * @see #getNoDataMessagePaint()
424     */
425    public void setNoDataMessagePaint(Paint paint) {
426        if (paint == null) {
427            throw new IllegalArgumentException("Null 'paint' argument.");
428        }
429        this.noDataMessagePaint = paint;
430        fireChangeEvent();
431    }
432
433    /**
434     * Returns a short string describing the plot type.
435     * <P>
436     * Note: this gets used in the chart property editing user interface,
437     * but there needs to be a better mechanism for identifying the plot type.
438     *
439     * @return A short string describing the plot type (never
440     *     <code>null</code>).
441     */
442    public abstract String getPlotType();
443
444    /**
445     * Returns the parent plot (or <code>null</code> if this plot is not part
446     * of a combined plot).
447     *
448     * @return The parent plot.
449     *
450     * @see #setParent(Plot)
451     * @see #getRootPlot()
452     */
453    public Plot getParent() {
454        return this.parent;
455    }
456
457    /**
458     * Sets the parent plot.  This method is intended for internal use, you
459     * shouldn't need to call it directly.
460     *
461     * @param parent  the parent plot (<code>null</code> permitted).
462     *
463     * @see #getParent()
464     */
465    public void setParent(Plot parent) {
466        this.parent = parent;
467    }
468
469    /**
470     * Returns the root plot.
471     *
472     * @return The root plot.
473     *
474     * @see #getParent()
475     */
476    public Plot getRootPlot() {
477
478        Plot p = getParent();
479        if (p == null) {
480            return this;
481        }
482        return p.getRootPlot();
483
484    }
485
486    /**
487     * Returns <code>true</code> if this plot is part of a combined plot
488     * structure (that is, {@link #getParent()} returns a non-<code>null</code>
489     * value), and <code>false</code> otherwise.
490     *
491     * @return <code>true</code> if this plot is part of a combined plot
492     *         structure.
493     *
494     * @see #getParent()
495     */
496    public boolean isSubplot() {
497        return (getParent() != null);
498    }
499
500    /**
501     * Returns the insets for the plot area.
502     *
503     * @return The insets (never <code>null</code>).
504     *
505     * @see #setInsets(RectangleInsets)
506     */
507    public RectangleInsets getInsets() {
508        return this.insets;
509    }
510
511    /**
512     * Sets the insets for the plot and sends a {@link PlotChangeEvent} to
513     * all registered listeners.
514     *
515     * @param insets  the new insets (<code>null</code> not permitted).
516     *
517     * @see #getInsets()
518     * @see #setInsets(RectangleInsets, boolean)
519     */
520    public void setInsets(RectangleInsets insets) {
521        setInsets(insets, true);
522    }
523
524    /**
525     * Sets the insets for the plot and, if requested,  and sends a
526     * {@link PlotChangeEvent} to all registered listeners.
527     *
528     * @param insets  the new insets (<code>null</code> not permitted).
529     * @param notify  a flag that controls whether the registered listeners are
530     *                notified.
531     *
532     * @see #getInsets()
533     * @see #setInsets(RectangleInsets)
534     */
535    public void setInsets(RectangleInsets insets, boolean notify) {
536        if (insets == null) {
537            throw new IllegalArgumentException("Null 'insets' argument.");
538        }
539        if (!this.insets.equals(insets)) {
540            this.insets = insets;
541            if (notify) {
542                fireChangeEvent();
543            }
544        }
545
546    }
547
548    /**
549     * Returns the background color of the plot area.
550     *
551     * @return The paint (possibly <code>null</code>).
552     *
553     * @see #setBackgroundPaint(Paint)
554     */
555    public Paint getBackgroundPaint() {
556        return this.backgroundPaint;
557    }
558
559    /**
560     * Sets the background color of the plot area and sends a
561     * {@link PlotChangeEvent} to all registered listeners.
562     *
563     * @param paint  the paint (<code>null</code> permitted).
564     *
565     * @see #getBackgroundPaint()
566     */
567    public void setBackgroundPaint(Paint paint) {
568
569        if (paint == null) {
570            if (this.backgroundPaint != null) {
571                this.backgroundPaint = null;
572                fireChangeEvent();
573            }
574        }
575        else {
576            if (this.backgroundPaint != null) {
577                if (this.backgroundPaint.equals(paint)) {
578                    return;  // nothing to do
579                }
580            }
581            this.backgroundPaint = paint;
582            fireChangeEvent();
583        }
584
585    }
586
587    /**
588     * Returns the alpha transparency of the plot area background.
589     *
590     * @return The alpha transparency.
591     *
592     * @see #setBackgroundAlpha(float)
593     */
594    public float getBackgroundAlpha() {
595        return this.backgroundAlpha;
596    }
597
598    /**
599     * Sets the alpha transparency of the plot area background, and notifies
600     * registered listeners that the plot has been modified.
601     *
602     * @param alpha the new alpha value (in the range 0.0f to 1.0f).
603     *
604     * @see #getBackgroundAlpha()
605     */
606    public void setBackgroundAlpha(float alpha) {
607        if (this.backgroundAlpha != alpha) {
608            this.backgroundAlpha = alpha;
609            fireChangeEvent();
610        }
611    }
612
613    /**
614     * Returns the drawing supplier for the plot.
615     *
616     * @return The drawing supplier (possibly <code>null</code>).
617     *
618     * @see #setDrawingSupplier(DrawingSupplier)
619     */
620    public DrawingSupplier getDrawingSupplier() {
621        DrawingSupplier result = null;
622        Plot p = getParent();
623        if (p != null) {
624            result = p.getDrawingSupplier();
625        }
626        else {
627            result = this.drawingSupplier;
628        }
629        return result;
630    }
631
632    /**
633     * Sets the drawing supplier for the plot and sends a
634     * {@link PlotChangeEvent} to all registered listeners.  The drawing
635     * supplier is responsible for supplying a limitless (possibly repeating)
636     * sequence of <code>Paint</code>, <code>Stroke</code> and
637     * <code>Shape</code> objects that the plot's renderer(s) can use to
638     * populate its (their) tables.
639     *
640     * @param supplier  the new supplier.
641     *
642     * @see #getDrawingSupplier()
643     */
644    public void setDrawingSupplier(DrawingSupplier supplier) {
645        this.drawingSupplier = supplier;
646        fireChangeEvent();
647    }
648
649    /**
650     * Sets the drawing supplier for the plot and, if requested, sends a
651     * {@link PlotChangeEvent} to all registered listeners.  The drawing
652     * supplier is responsible for supplying a limitless (possibly repeating)
653     * sequence of <code>Paint</code>, <code>Stroke</code> and
654     * <code>Shape</code> objects that the plot's renderer(s) can use to
655     * populate its (their) tables.
656     *
657     * @param supplier  the new supplier.
658     * @param notify  notify listeners?
659     *
660     * @see #getDrawingSupplier()
661     *
662     * @since 1.0.11
663     */
664    public void setDrawingSupplier(DrawingSupplier supplier, boolean notify) {
665        this.drawingSupplier = supplier;
666        if (notify) {
667            fireChangeEvent();
668        }
669    }
670
671    /**
672     * Returns the background image that is used to fill the plot's background
673     * area.
674     *
675     * @return The image (possibly <code>null</code>).
676     *
677     * @see #setBackgroundImage(Image)
678     */
679    public Image getBackgroundImage() {
680        return this.backgroundImage;
681    }
682
683    /**
684     * Sets the background image for the plot and sends a
685     * {@link PlotChangeEvent} to all registered listeners.
686     *
687     * @param image  the image (<code>null</code> permitted).
688     *
689     * @see #getBackgroundImage()
690     */
691    public void setBackgroundImage(Image image) {
692        this.backgroundImage = image;
693        fireChangeEvent();
694    }
695
696    /**
697     * Returns the background image alignment. Alignment constants are defined
698     * in the <code>org.jfree.ui.Align</code> class in the JCommon class
699     * library.
700     *
701     * @return The alignment.
702     *
703     * @see #setBackgroundImageAlignment(int)
704     */
705    public int getBackgroundImageAlignment() {
706        return this.backgroundImageAlignment;
707    }
708
709    /**
710     * Sets the alignment for the background image and sends a
711     * {@link PlotChangeEvent} to all registered listeners.  Alignment options
712     * are defined by the {@link org.jfree.ui.Align} class in the JCommon
713     * class library.
714     *
715     * @param alignment  the alignment.
716     *
717     * @see #getBackgroundImageAlignment()
718     */
719    public void setBackgroundImageAlignment(int alignment) {
720        if (this.backgroundImageAlignment != alignment) {
721            this.backgroundImageAlignment = alignment;
722            fireChangeEvent();
723        }
724    }
725
726    /**
727     * Returns the alpha transparency used to draw the background image.  This
728     * is a value in the range 0.0f to 1.0f, where 0.0f is fully transparent
729     * and 1.0f is fully opaque.
730     *
731     * @return The alpha transparency.
732     *
733     * @see #setBackgroundImageAlpha(float)
734     */
735    public float getBackgroundImageAlpha() {
736        return this.backgroundImageAlpha;
737    }
738
739    /**
740     * Sets the alpha transparency used when drawing the background image.
741     *
742     * @param alpha  the alpha transparency (in the range 0.0f to 1.0f, where
743     *     0.0f is fully transparent, and 1.0f is fully opaque).
744     *
745     * @throws IllegalArgumentException if <code>alpha</code> is not within
746     *     the specified range.
747     *
748     * @see #getBackgroundImageAlpha()
749     */
750    public void setBackgroundImageAlpha(float alpha) {
751        if (alpha < 0.0f || alpha > 1.0f)
752            throw new IllegalArgumentException(
753                    "The 'alpha' value must be in the range 0.0f to 1.0f.");
754        if (this.backgroundImageAlpha != alpha) {
755            this.backgroundImageAlpha = alpha;
756            fireChangeEvent();
757        }
758    }
759
760    /**
761     * Returns the flag that controls whether or not the plot outline is
762     * drawn.  The default value is <code>true</code>.  Note that for
763     * historical reasons, the plot's outline paint and stroke can take on
764     * <code>null</code> values, in which case the outline will not be drawn
765     * even if this flag is set to <code>true</code>.
766     *
767     * @return The outline visibility flag.
768     *
769     * @since 1.0.6
770     *
771     * @see #setOutlineVisible(boolean)
772     */
773    public boolean isOutlineVisible() {
774        return this.outlineVisible;
775    }
776
777    /**
778     * Sets the flag that controls whether or not the plot's outline is
779     * drawn, and sends a {@link PlotChangeEvent} to all registered listeners.
780     *
781     * @param visible  the new flag value.
782     *
783     * @since 1.0.6
784     *
785     * @see #isOutlineVisible()
786     */
787    public void setOutlineVisible(boolean visible) {
788        this.outlineVisible = visible;
789        fireChangeEvent();
790    }
791
792    /**
793     * Returns the stroke used to outline the plot area.
794     *
795     * @return The stroke (possibly <code>null</code>).
796     *
797     * @see #setOutlineStroke(Stroke)
798     */
799    public Stroke getOutlineStroke() {
800        return this.outlineStroke;
801    }
802
803    /**
804     * Sets the stroke used to outline the plot area and sends a
805     * {@link PlotChangeEvent} to all registered listeners. If you set this
806     * attribute to <code>null</code>, no outline will be drawn.
807     *
808     * @param stroke  the stroke (<code>null</code> permitted).
809     *
810     * @see #getOutlineStroke()
811     */
812    public void setOutlineStroke(Stroke stroke) {
813        if (stroke == null) {
814            if (this.outlineStroke != null) {
815                this.outlineStroke = null;
816                fireChangeEvent();
817            }
818        }
819        else {
820            if (this.outlineStroke != null) {
821                if (this.outlineStroke.equals(stroke)) {
822                    return;  // nothing to do
823                }
824            }
825            this.outlineStroke = stroke;
826            fireChangeEvent();
827        }
828    }
829
830    /**
831     * Returns the color used to draw the outline of the plot area.
832     *
833     * @return The color (possibly <code>null</code>).
834     *
835     * @see #setOutlinePaint(Paint)
836     */
837    public Paint getOutlinePaint() {
838        return this.outlinePaint;
839    }
840
841    /**
842     * Sets the paint used to draw the outline of the plot area and sends a
843     * {@link PlotChangeEvent} to all registered listeners.  If you set this
844     * attribute to <code>null</code>, no outline will be drawn.
845     *
846     * @param paint  the paint (<code>null</code> permitted).
847     *
848     * @see #getOutlinePaint()
849     */
850    public void setOutlinePaint(Paint paint) {
851        if (paint == null) {
852            if (this.outlinePaint != null) {
853                this.outlinePaint = null;
854                fireChangeEvent();
855            }
856        }
857        else {
858            if (this.outlinePaint != null) {
859                if (this.outlinePaint.equals(paint)) {
860                    return;  // nothing to do
861                }
862            }
863            this.outlinePaint = paint;
864            fireChangeEvent();
865        }
866    }
867
868    /**
869     * Returns the alpha-transparency for the plot foreground.
870     *
871     * @return The alpha-transparency.
872     *
873     * @see #setForegroundAlpha(float)
874     */
875    public float getForegroundAlpha() {
876        return this.foregroundAlpha;
877    }
878
879    /**
880     * Sets the alpha-transparency for the plot and sends a
881     * {@link PlotChangeEvent} to all registered listeners.
882     *
883     * @param alpha  the new alpha transparency.
884     *
885     * @see #getForegroundAlpha()
886     */
887    public void setForegroundAlpha(float alpha) {
888        if (this.foregroundAlpha != alpha) {
889            this.foregroundAlpha = alpha;
890            fireChangeEvent();
891        }
892    }
893
894    /**
895     * Returns the legend items for the plot.  By default, this method returns
896     * <code>null</code>.  Subclasses should override to return a
897     * {@link LegendItemCollection}.
898     *
899     * @return The legend items for the plot (possibly <code>null</code>).
900     */
901    public LegendItemCollection getLegendItems() {
902        return null;
903    }
904
905    /**
906     * Returns a flag that controls whether or not change events are sent to
907     * registered listeners.
908     *
909     * @return A boolean.
910     *
911     * @see #setNotify(boolean)
912     *
913     * @since 1.0.13
914     */
915    public boolean isNotify() {
916        return this.notify;
917    }
918
919    /**
920     * Sets a flag that controls whether or not listeners receive
921     * {@link PlotChangeEvent} notifications.
922     *
923     * @param notify  a boolean.
924     *
925     * @see #isNotify()
926     *
927     * @since 1.0.13
928     */
929    public void setNotify(boolean notify) {
930        this.notify = notify;
931        // if the flag is being set to true, there may be queued up changes...
932        if (notify) {
933            notifyListeners(new PlotChangeEvent(this));
934        }
935    }
936
937    /**
938     * Registers an object for notification of changes to the plot.
939     *
940     * @param listener  the object to be registered.
941     *
942     * @see #removeChangeListener(PlotChangeListener)
943     */
944    public void addChangeListener(PlotChangeListener listener) {
945        this.listenerList.add(PlotChangeListener.class, listener);
946    }
947
948    /**
949     * Unregisters an object for notification of changes to the plot.
950     *
951     * @param listener  the object to be unregistered.
952     *
953     * @see #addChangeListener(PlotChangeListener)
954     */
955    public void removeChangeListener(PlotChangeListener listener) {
956        this.listenerList.remove(PlotChangeListener.class, listener);
957    }
958
959    /**
960     * Notifies all registered listeners that the plot has been modified.
961     *
962     * @param event  information about the change event.
963     */
964    public void notifyListeners(PlotChangeEvent event) {
965        // if the 'notify' flag has been switched to false, we don't notify
966        // the listeners
967        if (!this.notify) {
968            return;
969        }
970        Object[] listeners = this.listenerList.getListenerList();
971        for (int i = listeners.length - 2; i >= 0; i -= 2) {
972            if (listeners[i] == PlotChangeListener.class) {
973                ((PlotChangeListener) listeners[i + 1]).plotChanged(event);
974            }
975        }
976    }
977
978    /**
979     * Sends a {@link PlotChangeEvent} to all registered listeners.
980     *
981     * @since 1.0.10
982     */
983    protected void fireChangeEvent() {
984        notifyListeners(new PlotChangeEvent(this));
985    }
986
987    /**
988     * Draws the plot within the specified area.  The anchor is a point on the
989     * chart that is specified externally (for instance, it may be the last
990     * point of the last mouse click performed by the user) - plots can use or
991     * ignore this value as they see fit.
992     * <br><br>
993     * Subclasses need to provide an implementation of this method, obviously.
994     *
995     * @param g2  the graphics device.
996     * @param area  the plot area.
997     * @param anchor  the anchor point (<code>null</code> permitted).
998     * @param parentState  the parent state (if any).
999     * @param info  carries back plot rendering info.
1000     */
1001    public abstract void draw(Graphics2D g2,
1002                              Rectangle2D area,
1003                              Point2D anchor,
1004                              PlotState parentState,
1005                              PlotRenderingInfo info);
1006
1007    /**
1008     * Draws the plot background (the background color and/or image).
1009     * <P>
1010     * This method will be called during the chart drawing process and is
1011     * declared public so that it can be accessed by the renderers used by
1012     * certain subclasses.  You shouldn't need to call this method directly.
1013     *
1014     * @param g2  the graphics device.
1015     * @param area  the area within which the plot should be drawn.
1016     */
1017    public void drawBackground(Graphics2D g2, Rectangle2D area) {
1018        // some subclasses override this method completely, so don't put
1019        // anything here that *must* be done
1020        fillBackground(g2, area);
1021        drawBackgroundImage(g2, area);
1022    }
1023
1024    /**
1025     * Fills the specified area with the background paint.
1026     *
1027     * @param g2  the graphics device.
1028     * @param area  the area.
1029     *
1030     * @see #getBackgroundPaint()
1031     * @see #getBackgroundAlpha()
1032     * @see #fillBackground(Graphics2D, Rectangle2D, PlotOrientation)
1033     */
1034    protected void fillBackground(Graphics2D g2, Rectangle2D area) {
1035        fillBackground(g2, area, PlotOrientation.VERTICAL);
1036    }
1037
1038    /**
1039     * Fills the specified area with the background paint.  If the background
1040     * paint is an instance of <code>GradientPaint</code>, the gradient will
1041     * run in the direction suggested by the plot's orientation.
1042     *
1043     * @param g2  the graphics target.
1044     * @param area  the plot area.
1045     * @param orientation  the plot orientation (<code>null</code> not
1046     *         permitted).
1047     *
1048     * @since 1.0.6
1049     */
1050    protected void fillBackground(Graphics2D g2, Rectangle2D area,
1051            PlotOrientation orientation) {
1052        if (orientation == null) {
1053            throw new IllegalArgumentException("Null 'orientation' argument.");
1054        }
1055        if (this.backgroundPaint == null) {
1056            return;
1057        }
1058        Paint p = this.backgroundPaint;
1059        if (p instanceof GradientPaint) {
1060            GradientPaint gp = (GradientPaint) p;
1061            if (orientation == PlotOrientation.VERTICAL) {
1062                p = new GradientPaint((float) area.getCenterX(),
1063                        (float) area.getMaxY(), gp.getColor1(),
1064                        (float) area.getCenterX(), (float) area.getMinY(),
1065                        gp.getColor2());
1066            }
1067            else if (orientation == PlotOrientation.HORIZONTAL) {
1068                p = new GradientPaint((float) area.getMinX(),
1069                        (float) area.getCenterY(), gp.getColor1(),
1070                        (float) area.getMaxX(), (float) area.getCenterY(),
1071                        gp.getColor2());
1072            }
1073        }
1074        Composite originalComposite = g2.getComposite();
1075        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
1076                this.backgroundAlpha));
1077        g2.setPaint(p);
1078        g2.fill(area);
1079        g2.setComposite(originalComposite);
1080    }
1081
1082    /**
1083     * Draws the background image (if there is one) aligned within the
1084     * specified area.
1085     *
1086     * @param g2  the graphics device.
1087     * @param area  the area.
1088     *
1089     * @see #getBackgroundImage()
1090     * @see #getBackgroundImageAlignment()
1091     * @see #getBackgroundImageAlpha()
1092     */
1093    public void drawBackgroundImage(Graphics2D g2, Rectangle2D area) {
1094        if (this.backgroundImage == null) {
1095            return;  // nothing to do
1096        }
1097        Composite savedComposite = g2.getComposite();
1098        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
1099                this.backgroundImageAlpha));
1100        Rectangle2D dest = new Rectangle2D.Double(0.0, 0.0,
1101                this.backgroundImage.getWidth(null),
1102                this.backgroundImage.getHeight(null));
1103        Align.align(dest, area, this.backgroundImageAlignment);
1104        Shape savedClip = g2.getClip();
1105        g2.clip(area);
1106        g2.drawImage(this.backgroundImage, (int) dest.getX(),
1107                (int) dest.getY(), (int) dest.getWidth() + 1,
1108                (int) dest.getHeight() + 1, null);
1109        g2.setClip(savedClip);
1110        g2.setComposite(savedComposite);
1111    }
1112
1113    /**
1114     * Draws the plot outline.  This method will be called during the chart
1115     * drawing process and is declared public so that it can be accessed by the
1116     * renderers used by certain subclasses. You shouldn't need to call this
1117     * method directly.
1118     *
1119     * @param g2  the graphics device.
1120     * @param area  the area within which the plot should be drawn.
1121     */
1122    public void drawOutline(Graphics2D g2, Rectangle2D area) {
1123        if (!this.outlineVisible) {
1124            return;
1125        }
1126        if ((this.outlineStroke != null) && (this.outlinePaint != null)) {
1127            g2.setStroke(this.outlineStroke);
1128            g2.setPaint(this.outlinePaint);
1129            g2.draw(area);
1130        }
1131    }
1132
1133    /**
1134     * Draws a message to state that there is no data to plot.
1135     *
1136     * @param g2  the graphics device.
1137     * @param area  the area within which the plot should be drawn.
1138     */
1139    protected void drawNoDataMessage(Graphics2D g2, Rectangle2D area) {
1140        Shape savedClip = g2.getClip();
1141        g2.clip(area);
1142        String message = this.noDataMessage;
1143        if (message != null) {
1144            g2.setFont(this.noDataMessageFont);
1145            g2.setPaint(this.noDataMessagePaint);
1146            TextBlock block = TextUtilities.createTextBlock(
1147                    this.noDataMessage, this.noDataMessageFont,
1148                    this.noDataMessagePaint, 0.9f * (float) area.getWidth(),
1149                    new G2TextMeasurer(g2));
1150            block.draw(g2, (float) area.getCenterX(),
1151                    (float) area.getCenterY(), TextBlockAnchor.CENTER);
1152        }
1153        g2.setClip(savedClip);
1154    }
1155
1156    /**
1157     * Creates a plot entity that contains a reference to the plot and the
1158     * data area as shape.
1159     *
1160     * @param dataArea  the data area used as hot spot for the entity.
1161     * @param plotState  the plot rendering info containing a reference to the
1162     *     EntityCollection.
1163     * @param toolTip  the tool tip (defined in the respective Plot
1164     *     subclass) (<code>null</code> permitted).
1165     * @param urlText  the url (defined in the respective Plot subclass)
1166     *     (<code>null</code> permitted).
1167     *
1168     *  @since 1.0.13
1169     */
1170    protected void createAndAddEntity(Rectangle2D dataArea,
1171            PlotRenderingInfo plotState, String toolTip, String urlText) {
1172        if (plotState != null && plotState.getOwner() != null) {
1173            EntityCollection e = plotState.getOwner().getEntityCollection();
1174            if (e != null) {
1175                e.add(new PlotEntity(dataArea, this, toolTip, urlText));
1176            }
1177        }
1178    }
1179
1180    /**
1181     * Handles a 'click' on the plot.  Since the plot does not maintain any
1182     * information about where it has been drawn, the plot rendering info is
1183     * supplied as an argument so that the plot dimensions can be determined.
1184     *
1185     * @param x  the x coordinate (in Java2D space).
1186     * @param y  the y coordinate (in Java2D space).
1187     * @param info  an object containing information about the dimensions of
1188     *              the plot.
1189     */
1190    public void handleClick(int x, int y, PlotRenderingInfo info) {
1191        // provides a 'no action' default
1192    }
1193
1194    /**
1195     * Performs a zoom on the plot.  Subclasses should override if zooming is
1196     * appropriate for the type of plot.
1197     *
1198     * @param percent  the zoom percentage.
1199     */
1200    public void zoom(double percent) {
1201        // do nothing by default.
1202    }
1203
1204    /**
1205     * Receives notification of a change to an {@link Annotation} added to
1206     * this plot.
1207     *
1208     * @param event  information about the event (not used here).
1209     *
1210     * @since 1.0.14
1211     */
1212    public void annotationChanged(AnnotationChangeEvent event) {
1213        fireChangeEvent();
1214    }
1215
1216    /**
1217     * Receives notification of a change to one of the plot's axes.
1218     *
1219     * @param event  information about the event (not used here).
1220     */
1221    public void axisChanged(AxisChangeEvent event) {
1222        fireChangeEvent();
1223    }
1224
1225    /**
1226     * Receives notification of a change to the plot's dataset.
1227     * <P>
1228     * The plot reacts by passing on a plot change event to all registered
1229     * listeners.
1230     *
1231     * @param event  information about the event (not used here).
1232     */
1233    public void datasetChanged(DatasetChangeEvent event) {
1234        PlotChangeEvent newEvent = new PlotChangeEvent(this);
1235        newEvent.setType(ChartChangeEventType.DATASET_UPDATED);
1236        notifyListeners(newEvent);
1237    }
1238
1239    /**
1240     * Receives notification of a change to a marker that is assigned to the
1241     * plot.
1242     *
1243     * @param event  the event.
1244     *
1245     * @since 1.0.3
1246     */
1247    public void markerChanged(MarkerChangeEvent event) {
1248        fireChangeEvent();
1249    }
1250
1251    /**
1252     * Adjusts the supplied x-value.
1253     *
1254     * @param x  the x-value.
1255     * @param w1  width 1.
1256     * @param w2  width 2.
1257     * @param edge  the edge (left or right).
1258     *
1259     * @return The adjusted x-value.
1260     */
1261    protected double getRectX(double x, double w1, double w2,
1262                              RectangleEdge edge) {
1263
1264        double result = x;
1265        if (edge == RectangleEdge.LEFT) {
1266            result = result + w1;
1267        }
1268        else if (edge == RectangleEdge.RIGHT) {
1269            result = result + w2;
1270        }
1271        return result;
1272
1273    }
1274
1275    /**
1276     * Adjusts the supplied y-value.
1277     *
1278     * @param y  the x-value.
1279     * @param h1  height 1.
1280     * @param h2  height 2.
1281     * @param edge  the edge (top or bottom).
1282     *
1283     * @return The adjusted y-value.
1284     */
1285    protected double getRectY(double y, double h1, double h2,
1286                              RectangleEdge edge) {
1287
1288        double result = y;
1289        if (edge == RectangleEdge.TOP) {
1290            result = result + h1;
1291        }
1292        else if (edge == RectangleEdge.BOTTOM) {
1293            result = result + h2;
1294        }
1295        return result;
1296
1297    }
1298
1299    /**
1300     * Tests this plot for equality with another object.
1301     *
1302     * @param obj  the object (<code>null</code> permitted).
1303     *
1304     * @return <code>true</code> or <code>false</code>.
1305     */
1306    public boolean equals(Object obj) {
1307        if (obj == this) {
1308            return true;
1309        }
1310        if (!(obj instanceof Plot)) {
1311            return false;
1312        }
1313        Plot that = (Plot) obj;
1314        if (!ObjectUtilities.equal(this.noDataMessage, that.noDataMessage)) {
1315            return false;
1316        }
1317        if (!ObjectUtilities.equal(
1318            this.noDataMessageFont, that.noDataMessageFont
1319        )) {
1320            return false;
1321        }
1322        if (!PaintUtilities.equal(this.noDataMessagePaint,
1323                that.noDataMessagePaint)) {
1324            return false;
1325        }
1326        if (!ObjectUtilities.equal(this.insets, that.insets)) {
1327            return false;
1328        }
1329        if (this.outlineVisible != that.outlineVisible) {
1330            return false;
1331        }
1332        if (!ObjectUtilities.equal(this.outlineStroke, that.outlineStroke)) {
1333            return false;
1334        }
1335        if (!PaintUtilities.equal(this.outlinePaint, that.outlinePaint)) {
1336            return false;
1337        }
1338        if (!PaintUtilities.equal(this.backgroundPaint, that.backgroundPaint)) {
1339            return false;
1340        }
1341        if (!ObjectUtilities.equal(this.backgroundImage,
1342                that.backgroundImage)) {
1343            return false;
1344        }
1345        if (this.backgroundImageAlignment != that.backgroundImageAlignment) {
1346            return false;
1347        }
1348        if (this.backgroundImageAlpha != that.backgroundImageAlpha) {
1349            return false;
1350        }
1351        if (this.foregroundAlpha != that.foregroundAlpha) {
1352            return false;
1353        }
1354        if (this.backgroundAlpha != that.backgroundAlpha) {
1355            return false;
1356        }
1357        if (!this.drawingSupplier.equals(that.drawingSupplier)) {
1358            return false;
1359        }
1360        if (this.notify != that.notify) {
1361            return false;
1362        }
1363        return true;
1364    }
1365
1366    /**
1367     * Creates a clone of the plot.
1368     *
1369     * @return A clone.
1370     *
1371     * @throws CloneNotSupportedException if some component of the plot does not
1372     *         support cloning.
1373     */
1374    public Object clone() throws CloneNotSupportedException {
1375
1376        Plot clone = (Plot) super.clone();
1377        // private Plot parent <-- don't clone the parent plot, but take care
1378        // childs in combined plots instead
1379        if (this.datasetGroup != null) {
1380            clone.datasetGroup
1381                = (DatasetGroup) ObjectUtilities.clone(this.datasetGroup);
1382        }
1383        clone.drawingSupplier
1384            = (DrawingSupplier) ObjectUtilities.clone(this.drawingSupplier);
1385        clone.listenerList = new EventListenerList();
1386        return clone;
1387
1388    }
1389
1390    /**
1391     * Provides serialization support.
1392     *
1393     * @param stream  the output stream.
1394     *
1395     * @throws IOException  if there is an I/O error.
1396     */
1397    private void writeObject(ObjectOutputStream stream) throws IOException {
1398        stream.defaultWriteObject();
1399        SerialUtilities.writePaint(this.noDataMessagePaint, stream);
1400        SerialUtilities.writeStroke(this.outlineStroke, stream);
1401        SerialUtilities.writePaint(this.outlinePaint, stream);
1402        // backgroundImage
1403        SerialUtilities.writePaint(this.backgroundPaint, stream);
1404    }
1405
1406    /**
1407     * Provides serialization support.
1408     *
1409     * @param stream  the input stream.
1410     *
1411     * @throws IOException  if there is an I/O error.
1412     * @throws ClassNotFoundException  if there is a classpath problem.
1413     */
1414    private void readObject(ObjectInputStream stream)
1415        throws IOException, ClassNotFoundException {
1416        stream.defaultReadObject();
1417        this.noDataMessagePaint = SerialUtilities.readPaint(stream);
1418        this.outlineStroke = SerialUtilities.readStroke(stream);
1419        this.outlinePaint = SerialUtilities.readPaint(stream);
1420        // backgroundImage
1421        this.backgroundPaint = SerialUtilities.readPaint(stream);
1422
1423        this.listenerList = new EventListenerList();
1424
1425    }
1426
1427    /**
1428     * Resolves a domain axis location for a given plot orientation.
1429     *
1430     * @param location  the location (<code>null</code> not permitted).
1431     * @param orientation  the orientation (<code>null</code> not permitted).
1432     *
1433     * @return The edge (never <code>null</code>).
1434     */
1435    public static RectangleEdge resolveDomainAxisLocation(
1436            AxisLocation location, PlotOrientation orientation) {
1437
1438        if (location == null) {
1439            throw new IllegalArgumentException("Null 'location' argument.");
1440        }
1441        if (orientation == null) {
1442            throw new IllegalArgumentException("Null 'orientation' argument.");
1443        }
1444
1445        RectangleEdge result = null;
1446
1447        if (location == AxisLocation.TOP_OR_RIGHT) {
1448            if (orientation == PlotOrientation.HORIZONTAL) {
1449                result = RectangleEdge.RIGHT;
1450            }
1451            else if (orientation == PlotOrientation.VERTICAL) {
1452                result = RectangleEdge.TOP;
1453            }
1454        }
1455        else if (location == AxisLocation.TOP_OR_LEFT) {
1456            if (orientation == PlotOrientation.HORIZONTAL) {
1457                result = RectangleEdge.LEFT;
1458            }
1459            else if (orientation == PlotOrientation.VERTICAL) {
1460                result = RectangleEdge.TOP;
1461            }
1462        }
1463        else if (location == AxisLocation.BOTTOM_OR_RIGHT) {
1464            if (orientation == PlotOrientation.HORIZONTAL) {
1465                result = RectangleEdge.RIGHT;
1466            }
1467            else if (orientation == PlotOrientation.VERTICAL) {
1468                result = RectangleEdge.BOTTOM;
1469            }
1470        }
1471        else if (location == AxisLocation.BOTTOM_OR_LEFT) {
1472            if (orientation == PlotOrientation.HORIZONTAL) {
1473                result = RectangleEdge.LEFT;
1474            }
1475            else if (orientation == PlotOrientation.VERTICAL) {
1476                result = RectangleEdge.BOTTOM;
1477            }
1478        }
1479        // the above should cover all the options...
1480        if (result == null) {
1481            throw new IllegalStateException("resolveDomainAxisLocation()");
1482        }
1483        return result;
1484
1485    }
1486
1487    /**
1488     * Resolves a range axis location for a given plot orientation.
1489     *
1490     * @param location  the location (<code>null</code> not permitted).
1491     * @param orientation  the orientation (<code>null</code> not permitted).
1492     *
1493     * @return The edge (never <code>null</code>).
1494     */
1495    public static RectangleEdge resolveRangeAxisLocation(
1496            AxisLocation location, PlotOrientation orientation) {
1497
1498        if (location == null) {
1499            throw new IllegalArgumentException("Null 'location' argument.");
1500        }
1501        if (orientation == null) {
1502            throw new IllegalArgumentException("Null 'orientation' argument.");
1503        }
1504
1505        RectangleEdge result = null;
1506
1507        if (location == AxisLocation.TOP_OR_RIGHT) {
1508            if (orientation == PlotOrientation.HORIZONTAL) {
1509                result = RectangleEdge.TOP;
1510            }
1511            else if (orientation == PlotOrientation.VERTICAL) {
1512                result = RectangleEdge.RIGHT;
1513            }
1514        }
1515        else if (location == AxisLocation.TOP_OR_LEFT) {
1516            if (orientation == PlotOrientation.HORIZONTAL) {
1517                result = RectangleEdge.TOP;
1518            }
1519            else if (orientation == PlotOrientation.VERTICAL) {
1520                result = RectangleEdge.LEFT;
1521            }
1522        }
1523        else if (location == AxisLocation.BOTTOM_OR_RIGHT) {
1524            if (orientation == PlotOrientation.HORIZONTAL) {
1525                result = RectangleEdge.BOTTOM;
1526            }
1527            else if (orientation == PlotOrientation.VERTICAL) {
1528                result = RectangleEdge.RIGHT;
1529            }
1530        }
1531        else if (location == AxisLocation.BOTTOM_OR_LEFT) {
1532            if (orientation == PlotOrientation.HORIZONTAL) {
1533                result = RectangleEdge.BOTTOM;
1534            }
1535            else if (orientation == PlotOrientation.VERTICAL) {
1536                result = RectangleEdge.LEFT;
1537            }
1538        }
1539
1540        // the above should cover all the options...
1541        if (result == null) {
1542            throw new IllegalStateException("resolveRangeAxisLocation()");
1543        }
1544        return result;
1545
1546    }
1547
1548}