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 }