All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jfree.chart3d.data.DataUtils Maven / Gradle / Ivy

/* ===========================================================
 * Orson Charts : a 3D chart library for the Java(tm) platform
 * ===========================================================
 * 
 * (C)opyright 2013-2022, by David Gilbert.  All rights reserved.
 * 
 * https://github.com/jfree/orson-charts
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 * 
 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
 * Other names may be trademarks of their respective owners.]
 * 
 * If you do not wish to be bound by the terms of the GPL, an alternative
 * commercial license can be purchased.  For details, please see visit the
 * Orson Charts home page:
 * 
 * http://www.object-refinery.com/orsoncharts/index.html
 * 
 */

package org.jfree.chart3d.data;

import java.util.List;
import org.jfree.chart3d.data.category.CategoryDataset3D;
import org.jfree.chart3d.data.xyz.XYZDataset;
import org.jfree.chart3d.data.xyz.XYZSeries;
import org.jfree.chart3d.data.xyz.XYZSeriesCollection;
import org.jfree.chart3d.internal.Args;

/**
 * Some utility methods for working with the various datasets and data
 * structures available in Orson Charts.
 */
public class DataUtils {
    
    private DataUtils() {
        // no need to create instances
    }
 
    /**
     * Returns the total of the values in the list.  Any {@code null}
     * values are ignored.
     * 
     * @param values  the values ({@code null} not permitted).
     * 
     * @return The total of the values in the list. 
     */
    public static double total(Values values) {
        double result = 0.0;
        for (int i = 0; i < values.getItemCount(); i++) {
            Number n = values.getValue(i);
            if (n != null) {
                result = result + n.doubleValue();
            }
        }
        return result;
    }
    
    /**
     * Returns the count of the non-{@code null} entries in the dataset
     * for the specified series.  An {@code IllegalArgumentException} is
     * thrown if the {@code seriesKey} is not present in the data.
     * 
     * @param   the series key (must implement Comparable).
     * @param data  the dataset ({@code null} not permitted).
     * @param seriesKey  the series key ({@code null} not permitted).
     * 
     * @return The count.
     * 
     * @since 1.2
     */
    public static > int count(
            KeyedValues3D data, S seriesKey) {
        Args.nullNotPermitted(data, "data");
        Args.nullNotPermitted(seriesKey, "seriesKey");
        int seriesIndex = data.getSeriesIndex(seriesKey);
        if (seriesIndex < 0) {
            throw new IllegalArgumentException("Series not found: " 
                    + seriesKey);
        }
        int count = 0;
        int rowCount = data.getRowCount();
        int columnCount = data.getColumnCount();
        for (int r = 0; r < rowCount; r++) {
            for (int c = 0; c < columnCount; c++) {
                Number n = (Number) data.getValue(seriesIndex, r, c);
                if (n != null) {
                    count++;
                }
            }
        }
        return count;
    }
        
    /**
     * Returns the count of the non-{@code null} entries in the dataset
     * for the specified row (all series).
     * 
     * @param   the row key (must implement Comparable).
     * @param data  the dataset ({@code null} not permitted).
     * @param rowKey  the row key ({@code null} not permitted).
     * 
     * @return The count.
     * 
     * @since 1.2
     */
    public static > int countForRow(
            KeyedValues3D data, R rowKey) {
        Args.nullNotPermitted(data, "data");
        Args.nullNotPermitted(rowKey, "rowKey");
        int rowIndex = data.getRowIndex(rowKey);
        if (rowIndex < 0) {
            throw new IllegalArgumentException("Row not found: " + rowKey);
        }
        int count = 0;
        int seriesCount = data.getSeriesCount();
        int columnCount = data.getColumnCount();
        for (int s = 0; s < seriesCount; s++) {
            for (int c = 0; c < columnCount; c++) {
                Number n = (Number) data.getValue(s, rowIndex, c);
                if (n != null) {
                    count++;
                }
            }
        }
        return count;
    }

