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     * CategoryTableXYDataset.java
029     * ---------------------------
030     * (C) Copyright 2004-2008, by Andreas Schroeder and Contributors.
031     *
032     * Original Author:  Andreas Schroeder;
033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
034     *
035     * Changes
036     * -------
037     * 31-Mar-2004 : Version 1 (AS);
038     * 05-May-2004 : Now extends AbstractIntervalXYDataset (DG);
039     * 15-Jul-2004 : Switched interval access method names (DG);
040     * 18-Aug-2004 : Moved from org.jfree.data --> org.jfree.data.xy (DG);
041     * 17-Nov-2004 : Updates required by changes to DomainInfo interface (DG);
042     * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG);
043     * 05-Oct-2005 : Made the interval delegate a dataset change listener (DG);
044     * 02-Feb-2007 : Removed author tags all over JFreeChart sources (DG);
045     * 22-Apr-2008 : Implemented PublicCloneable, and fixed clone() method (DG);
046     *
047     */
048    
049    package org.jfree.data.xy;
050    
051    import org.jfree.data.DefaultKeyedValues2D;
052    import org.jfree.data.DomainInfo;
053    import org.jfree.data.Range;
054    import org.jfree.data.general.DatasetChangeEvent;
055    import org.jfree.data.general.DatasetUtilities;
056    import org.jfree.util.PublicCloneable;
057    
058    /**
059     * An implementation variant of the {@link TableXYDataset} where every series
060     * shares the same x-values (required for generating stacked area charts).
061     * This implementation uses a {@link DefaultKeyedValues2D} Object as backend
062     * implementation and is hence more "category oriented" than the {@link
063     * DefaultTableXYDataset} implementation.
064     * <p>
065     * This implementation provides no means to remove data items yet.
066     * This is due to the lack of such facility in the DefaultKeyedValues2D class.
067     * <p>
068     * This class also implements the {@link IntervalXYDataset} interface, but this
069     * implementation is provisional.
070     */
071    public class CategoryTableXYDataset extends AbstractIntervalXYDataset
072            implements TableXYDataset, IntervalXYDataset, DomainInfo,
073                       PublicCloneable {
074    
075        /**
076         * The backing data structure.
077         */
078        private DefaultKeyedValues2D values;
079    
080        /** A delegate for controlling the interval width. */
081        private IntervalXYDelegate intervalDelegate;
082    
083        /**
084         * Creates a new empty CategoryTableXYDataset.
085         */
086        public CategoryTableXYDataset() {
087            this.values = new DefaultKeyedValues2D(true);
088            this.intervalDelegate = new IntervalXYDelegate(this);
089            addChangeListener(this.intervalDelegate);
090        }
091    
092        /**
093         * Adds a data item to this dataset and sends a {@link DatasetChangeEvent}
094         * to all registered listeners.
095         *
096         * @param x  the x value.
097         * @param y  the y value.
098         * @param seriesName  the name of the series to add the data item.
099         */
100        public void add(double x, double y, String seriesName) {
101            add(new Double(x), new Double(y), seriesName, true);
102        }
103    
104        /**
105         * Adds a data item to this dataset and, if requested, sends a
106         * {@link DatasetChangeEvent} to all registered listeners.
107         *
108         * @param x  the x value.
109         * @param y  the y value.
110         * @param seriesName  the name of the series to add the data item.
111         * @param notify  notify listeners?
112         */
113        public void add(Number x, Number y, String seriesName, boolean notify) {
114            this.values.addValue(y, (Comparable) x, seriesName);
115            if (notify) {
116                fireDatasetChanged();
117            }
118        }
119    
120        /**
121         * Removes a value from the dataset.
122         *
123         * @param x  the x-value.
124         * @param seriesName  the series name.
125         */
126        public void remove(double x, String seriesName) {
127            remove(new Double(x), seriesName, true);
128        }
129    
130        /**
131         * Removes an item from the dataset.
132         *
133         * @param x  the x-value.
134         * @param seriesName  the series name.
135         * @param notify  notify listeners?
136         */
137        public void remove(Number x, String seriesName, boolean notify) {
138            this.values.removeValue((Comparable) x, seriesName);
139            if (notify) {
140                fireDatasetChanged();
141            }
142        }
143    
144    
145        /**
146         * Returns the number of series in the collection.
147         *
148         * @return The series count.
149         */
150        public int getSeriesCount() {
151            return this.values.getColumnCount();
152        }
153    
154        /**
155         * Returns the key for a series.
156         *
157         * @param series  the series index (zero-based).
158         *
159         * @return The key for a series.
160         */
161        public Comparable getSeriesKey(int series) {
162            return this.values.getColumnKey(series);
163        }
164    
165        /**
166         * Returns the number of x values in the dataset.
167         *
168         * @return The item count.
169         */
170        public int getItemCount() {
171            return this.values.getRowCount();
172        }
173    
174        /**
175         * Returns the number of items in the specified series.
176         * Returns the same as {@link CategoryTableXYDataset#getItemCount()}.
177         *
178         * @param series  the series index (zero-based).
179         *
180         * @return The item count.
181         */
182        public int getItemCount(int series) {
183            return getItemCount();  // all series have the same number of items in
184                                    // this dataset
185        }
186    
187        /**
188         * Returns the x-value for the specified series and item.
189         *
190         * @param series  the series index (zero-based).
191         * @param item  the item index (zero-based).
192         *
193         * @return The value.
194         */
195        public Number getX(int series, int item) {
196            return (Number) this.values.getRowKey(item);
197        }
198    
199        /**
200         * Returns the starting X value for the specified series and item.
201         *
202         * @param series  the series index (zero-based).
203         * @param item  the item index (zero-based).
204         *
205         * @return The starting X value.
206         */
207        public Number getStartX(int series, int item) {
208            return this.intervalDelegate.getStartX(series, item);
209        }
210    
211        /**
212         * Returns the ending X value for the specified series and item.
213         *
214         * @param series  the series index (zero-based).
215         * @param item  the item index (zero-based).
216         *
217         * @return The ending X value.
218         */
219        public Number getEndX(int series, int item) {
220            return this.intervalDelegate.getEndX(series, item);
221        }
222    
223        /**
224         * Returns the y-value for the specified series and item.
225         *
226         * @param series  the series index (zero-based).
227         * @param item  the item index (zero-based).
228         *
229         * @return The y value (possibly <code>null</code>).
230         */
231        public Number getY(int series, int item) {
232            return this.values.getValue(item, series);
233        }
234    
235        /**
236         * Returns the starting Y value for the specified series and item.
237         *
238         * @param series  the series index (zero-based).
239         * @param item  the item index (zero-based).
240         *
241         * @return The starting Y value.
242         */
243        public Number getStartY(int series, int item) {
244            return getY(series, item);
245        }
246    
247        /**
248         * Returns the ending Y value for the specified series and item.
249         *
250         * @param series  the series index (zero-based).
251         * @param item  the item index (zero-based).
252         *
253         * @return The ending Y value.
254         */
255        public Number getEndY(int series, int item) {
256            return getY(series, item);
257        }
258    
259        /**
260         * Returns the minimum x-value in the dataset.
261         *
262         * @param includeInterval  a flag that determines whether or not the
263         *                         x-interval is taken into account.
264         *
265         * @return The minimum value.
266         */
267        public double getDomainLowerBound(boolean includeInterval) {
268            return this.intervalDelegate.getDomainLowerBound(includeInterval);
269        }
270    
271        /**
272         * Returns the maximum x-value in the dataset.
273         *
274         * @param includeInterval  a flag that determines whether or not the
275         *                         x-interval is taken into account.
276         *
277         * @return The maximum value.
278         */
279        public double getDomainUpperBound(boolean includeInterval) {
280            return this.intervalDelegate.getDomainUpperBound(includeInterval);
281        }
282    
283        /**
284         * Returns the range of the values in this dataset's domain.
285         *
286         * @param includeInterval  a flag that determines whether or not the
287         *                         x-interval is taken into account.
288         *
289         * @return The range.
290         */
291        public Range getDomainBounds(boolean includeInterval) {
292            if (includeInterval) {
293                return this.intervalDelegate.getDomainBounds(includeInterval);
294            }
295            else {
296                return DatasetUtilities.iterateDomainBounds(this, includeInterval);
297            }
298        }
299    
300        /**
301         * Returns the interval position factor.
302         *
303         * @return The interval position factor.
304         */
305        public double getIntervalPositionFactor() {
306            return this.intervalDelegate.getIntervalPositionFactor();
307        }
308    
309        /**
310         * Sets the interval position factor. Must be between 0.0 and 1.0 inclusive.
311         * If the factor is 0.5, the gap is in the middle of the x values. If it
312         * is lesser than 0.5, the gap is farther to the left and if greater than
313         * 0.5 it gets farther to the right.
314         *
315         * @param d  the new interval position factor.
316         */
317        public void setIntervalPositionFactor(double d) {
318            this.intervalDelegate.setIntervalPositionFactor(d);
319            fireDatasetChanged();
320        }
321    
322        /**
323         * Returns the full interval width.
324         *
325         * @return The interval width to use.
326         */
327        public double getIntervalWidth() {
328            return this.intervalDelegate.getIntervalWidth();
329        }
330    
331        /**
332         * Sets the interval width to a fixed value, and sends a
333         * {@link DatasetChangeEvent} to all registered listeners.
334         *
335         * @param d  the new interval width (must be > 0).
336         */
337        public void setIntervalWidth(double d) {
338            this.intervalDelegate.setFixedIntervalWidth(d);
339            fireDatasetChanged();
340        }
341    
342        /**
343         * Returns whether the interval width is automatically calculated or not.
344         *
345         * @return whether the width is automatically calculated or not.
346         */
347        public boolean isAutoWidth() {
348            return this.intervalDelegate.isAutoWidth();
349        }
350    
351        /**
352         * Sets the flag that indicates whether the interval width is automatically
353         * calculated or not.
354         *
355         * @param b  the flag.
356         */
357        public void setAutoWidth(boolean b) {
358            this.intervalDelegate.setAutoWidth(b);
359            fireDatasetChanged();
360        }
361    
362        /**
363         * Tests this dataset for equality with an arbitrary object.
364         *
365         * @param obj  the object (<code>null</code> permitted).
366         *
367         * @return A boolean.
368         */
369        public boolean equals(Object obj) {
370            if (!(obj instanceof CategoryTableXYDataset)) {
371                return false;
372            }
373            CategoryTableXYDataset that = (CategoryTableXYDataset) obj;
374            if (!this.intervalDelegate.equals(that.intervalDelegate)) {
375                return false;
376            }
377            if (!this.values.equals(that.values)) {
378                return false;
379            }
380            return true;
381        }
382    
383        /**
384         * Returns an independent copy of this dataset.
385         *
386         * @return A clone.
387         *
388         * @throws CloneNotSupportedException if there is some reason that cloning
389         *     cannot be performed.
390         */
391        public Object clone() throws CloneNotSupportedException {
392            CategoryTableXYDataset clone = (CategoryTableXYDataset) super.clone();
393            clone.values = (DefaultKeyedValues2D) this.values.clone();
394            clone.intervalDelegate = new IntervalXYDelegate(clone);
395            // need to configure the intervalDelegate to match the original
396            clone.intervalDelegate.setFixedIntervalWidth(getIntervalWidth());
397            clone.intervalDelegate.setAutoWidth(isAutoWidth());
398            clone.intervalDelegate.setIntervalPositionFactor(
399                    getIntervalPositionFactor());
400            return clone;
401        }
402    
403    }