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