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