001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2005, 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 * TimePeriodValues.java 029 * --------------------- 030 * (C) Copyright 2003-2005, by Object Refinery Limited. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): -; 034 * 035 * $Id: TimePeriodValues.java,v 1.8.2.1 2005/10/25 21:35:24 mungady Exp $ 036 * 037 * Changes 038 * ------- 039 * 22-Apr-2003 : Version 1 (DG); 040 * 30-Jul-2003 : Added clone and equals methods while testing (DG); 041 * 11-Mar-2005 : Fixed bug in bounds recalculation - see bug report 042 * 1161329 (DG); 043 * 044 */ 045 046 package org.jfree.data.time; 047 048 import java.io.Serializable; 049 import java.util.ArrayList; 050 import java.util.List; 051 052 import org.jfree.data.general.Series; 053 import org.jfree.data.general.SeriesException; 054 055 /** 056 * A structure containing zero, one or many {@link TimePeriodValue} instances. 057 * The time periods can overlap, and are maintained in the order that they are 058 * added to the collection. 059 * <p> 060 * This is similar to the {@link TimeSeries} class, except that the time 061 * periods can have irregular lengths. 062 */ 063 public class TimePeriodValues extends Series implements Serializable { 064 065 /** For serialization. */ 066 static final long serialVersionUID = -2210593619794989709L; 067 068 /** Default value for the domain description. */ 069 protected static final String DEFAULT_DOMAIN_DESCRIPTION = "Time"; 070 071 /** Default value for the range description. */ 072 protected static final String DEFAULT_RANGE_DESCRIPTION = "Value"; 073 074 /** A description of the domain. */ 075 private String domain; 076 077 /** A description of the range. */ 078 private String range; 079 080 /** The list of data pairs in the series. */ 081 private List data; 082 083 /** Index of the time period with the minimum start milliseconds. */ 084 private int minStartIndex = -1; 085 086 /** Index of the time period with the maximum start milliseconds. */ 087 private int maxStartIndex = -1; 088 089 /** Index of the time period with the minimum middle milliseconds. */ 090 private int minMiddleIndex = -1; 091 092 /** Index of the time period with the maximum middle milliseconds. */ 093 private int maxMiddleIndex = -1; 094 095 /** Index of the time period with the minimum end milliseconds. */ 096 private int minEndIndex = -1; 097 098 /** Index of the time period with the maximum end milliseconds. */ 099 private int maxEndIndex = -1; 100 101 /** 102 * Creates a new (empty) collection of time period values. 103 * 104 * @param name the name of the series. 105 */ 106 public TimePeriodValues(String name) { 107 this(name, DEFAULT_DOMAIN_DESCRIPTION, DEFAULT_RANGE_DESCRIPTION); 108 } 109 110 /** 111 * Creates a new time series that contains no data. 112 * <P> 113 * Descriptions can be specified for the domain and range. One situation 114 * where this is helpful is when generating a chart for the time series - 115 * axis labels can be taken from the domain and range description. 116 * 117 * @param name the name of the series. 118 * @param domain the domain description. 119 * @param range the range description. 120 */ 121 public TimePeriodValues(String name, String domain, String range) { 122 super(name); 123 this.domain = domain; 124 this.range = range; 125 this.data = new ArrayList(); 126 } 127 128 /** 129 * Returns the domain description. 130 * 131 * @return The domain description. 132 */ 133 public String getDomainDescription() { 134 return this.domain; 135 } 136 137 /** 138 * Sets the domain description and fires a property change event. 139 * 140 * @param description the new description. 141 */ 142 public void setDomainDescription(String description) { 143 String old = this.domain; 144 this.domain = description; 145 firePropertyChange("Domain", old, description); 146 } 147 148 /** 149 * Returns the range description. 150 * 151 * @return The range description. 152 */ 153 public String getRangeDescription() { 154 return this.range; 155 } 156 157 /** 158 * Sets the range description and fires a property change event. 159 * 160 * @param description the new description. 161 */ 162 public void setRangeDescription(String description) { 163 String old = this.range; 164 this.range = description; 165 firePropertyChange("Range", old, description); 166 } 167 168 /** 169 * Returns the number of items in the series. 170 * 171 * @return The item count. 172 */ 173 public int getItemCount() { 174 return this.data.size(); 175 } 176 177 /** 178 * Returns one data item for the series. 179 * 180 * @param index the item index (zero-based). 181 * 182 * @return One data item for the series. 183 */ 184 public TimePeriodValue getDataItem(int index) { 185 return (TimePeriodValue) this.data.get(index); 186 } 187 188 /** 189 * Returns the time period at the specified index. 190 * 191 * @param index the index of the data pair. 192 * 193 * @return The time period at the specified index. 194 */ 195 public TimePeriod getTimePeriod(int index) { 196 return getDataItem(index).getPeriod(); 197 } 198 199 /** 200 * Returns the value at the specified index. 201 * 202 * @param index index of a value. 203 * 204 * @return The value at the specified index. 205 */ 206 public Number getValue(int index) { 207 return getDataItem(index).getValue(); 208 } 209 210 /** 211 * Adds a data item to the series. 212 * 213 * @param item the (timeperiod, value) pair. 214 */ 215 public void add(TimePeriodValue item) { 216 217 // check arguments... 218 if (item == null) { 219 throw new IllegalArgumentException("Null item not allowed."); 220 } 221 222 // make the change 223 this.data.add(item); 224 updateBounds(item.getPeriod(), this.data.size() - 1); 225 226 } 227 228 /** 229 * Update the index values for the maximum and minimum bounds. 230 * 231 * @param period the time period. 232 * @param index the index of the time period. 233 */ 234 private void updateBounds(TimePeriod period, int index) { 235 236 long start = period.getStart().getTime(); 237 long end = period.getEnd().getTime(); 238 long middle = start + ((end - start) / 2); 239 240 if (this.minStartIndex >= 0) { 241 long minStart = getDataItem(this.minStartIndex).getPeriod() 242 .getStart().getTime(); 243 if (start < minStart) { 244 this.minStartIndex = index; 245 } 246 } 247 else { 248 this.minStartIndex = index; 249 } 250 251 if (this.maxStartIndex >= 0) { 252 long maxStart = getDataItem(this.maxStartIndex).getPeriod() 253 .getStart().getTime(); 254 if (start > maxStart) { 255 this.maxStartIndex = index; 256 } 257 } 258 else { 259 this.maxStartIndex = index; 260 } 261 262 if (this.minMiddleIndex >= 0) { 263 long s = getDataItem(this.minMiddleIndex).getPeriod().getStart() 264 .getTime(); 265 long e = getDataItem(this.minMiddleIndex).getPeriod().getEnd() 266 .getTime(); 267 long minMiddle = s + (e - s) / 2; 268 if (middle < minMiddle) { 269 this.minMiddleIndex = index; 270 } 271 } 272 else { 273 this.minMiddleIndex = index; 274 } 275 276 if (this.maxMiddleIndex >= 0) { 277 long s = getDataItem(this.minMiddleIndex).getPeriod().getStart() 278 .getTime(); 279 long e = getDataItem(this.minMiddleIndex).getPeriod().getEnd() 280 .getTime(); 281 long maxMiddle = s + (e - s) / 2; 282 if (middle > maxMiddle) { 283 this.maxMiddleIndex = index; 284 } 285 } 286 else { 287 this.maxMiddleIndex = index; 288 } 289 290 if (this.minEndIndex >= 0) { 291 long minEnd = getDataItem(this.minEndIndex).getPeriod().getEnd() 292 .getTime(); 293 if (end < minEnd) { 294 this.minEndIndex = index; 295 } 296 } 297 else { 298 this.minEndIndex = index; 299 } 300 301 if (this.maxEndIndex >= 0) { 302 long maxEnd = getDataItem(this.maxEndIndex).getPeriod().getEnd() 303 .getTime(); 304 if (end > maxEnd) { 305 this.maxEndIndex = index; 306 } 307 } 308 else { 309 this.maxEndIndex = index; 310 } 311 312 } 313 314 /** 315 * Recalculates the bounds for the collection of items. 316 */ 317 private void recalculateBounds() { 318 this.minStartIndex = -1; 319 this.minMiddleIndex = -1; 320 this.minEndIndex = -1; 321 this.maxStartIndex = -1; 322 this.maxMiddleIndex = -1; 323 this.maxEndIndex = -1; 324 for (int i = 0; i < this.data.size(); i++) { 325 TimePeriodValue tpv = (TimePeriodValue) this.data.get(i); 326 updateBounds(tpv.getPeriod(), i); 327 } 328 } 329 330 /** 331 * Adds a new data item to the series. 332 * 333 * @param period the time period. 334 * @param value the value. 335 */ 336 public void add(TimePeriod period, double value) { 337 TimePeriodValue item = new TimePeriodValue(period, value); 338 add(item); 339 } 340 341 /** 342 * Adds a new data item to the series. 343 * 344 * @param period the time period. 345 * @param value the value. 346 */ 347 public void add(TimePeriod period, Number value) { 348 TimePeriodValue item = new TimePeriodValue(period, value); 349 add(item); 350 } 351 352 /** 353 * Updates (changes) the value of a data item. 354 * 355 * @param index the index of the data item to update. 356 * @param value the new value. 357 */ 358 public void update(int index, Number value) { 359 TimePeriodValue item = getDataItem(index); 360 item.setValue(value); 361 fireSeriesChanged(); 362 } 363 364 /** 365 * Deletes data from start until end index (end inclusive). 366 * 367 * @param start the index of the first period to delete. 368 * @param end the index of the last period to delete. 369 */ 370 public void delete(int start, int end) { 371 for (int i = 0; i <= (end - start); i++) { 372 this.data.remove(start); 373 } 374 recalculateBounds(); 375 fireSeriesChanged(); 376 } 377 378 /** 379 * Tests the series for equality with another object. 380 * 381 * @param obj the object. 382 * 383 * @return <code>true</code> or <code>false</code>. 384 */ 385 public boolean equals(Object obj) { 386 387 if (obj == this) { 388 return true; 389 } 390 391 if (!(obj instanceof TimePeriodValues)) { 392 return false; 393 } 394 395 if (!super.equals(obj)) { 396 return false; 397 } 398 399 TimePeriodValues that = (TimePeriodValues) obj; 400 if (!getDomainDescription().equals(that.getDomainDescription())) { 401 return false; 402 } 403 if (!getRangeDescription().equals(that.getRangeDescription())) { 404 return false; 405 } 406 407 int count = getItemCount(); 408 if (count != that.getItemCount()) { 409 return false; 410 } 411 for (int i = 0; i < count; i++) { 412 if (!getDataItem(i).equals(that.getDataItem(i))) { 413 return false; 414 } 415 } 416 return true; 417 418 } 419 420 /** 421 * Returns a hash code value for the object. 422 * 423 * @return The hashcode 424 */ 425 public int hashCode() { 426 int result; 427 result = (this.domain != null ? this.domain.hashCode() : 0); 428 result = 29 * result + (this.range != null ? this.range.hashCode() : 0); 429 result = 29 * result + this.data.hashCode(); 430 result = 29 * result + this.minStartIndex; 431 result = 29 * result + this.maxStartIndex; 432 result = 29 * result + this.minMiddleIndex; 433 result = 29 * result + this.maxMiddleIndex; 434 result = 29 * result + this.minEndIndex; 435 result = 29 * result + this.maxEndIndex; 436 return result; 437 } 438 439 /** 440 * Returns a clone of the collection. 441 * <P> 442 * Notes: 443 * <ul> 444 * <li>no need to clone the domain and range descriptions, since String 445 * object is immutable;</li> 446 * <li>we pass over to the more general method createCopy(start, end). 447 * </li> 448 * </ul> 449 * 450 * @return A clone of the time series. 451 * 452 * @throws CloneNotSupportedException if there is a cloning problem. 453 */ 454 public Object clone() throws CloneNotSupportedException { 455 Object clone = createCopy(0, getItemCount() - 1); 456 return clone; 457 } 458 459 /** 460 * Creates a new instance by copying a subset of the data in this 461 * collection. 462 * 463 * @param start the index of the first item to copy. 464 * @param end the index of the last item to copy. 465 * 466 * @return A copy of a subset of the items. 467 * 468 * @throws CloneNotSupportedException if there is a cloning problem. 469 */ 470 public TimePeriodValues createCopy(int start, int end) 471 throws CloneNotSupportedException { 472 473 TimePeriodValues copy = (TimePeriodValues) super.clone(); 474 475 copy.data = new ArrayList(); 476 if (this.data.size() > 0) { 477 for (int index = start; index <= end; index++) { 478 TimePeriodValue item = (TimePeriodValue) this.data.get(index); 479 TimePeriodValue clone = (TimePeriodValue) item.clone(); 480 try { 481 copy.add(clone); 482 } 483 catch (SeriesException e) { 484 System.err.println("Failed to add cloned item."); 485 } 486 } 487 } 488 return copy; 489 490 } 491 492 /** 493 * Returns the index of the time period with the minimum start milliseconds. 494 * 495 * @return The index. 496 */ 497 public int getMinStartIndex() { 498 return this.minStartIndex; 499 } 500 501 /** 502 * Returns the index of the time period with the maximum start milliseconds. 503 * 504 * @return The index. 505 */ 506 public int getMaxStartIndex() { 507 return this.maxStartIndex; 508 } 509 510 /** 511 * Returns the index of the time period with the minimum middle 512 * milliseconds. 513 * 514 * @return The index. 515 */ 516 public int getMinMiddleIndex() { 517 return this.minMiddleIndex; 518 } 519 520 /** 521 * Returns the index of the time period with the maximum middle 522 * milliseconds. 523 * 524 * @return The index. 525 */ 526 public int getMaxMiddleIndex() { 527 return this.maxMiddleIndex; 528 } 529 530 /** 531 * Returns the index of the time period with the minimum end milliseconds. 532 * 533 * @return The index. 534 */ 535 public int getMinEndIndex() { 536 return this.minEndIndex; 537 } 538 539 /** 540 * Returns the index of the time period with the maximum end milliseconds. 541 * 542 * @return The index. 543 */ 544 public int getMaxEndIndex() { 545 return this.maxEndIndex; 546 } 547 548 }