Source for org.jfree.data.category.SlidingCategoryDataset

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors.
   6:  *
   7:  * Project Info:  http://www.jfree.org/jfreechart/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it
  10:  * under the terms of the GNU Lesser General Public License as published by
  11:  * the Free Software Foundation; either version 2.1 of the License, or
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
  22:  * USA.
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
  25:  * in the United States and other countries.]
  26:  *
  27:  * ---------------------------
  28:  * SlidingCategoryDataset.java
  29:  * ---------------------------
  30:  * (C) Copyright 2008, by Object Refinery Limited.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   -;
  34:  *
  35:  * Changes
  36:  * -------
  37:  * 08-May-2008 : Version 1 (DG);
  38:  *
  39:  */
  40: 
  41: package org.jfree.data.category;
  42: 
  43: import java.util.Collections;
  44: import java.util.List;
  45: 
  46: import org.jfree.data.UnknownKeyException;
  47: import org.jfree.data.general.AbstractDataset;
  48: import org.jfree.data.general.DatasetChangeEvent;
  49: import org.jfree.util.PublicCloneable;
  50: 
  51: /**
  52:  * A {@link CategoryDataset} implementation that presents a subset of the
  53:  * categories in an underlying dataset.  The index of the first "visible"
  54:  * category can be modified, which provides a means of "sliding" through
  55:  * the categories in the underlying dataset.
  56:  *
  57:  * @since 1.0.10
  58:  */
  59: public class SlidingCategoryDataset extends AbstractDataset
  60:         implements CategoryDataset {
  61: 
  62:     /** The underlying dataset. */
  63:     private CategoryDataset underlying;
  64: 
  65:     /** The index of the first category to present. */
  66:     private int firstCategoryIndex;
  67: 
  68:     /** The maximum number of categories to present. */
  69:     private int maximumCategoryCount;
  70: 
  71:     /**
  72:      * Creates a new instance.
  73:      *
  74:      * @param underlying  the underlying dataset (<code>null</code> not
  75:      *     permitted).
  76:      * @param firstColumn  the index of the first visible column from the
  77:      *     underlying dataset.
  78:      * @param maxColumns  the maximumColumnCount.
  79:      */
  80:     public SlidingCategoryDataset(CategoryDataset underlying, int firstColumn,
  81:             int maxColumns) {
  82:         this.underlying = underlying;
  83:         this.firstCategoryIndex = firstColumn;
  84:         this.maximumCategoryCount = maxColumns;
  85:     }
  86: 
  87:     /**
  88:      * Returns the underlying dataset that was supplied to the constructor.
  89:      *
  90:      * @return The underlying dataset (never <code>null</code>).
  91:      */
  92:     public CategoryDataset getUnderlyingDataset() {
  93:         return this.underlying;
  94:     }
  95: 
  96:     /**
  97:      * Returns the index of the first visible category.
  98:      *
  99:      * @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:      * Tests this <code>SlidingCategoryDataset</code> for equality with an
 300:      * arbitrary object.
 301:      *
 302:      * @param obj  the object (<code>null</code> permitted).
 303:      *
 304:      * @return A boolean.
 305:      */
 306:     public boolean equals(Object obj) {
 307:         if (obj == this) {
 308:             return true;
 309:         }
 310:         if (!(obj instanceof SlidingCategoryDataset)) {
 311:             return false;
 312:         }
 313:         SlidingCategoryDataset that = (SlidingCategoryDataset) obj;
 314:         if (this.firstCategoryIndex != that.firstCategoryIndex) {
 315:             return false;
 316:         }
 317:         if (this.maximumCategoryCount != that.maximumCategoryCount) {
 318:             return false;
 319:         }
 320:         if (!this.underlying.equals(that.underlying)) {
 321:             return false;
 322:         }
 323:         return true;
 324:     }
 325: 
 326:     /**
 327:      * Returns an independent copy of the dataset.  Note that:
 328:      * <ul>
 329:      * <li>the underlying dataset is only cloned if it implements the
 330:      * {@link PublicCloneable} interface;</li>
 331:      * <li>the listeners registered with this dataset are not carried over to
 332:      * the cloned dataset.</li>
 333:      * </ul>
 334:      *
 335:      * @return An independent copy of the dataset.
 336:      *
 337:      * @throws CloneNotSupportedException if the dataset cannot be cloned for
 338:      *         any reason.
 339:      */
 340:     public Object clone() throws CloneNotSupportedException {
 341:         SlidingCategoryDataset clone = (SlidingCategoryDataset) super.clone();
 342:         if (this.underlying instanceof PublicCloneable) {
 343:             PublicCloneable pc = (PublicCloneable) this.underlying;
 344:             clone.underlying = (CategoryDataset) pc.clone();
 345:         }
 346:         return clone;
 347:     }
 348: 
 349: }