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