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