    001/* ===========================================================
    002 * Orson Charts : a 3D chart library for the Java(tm) platform
    003 * ===========================================================
    004 *
    005 * (C)opyright 2013-2016, by Object Refinery Limited.  All rights reserved.
    006 *
    007 * http://www.object-refinery.com/orsoncharts/index.html
    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 com.orsoncharts.data;
    034
    035import java.util.List;
    036import com.orsoncharts.Range;
    037import com.orsoncharts.data.category.CategoryDataset3D;
    038import com.orsoncharts.data.xyz.XYZDataset;
    039import com.orsoncharts.data.xyz.XYZSeries;
    040import com.orsoncharts.data.xyz.XYZSeriesCollection;
    041import com.orsoncharts.util.ArgChecks;
    042
    043/**
    044 * Some utility methods for working with the various datasets and data
    045 * structures available in Orson Charts.
    046 */
    047public class DataUtils {
    048
    049    private DataUtils() {
    050        // no need to create instances
    051    }
    052
    053    /**
    054     * Returns the total of the values in the list.  Any {@code null}
    055     * values are ignored.
    056     *
    057     * @param values  the values ({@code null} not permitted).
    058     *
    059     * @return The total of the values in the list.
    060     */
    061    public static double total(Values<Number> values) {
    062        double result = 0.0;
    063        for (int i = 0; i < values.getItemCount(); i++) {
    064            Number n = values.getValue(i);
    065            if (n != null) {
    066                result = result + n.doubleValue();
    067            }
    068        }
    069        return result;
    070    }
    071
    072    /**
    073     * Returns the count of the non-{@code null} entries in the dataset
    074     * for the specified series.  An {@code IllegalArgumentException} is
    075     * thrown if the {@code seriesKey} is not present in the data.
    076     *
    077     * @param <S>  the series key (must implement Comparable).
    078     * @param data  the dataset ({@code null} not permitted).
    079     * @param seriesKey  the series key ({@code null} not permitted).
    080     *
    081     * @return The count.
    082     *
    083     * @since 1.2
    084     */
    085    public static <S extends Comparable<S>> int count(
    086            KeyedValues3D<S,?,?,?> data, S seriesKey) {
    087        ArgChecks.nullNotPermitted(data, "data");
    088        ArgChecks.nullNotPermitted(seriesKey, "seriesKey");
    089        int seriesIndex = data.getSeriesIndex(seriesKey);
    090        if (seriesIndex < 0) {
    091            throw new IllegalArgumentException("Series not found: "
    092                    + seriesKey);
    093        }
    094        int count = 0;
    095        int rowCount = data.getRowCount();
    096        int columnCount = data.getColumnCount();
    097        for (int r = 0; r < rowCount; r++) {
    098            for (int c = 0; c < columnCount; c++) {
    099                Number n = (Number) data.getValue(seriesIndex, r, c);
    100                if (n != null) {
    101                    count++;
    102                }
    103            }
    104        }
    105        return count;
    106    }
    107
    108    /**
    109     * Returns the count of the non-{@code null} entries in the dataset
    110     * for the specified row (all series).
    111     *
    112     * @param <R>  the row key (must implement Comparable).
    113     * @param data  the dataset ({@code null} not permitted).
    114     * @param rowKey  the row key ({@code null} not permitted).
    115     *
    116     * @return The count.
    117     *
    118     * @since 1.2
    119     */
    120    public static <R extends Comparable<R>> int countForRow(
    121            KeyedValues3D<?, R, ?, ?> data, R rowKey) {
    122        ArgChecks.nullNotPermitted(data, "data");
    123        ArgChecks.nullNotPermitted(rowKey, "rowKey");
    124        int rowIndex = data.getRowIndex(rowKey);
    125        if (rowIndex < 0) {
    126            throw new IllegalArgumentException("Row not found: " + rowKey);
    127        }
    128        int count = 0;
    129        int seriesCount = data.getSeriesCount();
    130        int columnCount = data.getColumnCount();
    131        for (int s = 0; s < seriesCount; s++) {
    132            for (int c = 0; c < columnCount; c++) {
    133                Number n = (Number) data.getValue(s, rowIndex, c);
    134                if (n != null) {
    135                    count++;
    136                }
    137            }
    138        }
    139        return count;
    140    }
    141
    142    /**
    143     * Returns the count of the non-{@code null} entries in the dataset
    144     * for the specified column (all series).
    145     *
    146     * @param <C>  the column key (must implement Comparable).
    147     * @param data  the dataset ({@code null} not permitted).
    148     * @param columnKey  the column key ({@code null} not permitted).
    149     *
    150     * @return The count.
    151     *
    152     * @since 1.2
    153     */
    154    public static <C extends Comparable<C>> int countForColumn(
    155            KeyedValues3D<?, ?, C, ?> data, C columnKey) {
    156        ArgChecks.nullNotPermitted(data, "data");
    157        ArgChecks.nullNotPermitted(columnKey, "columnKey");
    158        int columnIndex = data.getColumnIndex(columnKey);
    159        if (columnIndex < 0) {
    160            throw new IllegalArgumentException("Column not found: "
    161                    + columnKey);
    162        }
    163        int count = 0;
    164        int seriesCount = data.getSeriesCount();
    165        int rowCount = data.getRowCount();
    166        for (int s = 0; s < seriesCount; s++) {
    167            for (int r = 0; r < rowCount; r++) {
    168                Number n = (Number) data.getValue(s, r, columnIndex);
    169                if (n != null) {
    170                    count++;
    171                }
    172            }
    173        }
    174        return count;
    175    }
    176
    177    /**
    178     * Returns the total of the non-{@code null} values in the dataset
    179     * for the specified series.  If there is no series with the specified
    180     * key, this method will throw an {@code IllegalArgumentException}.
    181     *
    182     * @param <S>  the series key (must implement Comparable).
    183     * @param data  the dataset ({@code null} not permitted).
    184     * @param seriesKey  the series key ({@code null} not permitted).
    185     *
    186     * @return The total.
    187     *
    188     * @since 1.2
    189     */
    190    public static <S extends Comparable<S>> double total(
    191            KeyedValues3D<S, ?, ?, ? extends Number> data, S seriesKey) {
    192        ArgChecks.nullNotPermitted(data, "data");
    193        ArgChecks.nullNotPermitted(seriesKey, "seriesKey");
    194        int seriesIndex = data.getSeriesIndex(seriesKey);
    195        if (seriesIndex < 0) {
    196            throw new IllegalArgumentException("Series not found: "
    197                    + seriesKey);
    198        }
    199        double total = 0.0;
    200        int rowCount = data.getRowCount();
    201        int columnCount = data.getColumnCount();
    202        for (int r = 0; r < rowCount; r++) {
    203            for (int c = 0; c < columnCount; c++) {
    204                Number n = (Number) data.getValue(seriesIndex, r, c);
    205                if (n != null) {
    206                    total += n.doubleValue();
    207                }
    208            }
    209        }
    210        return total;
    211    }
    212
    213    /**
    214     * Returns the total of the non-{@code null} entries in the dataset
    215     * for the specified row (all series).
    216     *
    217     * @param <R>  the row key (must implement Comparable).
    218     * @param data  the dataset ({@code null} not permitted).
    219     * @param rowKey  the row key ({@code null} not permitted).
    220     *
    221     * @return The total.
    222     *
    223     * @since 1.2
    224     */
    225    public static <R extends Comparable<R>> double totalForRow(
    226            KeyedValues3D<?, R, ?, ? extends Number> data, R rowKey) {
    227        ArgChecks.nullNotPermitted(data, "data");
    228        ArgChecks.nullNotPermitted(rowKey, "rowKey");
    229        int rowIndex = data.getRowIndex(rowKey);
    230        if (rowIndex < 0) {
    231            throw new IllegalArgumentException("Row not found: " + rowKey);
    232        }
    233        double total = 0.0;
    234        int seriesCount = data.getSeriesCount();
    235        int columnCount = data.getColumnCount();
    236        for (int s = 0; s < seriesCount; s++) {
    237            for (int c = 0; c < columnCount; c++) {
    238                Number n = (Number) data.getValue(s, rowIndex, c);
    239                if (n != null) {
    240                    total += n.doubleValue();
    241                }
    242            }
    243        }
    244        return total;
    245    }
    246
    247    /**
    248     * Returns the total of the non-{@code null} entries in the dataset
    249     * for the specified column (all series).
    250     *
    251     * @param <C>  the column key (must implement Comparable).
    252     * @param data  the dataset ({@code null} not permitted).
    253     * @param columnKey  the row key ({@code null} not permitted).
    254     *
    255     * @return The total.
    256     *
    257     * @since 1.2
    258     */
    259    public static <C extends Comparable<C>> double totalForColumn(
    260            KeyedValues3D<?, ?, C, ? extends Number> data, C columnKey) {
    261        ArgChecks.nullNotPermitted(data, "data");
    262        ArgChecks.nullNotPermitted(columnKey, "columnKey");
    263        int columnIndex = data.getColumnIndex(columnKey);
    264        if (columnIndex < 0) {
    265            throw new IllegalArgumentException("Column not found: "
    266                    + columnKey);
    267        }
    268        double total = 0.0;
    269        int seriesCount = data.getSeriesCount();
    270        int rowCount = data.getRowCount();
    271        for (int s = 0; s < seriesCount; s++) {
    272            for (int r = 0; r < rowCount; r++) {
    273                Number n = (Number) data.getValue(s, r, columnIndex);
    274                if (n != null) {
    275                    total += n.doubleValue();
    276                }
    277            }
    278        }
    279        return total;
    280    }
    281
    282    /**
    283     * Returns the range of values in the specified data structure (a three
    284     * dimensional cube).  If there is no data, this method returns
    285     * {@code null}.
    286     *
    287     * @param data  the data ({@code null} not permitted).
    288     *
    289     * @return The range of data values (possibly {@code null}).
    290     */
    291    public static Range findValueRange(Values3D<? extends Number> data) {
    292        return findValueRange(data, Double.NaN);
    293    }
    294
    295    /**
    296     * Returns the range of values in the specified data cube, or
    297     * {@code null} if there is no data.  The range will be expanded, if
    298     * required, to include the {@code base} value (unless it
    299     * is {@code Double.NaN} in which case it is ignored).
    300     *
    301     * @param data  the data ({@code null} not permitted).
    302     * @param base  a value that must be included in the range (often 0).  This
    303     *         argument is ignored if it is {@code Double.NaN}.
    304     *
    305     * @return The range (possibly {@code null}).
    306     */
    307    public static Range findValueRange(Values3D<? extends Number> data,
    308            double base) {
    309        return findValueRange(data, base, true);
    310    }
    311
    312    /**
    313    /**
    314     * Returns the range of values in the specified data cube, or
    315     * {@code null} if there is no data.  The range will be expanded, if
    316     * required, to include the {@code base} value (unless it
    317     * is {@code Double.NaN} in which case it is ignored).
    318     *
    319     * @param data  the data ({@code null} not permitted).
    320     * @param base  a value that must be included in the range (often 0).  This
    321     *         argument is ignored if it is {@code Double.NaN}.
    322     * @param finite  if {@code true} infinite values will be ignored.
    323     *
    324     * @return The range (possibly {@code null}).
    325     *
    326     * @since 1.4
    327     */
    328    public static Range findValueRange(Values3D<? extends Number> data,
    329            double base, boolean finite) {
    330        ArgChecks.nullNotPermitted(data, "data");
    331        double min = Double.POSITIVE_INFINITY;
    332        double max = Double.NEGATIVE_INFINITY;
    333        for (int series = 0; series < data.getSeriesCount(); series++) {
    334            for (int row = 0; row < data.getRowCount(); row++) {
    335                for (int col = 0; col < data.getColumnCount(); col++) {
    336                    double d = data.getDoubleValue(series, row, col);
    337                    if (!Double.isNaN(d)) {
    338                        if (!finite || !Double.isInfinite(d)) {
    339                            min = Math.min(min, d);
    340                            max = Math.max(max, d);
    341                        }
    342                    }
    343                }
    344            }
    345        }
    346        // include the special value in the range
    347        if (!Double.isNaN(base)) {
    348             min = Math.min(min, base);
    349             max = Math.max(max, base);
    350        }
    351        if (min <= max) {
    352            return new Range(min, max);
    353        } else {
    354            return null;
    355        }
    356    }
    357
    358    /**
    359     * Finds the range of values in the dataset considering that each series
    360     * is stacked on top of the other.
    361     *
    362     * @param data  the data ({@code null} not permitted).
    363     *
    364     * @return The range.
    365     */
    366    public static Range findStackedValueRange(Values3D<? extends Number> data) {
    367        return findStackedValueRange(data, 0.0);
    368    }
    369
    370    /**
    371     * Finds the range of values in the dataset considering that each series
    372     * is stacked on top of the others, starting at the base value.
    373     *
    374     * @param data  the data values ({@code null} not permitted).
    375     * @param base  the base value.
    376     *
    377     * @return The range.
    378     */
    379    public static Range findStackedValueRange(Values3D<? extends Number> data,
    380            double base) {
    381        ArgChecks.nullNotPermitted(data, "data");
    382        double min = base;
    383        double max = base;
    384        int seriesCount = data.getSeriesCount();
    385        for (int row = 0; row < data.getRowCount(); row++) {
    386            for (int col = 0; col < data.getColumnCount(); col++) {
    387                double[] total = stackSubTotal(data, base, seriesCount, row,
    388                        col);
    389                min = Math.min(min, total[0]);
    390                max = Math.max(max, total[1]);
    391            }
    392        }
    393        if (min <= max) {
    394            return new Range(min, max);
    395        } else {
    396            return null;
    397        }
    398    }
    399
    400    /**
    401     * Returns the positive and negative subtotals of the values for all the
    402     * series preceding the specified series.
    403     * <br><br>
    404     * One application for this method is to compute the base values for
    405     * individual bars in a stacked bar chart.
    406     *
    407     * @param data  the data ({@code null} not permitted).
    408     * @param base  the initial base value (normally {@code 0.0}, but the
    409     *     values can be stacked from a different starting point).
    410     * @param series  the index of the current series (series with lower indices
    411     *     are included in the sub-totals).
    412     * @param row  the row index of the required item.
    413     * @param column  the column index of the required item.
    414     *
    415     * @return The subtotals, where {@code result[0]} is the subtotal of
    416     *     the negative data items, and {@code result[1]} is the subtotal
    417     *     of the positive data items.
    418     */
    419    public static double[] stackSubTotal(Values3D<? extends Number> data,
    420            double base, int series, int row, int column) {
    421        double neg = base;
    422        double pos = base;
    423        for (int s = 0; s < series; s++) {
    424            double v = data.getDoubleValue(s, row, column);
    425            if (v > 0.0) {
    426                pos = pos + v;
    427            } else if (v < 0.0) {
    428                neg = neg + v;
    429            }
    430        }
    431        return new double[] { neg, pos };
    432    }
    433
    434    /**
    435     * Returns the total of the non-{@code NaN} entries in the dataset
    436     * for the specified series.
    437     *
    438     * @param <S>  the series key (must implement Comparable).
    439     * @param data  the dataset ({@code null} not permitted).
    440     * @param seriesKey  the series key ({@code null} not permitted).
    441     *
    442     * @return The count.
    443     *
    444     * @since 1.2
    445     */
    446    public static <S extends Comparable<S>> double total(XYZDataset<S> data,
    447            S seriesKey) {
    448        ArgChecks.nullNotPermitted(data, "data");
    449        ArgChecks.nullNotPermitted(seriesKey, "seriesKey");
    450        int seriesIndex = data.getSeriesIndex(seriesKey);
    451        if (seriesIndex < 0) {
    452            throw new IllegalArgumentException("Series not found: "
    453                    + seriesKey);
    454        }
    455        double total = 0;
    456        int itemCount = data.getItemCount(seriesIndex);
    457        for (int item = 0; item < itemCount; item++) {
    458            double y = data.getY(seriesIndex, item);
    459            if (!Double.isNaN(y)) {
    460                total += y;
    461            }
    462        }
    463        return total;
    464    }
    465
    466    /**
    467     * Returns the range of x-values in the dataset by iterating over all
    468     * values (and ignoring {@code Double.NaN} and infinite values).
    469     * If there are no values eligible for inclusion in the range, this method
    470     * returns {@code null}.
    471     *
    472     * @param dataset  the dataset ({@code null} not permitted).
    473     *
    474     * @return The range (possibly {@code null}).
    475     */
    476    public static Range findXRange(XYZDataset dataset) {
    477        return findXRange(dataset, Double.NaN);
    478    }
    479
    480    /**
    481     * Returns the range of x-values in the dataset by iterating over all
    482     * values (and ignoring {@code Double.NaN} values).  The range will be
    483     * extended if necessary to include {@code inc} (unless it is
    484     * {@code Double.NaN} in which case it is ignored).  Infinite values
    485     * in the dataset will be ignored.  If there are no values eligible for
    486     * inclusion in the range, this method returns {@code null}.
    487     *
    488     * @param dataset  the dataset ({@code null} not permitted).
    489     * @param inc  an additional x-value to include.
    490     *
    491     * @return The range (possibly {@code null}).
    492     */
    493    public static Range findXRange(XYZDataset dataset, double inc) {
    494        return findXRange(dataset, inc, true);
    495    }
    496
    497    /**
    498     * Returns the range of x-values in the dataset by iterating over all
    499     * values (and ignoring {@code Double.NaN} values).  The range will be
    500     * extended if necessary to include {@code inc} (unless it is
    501     * {@code Double.NaN} in which case it is ignored).  If the
    502     * {@code finite} flag is set, infinite values in the dataset will be
    503     * ignored.  If there are no values eligible for inclusion in the range,
    504     * this method returns {@code null}.
    505     *
    506     * @param dataset  the dataset ({@code null} not permitted).
    507     * @param inc  an additional x-value to include.
    508     * @param finite  a flag indicating whether to exclude infinite values.
    509     *
    510     * @return The range (possibly {@code null}).
    511     *
    512     * @since 1.4
    513     */
    514    public static Range findXRange(XYZDataset dataset, double inc,
    515            boolean finite) {
    516        ArgChecks.nullNotPermitted(dataset, "dataset");
    517        double min = Double.POSITIVE_INFINITY;
    518        double max = Double.NEGATIVE_INFINITY;
    519        for (int s = 0; s < dataset.getSeriesCount(); s++) {
    520            for (int i = 0; i < dataset.getItemCount(s); i++) {
    521                double x = dataset.getX(s, i);
    522                if (!Double.isNaN(x)) {
    523                    if (!finite || !Double.isInfinite(x)) {
    524                        min = Math.min(x, min);
    525                        max = Math.max(x, max);
    526                    }
    527                }
    528            }
    529        }
    530        if (!Double.isNaN(inc)) {
    531            min = Math.min(inc, min);
    532            max = Math.max(inc, max);
    533        }
    534        if (min <= max) {
    535            return new Range(min, max);
    536        } else {
    537            return null;
    538        }
    539    }
    540
    541    /**
    542     * Returns the range of y-values in the dataset by iterating over all
    543     * values (and ignoring {@code Double.NaN} and infinite values).
    544     * If there are no values eligible for inclusion in the range, this method
    545     * returns {@code null}.
    546     *
    547     * @param dataset  the dataset ({@code null} not permitted).
    548     *
    549     * @return The range.
    550     */
    551    public static Range findYRange(XYZDataset dataset) {
    552        return findYRange(dataset, Double.NaN);
    553    }
    554
    555    /**
    556     * Returns the range of y-values in the dataset by iterating over all
    557     * values (and ignoring {@code Double.NaN} values).  The range will be
    558     * extended if necessary to include {@code inc} (unless it is
    559     * {@code Double.NaN} in which case it is ignored).  Infinite values
    560     * in the dataset will be ignored.  If there are no values eligible for
    561     * inclusion in the range, this method returns {@code null}.
    562     *
    563     * @param dataset  the dataset ({@code null} not permitted).
    564     * @param inc  an additional x-value to include.
    565     *
    566     * @return The range.
    567     */
    568    public static Range findYRange(XYZDataset dataset, double inc) {
    569        return findYRange(dataset, inc, true);
    570    }
    571
    572    /**
    573     * Returns the range of y-values in the dataset by iterating over all
    574     * values (and ignoring {@code Double.NaN} values).  The range will be
    575     * extended if necessary to include {@code inc} (unless it is
    576     * {@code Double.NaN} in which case it is ignored).  If the
    577     * {@code finite} flag is set, infinite values in the dataset will be
    578     * ignored.  If there are no values eligible for inclusion in the range,
    579     * this method returns {@code null}.
    580     *
    581     * @param dataset  the dataset ({@code null} not permitted).
    582     * @param inc  an additional y-value to include.
    583     * @param finite  a flag indicating whether to exclude infinite values.
    584     *
    585     * @return The range (possibly {@code null}).
    586     *
    587     * @since 1.4
    588     */
    589    public static Range findYRange(XYZDataset dataset, double inc,
    590            boolean finite) {
    591        ArgChecks.nullNotPermitted(dataset, "dataset");
    592        double min = Double.POSITIVE_INFINITY;
    593        double max = Double.NEGATIVE_INFINITY;
    594        for (int s = 0; s < dataset.getSeriesCount(); s++) {
    595            for (int i = 0; i < dataset.getItemCount(s); i++) {
    596                double y = dataset.getY(s, i);
    597                if (!Double.isNaN(y)) {
    598                    if (!finite || !Double.isInfinite(y)) {
    599                        min = Math.min(y, min);
    600                        max = Math.max(y, max);
    601                    }
    602                }
    603            }
    604        }
    605        if (!Double.isNaN(inc)) {
    606            min = Math.min(inc, min);
    607            max = Math.max(inc, max);
    608        }
    609        if (min <= max) {
    610            return new Range(min, max);
    611        } else {
    612            return null;
    613        }
    614    }
    615
    616    /**
    617     * Returns the range of z-values in the dataset by iterating over all
    618     * values (and ignoring {@code Double.NaN} and infinite values).
    619     * If there are no values eligible for inclusion in the range, this method
    620     * returns {@code null}.
    621     *
    622     * @param dataset  the dataset ({@code null} not permitted).
    623     *
    624     * @return The range (possibly {@code null}).
    625     */
    626    public static Range findZRange(XYZDataset dataset) {
    627        return findZRange(dataset, Double.NaN);
    628    }
    629
    630    /**
    631     * Returns the range of z-values in the dataset by iterating over all
    632     * values (and ignoring {@code Double.NaN} values).  The range will be
    633     * extended if necessary to include {@code inc} (unless it is
    634     * {@code Double.NaN} in which case it is ignored).  Infinite values
    635     * in the dataset will be ignored.  If there are no values eligible for
    636     * inclusion in the range, this method returns {@code null}.
    637     *
    638     * @param dataset  the dataset ({@code null} not permitted).
    639     * @param inc  an additional x-value to include.
    640     *
    641     * @return The range (possibly {@code null}).
    642     */
    643    public static Range findZRange(XYZDataset dataset, double inc) {
    644        return findZRange(dataset, inc, true);
    645    }
    646
    647    /**
    648     * Returns the range of z-values in the dataset by iterating over all
    649     * values (and ignoring {@code Double.NaN} values).  The range will be
    650     * extended if necessary to include {@code inc} (unless it is
    651     * {@code Double.NaN} in which case it is ignored).  If the
    652     * {@code finite} flag is set, infinite values in the dataset will be
    653     * ignored.  If there are no values eligible for inclusion in the range,
    654     * this method returns {@code null}.
    655     *
    656     * @param dataset  the dataset ({@code null} not permitted).
    657     * @param inc  an additional z-value to include.
    658     * @param finite  a flag indicating whether to exclude infinite values.
    659     *
    660     * @return The range (possibly {@code null}).
    661     *
    662     * @since 1.4
    663     */
    664    public static Range findZRange(XYZDataset dataset, double inc,
    665            boolean finite) {
    666        ArgChecks.nullNotPermitted(dataset, "dataset");
    667        ArgChecks.finiteRequired(inc, "inc");
    668        double min = Double.POSITIVE_INFINITY;
    669        double max = Double.NEGATIVE_INFINITY;
    670        for (int s = 0; s < dataset.getSeriesCount(); s++) {
    671            for (int i = 0; i < dataset.getItemCount(s); i++) {
    672                double z = dataset.getZ(s, i);
    673                if (!Double.isNaN(z)) {
    674                    if (!finite || !Double.isInfinite(z)) {
    675                        min = Math.min(z, min);
    676                        max = Math.max(z, max);
    677                    }
    678                }
    679            }
    680        }
    681        if (!Double.isNaN(inc)) {
    682            min = Math.min(inc, min);
    683            max = Math.max(inc, max);
    684        }
    685        if (min <= max) {
    686            return new Range(min, max);
    687        } else {
    688            return null;
    689        }
    690    }
    691
    692    /**
    693     * Creates an {@link XYZDataset} by extracting values from specified
    694     * rows in a {@link KeyedValues3D} instance, across all the available
    695     * columns (items where any of the x, y or z values is {@code null}
    696     * are skipped).  The new dataset contains a copy of the data and is
    697     * completely independent of the {@code source} dataset.
    698     * <br><br>
    699     * Note that {@link CategoryDataset3D} is an extension of
    700     * {@link KeyedValues3D} so you can use this method for any implementation
    701     * of the {@code CategoryDataset3D} interface.
    702     *
    703     * @param <S>  the series key (must implement Comparable).
    704     * @param <R>  the row key (must implement Comparable).
    705     * @param <C>  the column key (must implement Comparable).
    706     * @param source  the source data ({@code null} not permitted).
    707     * @param xRowKey  the row key for x-values ({@code null} not permitted).
    708     * @param yRowKey  the row key for y-values ({@code null} not permitted).
    709     * @param zRowKey  the row key for z-values ({@code null} not permitted).
    710     *
    711     * @return A new dataset.
    712     *
    713     * @since 1.3
    714     */
    715    public static <S extends Comparable<S>, R extends Comparable<R>,
    716            C extends Comparable<C>> XYZDataset extractXYZDatasetFromRows(
    717            KeyedValues3D<S, R, C, ? extends Number> source,
    718            R xRowKey, R yRowKey, R zRowKey) {
    719        return extractXYZDatasetFromRows(source, xRowKey, yRowKey, zRowKey,
    720                NullConversion.SKIP, null);
    721    }
    722
    723    /**
    724     * Creates an {@link XYZDataset} by extracting values from specified
    725     * rows in a {@link KeyedValues3D} instance.  The new dataset contains
    726     * a copy of the data and is completely independent of the
    727     * {@code source} dataset.  Note that {@link CategoryDataset3D} is an
    728     * extension of {@link KeyedValues3D}.
    729     * <br><br>
    730     * Special handling is provided for items that contain {@code null}
    731     * values.  The caller may pass in an {@code exceptions} list (
    732     * normally empty) that will be populated with the keys of the items that
    733     * receive special handling, if any.
    734     *
    735     * @param <S>  the series key (must implement Comparable).
    736     * @param <R>  the row key (must implement Comparable).
    737     * @param <C>  the column key (must implement Comparable).
    738     * @param source  the source data ({@code null} not permitted).
    739     * @param xRowKey  the row key for x-values ({@code null} not permitted).
    740     * @param yRowKey  the row key for y-values ({@code null} not permitted).
    741     * @param zRowKey  the row key for z-values ({@code null} not permitted).
    742     * @param nullConversion  specifies the treatment for {@code null}
    743     *         values in the dataset ({@code null} not permitted).
    744     * @param exceptions  a list that, if not null, will be populated with
    745     *         keys for the items in the source dataset that contain
    746     *         {@code null} values ({@code null} permitted).
    747     *
    748     * @return A new dataset.
    749     *
    750     * @since 1.3
    751     */
    752    public static <S extends Comparable<S>, R extends Comparable<R>,
    753            C extends Comparable<C>> XYZDataset extractXYZDatasetFromRows(
    754            KeyedValues3D<S, R, C, ? extends Number> source,
    755            R xRowKey, R yRowKey, R zRowKey, NullConversion nullConversion,
    756            List<KeyedValues3DItemKey> exceptions) {
    757
    758        ArgChecks.nullNotPermitted(source, "source");
    759        ArgChecks.nullNotPermitted(xRowKey, "xRowKey");
    760        ArgChecks.nullNotPermitted(yRowKey, "yRowKey");
    761        ArgChecks.nullNotPermitted(zRowKey, "zRowKey");
    762        XYZSeriesCollection<S> dataset = new XYZSeriesCollection<S>();
    763        for (S seriesKey : source.getSeriesKeys()) {
    764            XYZSeries<S> series = new XYZSeries<S>(seriesKey);
    765            for (C colKey : source.getColumnKeys()) {
    766                Number x = source.getValue(seriesKey, xRowKey, colKey);
    767                Number y = source.getValue(seriesKey, yRowKey, colKey);
    768                Number z = source.getValue(seriesKey, zRowKey, colKey);
    769                if (x != null && y != null && z != null) {
    770                    series.add(x.doubleValue(), y.doubleValue(),
    771                            z.doubleValue());
    772                } else {
    773                    if (exceptions != null) {
    774                        // add only one exception per data value
    775                        R rrKey = zRowKey;
    776                        if (x == null) {
    777                            rrKey = xRowKey;
    778                        } else if (y == null) {
    779                            rrKey = yRowKey;
    780                        }
    781                        exceptions.add(new KeyedValues3DItemKey<S, R, C>(seriesKey,
    782                                rrKey, colKey));
    783                    }
    784                    if (nullConversion.equals(NullConversion.THROW_EXCEPTION)) {
    785                        Comparable rrKey = zRowKey;
    786                        if (x == null) {
    787                            rrKey = yRowKey;
    788                        } else if (y == null) {
    789                            rrKey = yRowKey;
    790                        }
    791                        throw new RuntimeException("There is a null value for "
    792                                + "the item [" + seriesKey +", " + rrKey + ", "
    793                                + colKey + "].");
    794                    }
    795                    if (nullConversion != NullConversion.SKIP) {
    796                        double xx = convert(x, nullConversion);
    797                        double yy = convert(y, nullConversion);
    798                        double zz = convert(z, nullConversion);
    799                        series.add(xx, yy, zz);
    800                    }
    801                }
    802            }
    803            dataset.add(series);
    804        }
    805        return dataset;
    806    }
    807
    808    /**
    809     * Creates an {@link XYZDataset} by extracting values from specified
    810     * columns in a {@link KeyedValues3D} instance, across all the available
    811     * rows (items where any of the x, y or z values is {@code null} are
    812     * skipped).  The new dataset contains a copy of the data and is completely
    813     * independent of the {@code source} dataset.
    814     * <br><br>
    815     * Note that {@link CategoryDataset3D} is an extension of
    816     * {@link KeyedValues3D} so you can use this method for any implementation
    817     * of the {@code CategoryDataset3D} interface.
    818     *
    819     * @param <S>  the series key (must implement Comparable).
    820     * @param <R>  the row key (must implement Comparable).
    821     * @param <C>  the column key (must implement Comparable).
    822     * @param source  the source data ({@code null} not permitted).
    823     * @param xColKey  the column key for x-values ({@code null} not permitted).
    824     * @param yColKey  the column key for y-values ({@code null} not permitted).
    825     * @param zColKey  the column key for z-values ({@code null} not permitted).
    826     *
    827     * @return A new dataset.
    828     *
    829     * @since 1.3
    830     */
    831    public static <S extends Comparable<S>, R extends Comparable<R>,
    832            C extends Comparable<C>> XYZDataset<S> extractXYZDatasetFromColumns(
    833            KeyedValues3D<S, R, C, ? extends Number> source,
    834            C xColKey, C yColKey, C zColKey) {
    835        return extractXYZDatasetFromColumns(source, xColKey, yColKey, zColKey,
    836                NullConversion.SKIP, null);
    837    }
    838
    839    /**
    840     * Creates an {@link XYZDataset} by extracting values from specified
    841     * columns in a {@link KeyedValues3D} instance.  The new dataset contains
    842     * a copy of the data and is completely independent of the
    843     * {@code source} dataset.  Note that {@link CategoryDataset3D} is an
    844     * extension of {@link KeyedValues3D}.
    845     * <br><br>
    846     * Special handling is provided for items that contain {@code null}
    847     * values.  The caller may pass in an {@code exceptions} list (
    848     * normally empty) that will be populated with the keys of the items that
    849     * receive special handling, if any.
    850     *
    851     * @param <S>  the series key (must implement Comparable).
    852     * @param <R>  the row key (must implement Comparable).
    853     * @param <C>  the column key (must implement Comparable).
    854     * @param source  the source data ({@code null} not permitted).
    855     * @param xColKey  the column key for x-values ({@code null} not permitted).
    856     * @param yColKey  the column key for y-values ({@code null} not permitted).
    857     * @param zColKey  the column key for z-values ({@code null} not permitted).
    858     * @param nullConversion  specifies the treatment for {@code null}
    859     *         values in the dataset ({@code null} not permitted).
    860     * @param exceptions  a list that, if not null, will be populated with
    861     *         keys for the items in the source dataset that contain
    862     *         {@code null} values ({@code null} permitted).
    863     *
    864     * @return A new dataset.
    865     *
    866     * @since 1.3
    867     */
    868    public static <S extends Comparable<S>, R extends Comparable<R>,
    869            C extends Comparable<C>>
    870            XYZDataset<S> extractXYZDatasetFromColumns(
    871            KeyedValues3D<S, R, C, ? extends Number> source,
    872            C xColKey, C yColKey, C zColKey, NullConversion nullConversion,
    873            List<KeyedValues3DItemKey> exceptions) {
    874
    875        ArgChecks.nullNotPermitted(source, "source");
    876        ArgChecks.nullNotPermitted(xColKey, "xColKey");
    877        ArgChecks.nullNotPermitted(yColKey, "yColKey");
    878        ArgChecks.nullNotPermitted(zColKey, "zColKey");
    879        XYZSeriesCollection<S> dataset = new XYZSeriesCollection<S>();
    880        for (S seriesKey : source.getSeriesKeys()) {
    881            XYZSeries<S> series = new XYZSeries<S>(seriesKey);
    882            for (R rowKey : source.getRowKeys()) {
    883                Number x = source.getValue(seriesKey, rowKey, xColKey);
    884                Number y = source.getValue(seriesKey, rowKey, yColKey);
    885                Number z = source.getValue(seriesKey, rowKey, zColKey);
    886                if (x != null && y != null && z != null) {
    887                    series.add(x.doubleValue(), y.doubleValue(),
    888                            z.doubleValue());
    889                } else {
    890                    if (exceptions != null) {
    891                        // add only one key ref out of the possible 3 per item
    892                        C ccKey = zColKey;
    893                        if (x == null) {
    894                            ccKey = xColKey;
    895                        } else if (y == null) {
    896                            ccKey = yColKey;
    897                        }
    898                        exceptions.add(new KeyedValues3DItemKey<S, R, C>(
    899                                seriesKey, rowKey, ccKey));
    900                    }
    901                    if (nullConversion.equals(NullConversion.THROW_EXCEPTION)) {
    902                        Comparable<?> ccKey = zColKey;
    903                        if (x == null) {
    904                            ccKey = xColKey;
    905                        } else if (y == null) {
    906                            ccKey = yColKey;
    907                        }
    908                        throw new RuntimeException("There is a null value for "
    909                                + "the item [" + seriesKey +", " + rowKey + ", "
    910                                + ccKey + "].");
    911                    }
    912                    if (nullConversion != NullConversion.SKIP) {
    913                        double xx = convert(x, nullConversion);
    914                        double yy = convert(y, nullConversion);
    915                        double zz = convert(z, nullConversion);
    916                        series.add(xx, yy, zz);
    917                    }
    918                }
    919            }
    920            dataset.add(series);
    921        }
    922        return dataset;
    923    }
    924
    925    /**
    926     * Returns a double primitive for the specified number, with
    927     * {@code null} values returning {@code Double.NaN} except in the
    928     * case of {@code CONVERT_TO_ZERO} which returns 0.0.  Note that this
    929     * method does not throw an exception for {@code THROW_EXCEPTION}, it
    930     * expects code higher up the call chain to handle that (because there is
    931     * not enough information here to throw a useful exception).
    932     *
    933     * @param n  the number ({@code null} permitted).
    934     * @param nullConversion  the null conversion ({@code null} not
    935     *         permitted).
    936     *
    937     * @return A double primitive.
    938     */
    939    private static double convert(Number n, NullConversion nullConversion) {
    940        if (n != null) {
    941            return n.doubleValue();
    942        } else {
    943            if (nullConversion.equals(NullConversion.CONVERT_TO_ZERO)) {
    944                return 0.0;
    945            }
    946            return Double.NaN;
    947        }
    948    }
    949
    950}