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