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}