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    
041    package org.jfree.data.statistics;
042    
043    
044    import java.util.ArrayList;
045    import java.util.Collections;
046    import java.util.Iterator;
047    import java.util.List;
048    
049    import org.jfree.data.KeyedObjects2D;
050    import org.jfree.data.Range;
051    import org.jfree.data.RangeInfo;
052    import org.jfree.data.general.AbstractDataset;
053    import org.jfree.data.general.DatasetChangeEvent;
054    import org.jfree.util.PublicCloneable;
055    
056    /**
057     * A category dataset that defines multiple values for each item.
058     * 
059     * @since 1.0.7
060     */
061    public 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    }