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
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    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}