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