    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    }