All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jfree.chart.plot.CategoryPlot Maven / Gradle / Ivy

/* ===========================================================
 * JFreeChart : a free chart library for the Java(tm) platform
 * ===========================================================
 *
 * (C) Copyright 2000-2013, by Object Refinery Limited and Contributors.
 *
 * Project Info:  http://www.jfree.org/jfreechart/index.html
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
 * USA.
 *
 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
 * Other names may be trademarks of their respective owners.]
 *
 * -----------------
 * CategoryPlot.java
 * -----------------
 * (C) Copyright 2000-2013, by Object Refinery Limited and Contributors.
 *
 * Original Author:  David Gilbert (for Object Refinery Limited);
 * Contributor(s):   Jeremy Bowman;
 *                   Arnaud Lelievre;
 *                   Richard West, Advanced Micro Devices, Inc.;
 *                   Ulrich Voigt - patch 2686040;
 *                   Peter Kolb - patches 2603321 and 2809117;
 *
 * Changes
 * -------
 * 21-Jun-2001 : Removed redundant JFreeChart parameter from constructors (DG);
 * 21-Aug-2001 : Added standard header. Fixed DOS encoding problem (DG);
 * 18-Sep-2001 : Updated header (DG);
 * 15-Oct-2001 : Data source classes moved to com.jrefinery.data.* (DG);
 * 22-Oct-2001 : Renamed DataSource.java --> Dataset.java etc. (DG);
 * 23-Oct-2001 : Changed intro and trail gaps on bar plots to use percentage of
 *               available space rather than a fixed number of units (DG);
 * 12-Dec-2001 : Changed constructors to protected (DG);
 * 13-Dec-2001 : Added tooltips (DG);
 * 16-Jan-2002 : Increased maximum intro and trail gap percents, plus added
 *               some argument checking code.  Thanks to Taoufik Romdhane for
 *               suggesting this (DG);
 * 05-Feb-2002 : Added accessor methods for the tooltip generator, incorporated
 *               alpha-transparency for Plot and subclasses (DG);
 * 06-Mar-2002 : Updated import statements (DG);
 * 14-Mar-2002 : Renamed BarPlot.java --> CategoryPlot.java, and changed code
 *               to use the CategoryItemRenderer interface (DG);
 * 22-Mar-2002 : Dropped the getCategories() method (DG);
 * 23-Apr-2002 : Moved the dataset from the JFreeChart class to the Plot
 *               class (DG);
 * 29-Apr-2002 : New methods to support printing values at the end of bars,
 *               contributed by Jeremy Bowman (DG);
 * 11-May-2002 : New methods for label visibility and overlaid plot support,
 *               contributed by Jeremy Bowman (DG);
 * 06-Jun-2002 : Removed the tooltip generator, this is now stored with the
 *               renderer.  Moved constants into the CategoryPlotConstants
 *               interface.  Updated Javadoc comments (DG);
 * 10-Jun-2002 : Overridden datasetChanged() method to update the upper and
 *               lower bound on the range axis (if necessary), updated
 *               Javadocs (DG);
 * 25-Jun-2002 : Removed redundant imports (DG);
 * 20-Aug-2002 : Changed the constructor for Marker (DG);
 * 28-Aug-2002 : Added listener notification to setDomainAxis() and
 *               setRangeAxis() (DG);
 * 23-Sep-2002 : Added getLegendItems() method and fixed errors reported by
 *               Checkstyle (DG);
 * 28-Oct-2002 : Changes to the CategoryDataset interface (DG);
 * 05-Nov-2002 : Base dataset is now TableDataset not CategoryDataset (DG);
 * 07-Nov-2002 : Renamed labelXXX as valueLabelXXX (DG);
 * 18-Nov-2002 : Added grid settings for both domain and range axis (previously
 *               these were set in the axes) (DG);
 * 19-Nov-2002 : Added axis location parameters to constructor (DG);
 * 17-Jan-2003 : Moved to com.jrefinery.chart.plot package (DG);
 * 14-Feb-2003 : Fixed bug in auto-range calculation for secondary axis (DG);
 * 26-Mar-2003 : Implemented Serializable (DG);
 * 02-May-2003 : Moved render() method up from subclasses. Added secondary
 *               range markers. Added an attribute to control the dataset
 *               rendering order.  Added a drawAnnotations() method.  Changed
 *               the axis location from an int to an AxisLocation (DG);
 * 07-May-2003 : Merged HorizontalCategoryPlot and VerticalCategoryPlot into
 *               this class (DG);
 * 02-Jun-2003 : Removed check for range axis compatibility (DG);
 * 04-Jul-2003 : Added a domain gridline position attribute (DG);
 * 21-Jul-2003 : Moved DrawingSupplier to Plot superclass (DG);
 * 19-Aug-2003 : Added equals() method and implemented Cloneable (DG);
 * 01-Sep-2003 : Fixed bug 797466 (no change event when secondary dataset
 *               changes) (DG);
 * 02-Sep-2003 : Fixed bug 795209 (wrong dataset checked in render2 method) and
 *               790407 (initialise method) (DG);
 * 08-Sep-2003 : Added internationalization via use of properties
 *               resourceBundle (RFE 690236) (AL);
 * 08-Sep-2003 : Fixed bug (wrong secondary range axis being used).  Changed
 *               ValueAxis API (DG);
 * 10-Sep-2003 : Fixed bug in setRangeAxis() method (DG);
 * 15-Sep-2003 : Fixed two bugs in serialization, implemented
 *               PublicCloneable (DG);
 * 23-Oct-2003 : Added event notification for changes to renderer (DG);
 * 26-Nov-2003 : Fixed bug (849645) in clearRangeMarkers() method (DG);
 * 03-Dec-2003 : Modified draw method to accept anchor (DG);
 * 21-Jan-2004 : Update for renamed method in ValueAxis (DG);
 * 10-Mar-2004 : Fixed bug in axis range calculation when secondary renderer is
 *               stacked (DG);
 * 12-May-2004 : Added fixed legend items (DG);
 * 19-May-2004 : Added check for null legend item from renderer (DG);
 * 02-Jun-2004 : Updated the DatasetRenderingOrder class (DG);
 * 05-Nov-2004 : Renamed getDatasetsMappedToRangeAxis()
 *               --> datasetsMappedToRangeAxis(), and ensured that returned
 *               list doesn't contain null datasets (DG);
 * 12-Nov-2004 : Implemented new Zoomable interface (DG);
 * 07-Jan-2005 : Renamed getRangeExtent() --> findRangeBounds() in
 *               CategoryItemRenderer (DG);
 * 04-May-2005 : Fixed serialization of range markers (DG);
 * 05-May-2005 : Updated draw() method parameters (DG);
 * 20-May-2005 : Added setDomainAxes() and setRangeAxes() methods, as per
 *               RFE 1183100 (DG);
 * 01-Jun-2005 : Upon deserialization, register plot as a listener with its
 *               axes, dataset(s) and renderer(s) - see patch 1209475 (DG);
 * 02-Jun-2005 : Added support for domain markers (DG);
 * 06-Jun-2005 : Fixed equals() method for use with GradientPaint (DG);
 * 09-Jun-2005 : Added setRenderers(), as per RFE 1183100 (DG);
 * 16-Jun-2005 : Added getDomainAxisCount() and getRangeAxisCount() methods, to
 *               match XYPlot (see RFE 1220495) (DG);
 * ------------- JFREECHART 1.0.x ---------------------------------------------
 * 11-Jan-2006 : Added configureRangeAxes() to rendererChanged(), since the
 *               renderer might influence the axis range (DG);
 * 27-Jan-2006 : Added various null argument checks (DG);
 * 18-Aug-2006 : Added getDatasetCount() method, plus a fix for bug drawing
 *               category labels, thanks to Adriaan Joubert (1277726) (DG);
 * 05-Sep-2006 : Added MarkerChangeEvent support (DG);
 * 30-Oct-2006 : Added getDomainAxisIndex(), datasetsMappedToDomainAxis() and
 *               getCategoriesForAxis() methods (DG);
 * 22-Nov-2006 : Fire PlotChangeEvent from setColumnRenderingOrder() and
 *               setRowRenderingOrder() (DG);
 * 29-Nov-2006 : Fix for bug 1605207 (IntervalMarker exceeds bounds of data
 *               area) (DG);
 * 26-Feb-2007 : Fix for bug 1669218 (setDomainAxisLocation() notify argument
 *               ignored) (DG);
 * 13-Mar-2007 : Added null argument checks for setRangeCrosshairPaint() and
 *               setRangeCrosshairStroke(), fixed clipping for
 *               annotations (DG);
 * 07-Jun-2007 : Override drawBackground() for new GradientPaint handling (DG);
 * 10-Jul-2007 : Added getRangeAxisIndex(ValueAxis) method (DG);
 * 24-Sep-2007 : Implemented new zoom methods (DG);
 * 25-Oct-2007 : Added some argument checks (DG);
 * 05-Nov-2007 : Applied patch 1823697, by Richard West, for removal of domain
 *               and range markers (DG);
 * 14-Nov-2007 : Added missing event notifications (DG);
 * 25-Mar-2008 : Added new methods with optional notification - see patch
 *               1913751 (DG);
 * 07-Apr-2008 : Fixed NPE in removeDomainMarker() and
 *               removeRangeMarker() (DG);
 * 23-Apr-2008 : Fixed equals() and clone() methods (DG);
 * 26-Jun-2008 : Fixed crosshair support (DG);
 * 10-Jul-2008 : Fixed outline visibility for 3D renderers (DG);
 * 12-Aug-2008 : Added rendererCount() method (DG);
 * 25-Nov-2008 : Added facility to map datasets to multiples axes (DG);
 * 15-Dec-2008 : Cleaned up grid drawing methods (DG);
 * 18-Dec-2008 : Use ResourceBundleWrapper - see patch 1607918 by
 *               Jess Thrysoee (DG);
 * 21-Jan-2009 : Added rangeMinorGridlinesVisible flag (DG);
 * 18-Mar-2009 : Modified anchored zoom behaviour (DG);
 * 19-Mar-2009 : Implemented Pannable interface - see patch 2686040 (DG);
 * 19-Mar-2009 : Added entity support - see patch 2603321 by Peter Kolb (DG);
 * 24-Jun-2009 : Implemented AnnotationChangeListener (see patch 2809117 by
 *               PK) (DG);
 * 06-Jul-2009 : Fix for cloning of renderers - see bug 2817504 (DG)
 * 10-Jul-2009 : Added optional drop shadow generator (DG);
 * 27-Sep-2011 : Fixed annotation import (DG);
 * 18-Oct-2011 : Fixed tooltip offset with shadow generator (DG);
 * 20-Nov-2011 : Initialise shadow generator as null (DG);
 * 02-Jul-2013 : Use ParamChecks (DG);
 * 12-Sep-2013 : Check for KEY_SUPPRESS_SHADOW_GENERATION rendering hint (DG);
 *
 */

package org.jfree.chart.plot;

import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.TreeMap;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.LegendItemCollection;
import org.jfree.chart.annotations.Annotation;
import org.jfree.chart.annotations.CategoryAnnotation;
import org.jfree.chart.axis.Axis;
import org.jfree.chart.axis.AxisCollection;
import org.jfree.chart.axis.AxisLocation;
import org.jfree.chart.axis.AxisSpace;
import org.jfree.chart.axis.AxisState;
import org.jfree.chart.axis.CategoryAnchor;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.TickType;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.axis.ValueTick;
import org.jfree.chart.event.AnnotationChangeEvent;
import org.jfree.chart.event.AnnotationChangeListener;
import org.jfree.chart.event.ChartChangeEventType;
import org.jfree.chart.event.PlotChangeEvent;
import org.jfree.chart.event.RendererChangeEvent;
import org.jfree.chart.event.RendererChangeListener;
import org.jfree.chart.renderer.category.AbstractCategoryItemRenderer;
import org.jfree.chart.renderer.category.CategoryItemRenderer;
import org.jfree.chart.renderer.category.CategoryItemRendererState;
import org.jfree.chart.util.ParamChecks;
import org.jfree.chart.util.ResourceBundleWrapper;
import org.jfree.chart.util.ShadowGenerator;
import org.jfree.data.Range;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.general.Dataset;
import org.jfree.data.general.DatasetChangeEvent;
import org.jfree.data.general.DatasetUtilities;
import org.jfree.io.SerialUtilities;
import org.jfree.ui.Layer;
import org.jfree.ui.RectangleEdge;
import org.jfree.ui.RectangleInsets;
import org.jfree.util.ObjectList;
import org.jfree.util.ObjectUtilities;
import org.jfree.util.PaintUtilities;
import org.jfree.util.PublicCloneable;
import org.jfree.util.ShapeUtilities;
import org.jfree.util.SortOrder;

/**
 * A general plotting class that uses data from a {@link CategoryDataset} and
 * renders each data item using a {@link CategoryItemRenderer}.
 */
