001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2005, 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     * DefaultContourDataset.java
029     * --------------------------
030     * (C) Copyright 2002-2005, by David M. O'Donnell and Contributors.
031     *
032     * Original Author:  David M. O'Donnell;
033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
034     *
035     * $Id: DefaultContourDataset.java,v 1.6.2.1 2005/10/25 21:30:20 mungady Exp $
036     *
037     * Changes (from 23-Jan-2003)
038     * --------------------------
039     * 23-Jan-2003 : Added standard header (DG);
040     * 20-May-2003 : removed member vars numX and numY, which were never used (TM);
041     * 06-May-2004 : Now extends AbstractXYZDataset (DG);
042     * 15-Jul-2004 : Switched getX() with getXValue(), getY() with getYValue() and 
043     *               getZ() with getZValue() methods (DG);
044     * 
045     */
046    
047    package org.jfree.data.contour;
048    
049    import java.util.Arrays;
050    import java.util.Date;
051    import java.util.Vector;
052    
053    import org.jfree.data.Range;
054    import org.jfree.data.xy.AbstractXYZDataset;
055    import org.jfree.data.xy.XYDataset;
056    
057    /**
058     * A convenience class that provides a default implementation of the 
059     * {@link ContourDataset} interface.
060     *
061     * @author David M. O'Donnell
062     */
063    public class DefaultContourDataset extends AbstractXYZDataset 
064                                       implements ContourDataset {
065    
066        /** The series name (this dataset supports only one series). */
067        protected Comparable seriesKey = null;
068    
069        /** Storage for the x values. */
070        protected Number[] xValues = null;
071    
072        /** Storage for the y values. */
073        protected Number[] yValues = null;
074    
075        /** Storage for the z values. */
076        protected Number[] zValues = null;
077    
078        /** The index for the start of each column in the data. */
079        protected int[] xIndex = null;
080    
081        /** Flags that track whether x, y and z are dates. */
082        boolean[] dateAxis = new boolean[3];
083    
084        /**
085         * Creates a new dataset, initially empty.
086         */
087        public DefaultContourDataset() {
088            super();
089        }
090    
091        /**
092         * Constructs a new dataset with the given data.
093         *
094         * @param seriesKey  the series key.
095         * @param xData  the x values.
096         * @param yData  the y values.
097         * @param zData  the z values.
098         */
099        public DefaultContourDataset(Comparable seriesKey,
100                                     Object[] xData,
101                                     Object[] yData,
102                                     Object[] zData) {
103    
104            this.seriesKey = seriesKey;
105            initialize(xData, yData, zData);
106        }
107    
108        /**
109         * Initialises the dataset.
110         * 
111         * @param xData  the x values.
112         * @param yData  the y values.
113         * @param zData  the z values.
114         */
115        public void initialize(Object[] xData,
116                               Object[] yData,
117                               Object[] zData) {
118    
119            this.xValues = new Double[xData.length];
120            this.yValues = new Double[yData.length];
121            this.zValues = new Double[zData.length];
122    
123            // We organise the data with the following assumption:
124            // 1) the data are sorted by x then y
125            // 2) that the data will be represented by a rectangle formed by
126            //    using x[i+1], x, y[j+1], and y.
127            // 3) we march along the y-axis at the same value of x until a new 
128            //    value x is found at which point we will flag the index 
129            //    where x[i+1]<>x[i]
130    
131            Vector tmpVector = new Vector(); //create a temporary vector
132            double x = 1.123452e31; // set x to some arbitary value (used below)
133            for (int k = 0; k < this.xValues.length; k++) {
134                if (xData[k] != null) {
135                    Number xNumber;
136                    if (xData[k] instanceof Number) {
137                        xNumber = (Number) xData[k];
138                    }
139                    else if (xData[k] instanceof Date) {
140                        this.dateAxis[0] = true;
141                        Date xDate = (Date) xData[k];
142                        xNumber = new Long(xDate.getTime()); //store data as Long
143                    }
144                    else {
145                        xNumber = new Integer(0);
146                    }
147                    this.xValues[k] = new Double(xNumber.doubleValue()); 
148                        // store Number as Double
149    
150                    // check if starting new column
151                    if (x != this.xValues[k].doubleValue()) {
152                        tmpVector.add(new Integer(k)); //store index where new 
153                                                       //column starts
154                        x = this.xValues[k].doubleValue(); 
155                                                 // set x to most recent value
156                    }
157                }
158            }
159    
160            Object[] inttmp = tmpVector.toArray();
161            this.xIndex = new int[inttmp.length];  // create array xIndex to hold 
162                                                   // new column indices
163    
164            for (int i = 0; i < inttmp.length; i++) {
165                this.xIndex[i] = ((Integer) inttmp[i]).intValue();
166            }
167            for (int k = 0; k < this.yValues.length; k++) { // store y and z axes 
168                                                            // as Doubles
169                this.yValues[k] = (Double) yData[k];
170                if (zData[k] != null) {
171                    this.zValues[k] = (Double) zData[k];
172                }
173            }
174        }
175    
176        /**
177         * Creates an object array from an array of doubles.
178         *
179         * @param data  the data.
180         *
181         * @return An array of <code>Double</code> objects.
182         */
183        public static Object[][] formObjectArray(double[][] data) {
184            Object[][] object = new Double[data.length][data[0].length];
185    
186            for (int i = 0; i < object.length; i++) {
187                for (int j = 0; j < object[i].length; j++) {
188                    object[i][j] = new Double(data[i][j]);
189                }
190            }
191            return object;
192        }
193    
194        /**
195         * Creates an object array from an array of doubles.
196         *
197         * @param data  the data.
198         *
199         * @return An array of <code>Double</code> objects.
200         */
201        public static Object[] formObjectArray(double[] data) {
202            Object[] object = new Double[data.length];
203            for (int i = 0; i < object.length; i++) {
204                object[i] = new Double(data[i]);
205            }
206            return object;
207        }
208    
209        /**
210         * Returns the number of items in the specified series.  This method 
211         * is provided to satisfy the {@link XYDataset} interface implementation.
212         *
213         * @param series  must be zero, as this dataset only supports one series.
214         *
215         * @return The item count.
216         */
217        public int getItemCount(int series) {
218            if (series > 0) {
219                throw new IllegalArgumentException("Only one series for contour");
220            }
221            return this.zValues.length;
222        }
223    
224        /**
225         * Returns the maximum z-value.
226         *
227         * @return The maximum z-value.
228         */
229        public double getMaxZValue() {
230            double zMax = -1.e20;
231            for (int k = 0; k < this.zValues.length; k++) {
232                if (this.zValues[k] != null) {
233                    zMax = Math.max(zMax, this.zValues[k].doubleValue());
234                }
235            }
236            return zMax;
237        }
238    
239        /**
240         * Returns the minimum z-value.
241         *
242         * @return The minimum z-value.
243         */
244        public double getMinZValue() {
245            double zMin = 1.e20;
246            for (int k = 0; k < this.zValues.length; k++) {
247                if (this.zValues[k] != null) {
248                    zMin = Math.min(zMin, this.zValues[k].doubleValue());
249                }
250            }
251            return zMin;
252        }
253    
254        /**
255         * Returns the maximum z-value within visible region of plot.
256         *
257         * @param x  the x range.
258         * @param y  the y range.
259         *
260         * @return The z range.
261         */
262        public Range getZValueRange(Range x, Range y) {
263    
264            double minX = x.getLowerBound();
265            double minY = y.getLowerBound();
266            double maxX = x.getUpperBound();
267            double maxY = y.getUpperBound();
268    
269            double zMin = 1.e20;
270            double zMax = -1.e20;
271            for (int k = 0; k < this.zValues.length; k++) {
272                if (this.xValues[k].doubleValue() >= minX
273                    && this.xValues[k].doubleValue() <= maxX
274                    && this.yValues[k].doubleValue() >= minY
275                    && this.yValues[k].doubleValue() <= maxY) {
276                    if (this.zValues[k] != null) {
277                        zMin = Math.min(zMin, this.zValues[k].doubleValue());
278                        zMax = Math.max(zMax, this.zValues[k].doubleValue());
279                    }
280                }
281            }
282    
283            return new Range(zMin, zMax);
284        }
285    
286        /**
287         * Returns the minimum z-value.
288         *
289         * @param minX  the minimum x value.
290         * @param minY  the minimum y value.
291         * @param maxX  the maximum x value.
292         * @param maxY  the maximum y value.
293         *
294         * @return The minimum z-value.
295         */
296        public double getMinZValue(double minX, 
297                                   double minY, 
298                                   double maxX, 
299                                   double maxY) {
300    
301            double zMin = 1.e20;
302            for (int k = 0; k < this.zValues.length; k++) {
303                if (this.zValues[k] != null) {
304                    zMin = Math.min(zMin, this.zValues[k].doubleValue());
305                }
306            }
307            return zMin;
308    
309        }
310    
311        /**
312         * Returns the number of series.
313         * <P>
314         * Required by XYDataset interface (this will always return 1)
315         *
316         * @return 1.
317         */
318        public int getSeriesCount() {
319            return 1;
320        }
321    
322        /**
323         * Returns the name of the specified series.
324         *
325         * Method provided to satisfy the XYDataset interface implementation
326         *
327         * @param series must be zero.
328         *
329         * @return The series name.
330         */
331        public Comparable getSeriesKey(int series) {
332            if (series > 0) {
333                throw new IllegalArgumentException("Only one series for contour");
334            }
335            return this.seriesKey;
336        }
337    
338        /**
339         * Returns the index of the xvalues.
340         *
341         * @return The x values.
342         */
343        public int[] getXIndices() {
344            return this.xIndex;
345        }
346    
347        /**
348         * Returns the x values.
349         *
350         * @return The x values.
351         */
352        public Number[] getXValues() {
353            return this.xValues;
354        }
355    
356        /**
357         * Returns the x value for the specified series and index (zero-based 
358         * indices).  Required by the {@link XYDataset}.
359         *
360         * @param series  must be zero;
361         * @param item  the item index (zero-based).
362         *
363         * @return The x value.
364         */
365        public Number getX(int series, int item) {
366            if (series > 0) {
367                throw new IllegalArgumentException("Only one series for contour");
368            }
369            return this.xValues[item];
370        }
371    
372        /**
373         * Returns an x value.
374         *
375         * @param item  the item index (zero-based).
376         *
377         * @return The X value.
378         */
379        public Number getXValue(int item) {
380            return this.xValues[item];
381        }
382    
383        /**
384         * Returns a Number array containing all y values.
385         *
386         * @return The Y values.
387         */
388        public Number[] getYValues() {
389            return this.yValues;
390        }
391    
392        /**
393         * Returns the y value for the specified series and index (zero-based 
394         * indices).  Required by the {@link XYDataset}.
395         *
396         * @param series  the series index (must be zero for this dataset).
397         * @param item  the item index (zero-based).
398         *
399         * @return The Y value.
400         */
401        public Number getY(int series, int item) {
402            if (series > 0) {
403                throw new IllegalArgumentException("Only one series for contour");
404            }
405            return this.yValues[item];
406        }
407    
408        /**
409         * Returns a Number array containing all z values.
410         *
411         * @return The Z values.
412         */
413        public Number[] getZValues() {
414            return this.zValues;
415        }
416    
417        /**
418         * Returns the z value for the specified series and index (zero-based 
419         * indices).  Required by the {@link XYDataset}
420         *
421         * @param series  the series index (must be zero for this dataset).
422         * @param item  the item index (zero-based).
423         *
424         * @return The Z value.
425         */
426        public Number getZ(int series, int item) {
427            if (series > 0) {
428                throw new IllegalArgumentException("Only one series for contour");
429            }
430            return this.zValues[item];
431        }
432    
433        /**
434         * Returns an int array contain the index into the x values.
435         *
436         * @return The X values.
437         */
438        public int[] indexX() {
439            int[] index = new int[this.xValues.length];
440            for (int k = 0; k < index.length; k++) {
441                index[k] = indexX(k);
442            }
443            return index;
444        }
445    
446        /**
447         * Given index k, returns the column index containing k.
448         *
449         * @param k index of interest.
450         *
451         * @return The column index.
452         */
453        public int indexX(int k) {
454            int i = Arrays.binarySearch(this.xIndex, k);
455            if (i >= 0) {
456                return i;
457            } 
458            else {
459                return -1 * i - 2;
460            }
461        }
462    
463    
464        /**
465         * Given index k, return the row index containing k.
466         *
467         * @param k index of interest.
468         *
469         * @return The row index.
470         */
471        public int indexY(int k) { // this may be obsolete (not used anywhere)
472            return (k / this.xValues.length);
473        }
474    
475        /**
476         * Given column and row indices, returns the k index.
477         *
478         * @param i index of along x-axis.
479         * @param j index of along y-axis.
480         *
481         * @return The Z index.
482         */
483        public int indexZ(int i, int j) {
484            return this.xValues.length * j + i;
485        }
486    
487        /**
488         * Returns true if axis are dates.
489         * 
490         * @param axisNumber The axis where 0-x, 1-y, and 2-z.
491         * 
492         * @return A boolean.
493         */
494        public boolean isDateAxis(int axisNumber) {
495            if (axisNumber < 0 || axisNumber > 2) {
496                return false; // bad axisNumber
497            }
498            return this.dateAxis[axisNumber];
499        }
500    
501        /**
502         * Sets the names of the series in the data source.
503         *
504         * @param seriesKeys  the keys of the series in the data source.
505         */
506        public void setSeriesKeys(Comparable[] seriesKeys) {
507            if (seriesKeys.length > 1) {
508                throw new IllegalArgumentException(
509                    "Contours only support one series"
510                );
511            }
512            this.seriesKey = seriesKeys[0];
513            fireDatasetChanged();
514        }
515    
516    }