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