    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors.
    006     *
    007     * Project Info:  http://www.jfree.org/jfreechart/index.html
    008     *
    009     * This library is free software; you can redistribute it and/or modify it
    010     * under the terms of the GNU Lesser General Public License as published by
    011     * the Free Software Foundation; either version 2.1 of the License, or
    012     * (at your option) any later version.
    013     *
    014     * This library is distributed in the hope that it will be useful, but
    015     * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    016     * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
    017     * License for more details.
    018     *
    019     * You should have received a copy of the GNU Lesser General Public
    020     * License along with this library; if not, write to the Free Software
    021     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
    022     * USA.
    023     *
    024     * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
    025     * in the United States and other countries.]
    026     *
    027     * -------------------------
    028     * 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
    097    package org.jfree.chart.plot;
    098
    099    import java.awt.Graphics2D;
    100    import java.awt.geom.Point2D;
    101    import java.awt.geom.Rectangle2D;
    102    import java.util.Collections;
    103    import java.util.Iterator;
    104    import java.util.List;
    105
    106    import org.jfree.chart.LegendItemCollection;
    107    import org.jfree.chart.axis.AxisSpace;
    108    import org.jfree.chart.axis.AxisState;
    109    import org.jfree.chart.axis.NumberAxis;
    110    import org.jfree.chart.axis.ValueAxis;
    111    import org.jfree.chart.event.PlotChangeEvent;
    112    import org.jfree.chart.event.PlotChangeListener;
    113    import org.jfree.chart.renderer.xy.XYItemRenderer;
    114    import org.jfree.data.Range;
    115    import org.jfree.ui.RectangleEdge;
    116    import org.jfree.ui.RectangleInsets;
    117    import org.jfree.util.ObjectUtilities;
    118
    119    /**
    120     * An extension of {@link XYPlot} that contains multiple subplots that share a
    121     * common domain axis.
    122     */
    123    public 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    }