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 * TimePeriodValues.java 029 * --------------------- 030 * (C) Copyright 2003-2022, by David Gilbert. 031 * 032 * Original Author: David Gilbert; 033 * Contributor(s): -; 034 * 035 */ 036 037package org.jfree.data.time; 038 039import org.jfree.chart.internal.Args; 040import org.jfree.data.general.Series; 041import org.jfree.data.general.SeriesChangeEvent; 042import org.jfree.data.general.SeriesException; 043 044import java.io.Serializable; 045import java.util.ArrayList; 046import java.util.List; 047 048/** 049 * A structure containing zero, one or many {@link TimePeriodValue} instances. 050 * The time periods can overlap, and are maintained in the order that they are 051 * added to the collection. 052 * <p> 053 * This is similar to the {@link TimeSeries} class, except that the time 054 * periods can have irregular lengths. 055 */ 056public class TimePeriodValues<S extends Comparable<S>> extends Series<S> 057 implements Serializable { 058 059 /** For serialization. */ 060 static final long serialVersionUID = -2210593619794989709L; 061 062 /** The list of data pairs in the series. */ 063 private List<TimePeriodValue> data; 064 065 /** Index of the time period with the minimum start milliseconds. */ 066 private int minStartIndex = -1; 067 068 /** Index of the time period with the maximum start milliseconds. */ 069 private int maxStartIndex = -1; 070 071 /** Index of the time period with the minimum middle milliseconds. */ 072 private int minMiddleIndex = -1; 073 074 /** Index of the time period with the maximum middle milliseconds. */ 075 private int maxMiddleIndex = -1; 076 077 /** Index of the time period with the minimum end milliseconds. */ 078 private int minEndIndex = -1; 079 080 /** Index of the time period with the maximum end milliseconds. */ 081 private int maxEndIndex = -1; 082 083 /** 084 * Creates a new (empty) collection of time period values. 085 * 086 * @param name the name of the series ({@code null} not permitted). 087 */ 088 public TimePeriodValues(S name) { 089 super(name); 090 this.data = new ArrayList<>(); 091 } 092 093 /** 094 * Returns the number of items in the series. 095 * 096 * @return The item count. 097 */ 098 @Override 099 public int getItemCount() { 100 return this.data.size(); 101 } 102 103 /** 104 * Returns one data item for the series. 105 * 106 * @param index the item index (in the range {@code 0} to 107 * {@code getItemCount() -1}). 108 * 109 * @return One data item for the series. 110 */ 111 public TimePeriodValue getDataItem(int index) { 112 return this.data.get(index); 113 } 114 115 /** 116 * Returns the time period at the specified index. 117 * 118 * @param index the item index (in the range {@code 0} to 119 * {@code getItemCount() -1}). 120 * 121 * @return The time period at the specified index. 122 * 123 * @see #getDataItem(int) 124 */ 125 public TimePeriod getTimePeriod(int index) { 126 return getDataItem(index).getPeriod(); 127 } 128 129 /** 130 * Returns the value at the specified index. 131 * 132 * @param index the item index (in the range {@code 0} to 133 * {@code getItemCount() -1}). 134 * 135 * @return The value at the specified index (possibly {@code null}). 136 * 137 * @see #getDataItem(int) 138 */ 139 public Number getValue(int index) { 140 return getDataItem(index).getValue(); 141 } 142 143 /** 144 * Adds a data item to the series and sends a {@link SeriesChangeEvent} to 145 * all registered listeners. 146 * 147 * @param item the item ({@code null} not permitted). 148 */ 149 public void add(TimePeriodValue item) { 150 Args.nullNotPermitted(item, "item"); 151 this.data.add(item); 152 updateBounds(item.getPeriod(), this.data.size() - 1); 153 fireSeriesChanged(); 154 } 155 156 /** 157 * Update the index values for the maximum and minimum bounds. 158 * 159 * @param period the time period. 160 * @param index the index of the time period. 161 */ 162 private void updateBounds(TimePeriod period, int index) { 163 164 long start = period.getStart().getTime(); 165 long end = period.getEnd().getTime(); 166 long middle = start + ((end - start) / 2); 167 168 if (this.minStartIndex >= 0) { 169 long minStart = getDataItem(this.minStartIndex).getPeriod() 170 .getStart().getTime(); 171 if (start < minStart) { 172 this.minStartIndex = index; 173 } 174 } 175 else { 176 this.minStartIndex = index; 177 } 178 179 if (this.maxStartIndex >= 0) { 180 long maxStart = getDataItem(this.maxStartIndex).getPeriod() 181 .getStart().getTime(); 182 if (start > maxStart) { 183 this.maxStartIndex = index; 184 } 185 } 186 else { 187 this.maxStartIndex = index; 188 } 189 190 if (this.minMiddleIndex >= 0) { 191 long s = getDataItem(this.minMiddleIndex).getPeriod().getStart() 192 .getTime(); 193 long e = getDataItem(this.minMiddleIndex).getPeriod().getEnd() 194 .getTime(); 195 long minMiddle = s + (e - s) / 2; 196 if (middle < minMiddle) { 197 this.minMiddleIndex = index; 198 } 199 } 200 else { 201 this.minMiddleIndex = index; 202 } 203 204 if (this.maxMiddleIndex >= 0) { 205 long s = getDataItem(this.maxMiddleIndex).getPeriod().getStart() 206 .getTime(); 207 long e = getDataItem(this.maxMiddleIndex).getPeriod().getEnd() 208 .getTime(); 209 long maxMiddle = s + (e - s) / 2; 210 if (middle > maxMiddle) { 211 this.maxMiddleIndex = index; 212 } 213 } 214 else { 215 this.maxMiddleIndex = index; 216 } 217 218 if (this.minEndIndex >= 0) { 219 long minEnd = getDataItem(this.minEndIndex).getPeriod().getEnd() 220 .getTime(); 221 if (end < minEnd) { 222 this.minEndIndex = index; 223 } 224 } 225 else { 226 this.minEndIndex = index; 227 } 228 229 if (this.maxEndIndex >= 0) { 230 long maxEnd = getDataItem(this.maxEndIndex).getPeriod().getEnd() 231 .getTime(); 232 if (end > maxEnd) { 233 this.maxEndIndex = index; 234 } 235 } 236 else { 237 this.maxEndIndex = index; 238 } 239 240 } 241 242 /** 243 * Recalculates the bounds for the collection of items. 244 */ 245 private void recalculateBounds() { 246 this.minStartIndex = -1; 247 this.minMiddleIndex = -1; 248 this.minEndIndex = -1; 249 this.maxStartIndex = -1; 250 this.maxMiddleIndex = -1; 251 this.maxEndIndex = -1; 252 for (int i = 0; i < this.data.size(); i++) { 253 TimePeriodValue tpv = this.data.get(i); 254 updateBounds(tpv.getPeriod(), i); 255 } 256 } 257 258 /** 259 * Adds a new data item to the series and sends a {@link SeriesChangeEvent} 260 * to all registered listeners. 261 * 262 * @param period the time period ({@code null} not permitted). 263 * @param value the value. 264 * 265 * @see #add(TimePeriod, Number) 266 */ 267 public void add(TimePeriod period, double value) { 268 TimePeriodValue item = new TimePeriodValue(period, value); 269 add(item); 270 } 271 272 /** 273 * Adds a new data item to the series and sends a {@link SeriesChangeEvent} 274 * to all registered listeners. 275 * 276 * @param period the time period ({@code null} not permitted). 277 * @param value the value ({@code null} permitted). 278 */ 279 public void add(TimePeriod period, Number value) { 280 TimePeriodValue item = new TimePeriodValue(period, value); 281 add(item); 282 } 283 284 /** 285 * Updates (changes) the value of a data item and sends a 286 * {@link SeriesChangeEvent} to all registered listeners. 287 * 288 * @param index the index of the data item to update. 289 * @param value the new value ({@code null} not permitted). 290 */ 291 public void update(int index, Number value) { 292 TimePeriodValue item = getDataItem(index); 293 item.setValue(value); 294 fireSeriesChanged(); 295 } 296 297 /** 298 * Deletes data from start until end index (end inclusive) and sends a 299 * {@link SeriesChangeEvent} to all registered listeners. 300 * 301 * @param start the index of the first period to delete. 302 * @param end the index of the last period to delete. 303 */ 304 public void delete(int start, int end) { 305 for (int i = 0; i <= (end - start); i++) { 306 this.data.remove(start); 307 } 308 recalculateBounds(); 309 fireSeriesChanged(); 310 } 311 312 /** 313 * Tests the series for equality with another object. 314 * 315 * @param obj the object ({@code null} permitted). 316 * 317 * @return {@code true} or {@code false}. 318 */ 319 @Override 320 public boolean equals(Object obj) { 321 if (obj == this) { 322 return true; 323 } 324 if (!(obj instanceof TimePeriodValues)) { 325 return false; 326 } 327 if (!super.equals(obj)) { 328 return false; 329 } 330 TimePeriodValues that = (TimePeriodValues) obj; 331 int count = getItemCount(); 332 if (count != that.getItemCount()) { 333 return false; 334 } 335 for (int i = 0; i < count; i++) { 336 if (!getDataItem(i).equals(that.getDataItem(i))) { 337 return false; 338 } 339 } 340 return true; 341 } 342 343 /** 344 * Returns a hash code value for the object. 345 * 346 * @return The hashcode 347 */ 348 @Override 349 public int hashCode() { 350 int result; 351 result = this.data.hashCode(); 352 result = 29 * result + this.minStartIndex; 353 result = 29 * result + this.maxStartIndex; 354 result = 29 * result + this.minMiddleIndex; 355 result = 29 * result + this.maxMiddleIndex; 356 result = 29 * result + this.minEndIndex; 357 result = 29 * result + this.maxEndIndex; 358 return result; 359 } 360 361 /** 362 * Returns a clone of the collection. 363 * <P> 364 * Notes: 365 * <ul> 366 * <li>no need to clone the domain and range descriptions, since String 367 * object is immutable;</li> 368 * <li>we pass over to the more general method createCopy(start, end). 369 * </li> 370 * </ul> 371 * 372 * @return A clone of the time series. 373 * 374 * @throws CloneNotSupportedException if there is a cloning problem. 375 */ 376 @Override 377 public Object clone() throws CloneNotSupportedException { 378 Object clone = createCopy(0, getItemCount() - 1); 379 return clone; 380 } 381 382 /** 383 * Creates a new instance by copying a subset of the data in this 384 * collection. 385 * 386 * @param start the index of the first item to copy. 387 * @param end the index of the last item to copy. 388 * 389 * @return A copy of a subset of the items. 390 * 391 * @throws CloneNotSupportedException if there is a cloning problem. 392 */ 393 public TimePeriodValues createCopy(int start, int end) 394 throws CloneNotSupportedException { 395 396 TimePeriodValues copy = (TimePeriodValues) super.clone(); 397 398 copy.data = new ArrayList<>(); 399 if (this.data.size() > 0) { 400 for (int index = start; index <= end; index++) { 401 TimePeriodValue item = this.data.get(index); 402 TimePeriodValue clone = (TimePeriodValue) item.clone(); 403 try { 404 copy.add(clone); 405 } 406 catch (SeriesException e) { 407 System.err.println("Failed to add cloned item."); 408 } 409 } 410 } 411 return copy; 412 413 } 414 415 /** 416 * Returns the index of the time period with the minimum start milliseconds. 417 * 418 * @return The index. 419 */ 420 public int getMinStartIndex() { 421 return this.minStartIndex; 422 } 423 424 /** 425 * Returns the index of the time period with the maximum start milliseconds. 426 * 427 * @return The index. 428 */ 429 public int getMaxStartIndex() { 430 return this.maxStartIndex; 431 } 432 433 /** 434 * Returns the index of the time period with the minimum middle 435 * milliseconds. 436 * 437 * @return The index. 438 */ 439 public int getMinMiddleIndex() { 440 return this.minMiddleIndex; 441 } 442 443 /** 444 * Returns the index of the time period with the maximum middle 445 * milliseconds. 446 * 447 * @return The index. 448 */ 449 public int getMaxMiddleIndex() { 450 return this.maxMiddleIndex; 451 } 452 453 /** 454 * Returns the index of the time period with the minimum end milliseconds. 455 * 456 * @return The index. 457 */ 458 public int getMinEndIndex() { 459 return this.minEndIndex; 460 } 461 462 /** 463 * Returns the index of the time period with the maximum end milliseconds. 464 * 465 * @return The index. 466 */ 467 public int getMaxEndIndex() { 468 return this.maxEndIndex; 469 } 470 471}