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.6 2007/01/11 11:32:57 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 *
122 */
123
124 package org.jfree.chart.plot;
125
126 import java.awt.AlphaComposite;
127 import java.awt.BasicStroke;
128 import java.awt.Color;
129 import java.awt.Composite;
130 import java.awt.Font;
131 import java.awt.Graphics2D;
132 import java.awt.Image;
133 import java.awt.Paint;
134 import java.awt.Shape;
135 import java.awt.Stroke;
136 import java.awt.geom.Ellipse2D;
137 import java.awt.geom.Point2D;
138 import java.awt.geom.Rectangle2D;
139 import java.io.IOException;
140 import java.io.ObjectInputStream;
141 import java.io.ObjectOutputStream;
142 import java.io.Serializable;
143
144 import javax.swing.event.EventListenerList;
145
146 import org.jfree.chart.LegendItemCollection;
147 import org.jfree.chart.LegendItemSource;
148 import org.jfree.chart.axis.AxisLocation;
149 import org.jfree.chart.event.AxisChangeEvent;
150 import org.jfree.chart.event.AxisChangeListener;
151 import org.jfree.chart.event.ChartChangeEventType;
152 import org.jfree.chart.event.MarkerChangeEvent;
153 import org.jfree.chart.event.MarkerChangeListener;
154 import org.jfree.chart.event.PlotChangeEvent;
155 import org.jfree.chart.event.PlotChangeListener;
156 import org.jfree.data.general.DatasetChangeEvent;
157 import org.jfree.data.general.DatasetChangeListener;
158 import org.jfree.data.general.DatasetGroup;
159 import org.jfree.io.SerialUtilities;
160 import org.jfree.text.G2TextMeasurer;
161 import org.jfree.text.TextBlock;
162 import org.jfree.text.TextBlockAnchor;
163 import org.jfree.text.TextUtilities;
164 import org.jfree.ui.Align;
165 import org.jfree.ui.RectangleEdge;
166 import org.jfree.ui.RectangleInsets;
167 import org.jfree.util.ObjectUtilities;
168 import org.jfree.util.PaintUtilities;
169 import org.jfree.util.PublicCloneable;
170
171 /**
172 * The base class for all plots in JFreeChart. The
173 * {@link org.jfree.chart.JFreeChart} class delegates the drawing of axes and
174 * data to the plot. This base class provides facilities common to most plot
175 * types.
176 */
177 public abstract class Plot implements AxisChangeListener,
178 DatasetChangeListener,
179 MarkerChangeListener,
180 LegendItemSource,
181 PublicCloneable,
182 Cloneable,
183 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 /** The Stroke used to draw an outline around the plot. */
243 private transient Stroke outlineStroke;
244
245 /** The Paint used to draw an outline around the plot. */
246 private transient Paint outlinePaint;
247
248 /** An optional color used to fill the plot background. */
249 private transient Paint backgroundPaint;
250
251 /** An optional image for the plot background. */
252 private transient Image backgroundImage; // not currently serialized
253
254 /** The alignment for the background image. */
255 private int backgroundImageAlignment = Align.FIT;
256
257 /** The alpha value used to draw the background image. */
258 private float backgroundImageAlpha = 0.5f;
259
260 /** The alpha-transparency for the plot. */
261 private float foregroundAlpha;
262
263 /** The alpha transparency for the background paint. */
264 private float backgroundAlpha;
265
266 /** The drawing supplier. */
267 private DrawingSupplier drawingSupplier;
268
269 /** Storage for registered change listeners. */
270 private transient EventListenerList listenerList;
271
272 /**
273 * Creates a new plot.
274 */
275 protected Plot() {
276
277 this.parent = null;
278 this.insets = DEFAULT_INSETS;
279 this.backgroundPaint = DEFAULT_BACKGROUND_PAINT;
280 this.backgroundAlpha = DEFAULT_BACKGROUND_ALPHA;
281 this.backgroundImage = null;
282 this.outlineStroke = DEFAULT_OUTLINE_STROKE;
283 this.outlinePaint = DEFAULT_OUTLINE_PAINT;
284 this.foregroundAlpha = DEFAULT_FOREGROUND_ALPHA;
285
286 this.noDataMessage = null;
287 this.noDataMessageFont = new Font("SansSerif", Font.PLAIN, 12);
288 this.noDataMessagePaint = Color.black;
289
290 this.drawingSupplier = new DefaultDrawingSupplier();
291
292 this.listenerList = new EventListenerList();
293
294 }
295
296 /**
297 * Returns the dataset group for the plot (not currently used).
298 *
299 * @return The dataset group.
300 *
301 * @see #setDatasetGroup(DatasetGroup)
302 */
303 public DatasetGroup getDatasetGroup() {
304 return this.datasetGroup;
305 }
306
307 /**
308 * Sets the dataset group (not currently used).
309 *
310 * @param group the dataset group (<code>null</code> permitted).
311 *
312 * @see #getDatasetGroup()
313 */
314 protected void setDatasetGroup(DatasetGroup group) {
315 this.datasetGroup = group;
316 }
317
318 /**
319 * Returns the string that is displayed when the dataset is empty or
320 * <code>null</code>.
321 *
322 * @return The 'no data' message (<code>null</code> possible).
323 *
324 * @see #setNoDataMessage(String)
325 * @see #getNoDataMessageFont()
326 * @see #getNoDataMessagePaint()
327 */
328 public String getNoDataMessage() {
329 return this.noDataMessage;
330 }
331
332 /**
333 * Sets the message that is displayed when the dataset is empty or
334 * <code>null</code>, and sends a {@link PlotChangeEvent} to all registered
335 * listeners.
336 *
337 * @param message the message (<code>null</code> permitted).
338 *
339 * @see #getNoDataMessage()
340 */
341 public void setNoDataMessage(String message) {
342 this.noDataMessage = message;
343 notifyListeners(new PlotChangeEvent(this));
344 }
345
346 /**
347 * Returns the font used to display the 'no data' message.
348 *
349 * @return The font (never <code>null</code>).
350 *
351 * @see #setNoDataMessageFont(Font)
352 * @see #getNoDataMessage()
353 */
354 public Font getNoDataMessageFont() {
355 return this.noDataMessageFont;
356 }
357
358 /**
359 * Sets the font used to display the 'no data' message and sends a
360 * {@link PlotChangeEvent} to all registered listeners.
361 *
362 * @param font the font (<code>null</code> not permitted).
363 *
364 * @see #getNoDataMessageFont()
365 */
366 public void setNoDataMessageFont(Font font) {
367 if (font == null) {
368 throw new IllegalArgumentException("Null 'font' argument.");
369 }
370 this.noDataMessageFont = font;
371 notifyListeners(new PlotChangeEvent(this));
372 }
373
374 /**
375 * Returns the paint used to display the 'no data' message.
376 *
377 * @return The paint (never <code>null</code>).
378 *
379 * @see #setNoDataMessagePaint(Paint)
380 * @see #getNoDataMessage()
381 */
382 public Paint getNoDataMessagePaint() {
383 return this.noDataMessagePaint;
384 }
385
386 /**
387 * Sets the paint used to display the 'no data' message and sends a
388 * {@link PlotChangeEvent} to all registered listeners.
389 *
390 * @param paint the paint (<code>null</code> not permitted).
391 *
392 * @see #getNoDataMessagePaint()
393 */
394 public void setNoDataMessagePaint(Paint paint) {
395 if (paint == null) {
396 throw new IllegalArgumentException("Null 'paint' argument.");
397 }
398 this.noDataMessagePaint = paint;
399 notifyListeners(new PlotChangeEvent(this));
400 }
401
402 /**
403 * Returns a short string describing the plot type.
404 * <P>
405 * Note: this gets used in the chart property editing user interface,
406 * but there needs to be a better mechanism for identifying the plot type.
407 *
408 * @return A short string describing the plot type (never
409 * <code>null</code>).
410 */
411 public abstract String getPlotType();
412
413 /**
414 * Returns the parent plot (or <code>null</code> if this plot is not part
415 * of a combined plot).
416 *
417 * @return The parent plot.
418 *
419 * @see #setParent(Plot)
420 * @see #getRootPlot()
421 */
422 public Plot getParent() {
423 return this.parent;
424 }
425
426 /**
427 * Sets the parent plot. This method is intended for internal use, you
428 * shouldn't need to call it directly.
429 *
430 * @param parent the parent plot (<code>null</code> permitted).
431 *
432 * @see #getParent()
433 */
434 public void setParent(Plot parent) {
435 this.parent = parent;
436 }
437
438 /**
439 * Returns the root plot.
440 *
441 * @return The root plot.
442 *
443 * @see #getParent()
444 */
445 public Plot getRootPlot() {
446
447 Plot p = getParent();
448 if (p == null) {
449 return this;
450 }
451 else {
452 return p.getRootPlot();
453 }
454
455 }
456
457 /**
458 * Returns <code>true</code> if this plot is part of a combined plot
459 * structure (that is, {@link #getParent()} returns a non-<code>null</code>
460 * value), and <code>false</code> otherwise.
461 *
462 * @return <code>true</code> if this plot is part of a combined plot
463 * structure.
464 *
465 * @see #getParent()
466 */
467 public boolean isSubplot() {
468 return (getParent() != null);
469 }
470
471 /**
472 * Returns the insets for the plot area.
473 *
474 * @return The insets (never <code>null</code>).
475 *
476 * @see #setInsets(RectangleInsets)
477 */
478 public RectangleInsets getInsets() {
479 return this.insets;
480 }
481
482 /**
483 * Sets the insets for the plot and sends a {@link PlotChangeEvent} to
484 * all registered listeners.
485 *
486 * @param insets the new insets (<code>null</code> not permitted).
487 *
488 * @see #getInsets()
489 * @see #setInsets(RectangleInsets, boolean)
490 */
491 public void setInsets(RectangleInsets insets) {
492 setInsets(insets, true);
493 }
494
495 /**
496 * Sets the insets for the plot and, if requested, and sends a
497 * {@link PlotChangeEvent} to all registered listeners.
498 *
499 * @param insets the new insets (<code>null</code> not permitted).
500 * @param notify a flag that controls whether the registered listeners are
501 * notified.
502 *
503 * @see #getInsets()
504 * @see #setInsets(RectangleInsets)
505 */
506 public void setInsets(RectangleInsets insets, boolean notify) {
507 if (insets == null) {
508 throw new IllegalArgumentException("Null 'insets' argument.");
509 }
510 if (!this.insets.equals(insets)) {
511 this.insets = insets;
512 if (notify) {
513 notifyListeners(new PlotChangeEvent(this));
514 }
515 }
516
517 }
518
519 /**
520 * Returns the background color of the plot area.
521 *
522 * @return The paint (possibly <code>null</code>).
523 *
524 * @see #setBackgroundPaint(Paint)
525 */
526 public Paint getBackgroundPaint() {
527 return this.backgroundPaint;
528 }
529
530 /**
531 * Sets the background color of the plot area and sends a
532 * {@link PlotChangeEvent} to all registered listeners.
533 *
534 * @param paint the paint (<code>null</code> permitted).
535 *
536 * @see #getBackgroundPaint()
537 */
538 public void setBackgroundPaint(Paint paint) {
539
540 if (paint == null) {
541 if (this.backgroundPaint != null) {
542 this.backgroundPaint = null;
543 notifyListeners(new PlotChangeEvent(this));
544 }
545 }
546 else {
547 if (this.backgroundPaint != null) {
548 if (this.backgroundPaint.equals(paint)) {
549 return; // nothing to do
550 }
551 }
552 this.backgroundPaint = paint;
553 notifyListeners(new PlotChangeEvent(this));
554 }
555
556 }
557
558 /**
559 * Returns the alpha transparency of the plot area background.
560 *
561 * @return The alpha transparency.
562 *
563 * @see #setBackgroundAlpha(float)
564 */
565 public float getBackgroundAlpha() {
566 return this.backgroundAlpha;
567 }
568
569 /**
570 * Sets the alpha transparency of the plot area background, and notifies
571 * registered listeners that the plot has been modified.
572 *
573 * @param alpha the new alpha value (in the range 0.0f to 1.0f).
574 *
575 * @see #getBackgroundAlpha()
576 */
577 public void setBackgroundAlpha(float alpha) {
578 if (this.backgroundAlpha != alpha) {
579 this.backgroundAlpha = alpha;
580 notifyListeners(new PlotChangeEvent(this));
581 }
582 }
583
584 /**
585 * Returns the drawing supplier for the plot.
586 *
587 * @return The drawing supplier (possibly <code>null</code>).
588 *
589 * @see #setDrawingSupplier(DrawingSupplier)
590 */
591 public DrawingSupplier getDrawingSupplier() {
592 DrawingSupplier result = null;
593 Plot p = getParent();
594 if (p != null) {
595 result = p.getDrawingSupplier();
596 }
597 else {
598 result = this.drawingSupplier;
599 }
600 return result;
601 }
602
603 /**
604 * Sets the drawing supplier for the plot. The drawing supplier is
605 * responsible for supplying a limitless (possibly repeating) sequence of
606 * <code>Paint</code>, <code>Stroke</code> and <code>Shape</code> objects
607 * that the plot's renderer(s) can use to populate its (their) tables.
608 *
609 * @param supplier the new supplier.
610 *
611 * @see #getDrawingSupplier()
612 */
613 public void setDrawingSupplier(DrawingSupplier supplier) {
614 this.drawingSupplier = supplier;
615 notifyListeners(new PlotChangeEvent(this));
616 }
617
618 /**
619 * Returns the background image that is used to fill the plot's background
620 * area.
621 *
622 * @return The image (possibly <code>null</code>).
623 *
624 * @see #setBackgroundImage(Image)
625 */
626 public Image getBackgroundImage() {
627 return this.backgroundImage;
628 }
629
630 /**
631 * Sets the background image for the plot and sends a
632 * {@link PlotChangeEvent} to all registered listeners.
633 *
634 * @param image the image (<code>null</code> permitted).
635 *
636 * @see #getBackgroundImage()
637 */
638 public void setBackgroundImage(Image image) {
639 this.backgroundImage = image;
640 notifyListeners(new PlotChangeEvent(this));
641 }
642
643 /**
644 * Returns the background image alignment. Alignment constants are defined
645 * in the <code>org.jfree.ui.Align</code> class in the JCommon class
646 * library.
647 *
648 * @return The alignment.
649 *
650 * @see #setBackgroundImageAlignment(int)
651 */
652 public int getBackgroundImageAlignment() {
653 return this.backgroundImageAlignment;
654 }
655
656 /**
657 * Sets the alignment for the background image and sends a
658 * {@link PlotChangeEvent} to all registered listeners. Alignment options
659 * are defined by the {@link org.jfree.ui.Align} class in the JCommon
660 * class library.
661 *
662 * @param alignment the alignment.
663 *
664 * @see #getBackgroundImageAlignment()
665 */
666 public void setBackgroundImageAlignment(int alignment) {
667 if (this.backgroundImageAlignment != alignment) {
668 this.backgroundImageAlignment = alignment;
669 notifyListeners(new PlotChangeEvent(this));
670 }
671 }
672
673 /**
674 * Returns the alpha transparency used to draw the background image. This
675 * is a value in the range 0.0f to 1.0f, where 0.0f is fully transparent
676 * and 1.0f is fully opaque.
677 *
678 * @return The alpha transparency.
679 *
680 * @see #setBackgroundImageAlpha(float)
681 */
682 public float getBackgroundImageAlpha() {
683 return this.backgroundImageAlpha;
684 }
685
686 /**
687 * Sets the alpha transparency used when drawing the background image.
688 *
689 * @param alpha the alpha transparency (in the range 0.0f to 1.0f, where
690 * 0.0f is fully transparent, and 1.0f is fully opaque).
691 *
692 * @throws IllegalArgumentException if <code>alpha</code> is not within
693 * the specified range.
694 *
695 * @see #getBackgroundImageAlpha()
696 */
697 public void setBackgroundImageAlpha(float alpha) {
698 if (alpha < 0.0f || alpha > 1.0f)
699 throw new IllegalArgumentException(
700 "The 'alpha' value must be in the range 0.0f to 1.0f.");
701 if (this.backgroundImageAlpha != alpha) {
702 this.backgroundImageAlpha = alpha;
703 this.notifyListeners(new PlotChangeEvent(this));
704 }
705 }
706
707 /**
708 * Returns the stroke used to outline the plot area.
709 *
710 * @return The stroke (possibly <code>null</code>).
711 *
712 * @see #setOutlineStroke(Stroke)
713 */
714 public Stroke getOutlineStroke() {
715 return this.outlineStroke;
716 }
717
718 /**
719 * Sets the stroke used to outline the plot area and sends a
720 * {@link PlotChangeEvent} to all registered listeners. If you set this
721 * attribute to <code>null</code>, no outline will be drawn.
722 *
723 * @param stroke the stroke (<code>null</code> permitted).
724 *
725 * @see #getOutlineStroke()
726 */
727 public void setOutlineStroke(Stroke stroke) {
728 if (stroke == null) {
729 if (this.outlineStroke != null) {
730 this.outlineStroke = null;
731 notifyListeners(new PlotChangeEvent(this));
732 }
733 }
734 else {
735 if (this.outlineStroke != null) {
736 if (this.outlineStroke.equals(stroke)) {
737 return; // nothing to do
738 }
739 }
740 this.outlineStroke = stroke;
741 notifyListeners(new PlotChangeEvent(this));
742 }
743 }
744
745 /**
746 * Returns the color used to draw the outline of the plot area.
747 *
748 * @return The color (possibly <code>null<code>).
749 *
750 * @see #setOutlinePaint(Paint)
751 */
752 public Paint getOutlinePaint() {
753 return this.outlinePaint;
754 }
755
756 /**
757 * Sets the paint used to draw the outline of the plot area and sends a
758 * {@link PlotChangeEvent} to all registered listeners. If you set this
759 * attribute to <code>null</code>, no outline will be drawn.
760 *
761 * @param paint the paint (<code>null</code> permitted).
762 *
763 * @see #getOutlinePaint()
764 */
765 public void setOutlinePaint(Paint paint) {
766 if (paint == null) {
767 if (this.outlinePaint != null) {
768 this.outlinePaint = null;
769 notifyListeners(new PlotChangeEvent(this));
770 }
771 }
772 else {
773 if (this.outlinePaint != null) {
774 if (this.outlinePaint.equals(paint)) {
775 return; // nothing to do
776 }
777 }
778 this.outlinePaint = paint;
779 notifyListeners(new PlotChangeEvent(this));
780 }
781 }
782
783 /**
784 * Returns the alpha-transparency for the plot foreground.
785 *
786 * @return The alpha-transparency.
787 *
788 * @see #setForegroundAlpha(float)
789 */
790 public float getForegroundAlpha() {
791 return this.foregroundAlpha;
792 }
793
794 /**
795 * Sets the alpha-transparency for the plot and sends a
796 * {@link PlotChangeEvent} to all registered listeners.
797 *
798 * @param alpha the new alpha transparency.
799 *
800 * @see #getForegroundAlpha()
801 */
802 public void setForegroundAlpha(float alpha) {
803 if (this.foregroundAlpha != alpha) {
804 this.foregroundAlpha = alpha;
805 notifyListeners(new PlotChangeEvent(this));
806 }
807 }
808
809 /**
810 * Returns the legend items for the plot. By default, this method returns
811 * <code>null</code>. Subclasses should override to return a
812 * {@link LegendItemCollection}.
813 *
814 * @return The legend items for the plot (possibly <code>null</code>).
815 */
816 public LegendItemCollection getLegendItems() {
817 return null;
818 }
819
820 /**
821 * Registers an object for notification of changes to the plot.
822 *
823 * @param listener the object to be registered.
824 *
825 * @see #removeChangeListener(PlotChangeListener)
826 */
827 public void addChangeListener(PlotChangeListener listener) {
828 this.listenerList.add(PlotChangeListener.class, listener);
829 }
830
831 /**
832 * Unregisters an object for notification of changes to the plot.
833 *
834 * @param listener the object to be unregistered.
835 *
836 * @see #addChangeListener(PlotChangeListener)
837 */
838 public void removeChangeListener(PlotChangeListener listener) {
839 this.listenerList.remove(PlotChangeListener.class, listener);
840 }
841
842 /**
843 * Notifies all registered listeners that the plot has been modified.
844 *
845 * @param event information about the change event.
846 */
847 public void notifyListeners(PlotChangeEvent event) {
848 Object[] listeners = this.listenerList.getListenerList();
849 for (int i = listeners.length - 2; i >= 0; i -= 2) {
850 if (listeners[i] == PlotChangeListener.class) {
851 ((PlotChangeListener) listeners[i + 1]).plotChanged(event);
852 }
853 }
854 }
855
856 /**
857 * Draws the plot within the specified area. The anchor is a point on the
858 * chart that is specified externally (for instance, it may be the last
859 * point of the last mouse click performed by the user) - plots can use or
860 * ignore this value as they see fit.
861 * <br><br>
862 * Subclasses need to provide an implementation of this method, obviously.
863 *
864 * @param g2 the graphics device.
865 * @param area the plot area.
866 * @param anchor the anchor point (<code>null</code> permitted).
867 * @param parentState the parent state (if any).
868 * @param info carries back plot rendering info.
869 */
870 public abstract void draw(Graphics2D g2,
871 Rectangle2D area,
872 Point2D anchor,
873 PlotState parentState,
874 PlotRenderingInfo info);
875
876 /**
877 * Draws the plot background (the background color and/or image).
878 * <P>
879 * This method will be called during the chart drawing process and is
880 * declared public so that it can be accessed by the renderers used by
881 * certain subclasses. You shouldn't need to call this method directly.
882 *
883 * @param g2 the graphics device.
884 * @param area the area within which the plot should be drawn.
885 */
886 public void drawBackground(Graphics2D g2, Rectangle2D area) {
887 fillBackground(g2, area);
888 drawBackgroundImage(g2, area);
889 }
890
891 /**
892 * Fills the specified area with the background paint.
893 *
894 * @param g2 the graphics device.
895 * @param area the area.
896 *
897 * @see #getBackgroundPaint()
898 * @see #getBackgroundAlpha()
899 */
900 protected void fillBackground(Graphics2D g2, Rectangle2D area) {
901 if (this.backgroundPaint != null) {
902 Composite originalComposite = g2.getComposite();
903 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
904 this.backgroundAlpha));
905 g2.setPaint(this.backgroundPaint);
906 g2.fill(area);
907 g2.setComposite(originalComposite);
908 }
909 }
910
911 /**
912 * Draws the background image (if there is one) aligned within the
913 * specified area.
914 *
915 * @param g2 the graphics device.
916 * @param area the area.
917 *
918 * @see #getBackgroundImage()
919 * @see #getBackgroundImageAlignment()
920 * @see #getBackgroundImageAlpha()
921 */
922 protected void drawBackgroundImage(Graphics2D g2, Rectangle2D area) {
923 if (this.backgroundImage != null) {
924 Composite originalComposite = g2.getComposite();
925 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
926 this.backgroundImageAlpha));
927 Rectangle2D dest = new Rectangle2D.Double(0.0, 0.0,
928 this.backgroundImage.getWidth(null),
929 this.backgroundImage.getHeight(null));
930 Align.align(dest, area, this.backgroundImageAlignment);
931 g2.drawImage(this.backgroundImage, (int) dest.getX(),
932 (int) dest.getY(), (int) dest.getWidth() + 1,
933 (int) dest.getHeight() + 1, null);
934 g2.setComposite(originalComposite);
935 }
936 }
937
938 /**
939 * Draws the plot outline. This method will be called during the chart
940 * drawing process and is declared public so that it can be accessed by the
941 * renderers used by certain subclasses. You shouldn't need to call this
942 * method directly.
943 *
944 * @param g2 the graphics device.
945 * @param area the area within which the plot should be drawn.
946 */
947 public void drawOutline(Graphics2D g2, Rectangle2D area) {
948 if ((this.outlineStroke != null) && (this.outlinePaint != null)) {
949 g2.setStroke(this.outlineStroke);
950 g2.setPaint(this.outlinePaint);
951 g2.draw(area);
952 }
953 }
954
955 /**
956 * Draws a message to state that there is no data to plot.
957 *
958 * @param g2 the graphics device.
959 * @param area the area within which the plot should be drawn.
960 */
961 protected void drawNoDataMessage(Graphics2D g2, Rectangle2D area) {
962 Shape savedClip = g2.getClip();
963 g2.clip(area);
964 String message = this.noDataMessage;
965 if (message != null) {
966 g2.setFont(this.noDataMessageFont);
967 g2.setPaint(this.noDataMessagePaint);
968 TextBlock block = TextUtilities.createTextBlock(
969 this.noDataMessage, this.noDataMessageFont,
970 this.noDataMessagePaint, 0.9f * (float) area.getWidth(),
971 new G2TextMeasurer(g2));
972 block.draw(g2, (float) area.getCenterX(), (float) area.getCenterY(),
973 TextBlockAnchor.CENTER);
974 }
975 g2.setClip(savedClip);
976 }
977
978 /**
979 * Handles a 'click' on the plot. Since the plot does not maintain any
980 * information about where it has been drawn, the plot rendering info is
981 * supplied as an argument.
982 *
983 * @param x the x coordinate (in Java2D space).
984 * @param y the y coordinate (in Java2D space).
985 * @param info an object containing information about the dimensions of
986 * the plot.
987 */
988 public void handleClick(int x, int y, PlotRenderingInfo info) {
989 // provides a 'no action' default
990 }
991
992 /**
993 * Performs a zoom on the plot. Subclasses should override if zooming is
994 * appropriate for the type of plot.
995 *
996 * @param percent the zoom percentage.
997 */
998 public void zoom(double percent) {
999 // do nothing by default.
1000 }
1001
1002 /**
1003 * Receives notification of a change to one of the plot's axes.
1004 *
1005 * @param event information about the event (not used here).
1006 */
1007 public void axisChanged(AxisChangeEvent event) {
1008 notifyListeners(new PlotChangeEvent(this));
1009 }
1010
1011 /**
1012 * Receives notification of a change to the plot's dataset.
1013 * <P>
1014 * The plot reacts by passing on a plot change event to all registered
1015 * listeners.
1016 *
1017 * @param event information about the event (not used here).
1018 */
1019 public void datasetChanged(DatasetChangeEvent event) {
1020 PlotChangeEvent newEvent = new PlotChangeEvent(this);
1021 newEvent.setType(ChartChangeEventType.DATASET_UPDATED);
1022 notifyListeners(newEvent);
1023 }
1024
1025 /**
1026 * Receives notification of a change to a marker that is assigned to the
1027 * plot.
1028 *
1029 * @param event the event.
1030 *
1031 * @since 1.0.3
1032 */
1033 public void markerChanged(MarkerChangeEvent event) {
1034 notifyListeners(new PlotChangeEvent(this));
1035 }
1036
1037 /**
1038 * Adjusts the supplied x-value.
1039 *
1040 * @param x the x-value.
1041 * @param w1 width 1.
1042 * @param w2 width 2.
1043 * @param edge the edge (left or right).
1044 *
1045 * @return The adjusted x-value.
1046 */
1047 protected double getRectX(double x, double w1, double w2,
1048 RectangleEdge edge) {
1049
1050 double result = x;
1051 if (edge == RectangleEdge.LEFT) {
1052 result = result + w1;
1053 }
1054 else if (edge == RectangleEdge.RIGHT) {
1055 result = result + w2;
1056 }
1057 return result;
1058
1059 }
1060
1061 /**
1062 * Adjusts the supplied y-value.
1063 *
1064 * @param y the x-value.
1065 * @param h1 height 1.
1066 * @param h2 height 2.
1067 * @param edge the edge (top or bottom).
1068 *
1069 * @return The adjusted y-value.
1070 */
1071 protected double getRectY(double y, double h1, double h2,
1072 RectangleEdge edge) {
1073
1074 double result = y;
1075 if (edge == RectangleEdge.TOP) {
1076 result = result + h1;
1077 }
1078 else if (edge == RectangleEdge.BOTTOM) {
1079 result = result + h2;
1080 }
1081 return result;
1082
1083 }
1084
1085 /**
1086 * Tests this plot for equality with another object.
1087 *
1088 * @param obj the object (<code>null</code> permitted).
1089 *
1090 * @return <code>true</code> or <code>false</code>.
1091 */
1092 public boolean equals(Object obj) {
1093 if (obj == this) {
1094 return true;
1095 }
1096 if (!(obj instanceof Plot)) {
1097 return false;
1098 }
1099 Plot that = (Plot) obj;
1100 if (!ObjectUtilities.equal(this.noDataMessage, that.noDataMessage)) {
1101 return false;
1102 }
1103 if (!ObjectUtilities.equal(
1104 this.noDataMessageFont, that.noDataMessageFont
1105 )) {
1106 return false;
1107 }
1108 if (!PaintUtilities.equal(this.noDataMessagePaint,
1109 that.noDataMessagePaint)) {
1110 return false;
1111 }
1112 if (!ObjectUtilities.equal(this.insets, that.insets)) {
1113 return false;
1114 }
1115 if (!ObjectUtilities.equal(this.outlineStroke, that.outlineStroke)) {
1116 return false;
1117 }
1118 if (!PaintUtilities.equal(this.outlinePaint, that.outlinePaint)) {
1119 return false;
1120 }
1121 if (!PaintUtilities.equal(this.backgroundPaint, that.backgroundPaint)) {
1122 return false;
1123 }
1124 if (!ObjectUtilities.equal(this.backgroundImage,
1125 that.backgroundImage)) {
1126 return false;
1127 }
1128 if (this.backgroundImageAlignment != that.backgroundImageAlignment) {
1129 return false;
1130 }
1131 if (this.backgroundImageAlpha != that.backgroundImageAlpha) {
1132 return false;
1133 }
1134 if (this.foregroundAlpha != that.foregroundAlpha) {
1135 return false;
1136 }
1137 if (this.backgroundAlpha != that.backgroundAlpha) {
1138 return false;
1139 }
1140 if (!this.drawingSupplier.equals(that.drawingSupplier)) {
1141 return false;
1142 }
1143 return true;
1144 }
1145
1146 /**
1147 * Creates a clone of the plot.
1148 *
1149 * @return A clone.
1150 *
1151 * @throws CloneNotSupportedException if some component of the plot does not
1152 * support cloning.
1153 */
1154 public Object clone() throws CloneNotSupportedException {
1155
1156 Plot clone = (Plot) super.clone();
1157 // private Plot parent <-- don't clone the parent plot, but take care
1158 // childs in combined plots instead
1159 if (this.datasetGroup != null) {
1160 clone.datasetGroup
1161 = (DatasetGroup) ObjectUtilities.clone(this.datasetGroup);
1162 }
1163 clone.drawingSupplier
1164 = (DrawingSupplier) ObjectUtilities.clone(this.drawingSupplier);
1165 clone.listenerList = new EventListenerList();
1166 return clone;
1167
1168 }
1169
1170 /**
1171 * Provides serialization support.
1172 *
1173 * @param stream the output stream.
1174 *
1175 * @throws IOException if there is an I/O error.
1176 */
1177 private void writeObject(ObjectOutputStream stream) throws IOException {
1178 stream.defaultWriteObject();
1179 SerialUtilities.writePaint(this.noDataMessagePaint, stream);
1180 SerialUtilities.writeStroke(this.outlineStroke, stream);
1181 SerialUtilities.writePaint(this.outlinePaint, stream);
1182 // backgroundImage
1183 SerialUtilities.writePaint(this.backgroundPaint, stream);
1184 }
1185
1186 /**
1187 * Provides serialization support.
1188 *
1189 * @param stream the input stream.
1190 *
1191 * @throws IOException if there is an I/O error.
1192 * @throws ClassNotFoundException if there is a classpath problem.
1193 */
1194 private void readObject(ObjectInputStream stream)
1195 throws IOException, ClassNotFoundException {
1196 stream.defaultReadObject();
1197 this.noDataMessagePaint = SerialUtilities.readPaint(stream);
1198 this.outlineStroke = SerialUtilities.readStroke(stream);
1199 this.outlinePaint = SerialUtilities.readPaint(stream);
1200 // backgroundImage
1201 this.backgroundPaint = SerialUtilities.readPaint(stream);
1202
1203 this.listenerList = new EventListenerList();
1204
1205 }
1206
1207 /**
1208 * Resolves a domain axis location for a given plot orientation.
1209 *
1210 * @param location the location (<code>null</code> not permitted).
1211 * @param orientation the orientation (<code>null</code> not permitted).
1212 *
1213 * @return The edge (never <code>null</code>).
1214 */
1215 public static RectangleEdge resolveDomainAxisLocation(
1216 AxisLocation location, PlotOrientation orientation) {
1217
1218 if (location == null) {
1219 throw new IllegalArgumentException("Null 'location' argument.");
1220 }
1221 if (orientation == null) {
1222 throw new IllegalArgumentException("Null 'orientation' argument.");
1223 }
1224
1225 RectangleEdge result = null;
1226
1227 if (location == AxisLocation.TOP_OR_RIGHT) {
1228 if (orientation == PlotOrientation.HORIZONTAL) {
1229 result = RectangleEdge.RIGHT;
1230 }
1231 else if (orientation == PlotOrientation.VERTICAL) {
1232 result = RectangleEdge.TOP;
1233 }
1234 }
1235 else if (location == AxisLocation.TOP_OR_LEFT) {
1236 if (orientation == PlotOrientation.HORIZONTAL) {
1237 result = RectangleEdge.LEFT;
1238 }
1239 else if (orientation == PlotOrientation.VERTICAL) {
1240 result = RectangleEdge.TOP;
1241 }
1242 }
1243 else if (location == AxisLocation.BOTTOM_OR_RIGHT) {
1244 if (orientation == PlotOrientation.HORIZONTAL) {
1245 result = RectangleEdge.RIGHT;
1246 }
1247 else if (orientation == PlotOrientation.VERTICAL) {
1248 result = RectangleEdge.BOTTOM;
1249 }
1250 }
1251 else if (location == AxisLocation.BOTTOM_OR_LEFT) {
1252 if (orientation == PlotOrientation.HORIZONTAL) {
1253 result = RectangleEdge.LEFT;
1254 }
1255 else if (orientation == PlotOrientation.VERTICAL) {
1256 result = RectangleEdge.BOTTOM;
1257 }
1258 }
1259 // the above should cover all the options...
1260 if (result == null) {
1261 throw new IllegalStateException("resolveDomainAxisLocation()");
1262 }
1263 return result;
1264
1265 }
1266
1267 /**
1268 * Resolves a range axis location for a given plot orientation.
1269 *
1270 * @param location the location (<code>null</code> not permitted).
1271 * @param orientation the orientation (<code>null</code> not permitted).
1272 *
1273 * @return The edge (never <code>null</code>).
1274 */
1275 public static RectangleEdge resolveRangeAxisLocation(
1276 AxisLocation location, PlotOrientation orientation) {
1277
1278 if (location == null) {
1279 throw new IllegalArgumentException("Null 'location' argument.");
1280 }
1281 if (orientation == null) {
1282 throw new IllegalArgumentException("Null 'orientation' argument.");
1283 }
1284
1285 RectangleEdge result = null;
1286
1287 if (location == AxisLocation.TOP_OR_RIGHT) {
1288 if (orientation == PlotOrientation.HORIZONTAL) {
1289 result = RectangleEdge.TOP;
1290 }
1291 else if (orientation == PlotOrientation.VERTICAL) {
1292 result = RectangleEdge.RIGHT;
1293 }
1294 }
1295 else if (location == AxisLocation.TOP_OR_LEFT) {
1296 if (orientation == PlotOrientation.HORIZONTAL) {
1297 result = RectangleEdge.TOP;
1298 }
1299 else if (orientation == PlotOrientation.VERTICAL) {
1300 result = RectangleEdge.LEFT;
1301 }
1302 }
1303 else if (location == AxisLocation.BOTTOM_OR_RIGHT) {
1304 if (orientation == PlotOrientation.HORIZONTAL) {
1305 result = RectangleEdge.BOTTOM;
1306 }
1307 else if (orientation == PlotOrientation.VERTICAL) {
1308 result = RectangleEdge.RIGHT;
1309 }
1310 }
1311 else if (location == AxisLocation.BOTTOM_OR_LEFT) {
1312 if (orientation == PlotOrientation.HORIZONTAL) {
1313 result = RectangleEdge.BOTTOM;
1314 }
1315 else if (orientation == PlotOrientation.VERTICAL) {
1316 result = RectangleEdge.LEFT;
1317 }
1318 }
1319
1320 // the above should cover all the options...
1321 if (result == null) {
1322 throw new IllegalStateException("resolveRangeAxisLocation()");
1323 }
1324 return result;
1325
1326 }
1327
1328 }