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    }