001 /* ====================================================== 002 * Orson : a free chart beans library based on JFreeChart 003 * ====================================================== 004 * 005 * (C) Copyright 2007, by Object Refinery Limited. 006 * 007 * Project Info: not-yet-released 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 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 025 * in the United States and other countries.] 026 * 027 */ 028 029 package org.jfree.beans; 030 031 import java.awt.Color; 032 import java.awt.Dimension; 033 import java.awt.Font; 034 import java.awt.Graphics; 035 import java.awt.Graphics2D; 036 import java.awt.GraphicsConfiguration; 037 import java.awt.Image; 038 import java.awt.Insets; 039 import java.awt.Paint; 040 import java.awt.Point; 041 import java.awt.Stroke; 042 import java.awt.Transparency; 043 import java.awt.event.ActionEvent; 044 import java.awt.event.ActionListener; 045 import java.awt.event.MouseEvent; 046 import java.awt.event.MouseListener; 047 import java.awt.event.MouseMotionListener; 048 import java.awt.geom.AffineTransform; 049 import java.awt.geom.Point2D; 050 import java.awt.geom.Rectangle2D; 051 import java.beans.PropertyChangeEvent; 052 import java.beans.PropertyEditorManager; 053 import java.io.File; 054 import java.io.IOException; 055 056 import javax.swing.JComponent; 057 import javax.swing.JFileChooser; 058 import javax.swing.JMenuItem; 059 import javax.swing.JPopupMenu; 060 import javax.swing.ToolTipManager; 061 import javax.swing.event.EventListenerList; 062 063 import org.jfree.beans.editors.AxisLocationEditor; 064 import org.jfree.beans.editors.LegendPositionEditor; 065 import org.jfree.beans.editors.PaintEditor; 066 import org.jfree.beans.editors.PlotOrientationEditor; 067 import org.jfree.beans.editors.RectangleEdgeEditor; 068 import org.jfree.beans.editors.StrokeEditor; 069 import org.jfree.beans.events.LegendClickEvent; 070 import org.jfree.beans.events.LegendClickListener; 071 import org.jfree.chart.ChartRenderingInfo; 072 import org.jfree.chart.ChartUtilities; 073 import org.jfree.chart.JFreeChart; 074 import org.jfree.chart.axis.AxisLocation; 075 import org.jfree.chart.entity.ChartEntity; 076 import org.jfree.chart.entity.EntityCollection; 077 import org.jfree.chart.entity.LegendItemEntity; 078 import org.jfree.chart.event.ChartChangeEvent; 079 import org.jfree.chart.event.ChartChangeListener; 080 import org.jfree.chart.plot.Plot; 081 import org.jfree.chart.plot.PlotOrientation; 082 import org.jfree.chart.plot.PlotRenderingInfo; 083 import org.jfree.chart.plot.Zoomable; 084 import org.jfree.chart.title.LegendTitle; 085 import org.jfree.chart.title.TextTitle; 086 import org.jfree.ui.ExtensionFileFilter; 087 import org.jfree.ui.HorizontalAlignment; 088 import org.jfree.ui.RectangleEdge; 089 import org.jfree.ui.RectangleInsets; 090 091 /** 092 * A base class for creating chart beans. 093 */ 094 public abstract class AbstractChart extends JComponent 095 implements ChartChangeListener, ActionListener, MouseListener, 096 MouseMotionListener { 097 098 // here we register some custom editors that make the chart beans a little 099 // easier to work with... 100 static { 101 PropertyEditorManager.registerEditor(Paint.class, PaintEditor.class); 102 PropertyEditorManager.registerEditor(Stroke.class, StrokeEditor.class); 103 PropertyEditorManager.registerEditor(PlotOrientation.class, 104 PlotOrientationEditor.class); 105 PropertyEditorManager.registerEditor(RectangleEdge.class, 106 RectangleEdgeEditor.class); 107 PropertyEditorManager.registerEditor(AxisLocation.class, 108 AxisLocationEditor.class); 109 PropertyEditorManager.registerEditor(LegendPosition.class, 110 LegendPositionEditor.class); 111 } 112 113 /** The underlying chart. */ 114 protected JFreeChart chart; 115 116 /** 117 * The chart's legend. We keep a separate reference to this, so that 118 * the legend can be added/removed from the chart. 119 */ 120 protected LegendTitle legend; 121 122 /** 123 * The current legend position (TOP, BOTTOM, LEFT, RIGHT or NONE). 124 */ 125 protected LegendPosition legendPosition; 126 127 /** 128 * A subtitle for the chart. 129 */ 130 protected TextTitle subtitle; 131 132 /** 133 * A subtitle that shows the data source. 134 */ 135 protected TextTitle sourceSubtitle; 136 137 /** 138 * The chart rendering info, which is used for tooltips and mouse 139 * events. 140 */ 141 protected ChartRenderingInfo info; 142 143 /** Storage for registered listeners. */ 144 protected EventListenerList listeners; 145 146 /** A buffer for the rendered chart. */ 147 protected Image chartBuffer; 148 149 /** The height of the chart buffer. */ 150 protected int chartBufferHeight; 151 152 /** The width of the chart buffer. */ 153 protected int chartBufferWidth; 154 155 /** A flag that indicates that the buffer should be refreshed. */ 156 private boolean refreshBuffer; 157 158 /** The scale factor used to draw the chart. */ 159 protected double scaleX; 160 161 /** The scale factor used to draw the chart. */ 162 protected double scaleY; 163 164 /** The chart anchor point. */ 165 private Point2D anchor; 166 167 /** The zoom rectangle (selected by the user with the mouse). */ 168 private transient Rectangle2D zoomRectangle = null; 169 170 /** 171 * The zoom rectangle starting point (selected by the user with a mouse 172 * click). This is a point on the screen, not the chart (which may have 173 * been scaled up or down to fit the panel). 174 */ 175 private Point zoomPoint = null; 176 177 /** The minimum distance required to drag the mouse to trigger a zoom. */ 178 private int zoomTriggerDistance; 179 180 /** Controls if the zoom rectangle is drawn as an outline or filled. */ 181 private boolean fillZoomRectangle = false; 182 183 /** 184 * Default constructor. 185 */ 186 public AbstractChart() { 187 this.info = new ChartRenderingInfo(); 188 this.chart = createDefaultChart(); 189 this.chart.addChangeListener(this); 190 this.chart.getTitle().setFont(new Font("Dialog", Font.BOLD, 14)); 191 this.chart.setBackgroundPaint(Color.white); 192 this.legend = this.chart.getLegend(); 193 this.legend.setItemFont(new Font("Dialog", Font.PLAIN, 10)); 194 this.legendPosition = LegendPosition.BOTTOM; 195 this.subtitle = new TextTitle("Chart Subtitle", new Font("Dialog", 196 Font.ITALIC, 10)); 197 this.chart.addSubtitle(0, this.subtitle); 198 this.sourceSubtitle = new TextTitle("http://www.jfree.org/jfreechart", 199 new Font("Dialog", Font.PLAIN, 8)); 200 this.sourceSubtitle.setPosition(RectangleEdge.BOTTOM); 201 this.sourceSubtitle.setHorizontalAlignment(HorizontalAlignment.RIGHT); 202 this.chart.addSubtitle(0, this.sourceSubtitle); 203 addMouseListener(this); 204 addMouseMotionListener(this); 205 setToolTipsEnabled(true); 206 setPreferredSize(new Dimension(360, 230)); 207 this.listeners = new EventListenerList(); 208 } 209 210 /** 211 * Creates the default chart for initial display to the user. Subclasses 212 * implement this as appropriate for the chart type. 213 * 214 * @return The default chart. 215 */ 216 protected abstract JFreeChart createDefaultChart(); 217 218 /** 219 * Returns the flag that controls whether or not the chart is drawn 220 * with antialiasing. 221 * 222 * @return The antialiasing flag. 223 * 224 * @see #setAntiAlias(boolean) 225 */ 226 public boolean getAntiAlias() { 227 return this.chart.getAntiAlias(); 228 } 229 230 /** 231 * Sets the flag that controls whether or not the chart is drawn with 232 * antialiasing, and fires a {@link PropertyChangeEvent} for the 233 * <code>antiAlias</code> property. 234 * 235 * @param flag the new flag value. 236 * 237 * @see #getAntiAlias() 238 */ 239 public void setAntiAlias(boolean flag) { 240 boolean old = this.chart.getAntiAlias(); 241 this.chart.setAntiAlias(flag); 242 firePropertyChange("antiAlias", old, flag); 243 } 244 245 /** 246 * Returns a flag that controls whether or not the chart border is visible. 247 * In general, it makes more sense to use a Swing border around the 248 * component, but when saving a chart to an image, it is sometimes useful 249 * to display an outline border. 250 * 251 * @return A flag that controls whether or not the chart border is visible. 252 * 253 * @see #setChartBorderVisible(boolean) 254 */ 255 public boolean isChartBorderVisible() { 256 return this.chart.isBorderVisible(); 257 } 258 259 /** 260 * Sets the flag that controls whether or not a border is drawn around 261 * the chart, and fires a {@link PropertyChangeEvent} for the 262 * <code>chartBorderVisible</code> property. 263 * 264 * @param visible the new value for the flag. 265 * 266 * @see #isChartBorderVisible() 267 */ 268 public void setChartBorderVisible(boolean visible) { 269 boolean old = this.chart.isBorderVisible(); 270 this.chart.setBorderVisible(visible); 271 firePropertyChange("chartBorderVisible", old, visible); 272 } 273 274 /** 275 * Returns the stroke used to draw the outline for the chart. 276 * 277 * @return The stroke. 278 * 279 * @see #setChartBorderStroke(Stroke) 280 */ 281 public Stroke getChartBorderStroke() { 282 return this.chart.getBorderStroke(); 283 } 284 285 /** 286 * Sets the stroke used to draw the outline for the chart and 287 * sends a {@link PropertyChangeEvent} to all registered listeners for the 288 * <code>chartBorderPaint</code> property. 289 * 290 * @param stroke the stroke (<code>null</code> not permitted). 291 * 292 * @see #getChartBorderStroke() 293 */ 294 public void setChartBorderStroke(Stroke stroke) { 295 Stroke old = this.chart.getBorderStroke(); 296 this.chart.setBorderStroke(stroke); 297 firePropertyChange("chartBorderStroke", old, stroke); 298 } 299 300 /** 301 * Returns the paint used to draw the chart border, if it is visible. 302 * 303 * @return The paint used to draw the chart border (never 304 * <code>null</code>). 305 * 306 * @see #setChartBorderPaint(Paint) 307 */ 308 public Paint getChartBorderPaint() { 309 return this.chart.getBorderPaint(); 310 } 311 312 /** 313 * Sets the paint used to draw the chart border, if it is visible, and 314 * sends a {@link PropertyChangeEvent} to all registered listeners for the 315 * <code>chartBorderPaint</code> property. 316 * 317 * @param paint the paint (<code>null</code> not permitted). 318 * 319 * @see #getChartBorderPaint() 320 */ 321 public void setChartBorderPaint(Paint paint) { 322 Paint old = getChartBorderPaint(); 323 this.chart.setBorderPaint(paint); 324 firePropertyChange("chartBorderPaint", old, paint); 325 } 326 327 /** 328 * Returns the background paint for the chart. 329 * 330 * @return The background paint for the chart (possibly <code>null</code>). 331 * 332 * @see #setChartBackgroundPaint(Paint) 333 */ 334 public Paint getChartBackgroundPaint() { 335 return this.chart.getBackgroundPaint(); 336 } 337 338 /** 339 * Sets the background paint for the chart and sends a 340 * {@link PropertyChangeEvent} to all registered listeners for the 341 * <code>chartBackgroundPaint</code> property. 342 * 343 * @param paint the paint (<code>null</code> permitted). 344 * 345 * @see #getChartBackgroundPaint() 346 */ 347 public void setChartBackgroundPaint(Paint paint) { 348 Paint old = this.chart.getBackgroundPaint(); 349 this.chart.setBackgroundPaint(paint); 350 firePropertyChange("chartBackgroundPaint", old, paint); 351 } 352 353 // FIXME: the chartBackgroundImage isn't yet covered in the BeanInfo 354 355 /** 356 * Returns the background image for the chart. 357 * 358 * @return The image (possibly <code>null</code>). 359 * 360 * @see #setChartBackgroundImage(Image) 361 */ 362 public Image getChartBackgroundImage() { 363 return this.chart.getBackgroundImage(); 364 } 365 366 /** 367 * Sets the background image for the chart and sends a 368 * {@link PropertyChangeEvent} to all registered listeners for the 369 * <code>chartBackgroundImage</code> property. 370 * 371 * @param image the image (<code>null</code> permitted). 372 * 373 * @see #getChartBackgroundImage() 374 */ 375 public void setChartBackgroundImage(Image image) { 376 Image old = this.chart.getBackgroundImage(); 377 this.chart.setBackgroundImage(image); 378 firePropertyChange("chartBackgroundImage", old, image); 379 } 380 381 // FIXME: chartBackgroundImageAlpha is not yet covered in BeanInfo 382 383 /** 384 * Returns the alpha-transparency for the background image. 385 * 386 * @return The alpha value. 387 * 388 * @see #setChartBackgroundImageAlpha(float) 389 */ 390 public float getChartBackgroundImageAlpha() { 391 return this.chart.getBackgroundImageAlpha(); 392 } 393 394 /** 395 * Sets the alpha transparency for the background image. 396 * 397 * @param alpha the new value. 398 * 399 * @see #getChartBackgroundImageAlpha() 400 */ 401 public void setChartBackgroundImageAlpha(float alpha) { 402 float old = this.chart.getBackgroundImageAlpha(); 403 this.chart.setBackgroundImageAlpha(alpha); 404 firePropertyChange("chartBackgroundImageAlpha", old, alpha); 405 } 406 407 // FIXME: the chartPadding is not yet covered in the BeanInfo. 408 409 /** 410 * Returns the chart padding. 411 * 412 * @return The chart padding. 413 */ 414 public RectangleInsets getChartPadding() { 415 return this.chart.getPadding(); 416 } 417 418 /** 419 * Sets the chart padding. 420 * 421 * @param padding the padding. 422 */ 423 public void setChartPadding(RectangleInsets padding) { 424 this.chart.setPadding(padding); 425 } 426 427 /** 428 * Returns the text for the chart title. 429 * 430 * @return The text for the chart title. 431 * 432 * @see #setTitle(String) 433 */ 434 public String getTitle() { 435 String result = null; 436 TextTitle title = this.chart.getTitle(); 437 if (title != null) { 438 result = title.getText(); 439 } 440 return result; 441 } 442 443 /** 444 * Sets the text for the chart title and sends a 445 * {@link PropertyChangeEvent} to all registered listeners for the 446 * <code>title</code> property. 447 * 448 * @param title the title (<code>null</code> not permitted). 449 * 450 * @see #getTitle() 451 */ 452 public void setTitle(String title) { 453 if (title == null) { 454 throw new IllegalArgumentException("Null 'title' argument."); 455 } 456 TextTitle t = this.chart.getTitle(); 457 if (t != null) { 458 String old = getTitle(); 459 t.setText(title); 460 firePropertyChange("title", old, title); 461 } 462 } 463 464 /** 465 * Returns the font for the chart title. 466 * 467 * @return The font for the chart title. 468 * 469 * @see #setTitleFont(Font) 470 */ 471 public Font getTitleFont() { 472 Font result= null; 473 TextTitle title = this.chart.getTitle(); 474 if (title != null) { 475 result = title.getFont(); 476 } 477 return result; 478 } 479 480 /** 481 * Sets the font for the chart title and sends a 482 * {@link PropertyChangeEvent} to all registered listeners for the 483 * <code>titleFont</code> property. 484 * 485 * @param font the font. 486 * 487 * @see #getTitleFont() 488 */ 489 public void setTitleFont(Font font) { 490 TextTitle t = this.chart.getTitle(); 491 if (t != null) { 492 Font old = t.getFont(); 493 t.setFont(font); 494 firePropertyChange("titleFont", old, font); 495 } 496 } 497 498 /** 499 * Returns the paint used to draw the chart title. 500 * 501 * @return The paint used to draw the chart title. 502 * 503 * @see #getTitlePaint() 504 */ 505 public Paint getTitlePaint() { 506 Paint result = null; 507 TextTitle title = this.chart.getTitle(); 508 if (title != null) { 509 result = title.getPaint(); 510 } 511 return result; 512 } 513 514 /** 515 * Sets the paint for the chart title and sends a 516 * {@link PropertyChangeEvent} to all registered listeners for the 517 * <code>titlePaint</code> property. 518 * 519 * @param paint the paint. 520 * 521 * @see #getTitlePaint() 522 */ 523 public void setTitlePaint(Paint paint) { 524 TextTitle t = this.chart.getTitle(); 525 if (t != null) { 526 Paint old = t.getPaint(); 527 t.setPaint(paint); 528 firePropertyChange("titlePaint", old, paint); 529 } 530 } 531 532 /** 533 * Returns the text for the chart's subtitle. 534 * 535 * @return The text for the chart's subtitle. 536 * 537 * @see #setSubtitle(String) 538 */ 539 public String getSubtitle() { 540 return this.subtitle.getText(); 541 } 542 543 /** 544 * Sets the text for the chart's subtitle and sends a 545 * {@link PropertyChangeEvent} to all registered listeners for the 546 * <code>subtitle</code> property. 547 * 548 * @param title the title. 549 * 550 * @see #getSubtitle() 551 */ 552 public void setSubtitle(String title) { 553 String old = this.subtitle.getText(); 554 this.subtitle.setText(title); 555 firePropertyChange("subtitle", old, title); 556 } 557 558 /** 559 * Returns the font for the chart's subtitle. 560 * 561 * @return The font for the chart's subtitle. 562 * 563 * @see #setSubtitleFont(Font) 564 */ 565 public Font getSubtitleFont() { 566 return this.subtitle.getFont(); 567 } 568 569 /** 570 * Sets the font for the chart's subtitle and sends a 571 * {@link PropertyChangeEvent} to all registered listeners for the 572 * <code>subtitleFont</code> property. 573 * 574 * @param font the font (<code>null</code> not permitted). 575 * 576 * @see #getSubtitleFont() 577 */ 578 public void setSubtitleFont(Font font) { 579 if (font == null) { 580 throw new IllegalArgumentException("Null 'font' argument."); 581 } 582 Font old = this.subtitle.getFont(); 583 this.subtitle.setFont(font); 584 firePropertyChange("subtitleFont", old, font); 585 } 586 587 /** 588 * Returns the paint used to draw the chart's subtitle. 589 * 590 * @return The paint used to draw the chart's subtitle. 591 * 592 * @see #setSubtitlePaint(Paint) 593 */ 594 public Paint getSubtitlePaint() { 595 return this.subtitle.getPaint(); 596 } 597 598 /** 599 * Sets the paint for the chart's subtitle and sends a 600 * {@link PropertyChangeEvent} to all registered listeners for the 601 * <code>subtitlePaint</code> property. 602 * 603 * @param paint the paint (<code>null</code> not permitted). 604 * 605 * @see #getSubtitlePaint() 606 */ 607 public void setSubtitlePaint(Paint paint) { 608 if (paint == null) { 609 throw new IllegalArgumentException("Null 'paint' argument"); 610 } 611 Paint old = this.subtitle.getPaint(); 612 this.subtitle.setPaint(paint); 613 firePropertyChange("subtitlePaint", old, paint); 614 } 615 616 /** 617 * Returns the text for the chart's source subtitle. 618 * 619 * @return The text for the chart's sourcesubtitle. 620 * 621 * @see #setSource(String) 622 */ 623 public String getSource() { 624 return this.sourceSubtitle.getText(); 625 } 626 627 /** 628 * Sets the text for the chart's source subtitle and sends a 629 * {@link PropertyChangeEvent} to all registered listeners for the 630 * <code>source</code> property. 631 * 632 * @param title the title. 633 * 634 * @see #getSource() 635 */ 636 public void setSource(String title) { 637 String old = this.sourceSubtitle.getText(); 638 this.sourceSubtitle.setText(title); 639 firePropertyChange("source", old, title); 640 } 641 642 /** 643 * Returns the font for the chart's source subtitle. 644 * 645 * @return The font for the chart's source subtitle. 646 * 647 * @see #setSourceFont(Font) 648 */ 649 public Font getSourceFont() { 650 return this.sourceSubtitle.getFont(); 651 } 652 653 /** 654 * Sets the font for the chart's source subtitle and sends a 655 * {@link PropertyChangeEvent} to all registered listeners for the 656 * <code>sourceFont</code> property. 657 * 658 * @param font the font (<code>null</code> not permitted). 659 * 660 * @see #getSourceFont() 661 */ 662 public void setSourceFont(Font font) { 663 if (font == null) { 664 throw new IllegalArgumentException("Null 'font' argument."); 665 } 666 Font old = this.sourceSubtitle.getFont(); 667 this.sourceSubtitle.setFont(font); 668 firePropertyChange("sourceFont", old, font); 669 } 670 671 /** 672 * Returns the paint used to draw the chart's source subtitle. 673 * 674 * @return The paint used to draw the chart's source subtitle. 675 * 676 * @see #setSourcePaint(Paint) 677 */ 678 public Paint getSourcePaint() { 679 return this.sourceSubtitle.getPaint(); 680 } 681 682 /** 683 * Sets the paint for the chart's source subtitle and sends a 684 * {@link PropertyChangeEvent} to all registered listeners for the 685 * <code>sourcePaint</code> property. 686 * 687 * @param paint the paint (<code>null</code> not permitted). 688 * 689 * @see #getSourcePaint() 690 */ 691 public void setSourcePaint(Paint paint) { 692 if (paint == null) { 693 throw new IllegalArgumentException("Null 'paint' argument"); 694 } 695 Paint old = this.sourceSubtitle.getPaint(); 696 this.sourceSubtitle.setPaint(paint); 697 firePropertyChange("sourcePaint", old, paint); 698 } 699 700 /** 701 * Returns the flag that controls whether or not the plot outline is 702 * visible. 703 * 704 * @return The flag that controls whether or not the plot outline is 705 * visible. 706 * 707 * @see #setPlotOutlineVisible(boolean) 708 */ 709 public boolean isPlotOutlineVisible() { 710 Plot plot = this.chart.getPlot(); 711 if (plot != null) { 712 return plot.isOutlineVisible(); 713 } 714 return false; 715 } 716 717 /** 718 * Sets the flag that controls whether or not the plot outline is 719 * visible and sends a {@link PropertyChangeEvent} to all 720 * registered listeners for the <code>plotOutlineVisible</code> property. 721 * 722 * @param visible the new flag value. 723 * 724 * @see #isPlotOutlineVisible() 725 */ 726 public void setPlotOutlineVisible(boolean visible) { 727 Plot plot = this.chart.getPlot(); 728 if (plot != null) { 729 boolean old = plot.isOutlineVisible(); 730 plot.setOutlineVisible(visible); 731 firePropertyChange("plotOutlineVisible", old, visible); 732 } 733 } 734 735 /** 736 * Returns the alpha transparency used when filling the background of the 737 * plot area. 738 * 739 * @return The alpha transparency. 740 * 741 * @see #setPlotBackgroundAlpha(float) 742 */ 743 public float getPlotBackgroundAlpha() { 744 float result = 1.0f; 745 Plot plot = this.chart.getPlot(); 746 if (plot != null) { 747 result = plot.getBackgroundAlpha(); 748 } 749 return result; 750 } 751 752 /** 753 * Sets the alpha transparency used when filling the background of the 754 * plot area and sends a {@link PropertyChangeEvent} to all 755 * registered listeners for the <code>plotBackgroundAlpha</code> property. 756 * 757 * @param alpha the alpha transparency (in the range 0.0 to 1.0). 758 * 759 * @see #getPlotBackgroundAlpha() 760 */ 761 public void setPlotBackgroundAlpha(float alpha) { 762 Plot plot = this.chart.getPlot(); 763 if (plot != null) { 764 float old = plot.getBackgroundAlpha(); 765 plot.setBackgroundAlpha(alpha); 766 firePropertyChange("plotBackgroundAlpha", old, alpha); 767 } 768 } 769 770 /** 771 * Returns the background paint for the plot, or <code>null</code>. 772 * 773 * @return The background paint (possibly <code>null</code>). 774 * 775 * @see #setPlotBackgroundPaint(Paint) 776 */ 777 public Paint getPlotBackgroundPaint() { 778 Paint result = null; 779 Plot plot = this.chart.getPlot(); 780 if (plot != null) { 781 result = plot.getBackgroundPaint(); 782 } 783 return result; 784 } 785 786 /** 787 * Sets the background paint and sends a {@link PropertyChangeEvent} to all 788 * registered listeners for the <code>plotBackgroundPaint</code> property. 789 * 790 * @param paint the paint (<code>null</code> permitted). 791 * 792 * @see #getPlotBackgroundPaint() 793 */ 794 public void setPlotBackgroundPaint(Paint paint) { 795 Plot plot = this.chart.getPlot(); 796 if (plot != null) { 797 Paint old = plot.getBackgroundPaint(); 798 plot.setBackgroundPaint(paint); 799 firePropertyChange("plotBackgroundPaint", old, paint); 800 } 801 } 802 803 /** 804 * Returns the legend position. 805 * 806 * @return The legend position. 807 * 808 * @see #setLegendPosition(LegendPosition) 809 */ 810 public LegendPosition getLegendPosition() { 811 return this.legendPosition; 812 } 813 814 /** 815 * Sets the legend position and sends a {@link PropertyChangeEvent} to all 816 * registered listeners for the <code>legendPosition</code> property. 817 * 818 * @param position the position (<code>null</code> not permitted). 819 * 820 * @see #getLegendPosition() 821 */ 822 public void setLegendPosition(LegendPosition position) { 823 if (position == null) { 824 throw new IllegalArgumentException("Null 'position' argument."); 825 } 826 LegendPosition old = this.legendPosition; 827 if (position.equals(LegendPosition.NONE)) { 828 if (!old.equals(LegendPosition.NONE)) { 829 this.chart.removeSubtitle(this.legend); 830 } 831 } 832 else { 833 if (old.equals(LegendPosition.NONE)) { 834 this.chart.addSubtitle(1, this.legend); 835 } 836 if (position.equals(LegendPosition.TOP)) { 837 this.legend.setPosition(RectangleEdge.TOP); 838 } 839 else if (position.equals(LegendPosition.BOTTOM)) { 840 this.legend.setPosition(RectangleEdge.BOTTOM); 841 } 842 else if (position.equals(LegendPosition.LEFT)) { 843 this.legend.setPosition(RectangleEdge.LEFT); 844 } 845 else if (position.equals(LegendPosition.RIGHT)) { 846 this.legend.setPosition(RectangleEdge.RIGHT); 847 } 848 } 849 this.legendPosition = position; 850 firePropertyChange("legendPosition", old, position); 851 } 852 853 /** 854 * Returns the font for the legend items. 855 * 856 * @return The font. 857 * 858 * @see #setLegendItemFont(Font) 859 */ 860 public Font getLegendItemFont() { 861 return this.legend.getItemFont(); 862 } 863 864 /** 865 * Sets the font for the legend items and sends a 866 * {@link PropertyChangeEvent} to all registered listeners for the 867 * <code>legendItemFont</code> property. 868 * 869 * @param font the font (<code>null</code> not permitted). 870 * 871 * @see #getLegendItemFont() 872 */ 873 public void setLegendItemFont(Font font) { 874 Font old = this.legend.getItemFont(); 875 this.legend.setItemFont(font); 876 firePropertyChange("legendItemFont", old, font); 877 } 878 879 /** 880 * Returns the paint used to display the legend items. 881 * 882 * @return The paint. 883 * 884 * @see #setLegendItemPaint(Paint) 885 */ 886 public Paint getLegendItemPaint() { 887 return this.legend.getItemPaint(); 888 } 889 890 /** 891 * Sets the paint used to display the legend items and sends a 892 * {@link PropertyChangeEvent} to all registered listeners for the 893 * <code>legendItemPaint</code> property. 894 * 895 * @param paint the paint (<code>null</code> not permitted). 896 * 897 * @see #getLegendItemPaint() 898 */ 899 public void setLegendItemPaint(Paint paint) { 900 Paint old = this.legend.getItemPaint(); 901 this.legend.setItemPaint(paint); 902 firePropertyChange("legendItemPaint", old, paint); 903 } 904 905 private static final int MIN_DRAW_WIDTH = 200; 906 907 private static final int MIN_DRAW_HEIGHT = 150; 908 909 /** 910 * Paints this component, including the chart it contains. 911 * 912 * @param g the graphics target. 913 */ 914 protected void paintComponent(Graphics g) { 915 916 super.paintComponent(g); 917 if (this.chart == null) { 918 return; 919 } 920 921 Graphics2D g2 = (Graphics2D) g.create(); 922 923 // first determine the size of the chart rendering area... 924 Dimension size = getSize(); 925 Insets insets = getInsets(); 926 Rectangle2D available = new Rectangle2D.Double(insets.left, insets.top, 927 size.getWidth() - insets.left - insets.right, 928 size.getHeight() - insets.top - insets.bottom); 929 930 // work out if scaling is required (when the component is small, the 931 // chart will be drawn at a larger size and scaled down to fit the 932 // space... 933 boolean scale = false; 934 double drawWidth = available.getWidth(); 935 double drawHeight = available.getHeight(); 936 this.scaleX = 1.0; 937 this.scaleY = 1.0; 938 939 if (drawWidth < MIN_DRAW_WIDTH) { 940 this.scaleX = drawWidth / MIN_DRAW_WIDTH; 941 drawWidth = MIN_DRAW_WIDTH; 942 scale = true; 943 } 944 945 if (drawHeight < MIN_DRAW_HEIGHT) { 946 this.scaleY = drawHeight / MIN_DRAW_HEIGHT; 947 drawHeight = MIN_DRAW_HEIGHT; 948 scale = true; 949 } 950 951 Rectangle2D chartArea = new Rectangle2D.Double(0.0, 0.0, drawWidth, 952 drawHeight); 953 954 if ((this.chartBuffer == null) 955 || (this.chartBufferWidth != available.getWidth()) 956 || (this.chartBufferHeight != available.getHeight())) { 957 this.chartBufferWidth = (int) available.getWidth(); 958 this.chartBufferHeight = (int) available.getHeight(); 959 GraphicsConfiguration gc = g2.getDeviceConfiguration(); 960 this.chartBuffer = gc.createCompatibleImage( 961 this.chartBufferWidth, this.chartBufferHeight, 962 Transparency.TRANSLUCENT); 963 this.refreshBuffer = true; 964 } 965 966 // do we need to redraw the buffer? 967 if (this.refreshBuffer) { 968 969 Rectangle2D bufferArea = new Rectangle2D.Double(0, 0, 970 this.chartBufferWidth, this.chartBufferHeight); 971 972 Graphics2D bufferG2 = (Graphics2D) this.chartBuffer.getGraphics(); 973 if (scale) { 974 AffineTransform saved = bufferG2.getTransform(); 975 AffineTransform st = AffineTransform.getScaleInstance( 976 this.scaleX, this.scaleY); 977 bufferG2.transform(st); 978 this.chart.draw(bufferG2, chartArea, this.anchor, this.info); 979 bufferG2.setTransform(saved); 980 } 981 else { 982 this.chart.draw(bufferG2, bufferArea, this.anchor, this.info); 983 } 984 985 this.refreshBuffer = false; 986 987 } 988 989 // zap the buffer onto the panel... 990 g2.drawImage(this.chartBuffer, insets.left, insets.top, this); 991 g2.dispose(); 992 } 993 994 /** 995 * If the user clicks on the chart, see if that translates into an event 996 * that we report... 997 * 998 * @param event the event. 999 */ 1000 public void mouseClicked(MouseEvent event) { 1001 // if no-one is listening, just return... 1002 Object[] listeners = this.listeners.getListeners( 1003 LegendClickListener.class); 1004 if (listeners.length == 0) { 1005 return; 1006 } 1007 1008 Insets insets = getInsets(); 1009 int x = event.getX() - insets.left; 1010 int y = event.getY() - insets.top; 1011 1012 ChartEntity entity = null; 1013 if (this.info != null) { 1014 EntityCollection entities = this.info.getEntityCollection(); 1015 if (entities != null) { 1016 entity = entities.getEntity(x, y); 1017 } 1018 } 1019 if (entity instanceof LegendItemEntity) { 1020 LegendItemEntity lie = (LegendItemEntity) entity; 1021 LegendClickEvent lce = new LegendClickEvent(this, lie.getDataset(), 1022 lie.getSeriesKey()); 1023 fireLegendClickEvent(lce); 1024 } 1025 } 1026 1027 /* (non-Javadoc) 1028 * @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent) 1029 */ 1030 public void mouseEntered(MouseEvent e) { 1031 // ignore 1032 } 1033 1034 /* (non-Javadoc) 1035 * @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent) 1036 */ 1037 public void mouseExited(MouseEvent e) { 1038 // ignore 1039 } 1040 1041 /* (non-Javadoc) 1042 * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent) 1043 */ 1044 public void mousePressed(MouseEvent e) { 1045 if (this.zoomRectangle == null) { 1046 Rectangle2D screenDataArea = getScreenDataArea(); 1047 if (screenDataArea != null) { 1048 this.zoomPoint = getPointInRectangle(e.getX(), e.getY(), 1049 screenDataArea); 1050 } 1051 else { 1052 this.zoomPoint = null; 1053 } 1054 } 1055 if (e.isPopupTrigger()) { 1056 JPopupMenu popup = createPopup(); 1057 popup.show(this, e.getX(), e.getY()); 1058 } 1059 } 1060 1061 /* (non-Javadoc) 1062 * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent) 1063 */ 1064 public void mouseReleased(MouseEvent e) { 1065 if (this.zoomRectangle != null) { 1066 boolean hZoom = false; 1067 boolean vZoom = false; 1068 Plot plot = this.chart.getPlot(); 1069 if (plot instanceof Zoomable) { 1070 Zoomable z = (Zoomable) plot; 1071 PlotOrientation orientation = z.getOrientation(); 1072 if (orientation == PlotOrientation.HORIZONTAL) { 1073 hZoom = z.isRangeZoomable(); 1074 vZoom = z.isDomainZoomable(); 1075 } 1076 else { 1077 hZoom = z.isDomainZoomable(); 1078 vZoom = z.isRangeZoomable(); 1079 } 1080 } 1081 1082 boolean zoomTrigger1 = hZoom && Math.abs(e.getX() 1083 - this.zoomPoint.getX()) >= this.zoomTriggerDistance; 1084 boolean zoomTrigger2 = vZoom && Math.abs(e.getY() 1085 - this.zoomPoint.getY()) >= this.zoomTriggerDistance; 1086 if (zoomTrigger1 || zoomTrigger2) { 1087 if ((hZoom && (e.getX() < this.zoomPoint.getX())) 1088 || (vZoom && (e.getY() < this.zoomPoint.getY()))) { 1089 restoreAutoBounds(); 1090 } 1091 else { 1092 double x, y, w, h; 1093 Rectangle2D screenDataArea = getScreenDataArea(); 1094 // for mouseReleased event, (horizontalZoom || verticalZoom) 1095 // will be true, so we can just test for either being false; 1096 // otherwise both are true 1097 if (!vZoom) { 1098 x = this.zoomPoint.getX(); 1099 y = screenDataArea.getMinY(); 1100 w = Math.min(this.zoomRectangle.getWidth(), 1101 screenDataArea.getMaxX() 1102 - this.zoomPoint.getX()); 1103 h = screenDataArea.getHeight(); 1104 } 1105 else if (!hZoom) { 1106 x = screenDataArea.getMinX(); 1107 y = this.zoomPoint.getY(); 1108 w = screenDataArea.getWidth(); 1109 h = Math.min(this.zoomRectangle.getHeight(), 1110 screenDataArea.getMaxY() 1111 - this.zoomPoint.getY()); 1112 } 1113 else { 1114 x = this.zoomPoint.getX(); 1115 y = this.zoomPoint.getY(); 1116 w = Math.min(this.zoomRectangle.getWidth(), 1117 screenDataArea.getMaxX() 1118 - this.zoomPoint.getX()); 1119 h = Math.min(this.zoomRectangle.getHeight(), 1120 screenDataArea.getMaxY() 1121 - this.zoomPoint.getY()); 1122 } 1123 Rectangle2D zoomArea = new Rectangle2D.Double(x, y, w, h); 1124 zoom(zoomArea); 1125 } 1126 this.zoomPoint = null; 1127 this.zoomRectangle = null; 1128 } 1129 else { 1130 // Erase the zoom rectangle 1131 Graphics2D g2 = (Graphics2D) getGraphics(); 1132 drawZoomRectangle(g2); 1133 g2.dispose(); 1134 this.zoomPoint = null; 1135 this.zoomRectangle = null; 1136 } 1137 1138 } 1139 1140 else if (e.isPopupTrigger()) { 1141 JPopupMenu popup = createPopup(); 1142 popup.show(this, e.getX(), e.getY()); 1143 } 1144 } 1145 1146 /** 1147 * Implementation of the MouseMotionListener's method. 1148 * 1149 * @param e the event. 1150 */ 1151 public void mouseMoved(MouseEvent e) { 1152 // do nothing 1153 } 1154 1155 /** 1156 * Handles a 'mouse dragged' event. 1157 * 1158 * @param e the mouse event. 1159 */ 1160 public void mouseDragged(MouseEvent e) { 1161 // FIXME: handle popup 1162 // if the popup menu has already been triggered, then ignore dragging... 1163 // if (this.popup != null && this.popup.isShowing()) { 1164 // return; 1165 // } 1166 // if no initial zoom point was set, ignore dragging... 1167 if (this.zoomPoint == null) { 1168 return; 1169 } 1170 Graphics2D g2 = (Graphics2D) getGraphics(); 1171 1172 // Erase the previous zoom rectangle (if any)... 1173 drawZoomRectangle(g2); 1174 1175 boolean hZoom = false; 1176 boolean vZoom = false; 1177 Plot plot = this.chart.getPlot(); 1178 if (plot instanceof Zoomable) { 1179 Zoomable z = (Zoomable) plot; 1180 PlotOrientation orientation = z.getOrientation(); 1181 if (orientation == PlotOrientation.HORIZONTAL) { 1182 hZoom = z.isRangeZoomable(); 1183 vZoom = z.isDomainZoomable(); 1184 } 1185 else { 1186 hZoom = z.isDomainZoomable(); 1187 vZoom = z.isRangeZoomable(); 1188 } 1189 } 1190 Rectangle2D scaledDataArea = getScreenDataArea(); 1191 if (hZoom && vZoom) { 1192 // selected rectangle shouldn't extend outside the data area... 1193 double xmax = Math.min(e.getX(), scaledDataArea.getMaxX()); 1194 double ymax = Math.min(e.getY(), scaledDataArea.getMaxY()); 1195 this.zoomRectangle = new Rectangle2D.Double( 1196 this.zoomPoint.getX(), this.zoomPoint.getY(), 1197 xmax - this.zoomPoint.getX(), ymax - this.zoomPoint.getY()); 1198 } 1199 else if (hZoom) { 1200 double xmax = Math.min(e.getX(), scaledDataArea.getMaxX()); 1201 this.zoomRectangle = new Rectangle2D.Double( 1202 this.zoomPoint.getX(), scaledDataArea.getMinY(), 1203 xmax - this.zoomPoint.getX(), scaledDataArea.getHeight()); 1204 } 1205 else if (vZoom) { 1206 double ymax = Math.min(e.getY(), scaledDataArea.getMaxY()); 1207 this.zoomRectangle = new Rectangle2D.Double( 1208 scaledDataArea.getMinX(), this.zoomPoint.getY(), 1209 scaledDataArea.getWidth(), ymax - this.zoomPoint.getY()); 1210 } 1211 1212 // Draw the new zoom rectangle... 1213 drawZoomRectangle(g2); 1214 1215 g2.dispose(); 1216 1217 } 1218 1219 /** 1220 * Creates a popup menu for display on the component. 1221 * 1222 * @return A popup menu. 1223 */ 1224 protected JPopupMenu createPopup() { 1225 // This is still a work-in-progress, obviously! 1226 JPopupMenu popup = new JPopupMenu(); 1227 JMenuItem saveAs = new JMenuItem("Save As..."); 1228 saveAs.addActionListener(this); 1229 saveAs.setActionCommand("SAVE_AS"); 1230 popup.add(saveAs); 1231 return popup; 1232 } 1233 1234 /** 1235 * Returns the data area for the chart (the area inside the axes) with the 1236 * current scaling applied (that is, the area as it appears on screen). 1237 * 1238 * @return The scaled data area. 1239 */ 1240 public Rectangle2D getScreenDataArea() { 1241 Rectangle2D dataArea = this.info.getPlotInfo().getDataArea(); 1242 Insets insets = getInsets(); 1243 double x = dataArea.getX() * this.scaleX + insets.left; 1244 double y = dataArea.getY() * this.scaleY + insets.top; 1245 double w = dataArea.getWidth() * this.scaleX; 1246 double h = dataArea.getHeight() * this.scaleY; 1247 return new Rectangle2D.Double(x, y, w, h); 1248 } 1249 1250 /** 1251 * Returns a point based on (x, y) but constrained to be within the bounds 1252 * of the given rectangle. This method could be moved to JCommon. 1253 * 1254 * @param x the x-coordinate. 1255 * @param y the y-coordinate. 1256 * @param area the rectangle (<code>null</code> not permitted). 1257 * 1258 * @return A point within the rectangle. 1259 */ 1260 private Point getPointInRectangle(int x, int y, Rectangle2D area) { 1261 x = (int) Math.max(Math.ceil(area.getMinX()), Math.min(x, 1262 Math.floor(area.getMaxX()))); 1263 y = (int) Math.max(Math.ceil(area.getMinY()), Math.min(y, 1264 Math.floor(area.getMaxY()))); 1265 return new Point(x, y); 1266 } 1267 1268 /** 1269 * Restores the auto-range calculation on both axes. 1270 */ 1271 public void restoreAutoBounds() { 1272 restoreAutoDomainBounds(); 1273 restoreAutoRangeBounds(); 1274 } 1275 1276 /** 1277 * Restores the auto-range calculation on the domain axis. 1278 */ 1279 public void restoreAutoDomainBounds() { 1280 Plot p = this.chart.getPlot(); 1281 if (p instanceof Zoomable) { 1282 Zoomable z = (Zoomable) p; 1283 // we need to guard against this.zoomPoint being null 1284 Point zp = (this.zoomPoint != null ? this.zoomPoint : new Point()); 1285 z.zoomDomainAxes(0.0, this.info.getPlotInfo(), zp); 1286 } 1287 } 1288 1289 /** 1290 * Restores the auto-range calculation on the range axis. 1291 */ 1292 public void restoreAutoRangeBounds() { 1293 Plot p = this.chart.getPlot(); 1294 if (p instanceof Zoomable) { 1295 Zoomable z = (Zoomable) p; 1296 // we need to guard against this.zoomPoint being null 1297 Point zp = (this.zoomPoint != null ? this.zoomPoint : new Point()); 1298 z.zoomRangeAxes(0.0, this.info.getPlotInfo(), zp); 1299 } 1300 } 1301 1302 /** 1303 * Translates a Java2D point on the chart to a screen location. 1304 * 1305 * @param java2DPoint the Java2D point. 1306 * 1307 * @return The screen location. 1308 */ 1309 public Point translateJava2DToScreen(Point2D java2DPoint) { 1310 Insets insets = getInsets(); 1311 int x = (int) (java2DPoint.getX() * this.scaleX + insets.left); 1312 int y = (int) (java2DPoint.getY() * this.scaleY + insets.top); 1313 return new Point(x, y); 1314 } 1315 1316 /** 1317 * Translates a screen location to a Java2D point. 1318 * 1319 * @param screenPoint the screen location. 1320 * 1321 * @return The Java2D coordinates. 1322 */ 1323 public Point2D translateScreenToJava2D(Point screenPoint) { 1324 Insets insets = getInsets(); 1325 double x = (screenPoint.getX() - insets.left) / this.scaleX; 1326 double y = (screenPoint.getY() - insets.top) / this.scaleY; 1327 return new Point2D.Double(x, y); 1328 } 1329 1330 /** 1331 * Zooms in on a selected region. 1332 * 1333 * @param selection the selected region. 1334 */ 1335 public void zoom(Rectangle2D selection) { 1336 1337 // get the origin of the zoom selection in the Java2D space used for 1338 // drawing the chart (that is, before any scaling to fit the panel) 1339 Point2D selectOrigin = translateScreenToJava2D(new Point( 1340 (int) Math.ceil(selection.getX()), 1341 (int) Math.ceil(selection.getY()))); 1342 PlotRenderingInfo plotInfo = this.info.getPlotInfo(); 1343 Rectangle2D scaledDataArea = getScreenDataArea(); 1344 if ((selection.getHeight() > 0) && (selection.getWidth() > 0)) { 1345 1346 double hLower = (selection.getMinX() - scaledDataArea.getMinX()) 1347 / scaledDataArea.getWidth(); 1348 double hUpper = (selection.getMaxX() - scaledDataArea.getMinX()) 1349 / scaledDataArea.getWidth(); 1350 double vLower = (scaledDataArea.getMaxY() - selection.getMaxY()) 1351 / scaledDataArea.getHeight(); 1352 double vUpper = (scaledDataArea.getMaxY() - selection.getMinY()) 1353 / scaledDataArea.getHeight(); 1354 1355 Plot p = this.chart.getPlot(); 1356 if (p instanceof Zoomable) { 1357 Zoomable z = (Zoomable) p; 1358 if (z.getOrientation() == PlotOrientation.HORIZONTAL) { 1359 z.zoomDomainAxes(vLower, vUpper, plotInfo, selectOrigin); 1360 z.zoomRangeAxes(hLower, hUpper, plotInfo, selectOrigin); 1361 } 1362 else { 1363 z.zoomDomainAxes(hLower, hUpper, plotInfo, selectOrigin); 1364 z.zoomRangeAxes(vLower, vUpper, plotInfo, selectOrigin); 1365 } 1366 } 1367 1368 } 1369 1370 } 1371 1372 /** 1373 * Switches the display of tooltips for the panel on or off. Note that 1374 * tooltips can only be displayed if the chart has been configured to 1375 * generate tooltip items. 1376 * 1377 * @param flag <code>true</code> to enable tooltips, <code>false</code> to 1378 * disable tooltips. 1379 */ 1380 protected void setToolTipsEnabled(boolean flag) { 1381 if (flag) { 1382 ToolTipManager.sharedInstance().registerComponent(this); 1383 } 1384 else { 1385 ToolTipManager.sharedInstance().unregisterComponent(this); 1386 } 1387 } 1388 1389 /** 1390 * Returns a string for the tooltip. 1391 * 1392 * @param e the mouse event. 1393 * 1394 * @return A tool tip or <code>null</code> if no tooltip is available. 1395 */ 1396 public String getToolTipText(MouseEvent e) { 1397 String result = null; 1398 if (this.info != null) { 1399 EntityCollection entities = this.info.getEntityCollection(); 1400 if (entities != null) { 1401 Insets insets = getInsets(); 1402 ChartEntity entity = entities.getEntity(e.getX() - insets.left, 1403 e.getY() - insets.top); 1404 if (entity != null) { 1405 result = entity.getToolTipText(); 1406 } 1407 } 1408 } 1409 return result; 1410 } 1411 1412 /** 1413 * Registers a listener to receive notification of legend clicks. 1414 * 1415 * @param listener the listener (<code>null</code> not permitted). 1416 */ 1417 public void addLegendClickListener(LegendClickListener listener) { 1418 if (listener == null) { 1419 throw new IllegalArgumentException("Null 'listener' argument."); 1420 } 1421 this.listeners.add(LegendClickListener.class, listener); 1422 } 1423 1424 /** 1425 * Unregisters a listener so that it no longer receives notification of 1426 * legend clicks. 1427 * 1428 * @param listener the listener (<code>null</code> not permitted). 1429 */ 1430 public void removeLegendClickListener(LegendClickListener listener) { 1431 if (listener == null) { 1432 throw new IllegalArgumentException("Null 'listener' argument."); 1433 } 1434 this.listeners.remove(LegendClickListener.class, listener); 1435 } 1436 1437 /** 1438 * Fires a legend click event. 1439 * 1440 * @param event the event. 1441 */ 1442 public void fireLegendClickEvent(LegendClickEvent event) { 1443 Object[] listeners = this.listeners.getListeners( 1444 LegendClickListener.class); 1445 for (int i = listeners.length - 1; i >= 0; i -= 1) { 1446 ((LegendClickListener) listeners[i]).onLegendClick(event); 1447 } 1448 1449 } 1450 1451 /** 1452 * Draws zoom rectangle (if present). 1453 * The drawing is performed in XOR mode, therefore 1454 * when this method is called twice in a row, 1455 * the second call will completely restore the state 1456 * of the canvas. 1457 * 1458 * @param g2 the graphics device. 1459 */ 1460 private void drawZoomRectangle(Graphics2D g2) { 1461 // Set XOR mode to draw the zoom rectangle 1462 g2.setXORMode(Color.gray); 1463 if (this.zoomRectangle != null) { 1464 if (this.fillZoomRectangle) { 1465 g2.fill(this.zoomRectangle); 1466 } 1467 else { 1468 g2.draw(this.zoomRectangle); 1469 } 1470 } 1471 // Reset to the default 'overwrite' mode 1472 g2.setPaintMode(); 1473 } 1474 1475 /** 1476 * Receives notification of changes to the chart, and redraws the chart. 1477 * 1478 * @param event details of the chart change event. 1479 */ 1480 public void chartChanged(ChartChangeEvent event) { 1481 this.refreshBuffer = true; 1482 //Plot plot = this.chart.getPlot(); 1483 //if (plot instanceof Zoomable) { 1484 //Zoomable z = (Zoomable) plot; 1485 //this.orientation = z.getOrientation(); 1486 //} 1487 repaint(); 1488 } 1489 1490 /* (non-Javadoc) 1491 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) 1492 */ 1493 public void actionPerformed(ActionEvent e) { 1494 if (e.getActionCommand().equals("SAVE_AS")) { 1495 try { 1496 doSaveAs(); 1497 } 1498 catch (IOException ioe) { 1499 ioe.printStackTrace(); 1500 } 1501 } 1502 } 1503 1504 protected void doSaveAs() throws IOException { 1505 JFileChooser fileChooser = new JFileChooser(); 1506 ExtensionFileFilter filter = new ExtensionFileFilter("PNG Files", 1507 ".png"); 1508 fileChooser.addChoosableFileFilter(filter); 1509 1510 int option = fileChooser.showSaveDialog(this); 1511 if (option == JFileChooser.APPROVE_OPTION) { 1512 String filename = fileChooser.getSelectedFile().getPath(); 1513 ChartUtilities.saveChartAsPNG(new File(filename), this.chart, 1514 getWidth(), getHeight()); 1515 } 1516 1517 } 1518 1519 }