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