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