001/* ===========================================================
002 * Orson Charts : a 3D chart library for the Java(tm) platform
003 * ===========================================================
004 * 
005 * (C)opyright 2013-2022, by David Gilbert.  All rights reserved.
006 * 
007 * https://github.com/jfree/orson-charts
008 * 
009 * This program is free software: you can redistribute it and/or modify
010 * it under the terms of the GNU General Public License as published by
011 * the Free Software Foundation, either version 3 of the License, or
012 * (at your option) any later version.
013 *
014 * This program is distributed in the hope that it will be useful,
015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
017 * GNU General Public License for more details.
018 *
019 * You should have received a copy of the GNU General Public License
020 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
021 * 
022 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
023 * Other names may be trademarks of their respective owners.]
024 * 
025 * If you do not wish to be bound by the terms of the GPL, an alternative
026 * commercial license can be purchased.  For details, please see visit the
027 * Orson Charts home page:
028 * 
029 * http://www.object-refinery.com/orsoncharts/index.html
030 * 
031 */
032
033package org.jfree.chart3d.data.xyz;
034
035import java.io.Serializable;
036import java.util.ArrayList;
037import java.util.List;
038
039import org.jfree.chart3d.internal.Args;
040import org.jfree.chart3d.data.AbstractDataset3D;
041import org.jfree.chart3d.data.Dataset3DChangeEvent;
042import org.jfree.chart3d.data.JSONUtils;
043import org.jfree.chart3d.data.Series3DChangeEvent;
044import org.jfree.chart3d.data.Series3DChangeListener;
045import org.jfree.chart3d.internal.ObjectUtils;
046import org.jfree.chart3d.plot.XYZPlot;
047import org.jfree.chart3d.renderer.xyz.XYZRenderer;
048
049/**
050 * A collection of {@link XYZSeries} objects (implements the {@link XYZDataset}
051 * interface so that it can be used as a source of data for an 
052 * {@link XYZRenderer} on an {@link XYZPlot}).
053 * <br><br>
054 * NOTE: This class is serializable, but the serialization format is subject 
055 * to change in future releases and should not be relied upon for persisting 
056 * instances of this class. 
057 */
058@SuppressWarnings("serial")
059public class XYZSeriesCollection<S extends Comparable<S>> 
060        extends AbstractDataset3D 
061        implements XYZDataset<S>, Series3DChangeListener, Serializable {
062
063    /** Storage for the data series. */
064    private final List<XYZSeries<S>> series;
065
066    /**
067     * Creates a new (empty) {@code XYZSeriesCollection} instance.
068     */
069    public XYZSeriesCollection() {
070        this.series = new ArrayList<>();
071    }
072
073    /**
074     * Returns the number of series in the collection.
075     * 
076     * @return The number of series in the collection. 
077     */
078    @Override
079    public int getSeriesCount() {
080        return this.series.size();
081    }
082    
083    /**
084     * Returns the index of the series with the specified key, or 
085     * {@code -1} if there is no series with the specified key.
086     * 
087     * @param key  the key ({@code null} not permitted).
088     * 
089     * @return The series index or {@code -1}. 
090     */
091    @Override
092    public int getSeriesIndex(S key) {
093        Args.nullNotPermitted(key, "key");
094        return getSeriesKeys().indexOf(key);
095    }
096
097    /**
098     * Returns a new list containing all the series keys.  Modifying this list 
099     * will have no impact on the {@code XYZSeriesCollection} instance.
100     * 
101     * @return A list containing the series keys (possibly empty, but never 
102     *     {@code null}).
103     */
104    @Override
105    public List<S> getSeriesKeys() {
106        List<S> result = new ArrayList<>();
107        for (XYZSeries<S> s : this.series) {
108            result.add(s.getKey());
109        }
110        return result;
111    }
112    
113    /**
114     * Returns the key for the specified series.
115     * 
116     * @param seriesIndex  the series index.
117     * 
118     * @return The series key.
119     * 
120     * @since 1.3
121     */
122    @Override
123    public S getSeriesKey(int seriesIndex) {
124        return getSeries(seriesIndex).getKey();
125    }
126
127    /**
128     * Adds a series to the collection (note that the series key must be
129     * unique within the collection).  The collection will automatically
130     * register to receive change events from the series, and fire a 
131     * {@link Dataset3DChangeEvent} whenever the data in the series changes.
132     * 
133     * @param series  the series ({@code null} not permitted). 
134     */
135    public void add(XYZSeries<S> series) {
136        Args.nullNotPermitted(series, "series");
137        if (getSeriesIndex(series.getKey()) >= 0) {
138            throw new IllegalArgumentException("Another series with the same key already exists within the collection.");
139        }
140        this.series.add(series);
141        series.addChangeListener(this);
142        fireDatasetChanged();
143    }
144    
145    /**
146     * Removes a series from the collection and sends a
147     * {@link Dataset3DChangeEvent} to all registered listeners.
148     * 
149     * @param seriesIndex  the series index.
150     * 
151     * @since 1.6
152     */
153    public void remove(int seriesIndex) {
154        XYZSeries s = getSeries(seriesIndex);
155        remove(s);
156    }
157    
158    /**
159     * Removes a series from the collection and sends a
160     * {@link Dataset3DChangeEvent} to all registered listeners.  If the series
161     * is not part of the collection, this method does nothing.
162     *
163     * @param series  the series ({@code null} not permitted).
164     * 
165     * @since 1.6
166     */
167    public void remove(XYZSeries series) {
168        Args.nullNotPermitted(series, "series");
169        if (this.series.contains(series)) {
170            series.removeChangeListener(this);
171            this.series.remove(series);
172            fireDatasetChanged();
173        }
174    }
175
176    /**
177     * Removes all the series from the collection and sends a
178     * {@link Dataset3DChangeEvent} to all registered listeners.  If the
179     * collection is already empty, this method does nothing.
180     */
181    public void removeAll() {
182        if (!this.series.isEmpty()) {
183            for (XYZSeries s : this.series) {
184                s.removeChangeListener(this);
185            }
186            this.series.clear();
187            fireDatasetChanged();
188        }
189    }
190
191    /**
192     * Returns the series with the specified index.
193     * 
194     * @param index  the series index.
195     * 
196     * @return The series.
197     * 
198     * @since 1.2
199     */
200    public XYZSeries<S> getSeries(int index) {
201        Args.checkArrayBounds(index, "index", this.series.size());
202        return this.series.get(index);
203    }
204    
205    /**
206     * Returns the series with the specified key, or {@code null} if 
207     * there is no such series.
208     * 
209     * @param key  the key ({@code null} not permitted).
210     * 
211     * @return The series. 
212     * 
213     * @since 1.2
214     */
215    public XYZSeries getSeries(Comparable<?> key) {
216        Args.nullNotPermitted(key, "key");
217        for (XYZSeries s : this.series) {
218            if (s.getKey().equals(key)) {
219                return s;
220            }
221        }
222        return null;
223    }
224
225    /**
226     * Returns the number of items in the specified series.
227     * 
228     * @param seriesIndex  the series index.
229     * 
230     * @return The number of items in the specified series. 
231     */
232    @Override
233    public int getItemCount(int seriesIndex) {
234        XYZSeries s = this.series.get(seriesIndex);
235        return s.getItemCount();
236    }
237
238    /**
239     * Returns the x-value for one item in a series.
240     * 
241     * @param seriesIndex  the series index.
242     * @param itemIndex  the item index.
243     * 
244     * @return The x-value. 
245     */
246    @Override
247    public double getX(int seriesIndex, int itemIndex) {
248        XYZSeries s = this.series.get(seriesIndex);
249        return s.getXValue(itemIndex);
250    }
251
252    /**
253     * Returns the y-value for one item in a series.
254     * 
255     * @param seriesIndex  the series index.
256     * @param itemIndex  the item index.
257     * 
258     * @return The y-value. 
259     */
260    @Override
261    public double getY(int seriesIndex, int itemIndex) {
262        XYZSeries s = this.series.get(seriesIndex);
263        return s.getYValue(itemIndex);
264    }
265
266    /**
267     * Returns the z-value for one item in a series.
268     * 
269     * @param seriesIndex  the series index.
270     * @param itemIndex  the item index.
271     * 
272     * @return The z-value. 
273     */
274    @Override
275    public double getZ(int seriesIndex, int itemIndex) {
276        XYZSeries s = this.series.get(seriesIndex);
277        return s.getZValue(itemIndex);
278    }
279
280    /**
281     * Called when an observed series changes in some way.
282     *
283     * @param event  information about the change.
284     * 
285     * @since 1.6
286     */
287    @Override
288    public void seriesChanged(Series3DChangeEvent event) {
289        fireDatasetChanged();
290    }
291
292    /**
293     * Tests this dataset for equality with an arbitrary object.
294     * 
295     * @param obj  the object ({@code null} not permitted).
296     * 
297     * @return A boolean. 
298     */
299    @Override
300    public boolean equals(Object obj) {
301        if (obj == this) {
302            return true;
303        }
304        if (!(obj instanceof XYZSeriesCollection)) {
305            return false;
306        }
307        XYZSeriesCollection that = (XYZSeriesCollection) obj;
308        if (!this.series.equals(that.series)) {
309            return false;
310        }
311        return true;
312    }
313
314    @Override
315    public int hashCode() {
316        int hash = 5;
317        hash = 59 * hash + ObjectUtils.hashCode(this.series);
318        return hash;
319    }
320    
321    /**
322     * Returns a string representation of this instance, primarily for 
323     * debugging purposes.
324     * <br><br>
325     * Implementation note: the current implementation (which is subject to 
326     * change) writes the dataset in JSON format using 
327     * {@link JSONUtils#writeXYZDataset(org.jfree.chart3d.data.xyz.XYZDataset)}.
328     * 
329     * @return A string. 
330     */
331    @Override
332    public String toString() {
333        return JSONUtils.writeXYZDataset(this);
334    }
335
336}