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 * DefaultIntervalXYDataset.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 * 23-Oct-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 * 28-Nov-2006 : New override for clone() (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.general.DatasetChangeEvent;
051
052 /**
053 * A dataset that defines a range (interval) for both the x-values and the
054 * y-values. This implementation uses six arrays to store the x, start-x,
055 * end-x, y, start-y and end-y values.
056 * <br><br>
057 * An alternative implementation of the {@link IntervalXYDataset} interface
058 * is provided by the {@link XYIntervalSeriesCollection} class.
059 *
060 * @since 1.0.3
061 */
062 public class DefaultIntervalXYDataset extends AbstractIntervalXYDataset {
063
064 /**
065 * Storage for the series keys. This list must be kept in sync with the
066 * seriesList.
067 */
068 private List seriesKeys;
069
070 /**
071 * Storage for the series in the dataset. We use a list because the
072 * order of the series is significant. This list must be kept in sync
073 * with the seriesKeys list.
074 */
075 private List seriesList;
076
077 /**
078 * Creates a new <code>DefaultIntervalXYDataset</code> instance, initially
079 * containing no data.
080 */
081 public DefaultIntervalXYDataset() {
082 this.seriesKeys = new java.util.ArrayList();
083 this.seriesList = new java.util.ArrayList();
084 }
085
086 /**
087 * Returns the number of series in the dataset.
088 *
089 * @return The series count.
090 */
091 public int getSeriesCount() {
092 return this.seriesList.size();
093 }
094
095 /**
096 * Returns the key for a series.
097 *
098 * @param series the series index (in the range <code>0</code> to
099 * <code>getSeriesCount() - 1</code>).
100 *
101 * @return The key for the series.
102 *
103 * @throws IllegalArgumentException if <code>series</code> is not in the
104 * specified range.
105 */
106 public Comparable getSeriesKey(int series) {
107 if ((series < 0) || (series >= getSeriesCount())) {
108 throw new IllegalArgumentException("Series index out of bounds");
109 }
110 return (Comparable) this.seriesKeys.get(series);
111 }
112
113 /**
114 * Returns the number of items in the specified series.
115 *
116 * @param series the series index (in the range <code>0</code> to
117 * <code>getSeriesCount() - 1</code>).
118 *
119 * @return The item count.
120 *
121 * @throws IllegalArgumentException if <code>series</code> is not in the
122 * specified range.
123 */
124 public int getItemCount(int series) {
125 if ((series < 0) || (series >= getSeriesCount())) {
126 throw new IllegalArgumentException("Series index out of bounds");
127 }
128 double[][] seriesArray = (double[][]) this.seriesList.get(series);
129 return seriesArray[0].length;
130 }
131
132 /**
133 * Returns the x-value for an item within a series.
134 *
135 * @param series the series index (in the range <code>0</code> to
136 * <code>getSeriesCount() - 1</code>).
137 * @param item the item index (in the range <code>0</code> to
138 * <code>getItemCount(series)</code>).
139 *
140 * @return The x-value.
141 *
142 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
143 * within the specified range.
144 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
145 * within the specified range.
146 *
147 * @see #getX(int, int)
148 */
149 public double getXValue(int series, int item) {
150 double[][] seriesData = (double[][]) this.seriesList.get(series);
151 return seriesData[0][item];
152 }
153
154 /**
155 * Returns the y-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 y-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 #getY(int, int)
170 */
171 public double getYValue(int series, int item) {
172 double[][] seriesData = (double[][]) this.seriesList.get(series);
173 return seriesData[3][item];
174 }
175
176 /**
177 * Returns the starting 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 starting 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 #getStartX(int, int)
192 */
193 public double getStartXValue(int series, int item) {
194 double[][] seriesData = (double[][]) this.seriesList.get(series);
195 return seriesData[1][item];
196 }
197
198 /**
199 * Returns the ending x-value for an item within a series.
200 *
201 * @param series the series index (in the range <code>0</code> to
202 * <code>getSeriesCount() - 1</code>).
203 * @param item the item index (in the range <code>0</code> to
204 * <code>getItemCount(series)</code>).
205 *
206 * @return The ending x-value.
207 *
208 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
209 * within the specified range.
210 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
211 * within the specified range.
212 *
213 * @see #getEndX(int, int)
214 */
215 public double getEndXValue(int series, int item) {
216 double[][] seriesData = (double[][]) this.seriesList.get(series);
217 return seriesData[2][item];
218 }
219
220 /**
221 * Returns the starting y-value for an item within a series.
222 *
223 * @param series the series index (in the range <code>0</code> to
224 * <code>getSeriesCount() - 1</code>).
225 * @param item the item index (in the range <code>0</code> to
226 * <code>getItemCount(series)</code>).
227 *
228 * @return The starting y-value.
229 *
230 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
231 * within the specified range.
232 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
233 * within the specified range.
234 *
235 * @see #getStartY(int, int)
236 */
237 public double getStartYValue(int series, int item) {
238 double[][] seriesData = (double[][]) this.seriesList.get(series);
239 return seriesData[4][item];
240 }
241
242 /**
243 * Returns the ending y-value for an item within a series.
244 *
245 * @param series the series index (in the range <code>0</code> to
246 * <code>getSeriesCount() - 1</code>).
247 * @param item the item index (in the range <code>0</code> to
248 * <code>getItemCount(series)</code>).
249 *
250 * @return The ending y-value.
251 *
252 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
253 * within the specified range.
254 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
255 * within the specified range.
256 *
257 * @see #getEndY(int, int)
258 */
259 public double getEndYValue(int series, int item) {
260 double[][] seriesData = (double[][]) this.seriesList.get(series);
261 return seriesData[5][item];
262 }
263
264 /**
265 * Returns the ending x-value for an item within a series.
266 *
267 * @param series the series index (in the range <code>0</code> to
268 * <code>getSeriesCount() - 1</code>).
269 * @param item the item index (in the range <code>0</code> to
270 * <code>getItemCount(series)</code>).
271 *
272 * @return The ending x-value.
273 *
274 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
275 * within the specified range.
276 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
277 * within the specified range.
278 *
279 * @see #getEndXValue(int, int)
280 */
281 public Number getEndX(int series, int item) {
282 return new Double(getEndXValue(series, item));
283 }
284
285 /**
286 * Returns the ending y-value for an item within a series.
287 *
288 * @param series the series index (in the range <code>0</code> to
289 * <code>getSeriesCount() - 1</code>).
290 * @param item the item index (in the range <code>0</code> to
291 * <code>getItemCount(series)</code>).
292 *
293 * @return The ending y-value.
294 *
295 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
296 * within the specified range.
297 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
298 * within the specified range.
299 *
300 * @see #getEndYValue(int, int)
301 */
302 public Number getEndY(int series, int item) {
303 return new Double(getEndYValue(series, item));
304 }
305
306 /**
307 * Returns the starting x-value for an item within a series.
308 *
309 * @param series the series index (in the range <code>0</code> to
310 * <code>getSeriesCount() - 1</code>).
311 * @param item the item index (in the range <code>0</code> to
312 * <code>getItemCount(series)</code>).
313 *
314 * @return The starting x-value.
315 *
316 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
317 * within the specified range.
318 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
319 * within the specified range.
320 *
321 * @see #getStartXValue(int, int)
322 */
323 public Number getStartX(int series, int item) {
324 return new Double(getStartXValue(series, item));
325 }
326
327 /**
328 * Returns the starting y-value for an item within a series.
329 *
330 * @param series the series index (in the range <code>0</code> to
331 * <code>getSeriesCount() - 1</code>).
332 * @param item the item index (in the range <code>0</code> to
333 * <code>getItemCount(series)</code>).
334 *
335 * @return The starting y-value.
336 *
337 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
338 * within the specified range.
339 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
340 * within the specified range.
341 *
342 * @see #getStartYValue(int, int)
343 */
344 public Number getStartY(int series, int item) {
345 return new Double(getStartYValue(series, item));
346 }
347
348 /**
349 * Returns the x-value for an item within a series.
350 *
351 * @param series the series index (in the range <code>0</code> to
352 * <code>getSeriesCount() - 1</code>).
353 * @param item the item index (in the range <code>0</code> to
354 * <code>getItemCount(series)</code>).
355 *
356 * @return The x-value.
357 *
358 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
359 * within the specified range.
360 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
361 * within the specified range.
362 *
363 * @see #getXValue(int, int)
364 */
365 public Number getX(int series, int item) {
366 return new Double(getXValue(series, item));
367 }
368
369 /**
370 * Returns the y-value for an item within a series.
371 *
372 * @param series the series index (in the range <code>0</code> to
373 * <code>getSeriesCount() - 1</code>).
374 * @param item the item index (in the range <code>0</code> to
375 * <code>getItemCount(series)</code>).
376 *
377 * @return The y-value.
378 *
379 * @throws ArrayIndexOutOfBoundsException if <code>series</code> is not
380 * within the specified range.
381 * @throws ArrayIndexOutOfBoundsException if <code>item</code> is not
382 * within the specified range.
383 *
384 * @see #getYValue(int, int)
385 */
386 public Number getY(int series, int item) {
387 return new Double(getYValue(series, item));
388 }
389
390 /**
391 * Adds a series or if a series with the same key already exists replaces
392 * the data for that series, then sends a {@link DatasetChangeEvent} to
393 * all registered listeners.
394 *
395 * @param seriesKey the series key (<code>null</code> not permitted).
396 * @param data the data (must be an array with length 6, containing six
397 * arrays of equal length, the first containing the x-values and the
398 * second containing the y-values).
399 */
400 public void addSeries(Comparable seriesKey, double[][] data) {
401 if (seriesKey == null) {
402 throw new IllegalArgumentException(
403 "The 'seriesKey' cannot be null.");
404 }
405 if (data == null) {
406 throw new IllegalArgumentException("The 'data' is null.");
407 }
408 if (data.length != 6) {
409 throw new IllegalArgumentException(
410 "The 'data' array must have length == 6.");
411 }
412 int length = data[0].length;
413 if (length != data[1].length || length != data[2].length
414 || length != data[3].length || length != data[4].length
415 || length != data[5].length) {
416 throw new IllegalArgumentException(
417 "The 'data' array must contain two arrays with equal length.");
418 }
419 int seriesIndex = indexOf(seriesKey);
420 if (seriesIndex == -1) { // add a new series
421 this.seriesKeys.add(seriesKey);
422 this.seriesList.add(data);
423 }
424 else { // replace an existing series
425 this.seriesList.remove(seriesIndex);
426 this.seriesList.add(seriesIndex, data);
427 }
428 notifyListeners(new DatasetChangeEvent(this, this));
429 }
430
431 /**
432 * Tests this <code>DefaultIntervalXYDataset</code> instance for equality
433 * with an arbitrary object. This method returns <code>true</code> if and
434 * only if:
435 * <ul>
436 * <li><code>obj</code> is not <code>null</code>;</li>
437 * <li><code>obj</code> is an instance of
438 * <code>DefaultIntervalXYDataset</code>;</li>
439 * <li>both datasets have the same number of series, each containing
440 * exactly the same values.</li>
441 * </ul>
442 *
443 * @param obj the object (<code>null</code> permitted).
444 *
445 * @return A boolean.
446 */
447 public boolean equals(Object obj) {
448 if (obj == this) {
449 return true;
450 }
451 if (!(obj instanceof DefaultIntervalXYDataset)) {
452 return false;
453 }
454 DefaultIntervalXYDataset that = (DefaultIntervalXYDataset) obj;
455 if (!this.seriesKeys.equals(that.seriesKeys)) {
456 return false;
457 }
458 for (int i = 0; i < this.seriesList.size(); i++) {
459 double[][] d1 = (double[][]) this.seriesList.get(i);
460 double[][] d2 = (double[][]) that.seriesList.get(i);
461 double[] d1x = d1[0];
462 double[] d2x = d2[0];
463 if (!Arrays.equals(d1x, d2x)) {
464 return false;
465 }
466 double[] d1xs = d1[1];
467 double[] d2xs = d2[1];
468 if (!Arrays.equals(d1xs, d2xs)) {
469 return false;
470 }
471 double[] d1xe = d1[2];
472 double[] d2xe = d2[2];
473 if (!Arrays.equals(d1xe, d2xe)) {
474 return false;
475 }
476 double[] d1y = d1[3];
477 double[] d2y = d2[3];
478 if (!Arrays.equals(d1y, d2y)) {
479 return false;
480 }
481 double[] d1ys = d1[4];
482 double[] d2ys = d2[4];
483 if (!Arrays.equals(d1ys, d2ys)) {
484 return false;
485 }
486 double[] d1ye = d1[5];
487 double[] d2ye = d2[5];
488 if (!Arrays.equals(d1ye, d2ye)) {
489 return false;
490 }
491 }
492 return true;
493 }
494
495 /**
496 * Returns a hash code for this instance.
497 *
498 * @return A hash code.
499 */
500 public int hashCode() {
501 int result;
502 result = this.seriesKeys.hashCode();
503 result = 29 * result + this.seriesList.hashCode();
504 return result;
505 }
506
507 /**
508 * Returns a clone of this dataset.
509 *
510 * @return A clone.
511 *
512 * @throws CloneNotSupportedException if the dataset contains a series with
513 * a key that cannot be cloned.
514 */
515 public Object clone() throws CloneNotSupportedException {
516 DefaultIntervalXYDataset clone
517 = (DefaultIntervalXYDataset) super.clone();
518 clone.seriesKeys = new java.util.ArrayList(this.seriesKeys);
519 clone.seriesList = new ArrayList(this.seriesList.size());
520 for (int i = 0; i < this.seriesList.size(); i++) {
521 double[][] data = (double[][]) this.seriesList.get(i);
522 double[] x = data[0];
523 double[] xStart = data[1];
524 double[] xEnd = data[2];
525 double[] y = data[3];
526 double[] yStart = data[4];
527 double[] yEnd = data[5];
528 double[] xx = new double[x.length];
529 double[] xxStart = new double[xStart.length];
530 double[] xxEnd = new double[xEnd.length];
531 double[] yy = new double[y.length];
532 double[] yyStart = new double[yStart.length];
533 double[] yyEnd = new double[yEnd.length];
534 System.arraycopy(x, 0, xx, 0, x.length);
535 System.arraycopy(xStart, 0, xxStart, 0, xStart.length);
536 System.arraycopy(xEnd, 0, xxEnd, 0, xEnd.length);
537 System.arraycopy(y, 0, yy, 0, y.length);
538 System.arraycopy(yStart, 0, yyStart, 0, yStart.length);
539 System.arraycopy(yEnd, 0, yyEnd, 0, yEnd.length);
540 clone.seriesList.add(i, new double[][] {xx, xxStart, xxEnd, yy,
541 yyStart, yyEnd});
542 }
543 return clone;
544 }
545
546 }