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