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 }