001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-present, by David Gilbert and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 025 * Other names may be trademarks of their respective owners.] 026 * 027 * --------------- 028 * JFreeChart.java 029 * --------------- 030 * (C) Copyright 2000-present, by David Gilbert and Contributors. 031 * 032 * Original Author: David Gilbert; 033 * Contributor(s): Andrzej Porebski; 034 * David Li; 035 * Wolfgang Irler; 036 * Christian W. Zuckschwerdt; 037 * Klaus Rheinwald; 038 * Nicolas Brodu; 039 * Peter Kolb (patch 2603321); 040 * Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier); 041 * 042 * NOTE: The above list of contributors lists only the people that have 043 * contributed to this source file (JFreeChart.java) - for a list of ALL 044 * contributors to the project, please see the README.md file. 045 * 046 */ 047 048package org.jfree.chart; 049 050import java.awt.AlphaComposite; 051import java.awt.BasicStroke; 052import java.awt.Color; 053import java.awt.Composite; 054import java.awt.Font; 055import java.awt.Graphics2D; 056import java.awt.Image; 057import java.awt.Paint; 058import java.awt.RenderingHints; 059import java.awt.Shape; 060import java.awt.Stroke; 061import java.awt.geom.AffineTransform; 062import java.awt.geom.Point2D; 063import java.awt.geom.Rectangle2D; 064import java.awt.image.BufferedImage; 065import java.io.IOException; 066import java.io.ObjectInputStream; 067import java.io.ObjectOutputStream; 068import java.io.Serializable; 069import java.util.ArrayList; 070import java.util.HashMap; 071import java.util.Iterator; 072import java.util.List; 073import java.util.Map; 074import java.util.Objects; 075import javax.swing.UIManager; 076import javax.swing.event.EventListenerList; 077 078import org.jfree.chart.block.BlockParams; 079import org.jfree.chart.block.EntityBlockResult; 080import org.jfree.chart.block.LengthConstraintType; 081import org.jfree.chart.block.RectangleConstraint; 082import org.jfree.chart.entity.EntityCollection; 083import org.jfree.chart.entity.JFreeChartEntity; 084import org.jfree.chart.event.ChartChangeEvent; 085import org.jfree.chart.event.ChartChangeListener; 086import org.jfree.chart.event.ChartProgressEvent; 087import org.jfree.chart.event.ChartProgressListener; 088import org.jfree.chart.event.PlotChangeEvent; 089import org.jfree.chart.event.PlotChangeListener; 090import org.jfree.chart.event.TitleChangeEvent; 091import org.jfree.chart.event.TitleChangeListener; 092import org.jfree.chart.plot.CategoryPlot; 093import org.jfree.chart.plot.Plot; 094import org.jfree.chart.plot.PlotRenderingInfo; 095import org.jfree.chart.plot.XYPlot; 096import org.jfree.chart.title.LegendTitle; 097import org.jfree.chart.title.TextTitle; 098import org.jfree.chart.title.Title; 099import org.jfree.chart.ui.Align; 100import org.jfree.chart.ui.Drawable; 101import org.jfree.chart.ui.HorizontalAlignment; 102import org.jfree.chart.ui.RectangleEdge; 103import org.jfree.chart.ui.RectangleInsets; 104import org.jfree.chart.ui.Size2D; 105import org.jfree.chart.ui.VerticalAlignment; 106import org.jfree.chart.util.PaintUtils; 107import org.jfree.chart.util.Args; 108import org.jfree.chart.util.SerialUtils; 109import org.jfree.data.Range; 110 111/** 112 * A chart class implemented using the Java 2D APIs. The current version 113 * supports bar charts, line charts, pie charts and xy plots (including time 114 * series data). 115 * <P> 116 * JFreeChart coordinates several objects to achieve its aim of being able to 117 * draw a chart on a Java 2D graphics device: a list of {@link Title} objects 118 * (which often includes the chart's legend), a {@link Plot} and a 119 * {@link org.jfree.data.general.Dataset} (the plot in turn manages a 120 * domain axis and a range axis). 121 * <P> 122 * You should use a {@link ChartPanel} to display a chart in a GUI. 123 * <P> 124 * The {@link ChartFactory} class contains static methods for creating 125 * 'ready-made' charts. 126 * 127 * @see ChartPanel 128 * @see ChartFactory 129 * @see Title 130 * @see Plot 131 */ 132public class JFreeChart implements Drawable, TitleChangeListener, 133 PlotChangeListener, Serializable, Cloneable { 134 135 /** For serialization. */ 136 private static final long serialVersionUID = -3470703747817429120L; 137 138 /** The default font for titles. */ 139 public static final Font DEFAULT_TITLE_FONT 140 = new Font("SansSerif", Font.BOLD, 18); 141 142 /** The default background color. */ 143 public static final Paint DEFAULT_BACKGROUND_PAINT 144 = UIManager.getColor("Panel.background"); 145 146 /** The default background image. */ 147 public static final Image DEFAULT_BACKGROUND_IMAGE = null; 148 149 /** The default background image alignment. */ 150 public static final int DEFAULT_BACKGROUND_IMAGE_ALIGNMENT = Align.FIT; 151 152 /** The default background image alpha. */ 153 public static final float DEFAULT_BACKGROUND_IMAGE_ALPHA = 0.5f; 154 155 /** 156 * The key for a rendering hint that can suppress the generation of a 157 * shadow effect when drawing the chart. The hint value must be a 158 * Boolean. 159 */ 160 public static final RenderingHints.Key KEY_SUPPRESS_SHADOW_GENERATION 161 = new RenderingHints.Key(0) { 162 @Override 163 public boolean isCompatibleValue(Object val) { 164 return val instanceof Boolean; 165 } 166 }; 167 168 /** 169 * Rendering hints that will be used for chart drawing. This should never 170 * be {@code null}. 171 */ 172 private transient RenderingHints renderingHints; 173 174 /** The chart id (optional, will be used by JFreeSVG export). */ 175 private String id; 176 177 /** A flag that controls whether or not the chart border is drawn. */ 178 private boolean borderVisible; 179 180 /** The stroke used to draw the chart border (if visible). */ 181 private transient Stroke borderStroke; 182 183 /** The paint used to draw the chart border (if visible). */ 184 private transient Paint borderPaint; 185 186 /** The padding between the chart border and the chart drawing area. */ 187 private RectangleInsets padding; 188 189 /** The chart title (optional). */ 190 private TextTitle title; 191 192 /** 193 * The chart subtitles (zero, one or many). This field should never be 194 * {@code null}. 195 */ 196 private List subtitles; 197 198 /** Draws the visual representation of the data. */ 199 private Plot plot; 200 201 /** Paint used to draw the background of the chart. */ 202 private transient Paint backgroundPaint; 203 204 /** An optional background image for the chart. */ 205 private transient Image backgroundImage; // todo: not serialized yet 206 207 /** The alignment for the background image. */ 208 private int backgroundImageAlignment = Align.FIT; 209 210 /** The alpha transparency for the background image. */ 211 private float backgroundImageAlpha = 0.5f; 212 213 /** Storage for registered change listeners. */ 214 private transient EventListenerList changeListeners; 215 216 /** Storage for registered progress listeners. */ 217 private transient EventListenerList progressListeners; 218 219 /** 220 * A flag that can be used to enable/disable notification of chart change 221 * events. 222 */ 223 private boolean notify; 224 225 /** 226 * A flag that controls whether or not rendering hints that identify 227 * chart element should be added during rendering. This defaults to false 228 * and it should only be enabled if the output target will use the hints. 229 * JFreeSVG is one output target that supports these hints. 230 */ 231 private boolean elementHinting; 232 233 /** 234 * Creates a new chart based on the supplied plot. The chart will have 235 * a legend added automatically, but no title (although you can easily add 236 * one later). 237 * <br><br> 238 * Note that the {@link ChartFactory} class contains a range 239 * of static methods that will return ready-made charts, and often this 240 * is a more convenient way to create charts than using this constructor. 241 * 242 * @param plot the plot ({@code null} not permitted). 243 */ 244 public JFreeChart(Plot plot) { 245 this(null, null, plot, true); 246 } 247 248 /** 249 * Creates a new chart with the given title and plot. A default font 250 * ({@link #DEFAULT_TITLE_FONT}) is used for the title, and the chart will 251 * have a legend added automatically. 252 * <br><br> 253 * Note that the {@link ChartFactory} class contains a range 254 * of static methods that will return ready-made charts, and often this 255 * is a more convenient way to create charts than using this constructor. 256 * 257 * @param title the chart title ({@code null} permitted). 258 * @param plot the plot ({@code null} not permitted). 259 */ 260 public JFreeChart(String title, Plot plot) { 261 this(title, JFreeChart.DEFAULT_TITLE_FONT, plot, true); 262 } 263 264 /** 265 * Creates a new chart with the given title and plot. The 266 * {@code createLegend} argument specifies whether or not a legend 267 * should be added to the chart. 268 * <br><br> 269 * Note that the {@link ChartFactory} class contains a range 270 * of static methods that will return ready-made charts, and often this 271 * is a more convenient way to create charts than using this constructor. 272 * 273 * @param title the chart title ({@code null} permitted). 274 * @param titleFont the font for displaying the chart title 275 * ({@code null} permitted). 276 * @param plot controller of the visual representation of the data 277 * ({@code null} not permitted). 278 * @param createLegend a flag indicating whether or not a legend should 279 * be created for the chart. 280 */ 281 public JFreeChart(String title, Font titleFont, Plot plot, 282 boolean createLegend) { 283 284 Args.nullNotPermitted(plot, "plot"); 285 this.id = null; 286 plot.setChart(this); 287 288 // create storage for listeners... 289 this.progressListeners = new EventListenerList(); 290 this.changeListeners = new EventListenerList(); 291 this.notify = true; // default is to notify listeners when the 292 // chart changes 293 294 this.renderingHints = new RenderingHints( 295 RenderingHints.KEY_ANTIALIASING, 296 RenderingHints.VALUE_ANTIALIAS_ON); 297 // added the following hint because of 298 // http://stackoverflow.com/questions/7785082/ 299 this.renderingHints.put(RenderingHints.KEY_STROKE_CONTROL, 300 RenderingHints.VALUE_STROKE_PURE); 301 302 this.borderVisible = false; 303 this.borderStroke = new BasicStroke(1.0f); 304 this.borderPaint = Color.BLACK; 305 306 this.padding = RectangleInsets.ZERO_INSETS; 307 308 this.plot = plot; 309 plot.addChangeListener(this); 310 311 this.subtitles = new ArrayList(); 312 313 // create a legend, if requested... 314 if (createLegend) { 315 LegendTitle legend = new LegendTitle(this.plot); 316 legend.setMargin(new RectangleInsets(1.0, 1.0, 1.0, 1.0)); 317 legend.setBackgroundPaint(Color.WHITE); 318 legend.setPosition(RectangleEdge.BOTTOM); 319 this.subtitles.add(legend); 320 legend.addChangeListener(this); 321 } 322 323 // add the chart title, if one has been specified... 324 if (title != null) { 325 if (titleFont == null) { 326 titleFont = DEFAULT_TITLE_FONT; 327 } 328 this.title = new TextTitle(title, titleFont); 329 this.title.addChangeListener(this); 330 } 331 332 this.backgroundPaint = DEFAULT_BACKGROUND_PAINT; 333 334 this.backgroundImage = DEFAULT_BACKGROUND_IMAGE; 335 this.backgroundImageAlignment = DEFAULT_BACKGROUND_IMAGE_ALIGNMENT; 336 this.backgroundImageAlpha = DEFAULT_BACKGROUND_IMAGE_ALPHA; 337 } 338 339 /** 340 * Returns the ID for the chart. 341 * 342 * @return The ID for the chart (possibly {@code null}). 343 */ 344 public String getID() { 345 return this.id; 346 } 347 348 /** 349 * Sets the ID for the chart. 350 * 351 * @param id the id ({@code null} permitted). 352 */ 353 public void setID(String id) { 354 this.id = id; 355 } 356 357 /** 358 * Returns the flag that controls whether or not rendering hints 359 * ({@link ChartHints#KEY_BEGIN_ELEMENT} and 360 * {@link ChartHints#KEY_END_ELEMENT}) that identify chart elements are 361 * added during rendering. The default value is {@code false}. 362 * 363 * @return A boolean. 364 * 365 * @see #setElementHinting(boolean) 366 */ 367 public boolean getElementHinting() { 368 return this.elementHinting; 369 } 370 371 /** 372 * Sets the flag that controls whether or not rendering hints 373 * ({@link ChartHints#KEY_BEGIN_ELEMENT} and 374 * {@link ChartHints#KEY_END_ELEMENT}) that identify chart elements are 375 * added during rendering. 376 * 377 * @param hinting the new flag value. 378 * 379 * @see #getElementHinting() 380 */ 381 public void setElementHinting(boolean hinting) { 382 this.elementHinting = hinting; 383 } 384 385 /** 386 * Returns the collection of rendering hints for the chart. 387 * 388 * @return The rendering hints for the chart (never {@code null}). 389 * 390 * @see #setRenderingHints(RenderingHints) 391 */ 392 public RenderingHints getRenderingHints() { 393 return this.renderingHints; 394 } 395 396 /** 397 * Sets the rendering hints for the chart. These will be added (using the 398 * {@code Graphics2D.addRenderingHints()} method) near the start of the 399 * {@code JFreeChart.draw()} method. 400 * 401 * @param renderingHints the rendering hints ({@code null} not permitted). 402 * 403 * @see #getRenderingHints() 404 */ 405 public void setRenderingHints(RenderingHints renderingHints) { 406 Args.nullNotPermitted(renderingHints, "renderingHints"); 407 this.renderingHints = renderingHints; 408 fireChartChanged(); 409 } 410 411 /** 412 * Returns a flag that controls whether or not a border is drawn around the 413 * outside of the chart. 414 * 415 * @return A boolean. 416 * 417 * @see #setBorderVisible(boolean) 418 */ 419 public boolean isBorderVisible() { 420 return this.borderVisible; 421 } 422 423 /** 424 * Sets a flag that controls whether or not a border is drawn around the 425 * outside of the chart. 426 * 427 * @param visible the flag. 428 * 429 * @see #isBorderVisible() 430 */ 431 public void setBorderVisible(boolean visible) { 432 this.borderVisible = visible; 433 fireChartChanged(); 434 } 435 436 /** 437 * Returns the stroke used to draw the chart border (if visible). 438 * 439 * @return The border stroke. 440 * 441 * @see #setBorderStroke(Stroke) 442 */ 443 public Stroke getBorderStroke() { 444 return this.borderStroke; 445 } 446 447 /** 448 * Sets the stroke used to draw the chart border (if visible). 449 * 450 * @param stroke the stroke. 451 * 452 * @see #getBorderStroke() 453 */ 454 public void setBorderStroke(Stroke stroke) { 455 this.borderStroke = stroke; 456 fireChartChanged(); 457 } 458 459 /** 460 * Returns the paint used to draw the chart border (if visible). 461 * 462 * @return The border paint. 463 * 464 * @see #setBorderPaint(Paint) 465 */ 466 public Paint getBorderPaint() { 467 return this.borderPaint; 468 } 469 470 /** 471 * Sets the paint used to draw the chart border (if visible). 472 * 473 * @param paint the paint. 474 * 475 * @see #getBorderPaint() 476 */ 477 public void setBorderPaint(Paint paint) { 478 this.borderPaint = paint; 479 fireChartChanged(); 480 } 481 482 /** 483 * Returns the padding between the chart border and the chart drawing area. 484 * 485 * @return The padding (never {@code null}). 486 * 487 * @see #setPadding(RectangleInsets) 488 */ 489 public RectangleInsets getPadding() { 490 return this.padding; 491 } 492 493 /** 494 * Sets the padding between the chart border and the chart drawing area, 495 * and sends a {@link ChartChangeEvent} to all registered listeners. 496 * 497 * @param padding the padding ({@code null} not permitted). 498 * 499 * @see #getPadding() 500 */ 501 public void setPadding(RectangleInsets padding) { 502 Args.nullNotPermitted(padding, "padding"); 503 this.padding = padding; 504 notifyListeners(new ChartChangeEvent(this)); 505 } 506 507 /** 508 * Returns the main chart title. Very often a chart will have just one 509 * title, so we make this case simple by providing accessor methods for 510 * the main title. However, multiple titles are supported - see the 511 * {@link #addSubtitle(Title)} method. 512 * 513 * @return The chart title (possibly {@code null}). 514 * 515 * @see #setTitle(TextTitle) 516 */ 517 public TextTitle getTitle() { 518 return this.title; 519 } 520 521 /** 522 * Sets the main title for the chart and sends a {@link ChartChangeEvent} 523 * to all registered listeners. If you do not want a title for the 524 * chart, set it to {@code null}. If you want more than one title on 525 * a chart, use the {@link #addSubtitle(Title)} method. 526 * 527 * @param title the title ({@code null} permitted). 528 * 529 * @see #getTitle() 530 */ 531 public void setTitle(TextTitle title) { 532 if (this.title != null) { 533 this.title.removeChangeListener(this); 534 } 535 this.title = title; 536 if (title != null) { 537 title.addChangeListener(this); 538 } 539 fireChartChanged(); 540 } 541 542 /** 543 * Sets the chart title and sends a {@link ChartChangeEvent} to all 544 * registered listeners. This is a convenience method that ends up calling 545 * the {@link #setTitle(TextTitle)} method. If there is an existing title, 546 * its text is updated, otherwise a new title using the default font is 547 * added to the chart. If {@code text} is {@code null} the chart 548 * title is set to {@code null}. 549 * 550 * @param text the title text ({@code null} permitted). 551 * 552 * @see #getTitle() 553 */ 554 public void setTitle(String text) { 555 if (text != null) { 556 if (this.title == null) { 557 setTitle(new TextTitle(text, JFreeChart.DEFAULT_TITLE_FONT)); 558 } else { 559 this.title.setText(text); 560 } 561 } 562 else { 563 setTitle((TextTitle) null); 564 } 565 } 566 567 /** 568 * Adds a legend to the plot and sends a {@link ChartChangeEvent} to all 569 * registered listeners. 570 * 571 * @param legend the legend ({@code null} not permitted). 572 * 573 * @see #removeLegend() 574 */ 575 public void addLegend(LegendTitle legend) { 576 addSubtitle(legend); 577 } 578 579 /** 580 * Returns the legend for the chart, if there is one. Note that a chart 581 * can have more than one legend - this method returns the first. 582 * 583 * @return The legend (possibly {@code null}). 584 * 585 * @see #getLegend(int) 586 */ 587 public LegendTitle getLegend() { 588 return getLegend(0); 589 } 590 591 /** 592 * Returns the nth legend for a chart, or {@code null}. 593 * 594 * @param index the legend index (zero-based). 595 * 596 * @return The legend (possibly {@code null}). 597 * 598 * @see #addLegend(LegendTitle) 599 */ 600 public LegendTitle getLegend(int index) { 601 int seen = 0; 602 Iterator iterator = this.subtitles.iterator(); 603 while (iterator.hasNext()) { 604 Title subtitle = (Title) iterator.next(); 605 if (subtitle instanceof LegendTitle) { 606 if (seen == index) { 607 return (LegendTitle) subtitle; 608 } 609 else { 610 seen++; 611 } 612 } 613 } 614 return null; 615 } 616 617 /** 618 * Removes the first legend in the chart and sends a 619 * {@link ChartChangeEvent} to all registered listeners. 620 * 621 * @see #getLegend() 622 */ 623 public void removeLegend() { 624 removeSubtitle(getLegend()); 625 } 626 627 /** 628 * Returns the list of subtitles for the chart. 629 * 630 * @return The subtitle list (possibly empty, but never {@code null}). 631 * 632 * @see #setSubtitles(List) 633 */ 634 public List getSubtitles() { 635 return new ArrayList(this.subtitles); 636 } 637 638 /** 639 * Sets the title list for the chart (completely replaces any existing 640 * titles) and sends a {@link ChartChangeEvent} to all registered 641 * listeners. 642 * 643 * @param subtitles the new list of subtitles ({@code null} not 644 * permitted). 645 * 646 * @see #getSubtitles() 647 */ 648 public void setSubtitles(List subtitles) { 649 if (subtitles == null) { 650 throw new NullPointerException("Null 'subtitles' argument."); 651 } 652 setNotify(false); 653 clearSubtitles(); 654 Iterator iterator = subtitles.iterator(); 655 while (iterator.hasNext()) { 656 Title t = (Title) iterator.next(); 657 if (t != null) { 658 addSubtitle(t); 659 } 660 } 661 setNotify(true); // this fires a ChartChangeEvent 662 } 663 664 /** 665 * Returns the number of titles for the chart. 666 * 667 * @return The number of titles for the chart. 668 * 669 * @see #getSubtitles() 670 */ 671 public int getSubtitleCount() { 672 return this.subtitles.size(); 673 } 674 675 /** 676 * Returns a chart subtitle. 677 * 678 * @param index the index of the chart subtitle (zero based). 679 * 680 * @return A chart subtitle. 681 * 682 * @see #addSubtitle(Title) 683 */ 684 public Title getSubtitle(int index) { 685 if ((index < 0) || (index >= getSubtitleCount())) { 686 throw new IllegalArgumentException("Index out of range."); 687 } 688 return (Title) this.subtitles.get(index); 689 } 690 691 /** 692 * Adds a chart subtitle, and notifies registered listeners that the chart 693 * has been modified. 694 * 695 * @param subtitle the subtitle ({@code null} not permitted). 696 * 697 * @see #getSubtitle(int) 698 */ 699 public void addSubtitle(Title subtitle) { 700 Args.nullNotPermitted(subtitle, "subtitle"); 701 this.subtitles.add(subtitle); 702 subtitle.addChangeListener(this); 703 fireChartChanged(); 704 } 705 706 /** 707 * Adds a subtitle at a particular position in the subtitle list, and sends 708 * a {@link ChartChangeEvent} to all registered listeners. 709 * 710 * @param index the index (in the range 0 to {@link #getSubtitleCount()}). 711 * @param subtitle the subtitle to add ({@code null} not permitted). 712 */ 713 public void addSubtitle(int index, Title subtitle) { 714 if (index < 0 || index > getSubtitleCount()) { 715 throw new IllegalArgumentException( 716 "The 'index' argument is out of range."); 717 } 718 Args.nullNotPermitted(subtitle, "subtitle"); 719 this.subtitles.add(index, subtitle); 720 subtitle.addChangeListener(this); 721 fireChartChanged(); 722 } 723 724 /** 725 * Clears all subtitles from the chart and sends a {@link ChartChangeEvent} 726 * to all registered listeners. 727 * 728 * @see #addSubtitle(Title) 729 */ 730 public void clearSubtitles() { 731 Iterator iterator = this.subtitles.iterator(); 732 while (iterator.hasNext()) { 733 Title t = (Title) iterator.next(); 734 t.removeChangeListener(this); 735 } 736 this.subtitles.clear(); 737 fireChartChanged(); 738 } 739 740 /** 741 * Removes the specified subtitle and sends a {@link ChartChangeEvent} to 742 * all registered listeners. 743 * 744 * @param title the title. 745 * 746 * @see #addSubtitle(Title) 747 */ 748 public void removeSubtitle(Title title) { 749 this.subtitles.remove(title); 750 fireChartChanged(); 751 } 752 753 /** 754 * Returns the plot for the chart. The plot is a class responsible for 755 * coordinating the visual representation of the data, including the axes 756 * (if any). 757 * 758 * @return The plot. 759 */ 760 public Plot getPlot() { 761 return this.plot; 762 } 763 764 /** 765 * Returns the plot cast as a {@link CategoryPlot}. 766 * <p> 767 * NOTE: if the plot is not an instance of {@link CategoryPlot}, then a 768 * {@code ClassCastException} is thrown. 769 * 770 * @return The plot. 771 * 772 * @see #getPlot() 773 */ 774 public CategoryPlot getCategoryPlot() { 775 return (CategoryPlot) this.plot; 776 } 777 778 /** 779 * Returns the plot cast as an {@link XYPlot}. 780 * <p> 781 * NOTE: if the plot is not an instance of {@link XYPlot}, then a 782 * {@code ClassCastException} is thrown. 783 * 784 * @return The plot. 785 * 786 * @see #getPlot() 787 */ 788 public XYPlot getXYPlot() { 789 return (XYPlot) this.plot; 790 } 791 792 /** 793 * Returns a flag that indicates whether or not anti-aliasing is used when 794 * the chart is drawn. 795 * 796 * @return The flag. 797 * 798 * @see #setAntiAlias(boolean) 799 */ 800 public boolean getAntiAlias() { 801 Object val = this.renderingHints.get(RenderingHints.KEY_ANTIALIASING); 802 return RenderingHints.VALUE_ANTIALIAS_ON.equals(val); 803 } 804 805 /** 806 * Sets a flag that indicates whether or not anti-aliasing is used when the 807 * chart is drawn. 808 * <P> 809 * Anti-aliasing usually improves the appearance of charts, but is slower. 810 * 811 * @param flag the new value of the flag. 812 * 813 * @see #getAntiAlias() 814 */ 815 public void setAntiAlias(boolean flag) { 816 Object hint = flag ? RenderingHints.VALUE_ANTIALIAS_ON 817 : RenderingHints.VALUE_ANTIALIAS_OFF; 818 this.renderingHints.put(RenderingHints.KEY_ANTIALIASING, hint); 819 fireChartChanged(); 820 } 821 822 /** 823 * Returns the current value stored in the rendering hints table for 824 * {@link RenderingHints#KEY_TEXT_ANTIALIASING}. 825 * 826 * @return The hint value (possibly {@code null}). 827 * 828 * @see #setTextAntiAlias(Object) 829 */ 830 public Object getTextAntiAlias() { 831 return this.renderingHints.get(RenderingHints.KEY_TEXT_ANTIALIASING); 832 } 833 834 /** 835 * Sets the value in the rendering hints table for 836 * {@link RenderingHints#KEY_TEXT_ANTIALIASING} to either 837 * {@link RenderingHints#VALUE_TEXT_ANTIALIAS_ON} or 838 * {@link RenderingHints#VALUE_TEXT_ANTIALIAS_OFF}, then sends a 839 * {@link ChartChangeEvent} to all registered listeners. 840 * 841 * @param flag the new value of the flag. 842 * 843 * @see #getTextAntiAlias() 844 * @see #setTextAntiAlias(Object) 845 */ 846 public void setTextAntiAlias(boolean flag) { 847 if (flag) { 848 setTextAntiAlias(RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 849 } 850 else { 851 setTextAntiAlias(RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); 852 } 853 } 854 855 /** 856 * Sets the value in the rendering hints table for 857 * {@link RenderingHints#KEY_TEXT_ANTIALIASING} and sends a 858 * {@link ChartChangeEvent} to all registered listeners. 859 * 860 * @param val the new value ({@code null} permitted). 861 * 862 * @see #getTextAntiAlias() 863 * @see #setTextAntiAlias(boolean) 864 */ 865 public void setTextAntiAlias(Object val) { 866 this.renderingHints.put(RenderingHints.KEY_TEXT_ANTIALIASING, val); 867 notifyListeners(new ChartChangeEvent(this)); 868 } 869 870 /** 871 * Returns the paint used for the chart background. 872 * 873 * @return The paint (possibly {@code null}). 874 * 875 * @see #setBackgroundPaint(Paint) 876 */ 877 public Paint getBackgroundPaint() { 878 return this.backgroundPaint; 879 } 880 881 /** 882 * Sets the paint used to fill the chart background and sends a 883 * {@link ChartChangeEvent} to all registered listeners. 884 * 885 * @param paint the paint ({@code null} permitted). 886 * 887 * @see #getBackgroundPaint() 888 */ 889 public void setBackgroundPaint(Paint paint) { 890 891 if (this.backgroundPaint != null) { 892 if (!this.backgroundPaint.equals(paint)) { 893 this.backgroundPaint = paint; 894 fireChartChanged(); 895 } 896 } 897 else { 898 if (paint != null) { 899 this.backgroundPaint = paint; 900 fireChartChanged(); 901 } 902 } 903 904 } 905 906 /** 907 * Returns the background image for the chart, or {@code null} if 908 * there is no image. 909 * 910 * @return The image (possibly {@code null}). 911 * 912 * @see #setBackgroundImage(Image) 913 */ 914 public Image getBackgroundImage() { 915 return this.backgroundImage; 916 } 917 918 /** 919 * Sets the background image for the chart and sends a 920 * {@link ChartChangeEvent} to all registered listeners. 921 * 922 * @param image the image ({@code null} permitted). 923 * 924 * @see #getBackgroundImage() 925 */ 926 public void setBackgroundImage(Image image) { 927 928 if (this.backgroundImage != null) { 929 if (!this.backgroundImage.equals(image)) { 930 this.backgroundImage = image; 931 fireChartChanged(); 932 } 933 } 934 else { 935 if (image != null) { 936 this.backgroundImage = image; 937 fireChartChanged(); 938 } 939 } 940 941 } 942 943 /** 944 * Returns the background image alignment. Alignment constants are defined 945 * in the {@link Align} class. 946 * 947 * @return The alignment. 948 * 949 * @see #setBackgroundImageAlignment(int) 950 */ 951 public int getBackgroundImageAlignment() { 952 return this.backgroundImageAlignment; 953 } 954 955 /** 956 * Sets the background alignment. Alignment options are defined by the 957 * {@link org.jfree.chart.ui.Align} class. 958 * 959 * @param alignment the alignment. 960 * 961 * @see #getBackgroundImageAlignment() 962 */ 963 public void setBackgroundImageAlignment(int alignment) { 964 if (this.backgroundImageAlignment != alignment) { 965 this.backgroundImageAlignment = alignment; 966 fireChartChanged(); 967 } 968 } 969 970 /** 971 * Returns the alpha-transparency for the chart's background image. 972 * 973 * @return The alpha-transparency. 974 * 975 * @see #setBackgroundImageAlpha(float) 976 */ 977 public float getBackgroundImageAlpha() { 978 return this.backgroundImageAlpha; 979 } 980 981 /** 982 * Sets the alpha-transparency for the chart's background image. 983 * Registered listeners are notified that the chart has been changed. 984 * 985 * @param alpha the alpha value. 986 * 987 * @see #getBackgroundImageAlpha() 988 */ 989 public void setBackgroundImageAlpha(float alpha) { 990 if (this.backgroundImageAlpha != alpha) { 991 this.backgroundImageAlpha = alpha; 992 fireChartChanged(); 993 } 994 } 995 996 /** 997 * Returns a flag that controls whether or not change events are sent to 998 * registered listeners. 999 * 1000 * @return A boolean. 1001 * 1002 * @see #setNotify(boolean) 1003 */ 1004 public boolean isNotify() { 1005 return this.notify; 1006 } 1007 1008 /** 1009 * Sets a flag that controls whether or not listeners receive 1010 * {@link ChartChangeEvent} notifications. 1011 * 1012 * @param notify a boolean. 1013 * 1014 * @see #isNotify() 1015 */ 1016 public void setNotify(boolean notify) { 1017 this.notify = notify; 1018 // if the flag is being set to true, there may be queued up changes... 1019 if (notify) { 1020 notifyListeners(new ChartChangeEvent(this)); 1021 } 1022 } 1023 1024 /** 1025 * Draws the chart on a Java 2D graphics device (such as the screen or a 1026 * printer). 1027 * <P> 1028 * This method is the focus of the entire JFreeChart library. 1029 * 1030 * @param g2 the graphics device. 1031 * @param area the area within which the chart should be drawn. 1032 */ 1033 @Override 1034 public void draw(Graphics2D g2, Rectangle2D area) { 1035 draw(g2, area, null, null); 1036 } 1037 1038 /** 1039 * Draws the chart on a Java 2D graphics device (such as the screen or a 1040 * printer). This method is the focus of the entire JFreeChart library. 1041 * 1042 * @param g2 the graphics device. 1043 * @param area the area within which the chart should be drawn. 1044 * @param info records info about the drawing (null means collect no info). 1045 */ 1046 public void draw(Graphics2D g2, Rectangle2D area, ChartRenderingInfo info) { 1047 draw(g2, area, null, info); 1048 } 1049 1050 /** 1051 * Draws the chart on a Java 2D graphics device (such as the screen or a 1052 * printer). 1053 * <P> 1054 * This method is the focus of the entire JFreeChart library. 1055 * 1056 * @param g2 the graphics device. 1057 * @param chartArea the area within which the chart should be drawn. 1058 * @param anchor the anchor point (in Java2D space) for the chart 1059 * ({@code null} permitted). 1060 * @param info records info about the drawing (null means collect no info). 1061 */ 1062 public void draw(Graphics2D g2, Rectangle2D chartArea, Point2D anchor, 1063 ChartRenderingInfo info) { 1064 1065 notifyListeners(new ChartProgressEvent(this, this, 1066 ChartProgressEvent.DRAWING_STARTED, 0)); 1067 1068 if (this.elementHinting) { 1069 Map m = new HashMap<String, String>(); 1070 if (this.id != null) { 1071 m.put("id", this.id); 1072 } 1073 m.put("ref", "JFREECHART_TOP_LEVEL"); 1074 g2.setRenderingHint(ChartHints.KEY_BEGIN_ELEMENT, m); 1075 } 1076 1077 EntityCollection entities = null; 1078 // record the chart area, if info is requested... 1079 if (info != null) { 1080 info.clear(); 1081 info.setChartArea(chartArea); 1082 entities = info.getEntityCollection(); 1083 } 1084 if (entities != null) { 1085 entities.add(new JFreeChartEntity((Rectangle2D) chartArea.clone(), 1086 this)); 1087 } 1088 1089 // ensure no drawing occurs outside chart area... 1090 Shape savedClip = g2.getClip(); 1091 g2.clip(chartArea); 1092 1093 g2.addRenderingHints(this.renderingHints); 1094 1095 // draw the chart background... 1096 if (this.backgroundPaint != null) { 1097 g2.setPaint(this.backgroundPaint); 1098 g2.fill(chartArea); 1099 } 1100 1101 if (this.backgroundImage != null) { 1102 Composite originalComposite = g2.getComposite(); 1103 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1104 this.backgroundImageAlpha)); 1105 Rectangle2D dest = new Rectangle2D.Double(0.0, 0.0, 1106 this.backgroundImage.getWidth(null), 1107 this.backgroundImage.getHeight(null)); 1108 Align.align(dest, chartArea, this.backgroundImageAlignment); 1109 g2.drawImage(this.backgroundImage, (int) dest.getX(), 1110 (int) dest.getY(), (int) dest.getWidth(), 1111 (int) dest.getHeight(), null); 1112 g2.setComposite(originalComposite); 1113 } 1114 1115 if (isBorderVisible()) { 1116 Paint paint = getBorderPaint(); 1117 Stroke stroke = getBorderStroke(); 1118 if (paint != null && stroke != null) { 1119 Rectangle2D borderArea = new Rectangle2D.Double( 1120 chartArea.getX(), chartArea.getY(), 1121 chartArea.getWidth() - 1.0, chartArea.getHeight() 1122 - 1.0); 1123 g2.setPaint(paint); 1124 g2.setStroke(stroke); 1125 g2.draw(borderArea); 1126 } 1127 } 1128 1129 // draw the title and subtitles... 1130 Rectangle2D nonTitleArea = new Rectangle2D.Double(); 1131 nonTitleArea.setRect(chartArea); 1132 this.padding.trim(nonTitleArea); 1133 1134 if (this.title != null && this.title.isVisible()) { 1135 EntityCollection e = drawTitle(this.title, g2, nonTitleArea, 1136 (entities != null)); 1137 if (e != null && entities != null) { 1138 entities.addAll(e); 1139 } 1140 } 1141 1142 Iterator iterator = this.subtitles.iterator(); 1143 while (iterator.hasNext()) { 1144 Title currentTitle = (Title) iterator.next(); 1145 if (currentTitle.isVisible()) { 1146 EntityCollection e = drawTitle(currentTitle, g2, nonTitleArea, 1147 (entities != null)); 1148 if (e != null && entities != null) { 1149 entities.addAll(e); 1150 } 1151 } 1152 } 1153 1154 Rectangle2D plotArea = nonTitleArea; 1155 1156 // draw the plot (axes and data visualisation) 1157 PlotRenderingInfo plotInfo = null; 1158 if (info != null) { 1159 plotInfo = info.getPlotInfo(); 1160 } 1161 this.plot.draw(g2, plotArea, anchor, null, plotInfo); 1162 g2.setClip(savedClip); 1163 if (this.elementHinting) { 1164 g2.setRenderingHint(ChartHints.KEY_END_ELEMENT, Boolean.TRUE); 1165 } 1166 1167 notifyListeners(new ChartProgressEvent(this, this, 1168 ChartProgressEvent.DRAWING_FINISHED, 100)); 1169 } 1170 1171 /** 1172 * Creates a rectangle that is aligned to the frame. 1173 * 1174 * @param dimensions the dimensions for the rectangle. 1175 * @param frame the frame to align to. 1176 * @param hAlign the horizontal alignment. 1177 * @param vAlign the vertical alignment. 1178 * 1179 * @return A rectangle. 1180 */ 1181 private Rectangle2D createAlignedRectangle2D(Size2D dimensions, 1182 Rectangle2D frame, HorizontalAlignment hAlign, 1183 VerticalAlignment vAlign) { 1184 double x = Double.NaN; 1185 double y = Double.NaN; 1186 if (hAlign == HorizontalAlignment.LEFT) { 1187 x = frame.getX(); 1188 } 1189 else if (hAlign == HorizontalAlignment.CENTER) { 1190 x = frame.getCenterX() - (dimensions.width / 2.0); 1191 } 1192 else if (hAlign == HorizontalAlignment.RIGHT) { 1193 x = frame.getMaxX() - dimensions.width; 1194 } 1195 if (vAlign == VerticalAlignment.TOP) { 1196 y = frame.getY(); 1197 } 1198 else if (vAlign == VerticalAlignment.CENTER) { 1199 y = frame.getCenterY() - (dimensions.height / 2.0); 1200 } 1201 else if (vAlign == VerticalAlignment.BOTTOM) { 1202 y = frame.getMaxY() - dimensions.height; 1203 } 1204 1205 return new Rectangle2D.Double(x, y, dimensions.width, 1206 dimensions.height); 1207 } 1208 1209 /** 1210 * Draws a title. The title should be drawn at the top, bottom, left or 1211 * right of the specified area, and the area should be updated to reflect 1212 * the amount of space used by the title. 1213 * 1214 * @param t the title ({@code null} not permitted). 1215 * @param g2 the graphics device ({@code null} not permitted). 1216 * @param area the chart area, excluding any existing titles 1217 * ({@code null} not permitted). 1218 * @param entities a flag that controls whether or not an entity 1219 * collection is returned for the title. 1220 * 1221 * @return An entity collection for the title (possibly {@code null}). 1222 */ 1223 protected EntityCollection drawTitle(Title t, Graphics2D g2, 1224 Rectangle2D area, boolean entities) { 1225 1226 Args.nullNotPermitted(t, "t"); 1227 Args.nullNotPermitted(area, "area"); 1228 Rectangle2D titleArea; 1229 RectangleEdge position = t.getPosition(); 1230 double ww = area.getWidth(); 1231 if (ww <= 0.0) { 1232 return null; 1233 } 1234 double hh = area.getHeight(); 1235 if (hh <= 0.0) { 1236 return null; 1237 } 1238 RectangleConstraint constraint = new RectangleConstraint(ww, 1239 new Range(0.0, ww), LengthConstraintType.RANGE, hh, 1240 new Range(0.0, hh), LengthConstraintType.RANGE); 1241 Object retValue = null; 1242 BlockParams p = new BlockParams(); 1243 p.setGenerateEntities(entities); 1244 if (position == RectangleEdge.TOP) { 1245 Size2D size = t.arrange(g2, constraint); 1246 titleArea = createAlignedRectangle2D(size, area, 1247 t.getHorizontalAlignment(), VerticalAlignment.TOP); 1248 retValue = t.draw(g2, titleArea, p); 1249 area.setRect(area.getX(), Math.min(area.getY() + size.height, 1250 area.getMaxY()), area.getWidth(), Math.max(area.getHeight() 1251 - size.height, 0)); 1252 } else if (position == RectangleEdge.BOTTOM) { 1253 Size2D size = t.arrange(g2, constraint); 1254 titleArea = createAlignedRectangle2D(size, area, 1255 t.getHorizontalAlignment(), VerticalAlignment.BOTTOM); 1256 retValue = t.draw(g2, titleArea, p); 1257 area.setRect(area.getX(), area.getY(), area.getWidth(), 1258 area.getHeight() - size.height); 1259 } else if (position == RectangleEdge.RIGHT) { 1260 Size2D size = t.arrange(g2, constraint); 1261 titleArea = createAlignedRectangle2D(size, area, 1262 HorizontalAlignment.RIGHT, t.getVerticalAlignment()); 1263 retValue = t.draw(g2, titleArea, p); 1264 area.setRect(area.getX(), area.getY(), area.getWidth() 1265 - size.width, area.getHeight()); 1266 } else if (position == RectangleEdge.LEFT) { 1267 Size2D size = t.arrange(g2, constraint); 1268 titleArea = createAlignedRectangle2D(size, area, 1269 HorizontalAlignment.LEFT, t.getVerticalAlignment()); 1270 retValue = t.draw(g2, titleArea, p); 1271 area.setRect(area.getX() + size.width, area.getY(), area.getWidth() 1272 - size.width, area.getHeight()); 1273 } 1274 else { 1275 throw new RuntimeException("Unrecognised title position."); 1276 } 1277 EntityCollection result = null; 1278 if (retValue instanceof EntityBlockResult) { 1279 EntityBlockResult ebr = (EntityBlockResult) retValue; 1280 result = ebr.getEntityCollection(); 1281 } 1282 return result; 1283 } 1284 1285 /** 1286 * Creates and returns a buffered image into which the chart has been drawn. 1287 * 1288 * @param width the width. 1289 * @param height the height. 1290 * 1291 * @return A buffered image. 1292 */ 1293 public BufferedImage createBufferedImage(int width, int height) { 1294 return createBufferedImage(width, height, null); 1295 } 1296 1297 /** 1298 * Creates and returns a buffered image into which the chart has been drawn. 1299 * 1300 * @param width the width. 1301 * @param height the height. 1302 * @param info carries back chart state information ({@code null} 1303 * permitted). 1304 * 1305 * @return A buffered image. 1306 */ 1307 public BufferedImage createBufferedImage(int width, int height, 1308 ChartRenderingInfo info) { 1309 return createBufferedImage(width, height, BufferedImage.TYPE_INT_ARGB, 1310 info); 1311 } 1312 1313 /** 1314 * Creates and returns a buffered image into which the chart has been drawn. 1315 * 1316 * @param width the width. 1317 * @param height the height. 1318 * @param imageType the image type. 1319 * @param info carries back chart state information ({@code null} 1320 * permitted). 1321 * 1322 * @return A buffered image. 1323 */ 1324 public BufferedImage createBufferedImage(int width, int height, 1325 int imageType, ChartRenderingInfo info) { 1326 BufferedImage image = new BufferedImage(width, height, imageType); 1327 Graphics2D g2 = image.createGraphics(); 1328 draw(g2, new Rectangle2D.Double(0, 0, width, height), null, info); 1329 g2.dispose(); 1330 return image; 1331 } 1332 1333 /** 1334 * Creates and returns a buffered image into which the chart has been drawn. 1335 * 1336 * @param imageWidth the image width. 1337 * @param imageHeight the image height. 1338 * @param drawWidth the width for drawing the chart (will be scaled to 1339 * fit image). 1340 * @param drawHeight the height for drawing the chart (will be scaled to 1341 * fit image). 1342 * @param info optional object for collection chart dimension and entity 1343 * information. 1344 * 1345 * @return A buffered image. 1346 */ 1347 public BufferedImage createBufferedImage(int imageWidth, 1348 int imageHeight, 1349 double drawWidth, 1350 double drawHeight, 1351 ChartRenderingInfo info) { 1352 1353 BufferedImage image = new BufferedImage(imageWidth, imageHeight, 1354 BufferedImage.TYPE_INT_ARGB); 1355 Graphics2D g2 = image.createGraphics(); 1356 double scaleX = imageWidth / drawWidth; 1357 double scaleY = imageHeight / drawHeight; 1358 AffineTransform st = AffineTransform.getScaleInstance(scaleX, scaleY); 1359 g2.transform(st); 1360 draw(g2, new Rectangle2D.Double(0, 0, drawWidth, drawHeight), null, 1361 info); 1362 g2.dispose(); 1363 return image; 1364 } 1365 1366 /** 1367 * Handles a 'click' on the chart. JFreeChart is not a UI component, so 1368 * some other object (for example, {@link ChartPanel}) needs to capture 1369 * the click event and pass it onto the JFreeChart object. 1370 * If you are not using JFreeChart in a client application, then this 1371 * method is not required. 1372 * 1373 * @param x x-coordinate of the click (in Java2D space). 1374 * @param y y-coordinate of the click (in Java2D space). 1375 * @param info contains chart dimension and entity information 1376 * ({@code null} not permitted). 1377 */ 1378 public void handleClick(int x, int y, ChartRenderingInfo info) { 1379 // pass the click on to the plot... 1380 // rely on the plot to post a plot change event and redraw the chart... 1381 this.plot.handleClick(x, y, info.getPlotInfo()); 1382 } 1383 1384 /** 1385 * Registers an object for notification of changes to the chart. 1386 * 1387 * @param listener the listener ({@code null} not permitted). 1388 * 1389 * @see #removeChangeListener(ChartChangeListener) 1390 */ 1391 public void addChangeListener(ChartChangeListener listener) { 1392 Args.nullNotPermitted(listener, "listener"); 1393 this.changeListeners.add(ChartChangeListener.class, listener); 1394 } 1395 1396 /** 1397 * Deregisters an object for notification of changes to the chart. 1398 * 1399 * @param listener the listener ({@code null} not permitted) 1400 * 1401 * @see #addChangeListener(ChartChangeListener) 1402 */ 1403 public void removeChangeListener(ChartChangeListener listener) { 1404 Args.nullNotPermitted(listener, "listener"); 1405 this.changeListeners.remove(ChartChangeListener.class, listener); 1406 } 1407 1408 /** 1409 * Sends a default {@link ChartChangeEvent} to all registered listeners. 1410 * <P> 1411 * This method is for convenience only. 1412 */ 1413 public void fireChartChanged() { 1414 ChartChangeEvent event = new ChartChangeEvent(this); 1415 notifyListeners(event); 1416 } 1417 1418 /** 1419 * Sends a {@link ChartChangeEvent} to all registered listeners. 1420 * 1421 * @param event information about the event that triggered the 1422 * notification. 1423 */ 1424 protected void notifyListeners(ChartChangeEvent event) { 1425 if (this.notify) { 1426 Object[] listeners = this.changeListeners.getListenerList(); 1427 for (int i = listeners.length - 2; i >= 0; i -= 2) { 1428 if (listeners[i] == ChartChangeListener.class) { 1429 ((ChartChangeListener) listeners[i + 1]).chartChanged( 1430 event); 1431 } 1432 } 1433 } 1434 } 1435 1436 /** 1437 * Registers an object for notification of progress events relating to the 1438 * chart. 1439 * 1440 * @param listener the object being registered. 1441 * 1442 * @see #removeProgressListener(ChartProgressListener) 1443 */ 1444 public void addProgressListener(ChartProgressListener listener) { 1445 this.progressListeners.add(ChartProgressListener.class, listener); 1446 } 1447 1448 /** 1449 * Deregisters an object for notification of changes to the chart. 1450 * 1451 * @param listener the object being deregistered. 1452 * 1453 * @see #addProgressListener(ChartProgressListener) 1454 */ 1455 public void removeProgressListener(ChartProgressListener listener) { 1456 this.progressListeners.remove(ChartProgressListener.class, listener); 1457 } 1458 1459 /** 1460 * Sends a {@link ChartProgressEvent} to all registered listeners. 1461 * 1462 * @param event information about the event that triggered the 1463 * notification. 1464 */ 1465 protected void notifyListeners(ChartProgressEvent event) { 1466 Object[] listeners = this.progressListeners.getListenerList(); 1467 for (int i = listeners.length - 2; i >= 0; i -= 2) { 1468 if (listeners[i] == ChartProgressListener.class) { 1469 ((ChartProgressListener) listeners[i + 1]).chartProgress(event); 1470 } 1471 } 1472 } 1473 1474 /** 1475 * Receives notification that a chart title has changed, and passes this 1476 * on to registered listeners. 1477 * 1478 * @param event information about the chart title change. 1479 */ 1480 @Override 1481 public void titleChanged(TitleChangeEvent event) { 1482 event.setChart(this); 1483 notifyListeners(event); 1484 } 1485 1486 /** 1487 * Receives notification that the plot has changed, and passes this on to 1488 * registered listeners. 1489 * 1490 * @param event information about the plot change. 1491 */ 1492 @Override 1493 public void plotChanged(PlotChangeEvent event) { 1494 event.setChart(this); 1495 notifyListeners(event); 1496 } 1497 1498 /** 1499 * Tests this chart for equality with another object. 1500 * 1501 * @param obj the object ({@code null} permitted). 1502 * 1503 * @return A boolean. 1504 */ 1505 @Override 1506 public boolean equals(Object obj) { 1507 if (obj == this) { 1508 return true; 1509 } 1510 if (!(obj instanceof JFreeChart)) { 1511 return false; 1512 } 1513 JFreeChart that = (JFreeChart) obj; 1514 if (!Objects.equals(this.renderingHints, that.renderingHints)) { 1515 return false; 1516 } 1517 if (this.borderVisible != that.borderVisible) { 1518 return false; 1519 } 1520 if (this.elementHinting != that.elementHinting) { 1521 return false; 1522 } 1523 if (!Objects.equals(this.borderStroke, that.borderStroke)) { 1524 return false; 1525 } 1526 if (!PaintUtils.equal(this.borderPaint, that.borderPaint)) { 1527 return false; 1528 } 1529 if (!Objects.equals(this.padding, that.padding)) { 1530 return false; 1531 } 1532 if (!Objects.equals(this.title, that.title)) { 1533 return false; 1534 } 1535 if (!Objects.equals(this.subtitles, that.subtitles)) { 1536 return false; 1537 } 1538 if (!Objects.equals(this.plot, that.plot)) { 1539 return false; 1540 } 1541 if (!PaintUtils.equal(this.backgroundPaint, that.backgroundPaint)) { 1542 return false; 1543 } 1544 if (!Objects.equals(this.backgroundImage, that.backgroundImage)) { 1545 return false; 1546 } 1547 if (this.backgroundImageAlignment != that.backgroundImageAlignment) { 1548 return false; 1549 } 1550 if (Float.floatToIntBits(this.backgroundImageAlpha) != 1551 Float.floatToIntBits(that.backgroundImageAlpha)) { 1552 return false; 1553 } 1554 if (this.notify != that.notify) { 1555 return false; 1556 } 1557 if (!Objects.equals(this.id, that.id)) { 1558 return false; 1559 } 1560 return true; 1561 } 1562 1563 @Override 1564 public int hashCode() { 1565 int hash = 7; 1566 hash = 43 * hash + Objects.hashCode(this.renderingHints); 1567 hash = 43 * hash + Objects.hashCode(this.id); 1568 hash = 43 * hash + (this.borderVisible ? 1 : 0); 1569 hash = 43 * hash + Objects.hashCode(this.borderStroke); 1570 hash = 43 * hash + HashUtils.hashCodeForPaint(this.borderPaint); 1571 hash = 43 * hash + Objects.hashCode(this.padding); 1572 hash = 43 * hash + Objects.hashCode(this.title); 1573 hash = 43 * hash + Objects.hashCode(this.subtitles); 1574 hash = 43 * hash + Objects.hashCode(this.plot); 1575 hash = 43 * hash + HashUtils.hashCodeForPaint(this.backgroundPaint); 1576 hash = 43 * hash + Objects.hashCode(this.backgroundImage); 1577 hash = 43 * hash + this.backgroundImageAlignment; 1578 hash = 43 * hash + Float.floatToIntBits(this.backgroundImageAlpha); 1579 hash = 43 * hash + (this.notify ? 1 : 0); 1580 hash = 43 * hash + (this.elementHinting ? 1 : 0); 1581 return hash; 1582 } 1583 1584 /** 1585 * Provides serialization support. 1586 * 1587 * @param stream the output stream. 1588 * 1589 * @throws IOException if there is an I/O error. 1590 */ 1591 private void writeObject(ObjectOutputStream stream) throws IOException { 1592 stream.defaultWriteObject(); 1593 SerialUtils.writeStroke(this.borderStroke, stream); 1594 SerialUtils.writePaint(this.borderPaint, stream); 1595 SerialUtils.writePaint(this.backgroundPaint, stream); 1596 } 1597 1598 /** 1599 * Provides serialization support. 1600 * 1601 * @param stream the input stream. 1602 * 1603 * @throws IOException if there is an I/O error. 1604 * @throws ClassNotFoundException if there is a classpath problem. 1605 */ 1606 private void readObject(ObjectInputStream stream) 1607 throws IOException, ClassNotFoundException { 1608 stream.defaultReadObject(); 1609 this.borderStroke = SerialUtils.readStroke(stream); 1610 this.borderPaint = SerialUtils.readPaint(stream); 1611 this.backgroundPaint = SerialUtils.readPaint(stream); 1612 this.progressListeners = new EventListenerList(); 1613 this.changeListeners = new EventListenerList(); 1614 this.renderingHints = new RenderingHints( 1615 RenderingHints.KEY_ANTIALIASING, 1616 RenderingHints.VALUE_ANTIALIAS_ON); 1617 this.renderingHints.put(RenderingHints.KEY_STROKE_CONTROL, 1618 RenderingHints.VALUE_STROKE_PURE); 1619 1620 // register as a listener with sub-components... 1621 if (this.title != null) { 1622 this.title.addChangeListener(this); 1623 } 1624 1625 for (int i = 0; i < getSubtitleCount(); i++) { 1626 getSubtitle(i).addChangeListener(this); 1627 } 1628 this.plot.addChangeListener(this); 1629 } 1630 1631 /** 1632 * Clones the object, and takes care of listeners. 1633 * Note: caller shall register its own listeners on cloned graph. 1634 * 1635 * @return A clone. 1636 * 1637 * @throws CloneNotSupportedException if the chart is not cloneable. 1638 */ 1639 @Override 1640 public Object clone() throws CloneNotSupportedException { 1641 JFreeChart chart = (JFreeChart) super.clone(); 1642 1643 chart.renderingHints = (RenderingHints) this.renderingHints.clone(); 1644 // private boolean borderVisible; 1645 // private transient Stroke borderStroke; 1646 // private transient Paint borderPaint; 1647 1648 if (this.title != null) { 1649 chart.title = (TextTitle) this.title.clone(); 1650 chart.title.addChangeListener(chart); 1651 } 1652 1653 chart.subtitles = new ArrayList<>(); 1654 for (int i = 0; i < getSubtitleCount(); i++) { 1655 Title subtitle = (Title) getSubtitle(i).clone(); 1656 chart.subtitles.add(subtitle); 1657 subtitle.addChangeListener(chart); 1658 } 1659 1660 if (this.plot != null) { 1661 chart.plot = (Plot) this.plot.clone(); 1662 chart.plot.addChangeListener(chart); 1663 } 1664 1665 chart.progressListeners = new EventListenerList(); 1666 chart.changeListeners = new EventListenerList(); 1667 return chart; 1668 } 1669 1670}