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