    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 * CombinedDomainCategoryPlot.java
    029 * -------------------------------
    030 * (C) Copyright 2003-2008, by Object Refinery Limited.
    031 *
    032 * Original Author:  David Gilbert (for Object Refinery Limited);
    033 * Contributor(s):   Nicolas Brodu;
    034 *
    035 * Changes:
    036 * --------
    037 * 16-May-2003 : Version 1 (DG);
    038 * 08-Aug-2003 : Adjusted totalWeight in remove() method (DG);
    039 * 19-Aug-2003 : Added equals() method, implemented Cloneable and
    040 *               Serializable (DG);
    041 * 11-Sep-2003 : Fix cloning support (subplots) (NB);
    042 * 15-Sep-2003 : Implemented PublicCloneable (DG);
    043 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
    044 * 17-Sep-2003 : Updated handling of 'clicks' (DG);
    045 * 04-May-2004 : Added getter/setter methods for 'gap' attribute (DG);
    046 * 12-Nov-2004 : Implemented the Zoomable interface (DG);
    047 * 25-Nov-2004 : Small update to clone() implementation (DG);
    048 * 21-Feb-2005 : The getLegendItems() method now returns the fixed legend
    049 *               items if set (DG);
    050 * 05-May-2005 : Updated draw() method parameters (DG);
    051 * ------------- JFREECHART 1.0.x ---------------------------------------------
    052 * 13-Sep-2006 : Updated API docs (DG);
    053 * 30-Oct-2006 : Added new getCategoriesForAxis() override (DG);
    054 * 17-Apr-2007 : Added null argument checks to findSubplot() (DG);
    055 * 14-Nov-2007 : Updated setFixedRangeAxisSpaceForSubplots() method (DG);
    056 * 27-Mar-2008 : Add documentation for getDataRange() method (DG);
    057 * 31-Mar-2008 : Updated getSubplots() to return EMPTY_LIST for null
    058 *               subplots, as suggested by Richard West (DG);
    059 * 28-Apr-2008 : Fixed zooming problem (see bug 1950037) (DG);
    060 * 26-Jun-2008 : Fixed crosshair support (DG);
    061 * 11-Aug-2008 : Don't store totalWeight of subplots, calculate it as
    062 *               required (DG);
    063 *
    064 */
    065
    066package org.jfree.chart.plot;
    067
    068import java.awt.Graphics2D;
    069import java.awt.geom.Point2D;
    070import java.awt.geom.Rectangle2D;
    071import java.util.Collections;
    072import java.util.Iterator;
    073import java.util.List;
    074
    075import org.jfree.chart.LegendItemCollection;
    076import org.jfree.chart.axis.AxisSpace;
    077import org.jfree.chart.axis.AxisState;
    078import org.jfree.chart.axis.CategoryAxis;
    079import org.jfree.chart.axis.ValueAxis;
    080import org.jfree.chart.event.PlotChangeEvent;
    081import org.jfree.chart.event.PlotChangeListener;
    082import org.jfree.data.Range;
    083import org.jfree.ui.RectangleEdge;
    084import org.jfree.ui.RectangleInsets;
    085import org.jfree.util.ObjectUtilities;
    086
    087/**
    088 * A combined category plot where the domain axis is shared.
    089 */
    090public class CombinedDomainCategoryPlot extends CategoryPlot
    091        implements PlotChangeListener {
    092
    093    /** For serialization. */
    094    private static final long serialVersionUID = 8207194522653701572L;
    095
    096    /** Storage for the subplot references. */
    097    private List subplots;
    098
    099    /** The gap between subplots. */
    100    private double gap;
    101
    102    /** Temporary storage for the subplot areas. */
    103    private transient Rectangle2D[] subplotAreas;
    104    // TODO:  move the above to the plot state
    105
    106    /**
    107     * Default constructor.
    108     */
    109    public CombinedDomainCategoryPlot() {
    110        this(new CategoryAxis());
    111    }
    112
    113    /**
    114     * Creates a new plot.
    115     *
    116     * @param domainAxis  the shared domain axis (<code>null</code> not
    117     *                    permitted).
    118     */
    119    public CombinedDomainCategoryPlot(CategoryAxis domainAxis) {
    120        super(null, domainAxis, null, null);
    121        this.subplots = new java.util.ArrayList();
    122        this.gap = 5.0;
    123    }
    124
    125    /**
    126     * Returns the space between subplots.
    127     *
    128     * @return The gap (in Java2D units).
    129     */
    130    public double getGap() {
    131        return this.gap;
    132    }
    133
    134    /**
    135     * Sets the amount of space between subplots and sends a
    136     * {@link PlotChangeEvent} to all registered listeners.
    137     *
    138     * @param gap  the gap between subplots (in Java2D units).
    139     */
    140    public void setGap(double gap) {
    141        this.gap = gap;
    142        fireChangeEvent();
    143    }
    144
    145    /**
    146     * Adds a subplot to the combined chart and sends a {@link PlotChangeEvent}
    147     * to all registered listeners.
    148     * <br><br>
    149     * The domain axis for the subplot will be set to <code>null</code>.  You
    150     * must ensure that the subplot has a non-null range axis.
    151     *
    152     * @param subplot  the subplot (<code>null</code> not permitted).
    153     */
    154    public void add(CategoryPlot subplot) {
    155        add(subplot, 1);
    156    }
    157
    158    /**
    159     * Adds a subplot to the combined chart and sends a {@link PlotChangeEvent}
    160     * to all registered listeners.
    161     * <br><br>
    162     * The domain axis for the subplot will be set to <code>null</code>.  You
    163     * must ensure that the subplot has a non-null range axis.
    164     *
    165     * @param subplot  the subplot (<code>null</code> not permitted).
    166     * @param weight  the weight (must be >= 1).
    167     */
    168    public void add(CategoryPlot subplot, int weight) {
    169        if (subplot == null) {
    170            throw new IllegalArgumentException("Null 'subplot' argument.");
    171        }
    172        if (weight < 1) {
    173            throw new IllegalArgumentException("Require weight >= 1.");
    174        }
    175        subplot.setParent(this);
    176        subplot.setWeight(weight);
    177        subplot.setInsets(new RectangleInsets(0.0, 0.0, 0.0, 0.0));
    178        subplot.setDomainAxis(null);
    179        subplot.setOrientation(getOrientation());
    180        subplot.addChangeListener(this);
    181        this.subplots.add(subplot);
    182        CategoryAxis axis = getDomainAxis();
    183        if (axis != null) {
    184            axis.configure();
    185        }
    186        fireChangeEvent();
    187    }
    188
    189    /**
    190     * Removes a subplot from the combined chart.  Potentially, this removes
    191     * some unique categories from the overall union of the datasets...so the
    192     * domain axis is reconfigured, then a {@link PlotChangeEvent} is sent to
    193     * all registered listeners.
    194     *
    195     * @param subplot  the subplot (<code>null</code> not permitted).
    196     */
    197    public void remove(CategoryPlot subplot) {
    198        if (subplot == null) {
    199            throw new IllegalArgumentException("Null 'subplot' argument.");
    200        }
    201        int position = -1;
    202        int size = this.subplots.size();
    203        int i = 0;
    204        while (position == -1 && i < size) {
    205            if (this.subplots.get(i) == subplot) {
    206                position = i;
    207            }
    208            i++;
    209        }
    210        if (position != -1) {
    211            this.subplots.remove(position);
    212            subplot.setParent(null);
    213            subplot.removeChangeListener(this);
    214            CategoryAxis domain = getDomainAxis();
    215            if (domain != null) {
    216                domain.configure();
    217            }
    218            fireChangeEvent();
    219        }
    220    }
    221
    222    /**
    223     * Returns the list of subplots.  The returned list may be empty, but is
    224     * never <code>null</code>.
    225     *
    226     * @return An unmodifiable list of subplots.
    227     */
    228    public List getSubplots() {
    229        if (this.subplots != null) {
    230            return Collections.unmodifiableList(this.subplots);
    231        }
    232        else {
    233            return Collections.EMPTY_LIST;
    234        }
    235    }
    236
    237    /**
    238     * Returns the subplot (if any) that contains the (x, y) point (specified
    239     * in Java2D space).
    240     *
    241     * @param info  the chart rendering info (<code>null</code> not permitted).
    242     * @param source  the source point (<code>null</code> not permitted).
    243     *
    244     * @return A subplot (possibly <code>null</code>).
    245     */
    246    public CategoryPlot findSubplot(PlotRenderingInfo info, Point2D source) {
    247        if (info == null) {
    248            throw new IllegalArgumentException("Null 'info' argument.");
    249        }
    250        if (source == null) {
    251            throw new IllegalArgumentException("Null 'source' argument.");
    252        }
    253        CategoryPlot result = null;
    254        int subplotIndex = info.getSubplotIndex(source);
    255        if (subplotIndex >= 0) {
    256            result =  (CategoryPlot) this.subplots.get(subplotIndex);
    257        }
    258        return result;
    259    }
    260
    261    /**
    262     * Multiplies the range on the range axis/axes by the specified factor.
    263     *
    264     * @param factor  the zoom factor.
    265     * @param info  the plot rendering info (<code>null</code> not permitted).
    266     * @param source  the source point (<code>null</code> not permitted).
    267     */
    268    public void zoomRangeAxes(double factor, PlotRenderingInfo info,
    269                              Point2D source) {
    270        zoomRangeAxes(factor, info, source, false);
    271    }
    272
    273    /**
    274     * Multiplies the range on the range axis/axes by the specified factor.
    275     *
    276     * @param factor  the zoom factor.
    277     * @param info  the plot rendering info (<code>null</code> not permitted).
    278     * @param source  the source point (<code>null</code> not permitted).
    279     * @param useAnchor  zoom about the anchor point?
    280     */
    281    public void zoomRangeAxes(double factor, PlotRenderingInfo info,
    282                              Point2D source, boolean useAnchor) {
    283        // delegate 'info' and 'source' argument checks...
    284        CategoryPlot subplot = findSubplot(info, source);
    285        if (subplot != null) {
    286            subplot.zoomRangeAxes(factor, info, source, useAnchor);
    287        }
    288        else {
    289            // if the source point doesn't fall within a subplot, we do the
    290            // zoom on all subplots...
    291            Iterator iterator = getSubplots().iterator();
    292            while (iterator.hasNext()) {
    293                subplot = (CategoryPlot) iterator.next();
    294                subplot.zoomRangeAxes(factor, info, source, useAnchor);
    295            }
    296        }
    297    }
    298
    299    /**
    300     * Zooms in on the range axes.
    301     *
    302     * @param lowerPercent  the lower bound.
    303     * @param upperPercent  the upper bound.
    304     * @param info  the plot rendering info (<code>null</code> not permitted).
    305     * @param source  the source point (<code>null</code> not permitted).
    306     */
    307    public void zoomRangeAxes(double lowerPercent, double upperPercent,
    308                              PlotRenderingInfo info, Point2D source) {
    309        // delegate 'info' and 'source' argument checks...
    310        CategoryPlot subplot = findSubplot(info, source);
    311        if (subplot != null) {
    312            subplot.zoomRangeAxes(lowerPercent, upperPercent, info, source);
    313        }
    314        else {
    315            // if the source point doesn't fall within a subplot, we do the
    316            // zoom on all subplots...
    317            Iterator iterator = getSubplots().iterator();
    318            while (iterator.hasNext()) {
    319                subplot = (CategoryPlot) iterator.next();
    320                subplot.zoomRangeAxes(lowerPercent, upperPercent, info, source);
    321            }
    322        }
    323    }
    324
    325    /**
    326     * Calculates the space required for the axes.
    327     *
    328     * @param g2  the graphics device.
    329     * @param plotArea  the plot area.
    330     *
    331     * @return The space required for the axes.
    332     */
    333    protected AxisSpace calculateAxisSpace(Graphics2D g2,
    334                                           Rectangle2D plotArea) {
    335
    336        AxisSpace space = new AxisSpace();
    337        PlotOrientation orientation = getOrientation();
    338
    339        // work out the space required by the domain axis...
    340        AxisSpace fixed = getFixedDomainAxisSpace();
    341        if (fixed != null) {
    342            if (orientation == PlotOrientation.HORIZONTAL) {
    343                space.setLeft(fixed.getLeft());
    344                space.setRight(fixed.getRight());
    345            }
    346            else if (orientation == PlotOrientation.VERTICAL) {
    347                space.setTop(fixed.getTop());
    348                space.setBottom(fixed.getBottom());
    349            }
    350        }
    351        else {
    352            CategoryAxis categoryAxis = getDomainAxis();
    353            RectangleEdge categoryEdge = Plot.resolveDomainAxisLocation(
    354                    getDomainAxisLocation(), orientation);
    355            if (categoryAxis != null) {
    356                space = categoryAxis.reserveSpace(g2, this, plotArea,
    357                        categoryEdge, space);
    358            }
    359            else {
    360                if (getDrawSharedDomainAxis()) {
    361                    space = getDomainAxis().reserveSpace(g2, this, plotArea,
    362                            categoryEdge, space);
    363                }
    364            }
    365        }
    366
    367        Rectangle2D adjustedPlotArea = space.shrink(plotArea, null);
    368
    369        // work out the maximum height or width of the non-shared axes...
    370        int n = this.subplots.size();
    371        int totalWeight = 0;
    372        for (int i = 0; i < n; i++) {
    373            CategoryPlot sub = (CategoryPlot) this.subplots.get(i);
    374            totalWeight += sub.getWeight();
    375        }
    376        this.subplotAreas = new Rectangle2D[n];
    377        double x = adjustedPlotArea.getX();
    378        double y = adjustedPlotArea.getY();
    379        double usableSize = 0.0;
    380        if (orientation == PlotOrientation.HORIZONTAL) {
    381            usableSize = adjustedPlotArea.getWidth() - this.gap * (n - 1);
    382        }
    383        else if (orientation == PlotOrientation.VERTICAL) {
    384            usableSize = adjustedPlotArea.getHeight() - this.gap * (n - 1);
    385        }
    386
    387        for (int i = 0; i < n; i++) {
    388            CategoryPlot plot = (CategoryPlot) this.subplots.get(i);
    389
    390            // calculate sub-plot area
    391            if (orientation == PlotOrientation.HORIZONTAL) {
    392                double w = usableSize * plot.getWeight() / totalWeight;
    393                this.subplotAreas[i] = new Rectangle2D.Double(x, y, w,
    394                        adjustedPlotArea.getHeight());
    395                x = x + w + this.gap;
    396            }
    397            else if (orientation == PlotOrientation.VERTICAL) {
    398                double h = usableSize * plot.getWeight() / totalWeight;
    399                this.subplotAreas[i] = new Rectangle2D.Double(x, y,
    400                        adjustedPlotArea.getWidth(), h);
    401                y = y + h + this.gap;
    402            }
    403
    404            AxisSpace subSpace = plot.calculateRangeAxisSpace(g2,
    405                    this.subplotAreas[i], null);
    406            space.ensureAtLeast(subSpace);
    407
    408        }
    409
    410        return space;
    411    }
    412
    413    /**
    414     * Draws the plot on a Java 2D graphics device (such as the screen or a
    415     * printer).  Will perform all the placement calculations for each of the
    416     * sub-plots and then tell these to draw themselves.
    417     *
    418     * @param g2  the graphics device.
    419     * @param area  the area within which the plot (including axis labels)
    420     *              should be drawn.
    421     * @param anchor  the anchor point (<code>null</code> permitted).
    422     * @param parentState  the state from the parent plot, if there is one.
    423     * @param info  collects information about the drawing (<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        area.setRect(area.getX() + insets.getLeft(),
    440                area.getY() + insets.getTop(),
    441                area.getWidth() - insets.getLeft() - insets.getRight(),
    442                area.getHeight() - insets.getTop() - insets.getBottom());
    443
    444
    445        // calculate the data area...
    446        setFixedRangeAxisSpaceForSubplots(null);
    447        AxisSpace space = calculateAxisSpace(g2, area);
    448        Rectangle2D dataArea = space.shrink(area, null);
    449
    450        // set the width and height of non-shared axis of all sub-plots
    451        setFixedRangeAxisSpaceForSubplots(space);
    452
    453        // draw the shared axis
    454        CategoryAxis axis = getDomainAxis();
    455        RectangleEdge domainEdge = getDomainAxisEdge();
    456        double cursor = RectangleEdge.coordinate(dataArea, domainEdge);
    457        AxisState axisState = axis.draw(g2, cursor, area, dataArea,
    458                domainEdge, info);
    459        if (parentState == null) {
    460            parentState = new PlotState();
    461        }
    462        parentState.getSharedAxisStates().put(axis, axisState);
    463
    464        // draw all the subplots
    465        for (int i = 0; i < this.subplots.size(); i++) {
    466            CategoryPlot plot = (CategoryPlot) this.subplots.get(i);
    467            PlotRenderingInfo subplotInfo = null;
    468            if (info != null) {
    469                subplotInfo = new PlotRenderingInfo(info.getOwner());
    470                info.addSubplotInfo(subplotInfo);
    471            }
    472            Point2D subAnchor = null;
    473            if (anchor != null && this.subplotAreas[i].contains(anchor)) {
    474                subAnchor = anchor;
    475            }
    476            plot.draw(g2, this.subplotAreas[i], subAnchor, parentState,
    477                    subplotInfo);
    478        }
    479
    480        if (info != null) {
    481            info.setDataArea(dataArea);
    482        }
    483
    484    }
    485
    486    /**
    487     * Sets the size (width or height, depending on the orientation of the
    488     * plot) for the range axis of each subplot.
    489     *
    490     * @param space  the space (<code>null</code> permitted).
    491     */
    492    protected void setFixedRangeAxisSpaceForSubplots(AxisSpace space) {
    493        Iterator iterator = this.subplots.iterator();
    494        while (iterator.hasNext()) {
    495            CategoryPlot plot = (CategoryPlot) iterator.next();
    496            plot.setFixedRangeAxisSpace(space, false);
    497        }
    498    }
    499
    500    /**
    501     * Sets the orientation of the plot (and all subplots).
    502     *
    503     * @param orientation  the orientation (<code>null</code> not permitted).
    504     */
    505    public void setOrientation(PlotOrientation orientation) {
    506
    507        super.setOrientation(orientation);
    508
    509        Iterator iterator = this.subplots.iterator();
    510        while (iterator.hasNext()) {
    511            CategoryPlot plot = (CategoryPlot) iterator.next();
    512            plot.setOrientation(orientation);
    513        }
    514
    515    }
    516
    517    /**
    518     * Returns a range representing the extent of the data values in this plot
    519     * (obtained from the subplots) that will be rendered against the specified
    520     * axis.  NOTE: This method is intended for internal JFreeChart use, and
    521     * is public only so that code in the axis classes can call it.  Since,
    522     * for this class, the domain axis is a {@link CategoryAxis}
    523     * (not a <code>ValueAxis</code}) and subplots have independent range axes,
    524     * the JFreeChart code will never call this method (although this is not
    525     * checked/enforced).
    526      *
    527      * @param axis  the axis.
    528      *
    529      * @return The range.
    530      */
    531     public Range getDataRange(ValueAxis axis) {
    532         // override is only for documentation purposes
    533         return super.getDataRange(axis);
    534     }
    535
    536     /**
    537     * Returns a collection of legend items for the plot.
    538     *
    539     * @return The legend items.
    540     */
    541    public LegendItemCollection getLegendItems() {
    542        LegendItemCollection result = getFixedLegendItems();
    543        if (result == null) {
    544            result = new LegendItemCollection();
    545            if (this.subplots != null) {
    546                Iterator iterator = this.subplots.iterator();
    547                while (iterator.hasNext()) {
    548                    CategoryPlot plot = (CategoryPlot) iterator.next();
    549                    LegendItemCollection more = plot.getLegendItems();
    550                    result.addAll(more);
    551                }
    552            }
    553        }
    554        return result;
    555    }
    556
    557    /**
    558     * Returns an unmodifiable list of the categories contained in all the
    559     * subplots.
    560     *
    561     * @return The list.
    562     */
    563    public List getCategories() {
    564        List result = new java.util.ArrayList();
    565        if (this.subplots != null) {
    566            Iterator iterator = this.subplots.iterator();
    567            while (iterator.hasNext()) {
    568                CategoryPlot plot = (CategoryPlot) iterator.next();
    569                List more = plot.getCategories();
    570                Iterator moreIterator = more.iterator();
    571                while (moreIterator.hasNext()) {
    572                    Comparable category = (Comparable) moreIterator.next();
    573                    if (!result.contains(category)) {
    574                        result.add(category);
    575                    }
    576                }
    577            }
    578        }
    579        return Collections.unmodifiableList(result);
    580    }
    581
    582    /**
    583     * Overridden to return the categories in the subplots.
    584     *
    585     * @param axis  ignored.
    586     *
    587     * @return A list of the categories in the subplots.
    588     *
    589     * @since 1.0.3
    590     */
    591    public List getCategoriesForAxis(CategoryAxis axis) {
    592        // FIXME:  this code means that it is not possible to use more than
    593        // one domain axis for the combined plots...
    594        return getCategories();
    595    }
    596
    597    /**
    598     * Handles a 'click' on the plot.
    599     *
    600     * @param x  x-coordinate of the click.
    601     * @param y  y-coordinate of the click.
    602     * @param info  information about the plot's dimensions.
    603     *
    604     */
    605    public void handleClick(int x, int y, PlotRenderingInfo info) {
    606
    607        Rectangle2D dataArea = info.getDataArea();
    608        if (dataArea.contains(x, y)) {
    609            for (int i = 0; i < this.subplots.size(); i++) {
    610                CategoryPlot subplot = (CategoryPlot) this.subplots.get(i);
    611                PlotRenderingInfo subplotInfo = info.getSubplotInfo(i);
    612                subplot.handleClick(x, y, subplotInfo);
    613            }
    614        }
    615
    616    }
    617
    618    /**
    619     * Receives a {@link PlotChangeEvent} and responds by notifying all
    620     * listeners.
    621     *
    622     * @param event  the event.
    623     */
    624    public void plotChanged(PlotChangeEvent event) {
    625        notifyListeners(event);
    626    }
    627
    628    /**
    629     * Tests the plot for equality with an arbitrary object.
    630     *
    631     * @param obj  the object (<code>null</code> permitted).
    632     *
    633     * @return A boolean.
    634     */
    635    public boolean equals(Object obj) {
    636        if (obj == this) {
    637            return true;
    638        }
    639        if (!(obj instanceof CombinedDomainCategoryPlot)) {
    640            return false;
    641        }
    642        CombinedDomainCategoryPlot that = (CombinedDomainCategoryPlot) obj;
    643        if (this.gap != that.gap) {
    644            return false;
    645        }
    646        if (!ObjectUtilities.equal(this.subplots, that.subplots)) {
    647            return false;
    648        }
    649        return super.equals(obj);
    650    }
    651
    652    /**
    653     * Returns a clone of the plot.
    654     *
    655     * @return A clone.
    656     *
    657     * @throws CloneNotSupportedException  this class will not throw this
    658     *         exception, but subclasses (if any) might.
    659     */
    660    public Object clone() throws CloneNotSupportedException {
    661
    662        CombinedDomainCategoryPlot result
    663            = (CombinedDomainCategoryPlot) super.clone();
    664        result.subplots = (List) ObjectUtilities.deepClone(this.subplots);
    665        for (Iterator it = result.subplots.iterator(); it.hasNext();) {
    666            Plot child = (Plot) it.next();
    667            child.setParent(result);
    668        }
    669        return result;
    670
    671    }
    672
    673}