001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2008, 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, 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}