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