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