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