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 * DefaultXYDataset.java
029 * ---------------------
030 * (C) Copyright 2006, 2007, by Object Refinery Limited and Contributors.
031 *
032 * Original Author: David Gilbert (for Object Refinery Limited);
033 * Contributor(s): -;
034 *
035 * Changes
036 * -------
037 * 06-Jul-2006 : Version 1 (DG);
038 * 02-Nov-2006 : Fixed a problem with adding a new series with the same key
039 * as an existing series (see bug 1589392) (DG);
040 * 25-Jan-2007 : Implemented PublicCloneable (DG);
041 *
042 */
043
044 package org.jfree.data.xy;
045
046 import java.util.ArrayList;
047 import java.util.Arrays;
048 import java.util.List;
049
050 import org.jfree.data.DomainOrder;
051 import org.jfree.data.general.DatasetChangeEvent;
052 import org.jfree.util.PublicCloneable;
053
054 /**
055 * A default implementation of the {@link XYDataset} interface that stores
056 * data values in arrays of double primitives.
057 *
058 * @since 1.0.2
059 */
060 public class DefaultXYDataset extends AbstractXYDataset
061 implements XYDataset, PublicCloneable {
062
063 /**
064 * Storage for the series keys. This list must be kept in sync with the
065 * seriesList.
066 */
067 private List seriesKeys;
068
069 /**
070 * Storage for the series in the dataset. We use a list because the
071 * order of the series is significant. This list must be kept in sync
072 * with the seriesKeys list.
073 */
074 private List seriesList;
075
076 /**
077 * Creates a new <code>DefaultXYDataset</code> instance, initially
078 * containing no data.
079 */
080 public DefaultXYDataset() {
081 this.seriesKeys = new java.util.ArrayList();
082 this.seriesList = new java.util.ArrayList();
083 }
084
085 /**
086 * Returns the number of series in the dataset.
087 *
088 * @return The series count.
089 */
090 public int getSeriesCount() {
091 return this.seriesList.size();
092 }
093
094 /**
095 * Returns the key for a series.
096 *
097 * @param series the series index (in the range <code>0</code> to
098 * <code>getSeriesCount() - 1</code>).
099 *
100 * @return The key for the series.
101 *
102 * @throws IllegalArgumentException if <code>series</code> is not in the
103 * specified range.
104 */
105 public Comparable getSeriesKey(int series) {
106 if ((series < 0) || (series >= getSeriesCount())) {
107 throw new IllegalArgumentException("Series index out of bounds");
108 }
109 return (Comparable) this.seriesKeys.get(series);
110 }
111
112 /**
113 * Returns the index of the series with the specified key, or -1 if there
114 * is no such series in the dataset.
115 *
116 * @param seriesKey the series key (<code>null</code> permitted).
117 *
118 * @return The index, or -1.
119 */
120 public int indexOf(Comparable seriesKey) {
121 return this.seriesKeys.indexOf(seriesKey);
122 }
123
124 /**
125 * Returns the order of the domain (x-) values in the dataset. In this
126 * implementation, we cannot guarantee that the x-values are ordered, so
127 * this method returns <code>DomainOrder.NONE</code>.
128 *
129 * @return <code>DomainOrder.NONE</code>.
130 */
131 public DomainOrder getDomainOrder() {
132 return DomainOrder.NONE;
133 }
134
135 /**
136 * Returns the number of items in the specified series.
137 *
138 * @param series the series index (in the range <code>0</code> to
139 * <code>getSeriesCount() - 1</code>).
140 *
141 * @return The item count.
142 *
143 * @throws IllegalArgumentException if <code>series</code> is not in the
144 * specified range.
145 */
146 public int getItemCount(int series) {
147 if ((series < 0) || (series >= getSeriesCount())) {
148 throw new IllegalArgumentException("Series index out of bounds");
149 }
150 double[][] seriesArray = (double[][]) this.seriesList.get(series);
151 return seriesArray[0].length;
152 }
153
154 /**
155 * Returns the x-value for an item within a series.
156 *
157 * @param series the series index (in the range <code>0</code> to
158 * <code>getSeriesCount() - 1</code>).
159 * @param item the item index (in the range <code>0</code> to
160 * <code>getItemCount(series)</code>).
161 *
162 * @return The x-value.
163 *
164 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
165 * within the specified range.
166 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
167 * within the specified range.
168 *
169 * @see #getX(int, int)
170 */
171 public double getXValue(int series, int item) {
172 double[][] seriesData = (double[][]) this.seriesList.get(series);
173 return seriesData[0][item];
174 }
175
176 /**
177 * Returns the x-value for an item within a series.
178 *
179 * @param series the series index (in the range <code>0</code> to
180 * <code>getSeriesCount() - 1</code>).
181 * @param item the item index (in the range <code>0</code> to
182 * <code>getItemCount(series)</code>).
183 *
184 * @return The x-value.
185 *
186 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
187 * within the specified range.
188 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
189 * within the specified range.
190 *
191 * @see #getXValue(int, int)
192 */
193 public Number getX(int series, int item) {
194 return new Double(getXValue(series, item));
195 }
196
197 /**
198 * Returns the y-value for an item within a series.
199 *
200 * @param series the series index (in the range <code>0</code> to
201 * <code>getSeriesCount() - 1</code>).
202 * @param item the item index (in the range <code>0</code> to
203 * <code>getItemCount(series)</code>).
204 *
205 * @return The y-value.
206 *
207 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
208 * within the specified range.
209 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
210 * within the specified range.
211 *
212 * @see #getY(int, int)
213 */
214 public double getYValue(int series, int item) {
215 double[][] seriesData = (double[][]) this.seriesList.get(series);
216 return seriesData[1][item];
217 }
218
219 /**
220 * Returns the y-value for an item within a series.
221 *
222 * @param series the series index (in the range <code>0</code> to
223 * <code>getSeriesCount() - 1</code>).
224 * @param item the item index (in the range <code>0</code> to
225 * <code>getItemCount(series)</code>).
226 *
227 * @return The y-value.
228 *
229 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
230 * within the specified range.
231 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
232 * within the specified range.
233 *
234 * @see #getX(int, int)
235 */
236 public Number getY(int series, int item) {
237 return new Double(getYValue(series, item));
238 }
239
240 /**
241 * Adds a series or if a series with the same key already exists replaces
242 * the data for that series, then sends a {@link DatasetChangeEvent} to
243 * all registered listeners.
244 *
245 * @param seriesKey the series key (<code>null</code> not permitted).
246 * @param data the data (must be an array with length 2, containing two
247 * arrays of equal length, the first containing the x-values and the
248 * second containing the y-values).
249 */
250 public void addSeries(Comparable seriesKey, double[][] data) {
251 if (seriesKey == null) {
252 throw new IllegalArgumentException(
253 "The 'seriesKey' cannot be null.");
254 }
255 if (data == null) {
256 throw new IllegalArgumentException("The 'data' is null.");
257 }
258 if (data.length != 2) {
259 throw new IllegalArgumentException(
260 "The 'data' array must have length == 2.");
261 }
262 if (data[0].length != data[1].length) {
263 throw new IllegalArgumentException(
264 "The 'data' array must contain two arrays with equal length.");
265 }
266 int seriesIndex = indexOf(seriesKey);
267 if (seriesIndex == -1) { // add a new series
268 this.seriesKeys.add(seriesKey);
269 this.seriesList.add(data);
270 }
271 else { // replace an existing series
272 this.seriesList.remove(seriesIndex);
273 this.seriesList.add(seriesIndex, data);
274 }
275 notifyListeners(new DatasetChangeEvent(this, this));
276 }
277
278 /**
279 * Removes a series from the dataset, then sends a
280 * {@link DatasetChangeEvent} to all registered listeners.
281 *
282 * @param seriesKey the series key (<code>null</code> not permitted).
283 *
284 */
285 public void removeSeries(Comparable seriesKey) {
286 int seriesIndex = indexOf(seriesKey);
287 if (seriesIndex >= 0) {
288 this.seriesKeys.remove(seriesIndex);
289 this.seriesList.remove(seriesIndex);
290 notifyListeners(new DatasetChangeEvent(this, this));
291 }
292 }
293
294 /**
295 * Tests this <code>DefaultXYDataset</code> instance for equality with an
296 * arbitrary object. This method returns <code>true</code> if and only if:
297 * <ul>
298 * <li><code>obj</code> is not <code>null</code>;</li>
299 * <li><code>obj</code> is an instance of
300 * <code>DefaultXYDataset</code>;</li>
301 * <li>both datasets have the same number of series, each containing
302 * exactly the same values.</li>
303 * </ul>
304 *
305 * @param obj the object (<code>null</code> permitted).
306 *
307 * @return A boolean.
308 */
309 public boolean equals(Object obj) {
310 if (obj == this) {
311 return true;
312 }
313 if (!(obj instanceof DefaultXYDataset)) {
314 return false;
315 }
316 DefaultXYDataset that = (DefaultXYDataset) obj;
317 if (!this.seriesKeys.equals(that.seriesKeys)) {
318 return false;
319 }
320 for (int i = 0; i < this.seriesList.size(); i++) {
321 double[][] d1 = (double[][]) this.seriesList.get(i);
322 double[][] d2 = (double[][]) that.seriesList.get(i);
323 double[] d1x = d1[0];
324 double[] d2x = d2[0];
325 if (!Arrays.equals(d1x, d2x)) {
326 return false;
327 }
328 double[] d1y = d1[1];
329 double[] d2y = d2[1];
330 if (!Arrays.equals(d1y, d2y)) {
331 return false;
332 }
333 }
334 return true;
335 }
336
337 /**
338 * Returns a hash code for this instance.
339 *
340 * @return A hash code.
341 */
342 public int hashCode() {
343 int result;
344 result = this.seriesKeys.hashCode();
345 result = 29 * result + this.seriesList.hashCode();
346 return result;
347 }
348
349 /**
350 * Creates an independent copy of this dataset.
351 *
352 * @return The cloned dataset.
353 *
354 * @throws CloneNotSupportedException if there is a problem cloning the
355 * dataset (for instance, if a non-cloneable object is used for a
356 * series key).
357 */
358 public Object clone() throws CloneNotSupportedException {
359 DefaultXYDataset clone = (DefaultXYDataset) super.clone();
360 clone.seriesKeys = new java.util.ArrayList(this.seriesKeys);
361 clone.seriesList = new ArrayList(this.seriesList.size());
362 for (int i = 0; i < this.seriesList.size(); i++) {
363 double[][] data = (double[][]) this.seriesList.get(i);
364 double[] x = data[0];
365 double[] y = data[1];
366 double[] xx = new double[x.length];
367 double[] yy = new double[y.length];
368 System.arraycopy(x, 0, xx, 0, x.length);
369 System.arraycopy(y, 0, yy, 0, y.length);
370 clone.seriesList.add(i, new double[][] {xx, yy});
371 }
372 return clone;
373 }
374
375 }