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