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