    001/* ===========================================================
    002 * JFreeChart : a free chart library for the Java(tm) platform
    003 * ===========================================================
    004 *
    005 * (C) Copyright 2000-2011, 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 * [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, 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}