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