001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2009, 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 * 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 042 package org.jfree.data.category; 043 044 import java.util.Collections; 045 import java.util.List; 046 047 import org.jfree.data.UnknownKeyException; 048 import org.jfree.data.general.AbstractDataset; 049 import org.jfree.data.general.DatasetChangeEvent; 050 import 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 */ 060 public 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 public int getColumnIndex(Comparable key) { 173 int index = this.underlying.getColumnIndex(key); 174 if (index >= this.firstCategoryIndex && index <= lastCategoryIndex()) { 175 return index - this.firstCategoryIndex; 176 } 177 return -1; // we didn't find the key 178 } 179 180 /** 181 * Returns the column key for a given index. 182 * 183 * @param column the column index (zero-based). 184 * 185 * @return The column key. 186 * 187 * @throws IndexOutOfBoundsException if <code>row</code> is out of bounds. 188 */ 189 public Comparable getColumnKey(int column) { 190 return this.underlying.getColumnKey(column + this.firstCategoryIndex); 191 } 192 193 /** 194 * Returns the column keys. 195 * 196 * @return The keys. 197 * 198 * @see #getColumnKey(int) 199 */ 200 public List getColumnKeys() { 201 List result = new java.util.ArrayList(); 202 int last = lastCategoryIndex(); 203 for (int i = this.firstCategoryIndex; i <= last; i++) { 204 result.add(this.underlying.getColumnKey(i)); 205 } 206 return Collections.unmodifiableList(result); 207 } 208 209 /** 210 * Returns the row index for a given key. 211 * 212 * @param key the row key. 213 * 214 * @return The row index, or <code>-1</code> if the key is unrecognised. 215 */ 216 public int getRowIndex(Comparable key) { 217 return this.underlying.getRowIndex(key); 218 } 219 220 /** 221 * Returns the row key for a given index. 222 * 223 * @param row the row index (zero-based). 224 * 225 * @return The row key. 226 * 227 * @throws IndexOutOfBoundsException if <code>row</code> is out of bounds. 228 */ 229 public Comparable getRowKey(int row) { 230 return this.underlying.getRowKey(row); 231 } 232 233 /** 234 * Returns the row keys. 235 * 236 * @return The keys. 237 */ 238 public List getRowKeys() { 239 return this.underlying.getRowKeys(); 240 } 241 242 /** 243 * Returns the value for a pair of keys. 244 * 245 * @param rowKey the row key (<code>null</code> not permitted). 246 * @param columnKey the column key (<code>null</code> not permitted). 247 * 248 * @return The value (possibly <code>null</code>). 249 * 250 * @throws UnknownKeyException if either key is not defined in the dataset. 251 */ 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 public int getColumnCount() { 269 int last = lastCategoryIndex(); 270 if (last == -1) { 271 return 0; 272 } 273 else { 274 return Math.max(last - this.firstCategoryIndex + 1, 0); 275 } 276 } 277 278 /** 279 * Returns the number of rows in the table. 280 * 281 * @return The row count. 282 */ 283 public int getRowCount() { 284 return this.underlying.getRowCount(); 285 } 286 287 /** 288 * Returns a value from the table. 289 * 290 * @param row the row index (zero-based). 291 * @param column the column index (zero-based). 292 * 293 * @return The value (possibly <code>null</code>). 294 */ 295 public Number getValue(int row, int column) { 296 return this.underlying.getValue(row, column + this.firstCategoryIndex); 297 } 298 299 /** 300 * Tests this <code>SlidingCategoryDataset</code> for equality with an 301 * arbitrary object. 302 * 303 * @param obj the object (<code>null</code> permitted). 304 * 305 * @return A boolean. 306 */ 307 public boolean equals(Object obj) { 308 if (obj == this) { 309 return true; 310 } 311 if (!(obj instanceof SlidingCategoryDataset)) { 312 return false; 313 } 314 SlidingCategoryDataset that = (SlidingCategoryDataset) obj; 315 if (this.firstCategoryIndex != that.firstCategoryIndex) { 316 return false; 317 } 318 if (this.maximumCategoryCount != that.maximumCategoryCount) { 319 return false; 320 } 321 if (!this.underlying.equals(that.underlying)) { 322 return false; 323 } 324 return true; 325 } 326 327 /** 328 * Returns an independent copy of the dataset. Note that: 329 * <ul> 330 * <li>the underlying dataset is only cloned if it implements the 331 * {@link PublicCloneable} interface;</li> 332 * <li>the listeners registered with this dataset are not carried over to 333 * the cloned dataset.</li> 334 * </ul> 335 * 336 * @return An independent copy of the dataset. 337 * 338 * @throws CloneNotSupportedException if the dataset cannot be cloned for 339 * any reason. 340 */ 341 public Object clone() throws CloneNotSupportedException { 342 SlidingCategoryDataset clone = (SlidingCategoryDataset) super.clone(); 343 if (this.underlying instanceof PublicCloneable) { 344 PublicCloneable pc = (PublicCloneable) this.underlying; 345 clone.underlying = (CategoryDataset) pc.clone(); 346 } 347 return clone; 348 } 349 350 }