Frames | No Frames |
1: /* =========================================================== 2: * JFreeChart : a free chart library for the Java(tm) platform 3: * =========================================================== 4: * 5: * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors. 6: * 7: * Project Info: http://www.jfree.org/jfreechart/index.html 8: * 9: * This library is free software; you can redistribute it and/or modify it 10: * under the terms of the GNU Lesser General Public License as published by 11: * the Free Software Foundation; either version 2.1 of the License, or 12: * (at your option) any later version. 13: * 14: * This library is distributed in the hope that it will be useful, but 15: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 16: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 17: * License for more details. 18: * 19: * You should have received a copy of the GNU Lesser General Public 20: * License along with this library; if not, write to the Free Software 21: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 22: * USA. 23: * 24: * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 25: * in the United States and other countries.] 26: * 27: * ----------------------- 28: * TimeTableXYDataset.java 29: * ----------------------- 30: * (C) Copyright 2004-2008, by Andreas Schroeder and Contributors. 31: * 32: * Original Author: Andreas Schroeder; 33: * Contributor(s): David Gilbert (for Object Refinery Limited); 34: * Rob Eden; 35: * 36: * Changes 37: * ------- 38: * 01-Apr-2004 : Version 1 (AS); 39: * 05-May-2004 : Now implements AbstractIntervalXYDataset (DG); 40: * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 41: * getYValue() (DG); 42: * 15-Sep-2004 : Added getXPosition(), setXPosition(), equals() and 43: * clone() (DG); 44: * 17-Nov-2004 : Updated methods for changes in DomainInfo interface (DG); 45: * 25-Nov-2004 : Added getTimePeriod(int) method (DG); 46: * 11-Jan-2005 : Removed deprecated code in preparation for the 1.0.0 47: * release (DG); 48: * 27-Jan-2005 : Modified to use TimePeriod rather than RegularTimePeriod (DG); 49: * 02-Feb-2007 : Removed author tags all over JFreeChart sources (DG); 50: * 25-Jul-2007 : Added clear() method by Rob Eden, see patch 1752205 (DG); 51: * 04-Jun-2008 : Updated Javadocs (DG); 52: * 53: */ 54: 55: package org.jfree.data.time; 56: 57: import java.util.Calendar; 58: import java.util.List; 59: import java.util.Locale; 60: import java.util.TimeZone; 61: 62: import org.jfree.data.DefaultKeyedValues2D; 63: import org.jfree.data.DomainInfo; 64: import org.jfree.data.Range; 65: import org.jfree.data.general.DatasetChangeEvent; 66: import org.jfree.data.xy.AbstractIntervalXYDataset; 67: import org.jfree.data.xy.IntervalXYDataset; 68: import org.jfree.data.xy.TableXYDataset; 69: import org.jfree.util.PublicCloneable; 70: 71: /** 72: * A dataset for regular time periods that implements the 73: * {@link TableXYDataset} interface. Note that the {@link TableXYDataset} 74: * interface requires all series to share the same set of x-values. When 75: * adding a new item <code>(x, y)</code> to one series, all other series 76: * automatically get a new item <code>(x, null)</code> unless a non-null item 77: * has already been specified. 78: * 79: * @see org.jfree.data.xy.TableXYDataset 80: */ 81: public class TimeTableXYDataset extends AbstractIntervalXYDataset 82: implements Cloneable, PublicCloneable, IntervalXYDataset, DomainInfo, 83: TableXYDataset { 84: 85: /** 86: * The data structure to store the values. Each column represents 87: * a series (elsewhere in JFreeChart rows are typically used for series, 88: * but it doesn't matter that much since this data structure is private 89: * and symmetrical anyway), each row contains values for the same 90: * {@link RegularTimePeriod} (the rows are sorted into ascending order). 91: */ 92: private DefaultKeyedValues2D values; 93: 94: /** 95: * A flag that indicates that the domain is 'points in time'. If this flag 96: * is true, only the x-value (and not the x-interval) is used to determine 97: * the range of values in the domain. 98: */ 99: private boolean domainIsPointsInTime; 100: 101: /** 102: * The point within each time period that is used for the X value when this 103: * collection is used as an {@link org.jfree.data.xy.XYDataset}. This can 104: * be the start, middle or end of the time period. 105: */ 106: private TimePeriodAnchor xPosition; 107: 108: /** A working calendar (to recycle) */ 109: private Calendar workingCalendar; 110: 111: /** 112: * Creates a new dataset. 113: */ 114: public TimeTableXYDataset() { 115: // defer argument checking 116: this(TimeZone.getDefault(), Locale.getDefault()); 117: } 118: 119: /** 120: * Creates a new dataset with the given time zone. 121: * 122: * @param zone the time zone to use (<code>null</code> not permitted). 123: */ 124: public TimeTableXYDataset(TimeZone zone) { 125: // defer argument checking 126: this(zone, Locale.getDefault()); 127: } 128: 129: /** 130: * Creates a new dataset with the given time zone and locale. 131: * 132: * @param zone the time zone to use (<code>null</code> not permitted). 133: * @param locale the locale to use (<code>null</code> not permitted). 134: */ 135: public TimeTableXYDataset(TimeZone zone, Locale locale) { 136: if (zone == null) { 137: throw new IllegalArgumentException("Null 'zone' argument."); 138: } 139: if (locale == null) { 140: throw new IllegalArgumentException("Null 'locale' argument."); 141: } 142: this.values = new DefaultKeyedValues2D(true); 143: this.workingCalendar = Calendar.getInstance(zone, locale); 144: this.xPosition = TimePeriodAnchor.START; 145: } 146: 147: /** 148: * Returns a flag that controls whether the domain is treated as 'points in 149: * time'. 150: * <P> 151: * This flag is used when determining the max and min values for the domain. 152: * If true, then only the x-values are considered for the max and min 153: * values. If false, then the start and end x-values will also be taken 154: * into consideration. 155: * 156: * @return The flag. 157: * 158: * @see #setDomainIsPointsInTime(boolean) 159: */ 160: public boolean getDomainIsPointsInTime() { 161: return this.domainIsPointsInTime; 162: } 163: 164: /** 165: * Sets a flag that controls whether the domain is treated as 'points in 166: * time', or time periods. A {@link DatasetChangeEvent} is sent to all 167: * registered listeners. 168: * 169: * @param flag the new value of the flag. 170: * 171: * @see #getDomainIsPointsInTime() 172: */ 173: public void setDomainIsPointsInTime(boolean flag) { 174: this.domainIsPointsInTime = flag; 175: notifyListeners(new DatasetChangeEvent(this, this)); 176: } 177: 178: /** 179: * Returns the position within each time period that is used for the X 180: * value. 181: * 182: * @return The anchor position (never <code>null</code>). 183: * 184: * @see #setXPosition(TimePeriodAnchor) 185: */ 186: public TimePeriodAnchor getXPosition() { 187: return this.xPosition; 188: } 189: 190: /** 191: * Sets the position within each time period that is used for the X values, 192: * then sends a {@link DatasetChangeEvent} to all registered listeners. 193: * 194: * @param anchor the anchor position (<code>null</code> not permitted). 195: * 196: * @see #getXPosition() 197: */ 198: public void setXPosition(TimePeriodAnchor anchor) { 199: if (anchor == null) { 200: throw new IllegalArgumentException("Null 'anchor' argument."); 201: } 202: this.xPosition = anchor; 203: notifyListeners(new DatasetChangeEvent(this, this)); 204: } 205: 206: /** 207: * Adds a new data item to the dataset and sends a 208: * {@link DatasetChangeEvent} to all registered listeners. 209: * 210: * @param period the time period. 211: * @param y the value for this period. 212: * @param seriesName the name of the series to add the value. 213: * 214: * @see #remove(TimePeriod, String) 215: */ 216: public void add(TimePeriod period, double y, String seriesName) { 217: add(period, new Double(y), seriesName, true); 218: } 219: 220: /** 221: * Adds a new data item to the dataset and, if requested, sends a 222: * {@link DatasetChangeEvent} to all registered listeners. 223: * 224: * @param period the time period (<code>null</code> not permitted). 225: * @param y the value for this period (<code>null</code> permitted). 226: * @param seriesName the name of the series to add the value 227: * (<code>null</code> not permitted). 228: * @param notify whether dataset listener are notified or not. 229: * 230: * @see #remove(TimePeriod, String, boolean) 231: */ 232: public void add(TimePeriod period, Number y, String seriesName, 233: boolean notify) { 234: this.values.addValue(y, period, seriesName); 235: if (notify) { 236: fireDatasetChanged(); 237: } 238: } 239: 240: /** 241: * Removes an existing data item from the dataset. 242: * 243: * @param period the (existing!) time period of the value to remove 244: * (<code>null</code> not permitted). 245: * @param seriesName the (existing!) series name to remove the value 246: * (<code>null</code> not permitted). 247: * 248: * @see #add(TimePeriod, double, String) 249: */ 250: public void remove(TimePeriod period, String seriesName) { 251: remove(period, seriesName, true); 252: } 253: 254: /** 255: * Removes an existing data item from the dataset and, if requested, 256: * sends a {@link DatasetChangeEvent} to all registered listeners. 257: * 258: * @param period the (existing!) time period of the value to remove 259: * (<code>null</code> not permitted). 260: * @param seriesName the (existing!) series name to remove the value 261: * (<code>null</code> not permitted). 262: * @param notify whether dataset listener are notified or not. 263: * 264: * @see #add(TimePeriod, double, String) 265: */ 266: public void remove(TimePeriod period, String seriesName, boolean notify) { 267: this.values.removeValue(period, seriesName); 268: if (notify) { 269: fireDatasetChanged(); 270: } 271: } 272: 273: /** 274: * Removes all data items from the dataset and sends a 275: * {@link DatasetChangeEvent} to all registered listeners. 276: * 277: * @since 1.0.7 278: */ 279: public void clear() { 280: if (this.values.getRowCount() > 0) { 281: this.values.clear(); 282: fireDatasetChanged(); 283: } 284: } 285: 286: /** 287: * Returns the time period for the specified item. Bear in mind that all 288: * series share the same set of time periods. 289: * 290: * @param item the item index (0 <= i <= {@link #getItemCount()}). 291: * 292: * @return The time period. 293: */ 294: public TimePeriod getTimePeriod(int item) { 295: return (TimePeriod) this.values.getRowKey(item); 296: } 297: 298: /** 299: * Returns the number of items in ALL series. 300: * 301: * @return The item count. 302: */ 303: public int getItemCount() { 304: return this.values.getRowCount(); 305: } 306: 307: /** 308: * Returns the number of items in a series. This is the same value 309: * that is returned by {@link #getItemCount()} since all series 310: * share the same x-values (time periods). 311: * 312: * @param series the series (zero-based index, ignored). 313: * 314: * @return The number of items within the series. 315: */ 316: public int getItemCount(int series) { 317: return getItemCount(); 318: } 319: 320: /** 321: * Returns the number of series in the dataset. 322: * 323: * @return The series count. 324: */ 325: public int getSeriesCount() { 326: return this.values.getColumnCount(); 327: } 328: 329: /** 330: * Returns the key for a series. 331: * 332: * @param series the series (zero-based index). 333: * 334: * @return The key for the series. 335: */ 336: public Comparable getSeriesKey(int series) { 337: return this.values.getColumnKey(series); 338: } 339: 340: /** 341: * Returns the x-value for an item within a series. The x-values may or 342: * may not be returned in ascending order, that is up to the class 343: * implementing the interface. 344: * 345: * @param series the series (zero-based index). 346: * @param item the item (zero-based index). 347: * 348: * @return The x-value. 349: */ 350: public Number getX(int series, int item) { 351: return new Double(getXValue(series, item)); 352: } 353: 354: /** 355: * Returns the x-value (as a double primitive) for an item within a series. 356: * 357: * @param series the series index (zero-based). 358: * @param item the item index (zero-based). 359: * 360: * @return The value. 361: */ 362: public double getXValue(int series, int item) { 363: TimePeriod period = (TimePeriod) this.values.getRowKey(item); 364: return getXValue(period); 365: } 366: 367: /** 368: * Returns the starting X value for the specified series and item. 369: * 370: * @param series the series (zero-based index). 371: * @param item the item within a series (zero-based index). 372: * 373: * @return The starting X value for the specified series and item. 374: * 375: * @see #getStartXValue(int, int) 376: */ 377: public Number getStartX(int series, int item) { 378: return new Double(getStartXValue(series, item)); 379: } 380: 381: /** 382: * Returns the start x-value (as a double primitive) for an item within 383: * a series. 384: * 385: * @param series the series index (zero-based). 386: * @param item the item index (zero-based). 387: * 388: * @return The value. 389: */ 390: public double getStartXValue(int series, int item) { 391: TimePeriod period = (TimePeriod) this.values.getRowKey(item); 392: return period.getStart().getTime(); 393: } 394: 395: /** 396: * Returns the ending X value for the specified series and item. 397: * 398: * @param series the series (zero-based index). 399: * @param item the item within a series (zero-based index). 400: * 401: * @return The ending X value for the specified series and item. 402: * 403: * @see #getEndXValue(int, int) 404: */ 405: public Number getEndX(int series, int item) { 406: return new Double(getEndXValue(series, item)); 407: } 408: 409: /** 410: * Returns the end x-value (as a double primitive) for an item within 411: * a series. 412: * 413: * @param series the series index (zero-based). 414: * @param item the item index (zero-based). 415: * 416: * @return The value. 417: */ 418: public double getEndXValue(int series, int item) { 419: TimePeriod period = (TimePeriod) this.values.getRowKey(item); 420: return period.getEnd().getTime(); 421: } 422: 423: /** 424: * Returns the y-value for an item within a series. 425: * 426: * @param series the series (zero-based index). 427: * @param item the item (zero-based index). 428: * 429: * @return The y-value (possibly <code>null</code>). 430: */ 431: public Number getY(int series, int item) { 432: return this.values.getValue(item, series); 433: } 434: 435: /** 436: * Returns the starting Y value for the specified series and item. 437: * 438: * @param series the series (zero-based index). 439: * @param item the item within a series (zero-based index). 440: * 441: * @return The starting Y value for the specified series and item. 442: */ 443: public Number getStartY(int series, int item) { 444: return getY(series, item); 445: } 446: 447: /** 448: * Returns the ending Y value for the specified series and item. 449: * 450: * @param series the series (zero-based index). 451: * @param item the item within a series (zero-based index). 452: * 453: * @return The ending Y value for the specified series and item. 454: */ 455: public Number getEndY(int series, int item) { 456: return getY(series, item); 457: } 458: 459: /** 460: * Returns the x-value for a time period. 461: * 462: * @param period the time period. 463: * 464: * @return The x-value. 465: */ 466: private long getXValue(TimePeriod period) { 467: long result = 0L; 468: if (this.xPosition == TimePeriodAnchor.START) { 469: result = period.getStart().getTime(); 470: } 471: else if (this.xPosition == TimePeriodAnchor.MIDDLE) { 472: long t0 = period.getStart().getTime(); 473: long t1 = period.getEnd().getTime(); 474: result = t0 + (t1 - t0) / 2L; 475: } 476: else if (this.xPosition == TimePeriodAnchor.END) { 477: result = period.getEnd().getTime(); 478: } 479: return result; 480: } 481: 482: /** 483: * Returns the minimum x-value in the dataset. 484: * 485: * @param includeInterval a flag that determines whether or not the 486: * x-interval is taken into account. 487: * 488: * @return The minimum value. 489: */ 490: public double getDomainLowerBound(boolean includeInterval) { 491: double result = Double.NaN; 492: Range r = getDomainBounds(includeInterval); 493: if (r != null) { 494: result = r.getLowerBound(); 495: } 496: return result; 497: } 498: 499: /** 500: * Returns the maximum x-value in the dataset. 501: * 502: * @param includeInterval a flag that determines whether or not the 503: * x-interval is taken into account. 504: * 505: * @return The maximum value. 506: */ 507: public double getDomainUpperBound(boolean includeInterval) { 508: double result = Double.NaN; 509: Range r = getDomainBounds(includeInterval); 510: if (r != null) { 511: result = r.getUpperBound(); 512: } 513: return result; 514: } 515: 516: /** 517: * Returns the range of the values in this dataset's domain. 518: * 519: * @param includeInterval a flag that controls whether or not the 520: * x-intervals are taken into account. 521: * 522: * @return The range. 523: */ 524: public Range getDomainBounds(boolean includeInterval) { 525: List keys = this.values.getRowKeys(); 526: if (keys.isEmpty()) { 527: return null; 528: } 529: 530: TimePeriod first = (TimePeriod) keys.get(0); 531: TimePeriod last = (TimePeriod) keys.get(keys.size() - 1); 532: 533: if (!includeInterval || this.domainIsPointsInTime) { 534: return new Range(getXValue(first), getXValue(last)); 535: } 536: else { 537: return new Range(first.getStart().getTime(), 538: last.getEnd().getTime()); 539: } 540: } 541: 542: /** 543: * Tests this dataset for equality with an arbitrary object. 544: * 545: * @param obj the object (<code>null</code> permitted). 546: * 547: * @return A boolean. 548: */ 549: public boolean equals(Object obj) { 550: if (obj == this) { 551: return true; 552: } 553: if (!(obj instanceof TimeTableXYDataset)) { 554: return false; 555: } 556: TimeTableXYDataset that = (TimeTableXYDataset) obj; 557: if (this.domainIsPointsInTime != that.domainIsPointsInTime) { 558: return false; 559: } 560: if (this.xPosition != that.xPosition) { 561: return false; 562: } 563: if (!this.workingCalendar.getTimeZone().equals( 564: that.workingCalendar.getTimeZone()) 565: ) { 566: return false; 567: } 568: if (!this.values.equals(that.values)) { 569: return false; 570: } 571: return true; 572: } 573: 574: /** 575: * Returns a clone of this dataset. 576: * 577: * @return A clone. 578: * 579: * @throws CloneNotSupportedException if the dataset cannot be cloned. 580: */ 581: public Object clone() throws CloneNotSupportedException { 582: TimeTableXYDataset clone = (TimeTableXYDataset) super.clone(); 583: clone.values = (DefaultKeyedValues2D) this.values.clone(); 584: clone.workingCalendar = (Calendar) this.workingCalendar.clone(); 585: return clone; 586: } 587: 588: }