001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-present, 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 * XYTaskDataset.java 029 * ------------------ 030 * (C) Copyright 2008-present, by David Gilbert. 031 * 032 * Original Author: David Gilbert; 033 * Contributor(s): Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier); 034 * 035 */ 036 037package org.jfree.data.gantt; 038 039import java.util.Date; 040import java.util.Objects; 041 042import org.jfree.chart.axis.SymbolAxis; 043import org.jfree.chart.renderer.xy.XYBarRenderer; 044import org.jfree.chart.util.Args; 045import org.jfree.data.general.DatasetChangeEvent; 046import org.jfree.data.general.DatasetChangeListener; 047import org.jfree.data.time.TimePeriod; 048import org.jfree.data.xy.AbstractXYDataset; 049import org.jfree.data.xy.IntervalXYDataset; 050 051/** 052 * A dataset implementation that wraps a {@link TaskSeriesCollection} and 053 * presents it as an {@link IntervalXYDataset}, allowing a set of tasks to 054 * be displayed using an {@link XYBarRenderer} (and usually a 055 * {@link SymbolAxis}). This is a very specialised dataset implementation 056 * ---before using it, you should take some time to understand the use-cases 057 * that it is designed for. 058 */ 059public class XYTaskDataset extends AbstractXYDataset 060 implements IntervalXYDataset, DatasetChangeListener { 061 062 /** The underlying tasks. */ 063 private TaskSeriesCollection underlying; 064 065 /** The series interval width (typically 0.0 < w <= 1.0). */ 066 private double seriesWidth; 067 068 /** A flag that controls whether or not the data values are transposed. */ 069 private boolean transposed; 070 071 /** 072 * Creates a new dataset based on the supplied collection of tasks. 073 * 074 * @param tasks the underlying dataset ({@code null} not permitted). 075 */ 076 public XYTaskDataset(TaskSeriesCollection tasks) { 077 Args.nullNotPermitted(tasks, "tasks"); 078 this.underlying = tasks; 079 this.seriesWidth = 0.8; 080 this.underlying.addChangeListener(this); 081 } 082 083 /** 084 * Returns the underlying task series collection that was supplied to the 085 * constructor. 086 * 087 * @return The underlying collection (never {@code null}). 088 */ 089 public TaskSeriesCollection getTasks() { 090 return this.underlying; 091 } 092 093 /** 094 * Returns the width of the interval for each series this dataset. 095 * 096 * @return The width of the series interval. 097 * 098 * @see #setSeriesWidth(double) 099 */ 100 public double getSeriesWidth() { 101 return this.seriesWidth; 102 } 103 104 /** 105 * Sets the series interval width and sends a {@link DatasetChangeEvent} to 106 * all registered listeners. 107 * 108 * @param w the width. 109 * 110 * @see #getSeriesWidth() 111 */ 112 public void setSeriesWidth(double w) { 113 if (w <= 0.0) { 114 throw new IllegalArgumentException("Requires 'w' > 0.0."); 115 } 116 this.seriesWidth = w; 117 fireDatasetChanged(); 118 } 119 120 /** 121 * Returns a flag that indicates whether or not the dataset is transposed. 122 * The default is {@code false} which means the x-values are integers 123 * corresponding to the series indices, and the y-values are millisecond 124 * values corresponding to the task date/time intervals. If the flag 125 * is set to {@code true}, the x and y-values are reversed. 126 * 127 * @return The flag. 128 * 129 * @see #setTransposed(boolean) 130 */ 131 public boolean isTransposed() { 132 return this.transposed; 133 } 134 135 /** 136 * Sets the flag that controls whether or not the dataset is transposed 137 * and sends a {@link DatasetChangeEvent} to all registered listeners. 138 * 139 * @param transposed the new flag value. 140 * 141 * @see #isTransposed() 142 */ 143 public void setTransposed(boolean transposed) { 144 this.transposed = transposed; 145 fireDatasetChanged(); 146 } 147 148 /** 149 * Returns the number of series in the dataset. 150 * 151 * @return The series count. 152 */ 153 @Override 154 public int getSeriesCount() { 155 return this.underlying.getSeriesCount(); 156 } 157 158 /** 159 * Returns the name of a series. 160 * 161 * @param series the series index (zero-based). 162 * 163 * @return The name of a series. 164 */ 165 @Override 166 public Comparable getSeriesKey(int series) { 167 return this.underlying.getSeriesKey(series); 168 } 169 170 /** 171 * Returns the number of items (tasks) in the specified series. 172 * 173 * @param series the series index (zero-based). 174 * 175 * @return The item count. 176 */ 177 @Override 178 public int getItemCount(int series) { 179 return this.underlying.getSeries(series).getItemCount(); 180 } 181 182 /** 183 * Returns the x-value (as a double primitive) for an item within a series. 184 * 185 * @param series the series index (zero-based). 186 * @param item the item index (zero-based). 187 * 188 * @return The value. 189 */ 190 @Override 191 public double getXValue(int series, int item) { 192 if (!this.transposed) { 193 return getSeriesValue(series); 194 } 195 else { 196 return getItemValue(series, item); 197 } 198 } 199 200 /** 201 * Returns the starting date/time for the specified item (task) in the 202 * given series, measured in milliseconds since 1-Jan-1970 (as in 203 * java.util.Date). 204 * 205 * @param series the series index. 206 * @param item the item (or task) index. 207 * 208 * @return The start date/time. 209 */ 210 @Override 211 public double getStartXValue(int series, int item) { 212 if (!this.transposed) { 213 return getSeriesStartValue(series); 214 } 215 else { 216 return getItemStartValue(series, item); 217 } 218 } 219 220 /** 221 * Returns the ending date/time for the specified item (task) in the 222 * given series, measured in milliseconds since 1-Jan-1970 (as in 223 * java.util.Date). 224 * 225 * @param series the series index. 226 * @param item the item (or task) index. 227 * 228 * @return The end date/time. 229 */ 230 @Override 231 public double getEndXValue(int series, int item) { 232 if (!this.transposed) { 233 return getSeriesEndValue(series); 234 } 235 else { 236 return getItemEndValue(series, item); 237 } 238 } 239 240 /** 241 * Returns the x-value for the specified series. 242 * 243 * @param series the series index. 244 * @param item the item index. 245 * 246 * @return The x-value (in milliseconds). 247 */ 248 @Override 249 public Number getX(int series, int item) { 250 return getXValue(series, item); 251 } 252 253 /** 254 * Returns the starting date/time for the specified item (task) in the 255 * given series, measured in milliseconds since 1-Jan-1970 (as in 256 * java.util.Date). 257 * 258 * @param series the series index. 259 * @param item the item (or task) index. 260 * 261 * @return The start date/time. 262 */ 263 @Override 264 public Number getStartX(int series, int item) { 265 return getStartXValue(series, item); 266 } 267 268 /** 269 * Returns the ending date/time for the specified item (task) in the 270 * given series, measured in milliseconds since 1-Jan-1970 (as in 271 * java.util.Date). 272 * 273 * @param series the series index. 274 * @param item the item (or task) index. 275 * 276 * @return The end date/time. 277 */ 278 @Override 279 public Number getEndX(int series, int item) { 280 return getEndXValue(series, item); 281 } 282 283 /** 284 * Returns the y-value (as a double primitive) for an item within a series. 285 * 286 * @param series the series index (zero-based). 287 * @param item the item index (zero-based). 288 * 289 * @return The value. 290 */ 291 @Override 292 public double getYValue(int series, int item) { 293 if (!this.transposed) { 294 return getItemValue(series, item); 295 } 296 else { 297 return getSeriesValue(series); 298 } 299 } 300 301 /** 302 * Returns the starting value of the y-interval for an item in the 303 * given series. 304 * 305 * @param series the series index. 306 * @param item the item (or task) index. 307 * 308 * @return The y-interval start. 309 */ 310 @Override 311 public double getStartYValue(int series, int item) { 312 if (!this.transposed) { 313 return getItemStartValue(series, item); 314 } 315 else { 316 return getSeriesStartValue(series); 317 } 318 } 319 320 /** 321 * Returns the ending value of the y-interval for an item in the 322 * given series. 323 * 324 * @param series the series index. 325 * @param item the item (or task) index. 326 * 327 * @return The y-interval end. 328 */ 329 @Override 330 public double getEndYValue(int series, int item) { 331 if (!this.transposed) { 332 return getItemEndValue(series, item); 333 } 334 else { 335 return getSeriesEndValue(series); 336 } 337 } 338 339 /** 340 * Returns the y-value for the specified series/item. In this 341 * implementation, we return the series index as the y-value (this means 342 * that every item in the series has a constant integer value). 343 * 344 * @param series the series index. 345 * @param item the item index. 346 * 347 * @return The y-value. 348 */ 349 @Override 350 public Number getY(int series, int item) { 351 return getYValue(series, item); 352 } 353 354 /** 355 * Returns the starting value of the y-interval for an item in the 356 * given series. 357 * 358 * @param series the series index. 359 * @param item the item (or task) index. 360 * 361 * @return The y-interval start. 362 */ 363 @Override 364 public Number getStartY(int series, int item) { 365 return getStartYValue(series, item); 366 } 367 368 /** 369 * Returns the ending value of the y-interval for an item in the 370 * given series. 371 * 372 * @param series the series index. 373 * @param item the item (or task) index. 374 * 375 * @return The y-interval end. 376 */ 377 @Override 378 public Number getEndY(int series, int item) { 379 return getEndYValue(series, item); 380 } 381 382 private double getSeriesValue(int series) { 383 return series; 384 } 385 386 private double getSeriesStartValue(int series) { 387 return series - this.seriesWidth / 2.0; 388 } 389 390 private double getSeriesEndValue(int series) { 391 return series + this.seriesWidth / 2.0; 392 } 393 394 private double getItemValue(int series, int item) { 395 TaskSeries s = this.underlying.getSeries(series); 396 Task t = s.get(item); 397 TimePeriod duration = t.getDuration(); 398 Date start = duration.getStart(); 399 Date end = duration.getEnd(); 400 return (start.getTime() + end.getTime()) / 2.0; 401 } 402 403 private double getItemStartValue(int series, int item) { 404 TaskSeries s = this.underlying.getSeries(series); 405 Task t = s.get(item); 406 TimePeriod duration = t.getDuration(); 407 Date start = duration.getStart(); 408 return start.getTime(); 409 } 410 411 private double getItemEndValue(int series, int item) { 412 TaskSeries s = this.underlying.getSeries(series); 413 Task t = s.get(item); 414 TimePeriod duration = t.getDuration(); 415 Date end = duration.getEnd(); 416 return end.getTime(); 417 } 418 419 420 /** 421 * Receives a change event from the underlying dataset and responds by 422 * firing a change event for this dataset. 423 * 424 * @param event the event. 425 */ 426 @Override 427 public void datasetChanged(DatasetChangeEvent event) { 428 fireDatasetChanged(); 429 } 430 431 /** 432 * Tests this dataset for equality with an arbitrary object. 433 * 434 * @param obj the object ({@code null} permitted). 435 * 436 * @return A boolean. 437 */ 438 @Override 439 public boolean equals(Object obj) { 440 if (obj == this) { 441 return true; 442 } 443 if (!(obj instanceof XYTaskDataset)) { 444 return false; 445 } 446 XYTaskDataset that = (XYTaskDataset) obj; 447 if (Double.doubleToLongBits(this.seriesWidth) != 448 Double.doubleToLongBits(that.seriesWidth)) { 449 return false; 450 } 451 if (this.transposed != that.transposed) { 452 return false; 453 } 454 if (!Objects.equals(this.underlying, that.underlying)) { 455 return false; 456 } 457 if (!that.canEqual(this)) { 458 return false; 459 } 460 return super.equals(obj); 461 } 462 463 /** 464 * Ensures symmetry between super/subclass implementations of equals. For 465 * more detail, see http://jqno.nl/equalsverifier/manual/inheritance. 466 * 467 * @param other Object 468 * 469 * @return true ONLY if the parameter is THIS class type 470 */ 471 @Override 472 public boolean canEqual(Object other) { 473 // fix the "equals not symmetric" problem 474 return (other instanceof XYTaskDataset); 475 } 476 477 @Override 478 public int hashCode() { 479 int hash = super.hashCode(); // equals calls superclass, hashCode must also 480 hash = 97 * hash + Objects.hashCode(this.underlying); 481 hash = 97 * hash + (int) (Double.doubleToLongBits(this.seriesWidth) ^ 482 (Double.doubleToLongBits(this.seriesWidth) >>> 32)); 483 hash = 97 * hash + (this.transposed ? 1 : 0); 484 return hash; 485 } 486 487 /** 488 * Returns a clone of this dataset. 489 * 490 * @return A clone of this dataset. 491 * 492 * @throws CloneNotSupportedException if there is a problem cloning. 493 */ 494 @Override 495 public Object clone() throws CloneNotSupportedException { 496 XYTaskDataset clone = (XYTaskDataset) super.clone(); 497 clone.underlying = (TaskSeriesCollection) this.underlying.clone(); 498 return clone; 499 } 500 501}