    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2007, 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     * DefaultMultiValueCategoryDataset.java
    029     * -------------------------------------
    030     * (C) Copyright 2007, by David Forslund and Contributors.
    031     *
    032     * Original Author:  David Forslund;
    033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
    034     *
    035     * Changes
    036     * -------
    037     * 08-Oct-2007 : Version 1, see patch 1780779 (DG);
    038     * 06-Nov-2007 : Return EMPTY_LIST not null from getValues() (DG);
    039     */
    040
    041    package org.jfree.data.statistics;
    042
    043    import java.util.ArrayList;
    044    import java.util.Collections;
    045    import java.util.Iterator;
    046    import java.util.List;
    047
    048    import org.jfree.data.KeyedObjects2D;
    049    import org.jfree.data.Range;
    050    import org.jfree.data.RangeInfo;
    051    import org.jfree.data.general.AbstractDataset;
    052    import org.jfree.data.general.DatasetChangeEvent;
    053    import org.jfree.util.PublicCloneable;
    054
    055    /**
    056     * A category dataset that defines multiple values for each item.
    057     *
    058     * @since 1.0.7
    059     */
    060    public class DefaultMultiValueCategoryDataset extends AbstractDataset
    061            implements MultiValueCategoryDataset, RangeInfo, PublicCloneable {
    062
    063        /**
    064         * Storage for the data.
    065         */
    066        protected KeyedObjects2D data;
    067
    068        /**
    069         * The minimum range value.
    070         */
    071        private Number minimumRangeValue;
    072
    073        /**
    074         * The maximum range value.
    075         */
    076        private Number maximumRangeValue;
    077
    078        /**
    079         * The range of values.
    080         */
    081        private Range rangeBounds;
    082
    083        /**
    084         * Creates a new dataset.
    085         */
    086        public DefaultMultiValueCategoryDataset() {
    087            this.data = new KeyedObjects2D();
    088            this.minimumRangeValue = null;
    089            this.maximumRangeValue = null;
    090            this.rangeBounds = new Range(0.0, 0.0);
    091        }
    092
    093        /**
    094         * Adds a list of values to the dataset (<code>null</code> and Double.NaN
    095         * items are automatically removed) and sends a {@link DatasetChangeEvent}
    096         * to all registered listeners.
    097         *
    098         * @param values  a list of values (<code>null</code> not permitted).
    099         * @param rowKey  the row key (<code>null</code> not permitted).
    100         * @param columnKey  the column key (<code>null</code> not permitted).
    101         */
    102        public void add(List values, Comparable rowKey, Comparable columnKey) {
    103
    104            if (values == null) {
    105                throw new IllegalArgumentException("Null 'values' argument.");
    106            }
    107            if (rowKey == null) {
    108                throw new IllegalArgumentException("Null 'rowKey' argument.");
    109            }
    110            if (columnKey == null) {
    111                throw new IllegalArgumentException("Null 'columnKey' argument.");
    112            }
    113            List vlist = new ArrayList(values.size());
    114            Iterator iterator = values.listIterator();
    115            while (iterator.hasNext()) {
    116                Object obj = iterator.next();
    117                if (obj instanceof Number) {
    118                    Number n = (Number) obj;
    119                    double v = n.doubleValue();
    120                    if (!Double.isNaN(v)) {
    121                        vlist.add(n);
    122                    }
    123                }
    124            }
    125            Collections.sort(vlist);
    126            this.data.addObject(vlist, rowKey, columnKey);
    127
    128            if (vlist.size() > 0) {
    129                double maxval = Double.NEGATIVE_INFINITY;
    130                double minval = Double.POSITIVE_INFINITY;
    131                for (int i = 0; i < vlist.size(); i++) {
    132                    Number n = (Number) vlist.get(i);
    133                    double v = n.doubleValue();
    134                    minval = Math.min(minval, v);
    135                    maxval = Math.max(maxval, v);
    136                }
    137
    138                // update the cached range values...
    139                if (this.maximumRangeValue == null) {
    140                    this.maximumRangeValue = new Double(maxval);
    141                }
    142                else if (maxval > this.maximumRangeValue.doubleValue()) {
    143                    this.maximumRangeValue = new Double(maxval);
    144                }
    145
    146                if (this.minimumRangeValue == null) {
    147                    this.minimumRangeValue = new Double(minval);
    148                }
    149                else if (minval < this.minimumRangeValue.doubleValue()) {
    150                    this.minimumRangeValue = new Double(minval);
    151                }
    152                this.rangeBounds = new Range(this.minimumRangeValue.doubleValue(),
    153                        this.maximumRangeValue.doubleValue());
    154            }
    155
    156            fireDatasetChanged();
    157        }
    158
    159        /**
    160         * Returns a list (possibly empty) of the values for the specified item.
    161         * The returned list should be unmodifiable.
    162         *
    163         * @param row  the row index (zero-based).
    164         * @param column   the column index (zero-based).
    165         *
    166         * @return The list of values.
    167         */
    168        public List getValues(int row, int column) {
    169            List values = (List) this.data.getObject(row, column);
    170            if (values != null) {
    171                return Collections.unmodifiableList(values);
    172            }
    173            else {
    174                return Collections.EMPTY_LIST;
    175            }
    176        }
    177
    178        /**
    179         * Returns a list (possibly empty) of the values for the specified item.
    180         * The returned list should be unmodifiable.
    181         *
    182         * @param rowKey  the row key (<code>null</code> not permitted).
    183         * @param columnKey  the column key (<code>null</code> not permitted).
    184         *
    185         * @return The list of values.
    186         */
    187        public List getValues(Comparable rowKey, Comparable columnKey) {
    188            return Collections.unmodifiableList((List) this.data.getObject(rowKey,
    189                    columnKey));
    190        }
    191
    192        /**
    193         * Returns the average value for the specified item.
    194         *
    195         * @param row  the row key.
    196         * @param column  the column key.
    197         *
    198         * @return The average value.
    199         */
    200        public Number getValue(Comparable row, Comparable column) {
    201            List l = (List) this.data.getObject(row, column);
    202            double average = 0.0d;
    203            int count = 0;
    204            if (l != null && l.size() > 0) {
    205                for (int i = 0; i < l.size(); i++) {
    206                    Number n = (Number) l.get(i);
    207                    average += n.doubleValue();
    208                    count += 1;
    209                }
    210                if (count > 0) {
    211                    average = average / count;
    212                }
    213            }
    214            if (count == 0) {
    215                return null;
    216            }
    217            return new Double(average);
    218        }
    219
    220        /**
    221         * Returns the average value for the specified item.
    222         *
    223         * @param row  the row index.
    224         * @param column  the column index.
    225         *
    226         * @return The average value.
    227         */
    228        public Number getValue(int row, int column) {
    229            List l = (List) this.data.getObject(row, column);
    230            double average = 0.0d;
    231            int count = 0;
    232            if (l != null && l.size() > 0) {
    233                for (int i = 0; i < l.size(); i++) {
    234                    Number n = (Number) l.get(i);
    235                    average += n.doubleValue();
    236                    count += 1;
    237                }
    238                if (count > 0) {
    239                    average = average / count;
    240                }
    241            }
    242            if (count == 0) {
    243                return null;
    244            }
    245            return new Double(average);
    246        }
    247
    248        /**
    249         * Returns the column index for a given key.
    250         *
    251         * @param key  the column key.
    252         *
    253         * @return The column index.
    254         */
    255        public int getColumnIndex(Comparable key) {
    256            return this.data.getColumnIndex(key);
    257        }
    258
    259        /**
    260         * Returns a column key.
    261         *
    262         * @param column the column index (zero-based).
    263         *
    264         * @return The column key.
    265         */
    266        public Comparable getColumnKey(int column) {
    267            return this.data.getColumnKey(column);
    268        }
    269
    270        /**
    271         * Returns the column keys.
    272         *
    273         * @return The keys.
    274         */
    275        public List getColumnKeys() {
    276            return this.data.getColumnKeys();
    277        }
    278
    279        /**
    280         * Returns the row index for a given key.
    281         *
    282         * @param key the row key.
    283         *
    284         * @return The row index.
    285         */
    286        public int getRowIndex(Comparable key) {
    287            return this.data.getRowIndex(key);
    288        }
    289
    290        /**
    291         * Returns a row key.
    292         *
    293         * @param row the row index (zero-based).
    294         *
    295         * @return The row key.
    296         */
    297        public Comparable getRowKey(int row) {
    298            return this.data.getRowKey(row);
    299        }
    300
    301        /**
    302         * Returns the row keys.
    303         *
    304         * @return The keys.
    305         */
    306        public List getRowKeys() {
    307            return this.data.getRowKeys();
    308        }
    309
    310        /**
    311         * Returns the number of rows in the table.
    312         *
    313         * @return The row count.
    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        public int getColumnCount() {
    325            return this.data.getColumnCount();
    326        }
    327
    328        /**
    329         * Returns the minimum y-value in the dataset.
    330         *
    331         * @param includeInterval a flag that determines whether or not the
    332         *                        y-interval is taken into account.
    333         *
    334         * @return The minimum value.
    335         */
    336        public double getRangeLowerBound(boolean includeInterval) {
    337            double result = Double.NaN;
    338            if (this.minimumRangeValue != null) {
    339                result = this.minimumRangeValue.doubleValue();
    340            }
    341            return result;
    342        }
    343
    344        /**
    345         * Returns the maximum y-value in the dataset.
    346         *
    347         * @param includeInterval a flag that determines whether or not the
    348         *                        y-interval is taken into account.
    349         *
    350         * @return The maximum value.
    351         */
    352        public double getRangeUpperBound(boolean includeInterval) {
    353            double result = Double.NaN;
    354            if (this.maximumRangeValue != null) {
    355                result = this.maximumRangeValue.doubleValue();
    356            }
    357            return result;
    358        }
    359
    360        /**
    361         * Returns the range of the values in this dataset's range.
    362         *
    363         * @param includeInterval a flag that determines whether or not the
    364         *                        y-interval is taken into account.
    365         * @return The range.
    366         */
    367        public Range getRangeBounds(boolean includeInterval) {
    368            return this.rangeBounds;
    369        }
    370
    371        /**
    372         * Tests this dataset for equality with an arbitrary object.
    373         *
    374         * @param obj  the object (<code>null</code> permitted).
    375         *
    376         * @return A boolean.
    377         */
    378        public boolean equals(Object obj) {
    379            if (obj == this) {
    380                return true;
    381            }
    382            if (!(obj instanceof DefaultMultiValueCategoryDataset)) {
    383                return false;
    384            }
    385            DefaultMultiValueCategoryDataset that
    386                    = (DefaultMultiValueCategoryDataset) obj;
    387            return this.data.equals(that.data);
    388        }
    389
    390        /**
    391         * Returns a clone of this instance.
    392         *
    393         * @return A clone.
    394         *
    395         * @throws CloneNotSupportedException if the dataset cannot be cloned.
    396         */
    397        public Object clone() throws CloneNotSupportedException {
    398            DefaultMultiValueCategoryDataset clone
    399                    = (DefaultMultiValueCategoryDataset) super.clone();
    400            clone.data = (KeyedObjects2D) this.data.clone();
    401            return clone;
    402        }
    403    }