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