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 * SlidingGanttCategoryDataset.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.Collections; 040import java.util.List; 041import org.jfree.chart.util.PublicCloneable; 042 043import org.jfree.data.UnknownKeyException; 044import org.jfree.data.general.AbstractDataset; 045import org.jfree.data.general.DatasetChangeEvent; 046 047/** 048 * A {@link GanttCategoryDataset} implementation that presents a subset of the 049 * categories in an underlying dataset. The index of the first "visible" 050 * category can be modified, which provides a means of "sliding" through 051 * the categories in the underlying dataset. 052 */ 053public class SlidingGanttCategoryDataset extends AbstractDataset 054 implements GanttCategoryDataset { 055 056 /** The underlying dataset. */ 057 private GanttCategoryDataset underlying; 058 059 /** The index of the first category to present. */ 060 private int firstCategoryIndex; 061 062 /** The maximum number of categories to present. */ 063 private int maximumCategoryCount; 064 065 /** 066 * Creates a new instance. 067 * 068 * @param underlying the underlying dataset ({@code null} not 069 * permitted). 070 * @param firstColumn the index of the first visible column from the 071 * underlying dataset. 072 * @param maxColumns the maximumColumnCount. 073 */ 074 public SlidingGanttCategoryDataset(GanttCategoryDataset underlying, 075 int firstColumn, int maxColumns) { 076 this.underlying = underlying; 077 this.firstCategoryIndex = firstColumn; 078 this.maximumCategoryCount = maxColumns; 079 } 080 081 /** 082 * Returns the underlying dataset that was supplied to the constructor. 083 * 084 * @return The underlying dataset (never {@code null}). 085 */ 086 public GanttCategoryDataset getUnderlyingDataset() { 087 return this.underlying; 088 } 089 090 /** 091 * Returns the index of the first visible category. 092 * 093 * @return The index. 094 * 095 * @see #setFirstCategoryIndex(int) 096 */ 097 public int getFirstCategoryIndex() { 098 return this.firstCategoryIndex; 099 } 100 101 /** 102 * Sets the index of the first category that should be used from the 103 * underlying dataset, and sends a {@link DatasetChangeEvent} to all 104 * registered listeners. 105 * 106 * @param first the index. 107 * 108 * @see #getFirstCategoryIndex() 109 */ 110 public void setFirstCategoryIndex(int first) { 111 if (first < 0 || first >= this.underlying.getColumnCount()) { 112 throw new IllegalArgumentException("Invalid index."); 113 } 114 this.firstCategoryIndex = first; 115 fireDatasetChanged(); 116 } 117 118 /** 119 * Returns the maximum category count. 120 * 121 * @return The maximum category count. 122 * 123 * @see #setMaximumCategoryCount(int) 124 */ 125 public int getMaximumCategoryCount() { 126 return this.maximumCategoryCount; 127 } 128 129 /** 130 * Sets the maximum category count and sends a {@link DatasetChangeEvent} 131 * to all registered listeners. 132 * 133 * @param max the maximum. 134 * 135 * @see #getMaximumCategoryCount() 136 */ 137 public void setMaximumCategoryCount(int max) { 138 if (max < 0) { 139 throw new IllegalArgumentException("Requires 'max' >= 0."); 140 } 141 this.maximumCategoryCount = max; 142 fireDatasetChanged(); 143 } 144 145 /** 146 * Returns the index of the last column for this dataset, or -1. 147 * 148 * @return The index. 149 */ 150 private int lastCategoryIndex() { 151 if (this.maximumCategoryCount == 0) { 152 return -1; 153 } 154 return Math.min(this.firstCategoryIndex + this.maximumCategoryCount, 155 this.underlying.getColumnCount()) - 1; 156 } 157 158 /** 159 * Returns the index for the specified column key. 160 * 161 * @param key the key. 162 * 163 * @return The column index, or -1 if the key is not recognised. 164 */ 165 @Override 166 public int getColumnIndex(Comparable key) { 167 int index = this.underlying.getColumnIndex(key); 168 if (index >= this.firstCategoryIndex && index <= lastCategoryIndex()) { 169 return index - this.firstCategoryIndex; 170 } 171 return -1; // we didn't find the key 172 } 173 174 /** 175 * Returns the column key for a given index. 176 * 177 * @param column the column index (zero-based). 178 * 179 * @return The column key. 180 * 181 * @throws IndexOutOfBoundsException if {@code row} is out of bounds. 182 */ 183 @Override 184 public Comparable getColumnKey(int column) { 185 return this.underlying.getColumnKey(column + this.firstCategoryIndex); 186 } 187 188 /** 189 * Returns the column keys. 190 * 191 * @return The keys. 192 * 193 * @see #getColumnKey(int) 194 */ 195 @Override 196 public List getColumnKeys() { 197 List result = new java.util.ArrayList(); 198 int last = lastCategoryIndex(); 199 for (int i = this.firstCategoryIndex; i < last; i++) { 200 result.add(this.underlying.getColumnKey(i)); 201 } 202 return Collections.unmodifiableList(result); 203 } 204 205 /** 206 * Returns the row index for a given key. 207 * 208 * @param key the row key. 209 * 210 * @return The row index, or {@code -1} if the key is unrecognised. 211 */ 212 @Override 213 public int getRowIndex(Comparable key) { 214 return this.underlying.getRowIndex(key); 215 } 216 217 /** 218 * Returns the row key for a given index. 219 * 220 * @param row the row index (zero-based). 221 * 222 * @return The row key. 223 * 224 * @throws IndexOutOfBoundsException if {@code row} is out of bounds. 225 */ 226 @Override 227 public Comparable getRowKey(int row) { 228 return this.underlying.getRowKey(row); 229 } 230 231 /** 232 * Returns the row keys. 233 * 234 * @return The keys. 235 */ 236 @Override 237 public List getRowKeys() { 238 return this.underlying.getRowKeys(); 239 } 240 241 /** 242 * Returns the value for a pair of keys. 243 * 244 * @param rowKey the row key ({@code null} not permitted). 245 * @param columnKey the column key ({@code null} not permitted). 246 * 247 * @return The value (possibly {@code null}). 248 * 249 * @throws UnknownKeyException if either key is not defined in the dataset. 250 */ 251 @Override 252 public Number getValue(Comparable rowKey, Comparable columnKey) { 253 int r = getRowIndex(rowKey); 254 int c = getColumnIndex(columnKey); 255 if (c != -1) { 256 return this.underlying.getValue(r, c + this.firstCategoryIndex); 257 } 258 else { 259 throw new UnknownKeyException("Unknown columnKey: " + columnKey); 260 } 261 } 262 263 /** 264 * Returns the number of columns in the table. 265 * 266 * @return The column count. 267 */ 268 @Override 269 public int getColumnCount() { 270 int last = lastCategoryIndex(); 271 if (last == -1) { 272 return 0; 273 } 274 else { 275 return Math.max(last - this.firstCategoryIndex + 1, 0); 276 } 277 } 278 279 /** 280 * Returns the number of rows in the table. 281 * 282 * @return The row count. 283 */ 284 @Override 285 public int getRowCount() { 286 return this.underlying.getRowCount(); 287 } 288 289 /** 290 * Returns a value from the table. 291 * 292 * @param row the row index (zero-based). 293 * @param column the column index (zero-based). 294 * 295 * @return The value (possibly {@code null}). 296 */ 297 @Override 298 public Number getValue(int row, int column) { 299 return this.underlying.getValue(row, column + this.firstCategoryIndex); 300 } 301 302 /** 303 * Returns the percent complete for a given item. 304 * 305 * @param rowKey the row key. 306 * @param columnKey the column key. 307 * 308 * @return The percent complete. 309 */ 310 @Override 311 public Number getPercentComplete(Comparable rowKey, Comparable columnKey) { 312 int r = getRowIndex(rowKey); 313 int c = getColumnIndex(columnKey); 314 if (c != -1) { 315 return this.underlying.getPercentComplete(r, 316 c + this.firstCategoryIndex); 317 } 318 else { 319 throw new UnknownKeyException("Unknown columnKey: " + columnKey); 320 } 321 } 322 323 /** 324 * Returns the percentage complete value of a sub-interval for a given item. 325 * 326 * @param rowKey the row key. 327 * @param columnKey the column key. 328 * @param subinterval the sub-interval. 329 * 330 * @return The percent complete value (possibly {@code null}). 331 * 332 * @see #getPercentComplete(int, int, int) 333 */ 334 @Override 335 public Number getPercentComplete(Comparable rowKey, Comparable columnKey, 336 int subinterval) { 337 int r = getRowIndex(rowKey); 338 int c = getColumnIndex(columnKey); 339 if (c != -1) { 340 return this.underlying.getPercentComplete(r, 341 c + this.firstCategoryIndex, subinterval); 342 } 343 else { 344 throw new UnknownKeyException("Unknown columnKey: " + columnKey); 345 } 346 } 347 348 /** 349 * Returns the end value of a sub-interval for a given item. 350 * 351 * @param rowKey the row key. 352 * @param columnKey the column key. 353 * @param subinterval the sub-interval. 354 * 355 * @return The end value (possibly {@code null}). 356 * 357 * @see #getStartValue(Comparable, Comparable, int) 358 */ 359 @Override 360 public Number getEndValue(Comparable rowKey, Comparable columnKey, 361 int subinterval) { 362 int r = getRowIndex(rowKey); 363 int c = getColumnIndex(columnKey); 364 if (c != -1) { 365 return this.underlying.getEndValue(r, 366 c + this.firstCategoryIndex, subinterval); 367 } 368 else { 369 throw new UnknownKeyException("Unknown columnKey: " + columnKey); 370 } 371 } 372 373 /** 374 * Returns the end value of a sub-interval for a given item. 375 * 376 * @param row the row index (zero-based). 377 * @param column the column index (zero-based). 378 * @param subinterval the sub-interval. 379 * 380 * @return The end value (possibly {@code null}). 381 * 382 * @see #getStartValue(int, int, int) 383 */ 384 @Override 385 public Number getEndValue(int row, int column, int subinterval) { 386 return this.underlying.getEndValue(row, 387 column + this.firstCategoryIndex, subinterval); 388 } 389 390 /** 391 * Returns the percent complete for a given item. 392 * 393 * @param series the row index (zero-based). 394 * @param category the column index (zero-based). 395 * 396 * @return The percent complete. 397 */ 398 @Override 399 public Number getPercentComplete(int series, int category) { 400 return this.underlying.getPercentComplete(series, 401 category + this.firstCategoryIndex); 402 } 403 404 /** 405 * Returns the percentage complete value of a sub-interval for a given item. 406 * 407 * @param row the row index (zero-based). 408 * @param column the column index (zero-based). 409 * @param subinterval the sub-interval. 410 * 411 * @return The percent complete value (possibly {@code null}). 412 * 413 * @see #getPercentComplete(Comparable, Comparable, int) 414 */ 415 @Override 416 public Number getPercentComplete(int row, int column, int subinterval) { 417 return this.underlying.getPercentComplete(row, 418 column + this.firstCategoryIndex, subinterval); 419 } 420 421 /** 422 * Returns the start value of a sub-interval for a given item. 423 * 424 * @param rowKey the row key. 425 * @param columnKey the column key. 426 * @param subinterval the sub-interval. 427 * 428 * @return The start value (possibly {@code null}). 429 * 430 * @see #getEndValue(Comparable, Comparable, int) 431 */ 432 @Override 433 public Number getStartValue(Comparable rowKey, Comparable columnKey, 434 int subinterval) { 435 int r = getRowIndex(rowKey); 436 int c = getColumnIndex(columnKey); 437 if (c != -1) { 438 return this.underlying.getStartValue(r, 439 c + this.firstCategoryIndex, subinterval); 440 } 441 else { 442 throw new UnknownKeyException("Unknown columnKey: " + columnKey); 443 } 444 } 445 446 /** 447 * Returns the start value of a sub-interval for a given item. 448 * 449 * @param row the row index (zero-based). 450 * @param column the column index (zero-based). 451 * @param subinterval the sub-interval index (zero-based). 452 * 453 * @return The start value (possibly {@code null}). 454 * 455 * @see #getEndValue(int, int, int) 456 */ 457 @Override 458 public Number getStartValue(int row, int column, int subinterval) { 459 return this.underlying.getStartValue(row, 460 column + this.firstCategoryIndex, subinterval); 461 } 462 463 /** 464 * Returns the number of sub-intervals for a given item. 465 * 466 * @param rowKey the row key. 467 * @param columnKey the column key. 468 * 469 * @return The sub-interval count. 470 * 471 * @see #getSubIntervalCount(int, int) 472 */ 473 @Override 474 public int getSubIntervalCount(Comparable rowKey, Comparable columnKey) { 475 int r = getRowIndex(rowKey); 476 int c = getColumnIndex(columnKey); 477 if (c != -1) { 478 return this.underlying.getSubIntervalCount(r, 479 c + this.firstCategoryIndex); 480 } 481 else { 482 throw new UnknownKeyException("Unknown columnKey: " + columnKey); 483 } 484 } 485 486 /** 487 * Returns the number of sub-intervals for a given item. 488 * 489 * @param row the row index (zero-based). 490 * @param column the column index (zero-based). 491 * 492 * @return The sub-interval count. 493 * 494 * @see #getSubIntervalCount(Comparable, Comparable) 495 */ 496 @Override 497 public int getSubIntervalCount(int row, int column) { 498 return this.underlying.getSubIntervalCount(row, 499 column + this.firstCategoryIndex); 500 } 501 502 /** 503 * Returns the start value for the interval for a given series and category. 504 * 505 * @param rowKey the series key. 506 * @param columnKey the category key. 507 * 508 * @return The start value (possibly {@code null}). 509 * 510 * @see #getEndValue(Comparable, Comparable) 511 */ 512 @Override 513 public Number getStartValue(Comparable rowKey, Comparable columnKey) { 514 int r = getRowIndex(rowKey); 515 int c = getColumnIndex(columnKey); 516 if (c != -1) { 517 return this.underlying.getStartValue(r, 518 c + this.firstCategoryIndex); 519 } 520 else { 521 throw new UnknownKeyException("Unknown columnKey: " + columnKey); 522 } 523 } 524 525 /** 526 * Returns the start value for the interval for a given series and category. 527 * 528 * @param row the series (zero-based index). 529 * @param column the category (zero-based index). 530 * 531 * @return The start value (possibly {@code null}). 532 * 533 * @see #getEndValue(int, int) 534 */ 535 @Override 536 public Number getStartValue(int row, int column) { 537 return this.underlying.getStartValue(row, 538 column + this.firstCategoryIndex); 539 } 540 541 /** 542 * Returns the end value for the interval for a given series and category. 543 * 544 * @param rowKey the series key. 545 * @param columnKey the category key. 546 * 547 * @return The end value (possibly {@code null}). 548 * 549 * @see #getStartValue(Comparable, Comparable) 550 */ 551 @Override 552 public Number getEndValue(Comparable rowKey, Comparable columnKey) { 553 int r = getRowIndex(rowKey); 554 int c = getColumnIndex(columnKey); 555 if (c != -1) { 556 return this.underlying.getEndValue(r, c + this.firstCategoryIndex); 557 } 558 else { 559 throw new UnknownKeyException("Unknown columnKey: " + columnKey); 560 } 561 } 562 563 /** 564 * Returns the end value for the interval for a given series and category. 565 * 566 * @param series the series (zero-based index). 567 * @param category the category (zero-based index). 568 * 569 * @return The end value (possibly {@code null}). 570 */ 571 @Override 572 public Number getEndValue(int series, int category) { 573 return this.underlying.getEndValue(series, 574 category + this.firstCategoryIndex); 575 } 576 577 /** 578 * Tests this {@code SlidingGanttCategoryDataset} instance for equality 579 * with an arbitrary object. 580 * 581 * @param obj the object ({@code null} permitted). 582 * 583 * @return A boolean. 584 */ 585 @Override 586 public boolean equals(Object obj) { 587 if (obj == this) { 588 return true; 589 } 590 if (!(obj instanceof SlidingGanttCategoryDataset)) { 591 return false; 592 } 593 SlidingGanttCategoryDataset that = (SlidingGanttCategoryDataset) obj; 594 if (this.firstCategoryIndex != that.firstCategoryIndex) { 595 return false; 596 } 597 if (this.maximumCategoryCount != that.maximumCategoryCount) { 598 return false; 599 } 600 if (!this.underlying.equals(that.underlying)) { 601 return false; 602 } 603 return true; 604 } 605 606 /** 607 * Returns an independent copy of the dataset. Note that: 608 * <ul> 609 * <li>the underlying dataset is only cloned if it implements the 610 * {@link PublicCloneable} interface;</li> 611 * <li>the listeners registered with this dataset are not carried over to 612 * the cloned dataset.</li> 613 * </ul> 614 * 615 * @return An independent copy of the dataset. 616 * 617 * @throws CloneNotSupportedException if the dataset cannot be cloned for 618 * any reason. 619 */ 620 @Override 621 public Object clone() throws CloneNotSupportedException { 622 SlidingGanttCategoryDataset clone 623 = (SlidingGanttCategoryDataset) super.clone(); 624 if (this.underlying instanceof PublicCloneable) { 625 PublicCloneable pc = (PublicCloneable) this.underlying; 626 clone.underlying = (GanttCategoryDataset) pc.clone(); 627 } 628 return clone; 629 } 630 631}