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 }