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}