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 }