1 /* =========================================================== 2 * JFreeChart : a free chart library for the Java(tm) platform 3 * =========================================================== 4 * 5 * (C) Copyright 2000-2013, by Object Refinery Limited and Contributors. 6 * 7 * Project Info: http://www.jfree.org/jfreechart/index.html 8 * 9 * This library is free software; you can redistribute it and/or modify it 10 * under the terms of the GNU Lesser General Public License as published by 11 * the Free Software Foundation; either version 2.1 of the License, or 12 * (at your option) any later version. 13 * 14 * This library is distributed in the hope that it will be useful, but 15 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 16 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 17 * License for more details. 18 * 19 * You should have received a copy of the GNU Lesser General Public 20 * License along with this library; if not, write to the Free Software 21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 22 * USA. 23 * 24 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 25 * Other names may be trademarks of their respective owners.] 26 * 27 * --------------- 28 * JFreeChart.java 29 * --------------- 30 * (C) Copyright 2000-2013, by Object Refinery Limited and Contributors. 31 * 32 * Original Author: David Gilbert (for Object Refinery Limited); 33 * Contributor(s): Andrzej Porebski; 34 * David Li; 35 * Wolfgang Irler; 36 * Christian W. Zuckschwerdt; 37 * Klaus Rheinwald; 38 * Nicolas Brodu; 39 * Peter Kolb (patch 2603321); 40 * 41 * NOTE: The above list of contributors lists only the people that have 42 * contributed to this source file (JFreeChart.java) - for a list of ALL 43 * contributors to the project, please see the README.txt file. 44 * 45 * Changes (from 20-Jun-2001) 46 * -------------------------- 47 * 20-Jun-2001 : Modifications submitted by Andrzej Porebski for legend 48 * placement; 49 * 21-Jun-2001 : Removed JFreeChart parameter from Plot constructors (DG); 50 * 22-Jun-2001 : Multiple titles added (original code by David Berry, with 51 * reworkings by DG); 52 * 18-Sep-2001 : Updated header (DG); 53 * 15-Oct-2001 : Moved data source classes into new package 54 * com.jrefinery.data.* (DG); 55 * 18-Oct-2001 : New factory method for creating VerticalXYBarChart (DG); 56 * 19-Oct-2001 : Moved series paint and stroke methods to the Plot class (DG); 57 * Moved static chart creation methods to new ChartFactory 58 * class (DG); 59 * 22-Oct-2001 : Renamed DataSource.java --> Dataset.java etc. (DG); 60 * Fixed bug where chart isn't registered with the dataset (DG); 61 * 07-Nov-2001 : Fixed bug where null title in constructor causes 62 * exception (DG); 63 * Tidied up event notification code (DG); 64 * 17-Nov-2001 : Added getLegendItemCount() method (DG); 65 * 21-Nov-2001 : Set clipping in draw method to ensure that nothing gets drawn 66 * outside the chart area (DG); 67 * 11-Dec-2001 : Added the createBufferedImage() method, taken from the 68 * JFreeChartServletDemo class (DG); 69 * 13-Dec-2001 : Added tooltips (DG); 70 * 16-Jan-2002 : Added handleClick() method (DG); 71 * 22-Jan-2002 : Fixed bug correlating legend labels with pie data (DG); 72 * 05-Feb-2002 : Removed redundant tooltips code (DG); 73 * 19-Feb-2002 : Added accessor methods for the backgroundImage and 74 * backgroundImageAlpha attributes (DG); 75 * 21-Feb-2002 : Added static fields for INFO, COPYRIGHT, LICENCE, CONTRIBUTORS 76 * and LIBRARIES. These can be used to display information about 77 * JFreeChart (DG); 78 * 06-Mar-2002 : Moved constants to JFreeChartConstants interface (DG); 79 * 18-Apr-2002 : PieDataset is no longer sorted (oldman); 80 * 23-Apr-2002 : Moved dataset to the Plot class (DG); 81 * 13-Jun-2002 : Added an extra draw() method (DG); 82 * 25-Jun-2002 : Implemented the Drawable interface and removed redundant 83 * imports (DG); 84 * 26-Jun-2002 : Added another createBufferedImage() method (DG); 85 * 18-Sep-2002 : Fixed issues reported by Checkstyle (DG); 86 * 23-Sep-2002 : Added new contributor (DG); 87 * 28-Oct-2002 : Created main title and subtitle list to replace existing title 88 * list (DG); 89 * 08-Jan-2003 : Added contributor (DG); 90 * 17-Jan-2003 : Added new constructor (DG); 91 * 22-Jan-2003 : Added ChartColor class by Cameron Riley, and background image 92 * alignment code by Christian W. Zuckschwerdt (DG); 93 * 11-Feb-2003 : Added flag to allow suppression of chart change events, based 94 * on a suggestion by Klaus Rheinwald (DG); 95 * 04-Mar-2003 : Added small fix for suppressed chart change events (see bug id 96 * 690865) (DG); 97 * 10-Mar-2003 : Added Benoit Xhenseval to contributors (DG); 98 * 26-Mar-2003 : Implemented Serializable (DG); 99 * 15-Jul-2003 : Added an optional border for the chart (DG); 100 * 11-Sep-2003 : Took care of listeners while cloning (NB); 101 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG); 102 * 22-Sep-2003 : Added nullpointer checks. 103 * 25-Sep-2003 : Added nullpointer checks too (NB). 104 * 03-Dec-2003 : Legends are now registered by this class instead of using the 105 * old constructor way (TM); 106 * 03-Dec-2003 : Added anchorPoint to draw() method (DG); 107 * 08-Jan-2004 : Reworked title code, introducing line wrapping (DG); 108 * 09-Feb-2004 : Created additional createBufferedImage() method (DG); 109 * 05-Apr-2004 : Added new createBufferedImage() method (DG); 110 * 27-May-2004 : Moved constants from JFreeChartConstants.java back to this 111 * class (DG); 112 * 25-Nov-2004 : Updates for changes to Title class (DG); 113 * 06-Jan-2005 : Change lookup for default background color (DG); 114 * 31-Jan-2005 : Added Don Elliott to contributors (DG); 115 * 02-Feb-2005 : Added clearSubtitles() method (DG); 116 * 03-Feb-2005 : Added Mofeed Shahin to contributors (DG); 117 * 08-Feb-2005 : Updated for RectangleConstraint changes (DG); 118 * 28-Mar-2005 : Renamed Legend --> OldLegend (DG); 119 * 12-Apr-2005 : Added methods to access legend(s) in subtitle list (DG); 120 * 13-Apr-2005 : Added removeLegend() and removeSubtitle() methods (DG); 121 * 20-Apr-2005 : Modified to collect chart entities from titles and 122 * subtitles (DG); 123 * 26-Apr-2005 : Removed LOGGER (DG); 124 * 06-Jun-2005 : Added addLegend() method and padding attribute, fixed equals() 125 * method (DG); 126 * 24-Nov-2005 : Removed OldLegend and related code - don't want to support 127 * this in 1.0.0 final (DG); 128 * ------------- JFREECHART 1.0.x --------------------------------------------- 129 * 27-Jan-2006 : Updated version number (DG); 130 * 07-Dec-2006 : Added some missing credits (DG); 131 * 17-Jan-2007 : Added Darren Jung to contributor list (DG); 132 * 05-Mar-2007 : Added Sergei Ivanov to the contributor list (DG); 133 * 16-Mar-2007 : Modified initial legend border (DG); 134 * 22-Mar-2007 : New methods for text anti-aliasing (DG); 135 * 16-May-2007 : Fixed argument check in getSubtitle(), copy list in 136 * get/setSubtitles(), and added new addSubtitle(int, Title) 137 * method (DG); 138 * 05-Jun-2007 : Add change listener to default legend (DG); 139 * 04-Dec-2007 : In createBufferedImage() methods, make the default image type 140 * BufferedImage.TYPE_INT_ARGB (thanks to Klaus Rheinwald) (DG); 141 * 05-Dec-2007 : Fixed bug 1749124 (not registering as listener with 142 * TextTitle) (DG); 143 * 23-Apr-2008 : Added new contributor (Diego Pierangeli) (DG); 144 * 16-May-2008 : Added new contributor (Michael Siemer) (DG); 145 * 19-Sep-2008 : Check for title visibility (DG); 146 * 18-Dec-2008 : Use ResourceBundleWrapper - see patch 1607918 by 147 * Jess Thrysoee (DG); 148 * 19-Mar-2009 : Added entity support - see patch 2603321 by Peter Kolb (DG); 149 * 19-May-2009 : Fixed FindBugs warnings, patch by Michal Wozniak (DG); 150 * 29-Jun-2009 : Check visibility flag in main title (DG); 151 * 02-Jul-2013 : Use ParamChecks class (DG); 152 * 153 */ 154 155 package org.jfree.chart; 156 157 import java.awt.AlphaComposite; 158 import java.awt.BasicStroke; 159 import java.awt.Color; 160 import java.awt.Composite; 161 import java.awt.Font; 162 import java.awt.Graphics2D; 163 import java.awt.Image; 164 import java.awt.Paint; 165 import java.awt.RenderingHints; 166 import java.awt.Shape; 167 import java.awt.Stroke; 168 import java.awt.geom.AffineTransform; 169 import java.awt.geom.Point2D; 170 import java.awt.geom.Rectangle2D; 171 import java.awt.image.BufferedImage; 172 import java.io.IOException; 173 import java.io.ObjectInputStream; 174 import java.io.ObjectOutputStream; 175 import java.io.Serializable; 176 import java.net.URL; 177 import java.util.ArrayList; 178 import java.util.Arrays; 179 import java.util.Iterator; 180 import java.util.List; 181 import java.util.ResourceBundle; 182 183 import javax.swing.ImageIcon; 184 import javax.swing.UIManager; 185 import javax.swing.event.EventListenerList; 186 187 import org.jfree.JCommon; 188 import org.jfree.chart.block.BlockParams; 189 import org.jfree.chart.block.EntityBlockResult; 190 import org.jfree.chart.block.LengthConstraintType; 191 import org.jfree.chart.block.LineBorder; 192 import org.jfree.chart.block.RectangleConstraint; 193 import org.jfree.chart.entity.EntityCollection; 194 import org.jfree.chart.entity.JFreeChartEntity; 195 import org.jfree.chart.event.ChartChangeEvent; 196 import org.jfree.chart.event.ChartChangeListener; 197 import org.jfree.chart.event.ChartProgressEvent; 198 import org.jfree.chart.event.ChartProgressListener; 199 import org.jfree.chart.event.PlotChangeEvent; 200 import org.jfree.chart.event.PlotChangeListener; 201 import org.jfree.chart.event.TitleChangeEvent; 202 import org.jfree.chart.event.TitleChangeListener; 203 import org.jfree.chart.plot.CategoryPlot; 204 import org.jfree.chart.plot.Plot; 205 import org.jfree.chart.plot.PlotRenderingInfo; 206 import org.jfree.chart.plot.XYPlot; 207 import org.jfree.chart.title.LegendTitle; 208 import org.jfree.chart.title.TextTitle; 209 import org.jfree.chart.title.Title; 210 import org.jfree.chart.util.ParamChecks; 211 import org.jfree.chart.util.ResourceBundleWrapper; 212 import org.jfree.data.Range; 213 import org.jfree.io.SerialUtilities; 214 import org.jfree.ui.Align; 215 import org.jfree.ui.Drawable; 216 import org.jfree.ui.HorizontalAlignment; 217 import org.jfree.ui.RectangleEdge; 218 import org.jfree.ui.RectangleInsets; 219 import org.jfree.ui.Size2D; 220 import org.jfree.ui.VerticalAlignment; 221 import org.jfree.ui.about.Contributor; 222 import org.jfree.ui.about.Licences; 223 import org.jfree.ui.about.ProjectInfo; 224 import org.jfree.util.ObjectUtilities; 225 import org.jfree.util.PaintUtilities; 226 227 /** 228 * A chart class implemented using the Java 2D APIs. The current version 229 * supports bar charts, line charts, pie charts and xy plots (including time 230 * series data). 231 * <P> 232 * JFreeChart coordinates several objects to achieve its aim of being able to 233 * draw a chart on a Java 2D graphics device: a list of {@link Title} objects 234 * (which often includes the chart's legend), a {@link Plot} and a 235 * {@link org.jfree.data.general.Dataset} (the plot in turn manages a 236 * domain axis and a range axis). 237 * <P> 238 * You should use a {@link ChartPanel} to display a chart in a GUI. 239 * <P> 240 * The {@link ChartFactory} class contains static methods for creating 241 * 'ready-made' charts. 242 * 243 * @see ChartPanel 244 * @see ChartFactory 245 * @see Title 246 * @see Plot 247 */ 248 public class JFreeChart implements Drawable, TitleChangeListener, 249 PlotChangeListener, Serializable, Cloneable { 250 251 /** For serialization. */ 252 private static final long serialVersionUID = -3470703747817429120L; 253 254 /** Information about the project. */ 255 public static final ProjectInfo INFO = new JFreeChartInfo(); 256 257 /** The default font for titles. */ 258 public static final Font DEFAULT_TITLE_FONT 259 = new Font("SansSerif", Font.BOLD, 18); 260 261 /** The default background color. */ 262 public static final Paint DEFAULT_BACKGROUND_PAINT 263 = UIManager.getColor("Panel.background"); 264 265 /** The default background image. */ 266 public static final Image DEFAULT_BACKGROUND_IMAGE = null; 267 268 /** The default background image alignment. */ 269 public static final int DEFAULT_BACKGROUND_IMAGE_ALIGNMENT = Align.FIT; 270 271 /** The default background image alpha. */ 272 public static final float DEFAULT_BACKGROUND_IMAGE_ALPHA = 0.5f; 273 274 /** 275 * The key for a rendering hint that can suppress the generation of a 276 * shadow effect when drawing the chart. The hint value must be a 277 * Boolean. 278 * 279 * @since 1.0.16 280 */ 281 public static final RenderingHints.Key KEY_SUPPRESS_SHADOW_GENERATION 282 = new RenderingHints.Key(0) { 283 @Override 284 public boolean isCompatibleValue(Object val) { 285 return val instanceof Boolean; 286 } 287 }; 288 289 /** 290 * Rendering hints that will be used for chart drawing. This should never 291 * be <code>null</code>. 292 */ 293 private transient RenderingHints renderingHints; 294 295 /** A flag that controls whether or not the chart border is drawn. */ 296 private boolean borderVisible; 297 298 /** The stroke used to draw the chart border (if visible). */ 299 private transient Stroke borderStroke; 300 301 /** The paint used to draw the chart border (if visible). */ 302 private transient Paint borderPaint; 303 304 /** The padding between the chart border and the chart drawing area. */ 305 private RectangleInsets padding; 306 307 /** The chart title (optional). */ 308 private TextTitle title; 309 310 /** 311 * The chart subtitles (zero, one or many). This field should never be 312 * <code>null</code>. 313 */ 314 private List subtitles; 315 316 /** Draws the visual representation of the data. */ 317 private Plot plot; 318 319 /** Paint used to draw the background of the chart. */ 320 private transient Paint backgroundPaint; 321 322 /** An optional background image for the chart. */ 323 private transient Image backgroundImage; // todo: not serialized yet 324 325 /** The alignment for the background image. */ 326 private int backgroundImageAlignment = Align.FIT; 327 328 /** The alpha transparency for the background image. */ 329 private float backgroundImageAlpha = 0.5f; 330 331 /** Storage for registered change listeners. */ 332 private transient EventListenerList changeListeners; 333 334 /** Storage for registered progress listeners. */ 335 private transient EventListenerList progressListeners; 336 337 /** 338 * A flag that can be used to enable/disable notification of chart change 339 * events. 340 */ 341 private boolean notify; 342 343 /** 344 * Creates a new chart based on the supplied plot. The chart will have 345 * a legend added automatically, but no title (although you can easily add 346 * one later). 347 * <br><br> 348 * Note that the {@link ChartFactory} class contains a range 349 * of static methods that will return ready-made charts, and often this 350 * is a more convenient way to create charts than using this constructor. 351 * 352 * @param plot the plot (<code>null</code> not permitted). 353 */ JFreeChart(Plot plot)354 public JFreeChart(Plot plot) { 355 this(null, null, plot, true); 356 } 357 358 /** 359 * Creates a new chart with the given title and plot. A default font 360 * ({@link #DEFAULT_TITLE_FONT}) is used for the title, and the chart will 361 * have a legend added automatically. 362 * <br><br> 363 * Note that the {@link ChartFactory} class contains a range 364 * of static methods that will return ready-made charts, and often this 365 * is a more convenient way to create charts than using this constructor. 366 * 367 * @param title the chart title (<code>null</code> permitted). 368 * @param plot the plot (<code>null</code> not permitted). 369 */ JFreeChart(String title, Plot plot)370 public JFreeChart(String title, Plot plot) { 371 this(title, JFreeChart.DEFAULT_TITLE_FONT, plot, true); 372 } 373 374 /** 375 * Creates a new chart with the given title and plot. The 376 * <code>createLegend</code> argument specifies whether or not a legend 377 * should be added to the chart. 378 * <br><br> 379 * Note that the {@link ChartFactory} class contains a range 380 * of static methods that will return ready-made charts, and often this 381 * is a more convenient way to create charts than using this constructor. 382 * 383 * @param title the chart title (<code>null</code> permitted). 384 * @param titleFont the font for displaying the chart title 385 * (<code>null</code> permitted). 386 * @param plot controller of the visual representation of the data 387 * (<code>null</code> not permitted). 388 * @param createLegend a flag indicating whether or not a legend should 389 * be created for the chart. 390 */ JFreeChart(String title, Font titleFont, Plot plot, boolean createLegend)391 public JFreeChart(String title, Font titleFont, Plot plot, 392 boolean createLegend) { 393 394 ParamChecks.nullNotPermitted(plot, "plot"); 395 396 // create storage for listeners... 397 this.progressListeners = new EventListenerList(); 398 this.changeListeners = new EventListenerList(); 399 this.notify = true; // default is to notify listeners when the 400 // chart changes 401 402 this.renderingHints = new RenderingHints( 403 RenderingHints.KEY_ANTIALIASING, 404 RenderingHints.VALUE_ANTIALIAS_ON); 405 406 this.borderVisible = false; 407 this.borderStroke = new BasicStroke(1.0f); 408 this.borderPaint = Color.black; 409 410 this.padding = RectangleInsets.ZERO_INSETS; 411 412 this.plot = plot; 413 plot.addChangeListener(this); 414 415 this.subtitles = new ArrayList(); 416 417 // create a legend, if requested... 418 if (createLegend) { 419 LegendTitle legend = new LegendTitle(this.plot); 420 legend.setMargin(new RectangleInsets(1.0, 1.0, 1.0, 1.0)); 421 legend.setFrame(new LineBorder()); 422 legend.setBackgroundPaint(Color.white); 423 legend.setPosition(RectangleEdge.BOTTOM); 424 this.subtitles.add(legend); 425 legend.addChangeListener(this); 426 } 427 428 // add the chart title, if one has been specified... 429 if (title != null) { 430 if (titleFont == null) { 431 titleFont = DEFAULT_TITLE_FONT; 432 } 433 this.title = new TextTitle(title, titleFont); 434 this.title.addChangeListener(this); 435 } 436 437 this.backgroundPaint = DEFAULT_BACKGROUND_PAINT; 438 439 this.backgroundImage = DEFAULT_BACKGROUND_IMAGE; 440 this.backgroundImageAlignment = DEFAULT_BACKGROUND_IMAGE_ALIGNMENT; 441 this.backgroundImageAlpha = DEFAULT_BACKGROUND_IMAGE_ALPHA; 442 443 } 444 445 /** 446 * Returns the collection of rendering hints for the chart. 447 * 448 * @return The rendering hints for the chart (never <code>null</code>). 449 * 450 * @see #setRenderingHints(RenderingHints) 451 */ getRenderingHints()452 public RenderingHints getRenderingHints() { 453 return this.renderingHints; 454 } 455 456 /** 457 * Sets the rendering hints for the chart. These will be added (using the 458 * Graphics2D.addRenderingHints() method) near the start of the 459 * JFreeChart.draw() method. 460 * 461 * @param renderingHints the rendering hints (<code>null</code> not 462 * permitted). 463 * 464 * @see #getRenderingHints() 465 */ setRenderingHints(RenderingHints renderingHints)466 public void setRenderingHints(RenderingHints renderingHints) { 467 ParamChecks.nullNotPermitted(renderingHints, "renderingHints"); 468 this.renderingHints = renderingHints; 469 fireChartChanged(); 470 } 471 472 /** 473 * Returns a flag that controls whether or not a border is drawn around the 474 * outside of the chart. 475 * 476 * @return A boolean. 477 * 478 * @see #setBorderVisible(boolean) 479 */ isBorderVisible()480 public boolean isBorderVisible() { 481 return this.borderVisible; 482 } 483 484 /** 485 * Sets a flag that controls whether or not a border is drawn around the 486 * outside of the chart. 487 * 488 * @param visible the flag. 489 * 490 * @see #isBorderVisible() 491 */ setBorderVisible(boolean visible)492 public void setBorderVisible(boolean visible) { 493 this.borderVisible = visible; 494 fireChartChanged(); 495 } 496 497 /** 498 * Returns the stroke used to draw the chart border (if visible). 499 * 500 * @return The border stroke. 501 * 502 * @see #setBorderStroke(Stroke) 503 */ getBorderStroke()504 public Stroke getBorderStroke() { 505 return this.borderStroke; 506 } 507 508 /** 509 * Sets the stroke used to draw the chart border (if visible). 510 * 511 * @param stroke the stroke. 512 * 513 * @see #getBorderStroke() 514 */ setBorderStroke(Stroke stroke)515 public void setBorderStroke(Stroke stroke) { 516 this.borderStroke = stroke; 517 fireChartChanged(); 518 } 519 520 /** 521 * Returns the paint used to draw the chart border (if visible). 522 * 523 * @return The border paint. 524 * 525 * @see #setBorderPaint(Paint) 526 */ getBorderPaint()527 public Paint getBorderPaint() { 528 return this.borderPaint; 529 } 530 531 /** 532 * Sets the paint used to draw the chart border (if visible). 533 * 534 * @param paint the paint. 535 * 536 * @see #getBorderPaint() 537 */ setBorderPaint(Paint paint)538 public void setBorderPaint(Paint paint) { 539 this.borderPaint = paint; 540 fireChartChanged(); 541 } 542 543 /** 544 * Returns the padding between the chart border and the chart drawing area. 545 * 546 * @return The padding (never <code>null</code>). 547 * 548 * @see #setPadding(RectangleInsets) 549 */ getPadding()550 public RectangleInsets getPadding() { 551 return this.padding; 552 } 553 554 /** 555 * Sets the padding between the chart border and the chart drawing area, 556 * and sends a {@link ChartChangeEvent} to all registered listeners. 557 * 558 * @param padding the padding (<code>null</code> not permitted). 559 * 560 * @see #getPadding() 561 */ setPadding(RectangleInsets padding)562 public void setPadding(RectangleInsets padding) { 563 ParamChecks.nullNotPermitted(padding, "padding"); 564 this.padding = padding; 565 notifyListeners(new ChartChangeEvent(this)); 566 } 567 568 /** 569 * Returns the main chart title. Very often a chart will have just one 570 * title, so we make this case simple by providing accessor methods for 571 * the main title. However, multiple titles are supported - see the 572 * {@link #addSubtitle(Title)} method. 573 * 574 * @return The chart title (possibly <code>null</code>). 575 * 576 * @see #setTitle(TextTitle) 577 */ getTitle()578 public TextTitle getTitle() { 579 return this.title; 580 } 581 582 /** 583 * Sets the main title for the chart and sends a {@link ChartChangeEvent} 584 * to all registered listeners. If you do not want a title for the 585 * chart, set it to <code>null</code>. If you want more than one title on 586 * a chart, use the {@link #addSubtitle(Title)} method. 587 * 588 * @param title the title (<code>null</code> permitted). 589 * 590 * @see #getTitle() 591 */ setTitle(TextTitle title)592 public void setTitle(TextTitle title) { 593 if (this.title != null) { 594 this.title.removeChangeListener(this); 595 } 596 this.title = title; 597 if (title != null) { 598 title.addChangeListener(this); 599 } 600 fireChartChanged(); 601 } 602 603 /** 604 * Sets the chart title and sends a {@link ChartChangeEvent} to all 605 * registered listeners. This is a convenience method that ends up calling 606 * the {@link #setTitle(TextTitle)} method. If there is an existing title, 607 * its text is updated, otherwise a new title using the default font is 608 * added to the chart. If <code>text</code> is <code>null</code> the chart 609 * title is set to <code>null</code>. 610 * 611 * @param text the title text (<code>null</code> permitted). 612 * 613 * @see #getTitle() 614 */ setTitle(String text)615 public void setTitle(String text) { 616 if (text != null) { 617 if (this.title == null) { 618 setTitle(new TextTitle(text, JFreeChart.DEFAULT_TITLE_FONT)); 619 } 620 else { 621 this.title.setText(text); 622 } 623 } 624 else { 625 setTitle((TextTitle) null); 626 } 627 } 628 629 /** 630 * Adds a legend to the plot and sends a {@link ChartChangeEvent} to all 631 * registered listeners. 632 * 633 * @param legend the legend (<code>null</code> not permitted). 634 * 635 * @see #removeLegend() 636 */ addLegend(LegendTitle legend)637 public void addLegend(LegendTitle legend) { 638 addSubtitle(legend); 639 } 640 641 /** 642 * Returns the legend for the chart, if there is one. Note that a chart 643 * can have more than one legend - this method returns the first. 644 * 645 * @return The legend (possibly <code>null</code>). 646 * 647 * @see #getLegend(int) 648 */ getLegend()649 public LegendTitle getLegend() { 650 return getLegend(0); 651 } 652 653 /** 654 * Returns the nth legend for a chart, or <code>null</code>. 655 * 656 * @param index the legend index (zero-based). 657 * 658 * @return The legend (possibly <code>null</code>). 659 * 660 * @see #addLegend(LegendTitle) 661 */ getLegend(int index)662 public LegendTitle getLegend(int index) { 663 int seen = 0; 664 Iterator iterator = this.subtitles.iterator(); 665 while (iterator.hasNext()) { 666 Title subtitle = (Title) iterator.next(); 667 if (subtitle instanceof LegendTitle) { 668 if (seen == index) { 669 return (LegendTitle) subtitle; 670 } 671 else { 672 seen++; 673 } 674 } 675 } 676 return null; 677 } 678 679 /** 680 * Removes the first legend in the chart and sends a 681 * {@link ChartChangeEvent} to all registered listeners. 682 * 683 * @see #getLegend() 684 */ removeLegend()685 public void removeLegend() { 686 removeSubtitle(getLegend()); 687 } 688 689 /** 690 * Returns the list of subtitles for the chart. 691 * 692 * @return The subtitle list (possibly empty, but never <code>null</code>). 693 * 694 * @see #setSubtitles(List) 695 */ getSubtitles()696 public List getSubtitles() { 697 return new ArrayList(this.subtitles); 698 } 699 700 /** 701 * Sets the title list for the chart (completely replaces any existing 702 * titles) and sends a {@link ChartChangeEvent} to all registered 703 * listeners. 704 * 705 * @param subtitles the new list of subtitles (<code>null</code> not 706 * permitted). 707 * 708 * @see #getSubtitles() 709 */ setSubtitles(List subtitles)710 public void setSubtitles(List subtitles) { 711 if (subtitles == null) { 712 throw new NullPointerException("Null 'subtitles' argument."); 713 } 714 setNotify(false); 715 clearSubtitles(); 716 Iterator iterator = subtitles.iterator(); 717 while (iterator.hasNext()) { 718 Title t = (Title) iterator.next(); 719 if (t != null) { 720 addSubtitle(t); 721 } 722 } 723 setNotify(true); // this fires a ChartChangeEvent 724 } 725 726 /** 727 * Returns the number of titles for the chart. 728 * 729 * @return The number of titles for the chart. 730 * 731 * @see #getSubtitles() 732 */ getSubtitleCount()733 public int getSubtitleCount() { 734 return this.subtitles.size(); 735 } 736 737 /** 738 * Returns a chart subtitle. 739 * 740 * @param index the index of the chart subtitle (zero based). 741 * 742 * @return A chart subtitle. 743 * 744 * @see #addSubtitle(Title) 745 */ getSubtitle(int index)746 public Title getSubtitle(int index) { 747 if ((index < 0) || (index >= getSubtitleCount())) { 748 throw new IllegalArgumentException("Index out of range."); 749 } 750 return (Title) this.subtitles.get(index); 751 } 752 753 /** 754 * Adds a chart subtitle, and notifies registered listeners that the chart 755 * has been modified. 756 * 757 * @param subtitle the subtitle (<code>null</code> not permitted). 758 * 759 * @see #getSubtitle(int) 760 */ addSubtitle(Title subtitle)761 public void addSubtitle(Title subtitle) { 762 ParamChecks.nullNotPermitted(subtitle, "subtitle"); 763 this.subtitles.add(subtitle); 764 subtitle.addChangeListener(this); 765 fireChartChanged(); 766 } 767 768 /** 769 * Adds a subtitle at a particular position in the subtitle list, and sends 770 * a {@link ChartChangeEvent} to all registered listeners. 771 * 772 * @param index the index (in the range 0 to {@link #getSubtitleCount()}). 773 * @param subtitle the subtitle to add (<code>null</code> not permitted). 774 * 775 * @since 1.0.6 776 */ addSubtitle(int index, Title subtitle)777 public void addSubtitle(int index, Title subtitle) { 778 if (index < 0 || index > getSubtitleCount()) { 779 throw new IllegalArgumentException( 780 "The 'index' argument is out of range."); 781 } 782 ParamChecks.nullNotPermitted(subtitle, "subtitle"); 783 this.subtitles.add(index, subtitle); 784 subtitle.addChangeListener(this); 785 fireChartChanged(); 786 } 787 788 /** 789 * Clears all subtitles from the chart and sends a {@link ChartChangeEvent} 790 * to all registered listeners. 791 * 792 * @see #addSubtitle(Title) 793 */ clearSubtitles()794 public void clearSubtitles() { 795 Iterator iterator = this.subtitles.iterator(); 796 while (iterator.hasNext()) { 797 Title t = (Title) iterator.next(); 798 t.removeChangeListener(this); 799 } 800 this.subtitles.clear(); 801 fireChartChanged(); 802 } 803 804 /** 805 * Removes the specified subtitle and sends a {@link ChartChangeEvent} to 806 * all registered listeners. 807 * 808 * @param title the title. 809 * 810 * @see #addSubtitle(Title) 811 */ removeSubtitle(Title title)812 public void removeSubtitle(Title title) { 813 this.subtitles.remove(title); 814 fireChartChanged(); 815 } 816 817 /** 818 * Returns the plot for the chart. The plot is a class responsible for 819 * coordinating the visual representation of the data, including the axes 820 * (if any). 821 * 822 * @return The plot. 823 */ getPlot()824 public Plot getPlot() { 825 return this.plot; 826 } 827 828 /** 829 * Returns the plot cast as a {@link CategoryPlot}. 830 * <p> 831 * NOTE: if the plot is not an instance of {@link CategoryPlot}, then a 832 * <code>ClassCastException</code> is thrown. 833 * 834 * @return The plot. 835 * 836 * @see #getPlot() 837 */ getCategoryPlot()838 public CategoryPlot getCategoryPlot() { 839 return (CategoryPlot) this.plot; 840 } 841 842 /** 843 * Returns the plot cast as an {@link XYPlot}. 844 * <p> 845 * NOTE: if the plot is not an instance of {@link XYPlot}, then a 846 * <code>ClassCastException</code> is thrown. 847 * 848 * @return The plot. 849 * 850 * @see #getPlot() 851 */ getXYPlot()852 public XYPlot getXYPlot() { 853 return (XYPlot) this.plot; 854 } 855 856 /** 857 * Returns a flag that indicates whether or not anti-aliasing is used when 858 * the chart is drawn. 859 * 860 * @return The flag. 861 * 862 * @see #setAntiAlias(boolean) 863 */ getAntiAlias()864 public boolean getAntiAlias() { 865 Object val = this.renderingHints.get(RenderingHints.KEY_ANTIALIASING); 866 return RenderingHints.VALUE_ANTIALIAS_ON.equals(val); 867 } 868 869 /** 870 * Sets a flag that indicates whether or not anti-aliasing is used when the 871 * chart is drawn. 872 * <P> 873 * Anti-aliasing usually improves the appearance of charts, but is slower. 874 * 875 * @param flag the new value of the flag. 876 * 877 * @see #getAntiAlias() 878 */ setAntiAlias(boolean flag)879 public void setAntiAlias(boolean flag) { 880 881 Object val = this.renderingHints.get(RenderingHints.KEY_ANTIALIASING); 882 if (val == null) { 883 val = RenderingHints.VALUE_ANTIALIAS_DEFAULT; 884 } 885 if (!flag && RenderingHints.VALUE_ANTIALIAS_OFF.equals(val) 886 || flag && RenderingHints.VALUE_ANTIALIAS_ON.equals(val)) { 887 // no change, do nothing 888 return; 889 } 890 if (flag) { 891 this.renderingHints.put(RenderingHints.KEY_ANTIALIASING, 892 RenderingHints.VALUE_ANTIALIAS_ON); 893 } 894 else { 895 this.renderingHints.put(RenderingHints.KEY_ANTIALIASING, 896 RenderingHints.VALUE_ANTIALIAS_OFF); 897 } 898 fireChartChanged(); 899 900 } 901 902 /** 903 * Returns the current value stored in the rendering hints table for 904 * {@link RenderingHints#KEY_TEXT_ANTIALIASING}. 905 * 906 * @return The hint value (possibly <code>null</code>). 907 * 908 * @since 1.0.5 909 * 910 * @see #setTextAntiAlias(Object) 911 */ getTextAntiAlias()912 public Object getTextAntiAlias() { 913 return this.renderingHints.get(RenderingHints.KEY_TEXT_ANTIALIASING); 914 } 915 916 /** 917 * Sets the value in the rendering hints table for 918 * {@link RenderingHints#KEY_TEXT_ANTIALIASING} to either 919 * {@link RenderingHints#VALUE_TEXT_ANTIALIAS_ON} or 920 * {@link RenderingHints#VALUE_TEXT_ANTIALIAS_OFF}, then sends a 921 * {@link ChartChangeEvent} to all registered listeners. 922 * 923 * @param flag the new value of the flag. 924 * 925 * @since 1.0.5 926 * 927 * @see #getTextAntiAlias() 928 * @see #setTextAntiAlias(Object) 929 */ setTextAntiAlias(boolean flag)930 public void setTextAntiAlias(boolean flag) { 931 if (flag) { 932 setTextAntiAlias(RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 933 } 934 else { 935 setTextAntiAlias(RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); 936 } 937 } 938 939 /** 940 * Sets the value in the rendering hints table for 941 * {@link RenderingHints#KEY_TEXT_ANTIALIASING} and sends a 942 * {@link ChartChangeEvent} to all registered listeners. 943 * 944 * @param val the new value (<code>null</code> permitted). 945 * 946 * @since 1.0.5 947 * 948 * @see #getTextAntiAlias() 949 * @see #setTextAntiAlias(boolean) 950 */ setTextAntiAlias(Object val)951 public void setTextAntiAlias(Object val) { 952 this.renderingHints.put(RenderingHints.KEY_TEXT_ANTIALIASING, val); 953 notifyListeners(new ChartChangeEvent(this)); 954 } 955 956 /** 957 * Returns the paint used for the chart background. 958 * 959 * @return The paint (possibly <code>null</code>). 960 * 961 * @see #setBackgroundPaint(Paint) 962 */ getBackgroundPaint()963 public Paint getBackgroundPaint() { 964 return this.backgroundPaint; 965 } 966 967 /** 968 * Sets the paint used to fill the chart background and sends a 969 * {@link ChartChangeEvent} to all registered listeners. 970 * 971 * @param paint the paint (<code>null</code> permitted). 972 * 973 * @see #getBackgroundPaint() 974 */ setBackgroundPaint(Paint paint)975 public void setBackgroundPaint(Paint paint) { 976 977 if (this.backgroundPaint != null) { 978 if (!this.backgroundPaint.equals(paint)) { 979 this.backgroundPaint = paint; 980 fireChartChanged(); 981 } 982 } 983 else { 984 if (paint != null) { 985 this.backgroundPaint = paint; 986 fireChartChanged(); 987 } 988 } 989 990 } 991 992 /** 993 * Returns the background image for the chart, or <code>null</code> if 994 * there is no image. 995 * 996 * @return The image (possibly <code>null</code>). 997 * 998 * @see #setBackgroundImage(Image) 999 */ getBackgroundImage()1000 public Image getBackgroundImage() { 1001 return this.backgroundImage; 1002 } 1003 1004 /** 1005 * Sets the background image for the chart and sends a 1006 * {@link ChartChangeEvent} to all registered listeners. 1007 * 1008 * @param image the image (<code>null</code> permitted). 1009 * 1010 * @see #getBackgroundImage() 1011 */ setBackgroundImage(Image image)1012 public void setBackgroundImage(Image image) { 1013 1014 if (this.backgroundImage != null) { 1015 if (!this.backgroundImage.equals(image)) { 1016 this.backgroundImage = image; 1017 fireChartChanged(); 1018 } 1019 } 1020 else { 1021 if (image != null) { 1022 this.backgroundImage = image; 1023 fireChartChanged(); 1024 } 1025 } 1026 1027 } 1028 1029 /** 1030 * Returns the background image alignment. Alignment constants are defined 1031 * in the <code>org.jfree.ui.Align</code> class in the JCommon class 1032 * library. 1033 * 1034 * @return The alignment. 1035 * 1036 * @see #setBackgroundImageAlignment(int) 1037 */ getBackgroundImageAlignment()1038 public int getBackgroundImageAlignment() { 1039 return this.backgroundImageAlignment; 1040 } 1041 1042 /** 1043 * Sets the background alignment. Alignment options are defined by the 1044 * {@link org.jfree.ui.Align} class. 1045 * 1046 * @param alignment the alignment. 1047 * 1048 * @see #getBackgroundImageAlignment() 1049 */ setBackgroundImageAlignment(int alignment)1050 public void setBackgroundImageAlignment(int alignment) { 1051 if (this.backgroundImageAlignment != alignment) { 1052 this.backgroundImageAlignment = alignment; 1053 fireChartChanged(); 1054 } 1055 } 1056 1057 /** 1058 * Returns the alpha-transparency for the chart's background image. 1059 * 1060 * @return The alpha-transparency. 1061 * 1062 * @see #setBackgroundImageAlpha(float) 1063 */ getBackgroundImageAlpha()1064 public float getBackgroundImageAlpha() { 1065 return this.backgroundImageAlpha; 1066 } 1067 1068 /** 1069 * Sets the alpha-transparency for the chart's background image. 1070 * Registered listeners are notified that the chart has been changed. 1071 * 1072 * @param alpha the alpha value. 1073 * 1074 * @see #getBackgroundImageAlpha() 1075 */ setBackgroundImageAlpha(float alpha)1076 public void setBackgroundImageAlpha(float alpha) { 1077 1078 if (this.backgroundImageAlpha != alpha) { 1079 this.backgroundImageAlpha = alpha; 1080 fireChartChanged(); 1081 } 1082 1083 } 1084 1085 /** 1086 * Returns a flag that controls whether or not change events are sent to 1087 * registered listeners. 1088 * 1089 * @return A boolean. 1090 * 1091 * @see #setNotify(boolean) 1092 */ isNotify()1093 public boolean isNotify() { 1094 return this.notify; 1095 } 1096 1097 /** 1098 * Sets a flag that controls whether or not listeners receive 1099 * {@link ChartChangeEvent} notifications. 1100 * 1101 * @param notify a boolean. 1102 * 1103 * @see #isNotify() 1104 */ setNotify(boolean notify)1105 public void setNotify(boolean notify) { 1106 this.notify = notify; 1107 // if the flag is being set to true, there may be queued up changes... 1108 if (notify) { 1109 notifyListeners(new ChartChangeEvent(this)); 1110 } 1111 } 1112 1113 /** 1114 * Draws the chart on a Java 2D graphics device (such as the screen or a 1115 * printer). 1116 * <P> 1117 * This method is the focus of the entire JFreeChart library. 1118 * 1119 * @param g2 the graphics device. 1120 * @param area the area within which the chart should be drawn. 1121 */ 1122 @Override draw(Graphics2D g2, Rectangle2D area)1123 public void draw(Graphics2D g2, Rectangle2D area) { 1124 draw(g2, area, null, null); 1125 } 1126 1127 /** 1128 * Draws the chart on a Java 2D graphics device (such as the screen or a 1129 * printer). This method is the focus of the entire JFreeChart library. 1130 * 1131 * @param g2 the graphics device. 1132 * @param area the area within which the chart should be drawn. 1133 * @param info records info about the drawing (null means collect no info). 1134 */ draw(Graphics2D g2, Rectangle2D area, ChartRenderingInfo info)1135 public void draw(Graphics2D g2, Rectangle2D area, ChartRenderingInfo info) { 1136 draw(g2, area, null, info); 1137 } 1138 1139 /** 1140 * Draws the chart on a Java 2D graphics device (such as the screen or a 1141 * printer). 1142 * <P> 1143 * This method is the focus of the entire JFreeChart library. 1144 * 1145 * @param g2 the graphics device. 1146 * @param chartArea the area within which the chart should be drawn. 1147 * @param anchor the anchor point (in Java2D space) for the chart 1148 * (<code>null</code> permitted). 1149 * @param info records info about the drawing (null means collect no info). 1150 */ draw(Graphics2D g2, Rectangle2D chartArea, Point2D anchor, ChartRenderingInfo info)1151 public void draw(Graphics2D g2, 1152 Rectangle2D chartArea, Point2D anchor, 1153 ChartRenderingInfo info) { 1154 1155 notifyListeners(new ChartProgressEvent(this, this, 1156 ChartProgressEvent.DRAWING_STARTED, 0)); 1157 1158 EntityCollection entities = null; 1159 // record the chart area, if info is requested... 1160 if (info != null) { 1161 info.clear(); 1162 info.setChartArea(chartArea); 1163 entities = info.getEntityCollection(); 1164 } 1165 if (entities != null) { 1166 entities.add(new JFreeChartEntity((Rectangle2D) chartArea.clone(), 1167 this)); 1168 } 1169 1170 // ensure no drawing occurs outside chart area... 1171 Shape savedClip = g2.getClip(); 1172 g2.clip(chartArea); 1173 1174 g2.addRenderingHints(this.renderingHints); 1175 1176 // draw the chart background... 1177 if (this.backgroundPaint != null) { 1178 g2.setPaint(this.backgroundPaint); 1179 g2.fill(chartArea); 1180 } 1181 1182 if (this.backgroundImage != null) { 1183 Composite originalComposite = g2.getComposite(); 1184 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1185 this.backgroundImageAlpha)); 1186 Rectangle2D dest = new Rectangle2D.Double(0.0, 0.0, 1187 this.backgroundImage.getWidth(null), 1188 this.backgroundImage.getHeight(null)); 1189 Align.align(dest, chartArea, this.backgroundImageAlignment); 1190 g2.drawImage(this.backgroundImage, (int) dest.getX(), 1191 (int) dest.getY(), (int) dest.getWidth(), 1192 (int) dest.getHeight(), null); 1193 g2.setComposite(originalComposite); 1194 } 1195 1196 if (isBorderVisible()) { 1197 Paint paint = getBorderPaint(); 1198 Stroke stroke = getBorderStroke(); 1199 if (paint != null && stroke != null) { 1200 Rectangle2D borderArea = new Rectangle2D.Double( 1201 chartArea.getX(), chartArea.getY(), 1202 chartArea.getWidth() - 1.0, chartArea.getHeight() 1203 - 1.0); 1204 g2.setPaint(paint); 1205 g2.setStroke(stroke); 1206 g2.draw(borderArea); 1207 } 1208 } 1209 1210 // draw the title and subtitles... 1211 Rectangle2D nonTitleArea = new Rectangle2D.Double(); 1212 nonTitleArea.setRect(chartArea); 1213 this.padding.trim(nonTitleArea); 1214 1215 if (this.title != null && this.title.isVisible()) { 1216 EntityCollection e = drawTitle(this.title, g2, nonTitleArea, 1217 (entities != null)); 1218 if (e != null && entities != null) { 1219 entities.addAll(e); 1220 } 1221 } 1222 1223 Iterator iterator = this.subtitles.iterator(); 1224 while (iterator.hasNext()) { 1225 Title currentTitle = (Title) iterator.next(); 1226 if (currentTitle.isVisible()) { 1227 EntityCollection e = drawTitle(currentTitle, g2, nonTitleArea, 1228 (entities != null)); 1229 if (e != null && entities != null) { 1230 entities.addAll(e); 1231 } 1232 } 1233 } 1234 1235 Rectangle2D plotArea = nonTitleArea; 1236 1237 // draw the plot (axes and data visualisation) 1238 PlotRenderingInfo plotInfo = null; 1239 if (info != null) { 1240 plotInfo = info.getPlotInfo(); 1241 } 1242 this.plot.draw(g2, plotArea, anchor, null, plotInfo); 1243 1244 g2.setClip(savedClip); 1245 1246 notifyListeners(new ChartProgressEvent(this, this, 1247 ChartProgressEvent.DRAWING_FINISHED, 100)); 1248 } 1249 1250 /** 1251 * Creates a rectangle that is aligned to the frame. 1252 * 1253 * @param dimensions the dimensions for the rectangle. 1254 * @param frame the frame to align to. 1255 * @param hAlign the horizontal alignment. 1256 * @param vAlign the vertical alignment. 1257 * 1258 * @return A rectangle. 1259 */ createAlignedRectangle2D(Size2D dimensions, Rectangle2D frame, HorizontalAlignment hAlign, VerticalAlignment vAlign)1260 private Rectangle2D createAlignedRectangle2D(Size2D dimensions, 1261 Rectangle2D frame, HorizontalAlignment hAlign, 1262 VerticalAlignment vAlign) { 1263 double x = Double.NaN; 1264 double y = Double.NaN; 1265 if (hAlign == HorizontalAlignment.LEFT) { 1266 x = frame.getX(); 1267 } 1268 else if (hAlign == HorizontalAlignment.CENTER) { 1269 x = frame.getCenterX() - (dimensions.width / 2.0); 1270 } 1271 else if (hAlign == HorizontalAlignment.RIGHT) { 1272 x = frame.getMaxX() - dimensions.width; 1273 } 1274 if (vAlign == VerticalAlignment.TOP) { 1275 y = frame.getY(); 1276 } 1277 else if (vAlign == VerticalAlignment.CENTER) { 1278 y = frame.getCenterY() - (dimensions.height / 2.0); 1279 } 1280 else if (vAlign == VerticalAlignment.BOTTOM) { 1281 y = frame.getMaxY() - dimensions.height; 1282 } 1283 1284 return new Rectangle2D.Double(x, y, dimensions.width, 1285 dimensions.height); 1286 } 1287 1288 /** 1289 * Draws a title. The title should be drawn at the top, bottom, left or 1290 * right of the specified area, and the area should be updated to reflect 1291 * the amount of space used by the title. 1292 * 1293 * @param t the title (<code>null</code> not permitted). 1294 * @param g2 the graphics device (<code>null</code> not permitted). 1295 * @param area the chart area, excluding any existing titles 1296 * (<code>null</code> not permitted). 1297 * @param entities a flag that controls whether or not an entity 1298 * collection is returned for the title. 1299 * 1300 * @return An entity collection for the title (possibly <code>null</code>). 1301 */ drawTitle(Title t, Graphics2D g2, Rectangle2D area, boolean entities)1302 protected EntityCollection drawTitle(Title t, Graphics2D g2, 1303 Rectangle2D area, boolean entities) { 1304 1305 ParamChecks.nullNotPermitted(t, "t"); 1306 ParamChecks.nullNotPermitted(area, "area"); 1307 Rectangle2D titleArea; 1308 RectangleEdge position = t.getPosition(); 1309 double ww = area.getWidth(); 1310 if (ww <= 0.0) { 1311 return null; 1312 } 1313 double hh = area.getHeight(); 1314 if (hh <= 0.0) { 1315 return null; 1316 } 1317 RectangleConstraint constraint = new RectangleConstraint(ww, 1318 new Range(0.0, ww), LengthConstraintType.RANGE, hh, 1319 new Range(0.0, hh), LengthConstraintType.RANGE); 1320 Object retValue = null; 1321 BlockParams p = new BlockParams(); 1322 p.setGenerateEntities(entities); 1323 if (position == RectangleEdge.TOP) { 1324 Size2D size = t.arrange(g2, constraint); 1325 titleArea = createAlignedRectangle2D(size, area, 1326 t.getHorizontalAlignment(), VerticalAlignment.TOP); 1327 retValue = t.draw(g2, titleArea, p); 1328 area.setRect(area.getX(), Math.min(area.getY() + size.height, 1329 area.getMaxY()), area.getWidth(), Math.max(area.getHeight() 1330 - size.height, 0)); 1331 } 1332 else if (position == RectangleEdge.BOTTOM) { 1333 Size2D size = t.arrange(g2, constraint); 1334 titleArea = createAlignedRectangle2D(size, area, 1335 t.getHorizontalAlignment(), VerticalAlignment.BOTTOM); 1336 retValue = t.draw(g2, titleArea, p); 1337 area.setRect(area.getX(), area.getY(), area.getWidth(), 1338 area.getHeight() - size.height); 1339 } 1340 else if (position == RectangleEdge.RIGHT) { 1341 Size2D size = t.arrange(g2, constraint); 1342 titleArea = createAlignedRectangle2D(size, area, 1343 HorizontalAlignment.RIGHT, t.getVerticalAlignment()); 1344 retValue = t.draw(g2, titleArea, p); 1345 area.setRect(area.getX(), area.getY(), area.getWidth() 1346 - size.width, area.getHeight()); 1347 } 1348 1349 else if (position == RectangleEdge.LEFT) { 1350 Size2D size = t.arrange(g2, constraint); 1351 titleArea = createAlignedRectangle2D(size, area, 1352 HorizontalAlignment.LEFT, t.getVerticalAlignment()); 1353 retValue = t.draw(g2, titleArea, p); 1354 area.setRect(area.getX() + size.width, area.getY(), area.getWidth() 1355 - size.width, area.getHeight()); 1356 } 1357 else { 1358 throw new RuntimeException("Unrecognised title position."); 1359 } 1360 EntityCollection result = null; 1361 if (retValue instanceof EntityBlockResult) { 1362 EntityBlockResult ebr = (EntityBlockResult) retValue; 1363 result = ebr.getEntityCollection(); 1364 } 1365 return result; 1366 } 1367 1368 /** 1369 * Creates and returns a buffered image into which the chart has been drawn. 1370 * 1371 * @param width the width. 1372 * @param height the height. 1373 * 1374 * @return A buffered image. 1375 */ createBufferedImage(int width, int height)1376 public BufferedImage createBufferedImage(int width, int height) { 1377 return createBufferedImage(width, height, null); 1378 } 1379 1380 /** 1381 * Creates and returns a buffered image into which the chart has been drawn. 1382 * 1383 * @param width the width. 1384 * @param height the height. 1385 * @param info carries back chart state information (<code>null</code> 1386 * permitted). 1387 * 1388 * @return A buffered image. 1389 */ createBufferedImage(int width, int height, ChartRenderingInfo info)1390 public BufferedImage createBufferedImage(int width, int height, 1391 ChartRenderingInfo info) { 1392 return createBufferedImage(width, height, BufferedImage.TYPE_INT_ARGB, 1393 info); 1394 } 1395 1396 /** 1397 * Creates and returns a buffered image into which the chart has been drawn. 1398 * 1399 * @param width the width. 1400 * @param height the height. 1401 * @param imageType the image type. 1402 * @param info carries back chart state information (<code>null</code> 1403 * permitted). 1404 * 1405 * @return A buffered image. 1406 */ createBufferedImage(int width, int height, int imageType, ChartRenderingInfo info)1407 public BufferedImage createBufferedImage(int width, int height, 1408 int imageType, 1409 ChartRenderingInfo info) { 1410 BufferedImage image = new BufferedImage(width, height, imageType); 1411 Graphics2D g2 = image.createGraphics(); 1412 draw(g2, new Rectangle2D.Double(0, 0, width, height), null, info); 1413 g2.dispose(); 1414 return image; 1415 } 1416 1417 /** 1418 * Creates and returns a buffered image into which the chart has been drawn. 1419 * 1420 * @param imageWidth the image width. 1421 * @param imageHeight the image height. 1422 * @param drawWidth the width for drawing the chart (will be scaled to 1423 * fit image). 1424 * @param drawHeight the height for drawing the chart (will be scaled to 1425 * fit image). 1426 * @param info optional object for collection chart dimension and entity 1427 * information. 1428 * 1429 * @return A buffered image. 1430 */ createBufferedImage(int imageWidth, int imageHeight, double drawWidth, double drawHeight, ChartRenderingInfo info)1431 public BufferedImage createBufferedImage(int imageWidth, 1432 int imageHeight, 1433 double drawWidth, 1434 double drawHeight, 1435 ChartRenderingInfo info) { 1436 1437 BufferedImage image = new BufferedImage(imageWidth, imageHeight, 1438 BufferedImage.TYPE_INT_ARGB); 1439 Graphics2D g2 = image.createGraphics(); 1440 double scaleX = imageWidth / drawWidth; 1441 double scaleY = imageHeight / drawHeight; 1442 AffineTransform st = AffineTransform.getScaleInstance(scaleX, scaleY); 1443 g2.transform(st); 1444 draw(g2, new Rectangle2D.Double(0, 0, drawWidth, drawHeight), null, 1445 info); 1446 g2.dispose(); 1447 return image; 1448 1449 } 1450 1451 /** 1452 * Handles a 'click' on the chart. JFreeChart is not a UI component, so 1453 * some other object (for example, {@link ChartPanel}) needs to capture 1454 * the click event and pass it onto the JFreeChart object. 1455 * If you are not using JFreeChart in a client application, then this 1456 * method is not required. 1457 * 1458 * @param x x-coordinate of the click (in Java2D space). 1459 * @param y y-coordinate of the click (in Java2D space). 1460 * @param info contains chart dimension and entity information 1461 * (<code>null</code> not permitted). 1462 */ handleClick(int x, int y, ChartRenderingInfo info)1463 public void handleClick(int x, int y, ChartRenderingInfo info) { 1464 1465 // pass the click on to the plot... 1466 // rely on the plot to post a plot change event and redraw the chart... 1467 this.plot.handleClick(x, y, info.getPlotInfo()); 1468 1469 } 1470 1471 /** 1472 * Registers an object for notification of changes to the chart. 1473 * 1474 * @param listener the listener (<code>null</code> not permitted). 1475 * 1476 * @see #removeChangeListener(ChartChangeListener) 1477 */ addChangeListener(ChartChangeListener listener)1478 public void addChangeListener(ChartChangeListener listener) { 1479 ParamChecks.nullNotPermitted(listener, "listener"); 1480 this.changeListeners.add(ChartChangeListener.class, listener); 1481 } 1482 1483 /** 1484 * Deregisters an object for notification of changes to the chart. 1485 * 1486 * @param listener the listener (<code>null</code> not permitted) 1487 * 1488 * @see #addChangeListener(ChartChangeListener) 1489 */ removeChangeListener(ChartChangeListener listener)1490 public void removeChangeListener(ChartChangeListener listener) { 1491 ParamChecks.nullNotPermitted(listener, "listener"); 1492 this.changeListeners.remove(ChartChangeListener.class, listener); 1493 } 1494 1495 /** 1496 * Sends a default {@link ChartChangeEvent} to all registered listeners. 1497 * <P> 1498 * This method is for convenience only. 1499 */ fireChartChanged()1500 public void fireChartChanged() { 1501 ChartChangeEvent event = new ChartChangeEvent(this); 1502 notifyListeners(event); 1503 } 1504 1505 /** 1506 * Sends a {@link ChartChangeEvent} to all registered listeners. 1507 * 1508 * @param event information about the event that triggered the 1509 * notification. 1510 */ notifyListeners(ChartChangeEvent event)1511 protected void notifyListeners(ChartChangeEvent event) { 1512 if (this.notify) { 1513 Object[] listeners = this.changeListeners.getListenerList(); 1514 for (int i = listeners.length - 2; i >= 0; i -= 2) { 1515 if (listeners[i] == ChartChangeListener.class) { 1516 ((ChartChangeListener) listeners[i + 1]).chartChanged( 1517 event); 1518 } 1519 } 1520 } 1521 } 1522 1523 /** 1524 * Registers an object for notification of progress events relating to the 1525 * chart. 1526 * 1527 * @param listener the object being registered. 1528 * 1529 * @see #removeProgressListener(ChartProgressListener) 1530 */ addProgressListener(ChartProgressListener listener)1531 public void addProgressListener(ChartProgressListener listener) { 1532 this.progressListeners.add(ChartProgressListener.class, listener); 1533 } 1534 1535 /** 1536 * Deregisters an object for notification of changes to the chart. 1537 * 1538 * @param listener the object being deregistered. 1539 * 1540 * @see #addProgressListener(ChartProgressListener) 1541 */ removeProgressListener(ChartProgressListener listener)1542 public void removeProgressListener(ChartProgressListener listener) { 1543 this.progressListeners.remove(ChartProgressListener.class, listener); 1544 } 1545 1546 /** 1547 * Sends a {@link ChartProgressEvent} to all registered listeners. 1548 * 1549 * @param event information about the event that triggered the 1550 * notification. 1551 */ notifyListeners(ChartProgressEvent event)1552 protected void notifyListeners(ChartProgressEvent event) { 1553 1554 Object[] listeners = this.progressListeners.getListenerList(); 1555 for (int i = listeners.length - 2; i >= 0; i -= 2) { 1556 if (listeners[i] == ChartProgressListener.class) { 1557 ((ChartProgressListener) listeners[i + 1]).chartProgress(event); 1558 } 1559 } 1560 1561 } 1562 1563 /** 1564 * Receives notification that a chart title has changed, and passes this 1565 * on to registered listeners. 1566 * 1567 * @param event information about the chart title change. 1568 */ 1569 @Override titleChanged(TitleChangeEvent event)1570 public void titleChanged(TitleChangeEvent event) { 1571 event.setChart(this); 1572 notifyListeners(event); 1573 } 1574 1575 /** 1576 * Receives notification that the plot has changed, and passes this on to 1577 * registered listeners. 1578 * 1579 * @param event information about the plot change. 1580 */ 1581 @Override plotChanged(PlotChangeEvent event)1582 public void plotChanged(PlotChangeEvent event) { 1583 event.setChart(this); 1584 notifyListeners(event); 1585 } 1586 1587 /** 1588 * Tests this chart for equality with another object. 1589 * 1590 * @param obj the object (<code>null</code> permitted). 1591 * 1592 * @return A boolean. 1593 */ 1594 @Override equals(Object obj)1595 public boolean equals(Object obj) { 1596 if (obj == this) { 1597 return true; 1598 } 1599 if (!(obj instanceof JFreeChart)) { 1600 return false; 1601 } 1602 JFreeChart that = (JFreeChart) obj; 1603 if (!this.renderingHints.equals(that.renderingHints)) { 1604 return false; 1605 } 1606 if (this.borderVisible != that.borderVisible) { 1607 return false; 1608 } 1609 if (!ObjectUtilities.equal(this.borderStroke, that.borderStroke)) { 1610 return false; 1611 } 1612 if (!PaintUtilities.equal(this.borderPaint, that.borderPaint)) { 1613 return false; 1614 } 1615 if (!this.padding.equals(that.padding)) { 1616 return false; 1617 } 1618 if (!ObjectUtilities.equal(this.title, that.title)) { 1619 return false; 1620 } 1621 if (!ObjectUtilities.equal(this.subtitles, that.subtitles)) { 1622 return false; 1623 } 1624 if (!ObjectUtilities.equal(this.plot, that.plot)) { 1625 return false; 1626 } 1627 if (!PaintUtilities.equal( 1628 this.backgroundPaint, that.backgroundPaint 1629 )) { 1630 return false; 1631 } 1632 if (!ObjectUtilities.equal(this.backgroundImage, 1633 that.backgroundImage)) { 1634 return false; 1635 } 1636 if (this.backgroundImageAlignment != that.backgroundImageAlignment) { 1637 return false; 1638 } 1639 if (this.backgroundImageAlpha != that.backgroundImageAlpha) { 1640 return false; 1641 } 1642 if (this.notify != that.notify) { 1643 return false; 1644 } 1645 return true; 1646 } 1647 1648 /** 1649 * Provides serialization support. 1650 * 1651 * @param stream the output stream. 1652 * 1653 * @throws IOException if there is an I/O error. 1654 */ writeObject(ObjectOutputStream stream)1655 private void writeObject(ObjectOutputStream stream) throws IOException { 1656 stream.defaultWriteObject(); 1657 SerialUtilities.writeStroke(this.borderStroke, stream); 1658 SerialUtilities.writePaint(this.borderPaint, stream); 1659 SerialUtilities.writePaint(this.backgroundPaint, stream); 1660 } 1661 1662 /** 1663 * Provides serialization support. 1664 * 1665 * @param stream the input stream. 1666 * 1667 * @throws IOException if there is an I/O error. 1668 * @throws ClassNotFoundException if there is a classpath problem. 1669 */ readObject(ObjectInputStream stream)1670 private void readObject(ObjectInputStream stream) 1671 throws IOException, ClassNotFoundException { 1672 stream.defaultReadObject(); 1673 this.borderStroke = SerialUtilities.readStroke(stream); 1674 this.borderPaint = SerialUtilities.readPaint(stream); 1675 this.backgroundPaint = SerialUtilities.readPaint(stream); 1676 this.progressListeners = new EventListenerList(); 1677 this.changeListeners = new EventListenerList(); 1678 this.renderingHints = new RenderingHints( 1679 RenderingHints.KEY_ANTIALIASING, 1680 RenderingHints.VALUE_ANTIALIAS_ON); 1681 1682 // register as a listener with sub-components... 1683 if (this.title != null) { 1684 this.title.addChangeListener(this); 1685 } 1686 1687 for (int i = 0; i < getSubtitleCount(); i++) { 1688 getSubtitle(i).addChangeListener(this); 1689 } 1690 this.plot.addChangeListener(this); 1691 } 1692 1693 /** 1694 * Prints information about JFreeChart to standard output. 1695 * 1696 * @param args no arguments are honored. 1697 */ main(String[] args)1698 public static void main(String[] args) { 1699 System.out.println(JFreeChart.INFO.toString()); 1700 } 1701 1702 /** 1703 * Clones the object, and takes care of listeners. 1704 * Note: caller shall register its own listeners on cloned graph. 1705 * 1706 * @return A clone. 1707 * 1708 * @throws CloneNotSupportedException if the chart is not cloneable. 1709 */ 1710 @Override clone()1711 public Object clone() throws CloneNotSupportedException { 1712 JFreeChart chart = (JFreeChart) super.clone(); 1713 1714 chart.renderingHints = (RenderingHints) this.renderingHints.clone(); 1715 // private boolean borderVisible; 1716 // private transient Stroke borderStroke; 1717 // private transient Paint borderPaint; 1718 1719 if (this.title != null) { 1720 chart.title = (TextTitle) this.title.clone(); 1721 chart.title.addChangeListener(chart); 1722 } 1723 1724 chart.subtitles = new ArrayList(); 1725 for (int i = 0; i < getSubtitleCount(); i++) { 1726 Title subtitle = (Title) getSubtitle(i).clone(); 1727 chart.subtitles.add(subtitle); 1728 subtitle.addChangeListener(chart); 1729 } 1730 1731 if (this.plot != null) { 1732 chart.plot = (Plot) this.plot.clone(); 1733 chart.plot.addChangeListener(chart); 1734 } 1735 1736 chart.progressListeners = new EventListenerList(); 1737 chart.changeListeners = new EventListenerList(); 1738 return chart; 1739 } 1740 1741 } 1742 1743 /** 1744 * Information about the JFreeChart project. One instance of this class is 1745 * assigned to <code>JFreeChart.INFO</code>. 1746 */ 1747 class JFreeChartInfo extends ProjectInfo { 1748 1749 /** 1750 * Default constructor. 1751 */ JFreeChartInfo()1752 public JFreeChartInfo() { 1753 1754 // get a locale-specific resource bundle... 1755 String baseResourceClass 1756 = "org.jfree.chart.resources.JFreeChartResources"; 1757 ResourceBundle resources = ResourceBundleWrapper.getBundle( 1758 baseResourceClass); 1759 1760 setName(resources.getString("project.name")); 1761 setVersion(resources.getString("project.version")); 1762 setInfo(resources.getString("project.info")); 1763 setCopyright(resources.getString("project.copyright")); 1764 setLogo(null); // load only when required 1765 setLicenceName("LGPL"); 1766 setLicenceText(Licences.getInstance().getLGPL()); 1767 1768 setContributors(Arrays.asList( 1769 new Contributor[]{ 1770 new Contributor("Eric Alexander", "-"), 1771 new Contributor("Richard Atkinson", 1772 "richard_c_atkinson@ntlworld.com"), 1773 new Contributor("David Basten", "-"), 1774 new Contributor("David Berry", "-"), 1775 new Contributor("Chris Boek", "-"), 1776 new Contributor("Zoheb Borbora", "-"), 1777 new Contributor("Anthony Boulestreau", "-"), 1778 new Contributor("Jeremy Bowman", "-"), 1779 new Contributor("Nicolas Brodu", "-"), 1780 new Contributor("Jody Brownell", "-"), 1781 new Contributor("David Browning", "-"), 1782 new Contributor("Soren Caspersen", "-"), 1783 new Contributor("Chuanhao Chiu", "-"), 1784 new Contributor("Brian Cole", "-"), 1785 new Contributor("Pascal Collet", "-"), 1786 new Contributor("Martin Cordova", "-"), 1787 new Contributor("Paolo Cova", "-"), 1788 new Contributor("Greg Darke", "-"), 1789 new Contributor("Mike Duffy", "-"), 1790 new Contributor("Don Elliott", "-"), 1791 new Contributor("David Forslund", "-"), 1792 new Contributor("Jonathan Gabbai", "-"), 1793 new Contributor("David Gilbert", 1794 "david.gilbert@object-refinery.com"), 1795 new Contributor("Serge V. Grachov", "-"), 1796 new Contributor("Daniel Gredler", "-"), 1797 new Contributor("Hans-Jurgen Greiner", "-"), 1798 new Contributor("Joao Guilherme Del Valle", "-"), 1799 new Contributor("Aiman Han", "-"), 1800 new Contributor("Cameron Hayne", "-"), 1801 new Contributor("Martin Hoeller", "-"), 1802 new Contributor("Jon Iles", "-"), 1803 new Contributor("Wolfgang Irler", "-"), 1804 new Contributor("Sergei Ivanov", "-"), 1805 new Contributor("Adriaan Joubert", "-"), 1806 new Contributor("Darren Jung", "-"), 1807 new Contributor("Xun Kang", "-"), 1808 new Contributor("Bill Kelemen", "-"), 1809 new Contributor("Norbert Kiesel", "-"), 1810 new Contributor("Peter Kolb", "-"), 1811 new Contributor("Gideon Krause", "-"), 1812 new Contributor("Pierre-Marie Le Biot", "-"), 1813 new Contributor("Arnaud Lelievre", "-"), 1814 new Contributor("Wolfgang Lenhard", "-"), 1815 new Contributor("David Li", "-"), 1816 new Contributor("Yan Liu", "-"), 1817 new Contributor("Tin Luu", "-"), 1818 new Contributor("Craig MacFarlane", "-"), 1819 new Contributor("Achilleus Mantzios", "-"), 1820 new Contributor("Thomas Meier", "-"), 1821 new Contributor("Jim Moore", "-"), 1822 new Contributor("Jonathan Nash", "-"), 1823 new Contributor("Barak Naveh", "-"), 1824 new Contributor("David M. O'Donnell", "-"), 1825 new Contributor("Krzysztof Paz", "-"), 1826 new Contributor("Eric Penfold", "-"), 1827 new Contributor("Tomer Peretz", "-"), 1828 new Contributor("Diego Pierangeli", "-"), 1829 new Contributor("Xavier Poinsard", "-"), 1830 new Contributor("Andrzej Porebski", "-"), 1831 new Contributor("Viktor Rajewski", "-"), 1832 new Contributor("Eduardo Ramalho", "-"), 1833 new Contributor("Michael Rauch", "-"), 1834 new Contributor("Cameron Riley", "-"), 1835 new Contributor("Klaus Rheinwald", "-"), 1836 new Contributor("Dan Rivett", "d.rivett@ukonline.co.uk"), 1837 new Contributor("Scott Sams", "-"), 1838 new Contributor("Michel Santos", "-"), 1839 new Contributor("Thierry Saura", "-"), 1840 new Contributor("Andreas Schneider", "-"), 1841 new Contributor("Jean-Luc SCHWAB", "-"), 1842 new Contributor("Bryan Scott", "-"), 1843 new Contributor("Tobias Selb", "-"), 1844 new Contributor("Darshan Shah", "-"), 1845 new Contributor("Mofeed Shahin", "-"), 1846 new Contributor("Michael Siemer", "-"), 1847 new Contributor("Pady Srinivasan", "-"), 1848 new Contributor("Greg Steckman", "-"), 1849 new Contributor("Gerald Struck", "-"), 1850 new Contributor("Roger Studner", "-"), 1851 new Contributor("Irv Thomae", "-"), 1852 new Contributor("Eric Thomas", "-"), 1853 new Contributor("Rich Unger", "-"), 1854 new Contributor("Daniel van Enckevort", "-"), 1855 new Contributor("Laurence Vanhelsuwe", "-"), 1856 new Contributor("Sylvain Vieujot", "-"), 1857 new Contributor("Ulrich Voigt", "-"), 1858 new Contributor("Jelai Wang", "-"), 1859 new Contributor("Mark Watson", "www.markwatson.com"), 1860 new Contributor("Alex Weber", "-"), 1861 new Contributor("Matthew Wright", "-"), 1862 new Contributor("Benoit Xhenseval", "-"), 1863 new Contributor("Christian W. Zuckschwerdt", 1864 "Christian.Zuckschwerdt@Informatik.Uni-Oldenburg.de"), 1865 new Contributor("Hari", "-"), 1866 new Contributor("Sam (oldman)", "-"), 1867 new Contributor("Patrick Schlott", "-"), 1868 new Contributor("Christoph Schroeder", "-") 1869 } 1870 )); 1871 1872 addLibrary(JCommon.INFO); 1873 1874 } 1875 1876 /** 1877 * Returns the JFreeChart logo (a picture of a gorilla). 1878 * 1879 * @return The JFreeChart logo. 1880 */ 1881 @Override getLogo()1882 public Image getLogo() { 1883 Image logo = super.getLogo(); 1884 if (logo == null) { 1885 URL imageURL = this.getClass().getClassLoader().getResource( 1886 "org/jfree/chart/gorilla.jpg"); 1887 if (imageURL != null) { 1888 ImageIcon temp = new ImageIcon(imageURL); 1889 // use ImageIcon because it waits for the image to load... 1890 logo = temp.getImage(); 1891 setLogo(logo); 1892 } 1893 } 1894 return logo; 1895 } 1896 1897 } 1898