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