    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2005, 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     * DefaultIntervalCategoryDataset.java
    029     * -----------------------------------
    030     * (C) Copyright 2002-2005, by Jeremy Bowman and Contributors.
    031     *
    032     * Original Author:  Jeremy Bowman;
    033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
    034     *
    035     * $Id: DefaultIntervalCategoryDataset.java,v 1.9.2.2 2005/10/25 21:29:58 mungady Exp $
    036     *
    037     * Changes
    038     * -------
    039     * 29-Apr-2002 : Version 1, contributed by Jeremy Bowman (DG);
    040     * 24-Oct-2002 : Amendments for changes made to the dataset interface (DG);
    041     *
    042     */
    043
    044    package org.jfree.data.category;
    045
    046    import java.util.ArrayList;
    047    import java.util.Arrays;
    048    import java.util.Collections;
    049    import java.util.List;
    050    import java.util.ResourceBundle;
    051
    052    import org.jfree.data.DataUtilities;
    053    import org.jfree.data.general.AbstractSeriesDataset;
    054
    055    /**
    056     * A convenience class that provides a default implementation of the
    057     * {@link IntervalCategoryDataset} interface.
    058     * <p>
    059     * The standard constructor accepts data in a two dimensional array where the
    060     * first dimension is the series, and the second dimension is the category.
    061     *
    062     * @author Jeremy Bowman
    063     */
    064    public class DefaultIntervalCategoryDataset extends AbstractSeriesDataset
    065                                                implements IntervalCategoryDataset {
    066
    067        /** The series keys. */
    068        private Comparable[] seriesKeys;
    069
    070        /** The category keys. */
    071        private Comparable[] categoryKeys;
    072
    073        /** Storage for the start value data. */
    074        private Number[][] startData;
    075
    076        /** Storage for the end value data. */
    077        private Number[][] endData;
    078
    079        /**
    080         * Creates a new dataset.
    081         *
    082         * @param starts  the starting values for the intervals.
    083         * @param ends  the ending values for the intervals.
    084         */
    085        public DefaultIntervalCategoryDataset(double[][] starts, double[][] ends) {
    086            this(
    087                DataUtilities.createNumberArray2D(starts),
    088                DataUtilities.createNumberArray2D(ends)
    089            );
    090        }
    091
    092        /**
    093         * Constructs a dataset and populates it with data from the array.
    094         * <p>
    095         * The arrays are indexed as data[series][category].  Series and category
    096         * names are automatically generated - you can change them using the
    097         * {@link #setSeriesKeys(Comparable[])} and
    098         * {@link #setCategoryKeys(Comparable[])} methods.
    099         *
    100         * @param starts  the start values data.
    101         * @param ends  the end values data.
    102         */
    103        public DefaultIntervalCategoryDataset(Number[][] starts, Number[][] ends) {
    104            this(null, null, starts, ends);
    105        }
    106
    107        /**
    108         * Constructs a DefaultIntervalCategoryDataset, populates it with data
    109         * from the arrays, and uses the supplied names for the series.
    110         * <p>
    111         * Category names are generated automatically ("Category 1", "Category 2",
    112         * etc).
    113         *
    114         * @param seriesNames  the series names.
    115         * @param starts  the start values data, indexed as data[series][category].
    116         * @param ends  the end values data, indexed as data[series][category].
    117         */
    118        public DefaultIntervalCategoryDataset(String[] seriesNames,
    119                                              Number[][] starts,
    120                                              Number[][] ends) {
    121
    122            this(seriesNames, null, starts, ends);
    123
    124        }
    125
    126        /**
    127         * Constructs a DefaultIntervalCategoryDataset, populates it with data
    128         * from the arrays, and uses the supplied names for the series and the
    129         * supplied objects for the categories.
    130         *
    131         * @param seriesKeys the series keys.
    132         * @param categoryKeys  the categories.
    133         * @param starts  the start values data, indexed as data[series][category].
    134         * @param ends  the end values data, indexed as data[series][category].
    135         */
    136        public DefaultIntervalCategoryDataset(Comparable[] seriesKeys,
    137                                              Comparable[] categoryKeys,
    138                                              Number[][] starts,
    139                                              Number[][] ends) {
    140
    141            this.startData = starts;
    142            this.endData = ends;
    143
    144            if (starts != null && ends != null) {
    145
    146                String baseName = "org.jfree.data.resources.DataPackageResources";
    147                ResourceBundle resources = ResourceBundle.getBundle(baseName);
    148
    149                int seriesCount = starts.length;
    150                if (seriesCount != ends.length) {
    151                    String errMsg = "DefaultIntervalCategoryDataset: the number "
    152                        + "of series in the start value dataset does "
    153                        + "not match the number of series in the end "
    154                        + "value dataset.";
    155                    throw new IllegalArgumentException(errMsg);
    156                }
    157                if (seriesCount > 0) {
    158
    159                    // set up the series names...
    160                    if (seriesKeys != null) {
    161
    162                        if (seriesKeys.length != seriesCount) {
    163                            throw new IllegalArgumentException(
    164                                "The number of series keys does "
    165                                + "not match the number of series in the data."
    166                            );
    167                        }
    168
    169                        this.seriesKeys = seriesKeys;
    170                    }
    171                    else {
    172                        String prefix
    173                            = resources.getString("series.default-prefix") + " ";
    174                        this.seriesKeys = generateKeys(seriesCount, prefix);
    175                    }
    176
    177                    // set up the category names...
    178                    int categoryCount = starts[0].length;
    179                    if (categoryCount != ends[0].length) {
    180                        String errMsg = "DefaultIntervalCategoryDataset: the "
    181                                    + "number of categories in the start value "
    182                                    + "dataset does not match the number of "
    183                                    + "categories in the end value dataset.";
    184                        throw new IllegalArgumentException(errMsg);
    185                    }
    186                    if (categoryKeys != null) {
    187                        if (categoryKeys.length != categoryCount) {
    188                            throw new IllegalArgumentException(
    189                                "The number of category keys does "
    190                                + "not match the number of categories in the data."
    191                            );
    192                        }
    193                        this.categoryKeys = categoryKeys;
    194                    }
    195                    else {
    196                        String prefix = resources.getString(
    197                            "categories.default-prefix"
    198                        ) + " ";
    199                        this.categoryKeys = generateKeys(categoryCount, prefix);
    200                    }
    201
    202                }
    203                else {
    204                    this.seriesKeys = null;
    205                    this.categoryKeys = null;
    206                }
    207            }
    208
    209        }
    210
    211        /**
    212         * Returns the number of series in the dataset (possibly zero).
    213         *
    214         * @return The number of series in the dataset.
    215         */
    216        public int getSeriesCount() {
    217            int result = 0;
    218            if (this.startData != null) {
    219                result = this.startData.length;
    220            }
    221            return result;
    222        }
    223
    224        /**
    225         * Returns the item count.
    226         *
    227         * @return The item count.
    228         */
    229        public int getItemCount() {
    230            return this.categoryKeys.length;
    231        }
    232
    233        /**
    234         * Returns a series index.
    235         *
    236         * @param series  the series key.
    237         *
    238         * @return The series index.
    239         */
    240        public int getSeriesIndex(Comparable series) {
    241            List seriesKeys = getSeries();
    242            return seriesKeys.indexOf(series);
    243        }
    244
    245        /**
    246         * Returns the name of the specified series.
    247         *
    248         * @param series  the index of the required series (zero-based).
    249         *
    250         * @return The name of the specified series.
    251         */
    252        public Comparable getSeriesKey(int series) {
    253            if ((series >= getSeriesCount()) || (series < 0)) {
    254                throw new IllegalArgumentException("No such series : " + series);
    255            }
    256            return this.seriesKeys[series];
    257        }
    258
    259        /**
    260         * Sets the names of the series in the dataset.
    261         *
    262         * @param seriesKeys  the keys of the series in the dataset.
    263         */
    264        public void setSeriesKeys(Comparable[] seriesKeys) {
    265
    266            // check argument...
    267            if (seriesKeys == null) {
    268                throw new IllegalArgumentException("Null 'seriesKeys' argument.");
    269            }
    270
    271            if (seriesKeys.length != getSeriesCount()) {
    272                throw new IllegalArgumentException(
    273                    "DefaultIntervalCategoryDataset.setSeriesKeys(): "
    274                    + "the number of series keys does not match the data."
    275                );
    276            }
    277
    278            // make the change...
    279            this.seriesKeys = seriesKeys;
    280            fireDatasetChanged();
    281
    282        }
    283
    284        /**
    285         * Returns the number of categories in the dataset.
    286         * <P>
    287         * This method is part of the CategoryDataset interface.
    288         *
    289         * @return The number of categories in the dataset.
    290         */
    291        public int getCategoryCount() {
    292            int result = 0;
    293            if (this.startData != null) {
    294                if (getSeriesCount() > 0) {
    295                    result = this.startData[0].length;
    296                }
    297            }
    298            return result;
    299        }
    300
    301        /**
    302         * Returns a list of the series in the dataset.
    303         * <P>
    304         * Supports the CategoryDataset interface.
    305         *
    306         * @return A list of the series in the dataset.
    307         */
    308        public List getSeries() {
    309
    310            // the CategoryDataset interface expects a list of series, but
    311            // we've stored them in an array...
    312            if (this.seriesKeys == null) {
    313                return new java.util.ArrayList();
    314            }
    315            else {
    316                return Collections.unmodifiableList(Arrays.asList(this.seriesKeys));
    317            }
    318
    319        }
    320
    321        /**
    322         * Returns a list of the categories in the dataset.
    323         * <P>
    324         * Supports the CategoryDataset interface.
    325         *
    326         * @return A list of the categories in the dataset.
    327         */
    328        public List getCategories() {
    329            return getColumnKeys();
    330        }
    331
    332        /**
    333         * Returns a list of the categories in the dataset.
    334         * <P>
    335         * Supports the CategoryDataset interface.
    336         *
    337         * @return A list of the categories in the dataset.
    338         */
    339        public List getColumnKeys() {
    340
    341            // the CategoryDataset interface expects a list of categories, but
    342            // we've stored them in an array...
    343            if (this.categoryKeys == null) {
    344                return new ArrayList();
    345            }
    346            else {
    347                return Collections.unmodifiableList(
    348                    Arrays.asList(this.categoryKeys)
    349                );
    350            }
    351
    352        }
    353
    354        /**
    355         * Sets the categories for the dataset.
    356         *
    357         * @param categoryKeys  an array of objects representing the categories in
    358         *                      the dataset.
    359         */
    360        public void setCategoryKeys(Comparable[] categoryKeys) {
    361
    362            // check arguments...
    363            if (categoryKeys == null) {
    364                throw new IllegalArgumentException("Null 'categoryKeys' argument.");
    365            }
    366
    367            if (categoryKeys.length != this.startData[0].length) {
    368                throw new IllegalArgumentException(
    369                    "The number of categories does not match the data."
    370                );
    371            }
    372
    373            for (int i = 0; i < categoryKeys.length; i++) {
    374                if (categoryKeys[i] == null) {
    375                    throw new IllegalArgumentException(
    376                        "DefaultIntervalCategoryDataset.setCategoryKeys(): "
    377                        + "null category not permitted.");
    378                }
    379            }
    380
    381            // make the change...
    382            this.categoryKeys = categoryKeys;
    383            fireDatasetChanged();
    384
    385        }
    386
    387        /**
    388         * Returns the data value for one category in a series.
    389         * <P>
    390         * This method is part of the CategoryDataset interface.  Not particularly
    391         * meaningful for this class...returns the end value.
    392         * @param series    The required series (zero based index).
    393         * @param category  The required category.
    394         * @return The data value for one category in a series (null possible).
    395         */
    396        public Number getValue(Comparable series, Comparable category) {
    397            int seriesIndex = getSeriesIndex(series);
    398            int itemIndex = getColumnIndex(category);
    399            return getValue(seriesIndex, itemIndex);
    400        }
    401
    402        /**
    403         * Returns the data value for one category in a series.
    404         * <P>
    405         * This method is part of the CategoryDataset interface.  Not particularly
    406         * meaningful for this class...returns the end value.
    407         *
    408         * @param series  the required series (zero based index).
    409         * @param category  the required category.
    410         *
    411         * @return The data value for one category in a series (null possible).
    412         */
    413        public Number getValue(int series, int category) {
    414            return getEndValue(series, category);
    415        }
    416
    417        /**
    418         * Returns the start data value for one category in a series.
    419         *
    420         * @param series  the required series.
    421         * @param category  the required category.
    422         *
    423         * @return The start data value for one category in a series
    424         *         (possibly <code>null</code>).
    425         */
    426        public Number getStartValue(Comparable series, Comparable category) {
    427            int seriesIndex = getSeriesIndex(series);
    428            int itemIndex = getColumnIndex(category);
    429            return getStartValue(seriesIndex, itemIndex);
    430        }
    431
    432        /**
    433         * Returns the start data value for one category in a series.
    434         *
    435         * @param series  the required series (zero based index).
    436         * @param category  the required category.
    437         *
    438         * @return The start data value for one category in a series
    439         *         (possibly <code>null</code>).
    440         */
    441        public Number getStartValue(int series, int category) {
    442
    443            // check arguments...
    444            if ((series < 0) || (series >= getSeriesCount())) {
    445                throw new IllegalArgumentException(
    446                    "DefaultIntervalCategoryDataset.getValue(): "
    447                    + "series index out of range.");
    448            }
    449
    450            if ((category < 0) || (category >= getCategoryCount())) {
    451                throw new IllegalArgumentException(
    452                    "DefaultIntervalCategoryDataset.getValue(): "
    453                    + "category index out of range.");
    454            }
    455
    456            // fetch the value...
    457            return this.startData[series][category];
    458
    459        }
    460
    461        /**
    462         * Returns the end data value for one category in a series.
    463         *
    464         * @param series  the required series.
    465         * @param category  the required category.
    466         *
    467         * @return The end data value for one category in a series (null possible).
    468         */
    469        public Number getEndValue(Comparable series, Comparable category) {
    470            int seriesIndex = getSeriesIndex(series);
    471            int itemIndex = getColumnIndex(category);
    472            return getEndValue(seriesIndex, itemIndex);
    473        }
    474
    475        /**
    476         * Returns the end data value for one category in a series.
    477         *
    478         * @param series  the required series (zero based index).
    479         * @param category  the required category.
    480         *
    481         * @return The end data value for one category in a series (null possible).
    482         */
    483        public Number getEndValue(int series, int category) {
    484
    485            // check arguments...
    486            if ((series < 0) || (series >= getSeriesCount())) {
    487                throw new IllegalArgumentException(
    488                    "DefaultIntervalCategoryDataset.getValue(): "
    489                    + "series index out of range.");
    490            }
    491
    492            if ((category < 0) || (category >= getCategoryCount())) {
    493                throw new IllegalArgumentException(
    494                    "DefaultIntervalCategoryDataset.getValue(): "
    495                    + "category index out of range.");
    496            }
    497
    498            // fetch the value...
    499            return this.endData[series][category];
    500
    501        }
    502
    503        /**
    504         * Sets the start data value for one category in a series.
    505         *
    506         * @param series  the series (zero-based index).
    507         * @param category  the category.
    508         *
    509         * @param value The value.
    510         */
    511        public void setStartValue(int series, Comparable category, Number value) {
    512
    513            // does the series exist?
    514            if ((series < 0) || (series > getSeriesCount())) {
    515                throw new IllegalArgumentException(
    516                    "DefaultIntervalCategoryDataset.setValue: "
    517                    + "series outside valid range.");
    518            }
    519
    520            // is the category valid?
    521            int categoryIndex = getCategoryIndex(category);
    522            if (categoryIndex < 0) {
    523                throw new IllegalArgumentException(
    524                    "DefaultIntervalCategoryDataset.setValue: "
    525                    + "unrecognised category.");
    526            }
    527
    528            // update the data...
    529            this.startData[series][categoryIndex] = value;
    530            fireDatasetChanged();
    531
    532        }
    533
    534        /**
    535         * Sets the end data value for one category in a series.
    536         *
    537         * @param series  the series (zero-based index).
    538         * @param category  the category.
    539         *
    540         * @param value the value.
    541         */
    542        public void setEndValue(int series, Comparable category, Number value) {
    543
    544            // does the series exist?
    545            if ((series < 0) || (series > getSeriesCount())) {
    546                throw new IllegalArgumentException(
    547                    "DefaultIntervalCategoryDataset.setValue: "
    548                    + "series outside valid range.");
    549            }
    550
    551            // is the category valid?
    552            int categoryIndex = getCategoryIndex(category);
    553            if (categoryIndex < 0) {
    554                throw new IllegalArgumentException(
    555                    "DefaultIntervalCategoryDataset.setValue: "
    556                    + "unrecognised category.");
    557            }
    558
    559            // update the data...
    560            this.endData[series][categoryIndex] = value;
    561            fireDatasetChanged();
    562
    563        }
    564
    565        /**
    566         * Returns the index for the given category.
    567         *
    568         * @param category  the category.
    569         *
    570         * @return The index.
    571         */
    572        private int getCategoryIndex(Comparable category) {
    573            int result = -1;
    574            for (int i = 0; i < this.categoryKeys.length; i++) {
    575                if (category.equals(this.categoryKeys[i])) {
    576                    result = i;
    577                    break;
    578                }
    579            }
    580            return result;
    581        }
    582
    583        /**
    584         * Generates an array of keys, by appending a space plus an integer
    585         * (starting with 1) to the supplied prefix string.
    586         *
    587         * @param count  the number of keys required.
    588         * @param prefix  the name prefix.
    589         *
    590         * @return An array of <i>prefixN</i> with N = { 1 .. count}.
    591         */
    592        private Comparable[] generateKeys(int count, String prefix) {
    593            Comparable[] result = new Comparable[count];
    594            String name;
    595            for (int i = 0; i < count; i++) {
    596                name = prefix + (i + 1);
    597                result[i] = name;
    598            }
    599            return result;
    600        }
    601
    602        /**
    603         * Returns a column key.
    604         *
    605         * @param column  the column index.
    606         *
    607         * @return The column key.
    608         */
    609        public Comparable getColumnKey(int column) {
    610            return this.categoryKeys[column];
    611        }
    612
    613        /**
    614         * Returns a column index.
    615         *
    616         * @param columnKey  the column key.
    617         *
    618         * @return The column index.
    619         */
    620        public int getColumnIndex(Comparable columnKey) {
    621            List categories = getCategories();
    622            return categories.indexOf(columnKey);
    623        }
    624
    625        /**
    626         * Returns a row index.
    627         *
    628         * @param rowKey  the row key.
    629         *
    630         * @return The row index.
    631         */
    632        public int getRowIndex(Comparable rowKey) {
    633            List seriesKeys = getSeries();
    634            return seriesKeys.indexOf(rowKey);
    635        }
    636
    637        /**
    638         * Returns a list of the series in the dataset.
    639         * <P>
    640         * Supports the CategoryDataset interface.
    641         *
    642         * @return A list of the series in the dataset.
    643         */
    644        public List getRowKeys() {
    645            // the CategoryDataset interface expects a list of series, but
    646            // we've stored them in an array...
    647            if (this.seriesKeys == null) {
    648                return new java.util.ArrayList();
    649            }
    650            else {
    651                return Collections.unmodifiableList(Arrays.asList(this.seriesKeys));
    652            }
    653        }
    654
    655        /**
    656         * Returns the name of the specified series.
    657         *
    658         * @param row  the index of the required row/series (zero-based).
    659         *
    660         * @return The name of the specified series.
    661         */
    662        public Comparable getRowKey(int row) {
    663            if ((row >= getRowCount()) || (row < 0)) {
    664                throw new IllegalArgumentException(
    665                        "The 'row' argument is out of bounds.");
    666            }
    667            return this.seriesKeys[row];
    668        }
    669
    670        /**
    671         * Returns the number of categories in the dataset.  This method is part of
    672         * the {@link CategoryDataset} interface.
    673         *
    674         * @return The number of categories in the dataset.
    675         */
    676        public int getColumnCount() {
    677            int result = 0;
    678            if (this.startData != null) {
    679                if (getSeriesCount() > 0) {
    680                    result = this.startData[0].length;
    681                }
    682            }
    683            return result;
    684        }
    685
    686        /**
    687         * Returns the number of series in the dataset (possibly zero).
    688         *
    689         * @return The number of series in the dataset.
    690         */
    691        public int getRowCount() {
    692            int result = 0;
    693            if (this.startData != null) {
    694                result = this.startData.length;
    695            }
    696            return result;
    697        }
    698
    699    }