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