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 */ 032package org.jfree.chart3d.plot; 033 034import java.awt.BasicStroke; 035import java.awt.Color; 036import java.awt.Paint; 037import java.awt.Stroke; 038import java.io.IOException; 039import java.io.ObjectInputStream; 040import java.io.ObjectOutputStream; 041import java.io.Serializable; 042import java.util.ArrayList; 043import java.util.List; 044 045import org.jfree.chart3d.Chart3D; 046import org.jfree.chart3d.ChartElementVisitor; 047import org.jfree.chart3d.axis.Axis3DChangeEvent; 048import org.jfree.chart3d.axis.Axis3DChangeListener; 049import org.jfree.chart3d.axis.CategoryAxis3D; 050import org.jfree.chart3d.axis.ValueAxis3D; 051import org.jfree.chart3d.data.Dataset3DChangeEvent; 052import org.jfree.chart3d.data.ItemKey; 053import org.jfree.chart3d.data.KeyedValues3DItemKey; 054import org.jfree.chart3d.data.category.CategoryDataset3D; 055import org.jfree.chart3d.graphics3d.Dimension3D; 056import org.jfree.chart3d.graphics3d.World; 057import org.jfree.chart3d.internal.Args; 058import org.jfree.chart3d.internal.ObjectUtils; 059import org.jfree.chart3d.internal.SerialUtils; 060import org.jfree.chart3d.label.CategoryItemLabelGenerator; 061import org.jfree.chart3d.label.CategoryLabelGenerator; 062import org.jfree.chart3d.label.StandardCategoryItemLabelGenerator; 063import org.jfree.chart3d.label.StandardCategoryLabelGenerator; 064import org.jfree.chart3d.legend.LegendItemInfo; 065import org.jfree.chart3d.legend.StandardLegendItemInfo; 066import org.jfree.chart3d.renderer.Renderer3DChangeEvent; 067import org.jfree.chart3d.renderer.Renderer3DChangeListener; 068import org.jfree.chart3d.renderer.category.CategoryRenderer3D; 069 070/** 071 * A 3D plot with two category axes (x and z) and a numerical y-axis that can 072 * display data from a {@link CategoryDataset3D}. 073 * <br><br> 074 * The plot implements several listener interfaces so that it can receive 075 * notification of changes to its dataset, axes and renderer. When change events 076 * are received, the plot passes on a {@link Plot3DChangeEvent} to the 077 * {@link Chart3D} instance that owns the plot. This event chain is the 078 * mechanism that ensures that charts are repainted whenever the dataset 079 * changes, or when changes are made to the configuration of any chart 080 * component. 081 * <br><br> 082 * NOTE: This class is serializable, but the serialization format is subject to 083 * change in future releases and should not be relied upon for persisting 084 * instances of this class. 085 */ 086@SuppressWarnings("serial") 087public class CategoryPlot3D extends AbstractPlot3D 088 implements Axis3DChangeListener, Renderer3DChangeListener, 089 Serializable { 090 091 private static final Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke(0.5f, 092 BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 1f, 093 new float[]{3f, 3f}, 0f); 094 095 /** 096 * The dataset. 097 */ 098 private CategoryDataset3D dataset; 099 100 /** 101 * The renderer (never {@code null}). 102 */ 103 private CategoryRenderer3D renderer; 104 105 /** 106 * The row axis. 107 */ 108 private CategoryAxis3D rowAxis; 109 110 /** 111 * The column axis. 112 */ 113 private CategoryAxis3D columnAxis; 114 115 /** 116 * The value axis. 117 */ 118 private ValueAxis3D valueAxis; 119 120 /** 121 * Are gridlines shown for the row (z) axis? 122 */ 123 private boolean gridlinesVisibleForRows; 124 125 /** 126 * The paint for the row axis gridlines (never {@code null}). 127 */ 128 private transient Paint gridlinePaintForRows; 129 130 /** 131 * The stroke for the row axis gridlines (never {@code null}). 132 */ 133 private transient Stroke gridlineStrokeForRows; 134 135 /** 136 * Are gridlines shown for the column (x) axis? 137 */ 138 private boolean gridlinesVisibleForColumns; 139 140 /** 141 * The paint for the column axis gridlines (never {@code null}). 142 */ 143 private transient Paint gridlinePaintForColumns; 144 145 /** 146 * The stroke for the column axis gridlines (never {@code null}). 147 */ 148 private transient Stroke gridlineStrokeForColumns; 149 150 /** 151 * Are gridlines shown for the value axis? 152 */ 153 private boolean gridlinesVisibleForValues; 154 155 /** 156 * The paint for the value axis gridlines (never {@code null}). 157 */ 158 private transient Paint gridlinePaintForValues; 159 160 /** 161 * The stroke for the value axis gridlines (never {@code null}). 162 */ 163 private transient Stroke gridlineStrokeForValues; 164 165 /** 166 * The legend label generator. 167 */ 168 private CategoryLabelGenerator legendLabelGenerator; 169 170 /** 171 * A special attribute to provide control over the y-dimension for the plot 172 * when the plot dimensions are auto-calculated. The default value is 173 * {@code null}. 174 * 175 * @since 1.2 176 */ 177 private Double yDimensionOverride; 178 179 /** 180 * The tool tip generator (if null there will be no tool tips). 181 * 182 * @since 1.3 183 */ 184 private CategoryItemLabelGenerator toolTipGenerator; 185 186 /** 187 * Creates a new plot with the supplied dataset, renderer and axes. 188 * 189 * @param dataset the dataset ({@code null} not permitted). 190 * @param renderer the renderer ({@code null} not permitted). 191 * @param rowAxis the row axis ({@code null} not permitted). 192 * @param columnAxis the column axis ({@code null} not permitted). 193 * @param valueAxis the value axis ({@code null} not permitted). 194 */ 195 public CategoryPlot3D(CategoryDataset3D dataset, 196 CategoryRenderer3D renderer, CategoryAxis3D rowAxis, 197 CategoryAxis3D columnAxis, ValueAxis3D valueAxis) { 198 Args.nullNotPermitted(dataset, "dataset"); 199 Args.nullNotPermitted(renderer, "renderer"); 200 Args.nullNotPermitted(rowAxis, "rowAxis"); 201 Args.nullNotPermitted(columnAxis, "columnAxis"); 202 Args.nullNotPermitted(valueAxis, "valueAxis"); 203 this.dataset = dataset; 204 this.dataset.addChangeListener(this); 205 this.dimensions = calculateDimensions(); 206 this.renderer = renderer; 207 this.renderer.setPlot(this); 208 this.renderer.addChangeListener(this); 209 this.rowAxis = rowAxis; 210 this.rowAxis.addChangeListener(this); 211 this.columnAxis = columnAxis; 212 this.columnAxis.addChangeListener(this); 213 this.valueAxis = valueAxis; 214 this.valueAxis.addChangeListener(this); 215 this.rowAxis.configureAsRowAxis(this); 216 this.columnAxis.configureAsColumnAxis(this); 217 this.valueAxis.configureAsValueAxis(this); 218 this.gridlinesVisibleForValues = true; 219 this.gridlinesVisibleForColumns = false; 220 this.gridlinesVisibleForRows = false; 221 this.gridlinePaintForRows = Color.WHITE; 222 this.gridlinePaintForColumns = Color.WHITE; 223 this.gridlinePaintForValues = Color.WHITE; 224 this.gridlineStrokeForRows = DEFAULT_GRIDLINE_STROKE; 225 this.gridlineStrokeForColumns = DEFAULT_GRIDLINE_STROKE; 226 this.gridlineStrokeForValues = DEFAULT_GRIDLINE_STROKE; 227 this.legendLabelGenerator = new StandardCategoryLabelGenerator(); 228 this.yDimensionOverride = null; 229 this.toolTipGenerator = new StandardCategoryItemLabelGenerator(); 230 } 231 232 /** 233 * Sets the flag that controls whether the plot's dimensions are 234 * automatically calculated and, if {@code true}, sends a change event to 235 * all registered listeners. 236 * 237 * @param auto the new flag value. 238 * 239 * @since 1.2 240 */ 241 public void setAutoAdjustDimensions(boolean auto) { 242 this.autoAdjustDimensions = auto; 243 if (auto) { 244 this.dimensions = calculateDimensions(); 245 fireChangeEvent(true); 246 } 247 } 248 249 /** 250 * Sets the dimensions (in 3D space) for the plot, resets the 251 * {@code autoAdjustDimensions} flag to {@code false}, and sends a 252 * {@link Plot3DChangeEvent} to all registered listeners. 253 * 254 * @param dimensions the dimensions ({@code null} not permitted). 255 * 256 * @see Plot3D#getDimensions() 257 */ 258 public void setDimensions(Dimension3D dimensions) { 259 Args.nullNotPermitted(dimensions, "dimensions"); 260 this.dimensions = dimensions; 261 this.autoAdjustDimensions = false; 262 fireChangeEvent(true); 263 } 264 265 /** 266 * Returns the dataset for the chart. 267 * 268 * @return The dataset (never {@code null}). 269 */ 270 public CategoryDataset3D getDataset() { 271 return this.dataset; 272 } 273 274 /** 275 * Sets the dataset and sends a {@link Plot3DChangeEvent} to all registered 276 * listeners. When you call this method, the axes will be reconfigured for 277 * the new data. 278 * 279 * @param dataset the dataset ({@code null} not permitted). 280 */ 281 public void setDataset(CategoryDataset3D dataset) { 282 Args.nullNotPermitted(dataset, "dataset"); 283 this.dataset.removeChangeListener(this); 284 this.dataset = dataset; 285 this.dataset.addChangeListener(this); 286 // we send ourselves a dataset change event since this will 287 // reconfigure the axes then trigger the required plot change event 288 datasetChanged(new Dataset3DChangeEvent(this, this.dataset)); 289 } 290 291 /** 292 * Returns the renderer (very often you will need to cast this to a specific 293 * class to make customisations). 294 * 295 * @return The renderer (never {@code null}). 296 */ 297 public CategoryRenderer3D getRenderer() { 298 return this.renderer; 299 } 300 301 /** 302 * Sets the renderer and sends a change event to all registered listeners. 303 * 304 * @param renderer the renderer ({@code null} not permitted). 305 */ 306 public void setRenderer(CategoryRenderer3D renderer) { 307 Args.nullNotPermitted(renderer, "renderer"); 308 this.renderer.removeChangeListener(this); 309 this.renderer = renderer; 310 this.renderer.addChangeListener(this); 311 // a new renderer might mean the axis range needs changing... 312 this.valueAxis.configureAsValueAxis(this); 313 fireChangeEvent(true); 314 } 315 316 /** 317 * Returns the row axis. 318 * 319 * @return The row axis. 320 */ 321 public CategoryAxis3D getRowAxis() { 322 return this.rowAxis; 323 } 324 325 /** 326 * Sets the row axis and sends a {@link Plot3DChangeEvent} to all registered 327 * listeners. The row axis is equivalent to the z-axis. 328 * 329 * @param axis the row axis ({@code null} not permitted). 330 */ 331 public void setRowAxis(CategoryAxis3D axis) { 332 Args.nullNotPermitted(axis, "axis"); 333 this.rowAxis.removeChangeListener(this); 334 this.rowAxis = axis; 335 this.rowAxis.addChangeListener(this); 336 fireChangeEvent(true); 337 } 338 339 /** 340 * Returns the column axis. 341 * 342 * @return The column axis (never {@code null}). 343 */ 344 public CategoryAxis3D getColumnAxis() { 345 return this.columnAxis; 346 } 347 348 /** 349 * Sets the column axis and sends a {@link Plot3DChangeEvent} to all 350 * registered listeners. 351 * 352 * @param axis the new axis ({@code null} not permitted). 353 * 354 * @see #setRowAxis(org.jfree.chart3d.axis.CategoryAxis3D) 355 * @see #setValueAxis(org.jfree.chart3d.axis.ValueAxis3D) 356 * 357 */ 358 public void setColumnAxis(CategoryAxis3D axis) { 359 Args.nullNotPermitted(axis, "axis"); 360 this.columnAxis.removeChangeListener(this); 361 this.columnAxis = axis; 362 this.columnAxis.addChangeListener(this); 363 fireChangeEvent(true); 364 } 365 366 /** 367 * Returns the value axis (the vertical axis in the plot). 368 * 369 * @return The value axis (never {@code null}). 370 */ 371 public ValueAxis3D getValueAxis() { 372 return this.valueAxis; 373 } 374 375 /** 376 * Sets the value axis and sends a {@link Plot3DChangeEvent} to all 377 * registered listeners. 378 * 379 * @param axis the axis ({@code null} not permitted). 380 */ 381 public void setValueAxis(ValueAxis3D axis) { 382 Args.nullNotPermitted(axis, "axis"); 383 this.valueAxis.removeChangeListener(this); 384 this.valueAxis = axis; 385 this.valueAxis.configureAsValueAxis(this); 386 this.valueAxis.addChangeListener(this); 387 fireChangeEvent(true); 388 } 389 390 /** 391 * Returns {@code true} if gridlines are shown for the column axis and 392 * {@code false} otherwise. The default value is {@code false}. 393 * 394 * @return A boolean. 395 */ 396 public boolean getGridlinesVisibleForRows() { 397 return this.gridlinesVisibleForRows; 398 } 399 400 /** 401 * Sets the flag that controls whether or not gridlines are shown for the 402 * row axis and sends a {@link Plot3DChangeEvent} to all registered 403 * listeners. 404 * 405 * @param visible the new flag value. 406 */ 407 public void setGridlinesVisibleForRows(boolean visible) { 408 this.gridlinesVisibleForRows = visible; 409 fireChangeEvent(false); 410 } 411 412 /** 413 * Returns the paint used to draw the gridlines for the row axis, if they 414 * are visible. 415 * 416 * @return The paint (never {@code null}). 417 */ 418 public Paint getGridlinePaintForRows() { 419 return this.gridlinePaintForRows; 420 } 421 422 /** 423 * Sets the paint used for the row axis gridlines and sends a 424 * {@link Plot3DChangeEvent} to all registered listeners. 425 * 426 * @param paint the paint ({@code null} not permitted). 427 */ 428 public void setGridlinePaintForRows(Paint paint) { 429 Args.nullNotPermitted(paint, "paint"); 430 this.gridlinePaintForRows = paint; 431 fireChangeEvent(false); 432 } 433 434 /** 435 * Returns the stroke for the gridlines associated with the row axis. The 436 * default value is {@code BasicStroke(0.5f, BasicStroke.CAP_ROUND, 437 * BasicStroke.JOIN_ROUND, 1f, new float[] { 3f, 3f }, 0f)}. 438 * 439 * @return The stroke (never {@code null}). 440 */ 441 public Stroke getGridlineStrokeForRows() { 442 return this.gridlineStrokeForRows; 443 } 444 445 /** 446 * Sets the stroke used to draw the gridlines for the row axis, if they are 447 * visible, and sends a {@link Plot3DChangeEvent} to all registered 448 * listeners. 449 * 450 * @param stroke the stroke ({@code null} not permitted). 451 */ 452 public void setGridlineStrokeForRows(Stroke stroke) { 453 Args.nullNotPermitted(stroke, "stroke"); 454 this.gridlineStrokeForRows = stroke; 455 fireChangeEvent(false); 456 } 457 458 /** 459 * Returns {@code true} if gridlines are shown for the column axis and 460 * {@code false} otherwise. The default value is {@code false}. 461 * 462 * @return A boolean. 463 */ 464 public boolean getGridlinesVisibleForColumns() { 465 return this.gridlinesVisibleForColumns; 466 } 467 468 /** 469 * Sets the flag that controls whether or not gridlines are shown for the 470 * column axis and sends a {@link Plot3DChangeEvent} to all registered 471 * listeners. 472 * 473 * @param visible the new flag value. 474 */ 475 public void setGridlinesVisibleForColumns(boolean visible) { 476 this.gridlinesVisibleForColumns = visible; 477 fireChangeEvent(false); 478 } 479 480 /** 481 * Returns {@code true} if gridlines are shown for the column axis and 482 * {@code false} otherwise. The default value is {@code true}. 483 * 484 * @return A boolean. 485 */ 486 public boolean getGridlinesVisibleForValues() { 487 return this.gridlinesVisibleForValues; 488 } 489 490 /** 491 * Sets the flag that controls whether or not gridlines are shown for the 492 * value axis and sends a {@link Plot3DChangeEvent} to all registered 493 * listeners. 494 * 495 * @param visible the new flag value. 496 */ 497 public void setGridlinesVisibleForValues(boolean visible) { 498 this.gridlinesVisibleForValues = visible; 499 fireChangeEvent(false); 500 } 501 502 /** 503 * Returns the paint for the gridlines associated with the value axis. The 504 * default value is {@code Color.WHITE}. 505 * 506 * @return The paint for value axis gridlines (never {@code null}). 507 */ 508 public Paint getGridlinePaintForValues() { 509 return this.gridlinePaintForValues; 510 } 511 512 /** 513 * Sets the paint used for the value axis gridlines and sends a 514 * {@link Plot3DChangeEvent} to all registered listeners. 515 * 516 * @param paint the paint ({@code null} not permitted). 517 */ 518 public void setGridlinePaintForValues(Paint paint) { 519 Args.nullNotPermitted(paint, "paint"); 520 this.gridlinePaintForValues = paint; 521 fireChangeEvent(false); 522 } 523 524 /** 525 * Returns the stroke for the gridlines associated with the value axis. The 526 * default value is {@code BasicStroke(0.5f, BasicStroke.CAP_ROUND, 527 * BasicStroke.JOIN_ROUND, 1f, new float[] { 3f, 3f }, 0f)}. 528 * 529 * @return The stroke (never {@code null}). 530 */ 531 public Stroke getGridlineStrokeForValues() { 532 return this.gridlineStrokeForValues; 533 } 534 535 /** 536 * Sets the stroke used to draw the grid lines for the value axis, if they 537 * are visible, and sends a {@link Plot3DChangeEvent} to all registered 538 * listeners. 539 * 540 * @param stroke the stroke ({@code null} not permitted). 541 */ 542 public void setGridlineStrokeForValues(Stroke stroke) { 543 Args.nullNotPermitted(stroke, "stroke"); 544 this.gridlineStrokeForValues = stroke; 545 fireChangeEvent(false); 546 } 547 548 /** 549 * Returns the paint used to draw the grid lines for the column axis, if 550 * they are visible. The default value is {@code Color.WHITE}. 551 * 552 * @return The paint (never {@code null}). 553 */ 554 public Paint getGridlinePaintForColumns() { 555 return this.gridlinePaintForColumns; 556 } 557 558 /** 559 * Sets the paint used to draw the grid lines for the column axis, if they 560 * are visible, and sends a {@link Plot3DChangeEvent} to all registered 561 * listeners. 562 * 563 * @param paint the paint ({@code null} not permitted). 564 */ 565 public void setGridlinePaintForColumns(Paint paint) { 566 Args.nullNotPermitted(paint, "paint"); 567 this.gridlinePaintForColumns = paint; 568 fireChangeEvent(false); 569 } 570 571 /** 572 * Returns the stroke for the gridlines associated with the column axis. The 573 * default value is {@code BasicStroke(0.5f, BasicStroke.CAP_ROUND, 574 * BasicStroke.JOIN_ROUND, 1f, new float[] { 3f, 3f }, 0f)}. 575 * 576 * @return The stroke (never {@code null}). 577 */ 578 public Stroke getGridlineStrokeForColumns() { 579 return this.gridlineStrokeForColumns; 580 } 581 582 /** 583 * Sets the stroke used to draw the grid lines for the column axis, if they 584 * are visible, and sends a {@link Plot3DChangeEvent} to all registered 585 * listeners. 586 * 587 * @param stroke the stroke ({@code null} not permitted). 588 */ 589 public void setGridlineStrokeForColumns(Stroke stroke) { 590 Args.nullNotPermitted(stroke, "stroke"); 591 this.gridlineStrokeForColumns = stroke; 592 fireChangeEvent(false); 593 } 594 595 /** 596 * Returns the legend label generator, an object that converts key values in 597 * the dataset into corresponding strings for presentation in the chart. 598 * 599 * @return The legend label generator (never {@code null}). 600 * 601 * @since 1.2 602 */ 603 public CategoryLabelGenerator getLegendLabelGenerator() { 604 return this.legendLabelGenerator; 605 } 606 607 /** 608 * Sets the legend label generator and sends a {@link Plot3DChangeEvent} to 609 * all registered listeners. 610 * 611 * @param generator the generator ({@code null} not permitted). 612 * 613 * @since 1.2 614 */ 615 public void setLegendLabelGenerator(CategoryLabelGenerator generator) { 616 Args.nullNotPermitted(generator, "generator"); 617 this.legendLabelGenerator = generator; 618 fireChangeEvent(false); 619 } 620 621 /** 622 * Returns the y-dimension override. The default value is {@code null}, 623 * which means that when the plot dimensions are automatically calculated, 624 * the height of the plot will be set to the greater of the width and the 625 * depth. 626 * 627 * @return The y-dimension override (possibly {@code null}). 628 * 629 * @since 1.2 630 */ 631 public Double getYDimensionOverride() { 632 return this.yDimensionOverride; 633 } 634 635 /** 636 * Sets the y-dimension override and, if the {@code autoAdjustDimensions} 637 * flag is set, recalculates the dimensions and sends a 638 * {@link Plot3DChangeEvent} to all registered listeners. 639 * 640 * @param dim the new y-dimension override ({@code null} permitted). 641 * 642 * @since 1.2 643 */ 644 public void setYDimensionOverride(Double dim) { 645 this.yDimensionOverride = dim; 646 if (this.autoAdjustDimensions) { 647 this.dimensions = calculateDimensions(); 648 fireChangeEvent(true); 649 } 650 } 651 652 /** 653 * Returns the tool tip generator. This is an object that calculates and 654 * returns a string (that will be used as the tool tip) for any given data 655 * value in the dataset. 656 * 657 * @return The tool tip generator (possibly {@code null}). 658 * 659 * @since 1.3 660 */ 661 public CategoryItemLabelGenerator getToolTipGenerator() { 662 return this.toolTipGenerator; 663 } 664 665 /** 666 * Sets the tool tip generator and sends a {@link Plot3DChangeEvent} to all 667 * registered listeners. 668 * 669 * @param generator the new generator ({@code null} permitted). 670 * 671 * @since 1.3 672 */ 673 public void setToolTipGenerator(CategoryItemLabelGenerator generator) { 674 this.toolTipGenerator = generator; 675 fireChangeEvent(false); 676 } 677 678 /** 679 * Returns a list containing legend item info, typically one item for each 680 * series in the chart. This is intended for use in the construction of a 681 * chart legend. 682 * 683 * @return A list containing legend item info (possibly empty but never 684 * {@code null}). 685 */ 686 @Override 687 @SuppressWarnings("unchecked") // we don't know the dataset generic types 688 public List<LegendItemInfo> getLegendInfo() { 689 List<LegendItemInfo> result = new ArrayList<>(); 690 List<Comparable<?>> keys = this.dataset.getSeriesKeys(); 691 for (Comparable<?> key : keys) { 692 int series = this.dataset.getSeriesIndex(key); 693 Color color = this.renderer.getColorSource().getLegendColor(series); 694 String seriesLabel = this.legendLabelGenerator.generateSeriesLabel( 695 this.dataset, key); 696 LegendItemInfo info = new StandardLegendItemInfo(key, 697 seriesLabel, color); 698 result.add(info); 699 } 700 return result; 701 } 702 703 @Override 704 public void compose(World world, double xOffset, double yOffset, 705 double zOffset) { 706 for (int series = 0; series < this.dataset.getSeriesCount(); series++) { 707 for (int row = 0; row < this.dataset.getRowCount(); row++) { 708 for (int column = 0; column < this.dataset.getColumnCount(); 709 column++) { 710 this.renderer.composeItem(this.dataset, series, row, column, 711 world, getDimensions(), xOffset, yOffset, zOffset); 712 } 713 } 714 } 715 } 716 717 @Override 718 public String generateToolTipText(ItemKey itemKey) { 719 if (!(itemKey instanceof KeyedValues3DItemKey)) { 720 throw new IllegalArgumentException( 721 "The itemKey must be a Values3DItemKey instance."); 722 } 723 KeyedValues3DItemKey vik = (KeyedValues3DItemKey) itemKey; 724 return this.toolTipGenerator.generateItemLabel(dataset, 725 vik.getSeriesKey(), vik.getRowKey(), vik.getColumnKey()); 726 } 727 728 /** 729 * Accepts a visitor for the plot. This method first calls the 730 * {@code receive()} method for each of the plot's axes and the renderer, 731 * then performs the visitor's function on the plot. This is a general 732 * purpose mechanism, but the main use is to apply chart style changes 733 * across all the elements of a chart. 734 * 735 * @param visitor the visitor ({@code null} not permitted). 736 * 737 * @since 1.2 738 */ 739 @Override 740 public void receive(ChartElementVisitor visitor) { 741 this.columnAxis.receive(visitor); 742 this.rowAxis.receive(visitor); 743 this.valueAxis.receive(visitor); 744 this.renderer.receive(visitor); 745 visitor.visit(this); 746 } 747 748 /** 749 * Tests this plot for equality with an arbitrary object. 750 * 751 * @param obj the object ({@code null} permitted). 752 * 753 * @return A boolean. 754 */ 755 @Override 756 public boolean equals(Object obj) { 757 if (obj == this) { 758 return true; 759 } 760 if (!(obj instanceof CategoryPlot3D)) { 761 return false; 762 } 763 CategoryPlot3D that = (CategoryPlot3D) obj; 764 if (this.gridlinesVisibleForRows != that.gridlinesVisibleForRows) { 765 return false; 766 } 767 if (!this.gridlineStrokeForRows.equals(that.gridlineStrokeForRows)) { 768 return false; 769 } 770 if (!ObjectUtils.equalsPaint(this.gridlinePaintForRows, 771 that.gridlinePaintForRows)) { 772 return false; 773 } 774 if (this.gridlinesVisibleForColumns 775 != that.gridlinesVisibleForColumns) { 776 return false; 777 } 778 if (!this.gridlineStrokeForColumns.equals( 779 that.gridlineStrokeForColumns)) { 780 return false; 781 } 782 if (!ObjectUtils.equalsPaint(this.gridlinePaintForColumns, 783 that.gridlinePaintForColumns)) { 784 return false; 785 } 786 if (this.gridlinesVisibleForValues != that.gridlinesVisibleForValues) { 787 return false; 788 } 789 if (!this.gridlineStrokeForValues.equals(that.gridlineStrokeForValues)) { 790 return false; 791 } 792 if (!ObjectUtils.equalsPaint(this.gridlinePaintForValues, 793 that.gridlinePaintForValues)) { 794 return false; 795 } 796 if (!this.legendLabelGenerator.equals(that.legendLabelGenerator)) { 797 return false; 798 } 799 if (!ObjectUtils.equals(this.yDimensionOverride, 800 that.yDimensionOverride)) { 801 return false; 802 } 803 if (!ObjectUtils.equals(this.toolTipGenerator, that.toolTipGenerator)) { 804 return false; 805 } 806 return super.equals(obj); 807 } 808 809 /** 810 * Receives notification of a change to the dataset and handles this by 811 * adjusting the plot dimensions (according to the setting of the 812 * {@code autoAdjustDimensions} flag), reconfiguring the axes, and 813 * propagating a {@code Plot3DChangeEvent}. 814 * 815 * @param event the change event. 816 */ 817 @Override 818 public void datasetChanged(Dataset3DChangeEvent event) { 819 // update the category axis labels 820 // and the value axis range 821 if (this.autoAdjustDimensions) { 822 this.dimensions = calculateDimensions(); 823 } 824 this.columnAxis.configureAsColumnAxis(this); 825 this.rowAxis.configureAsRowAxis(this); 826 this.valueAxis.configureAsValueAxis(this); 827 super.datasetChanged(event); // propagates a plot change event 828 } 829 830 /** 831 * Returns the dimensions for the plot that best suit the current data 832 * values. The x-dimension is set to the number of columns in the dataset 833 * and the z-dimension is set to the number of rows in the dataset. For the 834 * y-dimension, the code first checks the {@code yDimensionOverride} 835 * attribute to see if a specific value is requested...and if not, the 836 * minimum of the x and z dimensions will be used. 837 * 838 * @return The dimensions (never {@code null}). 839 */ 840 private Dimension3D calculateDimensions() { 841 double depth = Math.max(1.0, this.dataset.getRowCount() + 1); 842 double width = Math.max(1.0, this.dataset.getColumnCount() + 1); 843 double height = Math.max(1.0, Math.min(width, depth)); 844 if (this.yDimensionOverride != null) { 845 height = this.yDimensionOverride; 846 } 847 return new Dimension3D(width, height, depth); 848 } 849 850 /** 851 * Receives notification that one of the axes has been changed. This will 852 * trigger a {@link Plot3DChangeEvent} that will usually cause the chart to 853 * be repainted. 854 * 855 * @param event the change event. 856 */ 857 @Override 858 public void axisChanged(Axis3DChangeEvent event) { 859 // for now we just fire a plot change event which will flow up the 860 // chain and eventually trigger a chart repaint 861 fireChangeEvent(event.requiresWorldUpdate()); 862 } 863 864 /** 865 * Receives notification that the renderer has been modified in some way. 866 * This will trigger a {@link Plot3DChangeEvent} that will usually cause the 867 * chart to be repainted. 868 * 869 * @param event information about the event. 870 */ 871 @Override 872 public void rendererChanged(Renderer3DChangeEvent event) { 873 // for now we just fire a plot change event which will flow up the 874 // chain and eventually trigger a chart repaint 875 fireChangeEvent(event.requiresWorldUpdate()); 876 } 877 878 /** 879 * Provides serialization support. 880 * 881 * @param stream the output stream. 882 * 883 * @throws IOException if there is an I/O error. 884 */ 885 private void writeObject(ObjectOutputStream stream) throws IOException { 886 stream.defaultWriteObject(); 887 SerialUtils.writePaint(this.gridlinePaintForRows, stream); 888 SerialUtils.writePaint(this.gridlinePaintForColumns, stream); 889 SerialUtils.writePaint(this.gridlinePaintForValues, stream); 890 SerialUtils.writeStroke(this.gridlineStrokeForRows, stream); 891 SerialUtils.writeStroke(this.gridlineStrokeForColumns, stream); 892 SerialUtils.writeStroke(this.gridlineStrokeForValues, stream); 893 } 894 895 /** 896 * Provides serialization support. 897 * 898 * @param stream the input stream. 899 * 900 * @throws IOException if there is an I/O error. 901 * @throws ClassNotFoundException if there is a classpath problem. 902 */ 903 private void readObject(ObjectInputStream stream) 904 throws IOException, ClassNotFoundException { 905 stream.defaultReadObject(); 906 this.gridlinePaintForRows = SerialUtils.readPaint(stream); 907 this.gridlinePaintForColumns = SerialUtils.readPaint(stream); 908 this.gridlinePaintForValues = SerialUtils.readPaint(stream); 909 this.gridlineStrokeForRows = SerialUtils.readStroke(stream); 910 this.gridlineStrokeForColumns = SerialUtils.readStroke(stream); 911 this.gridlineStrokeForValues = SerialUtils.readStroke(stream); 912 } 913 914}