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
049package org.jfree.data.xy;
050
051import org.jfree.data.DefaultKeyedValues2D;
052import org.jfree.data.DomainInfo;
053import org.jfree.data.Range;
054import org.jfree.data.general.DatasetChangeEvent;
055import org.jfree.data.general.DatasetUtilities;
056import 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 */
071public 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}