public class CategoryPlot extends Plot implements ValueAxisPlot, Pannable,
        Zoomable, AnnotationChangeListener, RendererChangeListener,
        Cloneable, PublicCloneable, Serializable {

    /** For serialization. */
    private static final long serialVersionUID = -3537691700434728188L;

    /**
     * The default visibility of the grid lines plotted against the domain
     * axis.
     */
    public static final boolean DEFAULT_DOMAIN_GRIDLINES_VISIBLE = false;

    /**
     * The default visibility of the grid lines plotted against the range
     * axis.
     */
    public static final boolean DEFAULT_RANGE_GRIDLINES_VISIBLE = true;

    /** The default grid line stroke. */
    public static final Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke(0.5f,
            BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0.0f, new float[]
            {2.0f, 2.0f}, 0.0f);

    /** The default grid line paint. */
    public static final Paint DEFAULT_GRIDLINE_PAINT = Color.lightGray;

    /** The default value label font. */
    public static final Font DEFAULT_VALUE_LABEL_FONT = new Font("SansSerif",
            Font.PLAIN, 10);

    /**
     * The default crosshair visibility.
     *
     * @since 1.0.5
     */
    public static final boolean DEFAULT_CROSSHAIR_VISIBLE = false;

    /**
     * The default crosshair stroke.
     *
     * @since 1.0.5
     */
    public static final Stroke DEFAULT_CROSSHAIR_STROKE
            = DEFAULT_GRIDLINE_STROKE;

    /**
     * The default crosshair paint.
     *
     * @since 1.0.5
     */
    public static final Paint DEFAULT_CROSSHAIR_PAINT = Color.blue;

    /** The resourceBundle for the localization. */
    protected static ResourceBundle localizationResources
            = ResourceBundleWrapper.getBundle(
            "org.jfree.chart.plot.LocalizationBundle");

    /** The plot orientation. */
    private PlotOrientation orientation;

    /** The offset between the data area and the axes. */
    private RectangleInsets axisOffset;

    /** Storage for the domain axes. */
    private ObjectList domainAxes;

    /** Storage for the domain axis locations. */
    private ObjectList domainAxisLocations;

    /**
     * A flag that controls whether or not the shared domain axis is drawn
     * (only relevant when the plot is being used as a subplot).
     */
    private boolean drawSharedDomainAxis;

    /** Storage for the range axes. */
    private ObjectList rangeAxes;

    /** Storage for the range axis locations. */
    private ObjectList rangeAxisLocations;

    /** Storage for the datasets. */
    private ObjectList datasets;

    /** Storage for keys that map datasets to domain axes. */
    private TreeMap datasetToDomainAxesMap;

    /** Storage for keys that map datasets to range axes. */
    private TreeMap datasetToRangeAxesMap;

    /** Storage for the renderers. */
    private ObjectList renderers;

    /** The dataset rendering order. */
    private DatasetRenderingOrder renderingOrder
            = DatasetRenderingOrder.REVERSE;

    /**
     * Controls the order in which the columns are traversed when rendering the
     * data items.
     */
    private SortOrder columnRenderingOrder = SortOrder.ASCENDING;

    /**
     * Controls the order in which the rows are traversed when rendering the
     * data items.
     */
    private SortOrder rowRenderingOrder = SortOrder.ASCENDING;

    /**
     * A flag that controls whether the grid-lines for the domain axis are
     * visible.
     */
    private boolean domainGridlinesVisible;

    /** The position of the domain gridlines relative to the category. */
    private CategoryAnchor domainGridlinePosition;

    /** The stroke used to draw the domain grid-lines. */
    private transient Stroke domainGridlineStroke;

    /** The paint used to draw the domain  grid-lines. */
    private transient Paint domainGridlinePaint;

    /**
     * A flag that controls whether or not the zero baseline against the range
     * axis is visible.
     *
     * @since 1.0.13
     */
    private boolean rangeZeroBaselineVisible;

    /**
     * The stroke used for the zero baseline against the range axis.
     *
     * @since 1.0.13
     */
    private transient Stroke rangeZeroBaselineStroke;

    /**
     * The paint used for the zero baseline against the range axis.
     *
     * @since 1.0.13
     */
    private transient Paint rangeZeroBaselinePaint;

    /**
     * A flag that controls whether the grid-lines for the range axis are
     * visible.
     */
    private boolean rangeGridlinesVisible;

    /** The stroke used to draw the range axis grid-lines. */
    private transient Stroke rangeGridlineStroke;

    /** The paint used to draw the range axis grid-lines. */
    private transient Paint rangeGridlinePaint;

    /**
     * A flag that controls whether or not gridlines are shown for the minor
     * tick values on the primary range axis.
     *
     * @since 1.0.13
     */
    private boolean rangeMinorGridlinesVisible;

    /**
     * The stroke used to draw the range minor grid-lines.
     *
     * @since 1.0.13
     */
    private transient Stroke rangeMinorGridlineStroke;

    /**
     * The paint used to draw the range minor grid-lines.
     *
     * @since 1.0.13
     */
    private transient Paint rangeMinorGridlinePaint;

    /** The anchor value. */
    private double anchorValue;

    /**
     * The index for the dataset that the crosshairs are linked to (this
     * determines which axes the crosshairs are plotted against).
     *
     * @since 1.0.11
     */
    private int crosshairDatasetIndex;

    /**
     * A flag that controls the visibility of the domain crosshair.
     *
     * @since 1.0.11
     */
    private boolean domainCrosshairVisible;

    /**
     * The row key for the crosshair point.
     *
     * @since 1.0.11
     */
    private Comparable domainCrosshairRowKey;

    /**
     * The column key for the crosshair point.
     *
     * @since 1.0.11
     */
    private Comparable domainCrosshairColumnKey;

    /**
     * The stroke used to draw the domain crosshair if it is visible.
     *
     * @since 1.0.11
     */
    private transient Stroke domainCrosshairStroke;

    /**
     * The paint used to draw the domain crosshair if it is visible.
     *
     * @since 1.0.11
     */
    private transient Paint domainCrosshairPaint;

    /** A flag that controls whether or not a range crosshair is drawn. */
    private boolean rangeCrosshairVisible;

    /** The range crosshair value. */
    private double rangeCrosshairValue;

    /** The pen/brush used to draw the crosshair (if any). */
    private transient Stroke rangeCrosshairStroke;

    /** The color used to draw the crosshair (if any). */
    private transient Paint rangeCrosshairPaint;

    /**
     * A flag that controls whether or not the crosshair locks onto actual
     * data points.
     */
    private boolean rangeCrosshairLockedOnData = true;

    /** A map containing lists of markers for the domain axes. */
    private Map foregroundDomainMarkers;

    /** A map containing lists of markers for the domain axes. */
    private Map backgroundDomainMarkers;

    /** A map containing lists of markers for the range axes. */
    private Map foregroundRangeMarkers;

    /** A map containing lists of markers for the range axes. */
    private Map backgroundRangeMarkers;

    /**
     * A (possibly empty) list of annotations for the plot.  The list should
     * be initialised in the constructor and never allowed to be
     * null.
     */
    private List annotations;

    /**
     * The weight for the plot (only relevant when the plot is used as a subplot
     * within a combined plot).
     */
    private int weight;

    /** The fixed space for the domain axis. */
    private AxisSpace fixedDomainAxisSpace;

    /** The fixed space for the range axis. */
    private AxisSpace fixedRangeAxisSpace;

    /**
     * An optional collection of legend items that can be returned by the
     * getLegendItems() method.
     */
    private LegendItemCollection fixedLegendItems;

    /**
     * A flag that controls whether or not panning is enabled for the
     * range axis/axes.
     *
     * @since 1.0.13
     */
    private boolean rangePannable;

    /**
     * The shadow generator for the plot (null permitted).
     *
     * @since 1.0.14
     */
    private ShadowGenerator shadowGenerator;

    /**
     * Default constructor.
     */
    public CategoryPlot() {
        this(null, null, null, null);
    }

    /**
     * Creates a new plot.
     *
     * @param dataset  the dataset (null permitted).
     * @param domainAxis  the domain axis (null permitted).
     * @param rangeAxis  the range axis (null permitted).
     * @param renderer  the item renderer (null permitted).
     *
     */
    public CategoryPlot(CategoryDataset dataset,
                        CategoryAxis domainAxis,
                        ValueAxis rangeAxis,
                        CategoryItemRenderer renderer) {

        super();

        this.orientation = PlotOrientation.VERTICAL;

        // allocate storage for dataset, axes and renderers
        this.domainAxes = new ObjectList();
        this.domainAxisLocations = new ObjectList();
        this.rangeAxes = new ObjectList();
        this.rangeAxisLocations = new ObjectList();

        this.datasetToDomainAxesMap = new TreeMap();
        this.datasetToRangeAxesMap = new TreeMap();

        this.renderers = new ObjectList();

        this.datasets = new ObjectList();
        this.datasets.set(0, dataset);
        if (dataset != null) {
            dataset.addChangeListener(this);
        }

        this.axisOffset = RectangleInsets.ZERO_INSETS;

        setDomainAxisLocation(AxisLocation.BOTTOM_OR_LEFT, false);
        setRangeAxisLocation(AxisLocation.TOP_OR_LEFT, false);

        this.renderers.set(0, renderer);
        if (renderer != null) {
            renderer.setPlot(this);
            renderer.addChangeListener(this);
        }

        this.domainAxes.set(0, domainAxis);
        this.mapDatasetToDomainAxis(0, 0);
        if (domainAxis != null) {
            domainAxis.setPlot(this);
            domainAxis.addChangeListener(this);
        }
        this.drawSharedDomainAxis = false;

        this.rangeAxes.set(0, rangeAxis);
        this.mapDatasetToRangeAxis(0, 0);
        if (rangeAxis != null) {
            rangeAxis.setPlot(this);
            rangeAxis.addChangeListener(this);
        }

        configureDomainAxes();
        configureRangeAxes();

        this.domainGridlinesVisible = DEFAULT_DOMAIN_GRIDLINES_VISIBLE;
        this.domainGridlinePosition = CategoryAnchor.MIDDLE;
        this.domainGridlineStroke = DEFAULT_GRIDLINE_STROKE;
        this.domainGridlinePaint = DEFAULT_GRIDLINE_PAINT;

        this.rangeZeroBaselineVisible = false;
        this.rangeZeroBaselinePaint = Color.black;
        this.rangeZeroBaselineStroke = new BasicStroke(0.5f);

        this.rangeGridlinesVisible = DEFAULT_RANGE_GRIDLINES_VISIBLE;
        this.rangeGridlineStroke = DEFAULT_GRIDLINE_STROKE;
        this.rangeGridlinePaint = DEFAULT_GRIDLINE_PAINT;

        this.rangeMinorGridlinesVisible = false;
        this.rangeMinorGridlineStroke = DEFAULT_GRIDLINE_STROKE;
        this.rangeMinorGridlinePaint = Color.white;

        this.foregroundDomainMarkers = new HashMap();
        this.backgroundDomainMarkers = new HashMap();
        this.foregroundRangeMarkers = new HashMap();
        this.backgroundRangeMarkers = new HashMap();

        this.anchorValue = 0.0;

        this.domainCrosshairVisible = false;
        this.domainCrosshairStroke = DEFAULT_CROSSHAIR_STROKE;
        this.domainCrosshairPaint = DEFAULT_CROSSHAIR_PAINT;

        this.rangeCrosshairVisible = DEFAULT_CROSSHAIR_VISIBLE;
        this.rangeCrosshairValue = 0.0;
        this.rangeCrosshairStroke = DEFAULT_CROSSHAIR_STROKE;
        this.rangeCrosshairPaint = DEFAULT_CROSSHAIR_PAINT;

        this.annotations = new java.util.ArrayList();

        this.rangePannable = false;
        this.shadowGenerator = null;
    }

    /**
     * Returns a string describing the type of plot.
     *
     * @return The type.
     */
    @Override
    public String getPlotType() {
        return localizationResources.getString("Category_Plot");
    }

    /**
     * Returns the orientation of the plot.
     *
     * @return The orientation of the plot (never null).
     *
     * @see #setOrientation(PlotOrientation)
     */
    @Override
    public PlotOrientation getOrientation() {
        return this.orientation;
    }

    /**
     * Sets the orientation for the plot and sends a {@link PlotChangeEvent} to
     * all registered listeners.
     *
     * @param orientation  the orientation (null not permitted).
     *
     * @see #getOrientation()
     */
    public void setOrientation(PlotOrientation orientation) {
        ParamChecks.nullNotPermitted(orientation, "orientation");
        this.orientation = orientation;
        fireChangeEvent();
    }

    /**
     * Returns the axis offset.
     *
     * @return The axis offset (never null).
     *
     * @see #setAxisOffset(RectangleInsets)
     */
    public RectangleInsets getAxisOffset() {
        return this.axisOffset;
    }

    /**
     * Sets the axis offsets (gap between the data area and the axes) and
     * sends a {@link PlotChangeEvent} to all registered listeners.
     *
     * @param offset  the offset (null not permitted).
     *
     * @see #getAxisOffset()
     */
    public void setAxisOffset(RectangleInsets offset) {
        ParamChecks.nullNotPermitted(offset, "offset");
        this.axisOffset = offset;
        fireChangeEvent();
    }

    /**
     * Returns the domain axis for the plot.  If the domain axis for this plot
     * is null, then the method will return the parent plot's
     * domain axis (if there is a parent plot).
     *
     * @return The domain axis (null permitted).
     *
     * @see #setDomainAxis(CategoryAxis)
     */
    public CategoryAxis getDomainAxis() {
        return getDomainAxis(0);
    }

    /**
     * Returns a domain axis.
     *
     * @param index  the axis index.
     *
     * @return The axis (null possible).
     *
     * @see #setDomainAxis(int, CategoryAxis)
     */
    public CategoryAxis getDomainAxis(int index) {
        CategoryAxis result = null;
        if (index < this.domainAxes.size()) {
            result = (CategoryAxis) this.domainAxes.get(index);
        }
        if (result == null) {
            Plot parent = getParent();
            if (parent instanceof CategoryPlot) {
                CategoryPlot cp = (CategoryPlot) parent;
                result = cp.getDomainAxis(index);
            }
        }
        return result;
    }

    /**
     * Sets the domain axis for the plot and sends a {@link PlotChangeEvent} to
     * all registered listeners.
     *
     * @param axis  the axis (null permitted).
     *
     * @see #getDomainAxis()
     */
    public void setDomainAxis(CategoryAxis axis) {
        setDomainAxis(0, axis);
    }

    /**
     * Sets a domain axis and sends a {@link PlotChangeEvent} to all
     * registered listeners.
     *
     * @param index  the axis index.
     * @param axis  the axis (null permitted).
     *
     * @see #getDomainAxis(int)
     */
    public void setDomainAxis(int index, CategoryAxis axis) {
        setDomainAxis(index, axis, true);
    }

    /**
     * Sets a domain axis and, if requested, sends a {@link PlotChangeEvent} to
     * all registered listeners.
     *
     * @param index  the axis index.
     * @param axis  the axis (null permitted).
     * @param notify  notify listeners?
     */
    public void setDomainAxis(int index, CategoryAxis axis, boolean notify) {
        CategoryAxis existing = (CategoryAxis) this.domainAxes.get(index);
        if (existing != null) {
            existing.removeChangeListener(this);
        }
        if (axis != null) {
            axis.setPlot(this);
        }
        this.domainAxes.set(index, axis);
        if (axis != null) {
            axis.configure();
            axis.addChangeListener(this);
        }
        if (notify) {
            fireChangeEvent();
        }
    }

    /**
     * Sets the domain axes for this plot and sends a {@link PlotChangeEvent}
     * to all registered listeners.
     *
     * @param axes  the axes (null not permitted).
     *
     * @see #setRangeAxes(ValueAxis[])
     */
    public void setDomainAxes(CategoryAxis[] axes) {
        for (int i = 0; i < axes.length; i++) {
            setDomainAxis(i, axes[i], false);
        }
        fireChangeEvent();
    }

    /**
     * Returns the index of the specified axis, or -1 if the axis
     * is not assigned to the plot.
     *
     * @param axis  the axis (null not permitted).
     *
     * @return The axis index.
     *
     * @see #getDomainAxis(int)
     * @see #getRangeAxisIndex(ValueAxis)
     *
     * @since 1.0.3
     */
    public int getDomainAxisIndex(CategoryAxis axis) {
        ParamChecks.nullNotPermitted(axis, "axis");
        return this.domainAxes.indexOf(axis);
    }

    /**
     * Returns the domain axis location for the primary domain axis.
     *
     * @return The location (never null).
     *
     * @see #getRangeAxisLocation()
     */
    public AxisLocation getDomainAxisLocation() {
        return getDomainAxisLocation(0);
    }

    /**
     * Returns the location for a domain axis.
     *
     * @param index  the axis index.
     *
     * @return The location.
     *
     * @see #setDomainAxisLocation(int, AxisLocation)
     */
    public AxisLocation getDomainAxisLocation(int index) {
        AxisLocation result = null;
        if (index < this.domainAxisLocations.size()) {
            result = (AxisLocation) this.domainAxisLocations.get(index);
        }
        if (result == null) {
            result = AxisLocation.getOpposite(getDomainAxisLocation(0));
        }
        return result;
    }

    /**
     * Sets the location of the domain axis and sends a {@link PlotChangeEvent}
     * to all registered listeners.
     *
     * @param location  the axis location (null not permitted).
     *
     * @see #getDomainAxisLocation()
     * @see #setDomainAxisLocation(int, AxisLocation)
     */
    public void setDomainAxisLocation(AxisLocation location) {
        // delegate...
        setDomainAxisLocation(0, location, true);
    }

    /**
     * Sets the location of the domain axis and, if requested, sends a
     * {@link PlotChangeEvent} to all registered listeners.
     *
     * @param location  the axis location (null not permitted).
     * @param notify  a flag that controls whether listeners are notified.
     */
    public void setDomainAxisLocation(AxisLocation location, boolean notify) {
        // delegate...
        setDomainAxisLocation(0, location, notify);
    }

    /**
     * Sets the location for a domain axis and sends a {@link PlotChangeEvent}
     * to all registered listeners.
     *
     * @param index  the axis index.
     * @param location  the location.
     *
     * @see #getDomainAxisLocation(int)
     * @see #setRangeAxisLocation(int, AxisLocation)
     */
    public void setDomainAxisLocation(int index, AxisLocation location) {
        // delegate...
        setDomainAxisLocation(index, location, true);
    }

    /**
     * Sets the location for a domain axis and sends a {@link PlotChangeEvent}
     * to all registered listeners.
     *
     * @param index  the axis index.
     * @param location  the location.
     * @param notify  notify listeners?
     *
     * @since 1.0.5
     *
     * @see #getDomainAxisLocation(int)
     * @see #setRangeAxisLocation(int, AxisLocation, boolean)
     */
    public void setDomainAxisLocation(int index, AxisLocation location,
            boolean notify) {
        if (index == 0 && location == null) {
            throw new IllegalArgumentException(
                    "Null 'location' for index 0 not permitted.");
        }
        this.domainAxisLocations.set(index, location);
        if (notify) {
            fireChangeEvent();
        }
    }

    /**
     * Returns the domain axis edge.  This is derived from the axis location
     * and the plot orientation.
     *
     * @return The edge (never null).
     */
    public RectangleEdge getDomainAxisEdge() {
        return getDomainAxisEdge(0);
    }

    /**
     * Returns the edge for a domain axis.
     *
     * @param index  the axis index.
     *
     * @return The edge (never null).
     */
    public RectangleEdge getDomainAxisEdge(int index) {
        RectangleEdge result;
        AxisLocation location = getDomainAxisLocation(index);
        if (location != null) {
            result = Plot.resolveDomainAxisLocation(location, this.orientation);
        }
        else {
            result = RectangleEdge.opposite(getDomainAxisEdge(0));
        }
        return result;
    }

    /**
     * Returns the number of domain axes.
     *
     * @return The axis count.
     */
    public int getDomainAxisCount() {
        return this.domainAxes.size();
    }

    /**
     * Clears the domain axes from the plot and sends a {@link PlotChangeEvent}
     * to all registered listeners.
     */
    public void clearDomainAxes() {
        for (int i = 0; i < this.domainAxes.size(); i++) {
            CategoryAxis axis = (CategoryAxis) this.domainAxes.get(i);
            if (axis != null) {
                axis.removeChangeListener(this);
            }
        }
        this.domainAxes.clear();
        fireChangeEvent();
    }

    /**
     * Configures the domain axes.
     */
    public void configureDomainAxes() {
        for (int i = 0; i < this.domainAxes.size(); i++) {
            CategoryAxis axis = (CategoryAxis) this.domainAxes.get(i);
            if (axis != null) {
                axis.configure();
            }
        }
    }

    /**
     * Returns the range axis for the plot.  If the range axis for this plot is
     * null, then the method will return the parent plot's range axis (if there
     * is a parent plot).
     *
     * @return The range axis (possibly null).
     */
    public ValueAxis getRangeAxis() {
        return getRangeAxis(0);
    }

    /**
     * Returns a range axis.
     *
     * @param index  the axis index.
     *
     * @return The axis (null possible).
     */
    public ValueAxis getRangeAxis(int index) {
        ValueAxis result = null;
        if (index < this.rangeAxes.size()) {
            result = (ValueAxis) this.rangeAxes.get(index);
        }
        if (result == null) {
            Plot parent = getParent();
            if (parent instanceof CategoryPlot) {
                CategoryPlot cp = (CategoryPlot) parent;
                result = cp.getRangeAxis(index);
            }
        }
        return result;
    }

    /**
     * Sets the range axis for the plot and sends a {@link PlotChangeEvent} to
     * all registered listeners.
     *
     * @param axis  the axis (null permitted).
     */
    public void setRangeAxis(ValueAxis axis) {
        setRangeAxis(0, axis);
    }

    /**
     * Sets a range axis and sends a {@link PlotChangeEvent} to all registered
     * listeners.
     *
     * @param index  the axis index.
     * @param axis  the axis.
     */
    public void setRangeAxis(int index, ValueAxis axis) {
        setRangeAxis(index, axis, true);
    }

    /**
     * Sets a range axis and, if requested, sends a {@link PlotChangeEvent} to
     * all registered listeners.
     *
     * @param index  the axis index.
     * @param axis  the axis.
     * @param notify  notify listeners?
     */
    public void setRangeAxis(int index, ValueAxis axis, boolean notify) {
        ValueAxis existing = (ValueAxis) this.rangeAxes.get(index);
        if (existing != null) {
            existing.removeChangeListener(this);
        }
        if (axis != null) {
            axis.setPlot(this);
        }
        this.rangeAxes.set(index, axis);
        if (axis != null) {
            axis.configure();
            axis.addChangeListener(this);
        }
        if (notify) {
            fireChangeEvent();
        }
    }

    /**
     * Sets the range axes for this plot and sends a {@link PlotChangeEvent}
     * to all registered listeners.
     *
     * @param axes  the axes (null not permitted).
     *
     * @see #setDomainAxes(CategoryAxis[])
     */
    public void setRangeAxes(ValueAxis[] axes) {
        for (int i = 0; i < axes.length; i++) {
            setRangeAxis(i, axes[i], false);
        }
        fireChangeEvent();
    }

    /**
     * Returns the index of the specified axis, or -1 if the axis
     * is not assigned to the plot.
     *
     * @param axis  the axis (null not permitted).
     *
     * @return The axis index.
     *
     * @see #getRangeAxis(int)
     * @see #getDomainAxisIndex(CategoryAxis)
     *
     * @since 1.0.7
     */
    public int getRangeAxisIndex(ValueAxis axis) {
        ParamChecks.nullNotPermitted(axis, "axis");
        int result = this.rangeAxes.indexOf(axis);
        if (result < 0) { // try the parent plot
            Plot parent = getParent();
            if (parent instanceof CategoryPlot) {
                CategoryPlot p = (CategoryPlot) parent;
                result = p.getRangeAxisIndex(axis);
            }
        }
        return result;
    }

    /**
     * Returns the range axis location.
     *
     * @return The location (never null).
     */
    public AxisLocation getRangeAxisLocation() {
        return getRangeAxisLocation(0);
    }

    /**
     * Returns the location for a range axis.
     *
     * @param index  the axis index.
     *
     * @return The location.
     *
     * @see #setRangeAxisLocation(int, AxisLocation)
     */
    public AxisLocation getRangeAxisLocation(int index) {
        AxisLocation result = null;
        if (index < this.rangeAxisLocations.size()) {
            result = (AxisLocation) this.rangeAxisLocations.get(index);
        }
        if (result == null) {
            result = AxisLocation.getOpposite(getRangeAxisLocation(0));
        }
        return result;
    }

    /**
     * Sets the location of the range axis and sends a {@link PlotChangeEvent}
     * to all registered listeners.
     *
     * @param location  the location (null not permitted).
     *
     * @see #setRangeAxisLocation(AxisLocation, boolean)
     * @see #setDomainAxisLocation(AxisLocation)
     */
    public void setRangeAxisLocation(AxisLocation location) {
        // defer argument checking...
        setRangeAxisLocation(location, true);
    }

    /**
     * Sets the location of the range axis and, if requested, sends a
     * {@link PlotChangeEvent} to all registered listeners.
     *
     * @param location  the location (null not permitted).
     * @param notify  notify listeners?
     *
     * @see #setDomainAxisLocation(AxisLocation, boolean)
     */
    public void setRangeAxisLocation(AxisLocation location, boolean notify) {
        setRangeAxisLocation(0, location, notify);
    }

    /**
     * Sets the location for a range axis and sends a {@link PlotChangeEvent}
     * to all registered listeners.
     *
     * @param index  the axis index.
     * @param location  the location.
     *
     * @see #getRangeAxisLocation(int)
     * @see #setRangeAxisLocation(int, AxisLocation, boolean)
     */
    public void setRangeAxisLocation(int index, AxisLocation location) {
        setRangeAxisLocation(index, location, true);
    }

    /**
     * Sets the location for a range axis and sends a {@link PlotChangeEvent}
     * to all registered listeners.
     *
     * @param index  the axis index.
     * @param location  the location.
     * @param notify  notify listeners?
     *
     * @see #getRangeAxisLocation(int)
     * @see #setDomainAxisLocation(int, AxisLocation, boolean)
     */
    public void setRangeAxisLocation(int index, AxisLocation location,
                                     boolean notify) {
        if (index == 0 && location == null) {
            throw new IllegalArgumentException(
                    "Null 'location' for index 0 not permitted.");
        }
        this.rangeAxisLocations.set(index, location);
        if (notify) {
            fireChangeEvent();
        }
    }

    /**
     * Returns the edge where the primary range axis is located.
     *
     * @return The edge (never null).
     */
    public RectangleEdge getRangeAxisEdge() {
        return getRangeAxisEdge(0);
    }

    /**
     * Returns the edge for a range axis.
     *
     * @param index  the axis index.
     *
     * @return The edge.
     */
    public RectangleEdge getRangeAxisEdge(int index) {
        AxisLocation location = getRangeAxisLocation(index);
        return Plot.resolveRangeAxisLocation(location, this.orientation);
    }

    /**
     * Returns the number of range axes.
     *
     * @return The axis count.
     */
    public int getRangeAxisCount() {
        return this.rangeAxes.size();
    }

    /**
     * Clears the range axes from the plot and sends a {@link PlotChangeEvent}
     * to all registered listeners.
     */
    public void clearRangeAxes() {
        for (int i = 0; i < this.rangeAxes.size(); i++) {
            ValueAxis axis = (ValueAxis) this.rangeAxes.get(i);
            if (axis != null) {
                axis.removeChangeListener(this);
            }
        }
        this.rangeAxes.clear();
        fireChangeEvent();
    }

    /**
     * Configures the range axes.
     */
    public void configureRangeAxes() {
        for (int i = 0; i < this.rangeAxes.size(); i++) {
            ValueAxis axis = (ValueAxis) this.rangeAxes.get(i);
            if (axis != null) {
                axis.configure();
            }
        }
    }

    /**
     * Returns the primary dataset for the plot.
     *
     * @return The primary dataset (possibly null).
     *
     * @see #setDataset(CategoryDataset)
     */
    public CategoryDataset getDataset() {
        return getDataset(0);
    }

    /**
     * Returns the dataset at the given index.
     *
     * @param index  the dataset index.
     *
     * @return The dataset (possibly null).
     *
     * @see #setDataset(int, CategoryDataset)
     */
    public CategoryDataset getDataset(int index) {
        CategoryDataset result = null;
        if (this.datasets.size() > index) {
            result = (CategoryDataset) this.datasets.get(index);
        }
        return result;
    }

    /**
     * Sets the dataset for the plot, replacing the existing dataset, if there
     * is one.  This method also calls the
     * {@link #datasetChanged(DatasetChangeEvent)} method, which adjusts the
     * axis ranges if necessary and sends a {@link PlotChangeEvent} to all
     * registered listeners.
     *
     * @param dataset  the dataset (null permitted).
     *
     * @see #getDataset()
     */
    public void setDataset(CategoryDataset dataset) {
        setDataset(0, dataset);
    }

    /**
     * Sets a dataset for the plot.
     *
     * @param index  the dataset index.
     * @param dataset  the dataset (null permitted).
     *
     * @see #getDataset(int)
     */
    public void setDataset(int index, CategoryDataset dataset) {

        CategoryDataset existing = (CategoryDataset) this.datasets.get(index);
        if (existing != null) {
            existing.removeChangeListener(this);
        }
        this.datasets.set(index, dataset);
        if (dataset != null) {
            dataset.addChangeListener(this);
        }

        // send a dataset change event to self...
        DatasetChangeEvent event = new DatasetChangeEvent(this, dataset);
        datasetChanged(event);

    }

    /**
     * Returns the number of datasets.
     *
     * @return The number of datasets.
     *
     * @since 1.0.2
     */
    public int getDatasetCount() {
        return this.datasets.size();
    }

    /**
     * Returns the index of the specified dataset, or -1 if the
     * dataset does not belong to the plot.
     *
     * @param dataset  the dataset (null not permitted).
     *
     * @return The index.
     *
     * @since 1.0.11
     */
    public int indexOf(CategoryDataset dataset) {
        int result = -1;
        for (int i = 0; i < this.datasets.size(); i++) {
            if (dataset == this.datasets.get(i)) {
                result = i;
                break;
            }
        }
        return result;
    }

    /**
     * Maps a dataset to a particular domain axis.
     *
     * @param index  the dataset index (zero-based).
     * @param axisIndex  the axis index (zero-based).
     *
     * @see #getDomainAxisForDataset(int)
     */
    public void mapDatasetToDomainAxis(int index, int axisIndex) {
        List axisIndices = new java.util.ArrayList(1);
        axisIndices.add(new Integer(axisIndex));
        mapDatasetToDomainAxes(index, axisIndices);
    }

    /**
     * Maps the specified dataset to the axes in the list.  Note that the
     * conversion of data values into Java2D space is always performed using
     * the first axis in the list.
     *
     * @param index  the dataset index (zero-based).
     * @param axisIndices  the axis indices (null permitted).
     *
     * @since 1.0.12
     */
    public void mapDatasetToDomainAxes(int index, List axisIndices) {
        if (index < 0) {
            throw new IllegalArgumentException("Requires 'index' >= 0.");
        }
        checkAxisIndices(axisIndices);
        Integer key = new Integer(index);
        this.datasetToDomainAxesMap.put(key, new ArrayList(axisIndices));
        // fake a dataset change event to update axes...
        datasetChanged(new DatasetChangeEvent(this, getDataset(index)));
    }

    /**
     * This method is used to perform argument checking on the list of
     * axis indices passed to mapDatasetToDomainAxes() and
     * mapDatasetToRangeAxes().
     *
     * @param indices  the list of indices (null permitted).
     */
    private void checkAxisIndices(List indices) {
        // axisIndices can be:
        // 1.  null;
        // 2.  non-empty, containing only Integer objects that are unique.
        if (indices == null) {
            return;  // OK
        }
        int count = indices.size();
        if (count == 0) {
            throw new IllegalArgumentException("Empty list not permitted.");
        }
        HashSet set = new HashSet();
        for (int i = 0; i < count; i++) {
            Object item = indices.get(i);
            if (!(item instanceof Integer)) {
                throw new IllegalArgumentException(
                        "Indices must be Integer instances.");
            }
            if (set.contains(item)) {
                throw new IllegalArgumentException("Indices must be unique.");
            }
            set.add(item);
        }
    }

    /**
     * Returns the domain axis for a dataset.  You can change the axis for a
     * dataset using the {@link #mapDatasetToDomainAxis(int, int)} method.
     *
     * @param index  the dataset index.
     *
     * @return The domain axis.
     *
     * @see #mapDatasetToDomainAxis(int, int)
     */
    public CategoryAxis getDomainAxisForDataset(int index) {
        if (index < 0) {
            throw new IllegalArgumentException("Negative 'index'.");
        }
        CategoryAxis axis;
        List axisIndices = (List) this.datasetToDomainAxesMap.get(
                new Integer(index));
        if (axisIndices != null) {
            // the first axis in the list is used for data <--> Java2D
            Integer axisIndex = (Integer) axisIndices.get(0);
            axis = getDomainAxis(axisIndex.intValue());
        }
        else {
            axis = getDomainAxis(0);
        }
        return axis;
    }

    /**
     * Maps a dataset to a particular range axis.
     *
     * @param index  the dataset index (zero-based).
     * @param axisIndex  the axis index (zero-based).
     *
     * @see #getRangeAxisForDataset(int)
     */
    public void mapDatasetToRangeAxis(int index, int axisIndex) {
        List axisIndices = new java.util.ArrayList(1);
        axisIndices.add(new Integer(axisIndex));
        mapDatasetToRangeAxes(index, axisIndices);
    }

    /**
     * Maps the specified dataset to the axes in the list.  Note that the
     * conversion of data values into Java2D space is always performed using
     * the first axis in the list.
     *
     * @param index  the dataset index (zero-based).
     * @param axisIndices  the axis indices (null permitted).
     *
     * @since 1.0.12
     */
    public void mapDatasetToRangeAxes(int index, List axisIndices) {
        if (index < 0) {
            throw new IllegalArgumentException("Requires 'index' >= 0.");
        }
        checkAxisIndices(axisIndices);
        Integer key = new Integer(index);
        this.datasetToRangeAxesMap.put(key, new ArrayList(axisIndices));
        // fake a dataset change event to update axes...
        datasetChanged(new DatasetChangeEvent(this, getDataset(index)));
    }

    /**
     * Returns the range axis for a dataset.  You can change the axis for a
     * dataset using the {@link #mapDatasetToRangeAxis(int, int)} method.
     *
     * @param index  the dataset index.
     *
     * @return The range axis.
     *
     * @see #mapDatasetToRangeAxis(int, int)
     */
    public ValueAxis getRangeAxisForDataset(int index) {
        if (index < 0) {
            throw new IllegalArgumentException("Negative 'index'.");
        }
        ValueAxis axis = null;
        List axisIndices = (List) this.datasetToRangeAxesMap.get(
                new Integer(index));
        if (axisIndices != null) {
            // the first axis in the list is used for data <--> Java2D
            Integer axisIndex = (Integer) axisIndices.get(0);
            axis = getRangeAxis(axisIndex.intValue());
        }
        else {
            axis = getRangeAxis(0);
        }
        return axis;
    }

    /**
     * Returns the number of renderer slots for this plot.
     *
     * @return The number of renderer slots.
     *
     * @since 1.0.11
     */
    public int getRendererCount() {
        return this.renderers.size();
    }

    /**
     * Returns a reference to the renderer for the plot.
     *
     * @return The renderer.
     *
     * @see #setRenderer(CategoryItemRenderer)
     */
    public CategoryItemRenderer getRenderer() {
        return getRenderer(0);
    }

    /**
     * Returns the renderer at the given index.
     *
     * @param index  the renderer index.
     *
     * @return The renderer (possibly null).
     *
     * @see #setRenderer(int, CategoryItemRenderer)
     */
    public CategoryItemRenderer getRenderer(int index) {
        CategoryItemRenderer result = null;
        if (this.renderers.size() > index) {
            result = (CategoryItemRenderer) this.renderers.get(index);
        }
        return result;
    }

    /**
     * Sets the renderer at index 0 (sometimes referred to as the "primary"
     * renderer) and sends a {@link PlotChangeEvent} to all registered
     * listeners.
     *
     * @param renderer  the renderer (null permitted.
     *
     * @see #getRenderer()
     */
    public void setRenderer(CategoryItemRenderer renderer) {
        setRenderer(0, renderer, true);
    }

    /**
     * Sets the renderer at index 0 (sometimes referred to as the "primary"
     * renderer) and, if requested, sends a {@link PlotChangeEvent} to all
     * registered listeners.
     * 

* You can set the renderer to null, but this is not * recommended because: *

    *
  • no data will be displayed;
  • *
  • the plot background will not be painted;
  • *
* * @param renderer the renderer (null permitted). * @param notify notify listeners? * * @see #getRenderer() */ public void setRenderer(CategoryItemRenderer renderer, boolean notify) { setRenderer(0, renderer, notify); } /** * Sets the renderer at the specified index and sends a * {@link PlotChangeEvent} to all registered listeners. * * @param index the index. * @param renderer the renderer (null permitted). * * @see #getRenderer(int) * @see #setRenderer(int, CategoryItemRenderer, boolean) */ public void setRenderer(int index, CategoryItemRenderer renderer) { setRenderer(index, renderer, true); } /** * Sets a renderer. A {@link PlotChangeEvent} is sent to all registered * listeners. * * @param index the index. * @param renderer the renderer (null permitted). * @param notify notify listeners? * * @see #getRenderer(int) */ public void setRenderer(int index, CategoryItemRenderer renderer, boolean notify) { // stop listening to the existing renderer... CategoryItemRenderer existing = (CategoryItemRenderer) this.renderers.get(index); if (existing != null) { existing.removeChangeListener(this); } // register the new renderer... this.renderers.set(index, renderer); if (renderer != null) { renderer.setPlot(this); renderer.addChangeListener(this); } configureDomainAxes(); configureRangeAxes(); if (notify) { fireChangeEvent(); } } /** * Sets the renderers for this plot and sends a {@link PlotChangeEvent} * to all registered listeners. * * @param renderers the renderers. */ public void setRenderers(CategoryItemRenderer[] renderers) { for (int i = 0; i < renderers.length; i++) { setRenderer(i, renderers[i], false); } fireChangeEvent(); } /** * Returns the renderer for the specified dataset. If the dataset doesn't * belong to the plot, this method will return null. * * @param dataset the dataset (null permitted). * * @return The renderer (possibly null). */ public CategoryItemRenderer getRendererForDataset(CategoryDataset dataset) { CategoryItemRenderer result = null; for (int i = 0; i < this.datasets.size(); i++) { if (this.datasets.get(i) == dataset) { result = (CategoryItemRenderer) this.renderers.get(i); break; } } return result; } /** * Returns the index of the specified renderer, or -1 if the * renderer is not assigned to this plot. * * @param renderer the renderer (null permitted). * * @return The renderer index. */ public int getIndexOf(CategoryItemRenderer renderer) { return this.renderers.indexOf(renderer); } /** * Returns the dataset rendering order. * * @return The order (never null). * * @see #setDatasetRenderingOrder(DatasetRenderingOrder) */ public DatasetRenderingOrder getDatasetRenderingOrder() { return this.renderingOrder; } /** * Sets the rendering order and sends a {@link PlotChangeEvent} to all * registered listeners. By default, the plot renders the primary dataset * last (so that the primary dataset overlays the secondary datasets). You * can reverse this if you want to. * * @param order the rendering order (null not permitted). * * @see #getDatasetRenderingOrder() */ public void setDatasetRenderingOrder(DatasetRenderingOrder order) { ParamChecks.nullNotPermitted(order, "order"); this.renderingOrder = order; fireChangeEvent(); } /** * Returns the order in which the columns are rendered. The default value * is SortOrder.ASCENDING. * * @return The column rendering order (never null). * * @see #setColumnRenderingOrder(SortOrder) */ public SortOrder getColumnRenderingOrder() { return this.columnRenderingOrder; } /** * Sets the column order in which the items in each dataset should be * rendered and sends a {@link PlotChangeEvent} to all registered * listeners. Note that this affects the order in which items are drawn, * NOT their position in the chart. * * @param order the order (null not permitted). * * @see #getColumnRenderingOrder() * @see #setRowRenderingOrder(SortOrder) */ public void setColumnRenderingOrder(SortOrder order) { ParamChecks.nullNotPermitted(order, "order"); this.columnRenderingOrder = order; fireChangeEvent(); } /** * Returns the order in which the rows should be rendered. The default * value is SortOrder.ASCENDING. * * @return The order (never null). * * @see #setRowRenderingOrder(SortOrder) */ public SortOrder getRowRenderingOrder() { return this.rowRenderingOrder; } /** * Sets the row order in which the items in each dataset should be * rendered and sends a {@link PlotChangeEvent} to all registered * listeners. Note that this affects the order in which items are drawn, * NOT their position in the chart. * * @param order the order (null not permitted). * * @see #getRowRenderingOrder() * @see #setColumnRenderingOrder(SortOrder) */ public void setRowRenderingOrder(SortOrder order) { ParamChecks.nullNotPermitted(order, "order"); this.rowRenderingOrder = order; fireChangeEvent(); } /** * Returns the flag that controls whether the domain grid-lines are visible. * * @return The true or false. * * @see #setDomainGridlinesVisible(boolean) */ public boolean isDomainGridlinesVisible() { return this.domainGridlinesVisible; } /** * Sets the flag that controls whether or not grid-lines are drawn against * the domain axis. *

* If the flag value changes, a {@link PlotChangeEvent} is sent to all * registered listeners. * * @param visible the new value of the flag. * * @see #isDomainGridlinesVisible() */ public void setDomainGridlinesVisible(boolean visible) { if (this.domainGridlinesVisible != visible) { this.domainGridlinesVisible = visible; fireChangeEvent(); } } /** * Returns the position used for the domain gridlines. * * @return The gridline position (never null). * * @see #setDomainGridlinePosition(CategoryAnchor) */ public CategoryAnchor getDomainGridlinePosition() { return this.domainGridlinePosition; } /** * Sets the position used for the domain gridlines and sends a * {@link PlotChangeEvent} to all registered listeners. * * @param position the position (null not permitted). * * @see #getDomainGridlinePosition() */ public void setDomainGridlinePosition(CategoryAnchor position) { ParamChecks.nullNotPermitted(position, "position"); this.domainGridlinePosition = position; fireChangeEvent(); } /** * Returns the stroke used to draw grid-lines against the domain axis. * * @return The stroke (never null). * * @see #setDomainGridlineStroke(Stroke) */ public Stroke getDomainGridlineStroke() { return this.domainGridlineStroke; } /** * Sets the stroke used to draw grid-lines against the domain axis and * sends a {@link PlotChangeEvent} to all registered listeners. * * @param stroke the stroke (null not permitted). * * @see #getDomainGridlineStroke() */ public void setDomainGridlineStroke(Stroke stroke) { ParamChecks.nullNotPermitted(stroke, "stroke"); this.domainGridlineStroke = stroke; fireChangeEvent(); } /** * Returns the paint used to draw grid-lines against the domain axis. * * @return The paint (never null). * * @see #setDomainGridlinePaint(Paint) */ public Paint getDomainGridlinePaint() { return this.domainGridlinePaint; } /** * Sets the paint used to draw the grid-lines (if any) against the domain * axis and sends a {@link PlotChangeEvent} to all registered listeners. * * @param paint the paint (null not permitted). * * @see #getDomainGridlinePaint() */ public void setDomainGridlinePaint(Paint paint) { ParamChecks.nullNotPermitted(paint, "paint"); this.domainGridlinePaint = paint; fireChangeEvent(); } /** * Returns a flag that controls whether or not a zero baseline is * displayed for the range axis. * * @return A boolean. * * @see #setRangeZeroBaselineVisible(boolean) * * @since 1.0.13 */ public boolean isRangeZeroBaselineVisible() { return this.rangeZeroBaselineVisible; } /** * Sets the flag that controls whether or not the zero baseline is * displayed for the range axis, and sends a {@link PlotChangeEvent} to * all registered listeners. * * @param visible the flag. * * @see #isRangeZeroBaselineVisible() * * @since 1.0.13 */ public void setRangeZeroBaselineVisible(boolean visible) { this.rangeZeroBaselineVisible = visible; fireChangeEvent(); } /** * Returns the stroke used for the zero baseline against the range axis. * * @return The stroke (never null). * * @see #setRangeZeroBaselineStroke(Stroke) * * @since 1.0.13 */ public Stroke getRangeZeroBaselineStroke() { return this.rangeZeroBaselineStroke; } /** * Sets the stroke for the zero baseline for the range axis, * and sends a {@link PlotChangeEvent} to all registered listeners. * * @param stroke the stroke (null not permitted). * * @see #getRangeZeroBaselineStroke() * * @since 1.0.13 */ public void setRangeZeroBaselineStroke(Stroke stroke) { ParamChecks.nullNotPermitted(stroke, "stroke"); this.rangeZeroBaselineStroke = stroke; fireChangeEvent(); } /** * Returns the paint for the zero baseline (if any) plotted against the * range axis. * * @return The paint (never null). * * @see #setRangeZeroBaselinePaint(Paint) * * @since 1.0.13 */ public Paint getRangeZeroBaselinePaint() { return this.rangeZeroBaselinePaint; } /** * Sets the paint for the zero baseline plotted against the range axis and * sends a {@link PlotChangeEvent} to all registered listeners. * * @param paint the paint (null not permitted). * * @see #getRangeZeroBaselinePaint() * * @since 1.0.13 */ public void setRangeZeroBaselinePaint(Paint paint) { ParamChecks.nullNotPermitted(paint, "paint"); this.rangeZeroBaselinePaint = paint; fireChangeEvent(); } /** * Returns the flag that controls whether the range grid-lines are visible. * * @return The flag. * * @see #setRangeGridlinesVisible(boolean) */ public boolean isRangeGridlinesVisible() { return this.rangeGridlinesVisible; } /** * Sets the flag that controls whether or not grid-lines are drawn against * the range axis. If the flag changes value, a {@link PlotChangeEvent} is * sent to all registered listeners. * * @param visible the new value of the flag. * * @see #isRangeGridlinesVisible() */ public void setRangeGridlinesVisible(boolean visible) { if (this.rangeGridlinesVisible != visible) { this.rangeGridlinesVisible = visible; fireChangeEvent(); } } /** * Returns the stroke used to draw the grid-lines against the range axis. * * @return The stroke (never null). * * @see #setRangeGridlineStroke(Stroke) */ public Stroke getRangeGridlineStroke() { return this.rangeGridlineStroke; } /** * Sets the stroke used to draw the grid-lines against the range axis and * sends a {@link PlotChangeEvent} to all registered listeners. * * @param stroke the stroke (null not permitted). * * @see #getRangeGridlineStroke() */ public void setRangeGridlineStroke(Stroke stroke) { ParamChecks.nullNotPermitted(stroke, "stroke"); this.rangeGridlineStroke = stroke; fireChangeEvent(); } /** * Returns the paint used to draw the grid-lines against the range axis. * * @return The paint (never null). * * @see #setRangeGridlinePaint(Paint) */ public Paint getRangeGridlinePaint() { return this.rangeGridlinePaint; } /** * Sets the paint used to draw the grid lines against the range axis and * sends a {@link PlotChangeEvent} to all registered listeners. * * @param paint the paint (null not permitted). * * @see #getRangeGridlinePaint() */ public void setRangeGridlinePaint(Paint paint) { ParamChecks.nullNotPermitted(paint, "paint"); this.rangeGridlinePaint = paint; fireChangeEvent(); } /** * Returns true if the range axis minor grid is visible, and * false otherwise. * * @return A boolean. * * @see #setRangeMinorGridlinesVisible(boolean) * * @since 1.0.13 */ public boolean isRangeMinorGridlinesVisible() { return this.rangeMinorGridlinesVisible; } /** * Sets the flag that controls whether or not the range axis minor grid * lines are visible. *

* If the flag value is changed, a {@link PlotChangeEvent} is sent to all * registered listeners. * * @param visible the new value of the flag. * * @see #isRangeMinorGridlinesVisible() * * @since 1.0.13 */ public void setRangeMinorGridlinesVisible(boolean visible) { if (this.rangeMinorGridlinesVisible != visible) { this.rangeMinorGridlinesVisible = visible; fireChangeEvent(); } } /** * Returns the stroke for the minor grid lines (if any) plotted against the * range axis. * * @return The stroke (never null). * * @see #setRangeMinorGridlineStroke(Stroke) * * @since 1.0.13 */ public Stroke getRangeMinorGridlineStroke() { return this.rangeMinorGridlineStroke; } /** * Sets the stroke for the minor grid lines plotted against the range axis, * and sends a {@link PlotChangeEvent} to all registered listeners. * * @param stroke the stroke (null not permitted). * * @see #getRangeMinorGridlineStroke() * * @since 1.0.13 */ public void setRangeMinorGridlineStroke(Stroke stroke) { ParamChecks.nullNotPermitted(stroke, "stroke"); this.rangeMinorGridlineStroke = stroke; fireChangeEvent(); } /** * Returns the paint for the minor grid lines (if any) plotted against the * range axis. * * @return The paint (never null). * * @see #setRangeMinorGridlinePaint(Paint) * * @since 1.0.13 */ public Paint getRangeMinorGridlinePaint() { return this.rangeMinorGridlinePaint; } /** * Sets the paint for the minor grid lines plotted against the range axis * and sends a {@link PlotChangeEvent} to all registered listeners. * * @param paint the paint (null not permitted). * * @see #getRangeMinorGridlinePaint() * * @since 1.0.13 */ public void setRangeMinorGridlinePaint(Paint paint) { ParamChecks.nullNotPermitted(paint, "paint"); this.rangeMinorGridlinePaint = paint; fireChangeEvent(); } /** * Returns the fixed legend items, if any. * * @return The legend items (possibly null). * * @see #setFixedLegendItems(LegendItemCollection) */ public LegendItemCollection getFixedLegendItems() { return this.fixedLegendItems; } /** * Sets the fixed legend items for the plot. Leave this set to * null if you prefer the legend items to be created * automatically. * * @param items the legend items (null permitted). * * @see #getFixedLegendItems() */ public void setFixedLegendItems(LegendItemCollection items) { this.fixedLegendItems = items; fireChangeEvent(); } /** * Returns the legend items for the plot. By default, this method creates * a legend item for each series in each of the datasets. You can change * this behaviour by overriding this method. * * @return The legend items. */ @Override public LegendItemCollection getLegendItems() { if (this.fixedLegendItems != null) { return this.fixedLegendItems; } LegendItemCollection result = new LegendItemCollection(); // get the legend items for the datasets... int count = this.datasets.size(); for (int datasetIndex = 0; datasetIndex < count; datasetIndex++) { CategoryDataset dataset = getDataset(datasetIndex); if (dataset != null) { CategoryItemRenderer renderer = getRenderer(datasetIndex); if (renderer != null) { result.addAll(renderer.getLegendItems()); } } } return result; } /** * Handles a 'click' on the plot by updating the anchor value. * * @param x x-coordinate of the click (in Java2D space). * @param y y-coordinate of the click (in Java2D space). * @param info information about the plot's dimensions. * */ @Override public void handleClick(int x, int y, PlotRenderingInfo info) { Rectangle2D dataArea = info.getDataArea(); if (dataArea.contains(x, y)) { // set the anchor value for the range axis... double java2D = 0.0; if (this.orientation == PlotOrientation.HORIZONTAL) { java2D = x; } else if (this.orientation == PlotOrientation.VERTICAL) { java2D = y; } RectangleEdge edge = Plot.resolveRangeAxisLocation( getRangeAxisLocation(), this.orientation); double value = getRangeAxis().java2DToValue( java2D, info.getDataArea(), edge); setAnchorValue(value); setRangeCrosshairValue(value); } } /** * Zooms (in or out) on the plot's value axis. *

* If the value 0.0 is passed in as the zoom percent, the auto-range * calculation for the axis is restored (which sets the range to include * the minimum and maximum data values, thus displaying all the data). * * @param percent the zoom amount. */ @Override public void zoom(double percent) { if (percent > 0.0) { double range = getRangeAxis().getRange().getLength(); double scaledRange = range * percent; getRangeAxis().setRange(this.anchorValue - scaledRange / 2.0, this.anchorValue + scaledRange / 2.0); } else { getRangeAxis().setAutoRange(true); } } /** * Receives notification of a change to an {@link Annotation} added to * this plot. * * @param event information about the event (not used here). * * @since 1.0.14 */ @Override public void annotationChanged(AnnotationChangeEvent event) { if (getParent() != null) { getParent().annotationChanged(event); } else { PlotChangeEvent e = new PlotChangeEvent(this); notifyListeners(e); } } /** * Receives notification of a change to the plot's dataset. *

* The range axis bounds will be recalculated if necessary. * * @param event information about the event (not used here). */ @Override public void datasetChanged(DatasetChangeEvent event) { int count = this.rangeAxes.size(); for (int axisIndex = 0; axisIndex < count; axisIndex++) { ValueAxis yAxis = getRangeAxis(axisIndex); if (yAxis != null) { yAxis.configure(); } } if (getParent() != null) { getParent().datasetChanged(event); } else { PlotChangeEvent e = new PlotChangeEvent(this); e.setType(ChartChangeEventType.DATASET_UPDATED); notifyListeners(e); } } /** * Receives notification of a renderer change event. * * @param event the event. */ @Override public void rendererChanged(RendererChangeEvent event) { Plot parent = getParent(); if (parent != null) { if (parent instanceof RendererChangeListener) { RendererChangeListener rcl = (RendererChangeListener) parent; rcl.rendererChanged(event); } else { // this should never happen with the existing code, but throw // an exception in case future changes make it possible... throw new RuntimeException( "The renderer has changed and I don't know what to do!"); } } else { configureRangeAxes(); PlotChangeEvent e = new PlotChangeEvent(this); notifyListeners(e); } } /** * Adds a marker for display (in the foreground) against the domain axis and * sends a {@link PlotChangeEvent} to all registered listeners. Typically a * marker will be drawn by the renderer as a line perpendicular to the * domain axis, however this is entirely up to the renderer. * * @param marker the marker (null not permitted). * * @see #removeDomainMarker(Marker) */ public void addDomainMarker(CategoryMarker marker) { addDomainMarker(marker, Layer.FOREGROUND); } /** * Adds a marker for display against the domain axis and sends a * {@link PlotChangeEvent} to all registered listeners. Typically a marker * will be drawn by the renderer as a line perpendicular to the domain * axis, however this is entirely up to the renderer. * * @param marker the marker (null not permitted). * @param layer the layer (foreground or background) (null * not permitted). * * @see #removeDomainMarker(Marker, Layer) */ public void addDomainMarker(CategoryMarker marker, Layer layer) { addDomainMarker(0, marker, layer); } /** * Adds a marker for display by a particular renderer and sends a * {@link PlotChangeEvent} to all registered listeners. *

* Typically a marker will be drawn by the renderer as a line perpendicular * to a domain axis, however this is entirely up to the renderer. * * @param index the renderer index. * @param marker the marker (null not permitted). * @param layer the layer (null not permitted). * * @see #removeDomainMarker(int, Marker, Layer) */ public void addDomainMarker(int index, CategoryMarker marker, Layer layer) { addDomainMarker(index, marker, layer, true); } /** * Adds a marker for display by a particular renderer and, if requested, * sends a {@link PlotChangeEvent} to all registered listeners. *

* Typically a marker will be drawn by the renderer as a line perpendicular * to a domain axis, however this is entirely up to the renderer. * * @param index the renderer index. * @param marker the marker (null not permitted). * @param layer the layer (null not permitted). * @param notify notify listeners? * * @since 1.0.10 * * @see #removeDomainMarker(int, Marker, Layer, boolean) */ public void addDomainMarker(int index, CategoryMarker marker, Layer layer, boolean notify) { ParamChecks.nullNotPermitted(marker, "marker"); ParamChecks.nullNotPermitted(layer, "layer"); Collection markers; if (layer == Layer.FOREGROUND) { markers = (Collection) this.foregroundDomainMarkers.get( new Integer(index)); if (markers == null) { markers = new java.util.ArrayList(); this.foregroundDomainMarkers.put(new Integer(index), markers); } markers.add(marker); } else if (layer == Layer.BACKGROUND) { markers = (Collection) this.backgroundDomainMarkers.get( new Integer(index)); if (markers == null) { markers = new java.util.ArrayList(); this.backgroundDomainMarkers.put(new Integer(index), markers); } markers.add(marker); } marker.addChangeListener(this); if (notify) { fireChangeEvent(); } } /** * Clears all the domain markers for the plot and sends a * {@link PlotChangeEvent} to all registered listeners. * * @see #clearRangeMarkers() */ public void clearDomainMarkers() { if (this.backgroundDomainMarkers != null) { Set keys = this.backgroundDomainMarkers.keySet(); Iterator iterator = keys.iterator(); while (iterator.hasNext()) { Integer key = (Integer) iterator.next(); clearDomainMarkers(key.intValue()); } this.backgroundDomainMarkers.clear(); } if (this.foregroundDomainMarkers != null) { Set keys = this.foregroundDomainMarkers.keySet(); Iterator iterator = keys.iterator(); while (iterator.hasNext()) { Integer key = (Integer) iterator.next(); clearDomainMarkers(key.intValue()); } this.foregroundDomainMarkers.clear(); } fireChangeEvent(); } /** * Returns the list of domain markers (read only) for the specified layer. * * @param layer the layer (foreground or background). * * @return The list of domain markers. */ public Collection getDomainMarkers(Layer layer) { return getDomainMarkers(0, layer); } /** * Returns a collection of domain markers for a particular renderer and * layer. * * @param index the renderer index. * @param layer the layer. * * @return A collection of markers (possibly null). */ public Collection getDomainMarkers(int index, Layer layer) { Collection result = null; Integer key = new Integer(index); if (layer == Layer.FOREGROUND) { result = (Collection) this.foregroundDomainMarkers.get(key); } else if (layer == Layer.BACKGROUND) { result = (Collection) this.backgroundDomainMarkers.get(key); } if (result != null) { result = Collections.unmodifiableCollection(result); } return result; } /** * Clears all the domain markers for the specified renderer. * * @param index the renderer index. * * @see #clearRangeMarkers(int) */ public void clearDomainMarkers(int index) { Integer key = new Integer(index); if (this.backgroundDomainMarkers != null) { Collection markers = (Collection) this.backgroundDomainMarkers.get(key); if (markers != null) { Iterator iterator = markers.iterator(); while (iterator.hasNext()) { Marker m = (Marker) iterator.next(); m.removeChangeListener(this); } markers.clear(); } } if (this.foregroundDomainMarkers != null) { Collection markers = (Collection) this.foregroundDomainMarkers.get(key); if (markers != null) { Iterator iterator = markers.iterator(); while (iterator.hasNext()) { Marker m = (Marker) iterator.next(); m.removeChangeListener(this); } markers.clear(); } } fireChangeEvent(); } /** * Removes a marker for the domain axis and sends a {@link PlotChangeEvent} * to all registered listeners. * * @param marker the marker. * * @return A boolean indicating whether or not the marker was actually * removed. * * @since 1.0.7 */ public boolean removeDomainMarker(Marker marker) { return removeDomainMarker(marker, Layer.FOREGROUND); } /** * Removes a marker for the domain axis in the specified layer and sends a * {@link PlotChangeEvent} to all registered listeners. * * @param marker the marker (null not permitted). * @param layer the layer (foreground or background). * * @return A boolean indicating whether or not the marker was actually * removed. * * @since 1.0.7 */ public boolean removeDomainMarker(Marker marker, Layer layer) { return removeDomainMarker(0, marker, layer); } /** * Removes a marker for a specific dataset/renderer and sends a * {@link PlotChangeEvent} to all registered listeners. * * @param index the dataset/renderer index. * @param marker the marker. * @param layer the layer (foreground or background). * * @return A boolean indicating whether or not the marker was actually * removed. * * @since 1.0.7 */ public boolean removeDomainMarker(int index, Marker marker, Layer layer) { return removeDomainMarker(index, marker, layer, true); } /** * Removes a marker for a specific dataset/renderer and, if requested, * sends a {@link PlotChangeEvent} to all registered listeners. * * @param index the dataset/renderer index. * @param marker the marker. * @param layer the layer (foreground or background). * @param notify notify listeners? * * @return A boolean indicating whether or not the marker was actually * removed. * * @since 1.0.10 */ public boolean removeDomainMarker(int index, Marker marker, Layer layer, boolean notify) { ArrayList markers; if (layer == Layer.FOREGROUND) { markers = (ArrayList) this.foregroundDomainMarkers.get(new Integer( index)); } else { markers = (ArrayList) this.backgroundDomainMarkers.get(new Integer( index)); } if (markers == null) { return false; } boolean removed = markers.remove(marker); if (removed && notify) { fireChangeEvent(); } return removed; } /** * Adds a marker for display (in the foreground) against the range axis and * sends a {@link PlotChangeEvent} to all registered listeners. Typically a * marker will be drawn by the renderer as a line perpendicular to the * range axis, however this is entirely up to the renderer. * * @param marker the marker (null not permitted). * * @see #removeRangeMarker(Marker) */ public void addRangeMarker(Marker marker) { addRangeMarker(marker, Layer.FOREGROUND); } /** * Adds a marker for display against the range axis and sends a * {@link PlotChangeEvent} to all registered listeners. Typically a marker * will be drawn by the renderer as a line perpendicular to the range axis, * however this is entirely up to the renderer. * * @param marker the marker (null not permitted). * @param layer the layer (foreground or background) (null * not permitted). * * @see #removeRangeMarker(Marker, Layer) */ public void addRangeMarker(Marker marker, Layer layer) { addRangeMarker(0, marker, layer); } /** * Adds a marker for display by a particular renderer and sends a * {@link PlotChangeEvent} to all registered listeners. *

* Typically a marker will be drawn by the renderer as a line perpendicular * to a range axis, however this is entirely up to the renderer. * * @param index the renderer index. * @param marker the marker. * @param layer the layer. * * @see #removeRangeMarker(int, Marker, Layer) */ public void addRangeMarker(int index, Marker marker, Layer layer) { addRangeMarker(index, marker, layer, true); } /** * Adds a marker for display by a particular renderer and sends a * {@link PlotChangeEvent} to all registered listeners. *

* Typically a marker will be drawn by the renderer as a line perpendicular * to a range axis, however this is entirely up to the renderer. * * @param index the renderer index. * @param marker the marker. * @param layer the layer. * @param notify notify listeners? * * @since 1.0.10 * * @see #removeRangeMarker(int, Marker, Layer, boolean) */ public void addRangeMarker(int index, Marker marker, Layer layer, boolean notify) { Collection markers; if (layer == Layer.FOREGROUND) { markers = (Collection) this.foregroundRangeMarkers.get( new Integer(index)); if (markers == null) { markers = new java.util.ArrayList(); this.foregroundRangeMarkers.put(new Integer(index), markers); } markers.add(marker); } else if (layer == Layer.BACKGROUND) { markers = (Collection) this.backgroundRangeMarkers.get( new Integer(index)); if (markers == null) { markers = new java.util.ArrayList(); this.backgroundRangeMarkers.put(new Integer(index), markers); } markers.add(marker); } marker.addChangeListener(this); if (notify) { fireChangeEvent(); } } /** * Clears all the range markers for the plot and sends a * {@link PlotChangeEvent} to all registered listeners. * * @see #clearDomainMarkers() */ public void clearRangeMarkers() { if (this.backgroundRangeMarkers != null) { Set keys = this.backgroundRangeMarkers.keySet(); Iterator iterator = keys.iterator(); while (iterator.hasNext()) { Integer key = (Integer) iterator.next(); clearRangeMarkers(key.intValue()); } this.backgroundRangeMarkers.clear(); } if (this.foregroundRangeMarkers != null) { Set keys = this.foregroundRangeMarkers.keySet(); Iterator iterator = keys.iterator(); while (iterator.hasNext()) { Integer key = (Integer) iterator.next(); clearRangeMarkers(key.intValue()); } this.foregroundRangeMarkers.clear(); } fireChangeEvent(); } /** * Returns the list of range markers (read only) for the specified layer. * * @param layer the layer (foreground or background). * * @return The list of range markers. * * @see #getRangeMarkers(int, Layer) */ public Collection getRangeMarkers(Layer layer) { return getRangeMarkers(0, layer); } /** * Returns a collection of range markers for a particular renderer and * layer. * * @param index the renderer index. * @param layer the layer. * * @return A collection of markers (possibly null). */ public Collection getRangeMarkers(int index, Layer layer) { Collection result = null; Integer key = new Integer(index); if (layer == Layer.FOREGROUND) { result = (Collection) this.foregroundRangeMarkers.get(key); } else if (layer == Layer.BACKGROUND) { result = (Collection) this.backgroundRangeMarkers.get(key); } if (result != null) { result = Collections.unmodifiableCollection(result); } return result; } /** * Clears all the range markers for the specified renderer. * * @param index the renderer index. * * @see #clearDomainMarkers(int) */ public void clearRangeMarkers(int index) { Integer key = new Integer(index); if (this.backgroundRangeMarkers != null) { Collection markers = (Collection) this.backgroundRangeMarkers.get(key); if (markers != null) { Iterator iterator = markers.iterator(); while (iterator.hasNext()) { Marker m = (Marker) iterator.next(); m.removeChangeListener(this); } markers.clear(); } } if (this.foregroundRangeMarkers != null) { Collection markers = (Collection) this.foregroundRangeMarkers.get(key); if (markers != null) { Iterator iterator = markers.iterator(); while (iterator.hasNext()) { Marker m = (Marker) iterator.next(); m.removeChangeListener(this); } markers.clear(); } } fireChangeEvent(); } /** * Removes a marker for the range axis and sends a {@link PlotChangeEvent} * to all registered listeners. * * @param marker the marker. * * @return A boolean indicating whether or not the marker was actually * removed. * * @since 1.0.7 * * @see #addRangeMarker(Marker) */ public boolean removeRangeMarker(Marker marker) { return removeRangeMarker(marker, Layer.FOREGROUND); } /** * Removes a marker for the range axis in the specified layer and sends a * {@link PlotChangeEvent} to all registered listeners. * * @param marker the marker (null not permitted). * @param layer the layer (foreground or background). * * @return A boolean indicating whether or not the marker was actually * removed. * * @since 1.0.7 * * @see #addRangeMarker(Marker, Layer) */ public boolean removeRangeMarker(Marker marker, Layer layer) { return removeRangeMarker(0, marker, layer); } /** * Removes a marker for a specific dataset/renderer and sends a * {@link PlotChangeEvent} to all registered listeners. * * @param index the dataset/renderer index. * @param marker the marker. * @param layer the layer (foreground or background). * * @return A boolean indicating whether or not the marker was actually * removed. * * @since 1.0.7 * * @see #addRangeMarker(int, Marker, Layer) */ public boolean removeRangeMarker(int index, Marker marker, Layer layer) { return removeRangeMarker(index, marker, layer, true); } /** * Removes a marker for a specific dataset/renderer and sends a * {@link PlotChangeEvent} to all registered listeners. * * @param index the dataset/renderer index. * @param marker the marker. * @param layer the layer (foreground or background). * @param notify notify listeners. * * @return A boolean indicating whether or not the marker was actually * removed. * * @since 1.0.10 * * @see #addRangeMarker(int, Marker, Layer, boolean) */ public boolean removeRangeMarker(int index, Marker marker, Layer layer, boolean notify) { ParamChecks.nullNotPermitted(marker, "marker"); ArrayList markers; if (layer == Layer.FOREGROUND) { markers = (ArrayList) this.foregroundRangeMarkers.get(new Integer( index)); } else { markers = (ArrayList) this.backgroundRangeMarkers.get(new Integer( index)); } if (markers == null) { return false; } boolean removed = markers.remove(marker); if (removed && notify) { fireChangeEvent(); } return removed; } /** * Returns the flag that controls whether or not the domain crosshair is * displayed by the plot. * * @return A boolean. * * @since 1.0.11 * * @see #setDomainCrosshairVisible(boolean) */ public boolean isDomainCrosshairVisible() { return this.domainCrosshairVisible; } /** * Sets the flag that controls whether or not the domain crosshair is * displayed by the plot, and sends a {@link PlotChangeEvent} to all * registered listeners. * * @param flag the new flag value. * * @since 1.0.11 * * @see #isDomainCrosshairVisible() * @see #setRangeCrosshairVisible(boolean) */ public void setDomainCrosshairVisible(boolean flag) { if (this.domainCrosshairVisible != flag) { this.domainCrosshairVisible = flag; fireChangeEvent(); } } /** * Returns the row key for the domain crosshair. * * @return The row key. * * @since 1.0.11 */ public Comparable getDomainCrosshairRowKey() { return this.domainCrosshairRowKey; } /** * Sets the row key for the domain crosshair and sends a * {PlotChangeEvent} to all registered listeners. * * @param key the key. * * @since 1.0.11 */ public void setDomainCrosshairRowKey(Comparable key) { setDomainCrosshairRowKey(key, true); } /** * Sets the row key for the domain crosshair and, if requested, sends a * {PlotChangeEvent} to all registered listeners. * * @param key the key. * @param notify notify listeners? * * @since 1.0.11 */ public void setDomainCrosshairRowKey(Comparable key, boolean notify) { this.domainCrosshairRowKey = key; if (notify) { fireChangeEvent(); } } /** * Returns the column key for the domain crosshair. * * @return The column key. * * @since 1.0.11 */ public Comparable getDomainCrosshairColumnKey() { return this.domainCrosshairColumnKey; } /** * Sets the column key for the domain crosshair and sends * a {@link PlotChangeEvent} to all registered listeners. * * @param key the key. * * @since 1.0.11 */ public void setDomainCrosshairColumnKey(Comparable key) { setDomainCrosshairColumnKey(key, true); } /** * Sets the column key for the domain crosshair and, if requested, sends * a {@link PlotChangeEvent} to all registered listeners. * * @param key the key. * @param notify notify listeners? * * @since 1.0.11 */ public void setDomainCrosshairColumnKey(Comparable key, boolean notify) { this.domainCrosshairColumnKey = key; if (notify) { fireChangeEvent(); } } /** * Returns the dataset index for the crosshair. * * @return The dataset index. * * @since 1.0.11 */ public int getCrosshairDatasetIndex() { return this.crosshairDatasetIndex; } /** * Sets the dataset index for the crosshair and sends a * {@link PlotChangeEvent} to all registered listeners. * * @param index the index. * * @since 1.0.11 */ public void setCrosshairDatasetIndex(int index) { setCrosshairDatasetIndex(index, true); } /** * Sets the dataset index for the crosshair and, if requested, sends a * {@link PlotChangeEvent} to all registered listeners. * * @param index the index. * @param notify notify listeners? * * @since 1.0.11 */ public void setCrosshairDatasetIndex(int index, boolean notify) { this.crosshairDatasetIndex = index; if (notify) { fireChangeEvent(); } } /** * Returns the paint used to draw the domain crosshair. * * @return The paint (never null). * * @since 1.0.11 * * @see #setDomainCrosshairPaint(Paint) * @see #getDomainCrosshairStroke() */ public Paint getDomainCrosshairPaint() { return this.domainCrosshairPaint; } /** * Sets the paint used to draw the domain crosshair. * * @param paint the paint (null not permitted). * * @since 1.0.11 * * @see #getDomainCrosshairPaint() */ public void setDomainCrosshairPaint(Paint paint) { ParamChecks.nullNotPermitted(paint, "paint"); this.domainCrosshairPaint = paint; fireChangeEvent(); } /** * Returns the stroke used to draw the domain crosshair. * * @return The stroke (never null). * * @since 1.0.11 * * @see #setDomainCrosshairStroke(Stroke) * @see #getDomainCrosshairPaint() */ public Stroke getDomainCrosshairStroke() { return this.domainCrosshairStroke; } /** * Sets the stroke used to draw the domain crosshair, and sends a * {@link PlotChangeEvent} to all registered listeners. * * @param stroke the stroke (null not permitted). * * @since 1.0.11 * * @see #getDomainCrosshairStroke() */ public void setDomainCrosshairStroke(Stroke stroke) { ParamChecks.nullNotPermitted(stroke, "stroke"); this.domainCrosshairStroke = stroke; } /** * Returns a flag indicating whether or not the range crosshair is visible. * * @return The flag. * * @see #setRangeCrosshairVisible(boolean) */ public boolean isRangeCrosshairVisible() { return this.rangeCrosshairVisible; } /** * Sets the flag indicating whether or not the range crosshair is visible. * * @param flag the new value of the flag. * * @see #isRangeCrosshairVisible() */ public void setRangeCrosshairVisible(boolean flag) { if (this.rangeCrosshairVisible != flag) { this.rangeCrosshairVisible = flag; fireChangeEvent(); } } /** * Returns a flag indicating whether or not the crosshair should "lock-on" * to actual data values. * * @return The flag. * * @see #setRangeCrosshairLockedOnData(boolean) */ public boolean isRangeCrosshairLockedOnData() { return this.rangeCrosshairLockedOnData; } /** * Sets the flag indicating whether or not the range crosshair should * "lock-on" to actual data values, and sends a {@link PlotChangeEvent} * to all registered listeners. * * @param flag the flag. * * @see #isRangeCrosshairLockedOnData() */ public void setRangeCrosshairLockedOnData(boolean flag) { if (this.rangeCrosshairLockedOnData != flag) { this.rangeCrosshairLockedOnData = flag; fireChangeEvent(); } } /** * Returns the range crosshair value. * * @return The value. * * @see #setRangeCrosshairValue(double) */ public double getRangeCrosshairValue() { return this.rangeCrosshairValue; } /** * Sets the range crosshair value and, if the crosshair is visible, sends * a {@link PlotChangeEvent} to all registered listeners. * * @param value the new value. * * @see #getRangeCrosshairValue() */ public void setRangeCrosshairValue(double value) { setRangeCrosshairValue(value, true); } /** * Sets the range crosshair value and, if requested, sends a * {@link PlotChangeEvent} to all registered listeners (but only if the * crosshair is visible). * * @param value the new value. * @param notify a flag that controls whether or not listeners are * notified. * * @see #getRangeCrosshairValue() */ public void setRangeCrosshairValue(double value, boolean notify) { this.rangeCrosshairValue = value; if (isRangeCrosshairVisible() && notify) { fireChangeEvent(); } } /** * Returns the pen-style (Stroke) used to draw the crosshair * (if visible). * * @return The crosshair stroke (never null). * * @see #setRangeCrosshairStroke(Stroke) * @see #isRangeCrosshairVisible() * @see #getRangeCrosshairPaint() */ public Stroke getRangeCrosshairStroke() { return this.rangeCrosshairStroke; } /** * Sets the pen-style (Stroke) used to draw the range * crosshair (if visible), and sends a {@link PlotChangeEvent} to all * registered listeners. * * @param stroke the new crosshair stroke (null not * permitted). * * @see #getRangeCrosshairStroke() */ public void setRangeCrosshairStroke(Stroke stroke) { ParamChecks.nullNotPermitted(stroke, "stroke"); this.rangeCrosshairStroke = stroke; fireChangeEvent(); } /** * Returns the paint used to draw the range crosshair. * * @return The paint (never null). * * @see #setRangeCrosshairPaint(Paint) * @see #isRangeCrosshairVisible() * @see #getRangeCrosshairStroke() */ public Paint getRangeCrosshairPaint() { return this.rangeCrosshairPaint; } /** * Sets the paint used to draw the range crosshair (if visible) and * sends a {@link PlotChangeEvent} to all registered listeners. * * @param paint the paint (null not permitted). * * @see #getRangeCrosshairPaint() */ public void setRangeCrosshairPaint(Paint paint) { ParamChecks.nullNotPermitted(paint, "paint"); this.rangeCrosshairPaint = paint; fireChangeEvent(); } /** * Returns the list of annotations. * * @return The list of annotations (never null). * * @see #addAnnotation(CategoryAnnotation) * @see #clearAnnotations() */ public List getAnnotations() { return this.annotations; } /** * Adds an annotation to the plot and sends a {@link PlotChangeEvent} to all * registered listeners. * * @param annotation the annotation (null not permitted). * * @see #removeAnnotation(CategoryAnnotation) */ public void addAnnotation(CategoryAnnotation annotation) { addAnnotation(annotation, true); } /** * Adds an annotation to the plot and, if requested, sends a * {@link PlotChangeEvent} to all registered listeners. * * @param annotation the annotation (null not permitted). * @param notify notify listeners? * * @since 1.0.10 */ public void addAnnotation(CategoryAnnotation annotation, boolean notify) { ParamChecks.nullNotPermitted(annotation, "annotation"); this.annotations.add(annotation); annotation.addChangeListener(this); if (notify) { fireChangeEvent(); } } /** * Removes an annotation from the plot and sends a {@link PlotChangeEvent} * to all registered listeners. * * @param annotation the annotation (null not permitted). * * @return A boolean (indicates whether or not the annotation was removed). * * @see #addAnnotation(CategoryAnnotation) */ public boolean removeAnnotation(CategoryAnnotation annotation) { return removeAnnotation(annotation, true); } /** * Removes an annotation from the plot and, if requested, sends a * {@link PlotChangeEvent} to all registered listeners. * * @param annotation the annotation (null not permitted). * @param notify notify listeners? * * @return A boolean (indicates whether or not the annotation was removed). * * @since 1.0.10 */ public boolean removeAnnotation(CategoryAnnotation annotation, boolean notify) { ParamChecks.nullNotPermitted(annotation, "annotation"); boolean removed = this.annotations.remove(annotation); annotation.removeChangeListener(this); if (removed && notify) { fireChangeEvent(); } return removed; } /** * Clears all the annotations and sends a {@link PlotChangeEvent} to all * registered listeners. */ public void clearAnnotations() { for(int i = 0; i < this.annotations.size(); i++) { CategoryAnnotation annotation = (CategoryAnnotation) this.annotations.get(i); annotation.removeChangeListener(this); } this.annotations.clear(); fireChangeEvent(); } /** * Returns the shadow generator for the plot, if any. * * @return The shadow generator (possibly null). * * @since 1.0.14 */ public ShadowGenerator getShadowGenerator() { return this.shadowGenerator; } /** * Sets the shadow generator for the plot and sends a * {@link PlotChangeEvent} to all registered listeners. * * @param generator the generator (null permitted). * * @since 1.0.14 */ public void setShadowGenerator(ShadowGenerator generator) { this.shadowGenerator = generator; fireChangeEvent(); } /** * Calculates the space required for the domain axis/axes. * * @param g2 the graphics device. * @param plotArea the plot area. * @param space a carrier for the result (null permitted). * * @return The required space. */ protected AxisSpace calculateDomainAxisSpace(Graphics2D g2, Rectangle2D plotArea, AxisSpace space) { if (space == null) { space = new AxisSpace(); } // reserve some space for the domain axis... if (this.fixedDomainAxisSpace != null) { if (this.orientation == PlotOrientation.HORIZONTAL) { space.ensureAtLeast( this.fixedDomainAxisSpace.getLeft(), RectangleEdge.LEFT); space.ensureAtLeast(this.fixedDomainAxisSpace.getRight(), RectangleEdge.RIGHT); } else if (this.orientation == PlotOrientation.VERTICAL) { space.ensureAtLeast(this.fixedDomainAxisSpace.getTop(), RectangleEdge.TOP); space.ensureAtLeast(this.fixedDomainAxisSpace.getBottom(), RectangleEdge.BOTTOM); } } else { // reserve space for the primary domain axis... RectangleEdge domainEdge = Plot.resolveDomainAxisLocation( getDomainAxisLocation(), this.orientation); if (this.drawSharedDomainAxis) { space = getDomainAxis().reserveSpace(g2, this, plotArea, domainEdge, space); } // reserve space for any domain axes... for (int i = 0; i < this.domainAxes.size(); i++) { Axis xAxis = (Axis) this.domainAxes.get(i); if (xAxis != null) { RectangleEdge edge = getDomainAxisEdge(i); space = xAxis.reserveSpace(g2, this, plotArea, edge, space); } } } return space; } /** * Calculates the space required for the range axis/axes. * * @param g2 the graphics device. * @param plotArea the plot area. * @param space a carrier for the result (null permitted). * * @return The required space. */ protected AxisSpace calculateRangeAxisSpace(Graphics2D g2, Rectangle2D plotArea, AxisSpace space) { if (space == null) { space = new AxisSpace(); } // reserve some space for the range axis... if (this.fixedRangeAxisSpace != null) { if (this.orientation == PlotOrientation.HORIZONTAL) { space.ensureAtLeast(this.fixedRangeAxisSpace.getTop(), RectangleEdge.TOP); space.ensureAtLeast(this.fixedRangeAxisSpace.getBottom(), RectangleEdge.BOTTOM); } else if (this.orientation == PlotOrientation.VERTICAL) { space.ensureAtLeast(this.fixedRangeAxisSpace.getLeft(), RectangleEdge.LEFT); space.ensureAtLeast(this.fixedRangeAxisSpace.getRight(), RectangleEdge.RIGHT); } } else { // reserve space for the range axes (if any)... for (int i = 0; i < this.rangeAxes.size(); i++) { Axis yAxis = (Axis) this.rangeAxes.get(i); if (yAxis != null) { RectangleEdge edge = getRangeAxisEdge(i); space = yAxis.reserveSpace(g2, this, plotArea, edge, space); } } } return space; } /** * Trims a rectangle to integer coordinates. * * @param rect the incoming rectangle. * * @return A rectangle with integer coordinates. */ private Rectangle integerise(Rectangle2D rect) { int x0 = (int) Math.ceil(rect.getMinX()); int y0 = (int) Math.ceil(rect.getMinY()); int x1 = (int) Math.floor(rect.getMaxX()); int y1 = (int) Math.floor(rect.getMaxY()); return new Rectangle(x0, y0, (x1 - x0), (y1 - y0)); } /** * Calculates the space required for the axes. * * @param g2 the graphics device. * @param plotArea the plot area. * * @return The space required for the axes. */ protected AxisSpace calculateAxisSpace(Graphics2D g2, Rectangle2D plotArea) { AxisSpace space = new AxisSpace(); space = calculateRangeAxisSpace(g2, plotArea, space); space = calculateDomainAxisSpace(g2, plotArea, space); return space; } /** * Draws the plot on a Java 2D graphics device (such as the screen or a * printer). *

* At your option, you may supply an instance of {@link PlotRenderingInfo}. * If you do, it will be populated with information about the drawing, * including various plot dimensions and tooltip info. * * @param g2 the graphics device. * @param area the area within which the plot (including axes) should * be drawn. * @param anchor the anchor point (null permitted). * @param parentState the state from the parent plot, if there is one. * @param state collects info as the chart is drawn (possibly * null). */ @Override public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor, PlotState parentState, PlotRenderingInfo state) { // if the plot area is too small, just return... boolean b1 = (area.getWidth() <= MINIMUM_WIDTH_TO_DRAW); boolean b2 = (area.getHeight() <= MINIMUM_HEIGHT_TO_DRAW); if (b1 || b2) { return; } // record the plot area... if (state == null) { // if the incoming state is null, no information will be passed // back to the caller - but we create a temporary state to record // the plot area, since that is used later by the axes state = new PlotRenderingInfo(null); } state.setPlotArea(area); // adjust the drawing area for the plot insets (if any)... RectangleInsets insets = getInsets(); insets.trim(area); // calculate the data area... AxisSpace space = calculateAxisSpace(g2, area); Rectangle2D dataArea = space.shrink(area, null); this.axisOffset.trim(dataArea); dataArea = integerise(dataArea); if (dataArea.isEmpty()) { return; } state.setDataArea(dataArea); createAndAddEntity((Rectangle2D) dataArea.clone(), state, null, null); // if there is a renderer, it draws the background, otherwise use the // default background... if (getRenderer() != null) { getRenderer().drawBackground(g2, this, dataArea); } else { drawBackground(g2, dataArea); } Map axisStateMap = drawAxes(g2, area, dataArea, state); // the anchor point is typically the point where the mouse last // clicked - the crosshairs will be driven off this point... if (anchor != null && !dataArea.contains(anchor)) { anchor = ShapeUtilities.getPointInRectangle(anchor.getX(), anchor.getY(), dataArea); } CategoryCrosshairState crosshairState = new CategoryCrosshairState(); crosshairState.setCrosshairDistance(Double.POSITIVE_INFINITY); crosshairState.setAnchor(anchor); // specify the anchor X and Y coordinates in Java2D space, for the // cases where these are not updated during rendering (i.e. no lock // on data) crosshairState.setAnchorX(Double.NaN); crosshairState.setAnchorY(Double.NaN); if (anchor != null) { ValueAxis rangeAxis = getRangeAxis(); if (rangeAxis != null) { double y; if (getOrientation() == PlotOrientation.VERTICAL) { y = rangeAxis.java2DToValue(anchor.getY(), dataArea, getRangeAxisEdge()); } else { y = rangeAxis.java2DToValue(anchor.getX(), dataArea, getRangeAxisEdge()); } crosshairState.setAnchorY(y); } } crosshairState.setRowKey(getDomainCrosshairRowKey()); crosshairState.setColumnKey(getDomainCrosshairColumnKey()); crosshairState.setCrosshairY(getRangeCrosshairValue()); // don't let anyone draw outside the data area Shape savedClip = g2.getClip(); g2.clip(dataArea); drawDomainGridlines(g2, dataArea); AxisState rangeAxisState = (AxisState) axisStateMap.get(getRangeAxis()); if (rangeAxisState == null) { if (parentState != null) { rangeAxisState = (AxisState) parentState.getSharedAxisStates() .get(getRangeAxis()); } } if (rangeAxisState != null) { drawRangeGridlines(g2, dataArea, rangeAxisState.getTicks()); drawZeroRangeBaseline(g2, dataArea); } Graphics2D savedG2 = g2; BufferedImage dataImage = null; boolean suppressShadow = Boolean.TRUE.equals(g2.getRenderingHint( JFreeChart.KEY_SUPPRESS_SHADOW_GENERATION)); if (this.shadowGenerator != null && !suppressShadow) { dataImage = new BufferedImage((int) dataArea.getWidth(), (int)dataArea.getHeight(), BufferedImage.TYPE_INT_ARGB); g2 = dataImage.createGraphics(); g2.translate(-dataArea.getX(), -dataArea.getY()); g2.setRenderingHints(savedG2.getRenderingHints()); } // draw the markers... for (int i = 0; i < this.renderers.size(); i++) { drawDomainMarkers(g2, dataArea, i, Layer.BACKGROUND); } for (int i = 0; i < this.renderers.size(); i++) { drawRangeMarkers(g2, dataArea, i, Layer.BACKGROUND); } // now render data items... boolean foundData = false; // set up the alpha-transparency... Composite originalComposite = g2.getComposite(); g2.setComposite(AlphaComposite.getInstance( AlphaComposite.SRC_OVER, getForegroundAlpha())); DatasetRenderingOrder order = getDatasetRenderingOrder(); if (order == DatasetRenderingOrder.FORWARD) { for (int i = 0; i < this.datasets.size(); i++) { foundData = render(g2, dataArea, i, state, crosshairState) || foundData; } } else { // DatasetRenderingOrder.REVERSE for (int i = this.datasets.size() - 1; i >= 0; i--) { foundData = render(g2, dataArea, i, state, crosshairState) || foundData; } } // draw the foreground markers... for (int i = 0; i < this.renderers.size(); i++) { drawDomainMarkers(g2, dataArea, i, Layer.FOREGROUND); } for (int i = 0; i < this.renderers.size(); i++) { drawRangeMarkers(g2, dataArea, i, Layer.FOREGROUND); } // draw the annotations (if any)... drawAnnotations(g2, dataArea); if (this.shadowGenerator != null && !suppressShadow) { BufferedImage shadowImage = this.shadowGenerator.createDropShadow( dataImage); g2 = savedG2; g2.drawImage(shadowImage, (int) dataArea.getX() + this.shadowGenerator.calculateOffsetX(), (int) dataArea.getY() + this.shadowGenerator.calculateOffsetY(), null); g2.drawImage(dataImage, (int) dataArea.getX(), (int) dataArea.getY(), null); } g2.setClip(savedClip); g2.setComposite(originalComposite); if (!foundData) { drawNoDataMessage(g2, dataArea); } int datasetIndex = crosshairState.getDatasetIndex(); setCrosshairDatasetIndex(datasetIndex, false); // draw domain crosshair if required... Comparable rowKey = crosshairState.getRowKey(); Comparable columnKey = crosshairState.getColumnKey(); setDomainCrosshairRowKey(rowKey, false); setDomainCrosshairColumnKey(columnKey, false); if (isDomainCrosshairVisible() && columnKey != null) { Paint paint = getDomainCrosshairPaint(); Stroke stroke = getDomainCrosshairStroke(); drawDomainCrosshair(g2, dataArea, this.orientation, datasetIndex, rowKey, columnKey, stroke, paint); } // draw range crosshair if required... ValueAxis yAxis = getRangeAxisForDataset(datasetIndex); RectangleEdge yAxisEdge = getRangeAxisEdge(); if (!this.rangeCrosshairLockedOnData && anchor != null) { double yy; if (getOrientation() == PlotOrientation.VERTICAL) { yy = yAxis.java2DToValue(anchor.getY(), dataArea, yAxisEdge); } else { yy = yAxis.java2DToValue(anchor.getX(), dataArea, yAxisEdge); } crosshairState.setCrosshairY(yy); } setRangeCrosshairValue(crosshairState.getCrosshairY(), false); if (isRangeCrosshairVisible()) { double y = getRangeCrosshairValue(); Paint paint = getRangeCrosshairPaint(); Stroke stroke = getRangeCrosshairStroke(); drawRangeCrosshair(g2, dataArea, getOrientation(), y, yAxis, stroke, paint); } // draw an outline around the plot area... if (isOutlineVisible()) { if (getRenderer() != null) { getRenderer().drawOutline(g2, this, dataArea); } else { drawOutline(g2, dataArea); } } } /** * Draws the plot background (the background color and/or image). *

* This method will be called during the chart drawing process and is * declared public so that it can be accessed by the renderers used by * certain subclasses. You shouldn't need to call this method directly. * * @param g2 the graphics device. * @param area the area within which the plot should be drawn. */ @Override public void drawBackground(Graphics2D g2, Rectangle2D area) { fillBackground(g2, area, this.orientation); drawBackgroundImage(g2, area); } /** * A utility method for drawing the plot's axes. * * @param g2 the graphics device. * @param plotArea the plot area. * @param dataArea the data area. * @param plotState collects information about the plot (null * permitted). * * @return A map containing the axis states. */ protected Map drawAxes(Graphics2D g2, Rectangle2D plotArea, Rectangle2D dataArea, PlotRenderingInfo plotState) { AxisCollection axisCollection = new AxisCollection(); // add domain axes to lists... for (int index = 0; index < this.domainAxes.size(); index++) { CategoryAxis xAxis = (CategoryAxis) this.domainAxes.get(index); if (xAxis != null) { axisCollection.add(xAxis, getDomainAxisEdge(index)); } } // add range axes to lists... for (int index = 0; index < this.rangeAxes.size(); index++) { ValueAxis yAxis = (ValueAxis) this.rangeAxes.get(index); if (yAxis != null) { axisCollection.add(yAxis, getRangeAxisEdge(index)); } } Map axisStateMap = new HashMap(); // draw the top axes double cursor = dataArea.getMinY() - this.axisOffset.calculateTopOutset( dataArea.getHeight()); Iterator iterator = axisCollection.getAxesAtTop().iterator(); while (iterator.hasNext()) { Axis axis = (Axis) iterator.next(); if (axis != null) { AxisState axisState = axis.draw(g2, cursor, plotArea, dataArea, RectangleEdge.TOP, plotState); cursor = axisState.getCursor(); axisStateMap.put(axis, axisState); } } // draw the bottom axes cursor = dataArea.getMaxY() + this.axisOffset.calculateBottomOutset(dataArea.getHeight()); iterator = axisCollection.getAxesAtBottom().iterator(); while (iterator.hasNext()) { Axis axis = (Axis) iterator.next(); if (axis != null) { AxisState axisState = axis.draw(g2, cursor, plotArea, dataArea, RectangleEdge.BOTTOM, plotState); cursor = axisState.getCursor(); axisStateMap.put(axis, axisState); } } // draw the left axes cursor = dataArea.getMinX() - this.axisOffset.calculateLeftOutset(dataArea.getWidth()); iterator = axisCollection.getAxesAtLeft().iterator(); while (iterator.hasNext()) { Axis axis = (Axis) iterator.next(); if (axis != null) { AxisState axisState = axis.draw(g2, cursor, plotArea, dataArea, RectangleEdge.LEFT, plotState); cursor = axisState.getCursor(); axisStateMap.put(axis, axisState); } } // draw the right axes cursor = dataArea.getMaxX() + this.axisOffset.calculateRightOutset(dataArea.getWidth()); iterator = axisCollection.getAxesAtRight().iterator(); while (iterator.hasNext()) { Axis axis = (Axis) iterator.next(); if (axis != null) { AxisState axisState = axis.draw(g2, cursor, plotArea, dataArea, RectangleEdge.RIGHT, plotState); cursor = axisState.getCursor(); axisStateMap.put(axis, axisState); } } return axisStateMap; } /** * Draws a representation of a dataset within the dataArea region using the * appropriate renderer. * * @param g2 the graphics device. * @param dataArea the region in which the data is to be drawn. * @param index the dataset and renderer index. * @param info an optional object for collection dimension information. * @param crosshairState a state object for tracking crosshair info * (null permitted). * * @return A boolean that indicates whether or not real data was found. * * @since 1.0.11 */ public boolean render(Graphics2D g2, Rectangle2D dataArea, int index, PlotRenderingInfo info, CategoryCrosshairState crosshairState) { boolean foundData = false; CategoryDataset currentDataset = getDataset(index); CategoryItemRenderer renderer = getRenderer(index); CategoryAxis domainAxis = getDomainAxisForDataset(index); ValueAxis rangeAxis = getRangeAxisForDataset(index); boolean hasData = !DatasetUtilities.isEmptyOrNull(currentDataset); if (hasData && renderer != null) { foundData = true; CategoryItemRendererState state = renderer.initialise(g2, dataArea, this, index, info); state.setCrosshairState(crosshairState); int columnCount = currentDataset.getColumnCount(); int rowCount = currentDataset.getRowCount(); int passCount = renderer.getPassCount(); for (int pass = 0; pass < passCount; pass++) { if (this.columnRenderingOrder == SortOrder.ASCENDING) { for (int column = 0; column < columnCount; column++) { if (this.rowRenderingOrder == SortOrder.ASCENDING) { for (int row = 0; row < rowCount; row++) { renderer.drawItem(g2, state, dataArea, this, domainAxis, rangeAxis, currentDataset, row, column, pass); } } else { for (int row = rowCount - 1; row >= 0; row--) { renderer.drawItem(g2, state, dataArea, this, domainAxis, rangeAxis, currentDataset, row, column, pass); } } } } else { for (int column = columnCount - 1; column >= 0; column--) { if (this.rowRenderingOrder == SortOrder.ASCENDING) { for (int row = 0; row < rowCount; row++) { renderer.drawItem(g2, state, dataArea, this, domainAxis, rangeAxis, currentDataset, row, column, pass); } } else { for (int row = rowCount - 1; row >= 0; row--) { renderer.drawItem(g2, state, dataArea, this, domainAxis, rangeAxis, currentDataset, row, column, pass); } } } } } } return foundData; } /** * Draws the domain gridlines for the plot, if they are visible. * * @param g2 the graphics device. * @param dataArea the area inside the axes. * * @see #drawRangeGridlines(Graphics2D, Rectangle2D, List) */ protected void drawDomainGridlines(Graphics2D g2, Rectangle2D dataArea) { if (!isDomainGridlinesVisible()) { return; } CategoryAnchor anchor = getDomainGridlinePosition(); RectangleEdge domainAxisEdge = getDomainAxisEdge(); CategoryDataset dataset = getDataset(); if (dataset == null) { return; } CategoryAxis axis = getDomainAxis(); if (axis != null) { int columnCount = dataset.getColumnCount(); for (int c = 0; c < columnCount; c++) { double xx = axis.getCategoryJava2DCoordinate(anchor, c, columnCount, dataArea, domainAxisEdge); CategoryItemRenderer renderer1 = getRenderer(); if (renderer1 != null) { renderer1.drawDomainGridline(g2, this, dataArea, xx); } } } } /** * Draws the range gridlines for the plot, if they are visible. * * @param g2 the graphics device. * @param dataArea the area inside the axes. * @param ticks the ticks. * * @see #drawDomainGridlines(Graphics2D, Rectangle2D) */ protected void drawRangeGridlines(Graphics2D g2, Rectangle2D dataArea, List ticks) { // draw the range grid lines, if any... if (!isRangeGridlinesVisible() && !isRangeMinorGridlinesVisible()) { return; } // no axis, no gridlines... ValueAxis axis = getRangeAxis(); if (axis == null) { return; } // no renderer, no gridlines... CategoryItemRenderer r = getRenderer(); if (r == null) { return; } Stroke gridStroke = null; Paint gridPaint = null; boolean paintLine; Iterator iterator = ticks.iterator(); while (iterator.hasNext()) { paintLine = false; ValueTick tick = (ValueTick) iterator.next(); if ((tick.getTickType() == TickType.MINOR) && isRangeMinorGridlinesVisible()) { gridStroke = getRangeMinorGridlineStroke(); gridPaint = getRangeMinorGridlinePaint(); paintLine = true; } else if ((tick.getTickType() == TickType.MAJOR) && isRangeGridlinesVisible()) { gridStroke = getRangeGridlineStroke(); gridPaint = getRangeGridlinePaint(); paintLine = true; } if (((tick.getValue() != 0.0) || !isRangeZeroBaselineVisible()) && paintLine) { // the method we want isn't in the CategoryItemRenderer // interface... if (r instanceof AbstractCategoryItemRenderer) { AbstractCategoryItemRenderer aci = (AbstractCategoryItemRenderer) r; aci.drawRangeLine(g2, this, axis, dataArea, tick.getValue(), gridPaint, gridStroke); } else { // we'll have to use the method in the interface, but // this doesn't have the paint and stroke settings... r.drawRangeGridline(g2, this, axis, dataArea, tick.getValue()); } } } } /** * Draws a base line across the chart at value zero on the range axis. * * @param g2 the graphics device. * @param area the data area. * * @see #setRangeZeroBaselineVisible(boolean) * * @since 1.0.13 */ protected void drawZeroRangeBaseline(Graphics2D g2, Rectangle2D area) { if (!isRangeZeroBaselineVisible()) { return; } CategoryItemRenderer r = getRenderer(); if (r instanceof AbstractCategoryItemRenderer) { AbstractCategoryItemRenderer aci = (AbstractCategoryItemRenderer) r; aci.drawRangeLine(g2, this, getRangeAxis(), area, 0.0, this.rangeZeroBaselinePaint, this.rangeZeroBaselineStroke); } else { r.drawRangeGridline(g2, this, getRangeAxis(), area, 0.0); } } /** * Draws the annotations. * * @param g2 the graphics device. * @param dataArea the data area. */ protected void drawAnnotations(Graphics2D g2, Rectangle2D dataArea) { if (getAnnotations() != null) { Iterator iterator = getAnnotations().iterator(); while (iterator.hasNext()) { CategoryAnnotation annotation = (CategoryAnnotation) iterator.next(); annotation.draw(g2, this, dataArea, getDomainAxis(), getRangeAxis()); } } } /** * Draws the domain markers (if any) for an axis and layer. This method is * typically called from within the draw() method. * * @param g2 the graphics device. * @param dataArea the data area. * @param index the renderer index. * @param layer the layer (foreground or background). * * @see #drawRangeMarkers(Graphics2D, Rectangle2D, int, Layer) */ protected void drawDomainMarkers(Graphics2D g2, Rectangle2D dataArea, int index, Layer layer) { CategoryItemRenderer r = getRenderer(index); if (r == null) { return; } Collection markers = getDomainMarkers(index, layer); CategoryAxis axis = getDomainAxisForDataset(index); if (markers != null && axis != null) { Iterator iterator = markers.iterator(); while (iterator.hasNext()) { CategoryMarker marker = (CategoryMarker) iterator.next(); r.drawDomainMarker(g2, this, axis, marker, dataArea); } } } /** * Draws the range markers (if any) for an axis and layer. This method is * typically called from within the draw() method. * * @param g2 the graphics device. * @param dataArea the data area. * @param index the renderer index. * @param layer the layer (foreground or background). * * @see #drawDomainMarkers(Graphics2D, Rectangle2D, int, Layer) */ protected void drawRangeMarkers(Graphics2D g2, Rectangle2D dataArea, int index, Layer layer) { CategoryItemRenderer r = getRenderer(index); if (r == null) { return; } Collection markers = getRangeMarkers(index, layer); ValueAxis axis = getRangeAxisForDataset(index); if (markers != null && axis != null) { Iterator iterator = markers.iterator(); while (iterator.hasNext()) { Marker marker = (Marker) iterator.next(); r.drawRangeMarker(g2, this, axis, marker, dataArea); } } } /** * Utility method for drawing a line perpendicular to the range axis (used * for crosshairs). * * @param g2 the graphics device. * @param dataArea the area defined by the axes. * @param value the data value. * @param stroke the line stroke (null not permitted). * @param paint the line paint (null not permitted). */ protected void drawRangeLine(Graphics2D g2, Rectangle2D dataArea, double value, Stroke stroke, Paint paint) { double java2D = getRangeAxis().valueToJava2D(value, dataArea, getRangeAxisEdge()); Line2D line = null; if (this.orientation == PlotOrientation.HORIZONTAL) { line = new Line2D.Double(java2D, dataArea.getMinY(), java2D, dataArea.getMaxY()); } else if (this.orientation == PlotOrientation.VERTICAL) { line = new Line2D.Double(dataArea.getMinX(), java2D, dataArea.getMaxX(), java2D); } g2.setStroke(stroke); g2.setPaint(paint); g2.draw(line); } /** * Draws a domain crosshair. * * @param g2 the graphics target. * @param dataArea the data area. * @param orientation the plot orientation. * @param datasetIndex the dataset index. * @param rowKey the row key. * @param columnKey the column key. * @param stroke the stroke used to draw the crosshair line. * @param paint the paint used to draw the crosshair line. * * @see #drawRangeCrosshair(Graphics2D, Rectangle2D, PlotOrientation, * double, ValueAxis, Stroke, Paint) * * @since 1.0.11 */ protected void drawDomainCrosshair(Graphics2D g2, Rectangle2D dataArea, PlotOrientation orientation, int datasetIndex, Comparable rowKey, Comparable columnKey, Stroke stroke, Paint paint) { CategoryDataset dataset = getDataset(datasetIndex); CategoryAxis axis = getDomainAxisForDataset(datasetIndex); CategoryItemRenderer renderer = getRenderer(datasetIndex); Line2D line; if (orientation == PlotOrientation.VERTICAL) { double xx = renderer.getItemMiddle(rowKey, columnKey, dataset, axis, dataArea, RectangleEdge.BOTTOM); line = new Line2D.Double(xx, dataArea.getMinY(), xx, dataArea.getMaxY()); } else { double yy = renderer.getItemMiddle(rowKey, columnKey, dataset, axis, dataArea, RectangleEdge.LEFT); line = new Line2D.Double(dataArea.getMinX(), yy, dataArea.getMaxX(), yy); } g2.setStroke(stroke); g2.setPaint(paint); g2.draw(line); } /** * Draws a range crosshair. * * @param g2 the graphics target. * @param dataArea the data area. * @param orientation the plot orientation. * @param value the crosshair value. * @param axis the axis against which the value is measured. * @param stroke the stroke used to draw the crosshair line. * @param paint the paint used to draw the crosshair line. * * @see #drawDomainCrosshair(Graphics2D, Rectangle2D, PlotOrientation, int, * Comparable, Comparable, Stroke, Paint) * * @since 1.0.5 */ protected void drawRangeCrosshair(Graphics2D g2, Rectangle2D dataArea, PlotOrientation orientation, double value, ValueAxis axis, Stroke stroke, Paint paint) { if (!axis.getRange().contains(value)) { return; } Line2D line; if (orientation == PlotOrientation.HORIZONTAL) { double xx = axis.valueToJava2D(value, dataArea, RectangleEdge.BOTTOM); line = new Line2D.Double(xx, dataArea.getMinY(), xx, dataArea.getMaxY()); } else { double yy = axis.valueToJava2D(value, dataArea, RectangleEdge.LEFT); line = new Line2D.Double(dataArea.getMinX(), yy, dataArea.getMaxX(), yy); } g2.setStroke(stroke); g2.setPaint(paint); g2.draw(line); } /** * Returns the range of data values that will be plotted against the range * axis. If the dataset is null, this method returns * null. * * @param axis the axis. * * @return The data range. */ @Override public Range getDataRange(ValueAxis axis) { Range result = null; List mappedDatasets = new ArrayList(); int rangeIndex = this.rangeAxes.indexOf(axis); if (rangeIndex >= 0) { mappedDatasets.addAll(datasetsMappedToRangeAxis(rangeIndex)); } else if (axis == getRangeAxis()) { mappedDatasets.addAll(datasetsMappedToRangeAxis(0)); } // iterate through the datasets that map to the axis and get the union // of the ranges. Iterator iterator = mappedDatasets.iterator(); while (iterator.hasNext()) { CategoryDataset d = (CategoryDataset) iterator.next(); CategoryItemRenderer r = getRendererForDataset(d); if (r != null) { result = Range.combine(result, r.findRangeBounds(d)); } } return result; } /** * Returns a list of the datasets that are mapped to the axis with the * specified index. * * @param axisIndex the axis index. * * @return The list (possibly empty, but never null). * * @since 1.0.3 */ private List datasetsMappedToDomainAxis(int axisIndex) { Integer key = new Integer(axisIndex); List result = new ArrayList(); for (int i = 0; i < this.datasets.size(); i++) { List mappedAxes = (List) this.datasetToDomainAxesMap.get( new Integer(i)); CategoryDataset dataset = (CategoryDataset) this.datasets.get(i); if (mappedAxes == null) { if (key.equals(ZERO)) { if (dataset != null) { result.add(dataset); } } } else { if (mappedAxes.contains(key)) { if (dataset != null) { result.add(dataset); } } } } return result; } /** * A utility method that returns a list of datasets that are mapped to a * given range axis. * * @param index the axis index. * * @return A list of datasets. */ private List datasetsMappedToRangeAxis(int index) { Integer key = new Integer(index); List result = new ArrayList(); for (int i = 0; i < this.datasets.size(); i++) { List mappedAxes = (List) this.datasetToRangeAxesMap.get( new Integer(i)); if (mappedAxes == null) { if (key.equals(ZERO)) { result.add(this.datasets.get(i)); } } else { if (mappedAxes.contains(key)) { result.add(this.datasets.get(i)); } } } return result; } /** * Returns the weight for this plot when it is used as a subplot within a * combined plot. * * @return The weight. * * @see #setWeight(int) */ public int getWeight() { return this.weight; } /** * Sets the weight for the plot and sends a {@link PlotChangeEvent} to all * registered listeners. * * @param weight the weight. * * @see #getWeight() */ public void setWeight(int weight) { this.weight = weight; fireChangeEvent(); } /** * Returns the fixed domain axis space. * * @return The fixed domain axis space (possibly null). * * @see #setFixedDomainAxisSpace(AxisSpace) */ public AxisSpace getFixedDomainAxisSpace() { return this.fixedDomainAxisSpace; } /** * Sets the fixed domain axis space and sends a {@link PlotChangeEvent} to * all registered listeners. * * @param space the space (null permitted). * * @see #getFixedDomainAxisSpace() */ public void setFixedDomainAxisSpace(AxisSpace space) { setFixedDomainAxisSpace(space, true); } /** * Sets the fixed domain axis space and sends a {@link PlotChangeEvent} to * all registered listeners. * * @param space the space (null permitted). * @param notify notify listeners? * * @see #getFixedDomainAxisSpace() * * @since 1.0.7 */ public void setFixedDomainAxisSpace(AxisSpace space, boolean notify) { this.fixedDomainAxisSpace = space; if (notify) { fireChangeEvent(); } } /** * Returns the fixed range axis space. * * @return The fixed range axis space (possibly null). * * @see #setFixedRangeAxisSpace(AxisSpace) */ public AxisSpace getFixedRangeAxisSpace() { return this.fixedRangeAxisSpace; } /** * Sets the fixed range axis space and sends a {@link PlotChangeEvent} to * all registered listeners. * * @param space the space (null permitted). * * @see #getFixedRangeAxisSpace() */ public void setFixedRangeAxisSpace(AxisSpace space) { setFixedRangeAxisSpace(space, true); } /** * Sets the fixed range axis space and sends a {@link PlotChangeEvent} to * all registered listeners. * * @param space the space (null permitted). * @param notify notify listeners? * * @see #getFixedRangeAxisSpace() * * @since 1.0.7 */ public void setFixedRangeAxisSpace(AxisSpace space, boolean notify) { this.fixedRangeAxisSpace = space; if (notify) { fireChangeEvent(); } } /** * Returns a list of the categories in the plot's primary dataset. * * @return A list of the categories in the plot's primary dataset. * * @see #getCategoriesForAxis(CategoryAxis) */ public List getCategories() { List result = null; if (getDataset() != null) { result = Collections.unmodifiableList(getDataset().getColumnKeys()); } return result; } /** * Returns a list of the categories that should be displayed for the * specified axis. * * @param axis the axis (null not permitted) * * @return The categories. * * @since 1.0.3 */ public List getCategoriesForAxis(CategoryAxis axis) { List result = new ArrayList(); int axisIndex = this.domainAxes.indexOf(axis); List datasets = datasetsMappedToDomainAxis(axisIndex); Iterator iterator = datasets.iterator(); while (iterator.hasNext()) { CategoryDataset dataset = (CategoryDataset) iterator.next(); // add the unique categories from this dataset for (int i = 0; i < dataset.getColumnCount(); i++) { Comparable category = dataset.getColumnKey(i); if (!result.contains(category)) { result.add(category); } } } return result; } /** * Returns the flag that controls whether or not the shared domain axis is * drawn for each subplot. * * @return A boolean. * * @see #setDrawSharedDomainAxis(boolean) */ public boolean getDrawSharedDomainAxis() { return this.drawSharedDomainAxis; } /** * Sets the flag that controls whether the shared domain axis is drawn when * this plot is being used as a subplot. * * @param draw a boolean. * * @see #getDrawSharedDomainAxis() */ public void setDrawSharedDomainAxis(boolean draw) { this.drawSharedDomainAxis = draw; fireChangeEvent(); } /** * Returns false always, because the plot cannot be panned * along the domain axis/axes. * * @return A boolean. * * @see #isRangePannable() * * @since 1.0.13 */ @Override public boolean isDomainPannable() { return false; } /** * Returns true if panning is enabled for the range axes, * and false otherwise. * * @return A boolean. * * @see #setRangePannable(boolean) * @see #isDomainPannable() * * @since 1.0.13 */ @Override public boolean isRangePannable() { return this.rangePannable; } /** * Sets the flag that enables or disables panning of the plot along * the range axes. * * @param pannable the new flag value. * * @see #isRangePannable() * * @since 1.0.13 */ public void setRangePannable(boolean pannable) { this.rangePannable = pannable; } /** * Pans the domain axes by the specified percentage. * * @param percent the distance to pan (as a percentage of the axis length). * @param info the plot info * @param source the source point where the pan action started. * * @since 1.0.13 */ @Override public void panDomainAxes(double percent, PlotRenderingInfo info, Point2D source) { // do nothing, because the plot is not pannable along the domain axes } /** * Pans the range axes by the specified percentage. * * @param percent the distance to pan (as a percentage of the axis length). * @param info the plot info * @param source the source point where the pan action started. * * @since 1.0.13 */ @Override public void panRangeAxes(double percent, PlotRenderingInfo info, Point2D source) { if (!isRangePannable()) { return; } int rangeAxisCount = getRangeAxisCount(); for (int i = 0; i < rangeAxisCount; i++) { ValueAxis axis = getRangeAxis(i); if (axis == null) { continue; } double length = axis.getRange().getLength(); double adj = percent * length; if (axis.isInverted()) { adj = -adj; } axis.setRange(axis.getLowerBound() + adj, axis.getUpperBound() + adj); } } /** * Returns false to indicate that the domain axes are not * zoomable. * * @return A boolean. * * @see #isRangeZoomable() */ @Override public boolean isDomainZoomable() { return false; } /** * Returns true to indicate that the range axes are zoomable. * * @return A boolean. * * @see #isDomainZoomable() */ @Override public boolean isRangeZoomable() { return true; } /** * This method does nothing, because CategoryPlot doesn't * support zooming on the domain. * * @param factor the zoom factor. * @param state the plot state. * @param source the source point (in Java2D space) for the zoom. */ @Override public void zoomDomainAxes(double factor, PlotRenderingInfo state, Point2D source) { // can't zoom domain axis } /** * This method does nothing, because CategoryPlot doesn't * support zooming on the domain. * * @param lowerPercent the lower bound. * @param upperPercent the upper bound. * @param state the plot state. * @param source the source point (in Java2D space) for the zoom. */ @Override public void zoomDomainAxes(double lowerPercent, double upperPercent, PlotRenderingInfo state, Point2D source) { // can't zoom domain axis } /** * This method does nothing, because CategoryPlot doesn't * support zooming on the domain. * * @param factor the zoom factor. * @param info the plot rendering info. * @param source the source point (in Java2D space). * @param useAnchor use source point as zoom anchor? * * @see #zoomRangeAxes(double, PlotRenderingInfo, Point2D, boolean) * * @since 1.0.7 */ @Override public void zoomDomainAxes(double factor, PlotRenderingInfo info, Point2D source, boolean useAnchor) { // can't zoom domain axis } /** * Multiplies the range on the range axis/axes by the specified factor. * * @param factor the zoom factor. * @param state the plot state. * @param source the source point (in Java2D space) for the zoom. */ @Override public void zoomRangeAxes(double factor, PlotRenderingInfo state, Point2D source) { // delegate to other method zoomRangeAxes(factor, state, source, false); } /** * Multiplies the range on the range axis/axes by the specified factor. * * @param factor the zoom factor. * @param info the plot rendering info. * @param source the source point. * @param useAnchor a flag that controls whether or not the source point * is used for the zoom anchor. * * @see #zoomDomainAxes(double, PlotRenderingInfo, Point2D, boolean) * * @since 1.0.7 */ @Override public void zoomRangeAxes(double factor, PlotRenderingInfo info, Point2D source, boolean useAnchor) { // perform the zoom on each range axis for (int i = 0; i < this.rangeAxes.size(); i++) { ValueAxis rangeAxis = (ValueAxis) this.rangeAxes.get(i); if (rangeAxis != null) { if (useAnchor) { // get the relevant source coordinate given the plot // orientation double sourceY = source.getY(); if (this.orientation == PlotOrientation.HORIZONTAL) { sourceY = source.getX(); } double anchorY = rangeAxis.java2DToValue(sourceY, info.getDataArea(), getRangeAxisEdge()); rangeAxis.resizeRange2(factor, anchorY); } else { rangeAxis.resizeRange(factor); } } } } /** * Zooms in on the range axes. * * @param lowerPercent the lower bound. * @param upperPercent the upper bound. * @param state the plot state. * @param source the source point (in Java2D space) for the zoom. */ @Override public void zoomRangeAxes(double lowerPercent, double upperPercent, PlotRenderingInfo state, Point2D source) { for (int i = 0; i < this.rangeAxes.size(); i++) { ValueAxis rangeAxis = (ValueAxis) this.rangeAxes.get(i); if (rangeAxis != null) { rangeAxis.zoomRange(lowerPercent, upperPercent); } } } /** * Returns the anchor value. * * @return The anchor value. * * @see #setAnchorValue(double) */ public double getAnchorValue() { return this.anchorValue; } /** * Sets the anchor value and sends a {@link PlotChangeEvent} to all * registered listeners. * * @param value the anchor value. * * @see #getAnchorValue() */ public void setAnchorValue(double value) { setAnchorValue(value, true); } /** * Sets the anchor value and, if requested, sends a {@link PlotChangeEvent} * to all registered listeners. * * @param value the value. * @param notify notify listeners? * * @see #getAnchorValue() */ public void setAnchorValue(double value, boolean notify) { this.anchorValue = value; if (notify) { fireChangeEvent(); } } /** * Tests the plot for equality with an arbitrary object. * * @param obj the object to test against (null permitted). * * @return A boolean. */ @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof CategoryPlot)) { return false; } CategoryPlot that = (CategoryPlot) obj; if (this.orientation != that.orientation) { return false; } if (!ObjectUtilities.equal(this.axisOffset, that.axisOffset)) { return false; } if (!this.domainAxes.equals(that.domainAxes)) { return false; } if (!this.domainAxisLocations.equals(that.domainAxisLocations)) { return false; } if (this.drawSharedDomainAxis != that.drawSharedDomainAxis) { return false; } if (!this.rangeAxes.equals(that.rangeAxes)) { return false; } if (!this.rangeAxisLocations.equals(that.rangeAxisLocations)) { return false; } if (!ObjectUtilities.equal(this.datasetToDomainAxesMap, that.datasetToDomainAxesMap)) { return false; } if (!ObjectUtilities.equal(this.datasetToRangeAxesMap, that.datasetToRangeAxesMap)) { return false; } if (!ObjectUtilities.equal(this.renderers, that.renderers)) { return false; } if (this.renderingOrder != that.renderingOrder) { return false; } if (this.columnRenderingOrder != that.columnRenderingOrder) { return false; } if (this.rowRenderingOrder != that.rowRenderingOrder) { return false; } if (this.domainGridlinesVisible != that.domainGridlinesVisible) { return false; } if (this.domainGridlinePosition != that.domainGridlinePosition) { return false; } if (!ObjectUtilities.equal(this.domainGridlineStroke, that.domainGridlineStroke)) { return false; } if (!PaintUtilities.equal(this.domainGridlinePaint, that.domainGridlinePaint)) { return false; } if (this.rangeGridlinesVisible != that.rangeGridlinesVisible) { return false; } if (!ObjectUtilities.equal(this.rangeGridlineStroke, that.rangeGridlineStroke)) { return false; } if (!PaintUtilities.equal(this.rangeGridlinePaint, that.rangeGridlinePaint)) { return false; } if (this.anchorValue != that.anchorValue) { return false; } if (this.rangeCrosshairVisible != that.rangeCrosshairVisible) { return false; } if (this.rangeCrosshairValue != that.rangeCrosshairValue) { return false; } if (!ObjectUtilities.equal(this.rangeCrosshairStroke, that.rangeCrosshairStroke)) { return false; } if (!PaintUtilities.equal(this.rangeCrosshairPaint, that.rangeCrosshairPaint)) { return false; } if (this.rangeCrosshairLockedOnData != that.rangeCrosshairLockedOnData) { return false; } if (!ObjectUtilities.equal(this.foregroundDomainMarkers, that.foregroundDomainMarkers)) { return false; } if (!ObjectUtilities.equal(this.backgroundDomainMarkers, that.backgroundDomainMarkers)) { return false; } if (!ObjectUtilities.equal(this.foregroundRangeMarkers, that.foregroundRangeMarkers)) { return false; } if (!ObjectUtilities.equal(this.backgroundRangeMarkers, that.backgroundRangeMarkers)) { return false; } if (!ObjectUtilities.equal(this.annotations, that.annotations)) { return false; } if (this.weight != that.weight) { return false; } if (!ObjectUtilities.equal(this.fixedDomainAxisSpace, that.fixedDomainAxisSpace)) { return false; } if (!ObjectUtilities.equal(this.fixedRangeAxisSpace, that.fixedRangeAxisSpace)) { return false; } if (!ObjectUtilities.equal(this.fixedLegendItems, that.fixedLegendItems)) { return false; } if (this.domainCrosshairVisible != that.domainCrosshairVisible) { return false; } if (this.crosshairDatasetIndex != that.crosshairDatasetIndex) { return false; } if (!ObjectUtilities.equal(this.domainCrosshairColumnKey, that.domainCrosshairColumnKey)) { return false; } if (!ObjectUtilities.equal(this.domainCrosshairRowKey, that.domainCrosshairRowKey)) { return false; } if (!PaintUtilities.equal(this.domainCrosshairPaint, that.domainCrosshairPaint)) { return false; } if (!ObjectUtilities.equal(this.domainCrosshairStroke, that.domainCrosshairStroke)) { return false; } if (this.rangeMinorGridlinesVisible != that.rangeMinorGridlinesVisible) { return false; } if (!PaintUtilities.equal(this.rangeMinorGridlinePaint, that.rangeMinorGridlinePaint)) { return false; } if (!ObjectUtilities.equal(this.rangeMinorGridlineStroke, that.rangeMinorGridlineStroke)) { return false; } if (this.rangeZeroBaselineVisible != that.rangeZeroBaselineVisible) { return false; } if (!PaintUtilities.equal(this.rangeZeroBaselinePaint, that.rangeZeroBaselinePaint)) { return false; } if (!ObjectUtilities.equal(this.rangeZeroBaselineStroke, that.rangeZeroBaselineStroke)) { return false; } if (!ObjectUtilities.equal(this.shadowGenerator, that.shadowGenerator)) { return false; } return super.equals(obj); } /** * Returns a clone of the plot. * * @return A clone. * * @throws CloneNotSupportedException if the cloning is not supported. */ @Override public Object clone() throws CloneNotSupportedException { CategoryPlot clone = (CategoryPlot) super.clone(); clone.domainAxes = new ObjectList(); for (int i = 0; i < this.domainAxes.size(); i++) { CategoryAxis xAxis = (CategoryAxis) this.domainAxes.get(i); if (xAxis != null) { CategoryAxis clonedAxis = (CategoryAxis) xAxis.clone(); clone.setDomainAxis(i, clonedAxis); } } clone.domainAxisLocations = (ObjectList) this.domainAxisLocations.clone(); clone.rangeAxes = new ObjectList(); for (int i = 0; i < this.rangeAxes.size(); i++) { ValueAxis yAxis = (ValueAxis) this.rangeAxes.get(i); if (yAxis != null) { ValueAxis clonedAxis = (ValueAxis) yAxis.clone(); clone.setRangeAxis(i, clonedAxis); } } clone.rangeAxisLocations = (ObjectList) this.rangeAxisLocations.clone(); clone.datasets = (ObjectList) this.datasets.clone(); for (int i = 0; i < clone.datasets.size(); i++) { CategoryDataset dataset = clone.getDataset(i); if (dataset != null) { dataset.addChangeListener(clone); } } clone.datasetToDomainAxesMap = new TreeMap(); clone.datasetToDomainAxesMap.putAll(this.datasetToDomainAxesMap); clone.datasetToRangeAxesMap = new TreeMap(); clone.datasetToRangeAxesMap.putAll(this.datasetToRangeAxesMap); clone.renderers = (ObjectList) this.renderers.clone(); for (int i = 0; i < this.renderers.size(); i++) { CategoryItemRenderer renderer2 = (CategoryItemRenderer) this.renderers.get(i); if (renderer2 instanceof PublicCloneable) { PublicCloneable pc = (PublicCloneable) renderer2; CategoryItemRenderer rc = (CategoryItemRenderer) pc.clone(); clone.renderers.set(i, rc); rc.setPlot(clone); rc.addChangeListener(clone); } } if (this.fixedDomainAxisSpace != null) { clone.fixedDomainAxisSpace = (AxisSpace) ObjectUtilities.clone( this.fixedDomainAxisSpace); } if (this.fixedRangeAxisSpace != null) { clone.fixedRangeAxisSpace = (AxisSpace) ObjectUtilities.clone( this.fixedRangeAxisSpace); } clone.annotations = (List) ObjectUtilities.deepClone(this.annotations); clone.foregroundDomainMarkers = cloneMarkerMap( this.foregroundDomainMarkers); clone.backgroundDomainMarkers = cloneMarkerMap( this.backgroundDomainMarkers); clone.foregroundRangeMarkers = cloneMarkerMap( this.foregroundRangeMarkers); clone.backgroundRangeMarkers = cloneMarkerMap( this.backgroundRangeMarkers); if (this.fixedLegendItems != null) { clone.fixedLegendItems = (LegendItemCollection) this.fixedLegendItems.clone(); } return clone; } /** * A utility method to clone the marker maps. * * @param map the map to clone. * * @return A clone of the map. * * @throws CloneNotSupportedException if there is some problem cloning the * map. */ private Map cloneMarkerMap(Map map) throws CloneNotSupportedException { Map clone = new HashMap(); Set keys = map.keySet(); Iterator iterator = keys.iterator(); while (iterator.hasNext()) { Object key = iterator.next(); List entry = (List) map.get(key); Object toAdd = ObjectUtilities.deepClone(entry); clone.put(key, toAdd); } return clone; } /** * Provides serialization support. * * @param stream the output stream. * * @throws IOException if there is an I/O error. */ private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); SerialUtilities.writeStroke(this.domainGridlineStroke, stream); SerialUtilities.writePaint(this.domainGridlinePaint, stream); SerialUtilities.writeStroke(this.rangeGridlineStroke, stream); SerialUtilities.writePaint(this.rangeGridlinePaint, stream); SerialUtilities.writeStroke(this.rangeCrosshairStroke, stream); SerialUtilities.writePaint(this.rangeCrosshairPaint, stream); SerialUtilities.writeStroke(this.domainCrosshairStroke, stream); SerialUtilities.writePaint(this.domainCrosshairPaint, stream); SerialUtilities.writeStroke(this.rangeMinorGridlineStroke, stream); SerialUtilities.writePaint(this.rangeMinorGridlinePaint, stream); SerialUtilities.writeStroke(this.rangeZeroBaselineStroke, stream); SerialUtilities.writePaint(this.rangeZeroBaselinePaint, stream); } /** * Provides serialization support. * * @param stream the input stream. * * @throws IOException if there is an I/O error. * @throws ClassNotFoundException if there is a classpath problem. */ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); this.domainGridlineStroke = SerialUtilities.readStroke(stream); this.domainGridlinePaint = SerialUtilities.readPaint(stream); this.rangeGridlineStroke = SerialUtilities.readStroke(stream); this.rangeGridlinePaint = SerialUtilities.readPaint(stream); this.rangeCrosshairStroke = SerialUtilities.readStroke(stream); this.rangeCrosshairPaint = SerialUtilities.readPaint(stream); this.domainCrosshairStroke = SerialUtilities.readStroke(stream); this.domainCrosshairPaint = SerialUtilities.readPaint(stream); this.rangeMinorGridlineStroke = SerialUtilities.readStroke(stream); this.rangeMinorGridlinePaint = SerialUtilities.readPaint(stream); this.rangeZeroBaselineStroke = SerialUtilities.readStroke(stream); this.rangeZeroBaselinePaint = SerialUtilities.readPaint(stream); for (int i = 0; i < this.domainAxes.size(); i++) { CategoryAxis xAxis = (CategoryAxis) this.domainAxes.get(i); if (xAxis != null) { xAxis.setPlot(this); xAxis.addChangeListener(this); } } for (int i = 0; i < this.rangeAxes.size(); i++) { ValueAxis yAxis = (ValueAxis) this.rangeAxes.get(i); if (yAxis != null) { yAxis.setPlot(this); yAxis.addChangeListener(this); } } int datasetCount = this.datasets.size(); for (int i = 0; i < datasetCount; i++) { Dataset dataset = (Dataset) this.datasets.get(i); if (dataset != null) { dataset.addChangeListener(this); } } int rendererCount = this.renderers.size(); for (int i = 0; i < rendererCount; i++) { CategoryItemRenderer renderer = (CategoryItemRenderer) this.renderers.get(i); if (renderer != null) { renderer.addChangeListener(this); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy