001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2008, 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 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 025 * in the United States and other countries.] 026 * 027 * -------------------------------- 028 * SlidingGanttCategoryDataset.java 029 * -------------------------------- 030 * (C) Copyright 2008, 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 041 package org.jfree.data.gantt; 042 043 import java.util.Collections; 044 import java.util.List; 045 046 import org.jfree.data.UnknownKeyException; 047 import org.jfree.data.general.AbstractDataset; 048 import org.jfree.data.general.DatasetChangeEvent; 049 import 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 */ 059 public 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 public int getColumnIndex(Comparable key) { 172 int index = this.underlying.getColumnIndex(key); 173 if (index >= this.firstCategoryIndex && index <= lastCategoryIndex()) { 174 return index - this.firstCategoryIndex; 175 } 176 return -1; // we didn't find the key 177 } 178 179 /** 180 * Returns the column key for a given index. 181 * 182 * @param column the column index (zero-based). 183 * 184 * @return The column key. 185 * 186 * @throws IndexOutOfBoundsException if <code>row</code> is out of bounds. 187 */ 188 public Comparable getColumnKey(int column) { 189 return this.underlying.getColumnKey(column + this.firstCategoryIndex); 190 } 191 192 /** 193 * Returns the column keys. 194 * 195 * @return The keys. 196 * 197 * @see #getColumnKey(int) 198 */ 199 public List getColumnKeys() { 200 List result = new java.util.ArrayList(); 201 int last = lastCategoryIndex(); 202 for (int i = this.firstCategoryIndex; i < last; i++) { 203 result.add(this.underlying.getColumnKey(i)); 204 } 205 return Collections.unmodifiableList(result); 206 } 207 208 /** 209 * Returns the row index for a given key. 210 * 211 * @param key the row key. 212 * 213 * @return The row index, or <code>-1</code> if the key is unrecognised. 214 */ 215 public int getRowIndex(Comparable key) { 216 return this.underlying.getRowIndex(key); 217 } 218 219 /** 220 * Returns the row key for a given index. 221 * 222 * @param row the row index (zero-based). 223 * 224 * @return The row key. 225 * 226 * @throws IndexOutOfBoundsException if <code>row</code> is out of bounds. 227 */ 228 public Comparable getRowKey(int row) { 229 return this.underlying.getRowKey(row); 230 } 231 232 /** 233 * Returns the row keys. 234 * 235 * @return The keys. 236 */ 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</code> not permitted). 245 * @param columnKey the column key (<code>null</code> not permitted). 246 * 247 * @return The value (possibly <code>null</code>). 248 * 249 * @throws UnknownKeyException if either key is not defined in the dataset. 250 */ 251 public Number getValue(Comparable rowKey, Comparable columnKey) { 252 int r = getRowIndex(rowKey); 253 int c = getColumnIndex(columnKey); 254 if (c != -1) { 255 return this.underlying.getValue(r, c + this.firstCategoryIndex); 256 } 257 else { 258 throw new UnknownKeyException("Unknown columnKey: " + columnKey); 259 } 260 } 261 262 /** 263 * Returns the number of columns in the table. 264 * 265 * @return The column count. 266 */ 267 public int getColumnCount() { 268 int last = lastCategoryIndex(); 269 if (last == -1) { 270 return 0; 271 } 272 else { 273 return Math.max(last - this.firstCategoryIndex + 1, 0); 274 } 275 } 276 277 /** 278 * Returns the number of rows in the table. 279 * 280 * @return The row count. 281 */ 282 public int getRowCount() { 283 return this.underlying.getRowCount(); 284 } 285 286 /** 287 * Returns a value from the table. 288 * 289 * @param row the row index (zero-based). 290 * @param column the column index (zero-based). 291 * 292 * @return The value (possibly <code>null</code>). 293 */ 294 public Number getValue(int row, int column) { 295 return this.underlying.getValue(row, column + this.firstCategoryIndex); 296 } 297 298 /** 299 * Returns the percent complete for a given item. 300 * 301 * @param rowKey the row key. 302 * @param columnKey the column key. 303 * 304 * @return The percent complete. 305 */ 306 public Number getPercentComplete(Comparable rowKey, Comparable columnKey) { 307 int r = getRowIndex(rowKey); 308 int c = getColumnIndex(columnKey); 309 if (c != -1) { 310 return this.underlying.getPercentComplete(r, 311 c + this.firstCategoryIndex); 312 } 313 else { 314 throw new UnknownKeyException("Unknown columnKey: " + columnKey); 315 } 316 } 317 318 /** 319 * Returns the percentage complete value of a sub-interval for a given item. 320 * 321 * @param rowKey the row key. 322 * @param columnKey the column key. 323 * @param subinterval the sub-interval. 324 * 325 * @return The percent complete value (possibly <code>null</code>). 326 * 327 * @see #getPercentComplete(int, int, int) 328 */ 329 public Number getPercentComplete(Comparable rowKey, Comparable columnKey, 330 int subinterval) { 331 int r = getRowIndex(rowKey); 332 int c = getColumnIndex(columnKey); 333 if (c != -1) { 334 return this.underlying.getPercentComplete(r, 335 c + this.firstCategoryIndex, subinterval); 336 } 337 else { 338 throw new UnknownKeyException("Unknown columnKey: " + columnKey); 339 } 340 } 341 342 /** 343 * Returns the end value of a sub-interval for a given item. 344 * 345 * @param rowKey the row key. 346 * @param columnKey the column key. 347 * @param subinterval the sub-interval. 348 * 349 * @return The end value (possibly <code>null</code>). 350 * 351 * @see #getStartValue(Comparable, Comparable, int) 352 */ 353 public Number getEndValue(Comparable rowKey, Comparable columnKey, 354 int subinterval) { 355 int r = getRowIndex(rowKey); 356 int c = getColumnIndex(columnKey); 357 if (c != -1) { 358 return this.underlying.getEndValue(r, 359 c + this.firstCategoryIndex, subinterval); 360 } 361 else { 362 throw new UnknownKeyException("Unknown columnKey: " + columnKey); 363 } 364 } 365 366 /** 367 * Returns the end value of a sub-interval for a given item. 368 * 369 * @param row the row index (zero-based). 370 * @param column the column index (zero-based). 371 * @param subinterval the sub-interval. 372 * 373 * @return The end value (possibly <code>null</code>). 374 * 375 * @see #getStartValue(int, int, int) 376 */ 377 public Number getEndValue(int row, int column, int subinterval) { 378 return this.underlying.getEndValue(row, 379 column + this.firstCategoryIndex, subinterval); 380 } 381 382 /** 383 * Returns the percent complete for a given item. 384 * 385 * @param series the row index (zero-based). 386 * @param category the column index (zero-based). 387 * 388 * @return The percent complete. 389 */ 390 public Number getPercentComplete(int series, int category) { 391 return this.underlying.getPercentComplete(series, 392 category + this.firstCategoryIndex); 393 } 394 395 /** 396 * Returns the percentage complete value of a sub-interval for a given item. 397 * 398 * @param row the row index (zero-based). 399 * @param column the column index (zero-based). 400 * @param subinterval the sub-interval. 401 * 402 * @return The percent complete value (possibly <code>null</code>). 403 * 404 * @see #getPercentComplete(Comparable, Comparable, int) 405 */ 406 public Number getPercentComplete(int row, int column, int subinterval) { 407 return this.underlying.getPercentComplete(row, 408 column + this.firstCategoryIndex, subinterval); 409 } 410 411 /** 412 * Returns the start value of a sub-interval for a given item. 413 * 414 * @param rowKey the row key. 415 * @param columnKey the column key. 416 * @param subinterval the sub-interval. 417 * 418 * @return The start value (possibly <code>null</code>). 419 * 420 * @see #getEndValue(Comparable, Comparable, int) 421 */ 422 public Number getStartValue(Comparable rowKey, Comparable columnKey, 423 int subinterval) { 424 int r = getRowIndex(rowKey); 425 int c = getColumnIndex(columnKey); 426 if (c != -1) { 427 return this.underlying.getStartValue(r, 428 c + this.firstCategoryIndex, subinterval); 429 } 430 else { 431 throw new UnknownKeyException("Unknown columnKey: " + columnKey); 432 } 433 } 434 435 /** 436 * Returns the start value of a sub-interval for a given item. 437 * 438 * @param row the row index (zero-based). 439 * @param column the column index (zero-based). 440 * @param subinterval the sub-interval index (zero-based). 441 * 442 * @return The start value (possibly <code>null</code>). 443 * 444 * @see #getEndValue(int, int, int) 445 */ 446 public Number getStartValue(int row, int column, int subinterval) { 447 return this.underlying.getStartValue(row, 448 column + this.firstCategoryIndex, subinterval); 449 } 450 451 /** 452 * Returns the number of sub-intervals for a given item. 453 * 454 * @param rowKey the row key. 455 * @param columnKey the column key. 456 * 457 * @return The sub-interval count. 458 * 459 * @see #getSubIntervalCount(int, int) 460 */ 461 public int getSubIntervalCount(Comparable rowKey, Comparable columnKey) { 462 int r = getRowIndex(rowKey); 463 int c = getColumnIndex(columnKey); 464 if (c != -1) { 465 return this.underlying.getSubIntervalCount(r, 466 c + this.firstCategoryIndex); 467 } 468 else { 469 throw new UnknownKeyException("Unknown columnKey: " + columnKey); 470 } 471 } 472 473 /** 474 * Returns the number of sub-intervals for a given item. 475 * 476 * @param row the row index (zero-based). 477 * @param column the column index (zero-based). 478 * 479 * @return The sub-interval count. 480 * 481 * @see #getSubIntervalCount(Comparable, Comparable) 482 */ 483 public int getSubIntervalCount(int row, int column) { 484 return this.underlying.getSubIntervalCount(row, 485 column + this.firstCategoryIndex); 486 } 487 488 /** 489 * Returns the start value for the interval for a given series and category. 490 * 491 * @param rowKey the series key. 492 * @param columnKey the category key. 493 * 494 * @return The start value (possibly <code>null</code>). 495 * 496 * @see #getEndValue(Comparable, Comparable) 497 */ 498 public Number getStartValue(Comparable rowKey, Comparable columnKey) { 499 int r = getRowIndex(rowKey); 500 int c = getColumnIndex(columnKey); 501 if (c != -1) { 502 return this.underlying.getStartValue(r, c + this.firstCategoryIndex); 503 } 504 else { 505 throw new UnknownKeyException("Unknown columnKey: " + columnKey); 506 } 507 } 508 509 /** 510 * Returns the start value for the interval for a given series and category. 511 * 512 * @param row the series (zero-based index). 513 * @param column the category (zero-based index). 514 * 515 * @return The start value (possibly <code>null</code>). 516 * 517 * @see #getEndValue(int, int) 518 */ 519 public Number getStartValue(int row, int column) { 520 return this.underlying.getStartValue(row, 521 column + this.firstCategoryIndex); 522 } 523 524 /** 525 * Returns the end value for the interval for a given series and category. 526 * 527 * @param rowKey the series key. 528 * @param columnKey the category key. 529 * 530 * @return The end value (possibly <code>null</code>). 531 * 532 * @see #getStartValue(Comparable, Comparable) 533 */ 534 public Number getEndValue(Comparable rowKey, Comparable columnKey) { 535 int r = getRowIndex(rowKey); 536 int c = getColumnIndex(columnKey); 537 if (c != -1) { 538 return this.underlying.getEndValue(r, c + this.firstCategoryIndex); 539 } 540 else { 541 throw new UnknownKeyException("Unknown columnKey: " + columnKey); 542 } 543 } 544 545 /** 546 * Returns the end value for the interval for a given series and category. 547 * 548 * @param series the series (zero-based index). 549 * @param category the category (zero-based index). 550 * 551 * @return The end value (possibly <code>null</code>). 552 */ 553 public Number getEndValue(int series, int category) { 554 return this.underlying.getEndValue(series, 555 category + this.firstCategoryIndex); 556 } 557 558 /** 559 * Tests this <code>SlidingCategoryDataset</code> for equality with an 560 * arbitrary object. 561 * 562 * @param obj the object (<code>null</code> permitted). 563 * 564 * @return A boolean. 565 */ 566 public boolean equals(Object obj) { 567 if (obj == this) { 568 return true; 569 } 570 if (!(obj instanceof SlidingGanttCategoryDataset)) { 571 return false; 572 } 573 SlidingGanttCategoryDataset that = (SlidingGanttCategoryDataset) obj; 574 if (this.firstCategoryIndex != that.firstCategoryIndex) { 575 return false; 576 } 577 if (this.maximumCategoryCount != that.maximumCategoryCount) { 578 return false; 579 } 580 if (!this.underlying.equals(that.underlying)) { 581 return false; 582 } 583 return true; 584 } 585 586 /** 587 * Returns an independent copy of the dataset. Note that: 588 * <ul> 589 * <li>the underlying dataset is only cloned if it implements the 590 * {@link PublicCloneable} interface;</li> 591 * <li>the listeners registered with this dataset are not carried over to 592 * the cloned dataset.</li> 593 * </ul> 594 * 595 * @return An independent copy of the dataset. 596 * 597 * @throws CloneNotSupportedException if the dataset cannot be cloned for 598 * any reason. 599 */ 600 public Object clone() throws CloneNotSupportedException { 601 SlidingGanttCategoryDataset clone 602 = (SlidingGanttCategoryDataset) super.clone(); 603 if (this.underlying instanceof PublicCloneable) { 604 PublicCloneable pc = (PublicCloneable) this.underlying; 605 clone.underlying = (GanttCategoryDataset) pc.clone(); 606 } 607 return clone; 608 } 609 610 }