    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 * DefaultStatisticalCategoryDataset.java
    029 * --------------------------------------
    030 * (C) Copyright 2002-2011, by Pascal Collet and Contributors.
    031 *
    032 * Original Author:  Pascal Collet;
    033 * Contributor(s):   David Gilbert (for Object Refinery Limited);
    034 *
    035 * Changes
    036 * -------
    037 * 21-Aug-2002 : Version 1, contributed by Pascal Collet (DG);
    038 * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG);
    039 * 05-Feb-2003 : Revised implementation to use KeyedObjects2D (DG);
    040 * 28-Aug-2003 : Moved from org.jfree.data --> org.jfree.data.statistics (DG);
    041 * 06-Oct-2003 : Removed incorrect Javadoc text (DG);
    042 * 18-Nov-2004 : Updated for changes in RangeInfo interface (DG);
    043 * 11-Jan-2005 : Removed deprecated code in preparation for the 1.0.0
    044 *               release (DG);
    045 * 01-Feb-2005 : Changed minimumRangeValue and maximumRangeValue from Double
    046 *               to double (DG);
    047 * 05-Feb-2005 : Implemented equals() method (DG);
    048 * ------------- JFREECHART 1.0.x ---------------------------------------------
    049 * 08-Aug-2006 : Reworked implementation of RangeInfo methods (DG);
    050 * 02-Feb-2007 : Removed author tags from all over JFreeChart sources (DG);
    051 * 28-Sep-2007 : Fixed cloning bug (DG);
    052 * 02-Oct-2007 : Fixed bug updating cached range values (DG);
    053 * 19-May-2009 : Fixed FindBugs warnings, patch by Michal Wozniak (DG);
    054 * 20-Oct-2011 : Fixed getRangeBounds() bug 3072674 (DG);
    055 *
    056 */
    057
    058package org.jfree.data.statistics;
    059
    060import java.util.List;
    061
    062import org.jfree.data.KeyedObjects2D;
    063import org.jfree.data.Range;
    064import org.jfree.data.RangeInfo;
    065import org.jfree.data.general.AbstractDataset;
    066import org.jfree.data.general.DatasetChangeEvent;
    067import org.jfree.util.PublicCloneable;
    068
    069/**
    070 * A convenience class that provides a default implementation of the
    071 * {@link StatisticalCategoryDataset} interface.
    072 */
    073public class DefaultStatisticalCategoryDataset extends AbstractDataset
    074        implements StatisticalCategoryDataset, RangeInfo, PublicCloneable {
    075
    076    /** Storage for the data. */
    077    private KeyedObjects2D data;
    078
    079    /** The minimum range value. */
    080    private double minimumRangeValue;
    081
    082    /** The row index for the minimum range value. */
    083    private int minimumRangeValueRow;
    084
    085    /** The column index for the minimum range value. */
    086    private int minimumRangeValueColumn;
    087
    088    /** The minimum range value including the standard deviation. */
    089    private double minimumRangeValueIncStdDev;
    090
    091    /**
    092     * The row index for the minimum range value (including the standard
    093     * deviation).
    094     */
    095    private int minimumRangeValueIncStdDevRow;
    096
    097    /**
    098     * The column index for the minimum range value (including the standard
    099     * deviation).
    100     */
    101    private int minimumRangeValueIncStdDevColumn;
    102
    103    /** The maximum range value. */
    104    private double maximumRangeValue;
    105
    106    /** The row index for the maximum range value. */
    107    private int maximumRangeValueRow;
    108
    109    /** The column index for the maximum range value. */
    110    private int maximumRangeValueColumn;
    111
    112    /** The maximum range value including the standard deviation. */
    113    private double maximumRangeValueIncStdDev;
    114
    115    /**
    116     * The row index for the maximum range value (including the standard
    117     * deviation).
    118     */
    119    private int maximumRangeValueIncStdDevRow;
    120
    121    /**
    122     * The column index for the maximum range value (including the standard
    123     * deviation).
    124     */
    125    private int maximumRangeValueIncStdDevColumn;
    126
    127    /**
    128     * Creates a new dataset.
    129     */
    130    public DefaultStatisticalCategoryDataset() {
    131        this.data = new KeyedObjects2D();
    132        this.minimumRangeValue = Double.NaN;
    133        this.minimumRangeValueRow = -1;
    134        this.minimumRangeValueColumn = -1;
    135        this.maximumRangeValue = Double.NaN;
    136        this.maximumRangeValueRow = -1;
    137        this.maximumRangeValueColumn = -1;
    138        this.minimumRangeValueIncStdDev = Double.NaN;
    139        this.minimumRangeValueIncStdDevRow = -1;
    140        this.minimumRangeValueIncStdDevColumn = -1;
    141        this.maximumRangeValueIncStdDev = Double.NaN;
    142        this.maximumRangeValueIncStdDevRow = -1;
    143        this.maximumRangeValueIncStdDevColumn = -1;
    144    }
    145
    146    /**
    147     * Returns the mean value for an item.
    148     *
    149     * @param row  the row index (zero-based).
    150     * @param column  the column index (zero-based).
    151     *
    152     * @return The mean value (possibly <code>null</code>).
    153     */
    154    public Number getMeanValue(int row, int column) {
    155        Number result = null;
    156        MeanAndStandardDeviation masd = (MeanAndStandardDeviation)
    157                this.data.getObject(row, column);
    158        if (masd != null) {
    159            result = masd.getMean();
    160        }
    161        return result;
    162    }
    163
    164    /**
    165     * Returns the value for an item (for this dataset, the mean value is
    166     * returned).
    167     *
    168     * @param row  the row index.
    169     * @param column  the column index.
    170     *
    171     * @return The value (possibly <code>null</code>).
    172     */
    173    public Number getValue(int row, int column) {
    174        return getMeanValue(row, column);
    175    }
    176
    177    /**
    178     * Returns the value for an item (for this dataset, the mean value is
    179     * returned).
    180     *
    181     * @param rowKey  the row key.
    182     * @param columnKey  the columnKey.
    183     *
    184     * @return The value (possibly <code>null</code>).
    185     */
    186    public Number getValue(Comparable rowKey, Comparable columnKey) {
    187        return getMeanValue(rowKey, columnKey);
    188    }
    189
    190    /**
    191     * Returns the mean value for an item.
    192     *
    193     * @param rowKey  the row key.
    194     * @param columnKey  the columnKey.
    195     *
    196     * @return The mean value (possibly <code>null</code>).
    197     */
    198    public Number getMeanValue(Comparable rowKey, Comparable columnKey) {
    199        Number result = null;
    200        MeanAndStandardDeviation masd = (MeanAndStandardDeviation)
    201                this.data.getObject(rowKey, columnKey);
    202        if (masd != null) {
    203            result = masd.getMean();
    204        }
    205        return result;
    206    }
    207
    208    /**
    209     * Returns the standard deviation value for an item.
    210     *
    211     * @param row  the row index (zero-based).
    212     * @param column  the column index (zero-based).
    213     *
    214     * @return The standard deviation (possibly <code>null</code>).
    215     */
    216    public Number getStdDevValue(int row, int column) {
    217        Number result = null;
    218        MeanAndStandardDeviation masd = (MeanAndStandardDeviation)
    219                this.data.getObject(row, column);
    220        if (masd != null) {
    221            result = masd.getStandardDeviation();
    222        }
    223        return result;
    224    }
    225
    226    /**
    227     * Returns the standard deviation value for an item.
    228     *
    229     * @param rowKey  the row key.
    230     * @param columnKey  the columnKey.
    231     *
    232     * @return The standard deviation (possibly <code>null</code>).
    233     */
    234    public Number getStdDevValue(Comparable rowKey, Comparable columnKey) {
    235        Number result = null;
    236        MeanAndStandardDeviation masd = (MeanAndStandardDeviation)
    237                this.data.getObject(rowKey, columnKey);
    238        if (masd != null) {
    239            result = masd.getStandardDeviation();
    240        }
    241        return result;
    242    }
    243
    244    /**
    245     * Returns the column index for a given key.
    246     *
    247     * @param key  the column key (<code>null</code> not permitted).
    248     *
    249     * @return The column index.
    250     */
    251    public int getColumnIndex(Comparable key) {
    252        // defer null argument check
    253        return this.data.getColumnIndex(key);
    254    }
    255
    256    /**
    257     * Returns a column key.
    258     *
    259     * @param column  the column index (zero-based).
    260     *
    261     * @return The column key.
    262     */
    263    public Comparable getColumnKey(int column) {
    264        return this.data.getColumnKey(column);
    265    }
    266
    267    /**
    268     * Returns the column keys.
    269     *
    270     * @return The keys.
    271     */
    272    public List getColumnKeys() {
    273        return this.data.getColumnKeys();
    274    }
    275
    276    /**
    277     * Returns the row index for a given key.
    278     *
    279     * @param key  the row key (<code>null</code> not permitted).
    280     *
    281     * @return The row index.
    282     */
    283    public int getRowIndex(Comparable key) {
    284        // defer null argument check
    285        return this.data.getRowIndex(key);
    286    }
    287
    288    /**
    289     * Returns a row key.
    290     *
    291     * @param row  the row index (zero-based).
    292     *
    293     * @return The row key.
    294     */
    295    public Comparable getRowKey(int row) {
    296        return this.data.getRowKey(row);
    297    }
    298
    299    /**
    300     * Returns the row keys.
    301     *
    302     * @return The keys.
    303     */
    304    public List getRowKeys() {
    305        return this.data.getRowKeys();
    306    }
    307
    308    /**
    309     * Returns the number of rows in the table.
    310     *
    311     * @return The row count.
    312     *
    313     * @see #getColumnCount()
    314     */
    315    public int getRowCount() {
    316        return this.data.getRowCount();
    317    }
    318
    319    /**
    320     * Returns the number of columns in the table.
    321     *
    322     * @return The column count.
    323     *
    324     * @see #getRowCount()
    325     */
    326    public int getColumnCount() {
    327        return this.data.getColumnCount();
    328    }
    329
    330    /**
    331     * Adds a mean and standard deviation to the table.
    332     *
    333     * @param mean  the mean.
    334     * @param standardDeviation  the standard deviation.
    335     * @param rowKey  the row key.
    336     * @param columnKey  the column key.
    337     */
    338    public void add(double mean, double standardDeviation,
    339                    Comparable rowKey, Comparable columnKey) {
    340        add(new Double(mean), new Double(standardDeviation), rowKey, columnKey);
    341    }
    342
    343    /**
    344     * Adds a mean and standard deviation to the table.
    345     *
    346     * @param mean  the mean.
    347     * @param standardDeviation  the standard deviation.
    348     * @param rowKey  the row key.
    349     * @param columnKey  the column key.
    350     */
    351    public void add(Number mean, Number standardDeviation,
    352                    Comparable rowKey, Comparable columnKey) {
    353        MeanAndStandardDeviation item = new MeanAndStandardDeviation(
    354                mean, standardDeviation);
    355        this.data.addObject(item, rowKey, columnKey);
    356
    357        double m = Double.NaN;
    358        double sd = Double.NaN;
    359        if (mean != null) {
    360            m = mean.doubleValue();
    361        }
    362        if (standardDeviation != null) {
    363            sd = standardDeviation.doubleValue();
    364        }
    365
    366        // update cached range values
    367        int r = this.data.getColumnIndex(columnKey);
    368        int c = this.data.getRowIndex(rowKey);
    369        if ((r == this.maximumRangeValueRow && c
    370                == this.maximumRangeValueColumn) || (r
    371                == this.maximumRangeValueIncStdDevRow && c
    372                == this.maximumRangeValueIncStdDevColumn) || (r
    373                == this.minimumRangeValueRow && c
    374                == this.minimumRangeValueColumn) || (r
    375                == this.minimumRangeValueIncStdDevRow && c
    376                == this.minimumRangeValueIncStdDevColumn)) {
    377
    378            // iterate over all data items and update mins and maxes
    379            updateBounds();
    380        }
    381        else {
    382            if (!Double.isNaN(m)) {
    383                if (Double.isNaN(this.maximumRangeValue)
    384                        || m > this.maximumRangeValue) {
    385                    this.maximumRangeValue = m;
    386                    this.maximumRangeValueRow = r;
    387                    this.maximumRangeValueColumn = c;
    388                }
    389            }
    390
    391            if (!Double.isNaN(m + sd)) {
    392                if (Double.isNaN(this.maximumRangeValueIncStdDev)
    393                        || (m + sd) > this.maximumRangeValueIncStdDev) {
    394                    this.maximumRangeValueIncStdDev = m + sd;
    395                    this.maximumRangeValueIncStdDevRow = r;
    396                    this.maximumRangeValueIncStdDevColumn = c;
    397                }
    398            }
    399
    400            if (!Double.isNaN(m)) {
    401                if (Double.isNaN(this.minimumRangeValue)
    402                        || m < this.minimumRangeValue) {
    403                    this.minimumRangeValue = m;
    404                    this.minimumRangeValueRow = r;
    405                    this.minimumRangeValueColumn = c;
    406                }
    407            }
    408
    409            if (!Double.isNaN(m - sd)) {
    410                if (Double.isNaN(this.minimumRangeValueIncStdDev)
    411                        || (m - sd) < this.minimumRangeValueIncStdDev) {
    412                    this.minimumRangeValueIncStdDev = m - sd;
    413                    this.minimumRangeValueIncStdDevRow = r;
    414                    this.minimumRangeValueIncStdDevColumn = c;
    415                }
    416            }
    417        }
    418        fireDatasetChanged();
    419    }
    420
    421    /**
    422     * Removes an item from the dataset and sends a {@link DatasetChangeEvent}
    423     * to all registered listeners.
    424     *
    425     * @param rowKey  the row key (<code>null</code> not permitted).
    426     * @param columnKey  the column key (<code>null</code> not permitted).
    427     *
    428     * @see #add(double, double, Comparable, Comparable)
    429     *
    430     * @since 1.0.7
    431     */
    432    public void remove(Comparable rowKey, Comparable columnKey) {
    433        // defer null argument checks
    434        int r = getRowIndex(rowKey);
    435        int c = getColumnIndex(columnKey);
    436        this.data.removeObject(rowKey, columnKey);
    437
    438        // if this cell held a maximum and/or minimum value, we'll need to
    439        // update the cached bounds...
    440        if ((r == this.maximumRangeValueRow && c
    441                == this.maximumRangeValueColumn) || (r
    442                == this.maximumRangeValueIncStdDevRow && c
    443                == this.maximumRangeValueIncStdDevColumn) || (r
    444                == this.minimumRangeValueRow && c
    445                == this.minimumRangeValueColumn) || (r
    446                == this.minimumRangeValueIncStdDevRow && c
    447                == this.minimumRangeValueIncStdDevColumn)) {
    448
    449            // iterate over all data items and update mins and maxes
    450            updateBounds();
    451        }
    452
    453        fireDatasetChanged();
    454    }
    455
    456
    457    /**
    458     * Removes a row from the dataset and sends a {@link DatasetChangeEvent}
    459     * to all registered listeners.
    460     *
    461     * @param rowIndex  the row index.
    462     *
    463     * @see #removeColumn(int)
    464     *
    465     * @since 1.0.7
    466     */
    467    public void removeRow(int rowIndex) {
    468        this.data.removeRow(rowIndex);
    469        updateBounds();
    470        fireDatasetChanged();
    471    }
    472
    473    /**
    474     * Removes a row from the dataset and sends a {@link DatasetChangeEvent}
    475     * to all registered listeners.
    476     *
    477     * @param rowKey  the row key (<code>null</code> not permitted).
    478     *
    479     * @see #removeColumn(Comparable)
    480     *
    481     * @since 1.0.7
    482     */
    483    public void removeRow(Comparable rowKey) {
    484        this.data.removeRow(rowKey);
    485        updateBounds();
    486        fireDatasetChanged();
    487    }
    488
    489    /**
    490     * Removes a column from the dataset and sends a {@link DatasetChangeEvent}
    491     * to all registered listeners.
    492     *
    493     * @param columnIndex  the column index.
    494     *
    495     * @see #removeRow(int)
    496     *
    497     * @since 1.0.7
    498     */
    499    public void removeColumn(int columnIndex) {
    500        this.data.removeColumn(columnIndex);
    501        updateBounds();
    502        fireDatasetChanged();
    503    }
    504
    505    /**
    506     * Removes a column from the dataset and sends a {@link DatasetChangeEvent}
    507     * to all registered listeners.
    508     *
    509     * @param columnKey  the column key (<code>null</code> not permitted).
    510     *
    511     * @see #removeRow(Comparable)
    512     *
    513     * @since 1.0.7
    514     */
    515    public void removeColumn(Comparable columnKey) {
    516        this.data.removeColumn(columnKey);
    517        updateBounds();
    518        fireDatasetChanged();
    519    }
    520
    521    /**
    522     * Clears all data from the dataset and sends a {@link DatasetChangeEvent}
    523     * to all registered listeners.
    524     *
    525     * @since 1.0.7
    526     */
    527    public void clear() {
    528        this.data.clear();
    529        updateBounds();
    530        fireDatasetChanged();
    531    }
    532
    533    /**
    534     * Iterate over all the data items and update the cached bound values.
    535     */
    536    private void updateBounds() {
    537        this.maximumRangeValue = Double.NaN;
    538        this.maximumRangeValueRow = -1;
    539        this.maximumRangeValueColumn = -1;
    540        this.minimumRangeValue = Double.NaN;
    541        this.minimumRangeValueRow = -1;
    542        this.minimumRangeValueColumn = -1;
    543        this.maximumRangeValueIncStdDev = Double.NaN;
    544        this.maximumRangeValueIncStdDevRow = -1;
    545        this.maximumRangeValueIncStdDevColumn = -1;
    546        this.minimumRangeValueIncStdDev = Double.NaN;
    547        this.minimumRangeValueIncStdDevRow = -1;
    548        this.minimumRangeValueIncStdDevColumn = -1;
    549
    550        int rowCount = this.data.getRowCount();
    551        int columnCount = this.data.getColumnCount();
    552        for (int r = 0; r < rowCount; r++) {
    553            for (int c = 0; c < columnCount; c++) {
    554                MeanAndStandardDeviation masd = (MeanAndStandardDeviation)
    555                        this.data.getObject(r, c);
    556                if (masd == null) {
    557                    continue;
    558                }
    559                double m = masd.getMeanValue();
    560                double sd = masd.getStandardDeviationValue();
    561
    562                if (!Double.isNaN(m)) {
    563
    564                    // update the max value
    565                    if (Double.isNaN(this.maximumRangeValue)) {
    566                        this.maximumRangeValue = m;
    567                        this.maximumRangeValueRow = r;
    568                        this.maximumRangeValueColumn = c;
    569                    }
    570                    else {
    571                        if (m > this.maximumRangeValue) {
    572                            this.maximumRangeValue = m;
    573                            this.maximumRangeValueRow = r;
    574                            this.maximumRangeValueColumn = c;
    575                        }
    576                    }
    577
    578                    // update the min value
    579                    if (Double.isNaN(this.minimumRangeValue)) {
    580                        this.minimumRangeValue = m;
    581                        this.minimumRangeValueRow = r;
    582                        this.minimumRangeValueColumn = c;
    583                    }
    584                    else {
    585                        if (m < this.minimumRangeValue) {
    586                            this.minimumRangeValue = m;
    587                            this.minimumRangeValueRow = r;
    588                            this.minimumRangeValueColumn = c;
    589                        }
    590                    }
    591
    592                    if (!Double.isNaN(sd)) {
    593                        // update the max value
    594                        if (Double.isNaN(this.maximumRangeValueIncStdDev)) {
    595                            this.maximumRangeValueIncStdDev = m + sd;
    596                            this.maximumRangeValueIncStdDevRow = r;
    597                            this.maximumRangeValueIncStdDevColumn = c;
    598                        }
    599                        else {
    600                            if (m + sd > this.maximumRangeValueIncStdDev) {
    601                                this.maximumRangeValueIncStdDev = m + sd;
    602                                this.maximumRangeValueIncStdDevRow = r;
    603                                this.maximumRangeValueIncStdDevColumn = c;
    604                            }
    605                        }
    606
    607                        // update the min value
    608                        if (Double.isNaN(this.minimumRangeValueIncStdDev)) {
    609                            this.minimumRangeValueIncStdDev = m - sd;
    610                            this.minimumRangeValueIncStdDevRow = r;
    611                            this.minimumRangeValueIncStdDevColumn = c;
    612                        }
    613                        else {
    614                            if (m - sd < this.minimumRangeValueIncStdDev) {
    615                                this.minimumRangeValueIncStdDev = m - sd;
    616                                this.minimumRangeValueIncStdDevRow = r;
    617                                this.minimumRangeValueIncStdDevColumn = c;
    618                            }
    619                        }
    620                    }
    621                }
    622            }
    623        }
    624    }
    625
    626    /**
    627     * Returns the minimum y-value in the dataset.
    628     *
    629     * @param includeInterval  a flag that determines whether or not the
    630     *                         y-interval is taken into account.
    631     *
    632     * @return The minimum value.
    633     *
    634     * @see #getRangeUpperBound(boolean)
    635     */
    636    public double getRangeLowerBound(boolean includeInterval) {
    637        if (includeInterval && !Double.isNaN(this.minimumRangeValueIncStdDev)) {
    638            return this.minimumRangeValueIncStdDev;
    639        }
    640        else {
    641            return this.minimumRangeValue;
    642        }
    643    }
    644
    645    /**
    646     * Returns the maximum y-value in the dataset.
    647     *
    648     * @param includeInterval  a flag that determines whether or not the
    649     *                         y-interval is taken into account.
    650     *
    651     * @return The maximum value.
    652     *
    653     * @see #getRangeLowerBound(boolean)
    654     */
    655    public double getRangeUpperBound(boolean includeInterval) {
    656        if (includeInterval && !Double.isNaN(this.maximumRangeValueIncStdDev)) {
    657            return this.maximumRangeValueIncStdDev;
    658        }
    659        else {
    660            return this.maximumRangeValue;
    661        }
    662    }
    663
    664    /**
    665     * Returns the bounds of the values in this dataset's y-values.
    666     *
    667     * @param includeInterval  a flag that determines whether or not the
    668     *                         y-interval is taken into account.
    669     *
    670     * @return The range.
    671     */
    672    public Range getRangeBounds(boolean includeInterval) {
    673        double lower = getRangeLowerBound(includeInterval);
    674        double upper = getRangeUpperBound(includeInterval);
    675        if (Double.isNaN(lower) && Double.isNaN(upper)) {
    676            return null;
    677        }
    678        return new Range(lower, upper);
    679    }
    680
    681    /**
    682     * Tests this instance for equality with an arbitrary object.
    683     *
    684     * @param obj  the object (<code>null</code> permitted).
    685     *
    686     * @return A boolean.
    687     */
    688    public boolean equals(Object obj) {
    689        if (obj == this) {
    690            return true;
    691        }
    692        if (!(obj instanceof DefaultStatisticalCategoryDataset)) {
    693            return false;
    694        }
    695        DefaultStatisticalCategoryDataset that
    696                = (DefaultStatisticalCategoryDataset) obj;
    697        if (!this.data.equals(that.data)) {
    698            return false;
    699        }
    700        return true;
    701    }
    702
    703    /**
    704     * Returns a clone of this dataset.
    705     *
    706     * @return A clone of this dataset.
    707     *
    708     * @throws CloneNotSupportedException if cloning cannot be completed.
    709     */
    710    public Object clone() throws CloneNotSupportedException {
    711        DefaultStatisticalCategoryDataset clone
    712                = (DefaultStatisticalCategoryDataset) super.clone();
    713        clone.data = (KeyedObjects2D) this.data.clone();
    714        return clone;
    715    }
    716}