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