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 }