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