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