001/* =========================================================== 002 * Orson Charts : a 3D chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C)opyright 2013-2022, by David Gilbert. All rights reserved. 006 * 007 * https://github.com/jfree/orson-charts 008 * 009 * This program is free software: you can redistribute it and/or modify 010 * it under the terms of the GNU General Public License as published by 011 * the Free Software Foundation, either version 3 of the License, or 012 * (at your option) any later version. 013 * 014 * This program is distributed in the hope that it will be useful, 015 * but WITHOUT ANY WARRANTY; without even the implied warranty of 016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 017 * GNU General Public License for more details. 018 * 019 * You should have received a copy of the GNU General Public License 020 * along with this program. If not, see <http://www.gnu.org/licenses/>. 021 * 022 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 023 * Other names may be trademarks of their respective owners.] 024 * 025 * If you do not wish to be bound by the terms of the GPL, an alternative 026 * commercial license can be purchased. For details, please see visit the 027 * Orson Charts home page: 028 * 029 * http://www.object-refinery.com/orsoncharts/index.html 030 * 031 */ 032 033package org.jfree.chart3d.plot; 034 035import java.awt.Color; 036import java.awt.Font; 037import java.io.Serializable; 038import java.util.ArrayList; 039import java.util.List; 040 041import org.jfree.chart3d.internal.Args; 042import org.jfree.chart3d.Chart3D; 043import org.jfree.chart3d.Chart3DFactory; 044import org.jfree.chart3d.ChartElementVisitor; 045import org.jfree.chart3d.data.DataUtils; 046import org.jfree.chart3d.data.ItemKey; 047import org.jfree.chart3d.data.KeyedValuesItemKey; 048import org.jfree.chart3d.data.PieDataset3D; 049import org.jfree.chart3d.graphics3d.Dimension3D; 050import org.jfree.chart3d.graphics3d.Dot3D; 051import org.jfree.chart3d.graphics3d.Object3D; 052import org.jfree.chart3d.graphics3d.World; 053import org.jfree.chart3d.label.PieLabelGenerator; 054import org.jfree.chart3d.label.StandardPieLabelGenerator; 055import org.jfree.chart3d.legend.LegendItemInfo; 056import org.jfree.chart3d.legend.StandardLegendItemInfo; 057 058/** 059 * A plot for creating 3D pie charts. To create a pie chart, you can use the 060 * {@code createPieChart()} method in the {@link Chart3DFactory} class. 061 * A typical pie chart will look like this: 062 * <div> 063 * <img src="../../../../../doc-files/PieChart3DDemo1.svg" alt="PieChart3DDemo1.svg" 064 * width="500" height="359"> 065 * </div> 066 * <br><br> 067 * NOTE: This class is serializable, but the serialization format is subject 068 * to change in future releases and should not be relied upon for persisting 069 * instances of this class. 070 */ 071@SuppressWarnings("serial") 072public class PiePlot3D extends AbstractPlot3D implements Serializable { 073 074 /** The default font for section labels on the chart. */ 075 public static final Font DEFAULT_SECTION_LABEL_FONT 076 = new Font("Dialog", Font.PLAIN, 14); 077 078 /** The dataset. */ 079 private PieDataset3D<? extends Comparable> dataset; 080 081 /** The radius of the pie chart. */ 082 private double radius; 083 084 /** The depth of the pie chart. */ 085 private double depth; 086 087 /** The section color source. */ 088 private ColorSource sectionColorSource; 089 090 /** The section label generator. */ 091 private PieLabelGenerator sectionLabelGenerator; 092 093 /** The font source used to determine the font for section labels. */ 094 private FontSource sectionLabelFontSource; 095 096 /** 097 * The color source used to determine the foreground color for section 098 * labels. 099 */ 100 private ColorSource sectionLabelColorSource; 101 102 /** The legend label generator. */ 103 private PieLabelGenerator legendLabelGenerator; 104 105 /** 106 * The tool tip generator (can be null, in which case there will be no 107 * tool tips. */ 108 private PieLabelGenerator toolTipGenerator; 109 110 /** 111 * The number of segments used to render 360 degrees of the pie. A higher 112 * number will give better output but slower performance. 113 */ 114 private int segments = 40; 115 116 /** 117 * Creates a new pie plot in 3D. 118 * 119 * @param dataset the dataset ({@code null} not permitted). 120 */ 121 public PiePlot3D(PieDataset3D<? extends Comparable> dataset) { 122 Args.nullNotPermitted(dataset, "dataset"); 123 this.dataset = dataset; 124 this.dataset.addChangeListener(this); 125 this.radius = 4.0; 126 this.depth = 0.5; 127 this.sectionColorSource = new StandardColorSource(); 128 this.sectionLabelGenerator = new StandardPieLabelGenerator( 129 StandardPieLabelGenerator.KEY_ONLY_TEMPLATE); 130 this.sectionLabelFontSource = new StandardFontSource( 131 DEFAULT_SECTION_LABEL_FONT); 132 this.sectionLabelColorSource = new StandardColorSource(Color.BLACK); 133 this.legendLabelGenerator = new StandardPieLabelGenerator(); 134 this.toolTipGenerator = new StandardPieLabelGenerator( 135 StandardPieLabelGenerator.PERCENT_TEMPLATE_2DP); 136 } 137 138 /** 139 * Returns the dataset. 140 * 141 * @return The dataset (never {@code null}). 142 */ 143 public PieDataset3D<? extends Comparable> getDataset() { 144 return this.dataset; 145 } 146 147 /** 148 * Sets the dataset and notifies registered listeners that the dataset has 149 * been updated. 150 * 151 * @param dataset the dataset ({@code null} not permitted). 152 */ 153 public void setDataset(PieDataset3D<? extends Comparable> dataset) { 154 Args.nullNotPermitted(dataset, "dataset"); 155 this.dataset.removeChangeListener(this); 156 this.dataset = dataset; 157 this.dataset.addChangeListener(this); 158 fireChangeEvent(true); 159 } 160 161 /** 162 * Returns the radius of the pie (the default value is 8.0). 163 * 164 * @return The radius of the pie. 165 */ 166 public double getRadius() { 167 return this.radius; 168 } 169 170 /** 171 * Sets the radius of the pie chart and sends a change event to all 172 * registered listeners. 173 * 174 * @param radius the radius. 175 */ 176 public void setRadius(double radius) { 177 this.radius = radius; 178 fireChangeEvent(true); 179 } 180 181 /** 182 * Returns the depth of the pie (the default value is 2.0). 183 * 184 * @return The depth of the pie. 185 */ 186 public double getDepth() { 187 return this.depth; 188 } 189 190 /** 191 * Sets the depth of the pie chart and sends a change event to all 192 * registered listeners. 193 * 194 * @param depth the depth. 195 */ 196 public void setDepth(double depth) { 197 this.depth = depth; 198 fireChangeEvent(true); 199 } 200 201 /** 202 * Returns the color source for section colors. 203 * 204 * @return The color source (never {@code null}). 205 */ 206 public ColorSource getSectionColorSource() { 207 return this.sectionColorSource; 208 } 209 210 /** 211 * Sets the color source and sends a {@link Plot3DChangeEvent} to all 212 * registered listeners. 213 * 214 * @param source the color source ({@code null} not permitted). 215 */ 216 public void setSectionColorSource(ColorSource source) { 217 Args.nullNotPermitted(source, "source"); 218 this.sectionColorSource = source; 219 fireChangeEvent(true); 220 } 221 222 /** 223 * Sets a new color source for the plot using the specified colors and 224 * sends a {@link Plot3DChangeEvent} to all registered listeners. This 225 * is a convenience method that is equivalent to 226 * {@code setSectionColorSource(new StandardColorSource(colors))}. 227 * 228 * @param colors one or more colors ({@code null} not permitted). 229 * 230 * @since 1.2 231 */ 232 public void setSectionColors(Color... colors) { 233 setSectionColorSource(new StandardColorSource(colors)); 234 } 235 236 /** 237 * Returns the object that creates labels for each section of the pie 238 * chart. 239 * 240 * @return The section label generator (never {@code null}). 241 * 242 * @since 1.2 243 */ 244 public PieLabelGenerator getSectionLabelGenerator() { 245 return this.sectionLabelGenerator; 246 } 247 248 /** 249 * Sets the object that creates labels for each section of the pie chart, 250 * and sends a {@link Plot3DChangeEvent} to all registered listeners. 251 * 252 * @param generator the generator ({@code null} not permitted). 253 * 254 * @since 1.2 255 */ 256 public void setSectionLabelGenerator(PieLabelGenerator generator) { 257 Args.nullNotPermitted(generator, "generator"); 258 this.sectionLabelGenerator = generator; 259 fireChangeEvent(false); 260 } 261 262 /** 263 * Returns the font source that is used to determine the font to use for 264 * the section labels. 265 * 266 * @return The font source for the section labels (never {@code null}). 267 */ 268 public FontSource getSectionLabelFontSource() { 269 return this.sectionLabelFontSource; 270 } 271 272 /** 273 * Sets the font source and sends a {@link Plot3DChangeEvent} to all 274 * registered listeners. 275 * 276 * @param source the source ({@code null} not permitted). 277 */ 278 public void setSectionLabelFontSource(FontSource source) { 279 Args.nullNotPermitted(source, "source"); 280 this.sectionLabelFontSource = source; 281 fireChangeEvent(false); 282 } 283 284 /** 285 * Returns the color source for section labels. The default value is 286 * an instance of {@link StandardColorSource} that always returns 287 * {@code Color.BLACK}. 288 * 289 * @return The color source (never {@code null}). 290 * 291 * @see #setSectionLabelColorSource(ColorSource) 292 */ 293 public ColorSource getSectionLabelColorSource() { 294 return this.sectionLabelColorSource; 295 } 296 297 /** 298 * Sets the color source for the section labels and sends a 299 * {@link Plot3DChangeEvent} to all registered listeners. 300 * 301 * @param source the color source. 302 * 303 * @see #getSectionLabelColorSource() 304 */ 305 public void setSectionLabelColorSource(ColorSource source) { 306 Args.nullNotPermitted(source, "source"); 307 this.sectionLabelColorSource = source; 308 fireChangeEvent(false); 309 } 310 311 /** 312 * Returns the object that creates legend labels for each section of the pie 313 * chart. 314 * 315 * @return The legend label generator (never {@code null}). 316 * 317 * @since 1.2 318 */ 319 public PieLabelGenerator getLegendLabelGenerator() { 320 return this.legendLabelGenerator; 321 } 322 323 /** 324 * Sets the object that creates legend labels for each section of the pie 325 * chart, and sends a {@link Plot3DChangeEvent} to all registered 326 * listeners. 327 * 328 * @param generator the generator ({@code null} not permitted). 329 * 330 * @since 1.2 331 */ 332 public void setLegendLabelGenerator(PieLabelGenerator generator) { 333 Args.nullNotPermitted(generator, "generator"); 334 this.legendLabelGenerator = generator; 335 fireChangeEvent(false); 336 } 337 338 /** 339 * Returns the tool tip generator. 340 * 341 * @return The tool tip generator (possibly {@code null}). 342 * 343 * @since 1.3 344 */ 345 public PieLabelGenerator getToolTipGenerator() { 346 return this.toolTipGenerator; 347 } 348 349 /** 350 * Sets the tool tip generator and sends a change event to all registered 351 * listeners. 352 * 353 * @param generator the generator ({@code null} permitted). 354 * 355 * @since 1.3 356 */ 357 public void setToolTipGenerator(PieLabelGenerator generator) { 358 this.toolTipGenerator = generator; 359 fireChangeEvent(false); 360 } 361 362 /** 363 * Returns the dimensions for the plot. For the pie chart, it is more 364 * natural to specify the dimensions in terms of a radius and a depth, so 365 * we use those values to calculate the dimensions here. 366 * 367 * @return The dimensions for the plot. 368 */ 369 @Override 370 public Dimension3D getDimensions() { 371 return new Dimension3D(this.radius * 2, this.depth, this.radius * 2); 372 } 373 374 /** 375 * Returns the number of segments used when composing the 3D objects 376 * representing the pie chart. The default value is {@code 40}. 377 * 378 * @return The number of segments used to compose the pie chart. 379 */ 380 public int getSegmentCount() { 381 return this.segments; 382 } 383 384 /** 385 * Sets the number of segments used when composing the pie chart and 386 * sends a {@link Plot3DChangeEvent} to all registered listeners. A higher 387 * number will result in a more rounded pie chart, but will take longer 388 * to render. 389 * 390 * @param count the count. 391 */ 392 public void setSegmentCount(int count) { 393 this.segments = count; 394 fireChangeEvent(true); 395 } 396 397 /** 398 * Returns a list containing legend item info, typically one item for 399 * each series in the chart. This is intended for use in the construction 400 * of a chart legend. 401 * 402 * @return A list containing legend item info. 403 */ 404 @Override @SuppressWarnings("unchecked") 405 public List<LegendItemInfo> getLegendInfo() { 406 List<LegendItemInfo> result = new ArrayList<>(); 407 for (Comparable<?> key : this.dataset.getKeys()) { 408 String label = this.legendLabelGenerator.generateLabel(dataset, 409 key); 410 LegendItemInfo info = new StandardLegendItemInfo(key, 411 label, this.sectionColorSource.getColor(key)); 412 result.add(info); 413 } 414 return result; 415 } 416 417 /** 418 * Adds 3D objects representing the current data for the plot to the 419 * specified world. After the world has been populated (or constructed) in 420 * this way, it is ready for rendering. This method is called by the 421 * {@link Chart3D} class, you won't normally call it directly. 422 * 423 * @param world the world ({@code null} not permitted). 424 * @param xOffset the x-offset. 425 * @param yOffset the y-offset. 426 * @param zOffset the z-offset. 427 */ 428 @Override 429 @SuppressWarnings("unchecked") 430 public void compose(World world, double xOffset, double yOffset, 431 double zOffset) { 432 double total = DataUtils.total(this.dataset); 433 double r = 0.0; 434 int count = this.dataset.getItemCount(); 435 for (int i = 0; i < count; i++) { 436 Comparable<?> key = this.dataset.getKey(i); 437 Number n = this.dataset.getValue(i); 438 if (n != null) { 439 double angle = Math.PI * 2 * (n.doubleValue() / total); 440 Color c = this.sectionColorSource.getColor( 441 this.dataset.getKey(i)); 442 Object3D segment = Object3D.createPieSegment(this.radius, 0.0, 443 yOffset, this.depth, r, r + angle, 444 Math.PI / this.segments, c); 445 segment.setProperty(Object3D.ITEM_KEY, 446 new KeyedValuesItemKey(key)); 447 world.add(segment); 448 r = r + angle; 449 } 450 } 451 } 452 453 /** 454 * Returns a list of label faces for the plot. These are non-visible 455 * objects added to the 3D model of the pie chart to track the positions 456 * for labels (which are added after the plot is projected and rendered). 457 * <br><br> 458 * NOTE: This method is public so that it can be called by the 459 * {@link Chart3D} class - you won't normally call it directly. 460 * 461 * @param xOffset the x-offset. 462 * @param yOffset the y-offset. 463 * @param zOffset the z-offset. 464 * 465 * @return A list of label faces. 466 */ 467 public List<Object3D> getLabelFaces(double xOffset, double yOffset, 468 double zOffset) { 469 double total = DataUtils.total(this.dataset); 470 List<Object3D> result = new ArrayList<>(); 471 // this adds the centre points 472 result.add(new Dot3D(0.0f, 0.0f, 0.0f, Color.RED)); 473 result.add(new Dot3D(0.0f, (float) yOffset, 0.0f, Color.RED)); 474 double r = 0.0; 475 int count = this.dataset.getItemCount(); 476 for (int i = 0; i < count; i++) { 477 Number n = this.dataset.getValue(i); 478 double angle = 0.0; 479 if (n != null) { 480 angle = Math.PI * 2 * (n.doubleValue() / total); 481 } 482 result.addAll(Object3D.createPieLabelMarkers(this.radius * 1.2, 483 0.0, yOffset - this.depth * 0.05, this.depth * 1.1, r, 484 r + angle)); 485 r = r + angle; 486 } 487 return result; 488 } 489 490 @Override 491 public String generateToolTipText(ItemKey itemKey) { 492 if (!(itemKey instanceof KeyedValuesItemKey)) { 493 throw new IllegalArgumentException( 494 "The itemKey must be a ValuesItemKey instance."); 495 } 496 KeyedValuesItemKey vik = (KeyedValuesItemKey) itemKey; 497 return this.toolTipGenerator.generateLabel(this.dataset, vik.getKey()); 498 } 499 500 /** 501 * Receives a visitor. This is a general purpose mechanism, but the main 502 * use is to apply chart style changes across all the elements of a 503 * chart. 504 * 505 * @param visitor the visitor ({@code null} not permitted). 506 * 507 * @since 1.2 508 */ 509 @Override 510 public void receive(ChartElementVisitor visitor) { 511 visitor.visit(this); 512 } 513 514 /** 515 * Tests this plot for equality with an arbitrary object. Note that the 516 * plot's dataset is NOT considered in the equality test. 517 * 518 * @param obj the object ({@code null} not permitted). 519 * 520 * @return A boolean. 521 */ 522 @Override 523 public boolean equals(Object obj) { 524 if (obj == this) { 525 return true; 526 } 527 if (!(obj instanceof PiePlot3D)) { 528 return false; 529 } 530 PiePlot3D that = (PiePlot3D) obj; 531 if (this.radius != that.radius) { 532 return false; 533 } 534 if (this.depth != that.depth) { 535 return false; 536 } 537 if (!this.sectionColorSource.equals(that.sectionColorSource)) { 538 return false; 539 } 540 if (!this.sectionLabelGenerator.equals(that.sectionLabelGenerator)) { 541 return false; 542 } 543 if (!this.sectionLabelFontSource.equals(that.sectionLabelFontSource)) { 544 return false; 545 } 546 if (!this.sectionLabelColorSource.equals( 547 that.sectionLabelColorSource)) { 548 return false; 549 } 550 if (!this.legendLabelGenerator.equals(that.legendLabelGenerator)) { 551 return false; 552 } 553 if (!this.toolTipGenerator.equals(that.toolTipGenerator)) { 554 return false; 555 } 556 if (this.segments != that.segments) { 557 return false; 558 } 559 return super.equals(obj); 560 } 561 562 @Override 563 public int hashCode() { 564 int hash = 5; 565 hash = 97 * hash + (int) (Double.doubleToLongBits(this.radius) 566 ^ (Double.doubleToLongBits(this.radius) >>> 32)); 567 hash = 97 * hash + (int) (Double.doubleToLongBits(this.depth) 568 ^ (Double.doubleToLongBits(this.depth) >>> 32)); 569 hash = 97 * hash + this.sectionColorSource.hashCode(); 570 hash = 97 * hash + this.sectionLabelGenerator.hashCode(); 571 hash = 97 * hash + this.sectionLabelFontSource.hashCode(); 572 hash = 97 * hash + this.sectionLabelColorSource.hashCode(); 573 hash = 97 * hash + this.segments; 574 return hash; 575 } 576 577}