    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 * DefaultMultiValueCategoryDataset.java
    029 * -------------------------------------
    030 * (C) Copyright 2007, 2008, 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
    041package org.jfree.data.statistics;
    042
    043import java.util.ArrayList;
    044import java.util.Collections;
    045import java.util.Iterator;
    046import java.util.List;
    047
    048import org.jfree.data.KeyedObjects2D;
    049import org.jfree.data.Range;
    050import org.jfree.data.RangeInfo;
    051import org.jfree.data.general.AbstractDataset;
    052import org.jfree.data.general.DatasetChangeEvent;
    053import org.jfree.util.PublicCloneable;
    054
    055/**
    056 * A category dataset that defines multiple values for each item.
    057 *
    058 * @since 1.0.7
    059 */
    060public 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}