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