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