    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    }