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