    001/* ===========================================================
    002 * JFreeChart : a free chart library for the Java(tm) platform
    003 * ===========================================================
    004 *
    005 * (C) Copyright 2000-2011, by Object Refinery Limited and Contributors.
    006 *
    007 * Project Info:  http://www.jfree.org/jfreechart/index.html
    008 *
    009 * This library is free software; you can redistribute it and/or modify it
    010 * under the terms of the GNU Lesser General Public License as published by
    011 * the Free Software Foundation; either version 2.1 of the License, or
    012 * (at your option) any later version.
    013 *
    014 * This library is distributed in the hope that it will be useful, but
    015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
    017 * License for more details.
    018 *
    019 * You should have received a copy of the GNU Lesser General Public
    020 * License along with this library; if not, write to the Free Software
    021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
    022 * USA.
    023 *
    024 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    025 * Other names may be trademarks of their respective owners.]
    026 *
    027 * -------------------------
    028 * CombinedDomainXYPlot.java
    029 * -------------------------
    030 * (C) Copyright 2001-2008, by Bill Kelemen and Contributors.
    031 *
    032 * Original Author:  Bill Kelemen;
    033 * Contributor(s):   David Gilbert (for Object Refinery Limited);
    034 *                   Anthony Boulestreau;
    035 *                   David Basten;
    036 *                   Kevin Frechette (for ISTI);
    037 *                   Nicolas Brodu;
    038 *                   Petr Kubanek (bug 1606205);
    039 *
    040 * Changes:
    041 * --------
    042 * 06-Dec-2001 : Version 1 (BK);
    043 * 12-Dec-2001 : Removed unnecessary 'throws' clause from constructor (DG);
    044 * 18-Dec-2001 : Added plotArea attribute and get/set methods (BK);
    045 * 22-Dec-2001 : Fixed bug in chartChanged with multiple combinations of
    046 *               CombinedPlots (BK);
    047 * 08-Jan-2002 : Moved to new package com.jrefinery.chart.combination (DG);
    048 * 25-Feb-2002 : Updated import statements (DG);
    049 * 28-Feb-2002 : Readded "this.plotArea = plotArea" that was deleted from
    050 *               draw() method (BK);
    051 * 26-Mar-2002 : Added an empty zoom method (this method needs to be written so
    052 *               that combined plots will support zooming (DG);
    053 * 29-Mar-2002 : Changed the method createCombinedAxis adding the creation of
    054 *               OverlaidSymbolicAxis and CombinedSymbolicAxis(AB);
    055 * 23-Apr-2002 : Renamed CombinedPlot-->MultiXYPlot, and simplified the
    056 *               structure (DG);
    057 * 23-May-2002 : Renamed (again) MultiXYPlot-->CombinedXYPlot (DG);
    058 * 19-Jun-2002 : Added get/setGap() methods suggested by David Basten (DG);
    059 * 25-Jun-2002 : Removed redundant imports (DG);
    060 * 16-Jul-2002 : Draws shared axis after subplots (to fix missing gridlines),
    061 *               added overrides of 'setSeriesPaint()' and 'setXYItemRenderer()'
    062 *               that pass changes down to subplots (KF);
    063 * 09-Oct-2002 : Added add(XYPlot) method (DG);
    064 * 26-Mar-2003 : Implemented Serializable (DG);
    065 * 16-May-2003 : Renamed CombinedXYPlot --> CombinedDomainXYPlot (DG);
    066 * 04-Aug-2003 : Removed leftover code that was causing domain axis drawing
    067 *               problem (DG);
    068 * 08-Aug-2003 : Adjusted totalWeight in remove() method (DG);
    069 * 21-Aug-2003 : Implemented Cloneable (DG);
    070 * 11-Sep-2003 : Fix cloning support (subplots) (NB);
    071 * 15-Sep-2003 : Fixed error in cloning (DG);
    072 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
    073 * 17-Sep-2003 : Updated handling of 'clicks' (DG);
    074 * 12-Nov-2004 : Implemented the new Zoomable interface (DG);
    075 * 25-Nov-2004 : Small update to clone() implementation (DG);
    076 * 21-Feb-2005 : The getLegendItems() method now returns the fixed legend
    077 *               items if set (DG);
    078 * 05-May-2005 : Removed unused draw() method (DG);
    079 * ------------- JFREECHART 1.0.x ---------------------------------------------
    080 * 23-Aug-2006 : Override setFixedRangeAxisSpace() to update subplots (DG);
    081 * 06-Feb-2007 : Fixed bug 1606205, draw shared axis after subplots (DG);
    082 * 23-Mar-2007 : Reverted previous patch (bug fix 1606205) (DG);
    083 * 17-Apr-2007 : Added null argument checks to findSubplot() (DG);
    084 * 27-Nov-2007 : Modified setFixedRangeAxisSpaceForSubplots() so as not to
    085 *               trigger change event in subplots (DG);
    086 * 28-Jan-2008 : Reset fixed range axis space in subplots for each call to
    087 *               draw() (DG);
    088 * 27-Mar-2008 : Add documentation for getDataRange() method (DG);
    089 * 31-Mar-2008 : Updated getSubplots() to return EMPTY_LIST for null
    090 *               subplots, as suggested by Richard West (DG);
    091 * 28-Apr-2008 : Fixed zooming problem (see bug 1950037) (DG);
    092 * 11-Aug-2008 : Don't store totalWeight of subplots, calculate it as
    093 *               required (DG);
    094 *
    095 */
    096
    097package org.jfree.chart.plot;
    098
    099import java.awt.Graphics2D;
    100import java.awt.geom.Point2D;
    101import java.awt.geom.Rectangle2D;
    102import java.util.Collections;
    103import java.util.Iterator;
    104import java.util.List;
    105
    106import org.jfree.chart.LegendItemCollection;
    107import org.jfree.chart.axis.AxisSpace;
    108import org.jfree.chart.axis.AxisState;
    109import org.jfree.chart.axis.NumberAxis;
    110import org.jfree.chart.axis.ValueAxis;
    111import org.jfree.chart.event.PlotChangeEvent;
    112import org.jfree.chart.event.PlotChangeListener;
    113import org.jfree.chart.renderer.xy.XYItemRenderer;
    114import org.jfree.data.Range;
    115import org.jfree.ui.RectangleEdge;
    116import org.jfree.ui.RectangleInsets;
    117import org.jfree.util.ObjectUtilities;
    118
    119/**
    120 * An extension of {@link XYPlot} that contains multiple subplots that share a
    121 * common domain axis.
    122 */
    123public class CombinedDomainXYPlot extends XYPlot
    124        implements PlotChangeListener {
    125
    126    /** For serialization. */
    127    private static final long serialVersionUID = -7765545541261907383L;
    128
    129    /** Storage for the subplot references. */
    130    private List subplots;
    131
    132    /** The gap between subplots. */
    133    private double gap = 5.0;
    134
    135    /** Temporary storage for the subplot areas. */
    136    private transient Rectangle2D[] subplotAreas;
    137    // TODO:  the subplot areas needs to be moved out of the plot into the plot
    138    //        state
    139
    140    /**
    141     * Default constructor.
    142     */
    143    public CombinedDomainXYPlot() {
    144        this(new NumberAxis());
    145    }
    146
    147    /**
    148     * Creates a new combined plot that shares a domain axis among multiple
    149     * subplots.
    150     *
    151     * @param domainAxis  the shared axis.
    152     */
    153    public CombinedDomainXYPlot(ValueAxis domainAxis) {
    154
    155        super(
    156            null,        // no data in the parent plot
    157            domainAxis,
    158            null,        // no range axis
    159            null         // no rendereer
    160        );
    161
    162        this.subplots = new java.util.ArrayList();
    163
    164    }
    165
    166    /**
    167     * Returns a string describing the type of plot.
    168     *
    169     * @return The type of plot.
    170     */
    171    public String getPlotType() {
    172        return "Combined_Domain_XYPlot";
    173    }
    174
    175    /**
    176     * Sets the orientation for the plot (also changes the orientation for all
    177     * the subplots to match).
    178     *
    179     * @param orientation  the orientation (<code>null</code> not allowed).
    180     */
    181    public void setOrientation(PlotOrientation orientation) {
    182
    183        super.setOrientation(orientation);
    184        Iterator iterator = this.subplots.iterator();
    185        while (iterator.hasNext()) {
    186            XYPlot plot = (XYPlot) iterator.next();
    187            plot.setOrientation(orientation);
    188        }
    189
    190    }
    191
    192    /**
    193     * Returns a range representing the extent of the data values in this plot
    194     * (obtained from the subplots) that will be rendered against the specified
    195     * axis.  NOTE: This method is intended for internal JFreeChart use, and
    196     * is public only so that code in the axis classes can call it.  Since
    197     * only the domain axis is shared between subplots, the JFreeChart code
    198     * will only call this method for the domain values (although this is not
    199     * checked/enforced).
    200     *
    201     * @param axis  the axis.
    202     *
    203     * @return The range (possibly <code>null</code>).
    204     */
    205    public Range getDataRange(ValueAxis axis) {
    206        Range result = null;
    207        if (this.subplots != null) {
    208            Iterator iterator = this.subplots.iterator();
    209            while (iterator.hasNext()) {
    210                XYPlot subplot = (XYPlot) iterator.next();
    211                result = Range.combine(result, subplot.getDataRange(axis));
    212            }
    213        }
    214        return result;
    215    }
    216
    217    /**
    218     * Returns the gap between subplots, measured in Java2D units.
    219     *
    220     * @return The gap (in Java2D units).
    221     */
    222    public double getGap() {
    223        return this.gap;
    224    }
    225
    226    /**
    227     * Sets the amount of space between subplots and sends a
    228     * {@link PlotChangeEvent} to all registered listeners.
    229     *
    230     * @param gap  the gap between subplots (in Java2D units).
    231     */
    232    public void setGap(double gap) {
    233        this.gap = gap;
    234        fireChangeEvent();
    235    }
    236
    237    /**
    238     * Adds a subplot (with a default 'weight' of 1) and sends a
    239     * {@link PlotChangeEvent} to all registered listeners.
    240     * <P>
    241     * The domain axis for the subplot will be set to <code>null</code>.  You
    242     * must ensure that the subplot has a non-null range axis.
    243     *
    244     * @param subplot  the subplot (<code>null</code> not permitted).
    245     */
    246    public void add(XYPlot subplot) {
    247        // defer argument checking
    248        add(subplot, 1);
    249    }
    250
    251    /**
    252     * Adds a subplot with the specified weight and sends a
    253     * {@link PlotChangeEvent} to all registered listeners.  The weight
    254     * determines how much space is allocated to the subplot relative to all
    255     * the other subplots.
    256     * <P>
    257     * The domain axis for the subplot will be set to <code>null</code>.  You
    258     * must ensure that the subplot has a non-null range axis.
    259     *
    260     * @param subplot  the subplot (<code>null</code> not permitted).
    261     * @param weight  the weight (must be >= 1).
    262     */
    263    public void add(XYPlot subplot, int weight) {
    264
    265        if (subplot == null) {
    266            throw new IllegalArgumentException("Null 'subplot' argument.");
    267        }
    268        if (weight <= 0) {
    269            throw new IllegalArgumentException("Require weight >= 1.");
    270        }
    271
    272        // store the plot and its weight
    273        subplot.setParent(this);
    274        subplot.setWeight(weight);
    275        subplot.setInsets(new RectangleInsets(0.0, 0.0, 0.0, 0.0), false);
    276        subplot.setDomainAxis(null);
    277        subplot.addChangeListener(this);
    278        this.subplots.add(subplot);
    279
    280        ValueAxis axis = getDomainAxis();
    281        if (axis != null) {
    282            axis.configure();
    283        }
    284        fireChangeEvent();
    285    }
    286
    287    /**
    288     * Removes a subplot from the combined chart and sends a
    289     * {@link PlotChangeEvent} to all registered listeners.
    290     *
    291     * @param subplot  the subplot (<code>null</code> not permitted).
    292     */
    293    public void remove(XYPlot subplot) {
    294        if (subplot == null) {
    295            throw new IllegalArgumentException(" Null 'subplot' argument.");
    296        }
    297        int position = -1;
    298        int size = this.subplots.size();
    299        int i = 0;
    300        while (position == -1 && i < size) {
    301            if (this.subplots.get(i) == subplot) {
    302                position = i;
    303            }
    304            i++;
    305        }
    306        if (position != -1) {
    307            this.subplots.remove(position);
    308            subplot.setParent(null);
    309            subplot.removeChangeListener(this);
    310            ValueAxis domain = getDomainAxis();
    311            if (domain != null) {
    312                domain.configure();
    313            }
    314            fireChangeEvent();
    315        }
    316    }
    317
    318    /**
    319     * Returns the list of subplots.  The returned list may be empty, but is
    320     * never <code>null</code>.
    321     *
    322     * @return An unmodifiable list of subplots.
    323     */
    324    public List getSubplots() {
    325        if (this.subplots != null) {
    326            return Collections.unmodifiableList(this.subplots);
    327        }
    328        else {
    329            return Collections.EMPTY_LIST;
    330        }
    331    }
    332
    333    /**
    334     * Calculates the axis space required.
    335     *
    336     * @param g2  the graphics device.
    337     * @param plotArea  the plot area.
    338     *
    339     * @return The space.
    340     */
    341    protected AxisSpace calculateAxisSpace(Graphics2D g2,
    342                                           Rectangle2D plotArea) {
    343
    344        AxisSpace space = new AxisSpace();
    345        PlotOrientation orientation = getOrientation();
    346
    347        // work out the space required by the domain axis...
    348        AxisSpace fixed = getFixedDomainAxisSpace();
    349        if (fixed != null) {
    350            if (orientation == PlotOrientation.HORIZONTAL) {
    351                space.setLeft(fixed.getLeft());
    352                space.setRight(fixed.getRight());
    353            }
    354            else if (orientation == PlotOrientation.VERTICAL) {
    355                space.setTop(fixed.getTop());
    356                space.setBottom(fixed.getBottom());
    357            }
    358        }
    359        else {
    360            ValueAxis xAxis = getDomainAxis();
    361            RectangleEdge xEdge = Plot.resolveDomainAxisLocation(
    362                    getDomainAxisLocation(), orientation);
    363            if (xAxis != null) {
    364                space = xAxis.reserveSpace(g2, this, plotArea, xEdge, space);
    365            }
    366        }
    367
    368        Rectangle2D adjustedPlotArea = space.shrink(plotArea, null);
    369
    370        // work out the maximum height or width of the non-shared axes...
    371        int n = this.subplots.size();
    372        int totalWeight = 0;
    373        for (int i = 0; i < n; i++) {
    374            XYPlot sub = (XYPlot) this.subplots.get(i);
    375            totalWeight += sub.getWeight();
    376        }
    377        this.subplotAreas = new Rectangle2D[n];
    378        double x = adjustedPlotArea.getX();
    379        double y = adjustedPlotArea.getY();
    380        double usableSize = 0.0;
    381        if (orientation == PlotOrientation.HORIZONTAL) {
    382            usableSize = adjustedPlotArea.getWidth() - this.gap * (n - 1);
    383        }
    384        else if (orientation == PlotOrientation.VERTICAL) {
    385            usableSize = adjustedPlotArea.getHeight() - this.gap * (n - 1);
    386        }
    387
    388        for (int i = 0; i < n; i++) {
    389            XYPlot plot = (XYPlot) this.subplots.get(i);
    390
    391            // calculate sub-plot area
    392            if (orientation == PlotOrientation.HORIZONTAL) {
    393                double w = usableSize * plot.getWeight() / totalWeight;
    394                this.subplotAreas[i] = new Rectangle2D.Double(x, y, w,
    395                        adjustedPlotArea.getHeight());
    396                x = x + w + this.gap;
    397            }
    398            else if (orientation == PlotOrientation.VERTICAL) {
    399                double h = usableSize * plot.getWeight() / totalWeight;
    400                this.subplotAreas[i] = new Rectangle2D.Double(x, y,
    401                        adjustedPlotArea.getWidth(), h);
    402                y = y + h + this.gap;
    403            }
    404
    405            AxisSpace subSpace = plot.calculateRangeAxisSpace(g2,
    406                    this.subplotAreas[i], null);
    407            space.ensureAtLeast(subSpace);
    408
    409        }
    410
    411        return space;
    412    }
    413
    414    /**
    415     * Draws the plot within the specified area on a graphics device.
    416     *
    417     * @param g2  the graphics device.
    418     * @param area  the plot area (in Java2D space).
    419     * @param anchor  an anchor point in Java2D space (<code>null</code>
    420     *                permitted).
    421     * @param parentState  the state from the parent plot, if there is one
    422     *                     (<code>null</code> permitted).
    423     * @param info  collects chart drawing information (<code>null</code>
    424     *              permitted).
    425     */
    426    public void draw(Graphics2D g2,
    427                     Rectangle2D area,
    428                     Point2D anchor,
    429                     PlotState parentState,
    430                     PlotRenderingInfo info) {
    431
    432        // set up info collection...
    433        if (info != null) {
    434            info.setPlotArea(area);
    435        }
    436
    437        // adjust the drawing area for plot insets (if any)...
    438        RectangleInsets insets = getInsets();
    439        insets.trim(area);
    440
    441        setFixedRangeAxisSpaceForSubplots(null);
    442        AxisSpace space = calculateAxisSpace(g2, area);
    443        Rectangle2D dataArea = space.shrink(area, null);
    444
    445        // set the width and height of non-shared axis of all sub-plots
    446        setFixedRangeAxisSpaceForSubplots(space);
    447
    448        // draw the shared axis
    449        ValueAxis axis = getDomainAxis();
    450        RectangleEdge edge = getDomainAxisEdge();
    451        double cursor = RectangleEdge.coordinate(dataArea, edge);
    452        AxisState axisState = axis.draw(g2, cursor, area, dataArea, edge, info);
    453        if (parentState == null) {
    454            parentState = new PlotState();
    455        }
    456        parentState.getSharedAxisStates().put(axis, axisState);
    457
    458        // draw all the subplots
    459        for (int i = 0; i < this.subplots.size(); i++) {
    460            XYPlot plot = (XYPlot) this.subplots.get(i);
    461            PlotRenderingInfo subplotInfo = null;
    462            if (info != null) {
    463                subplotInfo = new PlotRenderingInfo(info.getOwner());
    464                info.addSubplotInfo(subplotInfo);
    465            }
    466            plot.draw(g2, this.subplotAreas[i], anchor, parentState,
    467                    subplotInfo);
    468        }
    469
    470        if (info != null) {
    471            info.setDataArea(dataArea);
    472        }
    473
    474    }
    475
    476    /**
    477     * Returns a collection of legend items for the plot.
    478     *
    479     * @return The legend items.
    480     */
    481    public LegendItemCollection getLegendItems() {
    482        LegendItemCollection result = getFixedLegendItems();
    483        if (result == null) {
    484            result = new LegendItemCollection();
    485            if (this.subplots != null) {
    486                Iterator iterator = this.subplots.iterator();
    487                while (iterator.hasNext()) {
    488                    XYPlot plot = (XYPlot) iterator.next();
    489                    LegendItemCollection more = plot.getLegendItems();
    490                    result.addAll(more);
    491                }
    492            }
    493        }
    494        return result;
    495    }
    496
    497    /**
    498     * Multiplies the range on the range axis/axes by the specified factor.
    499     *
    500     * @param factor  the zoom factor.
    501     * @param info  the plot rendering info (<code>null</code> not permitted).
    502     * @param source  the source point (<code>null</code> not permitted).
    503     */
    504    public void zoomRangeAxes(double factor, PlotRenderingInfo info,
    505                              Point2D source) {
    506        zoomRangeAxes(factor, info, source, false);
    507    }
    508
    509    /**
    510     * Multiplies the range on the range axis/axes by the specified factor.
    511     *
    512     * @param factor  the zoom factor.
    513     * @param state  the plot state.
    514     * @param source  the source point (in Java2D coordinates).
    515     * @param useAnchor  use source point as zoom anchor?
    516     */
    517    public void zoomRangeAxes(double factor, PlotRenderingInfo state,
    518                              Point2D source, boolean useAnchor) {
    519        // delegate 'state' and 'source' argument checks...
    520        XYPlot subplot = findSubplot(state, source);
    521        if (subplot != null) {
    522            subplot.zoomRangeAxes(factor, state, source, useAnchor);
    523        }
    524        else {
    525            // if the source point doesn't fall within a subplot, we do the
    526            // zoom on all subplots...
    527            Iterator iterator = getSubplots().iterator();
    528            while (iterator.hasNext()) {
    529                subplot = (XYPlot) iterator.next();
    530                subplot.zoomRangeAxes(factor, state, source, useAnchor);
    531            }
    532        }
    533    }
    534
    535    /**
    536     * Zooms in on the range axes.
    537     *
    538     * @param lowerPercent  the lower bound.
    539     * @param upperPercent  the upper bound.
    540     * @param info  the plot rendering info (<code>null</code> not permitted).
    541     * @param source  the source point (<code>null</code> not permitted).
    542     */
    543    public void zoomRangeAxes(double lowerPercent, double upperPercent,
    544                              PlotRenderingInfo info, Point2D source) {
    545        // delegate 'info' and 'source' argument checks...
    546        XYPlot subplot = findSubplot(info, source);
    547        if (subplot != null) {
    548            subplot.zoomRangeAxes(lowerPercent, upperPercent, info, source);
    549        }
    550        else {
    551            // if the source point doesn't fall within a subplot, we do the
    552            // zoom on all subplots...
    553            Iterator iterator = getSubplots().iterator();
    554            while (iterator.hasNext()) {
    555                subplot = (XYPlot) iterator.next();
    556                subplot.zoomRangeAxes(lowerPercent, upperPercent, info, source);
    557            }
    558        }
    559    }
    560
    561    /**
    562     * Returns the subplot (if any) that contains the (x, y) point (specified
    563     * in Java2D space).
    564     *
    565     * @param info  the chart rendering info (<code>null</code> not permitted).
    566     * @param source  the source point (<code>null</code> not permitted).
    567     *
    568     * @return A subplot (possibly <code>null</code>).
    569     */
    570    public XYPlot findSubplot(PlotRenderingInfo info, Point2D source) {
    571        if (info == null) {
    572            throw new IllegalArgumentException("Null 'info' argument.");
    573        }
    574        if (source == null) {
    575            throw new IllegalArgumentException("Null 'source' argument.");
    576        }
    577        XYPlot result = null;
    578        int subplotIndex = info.getSubplotIndex(source);
    579        if (subplotIndex >= 0) {
    580            result =  (XYPlot) this.subplots.get(subplotIndex);
    581        }
    582        return result;
    583    }
    584
    585    /**
    586     * Sets the item renderer FOR ALL SUBPLOTS.  Registered listeners are
    587     * notified that the plot has been modified.
    588     * <P>
    589     * Note: usually you will want to set the renderer independently for each
    590     * subplot, which is NOT what this method does.
    591     *
    592     * @param renderer the new renderer.
    593     */
    594    public void setRenderer(XYItemRenderer renderer) {
    595
    596        super.setRenderer(renderer);  // not strictly necessary, since the
    597                                      // renderer set for the
    598                                      // parent plot is not used
    599
    600        Iterator iterator = this.subplots.iterator();
    601        while (iterator.hasNext()) {
    602            XYPlot plot = (XYPlot) iterator.next();
    603            plot.setRenderer(renderer);
    604        }
    605
    606    }
    607
    608    /**
    609     * Sets the fixed range axis space and sends a {@link PlotChangeEvent} to
    610     * all registered listeners.
    611     *
    612     * @param space  the space (<code>null</code> permitted).
    613     */
    614    public void setFixedRangeAxisSpace(AxisSpace space) {
    615        super.setFixedRangeAxisSpace(space);
    616        setFixedRangeAxisSpaceForSubplots(space);
    617        fireChangeEvent();
    618    }
    619
    620    /**
    621     * Sets the size (width or height, depending on the orientation of the
    622     * plot) for the domain axis of each subplot.
    623     *
    624     * @param space  the space.
    625     */
    626    protected void setFixedRangeAxisSpaceForSubplots(AxisSpace space) {
    627        Iterator iterator = this.subplots.iterator();
    628        while (iterator.hasNext()) {
    629            XYPlot plot = (XYPlot) iterator.next();
    630            plot.setFixedRangeAxisSpace(space, false);
    631        }
    632    }
    633
    634    /**
    635     * Handles a 'click' on the plot by updating the anchor values.
    636     *
    637     * @param x  x-coordinate, where the click occured.
    638     * @param y  y-coordinate, where the click occured.
    639     * @param info  object containing information about the plot dimensions.
    640     */
    641    public void handleClick(int x, int y, PlotRenderingInfo info) {
    642        Rectangle2D dataArea = info.getDataArea();
    643        if (dataArea.contains(x, y)) {
    644            for (int i = 0; i < this.subplots.size(); i++) {
    645                XYPlot subplot = (XYPlot) this.subplots.get(i);
    646                PlotRenderingInfo subplotInfo = info.getSubplotInfo(i);
    647                subplot.handleClick(x, y, subplotInfo);
    648            }
    649        }
    650    }
    651
    652    /**
    653     * Receives a {@link PlotChangeEvent} and responds by notifying all
    654     * listeners.
    655     *
    656     * @param event  the event.
    657     */
    658    public void plotChanged(PlotChangeEvent event) {
    659        notifyListeners(event);
    660    }
    661
    662    /**
    663     * Tests this plot for equality with another object.
    664     *
    665     * @param obj  the other object.
    666     *
    667     * @return <code>true</code> or <code>false</code>.
    668     */
    669    public boolean equals(Object obj) {
    670        if (obj == this) {
    671            return true;
    672        }
    673        if (!(obj instanceof CombinedDomainXYPlot)) {
    674            return false;
    675        }
    676        CombinedDomainXYPlot that = (CombinedDomainXYPlot) obj;
    677        if (this.gap != that.gap) {
    678            return false;
    679        }
    680        if (!ObjectUtilities.equal(this.subplots, that.subplots)) {
    681            return false;
    682        }
    683        return super.equals(obj);
    684    }
    685
    686    /**
    687     * Returns a clone of the annotation.
    688     *
    689     * @return A clone.
    690     *
    691     * @throws CloneNotSupportedException  this class will not throw this
    692     *         exception, but subclasses (if any) might.
    693     */
    694    public Object clone() throws CloneNotSupportedException {
    695
    696        CombinedDomainXYPlot result = (CombinedDomainXYPlot) super.clone();
    697        result.subplots = (List) ObjectUtilities.deepClone(this.subplots);
    698        for (Iterator it = result.subplots.iterator(); it.hasNext();) {
    699            Plot child = (Plot) it.next();
    700            child.setParent(result);
    701        }
    702
    703        // after setting up all the subplots, the shared domain axis may need
    704        // reconfiguring
    705        ValueAxis domainAxis = result.getDomainAxis();
    706        if (domainAxis != null) {
    707            domainAxis.configure();
    708        }
    709
    710        return result;
    711
    712    }
    713
    714}