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