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