    /**
     * Returns the count of the non-{@code null} entries in the dataset
     * for the specified column (all series).
     * 
     * @param   the column key (must implement Comparable).
     * @param data  the dataset ({@code null} not permitted).
     * @param columnKey  the column key ({@code null} not permitted).
     * 
     * @return The count.
     * 
     * @since 1.2
     */
    public static > int countForColumn(
            KeyedValues3D data, C columnKey) {
        Args.nullNotPermitted(data, "data");
        Args.nullNotPermitted(columnKey, "columnKey");
        int columnIndex = data.getColumnIndex(columnKey);
        if (columnIndex < 0) {
            throw new IllegalArgumentException("Column not found: " 
                    + columnKey);
        }
        int count = 0;
        int seriesCount = data.getSeriesCount();
        int rowCount = data.getRowCount();
        for (int s = 0; s < seriesCount; s++) {
            for (int r = 0; r < rowCount; r++) {
                Number n = (Number) data.getValue(s, r, columnIndex);
                if (n != null) {
                    count++;
                }
            }
        }
        return count;
    }
        
    /**
     * Returns the total of the non-{@code null} values in the dataset
     * for the specified series.  If there is no series with the specified 
     * key, this method will throw an {@code IllegalArgumentException}.
     * 
     * @param   the series key (must implement Comparable).
     * @param data  the dataset ({@code null} not permitted).
     * @param seriesKey  the series key ({@code null} not permitted).
     * 
     * @return The total.
     * 
     * @since 1.2
     */
    public static > double total(
            KeyedValues3D data, S seriesKey) {
        Args.nullNotPermitted(data, "data");
        Args.nullNotPermitted(seriesKey, "seriesKey");
        int seriesIndex = data.getSeriesIndex(seriesKey);
        if (seriesIndex < 0) {
            throw new IllegalArgumentException("Series not found: " 
                    + seriesKey);
        }
        double total = 0.0;
        int rowCount = data.getRowCount();
        int columnCount = data.getColumnCount();
        for (int r = 0; r < rowCount; r++) {
            for (int c = 0; c < columnCount; c++) {
                Number n = data.getValue(seriesIndex, r, c);
                if (n != null) {
                    total += n.doubleValue();
                }
            }
        }
        return total;
    }

    /**
     * Returns the total of the non-{@code null} entries in the dataset
     * for the specified row (all series).
     * 
     * @param   the row key (must implement Comparable).
     * @param data  the dataset ({@code null} not permitted).
     * @param rowKey  the row key ({@code null} not permitted).
     * 
     * @return The total.
     * 
     * @since 1.2
     */
    public static > double totalForRow(
            KeyedValues3D data, R rowKey) {
        Args.nullNotPermitted(data, "data");
        Args.nullNotPermitted(rowKey, "rowKey");
        int rowIndex = data.getRowIndex(rowKey);
        if (rowIndex < 0) {
            throw new IllegalArgumentException("Row not found: " + rowKey);
        }
        double total = 0.0;
        int seriesCount = data.getSeriesCount();
        int columnCount = data.getColumnCount();
        for (int s = 0; s < seriesCount; s++) {
            for (int c = 0; c < columnCount; c++) {
                Number n = data.getValue(s, rowIndex, c);
                if (n != null) {
                    total += n.doubleValue();
                }
            }
        }
        return total;
    }

    /**
     * Returns the total of the non-{@code null} entries in the dataset
     * for the specified column (all series).
     * 
     * @param   the column key (must implement Comparable).
     * @param data  the dataset ({@code null} not permitted).
     * @param columnKey  the row key ({@code null} not permitted).
     * 
     * @return The total.
     * 
     * @since 1.2
     */
    public static > double totalForColumn(
            KeyedValues3D data, C columnKey) {
        Args.nullNotPermitted(data, "data");
        Args.nullNotPermitted(columnKey, "columnKey");
        int columnIndex = data.getColumnIndex(columnKey);
        if (columnIndex < 0) {
            throw new IllegalArgumentException("Column not found: " 
                    + columnKey);
        }
        double total = 0.0;
        int seriesCount = data.getSeriesCount();
        int rowCount = data.getRowCount();
        for (int s = 0; s < seriesCount; s++) {
            for (int r = 0; r < rowCount; r++) {
                Number n = data.getValue(s, r, columnIndex);
                if (n != null) {
                    total += n.doubleValue();
                }
            }
        }
        return total;
    }

