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