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