    /**
     * Returns the range of values in the specified data structure (a three
     * dimensional cube).  If there is no data, this method returns
     * {@code null}.
     * 
     * @param data  the data ({@code null} not permitted).
     * 
     * @return The range of data values (possibly {@code null}).
     */
    public static Range findValueRange(Values3D data) {
        return findValueRange(data, Double.NaN);
    }

    /**
     * Returns the range of values in the specified data cube, or 
     * {@code null} if there is no data.  The range will be expanded, if 
     * required, to include the {@code base} value (unless it
     * is {@code Double.NaN} in which case it is ignored).
     * 
     * @param data  the data ({@code null} not permitted).
     * @param base  a value that must be included in the range (often 0).  This
     *         argument is ignored if it is {@code Double.NaN}.
     * 
     * @return The range (possibly {@code null}). 
     */
    public static Range findValueRange(Values3D data, 
            double base) {
        return findValueRange(data, base, true);
    }
    
    /**
    /**
     * Returns the range of values in the specified data cube, or 
     * {@code null} if there is no data.  The range will be expanded, if 
     * required, to include the {@code base} value (unless it
     * is {@code Double.NaN} in which case it is ignored).
     * 
     * @param data  the data ({@code null} not permitted).
     * @param base  a value that must be included in the range (often 0).  This
     *         argument is ignored if it is {@code Double.NaN}.
     * @param finite  if {@code true} infinite values will be ignored.
     * 
     * @return The range (possibly {@code null}).   
     * 
     * @since 1.4
     */
    public static Range findValueRange(Values3D data,
            double base, boolean finite) {
        Args.nullNotPermitted(data, "data");
        double min = Double.POSITIVE_INFINITY;
        double max = Double.NEGATIVE_INFINITY;
        for (int series = 0; series < data.getSeriesCount(); series++) {
            for (int row = 0; row < data.getRowCount(); row++) {
                for (int col = 0; col < data.getColumnCount(); col++) {
                    double d = data.getDoubleValue(series, row, col);
                    if (!Double.isNaN(d)) {
                        if (!finite || !Double.isInfinite(d)) {
                            min = Math.min(min, d);
                            max = Math.max(max, d);
                        }
                    }
                }
            }
        }
        // include the special value in the range
        if (!Double.isNaN(base)) {
             min = Math.min(min, base);
             max = Math.max(max, base);
        }
        if (min <= max) {
            return new Range(min, max);
        } else {
            return null;
        }
    }
    
    /**
     * Finds the range of values in the dataset considering that each series
     * is stacked on top of the other.
     * 
     * @param data  the data ({@code null} not permitted).
     * 
     * @return The range.
     */
    public static Range findStackedValueRange(Values3D data) {
        return findStackedValueRange(data, 0.0);
    }
    
    /**
     * Finds the range of values in the dataset considering that each series
     * is stacked on top of the others, starting at the base value.
     * 
     * @param data  the data values ({@code null} not permitted).
     * @param base  the base value.
     * 
     * @return The range.
     */
    public static Range findStackedValueRange(Values3D data, 
            double base) {
        Args.nullNotPermitted(data, "data");
        double min = base;
        double max = base;
        int seriesCount = data.getSeriesCount();
        for (int row = 0; row < data.getRowCount(); row++) {
            for (int col = 0; col < data.getColumnCount(); col++) {
                double[] total = stackSubTotal(data, base, seriesCount, row, 
                        col);
                min = Math.min(min, total[0]);
                max = Math.max(max, total[1]);
            }
        }
        if (min <= max) {
            return new Range(min, max);
        } else {
            return null;
        }        
    }
    
