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
125package org.jfree.chart.plot;
126
127import java.awt.AlphaComposite;
128import java.awt.BasicStroke;
129import java.awt.Color;
130import java.awt.Composite;
131import java.awt.Font;
132import java.awt.GradientPaint;
133import java.awt.Graphics2D;
134import java.awt.Image;
135import java.awt.Paint;
136import java.awt.Shape;
137import java.awt.Stroke;
138import java.awt.geom.Ellipse2D;
139import java.awt.geom.Point2D;
140import java.awt.geom.Rectangle2D;
141import java.io.IOException;
142import java.io.ObjectInputStream;
143import java.io.ObjectOutputStream;
144import java.io.Serializable;
145
146import javax.swing.event.EventListenerList;
147
148import org.jfree.chart.LegendItemCollection;
149import org.jfree.chart.LegendItemSource;
150import org.jfree.chart.axis.AxisLocation;
151import org.jfree.chart.event.AxisChangeEvent;
152import org.jfree.chart.event.AxisChangeListener;
153import org.jfree.chart.event.ChartChangeEventType;
154import org.jfree.chart.event.MarkerChangeEvent;
155import org.jfree.chart.event.MarkerChangeListener;
156import org.jfree.chart.event.PlotChangeEvent;
157import org.jfree.chart.event.PlotChangeListener;
158import org.jfree.data.general.DatasetChangeEvent;
159import org.jfree.data.general.DatasetChangeListener;
160import org.jfree.data.general.DatasetGroup;
161import org.jfree.io.SerialUtilities;
162import org.jfree.text.G2TextMeasurer;
163import org.jfree.text.TextBlock;
164import org.jfree.text.TextBlockAnchor;
165import org.jfree.text.TextUtilities;
166import org.jfree.ui.Align;
167import org.jfree.ui.RectangleEdge;
168import org.jfree.ui.RectangleInsets;
169import org.jfree.util.ObjectUtilities;
170import org.jfree.util.PaintUtilities;
171import 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 */
179public 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}