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