    /**
     * Returns the positive and negative subtotals of the values for all the 
     * series preceding the specified series.  
     * 

* One application for this method is to compute the base values for * individual bars in a stacked bar chart. * * @param data the data ({@code null} not permitted). * @param base the initial base value (normally {@code 0.0}, but the * values can be stacked from a different starting point). * @param series the index of the current series (series with lower indices * are included in the sub-totals). * @param row the row index of the required item. * @param column the column index of the required item. * * @return The subtotals, where {@code result[0]} is the subtotal of * the negative data items, and {@code result[1]} is the subtotal * of the positive data items. */ public static double[] stackSubTotal(Values3D data, double base, int series, int row, int column) { double neg = base; double pos = base; for (int s = 0; s < series; s++) { double v = data.getDoubleValue(s, row, column); if (v > 0.0) { pos = pos + v; } else if (v < 0.0) { neg = neg + v; } } return new double[] { neg, pos }; } /** * Returns the total of the non-{@code NaN} entries in the dataset * for the specified series. * * @param the series key (must implement Comparable). * @param data the dataset ({@code null} not permitted). * @param seriesKey the series key ({@code null} not permitted). * * @return The count. * * @since 1.2 */ public static > double total(XYZDataset data, S seriesKey) { Args.nullNotPermitted(data, "data"); Args.nullNotPermitted(seriesKey, "seriesKey"); int seriesIndex = data.getSeriesIndex(seriesKey); if (seriesIndex < 0) { throw new IllegalArgumentException("Series not found: " + seriesKey); } double total = 0; int itemCount = data.getItemCount(seriesIndex); for (int item = 0; item < itemCount; item++) { double y = data.getY(seriesIndex, item); if (!Double.isNaN(y)) { total += y; } } return total; } /** * Returns the range of x-values in the dataset by iterating over all * values (and ignoring {@code Double.NaN} and infinite values). * If there are no values eligible for inclusion in the range, this method * returns {@code null}. * * @param dataset the dataset ({@code null} not permitted). * * @return The range (possibly {@code null}). */ public static Range findXRange(XYZDataset dataset) { return findXRange(dataset, Double.NaN); } /** * Returns the range of x-values in the dataset by iterating over all * values (and ignoring {@code Double.NaN} values). The range will be * extended if necessary to include {@code inc} (unless it is * {@code Double.NaN} in which case it is ignored). Infinite values * in the dataset will be ignored. If there are no values eligible for * inclusion in the range, this method returns {@code null}. * * @param dataset the dataset ({@code null} not permitted). * @param inc an additional x-value to include. * * @return The range (possibly {@code null}). */ public static Range findXRange(XYZDataset dataset, double inc) { return findXRange(dataset, inc, true); } /** * Returns the range of x-values in the dataset by iterating over all * values (and ignoring {@code Double.NaN} values). The range will be * extended if necessary to include {@code inc} (unless it is * {@code Double.NaN} in which case it is ignored). If the * {@code finite} flag is set, infinite values in the dataset will be * ignored. If there are no values eligible for inclusion in the range, * this method returns {@code null}. * * @param dataset the dataset ({@code null} not permitted). * @param inc an additional x-value to include. * @param finite a flag indicating whether to exclude infinite values. * * @return The range (possibly {@code null}). * * @since 1.4 */ public static Range findXRange(XYZDataset dataset, double inc, boolean finite) { Args.nullNotPermitted(dataset, "dataset"); double min = Double.POSITIVE_INFINITY; double max = Double.NEGATIVE_INFINITY; for (int s = 0; s < dataset.getSeriesCount(); s++) { for (int i = 0; i < dataset.getItemCount(s); i++) { double x = dataset.getX(s, i); if (!Double.isNaN(x)) { if (!finite || !Double.isInfinite(x)) { min = Math.min(x, min); max = Math.max(x, max); } } } } if (!Double.isNaN(inc)) { min = Math.min(inc, min); max = Math.max(inc, max); } if (min <= max) { return new Range(min, max); } else { return null; } } /** * Returns the range of y-values in the dataset by iterating over all * values (and ignoring {@code Double.NaN} and infinite values). * If there are no values eligible for inclusion in the range, this method * returns {@code null}. * * @param dataset the dataset ({@code null} not permitted). * * @return The range. */ public static Range findYRange(XYZDataset dataset) { return findYRange(dataset, Double.NaN); } /** * Returns the range of y-values in the dataset by iterating over all * values (and ignoring {@code Double.NaN} values). The range will be * extended if necessary to include {@code inc} (unless it is * {@code Double.NaN} in which case it is ignored). Infinite values * in the dataset will be ignored. If there are no values eligible for * inclusion in the range, this method returns {@code null}. * * @param dataset the dataset ({@code null} not permitted). * @param inc an additional x-value to include. * * @return The range. */ public static Range findYRange(XYZDataset dataset, double inc) { return findYRange(dataset, inc, true); } /** * Returns the range of y-values in the dataset by iterating over all * values (and ignoring {@code Double.NaN} values). The range will be * extended if necessary to include {@code inc} (unless it is * {@code Double.NaN} in which case it is ignored). If the * {@code finite} flag is set, infinite values in the dataset will be * ignored. If there are no values eligible for inclusion in the range, * this method returns {@code null}. * * @param dataset the dataset ({@code null} not permitted). * @param inc an additional y-value to include. * @param finite a flag indicating whether to exclude infinite values. * * @return The range (possibly {@code null}). * * @since 1.4 */ public static Range findYRange(XYZDataset dataset, double inc, boolean finite) { Args.nullNotPermitted(dataset, "dataset"); double min = Double.POSITIVE_INFINITY; double max = Double.NEGATIVE_INFINITY; for (int s = 0; s < dataset.getSeriesCount(); s++) { for (int i = 0; i < dataset.getItemCount(s); i++) { double y = dataset.getY(s, i); if (!Double.isNaN(y)) { if (!finite || !Double.isInfinite(y)) { min = Math.min(y, min); max = Math.max(y, max); } } } } if (!Double.isNaN(inc)) { min = Math.min(inc, min); max = Math.max(inc, max); } if (min <= max) { return new Range(min, max); } else { return null; } } /** * Returns the range of z-values in the dataset by iterating over all * values (and ignoring {@code Double.NaN} and infinite values). * If there are no values eligible for inclusion in the range, this method * returns {@code null}. * * @param dataset the dataset ({@code null} not permitted). * * @return The range (possibly {@code null}). */ public static Range findZRange(XYZDataset dataset) { return findZRange(dataset, Double.NaN); } /** * Returns the range of z-values in the dataset by iterating over all * values (and ignoring {@code Double.NaN} values). The range will be * extended if necessary to include {@code inc} (unless it is * {@code Double.NaN} in which case it is ignored). Infinite values * in the dataset will be ignored. If there are no values eligible for * inclusion in the range, this method returns {@code null}. * * @param dataset the dataset ({@code null} not permitted). * @param inc an additional x-value to include. * * @return The range (possibly {@code null}). */ public static Range findZRange(XYZDataset dataset, double inc) { return findZRange(dataset, inc, true); } /** * Returns the range of z-values in the dataset by iterating over all * values (and ignoring {@code Double.NaN} values). The range will be * extended if necessary to include {@code inc} (unless it is * {@code Double.NaN} in which case it is ignored). If the * {@code finite} flag is set, infinite values in the dataset will be * ignored. If there are no values eligible for inclusion in the range, * this method returns {@code null}. * * @param dataset the dataset ({@code null} not permitted). * @param inc an additional z-value to include. * @param finite a flag indicating whether to exclude infinite values. * * @return The range (possibly {@code null}). * * @since 1.4 */ public static Range findZRange(XYZDataset dataset, double inc, boolean finite) { Args.nullNotPermitted(dataset, "dataset"); Args.finiteRequired(inc, "inc"); double min = Double.POSITIVE_INFINITY; double max = Double.NEGATIVE_INFINITY; for (int s = 0; s < dataset.getSeriesCount(); s++) { for (int i = 0; i < dataset.getItemCount(s); i++) { double z = dataset.getZ(s, i); if (!Double.isNaN(z)) { if (!finite || !Double.isInfinite(z)) { min = Math.min(z, min); max = Math.max(z, max); } } } } if (!Double.isNaN(inc)) { min = Math.min(inc, min); max = Math.max(inc, max); } if (min <= max) { return new Range(min, max); } else { return null; } } /** * Creates an {@link XYZDataset} by extracting values from specified * rows in a {@link KeyedValues3D} instance, across all the available * columns (items where any of the x, y or z values is {@code null} * are skipped). The new dataset contains a copy of the data and is * completely independent of the {@code source} dataset. *

* Note that {@link CategoryDataset3D} is an extension of * {@link KeyedValues3D} so you can use this method for any implementation * of the {@code CategoryDataset3D} interface. * * @param the series key (must implement Comparable). * @param the row key (must implement Comparable). * @param the column key (must implement Comparable). * @param source the source data ({@code null} not permitted). * @param xRowKey the row key for x-values ({@code null} not permitted). * @param yRowKey the row key for y-values ({@code null} not permitted). * @param zRowKey the row key for z-values ({@code null} not permitted). * * @return A new dataset. * * @since 1.3 */ public static , R extends Comparable, C extends Comparable> XYZDataset extractXYZDatasetFromRows( KeyedValues3D source, R xRowKey, R yRowKey, R zRowKey) { return extractXYZDatasetFromRows(source, xRowKey, yRowKey, zRowKey, NullConversion.SKIP, null); } /** * Creates an {@link XYZDataset} by extracting values from specified * rows in a {@link KeyedValues3D} instance. The new dataset contains * a copy of the data and is completely independent of the * {@code source} dataset. Note that {@link CategoryDataset3D} is an * extension of {@link KeyedValues3D}. *

* Special handling is provided for items that contain {@code null} * values. The caller may pass in an {@code exceptions} list ( * normally empty) that will be populated with the keys of the items that * receive special handling, if any. * * @param the series key (must implement Comparable). * @param the row key (must implement Comparable). * @param the column key (must implement Comparable). * @param source the source data ({@code null} not permitted). * @param xRowKey the row key for x-values ({@code null} not permitted). * @param yRowKey the row key for y-values ({@code null} not permitted). * @param zRowKey the row key for z-values ({@code null} not permitted). * @param nullConversion specifies the treatment for {@code null} * values in the dataset ({@code null} not permitted). * @param exceptions a list that, if not null, will be populated with * keys for the items in the source dataset that contain * {@code null} values ({@code null} permitted). * * @return A new dataset. * * @since 1.3 */ public static , R extends Comparable, C extends Comparable> XYZDataset extractXYZDatasetFromRows( KeyedValues3D source, R xRowKey, R yRowKey, R zRowKey, NullConversion nullConversion, List exceptions) { Args.nullNotPermitted(source, "source"); Args.nullNotPermitted(xRowKey, "xRowKey"); Args.nullNotPermitted(yRowKey, "yRowKey"); Args.nullNotPermitted(zRowKey, "zRowKey"); XYZSeriesCollection dataset = new XYZSeriesCollection<>(); for (S seriesKey : source.getSeriesKeys()) { XYZSeries series = new XYZSeries<>(seriesKey); for (C colKey : source.getColumnKeys()) { Number x = source.getValue(seriesKey, xRowKey, colKey); Number y = source.getValue(seriesKey, yRowKey, colKey); Number z = source.getValue(seriesKey, zRowKey, colKey); if (x != null && y != null && z != null) { series.add(x.doubleValue(), y.doubleValue(), z.doubleValue()); } else { if (exceptions != null) { // add only one exception per data value R rrKey = zRowKey; if (x == null) { rrKey = xRowKey; } else if (y == null) { rrKey = yRowKey; } exceptions.add(new KeyedValues3DItemKey<>(seriesKey, rrKey, colKey)); } if (nullConversion.equals(NullConversion.THROW_EXCEPTION)) { Comparable rrKey = zRowKey; if (x == null) { rrKey = yRowKey; } else if (y == null) { rrKey = yRowKey; } throw new RuntimeException("There is a null value for " + "the item [" + seriesKey +", " + rrKey + ", " + colKey + "]."); } if (nullConversion != NullConversion.SKIP) { double xx = convert(x, nullConversion); double yy = convert(y, nullConversion); double zz = convert(z, nullConversion); series.add(xx, yy, zz); } } } dataset.add(series); } return dataset; } /** * Creates an {@link XYZDataset} by extracting values from specified * columns in a {@link KeyedValues3D} instance, across all the available * rows (items where any of the x, y or z values is {@code null} are * skipped). The new dataset contains a copy of the data and is completely * independent of the {@code source} dataset. *

* Note that {@link CategoryDataset3D} is an extension of * {@link KeyedValues3D} so you can use this method for any implementation * of the {@code CategoryDataset3D} interface. * * @param the series key (must implement Comparable). * @param the row key (must implement Comparable). * @param the column key (must implement Comparable). * @param source the source data ({@code null} not permitted). * @param xColKey the column key for x-values ({@code null} not permitted). * @param yColKey the column key for y-values ({@code null} not permitted). * @param zColKey the column key for z-values ({@code null} not permitted). * * @return A new dataset. * * @since 1.3 */ public static , R extends Comparable, C extends Comparable> XYZDataset extractXYZDatasetFromColumns( KeyedValues3D source, C xColKey, C yColKey, C zColKey) { return extractXYZDatasetFromColumns(source, xColKey, yColKey, zColKey, NullConversion.SKIP, null); } /** * Creates an {@link XYZDataset} by extracting values from specified * columns in a {@link KeyedValues3D} instance. The new dataset contains * a copy of the data and is completely independent of the * {@code source} dataset. Note that {@link CategoryDataset3D} is an * extension of {@link KeyedValues3D}. *

* Special handling is provided for items that contain {@code null} * values. The caller may pass in an {@code exceptions} list ( * normally empty) that will be populated with the keys of the items that * receive special handling, if any. * * @param the series key (must implement Comparable). * @param the row key (must implement Comparable). * @param the column key (must implement Comparable). * @param source the source data ({@code null} not permitted). * @param xColKey the column key for x-values ({@code null} not permitted). * @param yColKey the column key for y-values ({@code null} not permitted). * @param zColKey the column key for z-values ({@code null} not permitted). * @param nullConversion specifies the treatment for {@code null} * values in the dataset ({@code null} not permitted). * @param exceptions a list that, if not null, will be populated with * keys for the items in the source dataset that contain * {@code null} values ({@code null} permitted). * * @return A new dataset. * * @since 1.3 */ public static , R extends Comparable, C extends Comparable> XYZDataset extractXYZDatasetFromColumns( KeyedValues3D source, C xColKey, C yColKey, C zColKey, NullConversion nullConversion, List exceptions) { Args.nullNotPermitted(source, "source"); Args.nullNotPermitted(xColKey, "xColKey"); Args.nullNotPermitted(yColKey, "yColKey"); Args.nullNotPermitted(zColKey, "zColKey"); XYZSeriesCollection dataset = new XYZSeriesCollection<>(); for (S seriesKey : source.getSeriesKeys()) { XYZSeries series = new XYZSeries<>(seriesKey); for (R rowKey : source.getRowKeys()) { Number x = source.getValue(seriesKey, rowKey, xColKey); Number y = source.getValue(seriesKey, rowKey, yColKey); Number z = source.getValue(seriesKey, rowKey, zColKey); if (x != null && y != null && z != null) { series.add(x.doubleValue(), y.doubleValue(), z.doubleValue()); } else { if (exceptions != null) { // add only one key ref out of the possible 3 per item C ccKey = zColKey; if (x == null) { ccKey = xColKey; } else if (y == null) { ccKey = yColKey; } exceptions.add(new KeyedValues3DItemKey<>(seriesKey, rowKey, ccKey)); } if (nullConversion.equals(NullConversion.THROW_EXCEPTION)) { Comparable ccKey = zColKey; if (x == null) { ccKey = xColKey; } else if (y == null) { ccKey = yColKey; } throw new RuntimeException("There is a null value for " + "the item [" + seriesKey +", " + rowKey + ", " + ccKey + "]."); } if (nullConversion != NullConversion.SKIP) { double xx = convert(x, nullConversion); double yy = convert(y, nullConversion); double zz = convert(z, nullConversion); series.add(xx, yy, zz); } } } dataset.add(series); } return dataset; } /** * Returns a double primitive for the specified number, with * {@code null} values returning {@code Double.NaN} except in the * case of {@code CONVERT_TO_ZERO} which returns 0.0. Note that this * method does not throw an exception for {@code THROW_EXCEPTION}, it * expects code higher up the call chain to handle that (because there is * not enough information here to throw a useful exception). * * @param n the number ({@code null} permitted). * @param nullConversion the null conversion ({@code null} not * permitted). * * @return A double primitive. */ private static double convert(Number n, NullConversion nullConversion) { if (n != null) { return n.doubleValue(); } else { if (nullConversion.equals(NullConversion.CONVERT_TO_ZERO)) { return 0.0; } return Double.NaN; } } }