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