- /* ===========================================================
- * JFreeChart : a free chart library for the Java(tm) platform
- * ===========================================================
- *
- * (C) Copyright 2000-2005, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
- *
- * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
- * in the United States and other countries.]
- *
- * -----------------
- * CategoryPlot.java
- * -----------------
- * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
- *
- * Original Author: David Gilbert (for Object Refinery Limited);
- * Contributor(s): Jeremy Bowman;
- * Arnaud Lelievre;
- *
- * $Id: CategoryPlot.java,v 1.12 2005/02/23 13:03:32 mungady Exp $
- *
- * Changes (from 21-Jun-2001)
- * --------------------------
- * 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);
- *
- */
- 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.Insets;
- import java.awt.Paint;
- 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.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.Iterator;
- import java.util.List;
- import java.util.Map;
- import java.util.ResourceBundle;
- import org.jfree.chart.LegendItem;
- import org.jfree.chart.LegendItemCollection;
- 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.ValueAxis;
- import org.jfree.chart.axis.ValueTick;
- 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.CategoryItemRenderer;
- import org.jfree.chart.renderer.category.CategoryItemRendererState;
- import org.jfree.data.Range;
- import org.jfree.data.category.CategoryDataset;
- 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.PublicCloneable;
- 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,
- Zoomable,
- RendererChangeListener,
- Cloneable, PublicCloneable, Serializable {
- /**
- * 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 resourceBundle for the localization. */
- protected static ResourceBundle localizationResources
- = ResourceBundle.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 ObjectList datasetToDomainAxisMap;
- /** Storage for keys that map datasets to range axes. */
- private ObjectList datasetToRangeAxisMap;
- /** 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 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;
- /** The anchor value. */
- private double anchorValue;
- /** 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 range axes. */
- private transient Map foregroundRangeMarkers;
- /** A map containing lists of markers for the range axes. */
- private transient Map backgroundRangeMarkers;
- /** A list of annotations (optional) for the plot. */
- private transient 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;
- /**
- * Default constructor.
- */
- public CategoryPlot() {
- this(null, null, null, null);
- }
- /**
- * Creates a new plot.
- *
- * @param dataset the dataset (<code>null</code> permitted).
- * @param domainAxis the domain axis (<code>null</code> permitted).
- * @param rangeAxis the range axis (<code>null</code> permitted).
- * @param renderer the item renderer (<code>null</code> 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.datasetToDomainAxisMap = new ObjectList();
- this.datasetToRangeAxisMap = new ObjectList();
- 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.rangeGridlinesVisible = DEFAULT_RANGE_GRIDLINES_VISIBLE;
- this.rangeGridlineStroke = DEFAULT_GRIDLINE_STROKE;
- this.rangeGridlinePaint = DEFAULT_GRIDLINE_PAINT;
- this.foregroundRangeMarkers = new HashMap();
- this.backgroundRangeMarkers = new HashMap();
- Marker baseline = new ValueMarker(
- 0.0, new Color(0.8f, 0.8f, 0.8f, 0.5f), new BasicStroke(1.0f),
- new Color(0.85f, 0.85f, 0.95f, 0.5f), new BasicStroke(1.0f), 0.6f
- );
- addRangeMarker(baseline, Layer.BACKGROUND);
- this.anchorValue = 0.0;
- }
- /**
- * Returns a string describing the type of plot.
- *
- * @return The type.
- */
- public String getPlotType() {
- return localizationResources.getString("Category_Plot");
- }
- /**
- * Returns the orientation of the plot.
- *
- * @return The orientation of the plot.
- */
- 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 (<code>null</code> not permitted).
- */
- public void setOrientation(PlotOrientation orientation) {
- if (orientation == null) {
- throw new IllegalArgumentException("Null 'orientation' argument.");
- }
- this.orientation = orientation;
- notifyListeners(new PlotChangeEvent(this));
- }
- /**
- * Returns the axis offset.
- *
- * @return The axis offset (never <code>null</code>).
- */
- public RectangleInsets getAxisOffset() {
- return this.axisOffset;
- }
- /**
- * Sets the axis offsets (gap between the data area and the axes).
- *
- * @param offset the offset (<code>null</code> not permitted).
- */
- public void setAxisOffset(RectangleInsets offset) {
- if (offset == null) {
- throw new IllegalArgumentException("Null 'offset' argument.");
- }
- this.axisOffset = offset;
- notifyListeners(new PlotChangeEvent(this));
- }
- /**
- * Returns the domain axis for the plot. If the domain axis for this plot
- * is <code>null</code>, then the method will return the parent plot's
- * domain axis (if there is a parent plot).
- *
- * @return The domain axis (<code>null</code> permitted).
- */
- public CategoryAxis getDomainAxis() {
- return getDomainAxis(0);
- }
- /**
- * Returns a domain axis.
- *
- * @param index the axis index.
- *
- * @return The axis (<code>null</code> possible).
- */
- 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 (<code>null</code> permitted).
- */
- public void setDomainAxis(CategoryAxis axis) {
- setDomainAxis(0, axis);
- }
- /**
- * Sets a domain axis.
- *
- * @param index the axis index.
- * @param axis the axis.
- */
- public void setDomainAxis(int index, CategoryAxis axis) {
- 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);
- }
- notifyListeners(new PlotChangeEvent(this));
- }
- /**
- * Returns the domain axis location.
- *
- * @return The location (never <code>null</code>).
- */
- public AxisLocation getDomainAxisLocation() {
- return getDomainAxisLocation(0);
- }
- /**
- * Returns the location for a domain axis.
- *
- * @param index the axis index.
- *
- * @return The location.
- */
- 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 (<code>null</code> not permitted).
- */
- public void setDomainAxisLocation(AxisLocation location) {
- // defer argument checking...
- setDomainAxisLocation(location, true);
- }
- /**
- * Sets the location of the domain axis.
- *
- * @param location the axis location (<code>null</code> not permitted).
- * @param notify a flag that controls whether listeners are notified.
- */
- public void setDomainAxisLocation(AxisLocation location, boolean notify) {
- if (location == null) {
- throw new IllegalArgumentException("Null 'location' argument.");
- }
- setDomainAxisLocation(0, location);
- }
- /**
- * 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.
- */
- public void setDomainAxisLocation(int index, AxisLocation location) {
- // TODO: handle argument checking for primary axis location which
- // should not be null
- this.domainAxisLocations.set(index, location);
- notifyListeners(new PlotChangeEvent(this));
- }
- /**
- * Returns the domain axis edge. This is derived from the axis location
- * and the plot orientation.
- *
- * @return The edge (never <code>null</code>).
- */
- public RectangleEdge getDomainAxisEdge() {
- return getDomainAxisEdge(0);
- }
- /**
- * Returns the edge for a domain axis.
- *
- * @param index the axis index.
- *
- * @return The edge (never <code>null</code>).
- */
- public RectangleEdge getDomainAxisEdge(int index) {
- RectangleEdge result = null;
- AxisLocation location = getDomainAxisLocation(index);
- if (location != null) {
- result = Plot.resolveDomainAxisLocation(location, this.orientation);
- }
- else {
- result = RectangleEdge.opposite(getDomainAxisEdge(0));
- }
- return result;
- }
- /**
- * 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();
- notifyListeners(new PlotChangeEvent(this));
- }
- /**
- * 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 <code>null</code>).
- */
- public ValueAxis getRangeAxis() {
- return getRangeAxis(0);
- }
- /**
- * Returns a range axis.
- *
- * @param index the axis index.
- *
- * @return The axis (<code>null</code> 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 (<code>null</code> permitted).
- */
- public void setRangeAxis(ValueAxis axis) {
- setRangeAxis(0, axis);
- }
- /**
- * Sets a range axis.
- *
- * @param index the axis index.
- * @param axis the axis.
- */
- public void setRangeAxis(int index, ValueAxis axis) {
- 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);
- }
- notifyListeners(new PlotChangeEvent(this));
- }
- /**
- * Returns the range axis location.
- *
- * @return the location (never <code>null</code>).
- */
- public AxisLocation getRangeAxisLocation() {
- return getRangeAxisLocation(0);
- }
- /**
- * Returns the location for a range axis.
- *
- * @param index the axis index.
- *
- * @return The location.
- */
- 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 (<code>null</code> not permitted).
- */
- 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 (<code>null</code> not permitted).
- * @param notify notify listeners?
- */
- 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.
- */
- 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?
- */
- public void setRangeAxisLocation(int index, AxisLocation location,
- boolean notify) {
- // TODO: don't allow null for index = 0
- this.rangeAxisLocations.set(index, location);
- if (notify) {
- notifyListeners(new PlotChangeEvent(this));
- }
- }
- /**
- * Returns the edge where the primary range axis is located.
- *
- * @return The edge (never <code>null</code>).
- */
- 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);
- RectangleEdge result = Plot.resolveRangeAxisLocation(
- location, this.orientation
- );
- if (result == null) {
- result = RectangleEdge.opposite(getRangeAxisEdge(0));
- }
- return result;
- }
- /**
- * 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();
- notifyListeners(new PlotChangeEvent(this));
- }
- /**
- * 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 <code>null</code>).
- */
- public CategoryDataset getDataset() {
- return getDataset(0);
- }
- /**
- * Returns the dataset at the given index.
- *
- * @param index the dataset index.
- *
- * @return The dataset (possibly <code>null</code>).
- */
- 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 (<code>null</code> permitted).
- */
- public void setDataset(CategoryDataset dataset) {
- setDataset(0, dataset);
- }
- /**
- * Sets a dataset for the plot.
- *
- * @param index the dataset index.
- * @param dataset the dataset (<code>null</code> permitted).
- */
- 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);
- }
- /**
- * Maps a dataset to a particular domain axis.
- *
- * @param index the dataset index (zero-based).
- * @param axisIndex the axis index (zero-based).
- */
- public void mapDatasetToDomainAxis(int index, int axisIndex) {
- this.datasetToDomainAxisMap.set(index, new Integer(axisIndex));
- // fake a dataset change event to update axes...
- datasetChanged(new DatasetChangeEvent(this, getDataset(index)));
- }
- /**
- * 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.
- */
- public CategoryAxis getDomainAxisForDataset(int index) {
- CategoryAxis result = getDomainAxis();
- Integer axisIndex = (Integer) this.datasetToDomainAxisMap.get(index);
- if (axisIndex != null) {
- result = getDomainAxis(axisIndex.intValue());
- }
- return result;
- }
- /**
- * Maps a dataset to a particular range axis.
- *
- * @param index the dataset index (zero-based).
- * @param axisIndex the axis index (zero-based).
- */
- public void mapDatasetToRangeAxis(int index, int axisIndex) {
- this.datasetToRangeAxisMap.set(index, new Integer(axisIndex));
- // 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.
- */
- public ValueAxis getRangeAxisForDataset(int index) {
- ValueAxis result = getRangeAxis();
- Integer axisIndex = (Integer) this.datasetToRangeAxisMap.get(index);
- if (axisIndex != null) {
- result = getRangeAxis(axisIndex.intValue());
- }
- return result;
- }
- /**
- * Returns a reference to the renderer for the plot.
- *
- * @return The renderer.
- */
- public CategoryItemRenderer getRenderer() {
- return getRenderer(0);
- }
- /**
- * Returns the renderer at the given index.
- *
- * @param index the renderer index.
- *
- * @return The renderer (possibly <code>null</code>).
- */
- 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 (<code>null</code> permitted.
- */
- 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.
- * <p>
- * You can set the renderer to <code>null</code>, but this is not
- * recommended because:
- * <ul>
- * <li>no data will be displayed;</li>
- * <li>the plot background will not be painted;</li>
- * </ul>
- *
- * @param renderer the renderer (<code>null</code> permitted).
- * @param notify notify listeners?
- */
- 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 (<code>null</code> permitted).
- */
- 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 (<code>null</code> permitted).
- * @param notify notify listeners?
- */
- 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) {
- notifyListeners(new PlotChangeEvent(this));
- }
- }
- /**
- * Returns the renderer for the specified dataset. If the dataset doesn't
- * belong to the plot, this method will return <code>null</code>.
- *
- * @param dataset the dataset (<code>null</code> permitted).
- *
- * @return The renderer (possibly <code>null</code>).
- */
- 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 <code>-1</code> if the
- * renderer is not assigned to this plot.
- *
- * @param renderer the renderer (<code>null</code> permitted).
- *
- * @return The renderer index.
- */
- public int getIndexOf(CategoryItemRenderer renderer) {
- return this.renderers.indexOf(renderer);
- }
- /**
- * Returns the dataset rendering order.
- *
- * @return The order (never <code>null</code>).
- */
- 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 (<code>null</code> not permitted).
- */
- public void setDatasetRenderingOrder(DatasetRenderingOrder order) {
- if (order == null) {
- throw new IllegalArgumentException("Null 'order' argument.");
- }
- this.renderingOrder = order;
- notifyListeners(new PlotChangeEvent(this));
- }
- /**
- * Returns the order in which the columns are rendered.
- *
- * @return The order.
- */
- public SortOrder getColumnRenderingOrder() {
- return this.columnRenderingOrder;
- }
- /**
- * Sets the order in which the columns should be rendered.
- *
- * @param order the order.
- */
- public void setColumnRenderingOrder(SortOrder order) {
- this.columnRenderingOrder = order;
- }
- /**
- * Returns the order in which the rows should be rendered.
- *
- * @return the order (never <code>null</code>).
- */
- public SortOrder getRowRenderingOrder() {
- return this.rowRenderingOrder;
- }
- /**
- * Sets the order in which the rows should be rendered.
- *
- * @param order the order (<code>null</code> not allowed).
- */
- public void setRowRenderingOrder(SortOrder order) {
- if (order == null) {
- throw new IllegalArgumentException("Null 'order' argument.");
- }
- this.rowRenderingOrder = order;
- }
- /**
- * Returns the flag that controls whether the domain grid-lines are visible.
- *
- * @return the <code>true</code> or <code>false</code>.
- */
- public boolean isDomainGridlinesVisible() {
- return this.domainGridlinesVisible;
- }
- /**
- * Sets the flag that controls whether or not grid-lines are drawn against
- * the domain axis.
- * <p>
- * If the flag value changes, a {@link PlotChangeEvent} is sent to all
- * registered listeners.
- *
- * @param visible the new value of the flag.
- */
- public void setDomainGridlinesVisible(boolean visible) {
- if (this.domainGridlinesVisible != visible) {
- this.domainGridlinesVisible = visible;
- notifyListeners(new PlotChangeEvent(this));
- }
- }
- /**
- * Returns the position used for the domain gridlines.
- *
- * @return The gridline position.
- */
- public CategoryAnchor getDomainGridlinePosition() {
- return this.domainGridlinePosition;
- }
- /**
- * Sets the position used for the domain gridlines.
- *
- * @param position the position.
- */
- public void setDomainGridlinePosition(CategoryAnchor position) {
- this.domainGridlinePosition = position;
- notifyListeners(new PlotChangeEvent(this));
- }
- /**
- * Returns the stroke used to draw grid-lines against the domain axis.
- *
- * @return the stroke.
- */
- public Stroke getDomainGridlineStroke() {
- return this.domainGridlineStroke;
- }
- /**
- * Sets the stroke used to draw grid-lines against the domain axis. A
- * {@link PlotChangeEvent} is sent to all registered listeners.
- *
- * @param stroke the stroke.
- */
- public void setDomainGridlineStroke(Stroke stroke) {
- this.domainGridlineStroke = stroke;
- notifyListeners(new PlotChangeEvent(this));
- }
- /**
- * Returns the paint used to draw grid-lines against the domain axis.
- *
- * @return the paint.
- */
- public Paint getDomainGridlinePaint() {
- return this.domainGridlinePaint;
- }
- /**
- * Sets the paint used to draw the grid-lines (if any) against the domain
- * axis. A {@link PlotChangeEvent} is sent to all registered listeners.
- *
- * @param paint the paint.
- */
- public void setDomainGridlinePaint(Paint paint) {
- this.domainGridlinePaint = paint;
- notifyListeners(new PlotChangeEvent(this));
- }
- /**
- * Returns the flag that controls whether the range grid-lines are visible.
- *
- * @return the flag.
- */
- 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.
- */
- public void setRangeGridlinesVisible(boolean visible) {
- if (this.rangeGridlinesVisible != visible) {
- this.rangeGridlinesVisible = visible;
- notifyListeners(new PlotChangeEvent(this));
- }
- }
- /**
- * Returns the stroke used to draw the grid-lines against the range axis.
- *
- * @return the stroke.
- */
- public Stroke getRangeGridlineStroke() {
- return this.rangeGridlineStroke;
- }
- /**
- * Sets the stroke used to draw the grid-lines against the range axis.
- * A {@link PlotChangeEvent} is sent to all registered listeners.
- *
- * @param stroke the stroke.
- */
- public void setRangeGridlineStroke(Stroke stroke) {
- this.rangeGridlineStroke = stroke;
- notifyListeners(new PlotChangeEvent(this));
- }
- /**
- * Returns the paint used to draw the grid-lines against the range axis.
- *
- * @return the paint.
- */
- public Paint getRangeGridlinePaint() {
- return this.rangeGridlinePaint;
- }
- /**
- * Sets the paint used to draw the grid lines against the range axis.
- * A {@link PlotChangeEvent} is sent to all registered listeners.
- *
- * @param paint the paint.
- */
- public void setRangeGridlinePaint(Paint paint) {
- this.rangeGridlinePaint = paint;
- notifyListeners(new PlotChangeEvent(this));
- }
- /**
- * Returns the fixed legend items, if any.
- *
- * @return The legend items (possibly <code>null</code>).
- */
- public LegendItemCollection getFixedLegendItems() {
- return this.fixedLegendItems;
- }
- /**
- * Sets the fixed legend items for the plot. Leave this set to
- * <code>null</code> if you prefer the legend items to be created
- * automatically.
- *
- * @param items the legend items (<code>null</code> permitted).
- */
- public void setFixedLegendItems(LegendItemCollection items) {
- this.fixedLegendItems = items;
- notifyListeners(new PlotChangeEvent(this));
- }
- /**
- * 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.
- */
- public LegendItemCollection getLegendItems() {
- LegendItemCollection result = this.fixedLegendItems;
- if (result == null) {
- 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) {
- int seriesCount = dataset.getRowCount();
- for (int i = 0; i < seriesCount; i++) {
- LegendItem item = renderer.getLegendItem(
- datasetIndex, i
- );
- if (item != null) {
- result.add(item);
- }
- }
- }
- }
- }
- }
- 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.
- *
- */
- 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.
- * <p>
- * 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.
- */
- 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 the plot's dataset.
- * <P>
- * The range axis bo