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 * SlidingCategoryDataset.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 * 08-May-2008 : Version 1 (DG); 038 * 15-Mar-2009 : Fixed bug in getColumnKeys() method (DG); 039 * 040 */ 041 042package org.jfree.data.category; 043 044import java.util.Collections; 045import java.util.List; 046 047import org.jfree.data.UnknownKeyException; 048import org.jfree.data.general.AbstractDataset; 049import org.jfree.data.general.DatasetChangeEvent; 050import org.jfree.util.PublicCloneable; 051 052/** 053 * A {@link CategoryDataset} implementation that presents a subset of the 054 * categories in an underlying dataset. The index of the first "visible" 055 * category can be modified, which provides a means of "sliding" through 056 * the categories in the underlying dataset. 057 * 058 * @since 1.0.10 059 */ 060public class SlidingCategoryDataset extends AbstractDataset 061 implements CategoryDataset { 062 063 /** The underlying dataset. */ 064 private CategoryDataset underlying; 065 066 /** The index of the first category to present. */ 067 private int firstCategoryIndex; 068 069 /** The maximum number of categories to present. */ 070 private int maximumCategoryCount; 071 072 /** 073 * Creates a new instance. 074 * 075 * @param underlying the underlying dataset (<code>null</code> not 076 * permitted). 077 * @param firstColumn the index of the first visible column from the 078 * underlying dataset. 079 * @param maxColumns the maximumColumnCount. 080 */ 081 public SlidingCategoryDataset(CategoryDataset underlying, int firstColumn, 082 int maxColumns) { 083 this.underlying = underlying; 084 this.firstCategoryIndex = firstColumn; 085 this.maximumCategoryCount = maxColumns; 086 } 087 088 /** 089 * Returns the underlying dataset that was supplied to the constructor. 090 * 091 * @return The underlying dataset (never <code>null</code>). 092 */ 093 public CategoryDataset getUnderlyingDataset() { 094 return this.underlying; 095 } 096 097 /** 098 * Returns the index of the first visible category. 099 * 100 * @return The index. 101 * 102 * @see #setFirstCategoryIndex(int) 103 */ 104 public int getFirstCategoryIndex() { 105 return this.firstCategoryIndex; 106 } 107 108 /** 109 * Sets the index of the first category that should be used from the 110 * underlying dataset, and sends a {@link DatasetChangeEvent} to all 111 * registered listeners. 112 * 113 * @param first the index. 114 * 115 * @see #getFirstCategoryIndex() 116 */ 117 public void setFirstCategoryIndex(int first) { 118 if (first < 0 || first >= this.underlying.getColumnCount()) { 119 throw new IllegalArgumentException("Invalid index."); 120 } 121 this.firstCategoryIndex = first; 122 fireDatasetChanged(); 123 } 124 125 /** 126 * Returns the maximum category count. 127 * 128 * @return The maximum category count. 129 * 130 * @see #setMaximumCategoryCount(int) 131 */ 132 public int getMaximumCategoryCount() { 133 return this.maximumCategoryCount; 134 } 135 136 /** 137 * Sets the maximum category count and sends a {@link DatasetChangeEvent} 138 * to all registered listeners. 139 * 140 * @param max the maximum. 141 * 142 * @see #getMaximumCategoryCount() 143 */ 144 public void setMaximumCategoryCount(int max) { 145 if (max < 0) { 146 throw new IllegalArgumentException("Requires 'max' >= 0."); 147 } 148 this.maximumCategoryCount = max; 149 fireDatasetChanged(); 150 } 151 152 /** 153 * Returns the index of the last column for this dataset, or -1. 154 * 155 * @return The index. 156 */ 157 private int lastCategoryIndex() { 158 if (this.maximumCategoryCount == 0) { 159 return -1; 160 } 161 return Math.min(this.firstCategoryIndex + this.maximumCategoryCount, 162 this.underlying.getColumnCount()) - 1; 163 } 164 165 /** 166 * Returns the index for the specified column key. 167 * 168 * @param key the key. 169 * 170 * @return The column index, or -1 if the key is not recognised. 171 */ 172 @Override 173 public int getColumnIndex(Comparable key) { 174 int index = this.underlying.getColumnIndex(key); 175 if (index >= this.firstCategoryIndex && index <= lastCategoryIndex()) { 176 return index - this.firstCategoryIndex; 177 } 178 return -1; // we didn't find the key 179 } 180 181 /** 182 * Returns the column key for a given index. 183 * 184 * @param column the column index (zero-based). 185 * 186 * @return The column key. 187 * 188 * @throws IndexOutOfBoundsException if <code>row</code> is out of bounds. 189 */ 190 @Override 191 public Comparable getColumnKey(int column) { 192 return this.underlying.getColumnKey(column + this.firstCategoryIndex); 193 } 194 195 /** 196 * Returns the column keys. 197 * 198 * @return The keys. 199 * 200 * @see #getColumnKey(int) 201 */ 202 @Override 203 public List getColumnKeys() { 204 List result = new java.util.ArrayList(); 205 int last = lastCategoryIndex(); 206 for (int i = this.firstCategoryIndex; i <= last; i++) { 207 result.add(this.underlying.getColumnKey(i)); 208 } 209 return Collections.unmodifiableList(result); 210 } 211 212 /** 213 * Returns the row index for a given key. 214 * 215 * @param key the row key. 216 * 217 * @return The row index, or <code>-1</code> if the key is unrecognised. 218 */ 219 @Override 220 public int getRowIndex(Comparable key) { 221 return this.underlying.getRowIndex(key); 222 } 223 224 /** 225 * Returns the row key for a given index. 226 * 227 * @param row the row index (zero-based). 228 * 229 * @return The row key. 230 * 231 * @throws IndexOutOfBoundsException if <code>row</code> is out of bounds. 232 */ 233 @Override 234 public Comparable getRowKey(int row) { 235 return this.underlying.getRowKey(row); 236 } 237 238 /** 239 * Returns the row keys. 240 * 241 * @return The keys. 242 */ 243 @Override 244 public List getRowKeys() { 245 return this.underlying.getRowKeys(); 246 } 247 248 /** 249 * Returns the value for a pair of keys. 250 * 251 * @param rowKey the row key (<code>null</code> not permitted). 252 * @param columnKey the column key (<code>null</code> not permitted). 253 * 254 * @return The value (possibly <code>null</code>). 255 * 256 * @throws UnknownKeyException if either key is not defined in the dataset. 257 */ 258 @Override 259 public Number getValue(Comparable rowKey, Comparable columnKey) { 260 int r = getRowIndex(rowKey); 261 int c = getColumnIndex(columnKey); 262 if (c != -1) { 263 return this.underlying.getValue(r, c + this.firstCategoryIndex); 264 } 265 else { 266 throw new UnknownKeyException("Unknown columnKey: " + columnKey); 267 } 268 } 269 270 /** 271 * Returns the number of columns in the table. 272 * 273 * @return The column count. 274 */ 275 @Override 276 public int getColumnCount() { 277 int last = lastCategoryIndex(); 278 if (last == -1) { 279 return 0; 280 } 281 else { 282 return Math.max(last - this.firstCategoryIndex + 1, 0); 283 } 284 } 285 286 /** 287 * Returns the number of rows in the table. 288 * 289 * @return The row count. 290 */ 291 @Override 292 public int getRowCount() { 293 return this.underlying.getRowCount(); 294 } 295 296 /** 297 * Returns a value from the table. 298 * 299 * @param row the row index (zero-based). 300 * @param column the column index (zero-based). 301 * 302 * @return The value (possibly <code>null</code>). 303 */ 304 @Override 305 public Number getValue(int row, int column) { 306 return this.underlying.getValue(row, column + this.firstCategoryIndex); 307 } 308 309 /** 310 * Tests this <code>SlidingCategoryDataset</code> for equality with an 311 * arbitrary object. 312 * 313 * @param obj the object (<code>null</code> permitted). 314 * 315 * @return A boolean. 316 */ 317 @Override 318 public boolean equals(Object obj) { 319 if (obj == this) { 320 return true; 321 } 322 if (!(obj instanceof SlidingCategoryDataset)) { 323 return false; 324 } 325 SlidingCategoryDataset that = (SlidingCategoryDataset) obj; 326 if (this.firstCategoryIndex != that.firstCategoryIndex) { 327 return false; 328 } 329 if (this.maximumCategoryCount != that.maximumCategoryCount) { 330 return false; 331 } 332 if (!this.underlying.equals(that.underlying)) { 333 return false; 334 } 335 return true; 336 } 337 338 /** 339 * Returns an independent copy of the dataset. Note that: 340 * <ul> 341 * <li>the underlying dataset is only cloned if it implements the 342 * {@link PublicCloneable} interface;</li> 343 * <li>the listeners registered with this dataset are not carried over to 344 * the cloned dataset.</li> 345 * </ul> 346 * 347 * @return An independent copy of the dataset. 348 * 349 * @throws CloneNotSupportedException if the dataset cannot be cloned for 350 * any reason. 351 */ 352 @Override 353 public Object clone() throws CloneNotSupportedException { 354 SlidingCategoryDataset clone = (SlidingCategoryDataset) super.clone(); 355 if (this.underlying instanceof PublicCloneable) { 356 PublicCloneable pc = (PublicCloneable) this.underlying; 357 clone.underlying = (CategoryDataset) pc.clone(); 358 } 359 return clone; 360 } 361 362}