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