001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2013, 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, 2009, by Object Refinery Limited. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 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; 045 046import org.jfree.data.UnknownKeyException; 047import org.jfree.data.general.AbstractDataset; 048import org.jfree.data.general.DatasetChangeEvent; 049import org.jfree.util.PublicCloneable; 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</code> 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</code>). 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</code> 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</code> 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</code> 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</code> not permitted). 251 * @param columnKey the column key (<code>null</code> not permitted). 252 * 253 * @return The value (possibly <code>null</code>). 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 return this.underlying.getValue(r, c + this.firstCategoryIndex); 263 } 264 else { 265 throw new UnknownKeyException("Unknown columnKey: " + columnKey); 266 } 267 } 268 269 /** 270 * Returns the number of columns in the table. 271 * 272 * @return The column count. 273 */ 274 @Override 275 public int getColumnCount() { 276 int last = lastCategoryIndex(); 277 if (last == -1) { 278 return 0; 279 } 280 else { 281 return Math.max(last - this.firstCategoryIndex + 1, 0); 282 } 283 } 284 285 /** 286 * Returns the number of rows in the table. 287 * 288 * @return The row count. 289 */ 290 @Override 291 public int getRowCount() { 292 return this.underlying.getRowCount(); 293 } 294 295 /** 296 * Returns a value from the table. 297 * 298 * @param row the row index (zero-based). 299 * @param column the column index (zero-based). 300 * 301 * @return The value (possibly <code>null</code>). 302 */ 303 @Override 304 public Number getValue(int row, int column) { 305 return this.underlying.getValue(row, column + this.firstCategoryIndex); 306 } 307 308 /** 309 * Returns the percent complete for a given item. 310 * 311 * @param rowKey the row key. 312 * @param columnKey the column key. 313 * 314 * @return The percent complete. 315 */ 316 @Override 317 public Number getPercentComplete(Comparable rowKey, Comparable columnKey) { 318 int r = getRowIndex(rowKey); 319 int c = getColumnIndex(columnKey); 320 if (c != -1) { 321 return this.underlying.getPercentComplete(r, 322 c + this.firstCategoryIndex); 323 } 324 else { 325 throw new UnknownKeyException("Unknown columnKey: " + columnKey); 326 } 327 } 328 329 /** 330 * Returns the percentage complete value of a sub-interval for a given item. 331 * 332 * @param rowKey the row key. 333 * @param columnKey the column key. 334 * @param subinterval the sub-interval. 335 * 336 * @return The percent complete value (possibly <code>null</code>). 337 * 338 * @see #getPercentComplete(int, int, int) 339 */ 340 @Override 341 public Number getPercentComplete(Comparable rowKey, Comparable columnKey, 342 int subinterval) { 343 int r = getRowIndex(rowKey); 344 int c = getColumnIndex(columnKey); 345 if (c != -1) { 346 return this.underlying.getPercentComplete(r, 347 c + this.firstCategoryIndex, subinterval); 348 } 349 else { 350 throw new UnknownKeyException("Unknown columnKey: " + columnKey); 351 } 352 } 353 354 /** 355 * Returns the end value of a sub-interval for a given item. 356 * 357 * @param rowKey the row key. 358 * @param columnKey the column key. 359 * @param subinterval the sub-interval. 360 * 361 * @return The end value (possibly <code>null</code>). 362 * 363 * @see #getStartValue(Comparable, Comparable, int) 364 */ 365 @Override 366 public Number getEndValue(Comparable rowKey, Comparable columnKey, 367 int subinterval) { 368 int r = getRowIndex(rowKey); 369 int c = getColumnIndex(columnKey); 370 if (c != -1) { 371 return this.underlying.getEndValue(r, 372 c + this.firstCategoryIndex, subinterval); 373 } 374 else { 375 throw new UnknownKeyException("Unknown columnKey: " + columnKey); 376 } 377 } 378 379 /** 380 * Returns the end value of a sub-interval for a given item. 381 * 382 * @param row the row index (zero-based). 383 * @param column the column index (zero-based). 384 * @param subinterval the sub-interval. 385 * 386 * @return The end value (possibly <code>null</code>). 387 * 388 * @see #getStartValue(int, int, int) 389 */ 390 @Override 391 public Number getEndValue(int row, int column, int subinterval) { 392 return this.underlying.getEndValue(row, 393 column + this.firstCategoryIndex, subinterval); 394 } 395 396 /** 397 * Returns the percent complete for a given item. 398 * 399 * @param series the row index (zero-based). 400 * @param category the column index (zero-based). 401 * 402 * @return The percent complete. 403 */ 404 @Override 405 public Number getPercentComplete(int series, int category) { 406 return this.underlying.getPercentComplete(series, 407 category + this.firstCategoryIndex); 408 } 409 410 /** 411 * Returns the percentage complete value of a sub-interval for a given item. 412 * 413 * @param row the row index (zero-based). 414 * @param column the column index (zero-based). 415 * @param subinterval the sub-interval. 416 * 417 * @return The percent complete value (possibly <code>null</code>). 418 * 419 * @see #getPercentComplete(Comparable, Comparable, int) 420 */ 421 @Override 422 public Number getPercentComplete(int row, int column, int subinterval) { 423 return this.underlying.getPercentComplete(row, 424 column + this.firstCategoryIndex, subinterval); 425 } 426 427 /** 428 * Returns the start value of a sub-interval for a given item. 429 * 430 * @param rowKey the row key. 431 * @param columnKey the column key. 432 * @param subinterval the sub-interval. 433 * 434 * @return The start value (possibly <code>null</code>). 435 * 436 * @see #getEndValue(Comparable, Comparable, int) 437 */ 438 @Override 439 public Number getStartValue(Comparable rowKey, Comparable columnKey, 440 int subinterval) { 441 int r = getRowIndex(rowKey); 442 int c = getColumnIndex(columnKey); 443 if (c != -1) { 444 return this.underlying.getStartValue(r, 445 c + this.firstCategoryIndex, subinterval); 446 } 447 else { 448 throw new UnknownKeyException("Unknown columnKey: " + columnKey); 449 } 450 } 451 452 /** 453 * Returns the start value of a sub-interval for a given item. 454 * 455 * @param row the row index (zero-based). 456 * @param column the column index (zero-based). 457 * @param subinterval the sub-interval index (zero-based). 458 * 459 * @return The start value (possibly <code>null</code>). 460 * 461 * @see #getEndValue(int, int, int) 462 */ 463 @Override 464 public Number getStartValue(int row, int column, int subinterval) { 465 return this.underlying.getStartValue(row, 466 column + this.firstCategoryIndex, subinterval); 467 } 468 469 /** 470 * Returns the number of sub-intervals for a given item. 471 * 472 * @param rowKey the row key. 473 * @param columnKey the column key. 474 * 475 * @return The sub-interval count. 476 * 477 * @see #getSubIntervalCount(int, int) 478 */ 479 @Override 480 public int getSubIntervalCount(Comparable rowKey, Comparable columnKey) { 481 int r = getRowIndex(rowKey); 482 int c = getColumnIndex(columnKey); 483 if (c != -1) { 484 return this.underlying.getSubIntervalCount(r, 485 c + this.firstCategoryIndex); 486 } 487 else { 488 throw new UnknownKeyException("Unknown columnKey: " + columnKey); 489 } 490 } 491 492 /** 493 * Returns the number of sub-intervals for a given item. 494 * 495 * @param row the row index (zero-based). 496 * @param column the column index (zero-based). 497 * 498 * @return The sub-interval count. 499 * 500 * @see #getSubIntervalCount(Comparable, Comparable) 501 */ 502 @Override 503 public int getSubIntervalCount(int row, int column) { 504 return this.underlying.getSubIntervalCount(row, 505 column + this.firstCategoryIndex); 506 } 507 508 /** 509 * Returns the start value for the interval for a given series and category. 510 * 511 * @param rowKey the series key. 512 * @param columnKey the category key. 513 * 514 * @return The start value (possibly <code>null</code>). 515 * 516 * @see #getEndValue(Comparable, Comparable) 517 */ 518 @Override 519 public Number getStartValue(Comparable rowKey, Comparable columnKey) { 520 int r = getRowIndex(rowKey); 521 int c = getColumnIndex(columnKey); 522 if (c != -1) { 523 return this.underlying.getStartValue(r, 524 c + this.firstCategoryIndex); 525 } 526 else { 527 throw new UnknownKeyException("Unknown columnKey: " + columnKey); 528 } 529 } 530 531 /** 532 * Returns the start value for the interval for a given series and category. 533 * 534 * @param row the series (zero-based index). 535 * @param column the category (zero-based index). 536 * 537 * @return The start value (possibly <code>null</code>). 538 * 539 * @see #getEndValue(int, int) 540 */ 541 @Override 542 public Number getStartValue(int row, int column) { 543 return this.underlying.getStartValue(row, 544 column + this.firstCategoryIndex); 545 } 546 547 /** 548 * Returns the end value for the interval for a given series and category. 549 * 550 * @param rowKey the series key. 551 * @param columnKey the category key. 552 * 553 * @return The end value (possibly <code>null</code>). 554 * 555 * @see #getStartValue(Comparable, Comparable) 556 */ 557 @Override 558 public Number getEndValue(Comparable rowKey, Comparable columnKey) { 559 int r = getRowIndex(rowKey); 560 int c = getColumnIndex(columnKey); 561 if (c != -1) { 562 return this.underlying.getEndValue(r, c + this.firstCategoryIndex); 563 } 564 else { 565 throw new UnknownKeyException("Unknown columnKey: " + columnKey); 566 } 567 } 568 569 /** 570 * Returns the end value for the interval for a given series and category. 571 * 572 * @param series the series (zero-based index). 573 * @param category the category (zero-based index). 574 * 575 * @return The end value (possibly <code>null</code>). 576 */ 577 @Override 578 public Number getEndValue(int series, int category) { 579 return this.underlying.getEndValue(series, 580 category + this.firstCategoryIndex); 581 } 582 583 /** 584 * Tests this <code>SlidingCategoryDataset</code> for equality with an 585 * arbitrary object. 586 * 587 * @param obj the object (<code>null</code> permitted). 588 * 589 * @return A boolean. 590 */ 591 @Override 592 public boolean equals(Object obj) { 593 if (obj == this) { 594 return true; 595 } 596 if (!(obj instanceof SlidingGanttCategoryDataset)) { 597 return false; 598 } 599 SlidingGanttCategoryDataset that = (SlidingGanttCategoryDataset) obj; 600 if (this.firstCategoryIndex != that.firstCategoryIndex) { 601 return false; 602 } 603 if (this.maximumCategoryCount != that.maximumCategoryCount) { 604 return false; 605 } 606 if (!this.underlying.equals(that.underlying)) { 607 return false; 608 } 609 return true; 610 } 611 612 /** 613 * Returns an independent copy of the dataset. Note that: 614 * <ul> 615 * <li>the underlying dataset is only cloned if it implements the 616 * {@link PublicCloneable} interface;</li> 617 * <li>the listeners registered with this dataset are not carried over to 618 * the cloned dataset.</li> 619 * </ul> 620 * 621 * @return An independent copy of the dataset. 622 * 623 * @throws CloneNotSupportedException if the dataset cannot be cloned for 624 * any reason. 625 */ 626 @Override 627 public Object clone() throws CloneNotSupportedException { 628 SlidingGanttCategoryDataset clone 629 = (SlidingGanttCategoryDataset) super.clone(); 630 if (this.underlying instanceof PublicCloneable) { 631 PublicCloneable pc = (PublicCloneable) this.underlying; 632 clone.underlying = (GanttCategoryDataset) pc.clone(); 633 } 634 return clone; 635 } 636 637}