001 /* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
006 *
007 * Project Info: http://www.jfree.org/jfreechart/index.html
008 *
009 * This library is free software; you can redistribute it and/or modify it
010 * under the terms of the GNU Lesser General Public License as published by
011 * the Free Software Foundation; either version 2.1 of the License, or
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
022 * USA.
023 *
024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025 * in the United States and other countries.]
026 *
027 * ---------
028 * Plot.java
029 * ---------
030 * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
031 *
032 * Original Author: David Gilbert (for Object Refinery Limited);
033 * Contributor(s): Sylvain Vieujot;
034 * Jeremy Bowman;
035 * Andreas Schneider;
036 * Gideon Krause;
037 * Nicolas Brodu;
038 * Michal Krause;
039 *
040 * $Id: Plot.java,v 1.18.2.9 2007/06/07 12:49:36 mungady Exp $
041 *
042 * Changes (from 21-Jun-2001)
043 * --------------------------
044 * 21-Jun-2001 : Removed redundant JFreeChart parameter from constructors (DG);
045 * 18-Sep-2001 : Updated header info and fixed DOS encoding problem (DG);
046 * 19-Oct-2001 : Moved series paint and stroke methods from JFreeChart
047 * class (DG);
048 * 23-Oct-2001 : Created renderer for LinePlot class (DG);
049 * 07-Nov-2001 : Changed type names for ChartChangeEvent (DG);
050 * Tidied up some Javadoc comments (DG);
051 * 13-Nov-2001 : Changes to allow for null axes on plots such as PiePlot (DG);
052 * Added plot/axis compatibility checks (DG);
053 * 12-Dec-2001 : Changed constructors to protected, and removed unnecessary
054 * 'throws' clauses (DG);
055 * 13-Dec-2001 : Added tooltips (DG);
056 * 22-Jan-2002 : Added handleClick() method, as part of implementation for
057 * crosshairs (DG);
058 * Moved tooltips reference into ChartInfo class (DG);
059 * 23-Jan-2002 : Added test for null axes in chartChanged() method, thanks
060 * to Barry Evans for the bug report (number 506979 on
061 * SourceForge) (DG);
062 * Added a zoom() method (DG);
063 * 05-Feb-2002 : Updated setBackgroundPaint(), setOutlineStroke() and
064 * setOutlinePaint() to better handle null values, as suggested
065 * by Sylvain Vieujot (DG);
066 * 06-Feb-2002 : Added background image, plus alpha transparency for background
067 * and foreground (DG);
068 * 06-Mar-2002 : Added AxisConstants interface (DG);
069 * 26-Mar-2002 : Changed zoom method from empty to abstract (DG);
070 * 23-Apr-2002 : Moved dataset from JFreeChart class (DG);
071 * 11-May-2002 : Added ShapeFactory interface for getShape() methods,
072 * contributed by Jeremy Bowman (DG);
073 * 28-May-2002 : Fixed bug in setSeriesPaint(int, Paint) for subplots (AS);
074 * 25-Jun-2002 : Removed redundant imports (DG);
075 * 30-Jul-2002 : Added 'no data' message for charts with null or empty
076 * datasets (DG);
077 * 21-Aug-2002 : Added code to extend series array if necessary (refer to
078 * SourceForge bug id 594547 for details) (DG);
079 * 17-Sep-2002 : Fixed bug in getSeriesOutlineStroke() method, reported by
080 * Andreas Schroeder (DG);
081 * 23-Sep-2002 : Added getLegendItems() abstract method (DG);
082 * 24-Sep-2002 : Removed firstSeriesIndex, subplots now use their own paint
083 * settings, there is a new mechanism for the legend to collect
084 * the legend items (DG);
085 * 27-Sep-2002 : Added dataset group (DG);
086 * 14-Oct-2002 : Moved listener storage into EventListenerList. Changed some
087 * abstract methods to empty implementations (DG);
088 * 28-Oct-2002 : Added a getBackgroundImage() method (DG);
089 * 21-Nov-2002 : Added a plot index for identifying subplots in combined and
090 * overlaid charts (DG);
091 * 22-Nov-2002 : Changed all attributes from 'protected' to 'private'. Added
092 * dataAreaRatio attribute from David M O'Donnell's code (DG);
093 * 09-Jan-2003 : Integrated fix for plot border contributed by Gideon
094 * Krause (DG);
095 * 17-Jan-2003 : Moved to com.jrefinery.chart.plot (DG);
096 * 23-Jan-2003 : Removed one constructor (DG);
097 * 26-Mar-2003 : Implemented Serializable (DG);
098 * 14-Jul-2003 : Moved the dataset and secondaryDataset attributes to the
099 * CategoryPlot and XYPlot classes (DG);
100 * 21-Jul-2003 : Moved DrawingSupplier from CategoryPlot and XYPlot up to this
101 * class (DG);
102 * 20-Aug-2003 : Implemented Cloneable (DG);
103 * 11-Sep-2003 : Listeners and clone (NB);
104 * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG);
105 * 03-Dec-2003 : Modified draw method to accept anchor (DG);
106 * 12-Mar-2004 : Fixed clipping bug in drawNoDataMessage() method (DG);
107 * 07-Apr-2004 : Modified string bounds calculation (DG);
108 * 04-Nov-2004 : Added default shapes for legend items (DG);
109 * 25-Nov-2004 : Some changes to the clone() method implementation (DG);
110 * 23-Feb-2005 : Implemented new LegendItemSource interface (and also
111 * PublicCloneable) (DG);
112 * 21-Apr-2005 : Replaced Insets with RectangleInsets (DG);
113 * 05-May-2005 : Removed unused draw() method (DG);
114 * 06-Jun-2005 : Fixed bugs in equals() method (DG);
115 * 01-Sep-2005 : Moved dataAreaRatio from here to ContourPlot (DG);
116 * ------------- JFREECHART 1.0.x ---------------------------------------------
117 * 30-Jun-2006 : Added background image alpha - see bug report 1514904 (DG);
118 * 05-Sep-2006 : Implemented the MarkerChangeListener interface (DG);
119 * 11-Jan-2007 : Added some argument checks, event notifications, and many
120 * API doc updates (DG);
121 * 03-Apr-2007 : Made drawBackgroundImage() public (DG);
122 * 07-Jun-2007 : Added new fillBackground() method to handle GradientPaint
123 * taking into account orientation (DG);
124 *
125 */
126
127 package org.jfree.chart.plot;
128
129 import java.awt.AlphaComposite;
130 import java.awt.BasicStroke;
131 import java.awt.Color;
132 import java.awt.Composite;
133 import java.awt.Font;
134 import java.awt.GradientPaint;
135 import java.awt.Graphics2D;
136 import java.awt.Image;
137 import java.awt.Paint;
138 import java.awt.Shape;
139 import java.awt.Stroke;
140 import java.awt.geom.Ellipse2D;
141 import java.awt.geom.Point2D;
142 import java.awt.geom.Rectangle2D;
143 import java.io.IOException;
144 import java.io.ObjectInputStream;
145 import java.io.ObjectOutputStream;
146 import java.io.Serializable;
147
148 import javax.swing.event.EventListenerList;
149
150 import org.jfree.chart.LegendItemCollection;
151 import org.jfree.chart.LegendItemSource;
152 import org.jfree.chart.axis.AxisLocation;
153 import org.jfree.chart.event.AxisChangeEvent;
154 import org.jfree.chart.event.AxisChangeListener;
155 import org.jfree.chart.event.ChartChangeEventType;
156 import org.jfree.chart.event.MarkerChangeEvent;
157 import org.jfree.chart.event.MarkerChangeListener;
158 import org.jfree.chart.event.PlotChangeEvent;
159 import org.jfree.chart.event.PlotChangeListener;
160 import org.jfree.data.general.DatasetChangeEvent;
161 import org.jfree.data.general.DatasetChangeListener;
162 import org.jfree.data.general.DatasetGroup;
163 import org.jfree.io.SerialUtilities;
164 import org.jfree.text.G2TextMeasurer;
165 import org.jfree.text.TextBlock;
166 import org.jfree.text.TextBlockAnchor;
167 import org.jfree.text.TextUtilities;
168 import org.jfree.ui.Align;
169 import org.jfree.ui.RectangleEdge;
170 import org.jfree.ui.RectangleInsets;
171 import org.jfree.util.ObjectUtilities;
172 import org.jfree.util.PaintUtilities;
173 import org.jfree.util.PublicCloneable;
174
175 /**
176 * The base class for all plots in JFreeChart. The
177 * {@link org.jfree.chart.JFreeChart} class delegates the drawing of axes and
178 * data to the plot. This base class provides facilities common to most plot
179 * types.
180 */
181 public abstract class Plot implements AxisChangeListener,
182 DatasetChangeListener,
183 MarkerChangeListener,
184 LegendItemSource,
185 PublicCloneable,
186 Cloneable,
187 Serializable {
188
189 /** For serialization. */
190 private static final long serialVersionUID = -8831571430103671324L;
191
192 /** Useful constant representing zero. */
193 public static final Number ZERO = new Integer(0);
194
195 /** The default insets. */
196 public static final RectangleInsets DEFAULT_INSETS
197 = new RectangleInsets(4.0, 8.0, 4.0, 8.0);
198
199 /** The default outline stroke. */
200 public static final Stroke DEFAULT_OUTLINE_STROKE = new BasicStroke(0.5f);
201
202 /** The default outline color. */
203 public static final Paint DEFAULT_OUTLINE_PAINT = Color.gray;
204
205 /** The default foreground alpha transparency. */
206 public static final float DEFAULT_FOREGROUND_ALPHA = 1.0f;
207
208 /** The default background alpha transparency. */
209 public static final float DEFAULT_BACKGROUND_ALPHA = 1.0f;
210
211 /** The default background color. */
212 public static final Paint DEFAULT_BACKGROUND_PAINT = Color.white;
213
214 /** The minimum width at which the plot should be drawn. */
215 public static final int MINIMUM_WIDTH_TO_DRAW = 10;
216
217 /** The minimum height at which the plot should be drawn. */
218 public static final int MINIMUM_HEIGHT_TO_DRAW = 10;
219
220 /** A default box shape for legend items. */
221 public static final Shape DEFAULT_LEGEND_ITEM_BOX
222 = new Rectangle2D.Double(-4.0, -4.0, 8.0, 8.0);
223
224 /** A default circle shape for legend items. */
225 public static final Shape DEFAULT_LEGEND_ITEM_CIRCLE
226 = new Ellipse2D.Double(-4.0, -4.0, 8.0, 8.0);
227
228 /** The parent plot (<code>null</code> if this is the root plot). */
229 private Plot parent;
230
231 /** The dataset group (to be used for thread synchronisation). */
232 private DatasetGroup datasetGroup;
233
234 /** The message to display if no data is available. */
235 private String noDataMessage;
236
237 /** The font used to display the 'no data' message. */
238 private Font noDataMessageFont;
239
240 /** The paint used to draw the 'no data' message. */
241 private transient Paint noDataMessagePaint;
242
243 /** Amount of blank space around the plot area. */
244 private RectangleInsets insets;
245
246 /**
247 * A flag that controls whether or not the plot outline is drawn.
248 *
249 * @since 1.0.6
250 */
251 private boolean outlineVisible;
252
253 /** The Stroke used to draw an outline around the plot. */
254 private transient Stroke outlineStroke;
255
256 /** The Paint used to draw an outline around the plot. */
257 private transient Paint outlinePaint;
258
259 /** An optional color used to fill the plot background. */
260 private transient Paint backgroundPaint;
261
262 /** An optional image for the plot background. */
263 private transient Image backgroundImage; // not currently serialized
264
265 /** The alignment for the background image. */
266 private int backgroundImageAlignment = Align.FIT;
267
268 /** The alpha value used to draw the background image. */
269 private float backgroundImageAlpha = 0.5f;
270
271 /** The alpha-transparency for the plot. */
272 private float foregroundAlpha;
273
274 /** The alpha transparency for the background paint. */
275 private float backgroundAlpha;
276
277 /** The drawing supplier. */
278 private DrawingSupplier drawingSupplier;
279
280 /** Storage for registered change listeners. */
281 private transient EventListenerList listenerList;
282
283 /**
284 * Creates a new plot.
285 */
286 protected Plot() {
287
288 this.parent = null;
289 this.insets = DEFAULT_INSETS;
290 this.backgroundPaint = DEFAULT_BACKGROUND_PAINT;
291 this.backgroundAlpha = DEFAULT_BACKGROUND_ALPHA;
292 this.backgroundImage = null;
293 this.outlineVisible = true;
294 this.outlineStroke = DEFAULT_OUTLINE_STROKE;
295 this.outlinePaint = DEFAULT_OUTLINE_PAINT;
296 this.foregroundAlpha = DEFAULT_FOREGROUND_ALPHA;
297
298 this.noDataMessage = null;
299 this.noDataMessageFont = new Font("SansSerif", Font.PLAIN, 12);
300 this.noDataMessagePaint = Color.black;
301
302 this.drawingSupplier = new DefaultDrawingSupplier();
303
304 this.listenerList = new EventListenerList();
305
306 }
307
308 /**
309 * Returns the dataset group for the plot (not currently used).
310 *
311 * @return The dataset group.
312 *
313 * @see #setDatasetGroup(DatasetGroup)
314 */
315 public DatasetGroup getDatasetGroup() {
316 return this.datasetGroup;
317 }
318
319 /**
320 * Sets the dataset group (not currently used).
321 *
322 * @param group the dataset group (<code>null</code> permitted).
323 *
324 * @see #getDatasetGroup()
325 */
326 protected void setDatasetGroup(DatasetGroup group) {
327 this.datasetGroup = group;
328 }
329
330 /**
331 * Returns the string that is displayed when the dataset is empty or
332 * <code>null</code>.
333 *
334 * @return The 'no data' message (<code>null</code> possible).
335 *
336 * @see #setNoDataMessage(String)
337 * @see #getNoDataMessageFont()
338 * @see #getNoDataMessagePaint()
339 */
340 public String getNoDataMessage() {
341 return this.noDataMessage;
342 }
343
344 /**
345 * Sets the message that is displayed when the dataset is empty or
346 * <code>null</code>, and sends a {@link PlotChangeEvent} to all registered
347 * listeners.
348 *
349 * @param message the message (<code>null</code> permitted).
350 *
351 * @see #getNoDataMessage()
352 */
353 public void setNoDataMessage(String message) {
354 this.noDataMessage = message;
355 notifyListeners(new PlotChangeEvent(this));
356 }
357
358 /**
359 * Returns the font used to display the 'no data' message.
360 *
361 * @return The font (never <code>null</code>).
362 *
363 * @see #setNoDataMessageFont(Font)
364 * @see #getNoDataMessage()
365 */
366 public Font getNoDataMessageFont() {
367 return this.noDataMessageFont;
368 }
369
370 /**
371 * Sets the font used to display the 'no data' message and sends a
372 * {@link PlotChangeEvent} to all registered listeners.
373 *
374 * @param font the font (<code>null</code> not permitted).
375 *
376 * @see #getNoDataMessageFont()
377 */
378 public void setNoDataMessageFont(Font font) {
379 if (font == null) {
380 throw new IllegalArgumentException("Null 'font' argument.");
381 }
382 this.noDataMessageFont = font;
383 notifyListeners(new PlotChangeEvent(this));
384 }
385
386 /**
387 * Returns the paint used to display the 'no data' message.
388 *
389 * @return The paint (never <code>null</code>).
390 *
391 * @see #setNoDataMessagePaint(Paint)
392 * @see #getNoDataMessage()
393 */
394 public Paint getNoDataMessagePaint() {
395 return this.noDataMessagePaint;
396 }
397
398 /**
399 * Sets the paint used to display the 'no data' message and sends a
400 * {@link PlotChangeEvent} to all registered listeners.
401 *
402 * @param paint the paint (<code>null</code> not permitted).
403 *
404 * @see #getNoDataMessagePaint()
405 */
406 public void setNoDataMessagePaint(Paint paint) {
407 if (paint == null) {
408 throw new IllegalArgumentException("Null 'paint' argument.");
409 }
410 this.noDataMessagePaint = paint;
411 notifyListeners(new PlotChangeEvent(this));
412 }
413
414 /**
415 * Returns a short string describing the plot type.
416 * <P>
417 * Note: this gets used in the chart property editing user interface,
418 * but there needs to be a better mechanism for identifying the plot type.
419 *
420 * @return A short string describing the plot type (never
421 * <code>null</code>).
422 */
423 public abstract String getPlotType();
424
425 /**
426 * Returns the parent plot (or <code>null</code> if this plot is not part
427 * of a combined plot).
428 *
429 * @return The parent plot.
430 *
431 * @see #setParent(Plot)
432 * @see #getRootPlot()
433 */
434 public Plot getParent() {
435 return this.parent;
436 }
437
438 /**
439 * Sets the parent plot. This method is intended for internal use, you
440 * shouldn't need to call it directly.
441 *
442 * @param parent the parent plot (<code>null</code> permitted).
443 *
444 * @see #getParent()
445 */
446 public void setParent(Plot parent) {
447 this.parent = parent;
448 }
449
450 /**
451 * Returns the root plot.
452 *
453 * @return The root plot.
454 *
455 * @see #getParent()
456 */
457 public Plot getRootPlot() {
458
459 Plot p = getParent();
460 if (p == null) {
461 return this;
462 }
463 else {
464 return p.getRootPlot();
465 }
466
467 }
468
469 /**
470 * Returns <code>true</code> if this plot is part of a combined plot
471 * structure (that is, {@link #getParent()} returns a non-<code>null</code>
472 * value), and <code>false</code> otherwise.
473 *
474 * @return <code>true</code> if this plot is part of a combined plot
475 * structure.
476 *
477 * @see #getParent()
478 */
479 public boolean isSubplot() {
480 return (getParent() != null);
481 }
482
483 /**
484 * Returns the insets for the plot area.
485 *
486 * @return The insets (never <code>null</code>).
487 *
488 * @see #setInsets(RectangleInsets)
489 */
490 public RectangleInsets getInsets() {
491 return this.insets;
492 }
493
494 /**
495 * Sets the insets for the plot and sends a {@link PlotChangeEvent} to
496 * all registered listeners.
497 *
498 * @param insets the new insets (<code>null</code> not permitted).
499 *
500 * @see #getInsets()
501 * @see #setInsets(RectangleInsets, boolean)
502 */
503 public void setInsets(RectangleInsets insets) {
504 setInsets(insets, true);
505 }
506
507 /**
508 * Sets the insets for the plot and, if requested, and sends a
509 * {@link PlotChangeEvent} to all registered listeners.
510 *
511 * @param insets the new insets (<code>null</code> not permitted).
512 * @param notify a flag that controls whether the registered listeners are
513 * notified.
514 *
515 * @see #getInsets()
516 * @see #setInsets(RectangleInsets)
517 */
518 public void setInsets(RectangleInsets insets, boolean notify) {
519 if (insets == null) {
520 throw new IllegalArgumentException("Null 'insets' argument.");
521 }
522 if (!this.insets.equals(insets)) {
523 this.insets = insets;
524 if (notify) {
525 notifyListeners(new PlotChangeEvent(this));
526 }
527 }
528
529 }
530
531 /**
532 * Returns the background color of the plot area.
533 *
534 * @return The paint (possibly <code>null</code>).
535 *
536 * @see #setBackgroundPaint(Paint)
537 */
538 public Paint getBackgroundPaint() {
539 return this.backgroundPaint;
540 }
541
542 /**
543 * Sets the background color of the plot area and sends a
544 * {@link PlotChangeEvent} to all registered listeners.
545 *
546 * @param paint the paint (<code>null</code> permitted).
547 *
548 * @see #getBackgroundPaint()
549 */
550 public void setBackgroundPaint(Paint paint) {
551
552 if (paint == null) {
553 if (this.backgroundPaint != null) {
554 this.backgroundPaint = null;
555 notifyListeners(new PlotChangeEvent(this));
556 }
557 }
558 else {
559 if (this.backgroundPaint != null) {
560 if (this.backgroundPaint.equals(paint)) {
561 return; // nothing to do
562 }
563 }
564 this.backgroundPaint = paint;
565 notifyListeners(new PlotChangeEvent(this));
566 }
567
568 }
569
570 /**
571 * Returns the alpha transparency of the plot area background.
572 *
573 * @return The alpha transparency.
574 *
575 * @see #setBackgroundAlpha(float)
576 */
577 public float getBackgroundAlpha() {
578 return this.backgroundAlpha;
579 }
580
581 /**
582 * Sets the alpha transparency of the plot area background, and notifies
583 * registered listeners that the plot has been modified.
584 *
585 * @param alpha the new alpha value (in the range 0.0f to 1.0f).
586 *
587 * @see #getBackgroundAlpha()
588 */
589 public void setBackgroundAlpha(float alpha) {
590 if (this.backgroundAlpha != alpha) {
591 this.backgroundAlpha = alpha;
592 notifyListeners(new PlotChangeEvent(this));
593 }
594 }
595
596 /**
597 * Returns the drawing supplier for the plot.
598 *
599 * @return The drawing supplier (possibly <code>null</code>).
600 *
601 * @see #setDrawingSupplier(DrawingSupplier)
602 */
603 public DrawingSupplier getDrawingSupplier() {
604 DrawingSupplier result = null;
605 Plot p = getParent();
606 if (p != null) {
607 result = p.getDrawingSupplier();
608 }
609 else {
610 result = this.drawingSupplier;
611 }
612 return result;
613 }
614
615 /**
616 * Sets the drawing supplier for the plot. The drawing supplier is
617 * responsible for supplying a limitless (possibly repeating) sequence of
618 * <code>Paint</code>, <code>Stroke</code> and <code>Shape</code> objects
619 * that the plot's renderer(s) can use to populate its (their) tables.
620 *
621 * @param supplier the new supplier.
622 *
623 * @see #getDrawingSupplier()
624 */
625 public void setDrawingSupplier(DrawingSupplier supplier) {
626 this.drawingSupplier = supplier;
627 notifyListeners(new PlotChangeEvent(this));
628 }
629
630 /**
631 * Returns the background image that is used to fill the plot's background
632 * area.
633 *
634 * @return The image (possibly <code>null</code>).
635 *
636 * @see #setBackgroundImage(Image)
637 */
638 public Image getBackgroundImage() {
639 return this.backgroundImage;
640 }
641
642 /**
643 * Sets the background image for the plot and sends a
644 * {@link PlotChangeEvent} to all registered listeners.
645 *
646 * @param image the image (<code>null</code> permitted).
647 *
648 * @see #getBackgroundImage()
649 */
650 public void setBackgroundImage(Image image) {
651 this.backgroundImage = image;
652 notifyListeners(new PlotChangeEvent(this));
653 }
654
655 /**
656 * Returns the background image alignment. Alignment constants are defined
657 * in the <code>org.jfree.ui.Align</code> class in the JCommon class
658 * library.
659 *
660 * @return The alignment.
661 *
662 * @see #setBackgroundImageAlignment(int)
663 */
664 public int getBackgroundImageAlignment() {
665 return this.backgroundImageAlignment;
666 }
667
668 /**
669 * Sets the alignment for the background image and sends a
670 * {@link PlotChangeEvent} to all registered listeners. Alignment options
671 * are defined by the {@link org.jfree.ui.Align} class in the JCommon
672 * class library.
673 *
674 * @param alignment the alignment.
675 *
676 * @see #getBackgroundImageAlignment()
677 */
678 public void setBackgroundImageAlignment(int alignment) {
679 if (this.backgroundImageAlignment != alignment) {
680 this.backgroundImageAlignment = alignment;
681 notifyListeners(new PlotChangeEvent(this));
682 }
683 }
684
685 /**
686 * Returns the alpha transparency used to draw the background image. This
687 * is a value in the range 0.0f to 1.0f, where 0.0f is fully transparent
688 * and 1.0f is fully opaque.
689 *
690 * @return The alpha transparency.
691 *
692 * @see #setBackgroundImageAlpha(float)
693 */
694 public float getBackgroundImageAlpha() {
695 return this.backgroundImageAlpha;
696 }
697
698 /**
699 * Sets the alpha transparency used when drawing the background image.
700 *
701 * @param alpha the alpha transparency (in the range 0.0f to 1.0f, where
702 * 0.0f is fully transparent, and 1.0f is fully opaque).
703 *
704 * @throws IllegalArgumentException if <code>alpha</code> is not within
705 * the specified range.
706 *
707 * @see #getBackgroundImageAlpha()
708 */
709 public void setBackgroundImageAlpha(float alpha) {
710 if (alpha < 0.0f || alpha > 1.0f)
711 throw new IllegalArgumentException(
712 "The 'alpha' value must be in the range 0.0f to 1.0f.");
713 if (this.backgroundImageAlpha != alpha) {
714 this.backgroundImageAlpha = alpha;
715 this.notifyListeners(new PlotChangeEvent(this));
716 }
717 }
718
719 /**
720 * Returns the flag that controls whether or not the plot outline is
721 * drawn. The default value is <code>true</code>. Note that for
722 * historical reasons, the plot's outline paint and stroke can take on
723 * <code>null</code> values, in which case the outline will not be drawn
724 * even if this flag is set to <code>true</code>.
725 *
726 * @return The outline visibility flag.
727 *
728 * @since 1.0.6
729 *
730 * @see #setOutlineVisible(boolean)
731 */
732 public boolean isOutlineVisible() {
733 return this.outlineVisible;
734 }
735
736 /**
737 * Sets the flag that controls whether or not the plot's outline is
738 * drawn, and sends a {@link PlotChangeEvent} to all registered listeners.
739 *
740 * @param visible the new flag value.
741 *
742 * @since 1.0.6
743 *
744 * @see #isOutlineVisible()
745 */
746 public void setOutlineVisible(boolean visible) {
747 this.outlineVisible = visible;
748 notifyListeners(new PlotChangeEvent(this));
749 }
750
751 /**
752 * Returns the stroke used to outline the plot area.
753 *
754 * @return The stroke (possibly <code>null</code>).
755 *
756 * @see #setOutlineStroke(Stroke)
757 */
758 public Stroke getOutlineStroke() {
759 return this.outlineStroke;
760 }
761
762 /**
763 * Sets the stroke used to outline the plot area and sends a
764 * {@link PlotChangeEvent} to all registered listeners. If you set this
765 * attribute to <code>null</code>, no outline will be drawn.
766 *
767 * @param stroke the stroke (<code>null</code> permitted).
768 *
769 * @see #getOutlineStroke()
770 */
771 public void setOutlineStroke(Stroke stroke) {
772 if (stroke == null) {
773 if (this.outlineStroke != null) {
774 this.outlineStroke = null;
775 notifyListeners(new PlotChangeEvent(this));
776 }
777 }
778 else {
779 if (this.outlineStroke != null) {
780 if (this.outlineStroke.equals(stroke)) {
781 return; // nothing to do
782 }
783 }
784 this.outlineStroke = stroke;
785 notifyListeners(new PlotChangeEvent(this));
786 }
787 }
788
789 /**
790 * Returns the color used to draw the outline of the plot area.
791 *
792 * @return The color (possibly <code>null<code>).
793 *
794 * @see #setOutlinePaint(Paint)
795 */
796 public Paint getOutlinePaint() {
797 return this.outlinePaint;
798 }
799
800 /**
801 * Sets the paint used to draw the outline of the plot area and sends a
802 * {@link PlotChangeEvent} to all registered listeners. If you set this
803 * attribute to <code>null</code>, no outline will be drawn.
804 *
805 * @param paint the paint (<code>null</code> permitted).
806 *
807 * @see #getOutlinePaint()
808 */
809 public void setOutlinePaint(Paint paint) {
810 if (paint == null) {
811 if (this.outlinePaint != null) {
812 this.outlinePaint = null;
813 notifyListeners(new PlotChangeEvent(this));
814 }
815 }
816 else {
817 if (this.outlinePaint != null) {
818 if (this.outlinePaint.equals(paint)) {
819 return; // nothing to do
820 }
821 }
822 this.outlinePaint = paint;
823 notifyListeners(new PlotChangeEvent(this));
824 }
825 }
826
827 /**
828 * Returns the alpha-transparency for the plot foreground.
829 *
830 * @return The alpha-transparency.
831 *
832 * @see #setForegroundAlpha(float)
833 */
834 public float getForegroundAlpha() {
835 return this.foregroundAlpha;
836 }
837
838 /**
839 * Sets the alpha-transparency for the plot and sends a
840 * {@link PlotChangeEvent} to all registered listeners.
841 *
842 * @param alpha the new alpha transparency.
843 *
844 * @see #getForegroundAlpha()
845 */
846 public void setForegroundAlpha(float alpha) {
847 if (this.foregroundAlpha != alpha) {
848 this.foregroundAlpha = alpha;
849 notifyListeners(new PlotChangeEvent(this));
850 }
851 }
852
853 /**
854 * Returns the legend items for the plot. By default, this method returns
855 * <code>null</code>. Subclasses should override to return a
856 * {@link LegendItemCollection}.
857 *
858 * @return The legend items for the plot (possibly <code>null</code>).
859 */
860 public LegendItemCollection getLegendItems() {
861 return null;
862 }
863
864 /**
865 * Registers an object for notification of changes to the plot.
866 *
867 * @param listener the object to be registered.
868 *
869 * @see #removeChangeListener(PlotChangeListener)
870 */
871 public void addChangeListener(PlotChangeListener listener) {
872 this.listenerList.add(PlotChangeListener.class, listener);
873 }
874
875 /**
876 * Unregisters an object for notification of changes to the plot.
877 *
878 * @param listener the object to be unregistered.
879 *
880 * @see #addChangeListener(PlotChangeListener)
881 */
882 public void removeChangeListener(PlotChangeListener listener) {
883 this.listenerList.remove(PlotChangeListener.class, listener);
884 }
885
886 /**
887 * Notifies all registered listeners that the plot has been modified.
888 *
889 * @param event information about the change event.
890 */
891 public void notifyListeners(PlotChangeEvent event) {
892 Object[] listeners = this.listenerList.getListenerList();
893 for (int i = listeners.length - 2; i >= 0; i -= 2) {
894 if (listeners[i] == PlotChangeListener.class) {
895 ((PlotChangeListener) listeners[i + 1]).plotChanged(event);
896 }
897 }
898 }
899
900 /**
901 * Draws the plot within the specified area. The anchor is a point on the
902 * chart that is specified externally (for instance, it may be the last
903 * point of the last mouse click performed by the user) - plots can use or
904 * ignore this value as they see fit.
905 * <br><br>
906 * Subclasses need to provide an implementation of this method, obviously.
907 *
908 * @param g2 the graphics device.
909 * @param area the plot area.
910 * @param anchor the anchor point (<code>null</code> permitted).
911 * @param parentState the parent state (if any).
912 * @param info carries back plot rendering info.
913 */
914 public abstract void draw(Graphics2D g2,
915 Rectangle2D area,
916 Point2D anchor,
917 PlotState parentState,
918 PlotRenderingInfo info);
919
920 /**
921 * Draws the plot background (the background color and/or image).
922 * <P>
923 * This method will be called during the chart drawing process and is
924 * declared public so that it can be accessed by the renderers used by
925 * certain subclasses. You shouldn't need to call this method directly.
926 *
927 * @param g2 the graphics device.
928 * @param area the area within which the plot should be drawn.
929 */
930 public void drawBackground(Graphics2D g2, Rectangle2D area) {
931 // some subclasses override this method completely, so don't put
932 // anything here that *must* be done
933 fillBackground(g2, area);
934 drawBackgroundImage(g2, area);
935 }
936
937 /**
938 * Fills the specified area with the background paint.
939 *
940 * @param g2 the graphics device.
941 * @param area the area.
942 *
943 * @see #getBackgroundPaint()
944 * @see #getBackgroundAlpha()
945 * @see #fillBackground(Graphics2D, Rectangle2D, PlotOrientation)
946 */
947 protected void fillBackground(Graphics2D g2, Rectangle2D area) {
948 fillBackground(g2, area, PlotOrientation.VERTICAL);
949 }
950
951 /**
952 * Fills the specified area with the background paint. If the background
953 * paint is an instance of <code>GradientPaint</code>, the gradient will
954 * run in the direction suggested by the plot's orientation.
955 *
956 * @param g2 the graphics target.
957 * @param area the plot area.
958 * @param orientation the plot orientation (<code>null</code> not
959 * permitted).
960 *
961 * @since 1.0.6
962 */
963 protected void fillBackground(Graphics2D g2, Rectangle2D area,
964 PlotOrientation orientation) {
965 if (orientation == null) {
966 throw new IllegalArgumentException("Null 'orientation' argument.");
967 }
968 if (this.backgroundPaint == null) {
969 return;
970 }
971 Paint p = this.backgroundPaint;
972 if (p instanceof GradientPaint) {
973 GradientPaint gp = (GradientPaint) p;
974 if (orientation == PlotOrientation.VERTICAL) {
975 p = new GradientPaint((float) area.getCenterX(),
976 (float) area.getMaxY(), gp.getColor1(),
977 (float) area.getCenterX(), (float) area.getMinY(),
978 gp.getColor2());
979 }
980 else if (orientation == PlotOrientation.HORIZONTAL) {
981 p = new GradientPaint((float) area.getMinX(),
982 (float) area.getCenterY(), gp.getColor1(),
983 (float) area.getMaxX(), (float) area.getCenterY(),
984 gp.getColor2());
985 }
986 }
987 Composite originalComposite = g2.getComposite();
988 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
989 this.backgroundAlpha));
990 g2.setPaint(p);
991 g2.fill(area);
992 g2.setComposite(originalComposite);
993 }
994
995 /**
996 * Draws the background image (if there is one) aligned within the
997 * specified area.
998 *
999 * @param g2 the graphics device.
1000 * @param area the area.
1001 *
1002 * @see #getBackgroundImage()
1003 * @see #getBackgroundImageAlignment()
1004 * @see #getBackgroundImageAlpha()
1005 */
1006 public void drawBackgroundImage(Graphics2D g2, Rectangle2D area) {
1007 if (this.backgroundImage != null) {
1008 Composite originalComposite = g2.getComposite();
1009 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
1010 this.backgroundImageAlpha));
1011 Rectangle2D dest = new Rectangle2D.Double(0.0, 0.0,
1012 this.backgroundImage.getWidth(null),
1013 this.backgroundImage.getHeight(null));
1014 Align.align(dest, area, this.backgroundImageAlignment);
1015 g2.drawImage(this.backgroundImage, (int) dest.getX(),
1016 (int) dest.getY(), (int) dest.getWidth() + 1,
1017 (int) dest.getHeight() + 1, null);
1018 g2.setComposite(originalComposite);
1019 }
1020 }
1021
1022 /**
1023 * Draws the plot outline. This method will be called during the chart
1024 * drawing process and is declared public so that it can be accessed by the
1025 * renderers used by certain subclasses. You shouldn't need to call this
1026 * method directly.
1027 *
1028 * @param g2 the graphics device.
1029 * @param area the area within which the plot should be drawn.
1030 */
1031 public void drawOutline(Graphics2D g2, Rectangle2D area) {
1032 if (!this.outlineVisible) {
1033 return;
1034 }
1035 if ((this.outlineStroke != null) && (this.outlinePaint != null)) {
1036 g2.setStroke(this.outlineStroke);
1037 g2.setPaint(this.outlinePaint);
1038 g2.draw(area);
1039 }
1040 }
1041
1042 /**
1043 * Draws a message to state that there is no data to plot.
1044 *
1045 * @param g2 the graphics device.
1046 * @param area the area within which the plot should be drawn.
1047 */
1048 protected void drawNoDataMessage(Graphics2D g2, Rectangle2D area) {
1049 Shape savedClip = g2.getClip();
1050 g2.clip(area);
1051 String message = this.noDataMessage;
1052 if (message != null) {
1053 g2.setFont(this.noDataMessageFont);
1054 g2.setPaint(this.noDataMessagePaint);
1055 TextBlock block = TextUtilities.createTextBlock(
1056 this.noDataMessage, this.noDataMessageFont,
1057 this.noDataMessagePaint, 0.9f * (float) area.getWidth(),
1058 new G2TextMeasurer(g2));
1059 block.draw(g2, (float) area.getCenterX(), (float) area.getCenterY(),
1060 TextBlockAnchor.CENTER);
1061 }
1062 g2.setClip(savedClip);
1063 }
1064
1065 /**
1066 * Handles a 'click' on the plot. Since the plot does not maintain any
1067 * information about where it has been drawn, the plot rendering info is
1068 * supplied as an argument.
1069 *
1070 * @param x the x coordinate (in Java2D space).
1071 * @param y the y coordinate (in Java2D space).
1072 * @param info an object containing information about the dimensions of
1073 * the plot.
1074 */
1075 public void handleClick(int x, int y, PlotRenderingInfo info) {
1076 // provides a 'no action' default
1077 }
1078
1079 /**
1080 * Performs a zoom on the plot. Subclasses should override if zooming is
1081 * appropriate for the type of plot.
1082 *
1083 * @param percent the zoom percentage.
1084 */
1085 public void zoom(double percent) {
1086 // do nothing by default.
1087 }
1088
1089 /**
1090 * Receives notification of a change to one of the plot's axes.
1091 *
1092 * @param event information about the event (not used here).
1093 */
1094 public void axisChanged(AxisChangeEvent event) {
1095 notifyListeners(new PlotChangeEvent(this));
1096 }
1097
1098 /**
1099 * Receives notification of a change to the plot's dataset.
1100 * <P>
1101 * The plot reacts by passing on a plot change event to all registered
1102 * listeners.
1103 *
1104 * @param event information about the event (not used here).
1105 */
1106 public void datasetChanged(DatasetChangeEvent event) {
1107 PlotChangeEvent newEvent = new PlotChangeEvent(this);
1108 newEvent.setType(ChartChangeEventType.DATASET_UPDATED);
1109 notifyListeners(newEvent);
1110 }
1111
1112 /**
1113 * Receives notification of a change to a marker that is assigned to the
1114 * plot.
1115 *
1116 * @param event the event.
1117 *
1118 * @since 1.0.3
1119 */
1120 public void markerChanged(MarkerChangeEvent event) {
1121 notifyListeners(new PlotChangeEvent(this));
1122 }
1123
1124 /**
1125 * Adjusts the supplied x-value.
1126 *
1127 * @param x the x-value.
1128 * @param w1 width 1.
1129 * @param w2 width 2.
1130 * @param edge the edge (left or right).
1131 *
1132 * @return The adjusted x-value.
1133 */
1134 protected double getRectX(double x, double w1, double w2,
1135 RectangleEdge edge) {
1136
1137 double result = x;
1138 if (edge == RectangleEdge.LEFT) {
1139 result = result + w1;
1140 }
1141 else if (edge == RectangleEdge.RIGHT) {
1142 result = result + w2;
1143 }
1144 return result;
1145
1146 }
1147
1148 /**
1149 * Adjusts the supplied y-value.
1150 *
1151 * @param y the x-value.
1152 * @param h1 height 1.
1153 * @param h2 height 2.
1154 * @param edge the edge (top or bottom).
1155 *
1156 * @return The adjusted y-value.
1157 */
1158 protected double getRectY(double y, double h1, double h2,
1159 RectangleEdge edge) {
1160
1161 double result = y;
1162 if (edge == RectangleEdge.TOP) {
1163 result = result + h1;
1164 }
1165 else if (edge == RectangleEdge.BOTTOM) {
1166 result = result + h2;
1167 }
1168 return result;
1169
1170 }
1171
1172 /**
1173 * Tests this plot for equality with another object.
1174 *
1175 * @param obj the object (<code>null</code> permitted).
1176 *
1177 * @return <code>true</code> or <code>false</code>.
1178 */
1179 public boolean equals(Object obj) {
1180 if (obj == this) {
1181 return true;
1182 }
1183 if (!(obj instanceof Plot)) {
1184 return false;
1185 }
1186 Plot that = (Plot) obj;
1187 if (!ObjectUtilities.equal(this.noDataMessage, that.noDataMessage)) {
1188 return false;
1189 }
1190 if (!ObjectUtilities.equal(
1191 this.noDataMessageFont, that.noDataMessageFont
1192 )) {
1193 return false;
1194 }
1195 if (!PaintUtilities.equal(this.noDataMessagePaint,
1196 that.noDataMessagePaint)) {
1197 return false;
1198 }
1199 if (!ObjectUtilities.equal(this.insets, that.insets)) {
1200 return false;
1201 }
1202 if (this.outlineVisible != that.outlineVisible) {
1203 return false;
1204 }
1205 if (!ObjectUtilities.equal(this.outlineStroke, that.outlineStroke)) {
1206 return false;
1207 }
1208 if (!PaintUtilities.equal(this.outlinePaint, that.outlinePaint)) {
1209 return false;
1210 }
1211 if (!PaintUtilities.equal(this.backgroundPaint, that.backgroundPaint)) {
1212 return false;
1213 }
1214 if (!ObjectUtilities.equal(this.backgroundImage,
1215 that.backgroundImage)) {
1216 return false;
1217 }
1218 if (this.backgroundImageAlignment != that.backgroundImageAlignment) {
1219 return false;
1220 }
1221 if (this.backgroundImageAlpha != that.backgroundImageAlpha) {
1222 return false;
1223 }
1224 if (this.foregroundAlpha != that.foregroundAlpha) {
1225 return false;
1226 }
1227 if (this.backgroundAlpha != that.backgroundAlpha) {
1228 return false;
1229 }
1230 if (!this.drawingSupplier.equals(that.drawingSupplier)) {
1231 return false;
1232 }
1233 return true;
1234 }
1235
1236 /**
1237 * Creates a clone of the plot.
1238 *
1239 * @return A clone.
1240 *
1241 * @throws CloneNotSupportedException if some component of the plot does not
1242 * support cloning.
1243 */
1244 public Object clone() throws CloneNotSupportedException {
1245
1246 Plot clone = (Plot) super.clone();
1247 // private Plot parent <-- don't clone the parent plot, but take care
1248 // childs in combined plots instead
1249 if (this.datasetGroup != null) {
1250 clone.datasetGroup
1251 = (DatasetGroup) ObjectUtilities.clone(this.datasetGroup);
1252 }
1253 clone.drawingSupplier
1254 = (DrawingSupplier) ObjectUtilities.clone(this.drawingSupplier);
1255 clone.listenerList = new EventListenerList();
1256 return clone;
1257
1258 }
1259
1260 /**
1261 * Provides serialization support.
1262 *
1263 * @param stream the output stream.
1264 *
1265 * @throws IOException if there is an I/O error.
1266 */
1267 private void writeObject(ObjectOutputStream stream) throws IOException {
1268 stream.defaultWriteObject();
1269 SerialUtilities.writePaint(this.noDataMessagePaint, stream);
1270 SerialUtilities.writeStroke(this.outlineStroke, stream);
1271 SerialUtilities.writePaint(this.outlinePaint, stream);
1272 // backgroundImage
1273 SerialUtilities.writePaint(this.backgroundPaint, stream);
1274 }
1275
1276 /**
1277 * Provides serialization support.
1278 *
1279 * @param stream the input stream.
1280 *
1281 * @throws IOException if there is an I/O error.
1282 * @throws ClassNotFoundException if there is a classpath problem.
1283 */
1284 private void readObject(ObjectInputStream stream)
1285 throws IOException, ClassNotFoundException {
1286 stream.defaultReadObject();
1287 this.noDataMessagePaint = SerialUtilities.readPaint(stream);
1288 this.outlineStroke = SerialUtilities.readStroke(stream);
1289 this.outlinePaint = SerialUtilities.readPaint(stream);
1290 // backgroundImage
1291 this.backgroundPaint = SerialUtilities.readPaint(stream);
1292
1293 this.listenerList = new EventListenerList();
1294
1295 }
1296
1297 /**
1298 * Resolves a domain axis location for a given plot orientation.
1299 *
1300 * @param location the location (<code>null</code> not permitted).
1301 * @param orientation the orientation (<code>null</code> not permitted).
1302 *
1303 * @return The edge (never <code>null</code>).
1304 */
1305 public static RectangleEdge resolveDomainAxisLocation(
1306 AxisLocation location, PlotOrientation orientation) {
1307
1308 if (location == null) {
1309 throw new IllegalArgumentException("Null 'location' argument.");
1310 }
1311 if (orientation == null) {
1312 throw new IllegalArgumentException("Null 'orientation' argument.");
1313 }
1314
1315 RectangleEdge result = null;
1316
1317 if (location == AxisLocation.TOP_OR_RIGHT) {
1318 if (orientation == PlotOrientation.HORIZONTAL) {
1319 result = RectangleEdge.RIGHT;
1320 }
1321 else if (orientation == PlotOrientation.VERTICAL) {
1322 result = RectangleEdge.TOP;
1323 }
1324 }
1325 else if (location == AxisLocation.TOP_OR_LEFT) {
1326 if (orientation == PlotOrientation.HORIZONTAL) {
1327 result = RectangleEdge.LEFT;
1328 }
1329 else if (orientation == PlotOrientation.VERTICAL) {
1330 result = RectangleEdge.TOP;
1331 }
1332 }
1333 else if (location == AxisLocation.BOTTOM_OR_RIGHT) {
1334 if (orientation == PlotOrientation.HORIZONTAL) {
1335 result = RectangleEdge.RIGHT;
1336 }
1337 else if (orientation == PlotOrientation.VERTICAL) {
1338 result = RectangleEdge.BOTTOM;
1339 }
1340 }
1341 else if (location == AxisLocation.BOTTOM_OR_LEFT) {
1342 if (orientation == PlotOrientation.HORIZONTAL) {
1343 result = RectangleEdge.LEFT;
1344 }
1345 else if (orientation == PlotOrientation.VERTICAL) {
1346 result = RectangleEdge.BOTTOM;
1347 }
1348 }
1349 // the above should cover all the options...
1350 if (result == null) {
1351 throw new IllegalStateException("resolveDomainAxisLocation()");
1352 }
1353 return result;
1354
1355 }
1356
1357 /**
1358 * Resolves a range axis location for a given plot orientation.
1359 *
1360 * @param location the location (<code>null</code> not permitted).
1361 * @param orientation the orientation (<code>null</code> not permitted).
1362 *
1363 * @return The edge (never <code>null</code>).
1364 */
1365 public static RectangleEdge resolveRangeAxisLocation(
1366 AxisLocation location, PlotOrientation orientation) {
1367
1368 if (location == null) {
1369 throw new IllegalArgumentException("Null 'location' argument.");
1370 }
1371 if (orientation == null) {
1372 throw new IllegalArgumentException("Null 'orientation' argument.");
1373 }
1374
1375 RectangleEdge result = null;
1376
1377 if (location == AxisLocation.TOP_OR_RIGHT) {
1378 if (orientation == PlotOrientation.HORIZONTAL) {
1379 result = RectangleEdge.TOP;
1380 }
1381 else if (orientation == PlotOrientation.VERTICAL) {
1382 result = RectangleEdge.RIGHT;
1383 }
1384 }
1385 else if (location == AxisLocation.TOP_OR_LEFT) {
1386 if (orientation == PlotOrientation.HORIZONTAL) {
1387 result = RectangleEdge.TOP;
1388 }
1389 else if (orientation == PlotOrientation.VERTICAL) {
1390 result = RectangleEdge.LEFT;
1391 }
1392 }
1393 else if (location == AxisLocation.BOTTOM_OR_RIGHT) {
1394 if (orientation == PlotOrientation.HORIZONTAL) {
1395 result = RectangleEdge.BOTTOM;
1396 }
1397 else if (orientation == PlotOrientation.VERTICAL) {
1398 result = RectangleEdge.RIGHT;
1399 }
1400 }
1401 else if (location == AxisLocation.BOTTOM_OR_LEFT) {
1402 if (orientation == PlotOrientation.HORIZONTAL) {
1403 result = RectangleEdge.BOTTOM;
1404 }
1405 else if (orientation == PlotOrientation.VERTICAL) {
1406 result = RectangleEdge.LEFT;
1407 }
1408 }
1409
1410 // the above should cover all the options...
1411 if (result == null) {
1412 throw new IllegalStateException("resolveRangeAxisLocation()");
1413 }
1414 return result;
1415
1416 }
1417
1418 }