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 * CategoryPlot.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): Jeremy Bowman;
034 * Arnaud Lelievre;
035 * Richard West, Advanced Micro Devices, Inc.;
036 *
037 * Changes
038 * -------
039 * 21-Jun-2001 : Removed redundant JFreeChart parameter from constructors (DG);
040 * 21-Aug-2001 : Added standard header. Fixed DOS encoding problem (DG);
041 * 18-Sep-2001 : Updated header (DG);
042 * 15-Oct-2001 : Data source classes moved to com.jrefinery.data.* (DG);
043 * 22-Oct-2001 : Renamed DataSource.java --> Dataset.java etc. (DG);
044 * 23-Oct-2001 : Changed intro and trail gaps on bar plots to use percentage of
045 * available space rather than a fixed number of units (DG);
046 * 12-Dec-2001 : Changed constructors to protected (DG);
047 * 13-Dec-2001 : Added tooltips (DG);
048 * 16-Jan-2002 : Increased maximum intro and trail gap percents, plus added
049 * some argument checking code. Thanks to Taoufik Romdhane for
050 * suggesting this (DG);
051 * 05-Feb-2002 : Added accessor methods for the tooltip generator, incorporated
052 * alpha-transparency for Plot and subclasses (DG);
053 * 06-Mar-2002 : Updated import statements (DG);
054 * 14-Mar-2002 : Renamed BarPlot.java --> CategoryPlot.java, and changed code
055 * to use the CategoryItemRenderer interface (DG);
056 * 22-Mar-2002 : Dropped the getCategories() method (DG);
057 * 23-Apr-2002 : Moved the dataset from the JFreeChart class to the Plot
058 * class (DG);
059 * 29-Apr-2002 : New methods to support printing values at the end of bars,
060 * contributed by Jeremy Bowman (DG);
061 * 11-May-2002 : New methods for label visibility and overlaid plot support,
062 * contributed by Jeremy Bowman (DG);
063 * 06-Jun-2002 : Removed the tooltip generator, this is now stored with the
064 * renderer. Moved constants into the CategoryPlotConstants
065 * interface. Updated Javadoc comments (DG);
066 * 10-Jun-2002 : Overridden datasetChanged() method to update the upper and
067 * lower bound on the range axis (if necessary), updated
068 * Javadocs (DG);
069 * 25-Jun-2002 : Removed redundant imports (DG);
070 * 20-Aug-2002 : Changed the constructor for Marker (DG);
071 * 28-Aug-2002 : Added listener notification to setDomainAxis() and
072 * setRangeAxis() (DG);
073 * 23-Sep-2002 : Added getLegendItems() method and fixed errors reported by
074 * Checkstyle (DG);
075 * 28-Oct-2002 : Changes to the CategoryDataset interface (DG);
076 * 05-Nov-2002 : Base dataset is now TableDataset not CategoryDataset (DG);
077 * 07-Nov-2002 : Renamed labelXXX as valueLabelXXX (DG);
078 * 18-Nov-2002 : Added grid settings for both domain and range axis (previously
079 * these were set in the axes) (DG);
080 * 19-Nov-2002 : Added axis location parameters to constructor (DG);
081 * 17-Jan-2003 : Moved to com.jrefinery.chart.plot package (DG);
082 * 14-Feb-2003 : Fixed bug in auto-range calculation for secondary axis (DG);
083 * 26-Mar-2003 : Implemented Serializable (DG);
084 * 02-May-2003 : Moved render() method up from subclasses. Added secondary
085 * range markers. Added an attribute to control the dataset
086 * rendering order. Added a drawAnnotations() method. Changed
087 * the axis location from an int to an AxisLocation (DG);
088 * 07-May-2003 : Merged HorizontalCategoryPlot and VerticalCategoryPlot into
089 * this class (DG);
090 * 02-Jun-2003 : Removed check for range axis compatibility (DG);
091 * 04-Jul-2003 : Added a domain gridline position attribute (DG);
092 * 21-Jul-2003 : Moved DrawingSupplier to Plot superclass (DG);
093 * 19-Aug-2003 : Added equals() method and implemented Cloneable (DG);
094 * 01-Sep-2003 : Fixed bug 797466 (no change event when secondary dataset
095 * changes) (DG);
096 * 02-Sep-2003 : Fixed bug 795209 (wrong dataset checked in render2 method) and
097 * 790407 (initialise method) (DG);
098 * 08-Sep-2003 : Added internationalization via use of properties
099 * resourceBundle (RFE 690236) (AL);
100 * 08-Sep-2003 : Fixed bug (wrong secondary range axis being used). Changed
101 * ValueAxis API (DG);
102 * 10-Sep-2003 : Fixed bug in setRangeAxis() method (DG);
103 * 15-Sep-2003 : Fixed two bugs in serialization, implemented
104 * PublicCloneable (DG);
105 * 23-Oct-2003 : Added event notification for changes to renderer (DG);
106 * 26-Nov-2003 : Fixed bug (849645) in clearRangeMarkers() method (DG);
107 * 03-Dec-2003 : Modified draw method to accept anchor (DG);
108 * 21-Jan-2004 : Update for renamed method in ValueAxis (DG);
109 * 10-Mar-2004 : Fixed bug in axis range calculation when secondary renderer is
110 * stacked (DG);
111 * 12-May-2004 : Added fixed legend items (DG);
112 * 19-May-2004 : Added check for null legend item from renderer (DG);
113 * 02-Jun-2004 : Updated the DatasetRenderingOrder class (DG);
114 * 05-Nov-2004 : Renamed getDatasetsMappedToRangeAxis()
115 * --> datasetsMappedToRangeAxis(), and ensured that returned
116 * list doesn't contain null datasets (DG);
117 * 12-Nov-2004 : Implemented new Zoomable interface (DG);
118 * 07-Jan-2005 : Renamed getRangeExtent() --> findRangeBounds() in
119 * CategoryItemRenderer (DG);
120 * 04-May-2005 : Fixed serialization of range markers (DG);
121 * 05-May-2005 : Updated draw() method parameters (DG);
122 * 20-May-2005 : Added setDomainAxes() and setRangeAxes() methods, as per
123 * RFE 1183100 (DG);
124 * 01-Jun-2005 : Upon deserialization, register plot as a listener with its
125 * axes, dataset(s) and renderer(s) - see patch 1209475 (DG);
126 * 02-Jun-2005 : Added support for domain markers (DG);
127 * 06-Jun-2005 : Fixed equals() method for use with GradientPaint (DG);
128 * 09-Jun-2005 : Added setRenderers(), as per RFE 1183100 (DG);
129 * 16-Jun-2005 : Added getDomainAxisCount() and getRangeAxisCount() methods, to
130 * match XYPlot (see RFE 1220495) (DG);
131 * ------------- JFREECHART 1.0.x ---------------------------------------------
132 * 11-Jan-2006 : Added configureRangeAxes() to rendererChanged(), since the
133 * renderer might influence the axis range (DG);
134 * 27-Jan-2006 : Added various null argument checks (DG);
135 * 18-Aug-2006 : Added getDatasetCount() method, plus a fix for bug drawing
136 * category labels, thanks to Adriaan Joubert (1277726) (DG);
137 * 05-Sep-2006 : Added MarkerChangeEvent support (DG);
138 * 30-Oct-2006 : Added getDomainAxisIndex(), datasetsMappedToDomainAxis() and
139 * getCategoriesForAxis() methods (DG);
140 * 22-Nov-2006 : Fire PlotChangeEvent from setColumnRenderingOrder() and
141 * setRowRenderingOrder() (DG);
142 * 29-Nov-2006 : Fix for bug 1605207 (IntervalMarker exceeds bounds of data
143 * area) (DG);
144 * 26-Feb-2007 : Fix for bug 1669218 (setDomainAxisLocation() notify argument
145 * ignored) (DG);
146 * 13-Mar-2007 : Added null argument checks for setRangeCrosshairPaint() and
147 * setRangeCrosshairStroke(), fixed clipping for
148 * annotations (DG);
149 * 07-Jun-2007 : Override drawBackground() for new GradientPaint handling (DG);
150 * 10-Jul-2007 : Added getRangeAxisIndex(ValueAxis) method (DG);
151 * 24-Sep-2007 : Implemented new zoom methods (DG);
152 * 25-Oct-2007 : Added some argument checks (DG);
153 * 05-Nov-2007 : Applied patch 1823697, by Richard West, for removal of domain
154 * and range markers (DG);
155 * 14-Nov-2007 : Added missing event notifications (DG);
156 * 25-Mar-2008 : Added new methods with optional notification - see patch
157 * 1913751 (DG);
158 * 07-Apr-2008 : Fixed NPE in removeDomainMarker() and
159 * removeRangeMarker() (DG);
160 * 23-Apr-2008 : Fixed equals() and clone() methods (DG);
161 *
162 *
163 */
164
165 package org.jfree.chart.plot;
166
167 import java.awt.AlphaComposite;
168 import java.awt.BasicStroke;
169 import java.awt.Color;
170 import java.awt.Composite;
171 import java.awt.Font;
172 import java.awt.Graphics2D;
173 import java.awt.Paint;
174 import java.awt.Shape;
175 import java.awt.Stroke;
176 import java.awt.geom.Line2D;
177 import java.awt.geom.Point2D;
178 import java.awt.geom.Rectangle2D;
179 import java.io.IOException;
180 import java.io.ObjectInputStream;
181 import java.io.ObjectOutputStream;
182 import java.io.Serializable;
183 import java.util.ArrayList;
184 import java.util.Collection;
185 import java.util.Collections;
186 import java.util.HashMap;
187 import java.util.Iterator;
188 import java.util.List;
189 import java.util.Map;
190 import java.util.ResourceBundle;
191 import java.util.Set;
192
193 import org.jfree.chart.LegendItem;
194 import org.jfree.chart.LegendItemCollection;
195 import org.jfree.chart.annotations.CategoryAnnotation;
196 import org.jfree.chart.axis.Axis;
197 import org.jfree.chart.axis.AxisCollection;
198 import org.jfree.chart.axis.AxisLocation;
199 import org.jfree.chart.axis.AxisSpace;
200 import org.jfree.chart.axis.AxisState;
201 import org.jfree.chart.axis.CategoryAnchor;
202 import org.jfree.chart.axis.CategoryAxis;
203 import org.jfree.chart.axis.ValueAxis;
204 import org.jfree.chart.axis.ValueTick;
205 import org.jfree.chart.event.ChartChangeEventType;
206 import org.jfree.chart.event.PlotChangeEvent;
207 import org.jfree.chart.event.RendererChangeEvent;
208 import org.jfree.chart.event.RendererChangeListener;
209 import org.jfree.chart.renderer.category.CategoryItemRenderer;
210 import org.jfree.chart.renderer.category.CategoryItemRendererState;
211 import org.jfree.data.Range;
212 import org.jfree.data.category.CategoryDataset;
213 import org.jfree.data.general.Dataset;
214 import org.jfree.data.general.DatasetChangeEvent;
215 import org.jfree.data.general.DatasetUtilities;
216 import org.jfree.io.SerialUtilities;
217 import org.jfree.ui.Layer;
218 import org.jfree.ui.RectangleEdge;
219 import org.jfree.ui.RectangleInsets;
220 import org.jfree.util.ObjectList;
221 import org.jfree.util.ObjectUtilities;
222 import org.jfree.util.PaintUtilities;
223 import org.jfree.util.PublicCloneable;
224 import org.jfree.util.SortOrder;
225
226 /**
227 * A general plotting class that uses data from a {@link CategoryDataset} and
228 * renders each data item using a {@link CategoryItemRenderer}.
229 */
230 public class CategoryPlot extends Plot implements ValueAxisPlot,
231 Zoomable, RendererChangeListener, Cloneable, PublicCloneable,
232 Serializable {
233
234 /** For serialization. */
235 private static final long serialVersionUID = -3537691700434728188L;
236
237 /**
238 * The default visibility of the grid lines plotted against the domain
239 * axis.
240 */
241 public static final boolean DEFAULT_DOMAIN_GRIDLINES_VISIBLE = false;
242
243 /**
244 * The default visibility of the grid lines plotted against the range
245 * axis.
246 */
247 public static final boolean DEFAULT_RANGE_GRIDLINES_VISIBLE = true;
248
249 /** The default grid line stroke. */
250 public static final Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke(0.5f,
251 BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0.0f, new float[]
252 {2.0f, 2.0f}, 0.0f);
253
254 /** The default grid line paint. */
255 public static final Paint DEFAULT_GRIDLINE_PAINT = Color.lightGray;
256
257 /** The default value label font. */
258 public static final Font DEFAULT_VALUE_LABEL_FONT = new Font("SansSerif",
259 Font.PLAIN, 10);
260
261 /**
262 * The default crosshair visibility.
263 *
264 * @since 1.0.5
265 */
266 public static final boolean DEFAULT_CROSSHAIR_VISIBLE = false;
267
268 /**
269 * The default crosshair stroke.
270 *
271 * @since 1.0.5
272 */
273 public static final Stroke DEFAULT_CROSSHAIR_STROKE
274 = DEFAULT_GRIDLINE_STROKE;
275
276 /**
277 * The default crosshair paint.
278 *
279 * @since 1.0.5
280 */
281 public static final Paint DEFAULT_CROSSHAIR_PAINT = Color.blue;
282
283 /** The resourceBundle for the localization. */
284 protected static ResourceBundle localizationResources
285 = ResourceBundle.getBundle(
286 "org.jfree.chart.plot.LocalizationBundle");
287
288 /** The plot orientation. */
289 private PlotOrientation orientation;
290
291 /** The offset between the data area and the axes. */
292 private RectangleInsets axisOffset;
293
294 /** Storage for the domain axes. */
295 private ObjectList domainAxes;
296
297 /** Storage for the domain axis locations. */
298 private ObjectList domainAxisLocations;
299
300 /**
301 * A flag that controls whether or not the shared domain axis is drawn
302 * (only relevant when the plot is being used as a subplot).
303 */
304 private boolean drawSharedDomainAxis;
305
306 /** Storage for the range axes. */
307 private ObjectList rangeAxes;
308
309 /** Storage for the range axis locations. */
310 private ObjectList rangeAxisLocations;
311
312 /** Storage for the datasets. */
313 private ObjectList datasets;
314
315 /** Storage for keys that map datasets to domain axes. */
316 private ObjectList datasetToDomainAxisMap;
317
318 /** Storage for keys that map datasets to range axes. */
319 private ObjectList datasetToRangeAxisMap;
320
321 /** Storage for the renderers. */
322 private ObjectList renderers;
323
324 /** The dataset rendering order. */
325 private DatasetRenderingOrder renderingOrder
326 = DatasetRenderingOrder.REVERSE;
327
328 /**
329 * Controls the order in which the columns are traversed when rendering the
330 * data items.
331 */
332 private SortOrder columnRenderingOrder = SortOrder.ASCENDING;
333
334 /**
335 * Controls the order in which the rows are traversed when rendering the
336 * data items.
337 */
338 private SortOrder rowRenderingOrder = SortOrder.ASCENDING;
339
340 /**
341 * A flag that controls whether the grid-lines for the domain axis are
342 * visible.
343 */
344 private boolean domainGridlinesVisible;
345
346 /** The position of the domain gridlines relative to the category. */
347 private CategoryAnchor domainGridlinePosition;
348
349 /** The stroke used to draw the domain grid-lines. */
350 private transient Stroke domainGridlineStroke;
351
352 /** The paint used to draw the domain grid-lines. */
353 private transient Paint domainGridlinePaint;
354
355 /**
356 * A flag that controls whether the grid-lines for the range axis are
357 * visible.
358 */
359 private boolean rangeGridlinesVisible;
360
361 /** The stroke used to draw the range axis grid-lines. */
362 private transient Stroke rangeGridlineStroke;
363
364 /** The paint used to draw the range axis grid-lines. */
365 private transient Paint rangeGridlinePaint;
366
367 /** The anchor value. */
368 private double anchorValue;
369
370 /** A flag that controls whether or not a range crosshair is drawn. */
371 private boolean rangeCrosshairVisible;
372
373 /** The range crosshair value. */
374 private double rangeCrosshairValue;
375
376 /** The pen/brush used to draw the crosshair (if any). */
377 private transient Stroke rangeCrosshairStroke;
378
379 /** The color used to draw the crosshair (if any). */
380 private transient Paint rangeCrosshairPaint;
381
382 /**
383 * A flag that controls whether or not the crosshair locks onto actual
384 * data points.
385 */
386 private boolean rangeCrosshairLockedOnData = true;
387
388 /** A map containing lists of markers for the domain axes. */
389 private Map foregroundDomainMarkers;
390
391 /** A map containing lists of markers for the domain axes. */
392 private Map backgroundDomainMarkers;
393
394 /** A map containing lists of markers for the range axes. */
395 private Map foregroundRangeMarkers;
396
397 /** A map containing lists of markers for the range axes. */
398 private Map backgroundRangeMarkers;
399
400 /**
401 * A (possibly empty) list of annotations for the plot. The list should
402 * be initialised in the constructor and never allowed to be
403 * <code>null</code>.
404 */
405 private List annotations;
406
407 /**
408 * The weight for the plot (only relevant when the plot is used as a subplot
409 * within a combined plot).
410 */
411 private int weight;
412
413 /** The fixed space for the domain axis. */
414 private AxisSpace fixedDomainAxisSpace;
415
416 /** The fixed space for the range axis. */
417 private AxisSpace fixedRangeAxisSpace;
418
419 /**
420 * An optional collection of legend items that can be returned by the
421 * getLegendItems() method.
422 */
423 private LegendItemCollection fixedLegendItems;
424
425 /**
426 * Default constructor.
427 */
428 public CategoryPlot() {
429 this(null, null, null, null);
430 }
431
432 /**
433 * Creates a new plot.
434 *
435 * @param dataset the dataset (<code>null</code> permitted).
436 * @param domainAxis the domain axis (<code>null</code> permitted).
437 * @param rangeAxis the range axis (<code>null</code> permitted).
438 * @param renderer the item renderer (<code>null</code> permitted).
439 *
440 */
441 public CategoryPlot(CategoryDataset dataset,
442 CategoryAxis domainAxis,
443 ValueAxis rangeAxis,
444 CategoryItemRenderer renderer) {
445
446 super();
447
448 this.orientation = PlotOrientation.VERTICAL;
449
450 // allocate storage for dataset, axes and renderers
451 this.domainAxes = new ObjectList();
452 this.domainAxisLocations = new ObjectList();
453 this.rangeAxes = new ObjectList();
454 this.rangeAxisLocations = new ObjectList();
455
456 this.datasetToDomainAxisMap = new ObjectList();
457 this.datasetToRangeAxisMap = new ObjectList();
458
459 this.renderers = new ObjectList();
460
461 this.datasets = new ObjectList();
462 this.datasets.set(0, dataset);
463 if (dataset != null) {
464 dataset.addChangeListener(this);
465 }
466
467 this.axisOffset = RectangleInsets.ZERO_INSETS;
468
469 setDomainAxisLocation(AxisLocation.BOTTOM_OR_LEFT, false);
470 setRangeAxisLocation(AxisLocation.TOP_OR_LEFT, false);
471
472 this.renderers.set(0, renderer);
473 if (renderer != null) {
474 renderer.setPlot(this);
475 renderer.addChangeListener(this);
476 }
477
478 this.domainAxes.set(0, domainAxis);
479 this.mapDatasetToDomainAxis(0, 0);
480 if (domainAxis != null) {
481 domainAxis.setPlot(this);
482 domainAxis.addChangeListener(this);
483 }
484 this.drawSharedDomainAxis = false;
485
486 this.rangeAxes.set(0, rangeAxis);
487 this.mapDatasetToRangeAxis(0, 0);
488 if (rangeAxis != null) {
489 rangeAxis.setPlot(this);
490 rangeAxis.addChangeListener(this);
491 }
492
493 configureDomainAxes();
494 configureRangeAxes();
495
496 this.domainGridlinesVisible = DEFAULT_DOMAIN_GRIDLINES_VISIBLE;
497 this.domainGridlinePosition = CategoryAnchor.MIDDLE;
498 this.domainGridlineStroke = DEFAULT_GRIDLINE_STROKE;
499 this.domainGridlinePaint = DEFAULT_GRIDLINE_PAINT;
500
501 this.rangeGridlinesVisible = DEFAULT_RANGE_GRIDLINES_VISIBLE;
502 this.rangeGridlineStroke = DEFAULT_GRIDLINE_STROKE;
503 this.rangeGridlinePaint = DEFAULT_GRIDLINE_PAINT;
504
505 this.foregroundDomainMarkers = new HashMap();
506 this.backgroundDomainMarkers = new HashMap();
507 this.foregroundRangeMarkers = new HashMap();
508 this.backgroundRangeMarkers = new HashMap();
509
510 Marker baseline = new ValueMarker(0.0, new Color(0.8f, 0.8f, 0.8f,
511 0.5f), new BasicStroke(1.0f), new Color(0.85f, 0.85f, 0.95f,
512 0.5f), new BasicStroke(1.0f), 0.6f);
513 addRangeMarker(baseline, Layer.BACKGROUND);
514
515 this.anchorValue = 0.0;
516
517 this.rangeCrosshairVisible = DEFAULT_CROSSHAIR_VISIBLE;
518 this.rangeCrosshairValue = 0.0;
519 this.rangeCrosshairStroke = DEFAULT_CROSSHAIR_STROKE;
520 this.rangeCrosshairPaint = DEFAULT_CROSSHAIR_PAINT;
521
522 this.annotations = new java.util.ArrayList();
523
524 }
525
526 /**
527 * Returns a string describing the type of plot.
528 *
529 * @return The type.
530 */
531 public String getPlotType() {
532 return localizationResources.getString("Category_Plot");
533 }
534
535 /**
536 * Returns the orientation of the plot.
537 *
538 * @return The orientation of the plot (never <code>null</code>).
539 *
540 * @see #setOrientation(PlotOrientation)
541 */
542 public PlotOrientation getOrientation() {
543 return this.orientation;
544 }
545
546 /**
547 * Sets the orientation for the plot and sends a {@link PlotChangeEvent} to
548 * all registered listeners.
549 *
550 * @param orientation the orientation (<code>null</code> not permitted).
551 *
552 * @see #getOrientation()
553 */
554 public void setOrientation(PlotOrientation orientation) {
555 if (orientation == null) {
556 throw new IllegalArgumentException("Null 'orientation' argument.");
557 }
558 this.orientation = orientation;
559 fireChangeEvent();
560 }
561
562 /**
563 * Returns the axis offset.
564 *
565 * @return The axis offset (never <code>null</code>).
566 *
567 * @see #setAxisOffset(RectangleInsets)
568 */
569 public RectangleInsets getAxisOffset() {
570 return this.axisOffset;
571 }
572
573 /**
574 * Sets the axis offsets (gap between the data area and the axes) and
575 * sends a {@link PlotChangeEvent} to all registered listeners.
576 *
577 * @param offset the offset (<code>null</code> not permitted).
578 *
579 * @see #getAxisOffset()
580 */
581 public void setAxisOffset(RectangleInsets offset) {
582 if (offset == null) {
583 throw new IllegalArgumentException("Null 'offset' argument.");
584 }
585 this.axisOffset = offset;
586 fireChangeEvent();
587 }
588
589 /**
590 * Returns the domain axis for the plot. If the domain axis for this plot
591 * is <code>null</code>, then the method will return the parent plot's
592 * domain axis (if there is a parent plot).
593 *
594 * @return The domain axis (<code>null</code> permitted).
595 *
596 * @see #setDomainAxis(CategoryAxis)
597 */
598 public CategoryAxis getDomainAxis() {
599 return getDomainAxis(0);
600 }
601
602 /**
603 * Returns a domain axis.
604 *
605 * @param index the axis index.
606 *
607 * @return The axis (<code>null</code> possible).
608 *
609 * @see #setDomainAxis(int, CategoryAxis)
610 */
611 public CategoryAxis getDomainAxis(int index) {
612 CategoryAxis result = null;
613 if (index < this.domainAxes.size()) {
614 result = (CategoryAxis) this.domainAxes.get(index);
615 }
616 if (result == null) {
617 Plot parent = getParent();
618 if (parent instanceof CategoryPlot) {
619 CategoryPlot cp = (CategoryPlot) parent;
620 result = cp.getDomainAxis(index);
621 }
622 }
623 return result;
624 }
625
626 /**
627 * Sets the domain axis for the plot and sends a {@link PlotChangeEvent} to
628 * all registered listeners.
629 *
630 * @param axis the axis (<code>null</code> permitted).
631 *
632 * @see #getDomainAxis()
633 */
634 public void setDomainAxis(CategoryAxis axis) {
635 setDomainAxis(0, axis);
636 }
637
638 /**
639 * Sets a domain axis and sends a {@link PlotChangeEvent} to all
640 * registered listeners.
641 *
642 * @param index the axis index.
643 * @param axis the axis (<code>null</code> permitted).
644 *
645 * @see #getDomainAxis(int)
646 */
647 public void setDomainAxis(int index, CategoryAxis axis) {
648 setDomainAxis(index, axis, true);
649 }
650
651 /**
652 * Sets a domain axis and, if requested, sends a {@link PlotChangeEvent} to
653 * all registered listeners.
654 *
655 * @param index the axis index.
656 * @param axis the axis (<code>null</code> permitted).
657 * @param notify notify listeners?
658 */
659 public void setDomainAxis(int index, CategoryAxis axis, boolean notify) {
660 CategoryAxis existing = (CategoryAxis) this.domainAxes.get(index);
661 if (existing != null) {
662 existing.removeChangeListener(this);
663 }
664 if (axis != null) {
665 axis.setPlot(this);
666 }
667 this.domainAxes.set(index, axis);
668 if (axis != null) {
669 axis.configure();
670 axis.addChangeListener(this);
671 }
672 if (notify) {
673 fireChangeEvent();
674 }
675 }
676
677 /**
678 * Sets the domain axes for this plot and sends a {@link PlotChangeEvent}
679 * to all registered listeners.
680 *
681 * @param axes the axes (<code>null</code> not permitted).
682 *
683 * @see #setRangeAxes(ValueAxis[])
684 */
685 public void setDomainAxes(CategoryAxis[] axes) {
686 for (int i = 0; i < axes.length; i++) {
687 setDomainAxis(i, axes[i], false);
688 }
689 fireChangeEvent();
690 }
691
692 /**
693 * Returns the index of the specified axis, or <code>-1</code> if the axis
694 * is not assigned to the plot.
695 *
696 * @param axis the axis (<code>null</code> not permitted).
697 *
698 * @return The axis index.
699 *
700 * @see #getDomainAxis(int)
701 * @see #getRangeAxisIndex(ValueAxis)
702 *
703 * @since 1.0.3
704 */
705 public int getDomainAxisIndex(CategoryAxis axis) {
706 if (axis == null) {
707 throw new IllegalArgumentException("Null 'axis' argument.");
708 }
709 return this.domainAxes.indexOf(axis);
710 }
711
712 /**
713 * Returns the domain axis location for the primary domain axis.
714 *
715 * @return The location (never <code>null</code>).
716 *
717 * @see #getRangeAxisLocation()
718 */
719 public AxisLocation getDomainAxisLocation() {
720 return getDomainAxisLocation(0);
721 }
722
723 /**
724 * Returns the location for a domain axis.
725 *
726 * @param index the axis index.
727 *
728 * @return The location.
729 *
730 * @see #setDomainAxisLocation(int, AxisLocation)
731 */
732 public AxisLocation getDomainAxisLocation(int index) {
733 AxisLocation result = null;
734 if (index < this.domainAxisLocations.size()) {
735 result = (AxisLocation) this.domainAxisLocations.get(index);
736 }
737 if (result == null) {
738 result = AxisLocation.getOpposite(getDomainAxisLocation(0));
739 }
740 return result;
741 }
742
743 /**
744 * Sets the location of the domain axis and sends a {@link PlotChangeEvent}
745 * to all registered listeners.
746 *
747 * @param location the axis location (<code>null</code> not permitted).
748 *
749 * @see #getDomainAxisLocation()
750 * @see #setDomainAxisLocation(int, AxisLocation)
751 */
752 public void setDomainAxisLocation(AxisLocation location) {
753 // delegate...
754 setDomainAxisLocation(0, location, true);
755 }
756
757 /**
758 * Sets the location of the domain axis and, if requested, sends a
759 * {@link PlotChangeEvent} to all registered listeners.
760 *
761 * @param location the axis location (<code>null</code> not permitted).
762 * @param notify a flag that controls whether listeners are notified.
763 */
764 public void setDomainAxisLocation(AxisLocation location, boolean notify) {
765 // delegate...
766 setDomainAxisLocation(0, location, notify);
767 }
768
769 /**
770 * Sets the location for a domain axis and sends a {@link PlotChangeEvent}
771 * to all registered listeners.
772 *
773 * @param index the axis index.
774 * @param location the location.
775 *
776 * @see #getDomainAxisLocation(int)
777 * @see #setRangeAxisLocation(int, AxisLocation)
778 */
779 public void setDomainAxisLocation(int index, AxisLocation location) {
780 // delegate...
781 setDomainAxisLocation(index, location, true);
782 }
783
784 /**
785 * Sets the location for a domain axis and sends a {@link PlotChangeEvent}
786 * to all registered listeners.
787 *
788 * @param index the axis index.
789 * @param location the location.
790 * @param notify notify listeners?
791 *
792 * @since 1.0.5
793 *
794 * @see #getDomainAxisLocation(int)
795 * @see #setRangeAxisLocation(int, AxisLocation, boolean)
796 */
797 public void setDomainAxisLocation(int index, AxisLocation location,
798 boolean notify) {
799 if (index == 0 && location == null) {
800 throw new IllegalArgumentException(
801 "Null 'location' for index 0 not permitted.");
802 }
803 this.domainAxisLocations.set(index, location);
804 if (notify) {
805 fireChangeEvent();
806 }
807 }
808
809 /**
810 * Returns the domain axis edge. This is derived from the axis location
811 * and the plot orientation.
812 *
813 * @return The edge (never <code>null</code>).
814 */
815 public RectangleEdge getDomainAxisEdge() {
816 return getDomainAxisEdge(0);
817 }
818
819 /**
820 * Returns the edge for a domain axis.
821 *
822 * @param index the axis index.
823 *
824 * @return The edge (never <code>null</code>).
825 */
826 public RectangleEdge getDomainAxisEdge(int index) {
827 RectangleEdge result = null;
828 AxisLocation location = getDomainAxisLocation(index);
829 if (location != null) {
830 result = Plot.resolveDomainAxisLocation(location, this.orientation);
831 }
832 else {
833 result = RectangleEdge.opposite(getDomainAxisEdge(0));
834 }
835 return result;
836 }
837
838 /**
839 * Returns the number of domain axes.
840 *
841 * @return The axis count.
842 */
843 public int getDomainAxisCount() {
844 return this.domainAxes.size();
845 }
846
847 /**
848 * Clears the domain axes from the plot and sends a {@link PlotChangeEvent}
849 * to all registered listeners.
850 */
851 public void clearDomainAxes() {
852 for (int i = 0; i < this.domainAxes.size(); i++) {
853 CategoryAxis axis = (CategoryAxis) this.domainAxes.get(i);
854 if (axis != null) {
855 axis.removeChangeListener(this);
856 }
857 }
858 this.domainAxes.clear();
859 fireChangeEvent();
860 }
861
862 /**
863 * Configures the domain axes.
864 */
865 public void configureDomainAxes() {
866 for (int i = 0; i < this.domainAxes.size(); i++) {
867 CategoryAxis axis = (CategoryAxis) this.domainAxes.get(i);
868 if (axis != null) {
869 axis.configure();
870 }
871 }
872 }
873
874 /**
875 * Returns the range axis for the plot. If the range axis for this plot is
876 * null, then the method will return the parent plot's range axis (if there
877 * is a parent plot).
878 *
879 * @return The range axis (possibly <code>null</code>).
880 */
881 public ValueAxis getRangeAxis() {
882 return getRangeAxis(0);
883 }
884
885 /**
886 * Returns a range axis.
887 *
888 * @param index the axis index.
889 *
890 * @return The axis (<code>null</code> possible).
891 */
892 public ValueAxis getRangeAxis(int index) {
893 ValueAxis result = null;
894 if (index < this.rangeAxes.size()) {
895 result = (ValueAxis) this.rangeAxes.get(index);
896 }
897 if (result == null) {
898 Plot parent = getParent();
899 if (parent instanceof CategoryPlot) {
900 CategoryPlot cp = (CategoryPlot) parent;
901 result = cp.getRangeAxis(index);
902 }
903 }
904 return result;
905 }
906
907 /**
908 * Sets the range axis for the plot and sends a {@link PlotChangeEvent} to
909 * all registered listeners.
910 *
911 * @param axis the axis (<code>null</code> permitted).
912 */
913 public void setRangeAxis(ValueAxis axis) {
914 setRangeAxis(0, axis);
915 }
916
917 /**
918 * Sets a range axis and sends a {@link PlotChangeEvent} to all registered
919 * listeners.
920 *
921 * @param index the axis index.
922 * @param axis the axis.
923 */
924 public void setRangeAxis(int index, ValueAxis axis) {
925 setRangeAxis(index, axis, true);
926 }
927
928 /**
929 * Sets a range axis and, if requested, sends a {@link PlotChangeEvent} to
930 * all registered listeners.
931 *
932 * @param index the axis index.
933 * @param axis the axis.
934 * @param notify notify listeners?
935 */
936 public void setRangeAxis(int index, ValueAxis axis, boolean notify) {
937 ValueAxis existing = (ValueAxis) this.rangeAxes.get(index);
938 if (existing != null) {
939 existing.removeChangeListener(this);
940 }
941 if (axis != null) {
942 axis.setPlot(this);
943 }
944 this.rangeAxes.set(index, axis);
945 if (axis != null) {
946 axis.configure();
947 axis.addChangeListener(this);
948 }
949 if (notify) {
950 fireChangeEvent();
951 }
952 }
953
954 /**
955 * Sets the range axes for this plot and sends a {@link PlotChangeEvent}
956 * to all registered listeners.
957 *
958 * @param axes the axes (<code>null</code> not permitted).
959 *
960 * @see #setDomainAxes(CategoryAxis[])
961 */
962 public void setRangeAxes(ValueAxis[] axes) {
963 for (int i = 0; i < axes.length; i++) {
964 setRangeAxis(i, axes[i], false);
965 }
966 fireChangeEvent();
967 }
968
969 /**
970 * Returns the index of the specified axis, or <code>-1</code> if the axis
971 * is not assigned to the plot.
972 *
973 * @param axis the axis (<code>null</code> not permitted).
974 *
975 * @return The axis index.
976 *
977 * @see #getRangeAxis(int)
978 * @see #getDomainAxisIndex(CategoryAxis)
979 *
980 * @since 1.0.7
981 */
982 public int getRangeAxisIndex(ValueAxis axis) {
983 if (axis == null) {
984 throw new IllegalArgumentException("Null 'axis' argument.");
985 }
986 int result = this.rangeAxes.indexOf(axis);
987 if (result < 0) { // try the parent plot
988 Plot parent = getParent();
989 if (parent instanceof CategoryPlot) {
990 CategoryPlot p = (CategoryPlot) parent;
991 result = p.getRangeAxisIndex(axis);
992 }
993 }
994 return result;
995 }
996
997 /**
998 * Returns the range axis location.
999 *
1000 * @return The location (never <code>null</code>).
1001 */
1002 public AxisLocation getRangeAxisLocation() {
1003 return getRangeAxisLocation(0);
1004 }
1005
1006 /**
1007 * Returns the location for a range axis.
1008 *
1009 * @param index the axis index.
1010 *
1011 * @return The location.
1012 *
1013 * @see #setRangeAxisLocation(int, AxisLocation)
1014 */
1015 public AxisLocation getRangeAxisLocation(int index) {
1016 AxisLocation result = null;
1017 if (index < this.rangeAxisLocations.size()) {
1018 result = (AxisLocation) this.rangeAxisLocations.get(index);
1019 }
1020 if (result == null) {
1021 result = AxisLocation.getOpposite(getRangeAxisLocation(0));
1022 }
1023 return result;
1024 }
1025
1026 /**
1027 * Sets the location of the range axis and sends a {@link PlotChangeEvent}
1028 * to all registered listeners.
1029 *
1030 * @param location the location (<code>null</code> not permitted).
1031 *
1032 * @see #setRangeAxisLocation(AxisLocation, boolean)
1033 * @see #setDomainAxisLocation(AxisLocation)
1034 */
1035 public void setRangeAxisLocation(AxisLocation location) {
1036 // defer argument checking...
1037 setRangeAxisLocation(location, true);
1038 }
1039
1040 /**
1041 * Sets the location of the range axis and, if requested, sends a
1042 * {@link PlotChangeEvent} to all registered listeners.
1043 *
1044 * @param location the location (<code>null</code> not permitted).
1045 * @param notify notify listeners?
1046 *
1047 * @see #setDomainAxisLocation(AxisLocation, boolean)
1048 */
1049 public void setRangeAxisLocation(AxisLocation location, boolean notify) {
1050 setRangeAxisLocation(0, location, notify);
1051 }
1052
1053 /**
1054 * Sets the location for a range axis and sends a {@link PlotChangeEvent}
1055 * to all registered listeners.
1056 *
1057 * @param index the axis index.
1058 * @param location the location.
1059 *
1060 * @see #getRangeAxisLocation(int)
1061 * @see #setRangeAxisLocation(int, AxisLocation, boolean)
1062 */
1063 public void setRangeAxisLocation(int index, AxisLocation location) {
1064 setRangeAxisLocation(index, location, true);
1065 }
1066
1067 /**
1068 * Sets the location for a range axis and sends a {@link PlotChangeEvent}
1069 * to all registered listeners.
1070 *
1071 * @param index the axis index.
1072 * @param location the location.
1073 * @param notify notify listeners?
1074 *
1075 * @see #getRangeAxisLocation(int)
1076 * @see #setDomainAxisLocation(int, AxisLocation, boolean)
1077 */
1078 public void setRangeAxisLocation(int index, AxisLocation location,
1079 boolean notify) {
1080 if (index == 0 && location == null) {
1081 throw new IllegalArgumentException(
1082 "Null 'location' for index 0 not permitted.");
1083 }
1084 this.rangeAxisLocations.set(index, location);
1085 if (notify) {
1086 fireChangeEvent();
1087 }
1088 }
1089
1090 /**
1091 * Returns the edge where the primary range axis is located.
1092 *
1093 * @return The edge (never <code>null</code>).
1094 */
1095 public RectangleEdge getRangeAxisEdge() {
1096 return getRangeAxisEdge(0);
1097 }
1098
1099 /**
1100 * Returns the edge for a range axis.
1101 *
1102 * @param index the axis index.
1103 *
1104 * @return The edge.
1105 */
1106 public RectangleEdge getRangeAxisEdge(int index) {
1107 AxisLocation location = getRangeAxisLocation(index);
1108 RectangleEdge result = Plot.resolveRangeAxisLocation(location,
1109 this.orientation);
1110 if (result == null) {
1111 result = RectangleEdge.opposite(getRangeAxisEdge(0));
1112 }
1113 return result;
1114 }
1115
1116 /**
1117 * Returns the number of range axes.
1118 *
1119 * @return The axis count.
1120 */
1121 public int getRangeAxisCount() {
1122 return this.rangeAxes.size();
1123 }
1124
1125 /**
1126 * Clears the range axes from the plot and sends a {@link PlotChangeEvent}
1127 * to all registered listeners.
1128 */
1129 public void clearRangeAxes() {
1130 for (int i = 0; i < this.rangeAxes.size(); i++) {
1131 ValueAxis axis = (ValueAxis) this.rangeAxes.get(i);
1132 if (axis != null) {
1133 axis.removeChangeListener(this);
1134 }
1135 }
1136 this.rangeAxes.clear();
1137 fireChangeEvent();
1138 }
1139
1140 /**
1141 * Configures the range axes.
1142 */
1143 public void configureRangeAxes() {
1144 for (int i = 0; i < this.rangeAxes.size(); i++) {
1145 ValueAxis axis = (ValueAxis) this.rangeAxes.get(i);
1146 if (axis != null) {
1147 axis.configure();
1148 }
1149 }
1150 }
1151
1152 /**
1153 * Returns the primary dataset for the plot.
1154 *
1155 * @return The primary dataset (possibly <code>null</code>).
1156 *
1157 * @see #setDataset(CategoryDataset)
1158 */
1159 public CategoryDataset getDataset() {
1160 return getDataset(0);
1161 }
1162
1163 /**
1164 * Returns the dataset at the given index.
1165 *
1166 * @param index the dataset index.
1167 *
1168 * @return The dataset (possibly <code>null</code>).
1169 *
1170 * @see #setDataset(int, CategoryDataset)
1171 */
1172 public CategoryDataset getDataset(int index) {
1173 CategoryDataset result = null;
1174 if (this.datasets.size() > index) {
1175 result = (CategoryDataset) this.datasets.get(index);
1176 }
1177 return result;
1178 }
1179
1180 /**
1181 * Sets the dataset for the plot, replacing the existing dataset, if there
1182 * is one. This method also calls the
1183 * {@link #datasetChanged(DatasetChangeEvent)} method, which adjusts the
1184 * axis ranges if necessary and sends a {@link PlotChangeEvent} to all
1185 * registered listeners.
1186 *
1187 * @param dataset the dataset (<code>null</code> permitted).
1188 *
1189 * @see #getDataset()
1190 */
1191 public void setDataset(CategoryDataset dataset) {
1192 setDataset(0, dataset);
1193 }
1194
1195 /**
1196 * Sets a dataset for the plot.
1197 *
1198 * @param index the dataset index.
1199 * @param dataset the dataset (<code>null</code> permitted).
1200 *
1201 * @see #getDataset(int)
1202 */
1203 public void setDataset(int index, CategoryDataset dataset) {
1204
1205 CategoryDataset existing = (CategoryDataset) this.datasets.get(index);
1206 if (existing != null) {
1207 existing.removeChangeListener(this);
1208 }
1209 this.datasets.set(index, dataset);
1210 if (dataset != null) {
1211 dataset.addChangeListener(this);
1212 }
1213
1214 // send a dataset change event to self...
1215 DatasetChangeEvent event = new DatasetChangeEvent(this, dataset);
1216 datasetChanged(event);
1217
1218 }
1219
1220 /**
1221 * Returns the number of datasets.
1222 *
1223 * @return The number of datasets.
1224 *
1225 * @since 1.0.2
1226 */
1227 public int getDatasetCount() {
1228 return this.datasets.size();
1229 }
1230
1231 /**
1232 * Maps a dataset to a particular domain axis.
1233 *
1234 * @param index the dataset index (zero-based).
1235 * @param axisIndex the axis index (zero-based).
1236 *
1237 * @see #getDomainAxisForDataset(int)
1238 */
1239 public void mapDatasetToDomainAxis(int index, int axisIndex) {
1240 this.datasetToDomainAxisMap.set(index, new Integer(axisIndex));
1241 // fake a dataset change event to update axes...
1242 datasetChanged(new DatasetChangeEvent(this, getDataset(index)));
1243 }
1244
1245 /**
1246 * Returns the domain axis for a dataset. You can change the axis for a
1247 * dataset using the {@link #mapDatasetToDomainAxis(int, int)} method.
1248 *
1249 * @param index the dataset index.
1250 *
1251 * @return The domain axis.
1252 *
1253 * @see #mapDatasetToDomainAxis(int, int)
1254 */
1255 public CategoryAxis getDomainAxisForDataset(int index) {
1256 CategoryAxis result = getDomainAxis();
1257 Integer axisIndex = (Integer) this.datasetToDomainAxisMap.get(index);
1258 if (axisIndex != null) {
1259 result = getDomainAxis(axisIndex.intValue());
1260 }
1261 return result;
1262 }
1263
1264 /**
1265 * Maps a dataset to a particular range axis.
1266 *
1267 * @param index the dataset index (zero-based).
1268 * @param axisIndex the axis index (zero-based).
1269 *
1270 * @see #getRangeAxisForDataset(int)
1271 */
1272 public void mapDatasetToRangeAxis(int index, int axisIndex) {
1273 this.datasetToRangeAxisMap.set(index, new Integer(axisIndex));
1274 // fake a dataset change event to update axes...
1275 datasetChanged(new DatasetChangeEvent(this, getDataset(index)));
1276 }
1277
1278 /**
1279 * Returns the range axis for a dataset. You can change the axis for a
1280 * dataset using the {@link #mapDatasetToRangeAxis(int, int)} method.
1281 *
1282 * @param index the dataset index.
1283 *
1284 * @return The range axis.
1285 *
1286 * @see #mapDatasetToRangeAxis(int, int)
1287 */
1288 public ValueAxis getRangeAxisForDataset(int index) {
1289 ValueAxis result = getRangeAxis();
1290 Integer axisIndex = (Integer) this.datasetToRangeAxisMap.get(index);
1291 if (axisIndex != null) {
1292 result = getRangeAxis(axisIndex.intValue());
1293 }
1294 return result;
1295 }
1296
1297 /**
1298 * Returns a reference to the renderer for the plot.
1299 *
1300 * @return The renderer.
1301 *
1302 * @see #setRenderer(CategoryItemRenderer)
1303 */
1304 public CategoryItemRenderer getRenderer() {
1305 return getRenderer(0);
1306 }
1307
1308 /**
1309 * Returns the renderer at the given index.
1310 *
1311 * @param index the renderer index.
1312 *
1313 * @return The renderer (possibly <code>null</code>).
1314 *
1315 * @see #setRenderer(int, CategoryItemRenderer)
1316 */
1317 public CategoryItemRenderer getRenderer(int index) {
1318 CategoryItemRenderer result = null;
1319 if (this.renderers.size() > index) {
1320 result = (CategoryItemRenderer) this.renderers.get(index);
1321 }
1322 return result;
1323 }
1324
1325 /**
1326 * Sets the renderer at index 0 (sometimes referred to as the "primary"
1327 * renderer) and sends a {@link PlotChangeEvent} to all registered
1328 * listeners.
1329 *
1330 * @param renderer the renderer (<code>null</code> permitted.
1331 *
1332 * @see #getRenderer()
1333 */
1334 public void setRenderer(CategoryItemRenderer renderer) {
1335 setRenderer(0, renderer, true);
1336 }
1337
1338 /**
1339 * Sets the renderer at index 0 (sometimes referred to as the "primary"
1340 * renderer) and, if requested, sends a {@link PlotChangeEvent} to all
1341 * registered listeners.
1342 * <p>
1343 * You can set the renderer to <code>null</code>, but this is not
1344 * recommended because:
1345 * <ul>
1346 * <li>no data will be displayed;</li>
1347 * <li>the plot background will not be painted;</li>
1348 * </ul>
1349 *
1350 * @param renderer the renderer (<code>null</code> permitted).
1351 * @param notify notify listeners?
1352 *
1353 * @see #getRenderer()
1354 */
1355 public void setRenderer(CategoryItemRenderer renderer, boolean notify) {
1356 setRenderer(0, renderer, notify);
1357 }
1358
1359 /**
1360 * Sets the renderer at the specified index and sends a
1361 * {@link PlotChangeEvent} to all registered listeners.
1362 *
1363 * @param index the index.
1364 * @param renderer the renderer (<code>null</code> permitted).
1365 *
1366 * @see #getRenderer(int)
1367 * @see #setRenderer(int, CategoryItemRenderer, boolean)
1368 */
1369 public void setRenderer(int index, CategoryItemRenderer renderer) {
1370 setRenderer(index, renderer, true);
1371 }
1372
1373 /**
1374 * Sets a renderer. A {@link PlotChangeEvent} is sent to all registered
1375 * listeners.
1376 *
1377 * @param index the index.
1378 * @param renderer the renderer (<code>null</code> permitted).
1379 * @param notify notify listeners?
1380 *
1381 * @see #getRenderer(int)
1382 */
1383 public void setRenderer(int index, CategoryItemRenderer renderer,
1384 boolean notify) {
1385
1386 // stop listening to the existing renderer...
1387 CategoryItemRenderer existing
1388 = (CategoryItemRenderer) this.renderers.get(index);
1389 if (existing != null) {
1390 existing.removeChangeListener(this);
1391 }
1392
1393 // register the new renderer...
1394 this.renderers.set(index, renderer);
1395 if (renderer != null) {
1396 renderer.setPlot(this);
1397 renderer.addChangeListener(this);
1398 }
1399
1400 configureDomainAxes();
1401 configureRangeAxes();
1402
1403 if (notify) {
1404 fireChangeEvent();
1405 }
1406 }
1407
1408 /**
1409 * Sets the renderers for this plot and sends a {@link PlotChangeEvent}
1410 * to all registered listeners.
1411 *
1412 * @param renderers the renderers.
1413 */
1414 public void setRenderers(CategoryItemRenderer[] renderers) {
1415 for (int i = 0; i < renderers.length; i++) {
1416 setRenderer(i, renderers[i], false);
1417 }
1418 fireChangeEvent();
1419 }
1420
1421 /**
1422 * Returns the renderer for the specified dataset. If the dataset doesn't
1423 * belong to the plot, this method will return <code>null</code>.
1424 *
1425 * @param dataset the dataset (<code>null</code> permitted).
1426 *
1427 * @return The renderer (possibly <code>null</code>).
1428 */
1429 public CategoryItemRenderer getRendererForDataset(CategoryDataset dataset) {
1430 CategoryItemRenderer result = null;
1431 for (int i = 0; i < this.datasets.size(); i++) {
1432 if (this.datasets.get(i) == dataset) {
1433 result = (CategoryItemRenderer) this.renderers.get(i);
1434 break;
1435 }
1436 }
1437 return result;
1438 }
1439
1440 /**
1441 * Returns the index of the specified renderer, or <code>-1</code> if the
1442 * renderer is not assigned to this plot.
1443 *
1444 * @param renderer the renderer (<code>null</code> permitted).
1445 *
1446 * @return The renderer index.
1447 */
1448 public int getIndexOf(CategoryItemRenderer renderer) {
1449 return this.renderers.indexOf(renderer);
1450 }
1451
1452 /**
1453 * Returns the dataset rendering order.
1454 *
1455 * @return The order (never <code>null</code>).
1456 *
1457 * @see #setDatasetRenderingOrder(DatasetRenderingOrder)
1458 */
1459 public DatasetRenderingOrder getDatasetRenderingOrder() {
1460 return this.renderingOrder;
1461 }
1462
1463 /**
1464 * Sets the rendering order and sends a {@link PlotChangeEvent} to all
1465 * registered listeners. By default, the plot renders the primary dataset
1466 * last (so that the primary dataset overlays the secondary datasets). You
1467 * can reverse this if you want to.
1468 *
1469 * @param order the rendering order (<code>null</code> not permitted).
1470 *
1471 * @see #getDatasetRenderingOrder()
1472 */
1473 public void setDatasetRenderingOrder(DatasetRenderingOrder order) {
1474 if (order == null) {
1475 throw new IllegalArgumentException("Null 'order' argument.");
1476 }
1477 this.renderingOrder = order;
1478 fireChangeEvent();
1479 }
1480
1481 /**
1482 * Returns the order in which the columns are rendered. The default value
1483 * is <code>SortOrder.ASCENDING</code>.
1484 *
1485 * @return The column rendering order (never <code>null</code).
1486 *
1487 * @see #setColumnRenderingOrder(SortOrder)
1488 */
1489 public SortOrder getColumnRenderingOrder() {
1490 return this.columnRenderingOrder;
1491 }
1492
1493 /**
1494 * Sets the column order in which the items in each dataset should be
1495 * rendered and sends a {@link PlotChangeEvent} to all registered
1496 * listeners. Note that this affects the order in which items are drawn,
1497 * NOT their position in the chart.
1498 *
1499 * @param order the order (<code>null</code> not permitted).
1500 *
1501 * @see #getColumnRenderingOrder()
1502 * @see #setRowRenderingOrder(SortOrder)
1503 */
1504 public void setColumnRenderingOrder(SortOrder order) {
1505 if (order == null) {
1506 throw new IllegalArgumentException("Null 'order' argument.");
1507 }
1508 this.columnRenderingOrder = order;
1509 fireChangeEvent();
1510 }
1511
1512 /**
1513 * Returns the order in which the rows should be rendered. The default
1514 * value is <code>SortOrder.ASCENDING</code>.
1515 *
1516 * @return The order (never <code>null</code>).
1517 *
1518 * @see #setRowRenderingOrder(SortOrder)
1519 */
1520 public SortOrder getRowRenderingOrder() {
1521 return this.rowRenderingOrder;
1522 }
1523
1524 /**
1525 * Sets the row order in which the items in each dataset should be
1526 * rendered and sends a {@link PlotChangeEvent} to all registered
1527 * listeners. Note that this affects the order in which items are drawn,
1528 * NOT their position in the chart.
1529 *
1530 * @param order the order (<code>null</code> not permitted).
1531 *
1532 * @see #getRowRenderingOrder()
1533 * @see #setColumnRenderingOrder(SortOrder)
1534 */
1535 public void setRowRenderingOrder(SortOrder order) {
1536 if (order == null) {
1537 throw new IllegalArgumentException("Null 'order' argument.");
1538 }
1539 this.rowRenderingOrder = order;
1540 fireChangeEvent();
1541 }
1542
1543 /**
1544 * Returns the flag that controls whether the domain grid-lines are visible.
1545 *
1546 * @return The <code>true</code> or <code>false</code>.
1547 *
1548 * @see #setDomainGridlinesVisible(boolean)
1549 */
1550 public boolean isDomainGridlinesVisible() {
1551 return this.domainGridlinesVisible;
1552 }
1553
1554 /**
1555 * Sets the flag that controls whether or not grid-lines are drawn against
1556 * the domain axis.
1557 * <p>
1558 * If the flag value changes, a {@link PlotChangeEvent} is sent to all
1559 * registered listeners.
1560 *
1561 * @param visible the new value of the flag.
1562 *
1563 * @see #isDomainGridlinesVisible()
1564 */
1565 public void setDomainGridlinesVisible(boolean visible) {
1566 if (this.domainGridlinesVisible != visible) {
1567 this.domainGridlinesVisible = visible;
1568 fireChangeEvent();
1569 }
1570 }
1571
1572 /**
1573 * Returns the position used for the domain gridlines.
1574 *
1575 * @return The gridline position (never <code>null</code>).
1576 *
1577 * @see #setDomainGridlinePosition(CategoryAnchor)
1578 */
1579 public CategoryAnchor getDomainGridlinePosition() {
1580 return this.domainGridlinePosition;
1581 }
1582
1583 /**
1584 * Sets the position used for the domain gridlines and sends a
1585 * {@link PlotChangeEvent} to all registered listeners.
1586 *
1587 * @param position the position (<code>null</code> not permitted).
1588 *
1589 * @see #getDomainGridlinePosition()
1590 */
1591 public void setDomainGridlinePosition(CategoryAnchor position) {
1592 if (position == null) {
1593 throw new IllegalArgumentException("Null 'position' argument.");
1594 }
1595 this.domainGridlinePosition = position;
1596 fireChangeEvent();
1597 }
1598
1599 /**
1600 * Returns the stroke used to draw grid-lines against the domain axis.
1601 *
1602 * @return The stroke (never <code>null</code>).
1603 *
1604 * @see #setDomainGridlineStroke(Stroke)
1605 */
1606 public Stroke getDomainGridlineStroke() {
1607 return this.domainGridlineStroke;
1608 }
1609
1610 /**
1611 * Sets the stroke used to draw grid-lines against the domain axis and
1612 * sends a {@link PlotChangeEvent} to all registered listeners.
1613 *
1614 * @param stroke the stroke (<code>null</code> not permitted).
1615 *
1616 * @see #getDomainGridlineStroke()
1617 */
1618 public void setDomainGridlineStroke(Stroke stroke) {
1619 if (stroke == null) {
1620 throw new IllegalArgumentException("Null 'stroke' not permitted.");
1621 }
1622 this.domainGridlineStroke = stroke;
1623 fireChangeEvent();
1624 }
1625
1626 /**
1627 * Returns the paint used to draw grid-lines against the domain axis.
1628 *
1629 * @return The paint (never <code>null</code>).
1630 *
1631 * @see #setDomainGridlinePaint(Paint)
1632 */
1633 public Paint getDomainGridlinePaint() {
1634 return this.domainGridlinePaint;
1635 }
1636
1637 /**
1638 * Sets the paint used to draw the grid-lines (if any) against the domain
1639 * axis and sends a {@link PlotChangeEvent} to all registered listeners.
1640 *
1641 * @param paint the paint (<code>null</code> not permitted).
1642 *
1643 * @see #getDomainGridlinePaint()
1644 */
1645 public void setDomainGridlinePaint(Paint paint) {
1646 if (paint == null) {
1647 throw new IllegalArgumentException("Null 'paint' argument.");
1648 }
1649 this.domainGridlinePaint = paint;
1650 fireChangeEvent();
1651 }
1652
1653 /**
1654 * Returns the flag that controls whether the range grid-lines are visible.
1655 *
1656 * @return The flag.
1657 *
1658 * @see #setRangeGridlinesVisible(boolean)
1659 */
1660 public boolean isRangeGridlinesVisible() {
1661 return this.rangeGridlinesVisible;
1662 }
1663
1664 /**
1665 * Sets the flag that controls whether or not grid-lines are drawn against
1666 * the range axis. If the flag changes value, a {@link PlotChangeEvent} is
1667 * sent to all registered listeners.
1668 *
1669 * @param visible the new value of the flag.
1670 *
1671 * @see #isRangeGridlinesVisible()
1672 */
1673 public void setRangeGridlinesVisible(boolean visible) {
1674 if (this.rangeGridlinesVisible != visible) {
1675 this.rangeGridlinesVisible = visible;
1676 fireChangeEvent();
1677 }
1678 }
1679
1680 /**
1681 * Returns the stroke used to draw the grid-lines against the range axis.
1682 *
1683 * @return The stroke (never <code>null</code>).
1684 *
1685 * @see #setRangeGridlineStroke(Stroke)
1686 */
1687 public Stroke getRangeGridlineStroke() {
1688 return this.rangeGridlineStroke;
1689 }
1690
1691 /**
1692 * Sets the stroke used to draw the grid-lines against the range axis and
1693 * sends a {@link PlotChangeEvent} to all registered listeners.
1694 *
1695 * @param stroke the stroke (<code>null</code> not permitted).
1696 *
1697 * @see #getRangeGridlineStroke()
1698 */
1699 public void setRangeGridlineStroke(Stroke stroke) {
1700 if (stroke == null) {
1701 throw new IllegalArgumentException("Null 'stroke' argument.");
1702 }
1703 this.rangeGridlineStroke = stroke;
1704 fireChangeEvent();
1705 }
1706
1707 /**
1708 * Returns the paint used to draw the grid-lines against the range axis.
1709 *
1710 * @return The paint (never <code>null</code>).
1711 *
1712 * @see #setRangeGridlinePaint(Paint)
1713 */
1714 public Paint getRangeGridlinePaint() {
1715 return this.rangeGridlinePaint;
1716 }
1717
1718 /**
1719 * Sets the paint used to draw the grid lines against the range axis and
1720 * sends a {@link PlotChangeEvent} to all registered listeners.
1721 *
1722 * @param paint the paint (<code>null</code> not permitted).
1723 *
1724 * @see #getRangeGridlinePaint()
1725 */
1726 public void setRangeGridlinePaint(Paint paint) {
1727 if (paint == null) {
1728 throw new IllegalArgumentException("Null 'paint' argument.");
1729 }
1730 this.rangeGridlinePaint = paint;
1731 fireChangeEvent();
1732 }
1733
1734 /**
1735 * Returns the fixed legend items, if any.
1736 *
1737 * @return The legend items (possibly <code>null</code>).
1738 *
1739 * @see #setFixedLegendItems(LegendItemCollection)
1740 */
1741 public LegendItemCollection getFixedLegendItems() {
1742 return this.fixedLegendItems;
1743 }
1744
1745 /**
1746 * Sets the fixed legend items for the plot. Leave this set to
1747 * <code>null</code> if you prefer the legend items to be created
1748 * automatically.
1749 *
1750 * @param items the legend items (<code>null</code> permitted).
1751 *
1752 * @see #getFixedLegendItems()
1753 */
1754 public void setFixedLegendItems(LegendItemCollection items) {
1755 this.fixedLegendItems = items;
1756 fireChangeEvent();
1757 }
1758
1759 /**
1760 * Returns the legend items for the plot. By default, this method creates
1761 * a legend item for each series in each of the datasets. You can change
1762 * this behaviour by overriding this method.
1763 *
1764 * @return The legend items.
1765 */
1766 public LegendItemCollection getLegendItems() {
1767 LegendItemCollection result = this.fixedLegendItems;
1768 if (result == null) {
1769 result = new LegendItemCollection();
1770 // get the legend items for the datasets...
1771 int count = this.datasets.size();
1772 for (int datasetIndex = 0; datasetIndex < count; datasetIndex++) {
1773 CategoryDataset dataset = getDataset(datasetIndex);
1774 if (dataset != null) {
1775 CategoryItemRenderer renderer = getRenderer(datasetIndex);
1776 if (renderer != null) {
1777 int seriesCount = dataset.getRowCount();
1778 for (int i = 0; i < seriesCount; i++) {
1779 LegendItem item = renderer.getLegendItem(
1780 datasetIndex, i);
1781 if (item != null) {
1782 result.add(item);
1783 }
1784 }
1785 }
1786 }
1787 }
1788 }
1789 return result;
1790 }
1791
1792 /**
1793 * Handles a 'click' on the plot by updating the anchor value.
1794 *
1795 * @param x x-coordinate of the click (in Java2D space).
1796 * @param y y-coordinate of the click (in Java2D space).
1797 * @param info information about the plot's dimensions.
1798 *
1799 */
1800 public void handleClick(int x, int y, PlotRenderingInfo info) {
1801
1802 Rectangle2D dataArea = info.getDataArea();
1803 if (dataArea.contains(x, y)) {
1804 // set the anchor value for the range axis...
1805 double java2D = 0.0;
1806 if (this.orientation == PlotOrientation.HORIZONTAL) {
1807 java2D = x;
1808 }
1809 else if (this.orientation == PlotOrientation.VERTICAL) {
1810 java2D = y;
1811 }
1812 RectangleEdge edge = Plot.resolveRangeAxisLocation(
1813 getRangeAxisLocation(), this.orientation);
1814 double value = getRangeAxis().java2DToValue(
1815 java2D, info.getDataArea(), edge);
1816 setAnchorValue(value);
1817 setRangeCrosshairValue(value);
1818 }
1819
1820 }
1821
1822 /**
1823 * Zooms (in or out) on the plot's value axis.
1824 * <p>
1825 * If the value 0.0 is passed in as the zoom percent, the auto-range
1826 * calculation for the axis is restored (which sets the range to include
1827 * the minimum and maximum data values, thus displaying all the data).
1828 *
1829 * @param percent the zoom amount.
1830 */
1831 public void zoom(double percent) {
1832
1833 if (percent > 0.0) {
1834 double range = getRangeAxis().getRange().getLength();
1835 double scaledRange = range * percent;
1836 getRangeAxis().setRange(this.anchorValue - scaledRange / 2.0,
1837 this.anchorValue + scaledRange / 2.0);
1838 }
1839 else {
1840 getRangeAxis().setAutoRange(true);
1841 }
1842
1843 }
1844
1845 /**
1846 * Receives notification of a change to the plot's dataset.
1847 * <P>
1848 * The range axis bounds will be recalculated if necessary.
1849 *
1850 * @param event information about the event (not used here).
1851 */
1852 public void datasetChanged(DatasetChangeEvent event) {
1853
1854 int count = this.rangeAxes.size();
1855 for (int axisIndex = 0; axisIndex < count; axisIndex++) {
1856 ValueAxis yAxis = getRangeAxis(axisIndex);
1857 if (yAxis != null) {
1858 yAxis.configure();
1859 }
1860 }
1861 if (getParent() != null) {
1862 getParent().datasetChanged(event);
1863 }
1864 else {
1865 PlotChangeEvent e = new PlotChangeEvent(this);
1866 e.setType(ChartChangeEventType.DATASET_UPDATED);
1867 notifyListeners(e);
1868 }
1869
1870 }
1871
1872 /**
1873 * Receives notification of a renderer change event.
1874 *
1875 * @param event the event.
1876 */
1877 public void rendererChanged(RendererChangeEvent event) {
1878 Plot parent = getParent();
1879 if (parent != null) {
1880 if (parent instanceof RendererChangeListener) {
1881 RendererChangeListener rcl = (RendererChangeListener) parent;
1882 rcl.rendererChanged(event);
1883 }
1884 else {
1885 // this should never happen with the existing code, but throw
1886 // an exception in case future changes make it possible...
1887 throw new RuntimeException(
1888 "The renderer has changed and I don't know what to do!");
1889 }
1890 }
1891 else {
1892 configureRangeAxes();
1893 PlotChangeEvent e = new PlotChangeEvent(this);
1894 notifyListeners(e);
1895 }
1896 }
1897
1898 /**
1899 * Adds a marker for display (in the foreground) against the domain axis and
1900 * sends a {@link PlotChangeEvent} to all registered listeners. Typically a
1901 * marker will be drawn by the renderer as a line perpendicular to the
1902 * domain axis, however this is entirely up to the renderer.
1903 *
1904 * @param marker the marker (<code>null</code> not permitted).
1905 *
1906 * @see #removeDomainMarker(Marker)
1907 */
1908 public void addDomainMarker(CategoryMarker marker) {
1909 addDomainMarker(marker, Layer.FOREGROUND);
1910 }
1911
1912 /**
1913 * Adds a marker for display against the domain axis and sends a
1914 * {@link PlotChangeEvent} to all registered listeners. Typically a marker
1915 * will be drawn by the renderer as a line perpendicular to the domain
1916 * axis, however this is entirely up to the renderer.
1917 *
1918 * @param marker the marker (<code>null</code> not permitted).
1919 * @param layer the layer (foreground or background) (<code>null</code>
1920 * not permitted).
1921 *
1922 * @see #removeDomainMarker(Marker, Layer)
1923 */
1924 public void addDomainMarker(CategoryMarker marker, Layer layer) {
1925 addDomainMarker(0, marker, layer);
1926 }
1927
1928 /**
1929 * Adds a marker for display by a particular renderer and sends a
1930 * {@link PlotChangeEvent} to all registered listeners.
1931 * <P>
1932 * Typically a marker will be drawn by the renderer as a line perpendicular
1933 * to a domain axis, however this is entirely up to the renderer.
1934 *
1935 * @param index the renderer index.
1936 * @param marker the marker (<code>null</code> not permitted).
1937 * @param layer the layer (<code>null</code> not permitted).
1938 *
1939 * @see #removeDomainMarker(int, Marker, Layer)
1940 */
1941 public void addDomainMarker(int index, CategoryMarker marker, Layer layer) {
1942 addDomainMarker(index, marker, layer, true);
1943 }
1944
1945 /**
1946 * Adds a marker for display by a particular renderer and, if requested,
1947 * sends a {@link PlotChangeEvent} to all registered listeners.
1948 * <P>
1949 * Typically a marker will be drawn by the renderer as a line perpendicular
1950 * to a domain axis, however this is entirely up to the renderer.
1951 *
1952 * @param index the renderer index.
1953 * @param marker the marker (<code>null</code> not permitted).
1954 * @param layer the layer (<code>null</code> not permitted).
1955 * @param notify notify listeners?
1956 *
1957 * @since 1.0.10
1958 *
1959 * @see #removeDomainMarker(int, Marker, Layer, boolean)
1960 */
1961 public void addDomainMarker(int index, CategoryMarker marker, Layer layer,
1962 boolean notify) {
1963 if (marker == null) {
1964 throw new IllegalArgumentException("Null 'marker' not permitted.");
1965 }
1966 if (layer == null) {
1967 throw new IllegalArgumentException("Null 'layer' not permitted.");
1968 }
1969 Collection markers;
1970 if (layer == Layer.FOREGROUND) {
1971 markers = (Collection) this.foregroundDomainMarkers.get(
1972 new Integer(index));
1973 if (markers == null) {
1974 markers = new java.util.ArrayList();
1975 this.foregroundDomainMarkers.put(new Integer(index), markers);
1976 }
1977 markers.add(marker);
1978 }
1979 else if (layer == Layer.BACKGROUND) {
1980 markers = (Collection) this.backgroundDomainMarkers.get(
1981 new Integer(index));
1982 if (markers == null) {
1983 markers = new java.util.ArrayList();
1984 this.backgroundDomainMarkers.put(new Integer(index), markers);
1985 }
1986 markers.add(marker);
1987 }
1988 marker.addChangeListener(this);
1989 if (notify) {
1990 fireChangeEvent();
1991 }
1992 }
1993
1994 /**
1995 * Clears all the domain markers for the plot and sends a
1996 * {@link PlotChangeEvent} to all registered listeners.
1997 *
1998 * @see #clearRangeMarkers()
1999 */
2000 public void clearDomainMarkers() {
2001 if (this.backgroundDomainMarkers != null) {
2002 Set keys = this.backgroundDomainMarkers.keySet();
2003 Iterator iterator = keys.iterator();
2004 while (iterator.hasNext()) {
2005 Integer key = (Integer) iterator.next();
2006 clearDomainMarkers(key.intValue());
2007 }
2008 this.backgroundDomainMarkers.clear();
2009 }
2010 if (this.foregroundDomainMarkers != null) {
2011 Set keys = this.foregroundDomainMarkers.keySet();
2012 Iterator iterator = keys.iterator();
2013 while (iterator.hasNext()) {
2014 Integer key = (Integer) iterator.next();
2015 clearDomainMarkers(key.intValue());
2016 }
2017 this.foregroundDomainMarkers.clear();
2018 }
2019 fireChangeEvent();
2020 }
2021
2022 /**
2023 * Returns the list of domain markers (read only) for the specified layer.
2024 *
2025 * @param layer the layer (foreground or background).
2026 *
2027 * @return The list of domain markers.
2028 */
2029 public Collection getDomainMarkers(Layer layer) {
2030 return getDomainMarkers(0, layer);
2031 }
2032
2033 /**
2034 * Returns a collection of domain markers for a particular renderer and
2035 * layer.
2036 *
2037 * @param index the renderer index.
2038 * @param layer the layer.
2039 *
2040 * @return A collection of markers (possibly <code>null</code>).
2041 */
2042 public Collection getDomainMarkers(int index, Layer layer) {
2043 Collection result = null;
2044 Integer key = new Integer(index);
2045 if (layer == Layer.FOREGROUND) {
2046 result = (Collection) this.foregroundDomainMarkers.get(key);
2047 }
2048 else if (layer == Layer.BACKGROUND) {
2049 result = (Collection) this.backgroundDomainMarkers.get(key);
2050 }
2051 if (result != null) {
2052 result = Collections.unmodifiableCollection(result);
2053 }
2054 return result;
2055 }
2056
2057 /**
2058 * Clears all the domain markers for the specified renderer.
2059 *
2060 * @param index the renderer index.
2061 *
2062 * @see #clearRangeMarkers(int)
2063 */
2064 public void clearDomainMarkers(int index) {
2065 Integer key = new Integer(index);
2066 if (this.backgroundDomainMarkers != null) {
2067 Collection markers
2068 = (Collection) this.backgroundDomainMarkers.get(key);
2069 if (markers != null) {
2070 Iterator iterator = markers.iterator();
2071 while (iterator.hasNext()) {
2072 Marker m = (Marker) iterator.next();
2073 m.removeChangeListener(this);
2074 }
2075 markers.clear();
2076 }
2077 }
2078 if (this.foregroundDomainMarkers != null) {
2079 Collection markers
2080 = (Collection) this.foregroundDomainMarkers.get(key);
2081 if (markers != null) {
2082 Iterator iterator = markers.iterator();
2083 while (iterator.hasNext()) {
2084 Marker m = (Marker) iterator.next();
2085 m.removeChangeListener(this);
2086 }
2087 markers.clear();
2088 }
2089 }
2090 fireChangeEvent();
2091 }
2092
2093 /**
2094 * Removes a marker for the domain axis and sends a {@link PlotChangeEvent}
2095 * to all registered listeners.
2096 *
2097 * @param marker the marker.
2098 *
2099 * @return A boolean indicating whether or not the marker was actually
2100 * removed.
2101 *
2102 * @since 1.0.7
2103 */
2104 public boolean removeDomainMarker(Marker marker) {
2105 return removeDomainMarker(marker, Layer.FOREGROUND);
2106 }
2107
2108 /**
2109 * Removes a marker for the domain axis in the specified layer and sends a
2110 * {@link PlotChangeEvent} to all registered listeners.
2111 *
2112 * @param marker the marker (<code>null</code> not permitted).
2113 * @param layer the layer (foreground or background).
2114 *
2115 * @return A boolean indicating whether or not the marker was actually
2116 * removed.
2117 *
2118 * @since 1.0.7
2119 */
2120 public boolean removeDomainMarker(Marker marker, Layer layer) {
2121 return removeDomainMarker(0, marker, layer);
2122 }
2123
2124 /**
2125 * Removes a marker for a specific dataset/renderer and sends a
2126 * {@link PlotChangeEvent} to all registered listeners.
2127 *
2128 * @param index the dataset/renderer index.
2129 * @param marker the marker.
2130 * @param layer the layer (foreground or background).
2131 *
2132 * @return A boolean indicating whether or not the marker was actually
2133 * removed.
2134 *
2135 * @since 1.0.7
2136 */
2137 public boolean removeDomainMarker(int index, Marker marker, Layer layer) {
2138 return removeDomainMarker(index, marker, layer, true);
2139 }
2140
2141 /**
2142 * Removes a marker for a specific dataset/renderer and, if requested,
2143 * sends a {@link PlotChangeEvent} to all registered listeners.
2144 *
2145 * @param index the dataset/renderer index.
2146 * @param marker the marker.
2147 * @param layer the layer (foreground or background).
2148 *
2149 * @return A boolean indicating whether or not the marker was actually
2150 * removed.
2151 *
2152 * @since 1.0.10
2153 */
2154 public boolean removeDomainMarker(int index, Marker marker, Layer layer,
2155 boolean notify) {
2156 ArrayList markers;
2157 if (layer == Layer.FOREGROUND) {
2158 markers = (ArrayList) this.foregroundDomainMarkers.get(new Integer(
2159 index));
2160 }
2161 else {
2162 markers = (ArrayList) this.backgroundDomainMarkers.get(new Integer(
2163 index));
2164 }
2165 if (markers == null) {
2166 return false;
2167 }
2168 boolean removed = markers.remove(marker);
2169 if (removed && notify) {
2170 fireChangeEvent();
2171 }
2172 return removed;
2173 }
2174
2175 /**
2176 * Adds a marker for display (in the foreground) against the range axis and
2177 * sends a {@link PlotChangeEvent} to all registered listeners. Typically a
2178 * marker will be drawn by the renderer as a line perpendicular to the
2179 * range axis, however this is entirely up to the renderer.
2180 *
2181 * @param marker the marker (<code>null</code> not permitted).
2182 *
2183 * @see #removeRangeMarker(Marker)
2184 */
2185 public void addRangeMarker(Marker marker) {
2186 addRangeMarker(marker, Layer.FOREGROUND);
2187 }
2188
2189 /**
2190 * Adds a marker for display against the range axis and sends a
2191 * {@link PlotChangeEvent} to all registered listeners. Typically a marker
2192 * will be drawn by the renderer as a line perpendicular to the range axis,
2193 * however this is entirely up to the renderer.
2194 *
2195 * @param marker the marker (<code>null</code> not permitted).
2196 * @param layer the layer (foreground or background) (<code>null</code>
2197 * not permitted).
2198 *
2199 * @see #removeRangeMarker(Marker, Layer)
2200 */
2201 public void addRangeMarker(Marker marker, Layer layer) {
2202 addRangeMarker(0, marker, layer);
2203 }
2204
2205 /**
2206 * Adds a marker for display by a particular renderer and sends a
2207 * {@link PlotChangeEvent} to all registered listeners.
2208 * <P>
2209 * Typically a marker will be drawn by the renderer as a line perpendicular
2210 * to a range axis, however this is entirely up to the renderer.
2211 *
2212 * @param index the renderer index.
2213 * @param marker the marker.
2214 * @param layer the layer.
2215 *
2216 * @see #removeRangeMarker(int, Marker, Layer)
2217 */
2218 public void addRangeMarker(int index, Marker marker, Layer layer) {
2219 addRangeMarker(index, marker, layer, true);
2220 }
2221
2222 /**
2223 * Adds a marker for display by a particular renderer and sends a
2224 * {@link PlotChangeEvent} to all registered listeners.
2225 * <P>
2226 * Typically a marker will be drawn by the renderer as a line perpendicular
2227 * to a range axis, however this is entirely up to the renderer.
2228 *
2229 * @param index the renderer index.
2230 * @param marker the marker.
2231 * @param layer the layer.
2232 * @param notify notify listeners?
2233 *
2234 * @since 1.0.10
2235 *
2236 * @see #removeRangeMarker(int, Marker, Layer, boolean)
2237 */
2238 public void addRangeMarker(int index, Marker marker, Layer layer,
2239 boolean notify) {
2240 Collection markers;
2241 if (layer == Layer.FOREGROUND) {
2242 markers = (Collection) this.foregroundRangeMarkers.get(
2243 new Integer(index));
2244 if (markers == null) {
2245 markers = new java.util.ArrayList();
2246 this.foregroundRangeMarkers.put(new Integer(index), markers);
2247 }
2248 markers.add(marker);
2249 }
2250 else if (layer == Layer.BACKGROUND) {
2251 markers = (Collection) this.backgroundRangeMarkers.get(
2252 new Integer(index));
2253 if (markers == null) {
2254 markers = new java.util.ArrayList();
2255 this.backgroundRangeMarkers.put(new Integer(index), markers);
2256 }
2257 markers.add(marker);
2258 }
2259 marker.addChangeListener(this);
2260 if (notify) {
2261 fireChangeEvent();
2262 }
2263 }
2264
2265 /**
2266 * Clears all the range markers for the plot and sends a
2267 * {@link PlotChangeEvent} to all registered listeners.
2268 *
2269 * @see #clearDomainMarkers()
2270 */
2271 public void clearRangeMarkers() {
2272 if (this.backgroundRangeMarkers != null) {
2273 Set keys = this.backgroundRangeMarkers.keySet();
2274 Iterator iterator = keys.iterator();
2275 while (iterator.hasNext()) {
2276 Integer key = (Integer) iterator.next();
2277 clearRangeMarkers(key.intValue());
2278 }
2279 this.backgroundRangeMarkers.clear();
2280 }
2281 if (this.foregroundRangeMarkers != null) {
2282 Set keys = this.foregroundRangeMarkers.keySet();
2283 Iterator iterator = keys.iterator();
2284 while (iterator.hasNext()) {
2285 Integer key = (Integer) iterator.next();
2286 clearRangeMarkers(key.intValue());
2287 }
2288 this.foregroundRangeMarkers.clear();
2289 }
2290 fireChangeEvent();
2291 }
2292
2293 /**
2294 * Returns the list of range markers (read only) for the specified layer.
2295 *
2296 * @param layer the layer (foreground or background).
2297 *
2298 * @return The list of range markers.
2299 *
2300 * @see #getRangeMarkers(int, Layer)
2301 */
2302 public Collection getRangeMarkers(Layer layer) {
2303 return getRangeMarkers(0, layer);
2304 }
2305
2306 /**
2307 * Returns a collection of range markers for a particular renderer and
2308 * layer.
2309 *
2310 * @param index the renderer index.
2311 * @param layer the layer.
2312 *
2313 * @return A collection of markers (possibly <code>null</code>).
2314 */
2315 public Collection getRangeMarkers(int index, Layer layer) {
2316 Collection result = null;
2317 Integer key = new Integer(index);
2318 if (layer == Layer.FOREGROUND) {
2319 result = (Collection) this.foregroundRangeMarkers.get(key);
2320 }
2321 else if (layer == Layer.BACKGROUND) {
2322 result = (Collection) this.backgroundRangeMarkers.get(key);
2323 }
2324 if (result != null) {
2325 result = Collections.unmodifiableCollection(result);
2326 }
2327 return result;
2328 }
2329
2330 /**
2331 * Clears all the range markers for the specified renderer.
2332 *
2333 * @param index the renderer index.
2334 *
2335 * @see #clearDomainMarkers(int)
2336 */
2337 public void clearRangeMarkers(int index) {
2338 Integer key = new Integer(index);
2339 if (this.backgroundRangeMarkers != null) {
2340 Collection markers
2341 = (Collection) this.backgroundRangeMarkers.get(key);
2342 if (markers != null) {
2343 Iterator iterator = markers.iterator();
2344 while (iterator.hasNext()) {
2345 Marker m = (Marker) iterator.next();
2346 m.removeChangeListener(this);
2347 }
2348 markers.clear();
2349 }
2350 }
2351 if (this.foregroundRangeMarkers != null) {
2352 Collection markers
2353 = (Collection) this.foregroundRangeMarkers.get(key);
2354 if (markers != null) {
2355 Iterator iterator = markers.iterator();
2356 while (iterator.hasNext()) {
2357 Marker m = (Marker) iterator.next();
2358 m.removeChangeListener(this);
2359 }
2360 markers.clear();
2361 }
2362 }
2363 fireChangeEvent();
2364 }
2365
2366 /**
2367 * Removes a marker for the range axis and sends a {@link PlotChangeEvent}
2368 * to all registered listeners.
2369 *
2370 * @param marker the marker.
2371 *
2372 * @return A boolean indicating whether or not the marker was actually
2373 * removed.
2374 *
2375 * @since 1.0.7
2376 *
2377 * @see #addRangeMarker(Marker)
2378 */
2379 public boolean removeRangeMarker(Marker marker) {
2380 return removeRangeMarker(marker, Layer.FOREGROUND);
2381 }
2382
2383 /**
2384 * Removes a marker for the range axis in the specified layer and sends a
2385 * {@link PlotChangeEvent} to all registered listeners.
2386 *
2387 * @param marker the marker (<code>null</code> not permitted).
2388 * @param layer the layer (foreground or background).
2389 *
2390 * @return A boolean indicating whether or not the marker was actually
2391 * removed.
2392 *
2393 * @since 1.0.7
2394 *
2395 * @see #addRangeMarker(Marker, Layer)
2396 */
2397 public boolean removeRangeMarker(Marker marker, Layer layer) {
2398 return removeRangeMarker(0, marker, layer);
2399 }
2400
2401 /**
2402 * Removes a marker for a specific dataset/renderer and sends a
2403 * {@link PlotChangeEvent} to all registered listeners.
2404 *
2405 * @param index the dataset/renderer index.
2406 * @param marker the marker.
2407 * @param layer the layer (foreground or background).
2408 *
2409 * @return A boolean indicating whether or not the marker was actually
2410 * removed.
2411 *
2412 * @since 1.0.7
2413 *
2414 * @see #addRangeMarker(int, Marker, Layer)
2415 */
2416 public boolean removeRangeMarker(int index, Marker marker, Layer layer) {
2417 return removeRangeMarker(index, marker, layer, true);
2418 }
2419
2420 /**
2421 * Removes a marker for a specific dataset/renderer and sends a
2422 * {@link PlotChangeEvent} to all registered listeners.
2423 *
2424 * @param index the dataset/renderer index.
2425 * @param marker the marker.
2426 * @param layer the layer (foreground or background).
2427 * @param notify notify listeners.
2428 *
2429 * @return A boolean indicating whether or not the marker was actually
2430 * removed.
2431 *
2432 * @since 1.0.10
2433 *
2434 * @see #addRangeMarker(int, Marker, Layer, boolean)
2435 */
2436 public boolean removeRangeMarker(int index, Marker marker, Layer layer,
2437 boolean notify) {
2438 if (marker == null) {
2439 throw new IllegalArgumentException("Null 'marker' argument.");
2440 }
2441 ArrayList markers;
2442 if (layer == Layer.FOREGROUND) {
2443 markers = (ArrayList) this.foregroundRangeMarkers.get(new Integer(
2444 index));
2445 }
2446 else {
2447 markers = (ArrayList) this.backgroundRangeMarkers.get(new Integer(
2448 index));
2449 }
2450 if (markers == null) {
2451 return false;
2452 }
2453 boolean removed = markers.remove(marker);
2454 if (removed && notify) {
2455 fireChangeEvent();
2456 }
2457 return removed;
2458 }
2459
2460 /**
2461 * Returns a flag indicating whether or not the range crosshair is visible.
2462 *
2463 * @return The flag.
2464 *
2465 * @see #setRangeCrosshairVisible(boolean)
2466 */
2467 public boolean isRangeCrosshairVisible() {
2468 return this.rangeCrosshairVisible;
2469 }
2470
2471 /**
2472 * Sets the flag indicating whether or not the range crosshair is visible.
2473 *
2474 * @param flag the new value of the flag.
2475 *
2476 * @see #isRangeCrosshairVisible()
2477 */
2478 public void setRangeCrosshairVisible(boolean flag) {
2479 if (this.rangeCrosshairVisible != flag) {
2480 this.rangeCrosshairVisible = flag;
2481 fireChangeEvent();
2482 }
2483 }
2484
2485 /**
2486 * Returns a flag indicating whether or not the crosshair should "lock-on"
2487 * to actual data values.
2488 *
2489 * @return The flag.
2490 *
2491 * @see #setRangeCrosshairLockedOnData(boolean)
2492 */
2493 public boolean isRangeCrosshairLockedOnData() {
2494 return this.rangeCrosshairLockedOnData;
2495 }
2496
2497 /**
2498 * Sets the flag indicating whether or not the range crosshair should
2499 * "lock-on" to actual data values, and sends a {@link PlotChangeEvent}
2500 * to all registered listeners.
2501 *
2502 * @param flag the flag.
2503 *
2504 * @see #isRangeCrosshairLockedOnData()
2505 */
2506 public void setRangeCrosshairLockedOnData(boolean flag) {
2507 if (this.rangeCrosshairLockedOnData != flag) {
2508 this.rangeCrosshairLockedOnData = flag;
2509 fireChangeEvent();
2510 }
2511 }
2512
2513 /**
2514 * Returns the range crosshair value.
2515 *
2516 * @return The value.
2517 *
2518 * @see #setRangeCrosshairValue(double)
2519 */
2520 public double getRangeCrosshairValue() {
2521 return this.rangeCrosshairValue;
2522 }
2523
2524 /**
2525 * Sets the range crosshair value and, if the crosshair is visible, sends
2526 * a {@link PlotChangeEvent} to all registered listeners.
2527 *
2528 * @param value the new value.
2529 *
2530 * @see #getRangeCrosshairValue()
2531 */
2532 public void setRangeCrosshairValue(double value) {
2533 setRangeCrosshairValue(value, true);
2534 }
2535
2536 /**
2537 * Sets the range crosshair value and, if requested, sends a
2538 * {@link PlotChangeEvent} to all registered listeners (but only if the
2539 * crosshair is visible).
2540 *
2541 * @param value the new value.
2542 * @param notify a flag that controls whether or not listeners are
2543 * notified.
2544 *
2545 * @see #getRangeCrosshairValue()
2546 */
2547 public void setRangeCrosshairValue(double value, boolean notify) {
2548 this.rangeCrosshairValue = value;
2549 if (isRangeCrosshairVisible() && notify) {
2550 fireChangeEvent();
2551 }
2552 }
2553
2554 /**
2555 * Returns the pen-style (<code>Stroke</code>) used to draw the crosshair
2556 * (if visible).
2557 *
2558 * @return The crosshair stroke (never <code>null</code>).
2559 *
2560 * @see #setRangeCrosshairStroke(Stroke)
2561 * @see #isRangeCrosshairVisible()
2562 * @see #getRangeCrosshairPaint()
2563 */
2564 public Stroke getRangeCrosshairStroke() {
2565 return this.rangeCrosshairStroke;
2566 }
2567
2568 /**
2569 * Sets the pen-style (<code>Stroke</code>) used to draw the range
2570 * crosshair (if visible), and sends a {@link PlotChangeEvent} to all
2571 * registered listeners.
2572 *
2573 * @param stroke the new crosshair stroke (<code>null</code> not
2574 * permitted).
2575 *
2576 * @see #getRangeCrosshairStroke()
2577 */
2578 public void setRangeCrosshairStroke(Stroke stroke) {
2579 if (stroke == null) {
2580 throw new IllegalArgumentException("Null 'stroke' argument.");
2581 }
2582 this.rangeCrosshairStroke = stroke;
2583 fireChangeEvent();
2584 }
2585
2586 /**
2587 * Returns the paint used to draw the range crosshair.
2588 *
2589 * @return The paint (never <code>null</code>).
2590 *
2591 * @see #setRangeCrosshairPaint(Paint)
2592 * @see #isRangeCrosshairVisible()
2593 * @see #getRangeCrosshairStroke()
2594 */
2595 public Paint getRangeCrosshairPaint() {
2596 return this.rangeCrosshairPaint;
2597 }
2598
2599 /**
2600 * Sets the paint used to draw the range crosshair (if visible) and
2601 * sends a {@link PlotChangeEvent} to all registered listeners.
2602 *
2603 * @param paint the paint (<code>null</code> not permitted).
2604 *
2605 * @see #getRangeCrosshairPaint()
2606 */
2607 public void setRangeCrosshairPaint(Paint paint) {
2608 if (paint == null) {
2609 throw new IllegalArgumentException("Null 'paint' argument.");
2610 }
2611 this.rangeCrosshairPaint = paint;
2612 fireChangeEvent();
2613 }
2614
2615 /**
2616 * Returns the list of annotations.
2617 *
2618 * @return The list of annotations (never <code>null</code>).
2619 *
2620 * @see #addAnnotation(CategoryAnnotation)
2621 * @see #clearAnnotations()
2622 */
2623 public List getAnnotations() {
2624 return this.annotations;
2625 }
2626
2627 /**
2628 * Adds an annotation to the plot and sends a {@link PlotChangeEvent} to all
2629 * registered listeners.
2630 *
2631 * @param annotation the annotation (<code>null</code> not permitted).
2632 *
2633 * @see #removeAnnotation(CategoryAnnotation)
2634 */
2635 public void addAnnotation(CategoryAnnotation annotation) {
2636 addAnnotation(annotation, true);
2637 }
2638
2639 /**
2640 * Adds an annotation to the plot and, if requested, sends a
2641 * {@link PlotChangeEvent} to all registered listeners.
2642 *
2643 * @param annotation the annotation (<code>null</code> not permitted).
2644 * @param notify notify listeners?
2645 *
2646 * @since 1.0.10
2647 */
2648 public void addAnnotation(CategoryAnnotation annotation, boolean notify) {
2649 if (annotation == null) {
2650 throw new IllegalArgumentException("Null 'annotation' argument.");
2651 }
2652 this.annotations.add(annotation);
2653 if (notify) {
2654 fireChangeEvent();
2655 }
2656 }
2657
2658 /**
2659 * Removes an annotation from the plot and sends a {@link PlotChangeEvent}
2660 * to all registered listeners.
2661 *
2662 * @param annotation the annotation (<code>null</code> not permitted).
2663 *
2664 * @return A boolean (indicates whether or not the annotation was removed).
2665 *
2666 * @see #addAnnotation(CategoryAnnotation)
2667 */
2668 public boolean removeAnnotation(CategoryAnnotation annotation) {
2669 return removeAnnotation(annotation, true);
2670 }
2671
2672 /**
2673 * Removes an annotation from the plot and, if requested, sends a
2674 * {@link PlotChangeEvent} to all registered listeners.
2675 *
2676 * @param annotation the annotation (<code>null</code> not permitted).
2677 * @param notify notify listeners?
2678 *
2679 * @return A boolean (indicates whether or not the annotation was removed).
2680 *
2681 * @since 1.0.10
2682 */
2683 public boolean removeAnnotation(CategoryAnnotation annotation,
2684 boolean notify) {
2685 if (annotation == null) {
2686 throw new IllegalArgumentException("Null 'annotation' argument.");
2687 }
2688 boolean removed = this.annotations.remove(annotation);
2689 if (removed && notify) {
2690 fireChangeEvent();
2691 }
2692 return removed;
2693 }
2694
2695 /**
2696 * Clears all the annotations and sends a {@link PlotChangeEvent} to all
2697 * registered listeners.
2698 */
2699 public void clearAnnotations() {
2700 this.annotations.clear();
2701 fireChangeEvent();
2702 }
2703
2704 /**
2705 * Calculates the space required for the domain axis/axes.
2706 *
2707 * @param g2 the graphics device.
2708 * @param plotArea the plot area.
2709 * @param space a carrier for the result (<code>null</code> permitted).
2710 *
2711 * @return The required space.
2712 */
2713 protected AxisSpace calculateDomainAxisSpace(Graphics2D g2,
2714 Rectangle2D plotArea,
2715 AxisSpace space) {
2716
2717 if (space == null) {
2718 space = new AxisSpace();
2719 }
2720
2721 // reserve some space for the domain axis...
2722 if (this.fixedDomainAxisSpace != null) {
2723 if (this.orientation == PlotOrientation.HORIZONTAL) {
2724 space.ensureAtLeast(
2725 this.fixedDomainAxisSpace.getLeft(), RectangleEdge.LEFT);
2726 space.ensureAtLeast(this.fixedDomainAxisSpace.getRight(),
2727 RectangleEdge.RIGHT);
2728 }
2729 else if (this.orientation == PlotOrientation.VERTICAL) {
2730 space.ensureAtLeast(this.fixedDomainAxisSpace.getTop(),
2731 RectangleEdge.TOP);
2732 space.ensureAtLeast(this.fixedDomainAxisSpace.getBottom(),
2733 RectangleEdge.BOTTOM);
2734 }
2735 }
2736 else {
2737 // reserve space for the primary domain axis...
2738 RectangleEdge domainEdge = Plot.resolveDomainAxisLocation(
2739 getDomainAxisLocation(), this.orientation);
2740 if (this.drawSharedDomainAxis) {
2741 space = getDomainAxis().reserveSpace(g2, this, plotArea,
2742 domainEdge, space);
2743 }
2744
2745 // reserve space for any domain axes...
2746 for (int i = 0; i < this.domainAxes.size(); i++) {
2747 Axis xAxis = (Axis) this.domainAxes.get(i);
2748 if (xAxis != null) {
2749 RectangleEdge edge = getDomainAxisEdge(i);
2750 space = xAxis.reserveSpace(g2, this, plotArea, edge, space);
2751 }
2752 }
2753 }
2754
2755 return space;
2756
2757 }
2758
2759 /**
2760 * Calculates the space required for the range axis/axes.
2761 *
2762 * @param g2 the graphics device.
2763 * @param plotArea the plot area.
2764 * @param space a carrier for the result (<code>null</code> permitted).
2765 *
2766 * @return The required space.
2767 */
2768 protected AxisSpace calculateRangeAxisSpace(Graphics2D g2,
2769 Rectangle2D plotArea,
2770 AxisSpace space) {
2771
2772 if (space == null) {
2773 space = new AxisSpace();
2774 }
2775
2776 // reserve some space for the range axis...
2777 if (this.fixedRangeAxisSpace != null) {
2778 if (this.orientation == PlotOrientation.HORIZONTAL) {
2779 space.ensureAtLeast(this.fixedRangeAxisSpace.getTop(),
2780 RectangleEdge.TOP);
2781 space.ensureAtLeast(this.fixedRangeAxisSpace.getBottom(),
2782 RectangleEdge.BOTTOM);
2783 }
2784 else if (this.orientation == PlotOrientation.VERTICAL) {
2785 space.ensureAtLeast(this.fixedRangeAxisSpace.getLeft(),
2786 RectangleEdge.LEFT);
2787 space.ensureAtLeast(this.fixedRangeAxisSpace.getRight(),
2788 RectangleEdge.RIGHT);
2789 }
2790 }
2791 else {
2792 // reserve space for the range axes (if any)...
2793 for (int i = 0; i < this.rangeAxes.size(); i++) {
2794 Axis yAxis = (Axis) this.rangeAxes.get(i);
2795 if (yAxis != null) {
2796 RectangleEdge edge = getRangeAxisEdge(i);
2797 space = yAxis.reserveSpace(g2, this, plotArea, edge, space);
2798 }
2799 }
2800 }
2801 return space;
2802
2803 }
2804
2805 /**
2806 * Calculates the space required for the axes.
2807 *
2808 * @param g2 the graphics device.
2809 * @param plotArea the plot area.
2810 *
2811 * @return The space required for the axes.
2812 */
2813 protected AxisSpace calculateAxisSpace(Graphics2D g2,
2814 Rectangle2D plotArea) {
2815 AxisSpace space = new AxisSpace();
2816 space = calculateRangeAxisSpace(g2, plotArea, space);
2817 space = calculateDomainAxisSpace(g2, plotArea, space);
2818 return space;
2819 }
2820
2821 /**
2822 * Draws the plot on a Java 2D graphics device (such as the screen or a
2823 * printer).
2824 * <P>
2825 * At your option, you may supply an instance of {@link PlotRenderingInfo}.
2826 * If you do, it will be populated with information about the drawing,
2827 * including various plot dimensions and tooltip info.
2828 *
2829 * @param g2 the graphics device.
2830 * @param area the area within which the plot (including axes) should
2831 * be drawn.
2832 * @param anchor the anchor point (<code>null</code> permitted).
2833 * @param parentState the state from the parent plot, if there is one.
2834 * @param state collects info as the chart is drawn (possibly
2835 * <code>null</code>).
2836 */
2837 public void draw(Graphics2D g2, Rectangle2D area,
2838 Point2D anchor,
2839 PlotState parentState,
2840 PlotRenderingInfo state) {
2841
2842 // if the plot area is too small, just return...
2843 boolean b1 = (area.getWidth() <= MINIMUM_WIDTH_TO_DRAW);
2844 boolean b2 = (area.getHeight() <= MINIMUM_HEIGHT_TO_DRAW);
2845 if (b1 || b2) {
2846 return;
2847 }
2848
2849 // record the plot area...
2850 if (state == null) {
2851 // if the incoming state is null, no information will be passed
2852 // back to the caller - but we create a temporary state to record
2853 // the plot area, since that is used later by the axes
2854 state = new PlotRenderingInfo(null);
2855 }
2856 state.setPlotArea(area);
2857
2858 // adjust the drawing area for the plot insets (if any)...
2859 RectangleInsets insets = getInsets();
2860 insets.trim(area);
2861
2862 // calculate the data area...
2863 AxisSpace space = calculateAxisSpace(g2, area);
2864 Rectangle2D dataArea = space.shrink(area, null);
2865 this.axisOffset.trim(dataArea);
2866
2867 state.setDataArea(dataArea);
2868
2869 // if there is a renderer, it draws the background, otherwise use the
2870 // default background...
2871 if (getRenderer() != null) {
2872 getRenderer().drawBackground(g2, this, dataArea);
2873 }
2874 else {
2875 drawBackground(g2, dataArea);
2876 }
2877
2878 Map axisStateMap = drawAxes(g2, area, dataArea, state);
2879
2880 // don't let anyone draw outside the data area
2881 Shape savedClip = g2.getClip();
2882 g2.clip(dataArea);
2883
2884 drawDomainGridlines(g2, dataArea);
2885
2886 AxisState rangeAxisState = (AxisState) axisStateMap.get(getRangeAxis());
2887 if (rangeAxisState == null) {
2888 if (parentState != null) {
2889 rangeAxisState = (AxisState) parentState.getSharedAxisStates()
2890 .get(getRangeAxis());
2891 }
2892 }
2893 if (rangeAxisState != null) {
2894 drawRangeGridlines(g2, dataArea, rangeAxisState.getTicks());
2895 }
2896
2897 // draw the markers...
2898 for (int i = 0; i < this.renderers.size(); i++) {
2899 drawDomainMarkers(g2, dataArea, i, Layer.BACKGROUND);
2900 }
2901 for (int i = 0; i < this.renderers.size(); i++) {
2902 drawRangeMarkers(g2, dataArea, i, Layer.BACKGROUND);
2903 }
2904
2905 // now render data items...
2906 boolean foundData = false;
2907
2908 // set up the alpha-transparency...
2909 Composite originalComposite = g2.getComposite();
2910 g2.setComposite(AlphaComposite.getInstance(
2911 AlphaComposite.SRC_OVER, getForegroundAlpha()));
2912
2913 DatasetRenderingOrder order = getDatasetRenderingOrder();
2914 if (order == DatasetRenderingOrder.FORWARD) {
2915 for (int i = 0; i < this.datasets.size(); i++) {
2916 foundData = render(g2, dataArea, i, state) || foundData;
2917 }
2918 }
2919 else { // DatasetRenderingOrder.REVERSE
2920 for (int i = this.datasets.size() - 1; i >= 0; i--) {
2921 foundData = render(g2, dataArea, i, state) || foundData;
2922 }
2923 }
2924 // draw the foreground markers...
2925 for (int i = 0; i < this.renderers.size(); i++) {
2926 drawDomainMarkers(g2, dataArea, i, Layer.FOREGROUND);
2927 }
2928 for (int i = 0; i < this.renderers.size(); i++) {
2929 drawRangeMarkers(g2, dataArea, i, Layer.FOREGROUND);
2930 }
2931
2932 // draw the annotations (if any)...
2933 drawAnnotations(g2, dataArea);
2934
2935 g2.setClip(savedClip);
2936 g2.setComposite(originalComposite);
2937
2938 if (!foundData) {
2939 drawNoDataMessage(g2, dataArea);
2940 }
2941
2942 // draw range crosshair if required...
2943 if (isRangeCrosshairVisible()) {
2944 // FIXME: this doesn't handle multiple range axes
2945 drawRangeCrosshair(g2, dataArea, getOrientation(),
2946 getRangeCrosshairValue(), getRangeAxis(),
2947 getRangeCrosshairStroke(), getRangeCrosshairPaint());
2948 }
2949
2950 // draw an outline around the plot area...
2951 if (getRenderer() != null) {
2952 getRenderer().drawOutline(g2, this, dataArea);
2953 }
2954 else {
2955 drawOutline(g2, dataArea);
2956 }
2957
2958 }
2959
2960 /**
2961 * Draws the plot background (the background color and/or image).
2962 * <P>
2963 * This method will be called during the chart drawing process and is
2964 * declared public so that it can be accessed by the renderers used by
2965 * certain subclasses. You shouldn't need to call this method directly.
2966 *
2967 * @param g2 the graphics device.
2968 * @param area the area within which the plot should be drawn.
2969 */
2970 public void drawBackground(Graphics2D g2, Rectangle2D area) {
2971 fillBackground(g2, area, this.orientation);
2972 drawBackgroundImage(g2, area);
2973 }
2974
2975 /**
2976 * A utility method for drawing the plot's axes.
2977 *
2978 * @param g2 the graphics device.
2979 * @param plotArea the plot area.
2980 * @param dataArea the data area.
2981 * @param plotState collects information about the plot (<code>null</code>
2982 * permitted).
2983 *
2984 * @return A map containing the axis states.
2985 */
2986 protected Map drawAxes(Graphics2D g2,
2987 Rectangle2D plotArea,
2988 Rectangle2D dataArea,
2989 PlotRenderingInfo plotState) {
2990
2991 AxisCollection axisCollection = new AxisCollection();
2992
2993 // add domain axes to lists...
2994 for (int index = 0; index < this.domainAxes.size(); index++) {
2995 CategoryAxis xAxis = (CategoryAxis) this.domainAxes.get(index);
2996 if (xAxis != null) {
2997 axisCollection.add(xAxis, getDomainAxisEdge(index));
2998 }
2999 }
3000
3001 // add range axes to lists...
3002 for (int index = 0; index < this.rangeAxes.size(); index++) {
3003 ValueAxis yAxis = (ValueAxis) this.rangeAxes.get(index);
3004 if (yAxis != null) {
3005 axisCollection.add(yAxis, getRangeAxisEdge(index));
3006 }
3007 }
3008
3009 Map axisStateMap = new HashMap();
3010
3011 // draw the top axes
3012 double cursor = dataArea.getMinY() - this.axisOffset.calculateTopOutset(
3013 dataArea.getHeight());
3014 Iterator iterator = axisCollection.getAxesAtTop().iterator();
3015 while (iterator.hasNext()) {
3016 Axis axis = (Axis) iterator.next();
3017 if (axis != null) {
3018 AxisState axisState = axis.draw(g2, cursor, plotArea, dataArea,
3019 RectangleEdge.TOP, plotState);
3020 cursor = axisState.getCursor();
3021 axisStateMap.put(axis, axisState);
3022 }
3023 }
3024
3025 // draw the bottom axes
3026 cursor = dataArea.getMaxY()
3027 + this.axisOffset.calculateBottomOutset(dataArea.getHeight());
3028 iterator = axisCollection.getAxesAtBottom().iterator();
3029 while (iterator.hasNext()) {
3030 Axis axis = (Axis) iterator.next();
3031 if (axis != null) {
3032 AxisState axisState = axis.draw(g2, cursor, plotArea, dataArea,
3033 RectangleEdge.BOTTOM, plotState);
3034 cursor = axisState.getCursor();
3035 axisStateMap.put(axis, axisState);
3036 }
3037 }
3038
3039 // draw the left axes
3040 cursor = dataArea.getMinX()
3041 - this.axisOffset.calculateLeftOutset(dataArea.getWidth());
3042 iterator = axisCollection.getAxesAtLeft().iterator();
3043 while (iterator.hasNext()) {
3044 Axis axis = (Axis) iterator.next();
3045 if (axis != null) {
3046 AxisState axisState = axis.draw(g2, cursor, plotArea, dataArea,
3047 RectangleEdge.LEFT, plotState);
3048 cursor = axisState.getCursor();
3049 axisStateMap.put(axis, axisState);
3050 }
3051 }
3052
3053 // draw the right axes
3054 cursor = dataArea.getMaxX()
3055 + this.axisOffset.calculateRightOutset(dataArea.getWidth());
3056 iterator = axisCollection.getAxesAtRight().iterator();
3057 while (iterator.hasNext()) {
3058 Axis axis = (Axis) iterator.next();
3059 if (axis != null) {
3060 AxisState axisState = axis.draw(g2, cursor, plotArea, dataArea,
3061 RectangleEdge.RIGHT, plotState);
3062 cursor = axisState.getCursor();
3063 axisStateMap.put(axis, axisState);
3064 }
3065 }
3066
3067 return axisStateMap;
3068
3069 }
3070
3071 /**
3072 * Draws a representation of a dataset within the dataArea region using the
3073 * appropriate renderer.
3074 *
3075 * @param g2 the graphics device.
3076 * @param dataArea the region in which the data is to be drawn.
3077 * @param index the dataset and renderer index.
3078 * @param info an optional object for collection dimension information.
3079 *
3080 * @return A boolean that indicates whether or not real data was found.
3081 */
3082 public boolean render(Graphics2D g2, Rectangle2D dataArea, int index,
3083 PlotRenderingInfo info) {
3084
3085 boolean foundData = false;
3086 CategoryDataset currentDataset = getDataset(index);
3087 CategoryItemRenderer renderer = getRenderer(index);
3088 CategoryAxis domainAxis = getDomainAxisForDataset(index);
3089 ValueAxis rangeAxis = getRangeAxisForDataset(index);
3090 boolean hasData = !DatasetUtilities.isEmptyOrNull(currentDataset);
3091 if (hasData && renderer != null) {
3092
3093 foundData = true;
3094 CategoryItemRendererState state = renderer.initialise(g2, dataArea,
3095 this, index, info);
3096 int columnCount = currentDataset.getColumnCount();
3097 int rowCount = currentDataset.getRowCount();
3098 int passCount = renderer.getPassCount();
3099 for (int pass = 0; pass < passCount; pass++) {
3100 if (this.columnRenderingOrder == SortOrder.ASCENDING) {
3101 for (int column = 0; column < columnCount; column++) {
3102 if (this.rowRenderingOrder == SortOrder.ASCENDING) {
3103 for (int row = 0; row < rowCount; row++) {
3104 renderer.drawItem(g2, state, dataArea, this,
3105 domainAxis, rangeAxis, currentDataset,
3106 row, column, pass);
3107 }
3108 }
3109 else {
3110 for (int row = rowCount - 1; row >= 0; row--) {
3111 renderer.drawItem(g2, state, dataArea, this,
3112 domainAxis, rangeAxis, currentDataset,
3113 row, column, pass);
3114 }
3115 }
3116 }
3117 }
3118 else {
3119 for (int column = columnCount - 1; column >= 0; column--) {
3120 if (this.rowRenderingOrder == SortOrder.ASCENDING) {
3121 for (int row = 0; row < rowCount; row++) {
3122 renderer.drawItem(g2, state, dataArea, this,
3123 domainAxis, rangeAxis, currentDataset,
3124 row, column, pass);
3125 }
3126 }
3127 else {
3128 for (int row = rowCount - 1; row >= 0; row--) {
3129 renderer.drawItem(g2, state, dataArea, this,
3130 domainAxis, rangeAxis, currentDataset,
3131 row, column, pass);
3132 }
3133 }
3134 }
3135 }
3136 }
3137 }
3138 return foundData;
3139
3140 }
3141
3142 /**
3143 * Draws the gridlines for the plot.
3144 *
3145 * @param g2 the graphics device.
3146 * @param dataArea the area inside the axes.
3147 *
3148 * @see #drawRangeGridlines(Graphics2D, Rectangle2D, List)
3149 */
3150 protected void drawDomainGridlines(Graphics2D g2, Rectangle2D dataArea) {
3151
3152 // draw the domain grid lines, if any...
3153 if (isDomainGridlinesVisible()) {
3154 CategoryAnchor anchor = getDomainGridlinePosition();
3155 RectangleEdge domainAxisEdge = getDomainAxisEdge();
3156 Stroke gridStroke = getDomainGridlineStroke();
3157 Paint gridPaint = getDomainGridlinePaint();
3158 if ((gridStroke != null) && (gridPaint != null)) {
3159 // iterate over the categories
3160 CategoryDataset data = getDataset();
3161 if (data != null) {
3162 CategoryAxis axis = getDomainAxis();
3163 if (axis != null) {
3164 int columnCount = data.getColumnCount();
3165 for (int c = 0; c < columnCount; c++) {
3166 double xx = axis.getCategoryJava2DCoordinate(
3167 anchor, c, columnCount, dataArea,
3168 domainAxisEdge);
3169 CategoryItemRenderer renderer1 = getRenderer();
3170 if (renderer1 != null) {
3171 renderer1.drawDomainGridline(g2, this,
3172 dataArea, xx);
3173 }
3174 }
3175 }
3176 }
3177 }
3178 }
3179 }
3180
3181 /**
3182 * Draws the gridlines for the plot.
3183 *
3184 * @param g2 the graphics device.
3185 * @param dataArea the area inside the axes.
3186 * @param ticks the ticks.
3187 *
3188 * @see #drawDomainGridlines(Graphics2D, Rectangle2D)
3189 */
3190 protected void drawRangeGridlines(Graphics2D g2, Rectangle2D dataArea,
3191 List ticks) {
3192 // draw the range grid lines, if any...
3193 if (isRangeGridlinesVisible()) {
3194 Stroke gridStroke = getRangeGridlineStroke();
3195 Paint gridPaint = getRangeGridlinePaint();
3196 if ((gridStroke != null) && (gridPaint != null)) {
3197 ValueAxis axis = getRangeAxis();
3198 if (axis != null) {
3199 Iterator iterator = ticks.iterator();
3200 while (iterator.hasNext()) {
3201 ValueTick tick = (ValueTick) iterator.next();
3202 CategoryItemRenderer renderer1 = getRenderer();
3203 if (renderer1 != null) {
3204 renderer1.drawRangeGridline(g2, this,
3205 getRangeAxis(), dataArea, tick.getValue());
3206 }
3207 }
3208 }
3209 }
3210 }
3211 }
3212
3213 /**
3214 * Draws the annotations.
3215 *
3216 * @param g2 the graphics device.
3217 * @param dataArea the data area.
3218 */
3219 protected void drawAnnotations(Graphics2D g2, Rectangle2D dataArea) {
3220
3221 if (getAnnotations() != null) {
3222 Iterator iterator = getAnnotations().iterator();
3223 while (iterator.hasNext()) {
3224 CategoryAnnotation annotation
3225 = (CategoryAnnotation) iterator.next();
3226 annotation.draw(g2, this, dataArea, getDomainAxis(),
3227 getRangeAxis());
3228 }
3229 }
3230
3231 }
3232
3233 /**
3234 * Draws the domain markers (if any) for an axis and layer. This method is
3235 * typically called from within the draw() method.
3236 *
3237 * @param g2 the graphics device.
3238 * @param dataArea the data area.
3239 * @param index the renderer index.
3240 * @param layer the layer (foreground or background).
3241 *
3242 * @see #drawRangeMarkers(Graphics2D, Rectangle2D, int, Layer)
3243 */
3244 protected void drawDomainMarkers(Graphics2D g2, Rectangle2D dataArea,
3245 int index, Layer layer) {
3246
3247 CategoryItemRenderer r = getRenderer(index);
3248 if (r == null) {
3249 return;
3250 }
3251
3252 Collection markers = getDomainMarkers(index, layer);
3253 CategoryAxis axis = getDomainAxisForDataset(index);
3254 if (markers != null && axis != null) {
3255 Iterator iterator = markers.iterator();
3256 while (iterator.hasNext()) {
3257 CategoryMarker marker = (CategoryMarker) iterator.next();
3258 r.drawDomainMarker(g2, this, axis, marker, dataArea);
3259 }
3260 }
3261
3262 }
3263
3264 /**
3265 * Draws the range markers (if any) for an axis and layer. This method is
3266 * typically called from within the draw() method.
3267 *
3268 * @param g2 the graphics device.
3269 * @param dataArea the data area.
3270 * @param index the renderer index.
3271 * @param layer the layer (foreground or background).
3272 *
3273 * @see #drawDomainMarkers(Graphics2D, Rectangle2D, int, Layer)
3274 */
3275 protected void drawRangeMarkers(Graphics2D g2, Rectangle2D dataArea,
3276 int index, Layer layer) {
3277
3278 CategoryItemRenderer r = getRenderer(index);
3279 if (r == null) {
3280 return;
3281 }
3282
3283 Collection markers = getRangeMarkers(index, layer);
3284 ValueAxis axis = getRangeAxisForDataset(index);
3285 if (markers != null && axis != null) {
3286 Iterator iterator = markers.iterator();
3287 while (iterator.hasNext()) {
3288 Marker marker = (Marker) iterator.next();
3289 r.drawRangeMarker(g2, this, axis, marker, dataArea);
3290 }
3291 }
3292
3293 }
3294
3295 /**
3296 * Utility method for drawing a line perpendicular to the range axis (used
3297 * for crosshairs).
3298 *
3299 * @param g2 the graphics device.
3300 * @param dataArea the area defined by the axes.
3301 * @param value the data value.
3302 * @param stroke the line stroke (<code>null</code> not permitted).
3303 * @param paint the line paint (<code>null</code> not permitted).
3304 */
3305 protected void drawRangeLine(Graphics2D g2, Rectangle2D dataArea,
3306 double value, Stroke stroke, Paint paint) {
3307
3308 double java2D = getRangeAxis().valueToJava2D(value, dataArea,
3309 getRangeAxisEdge());
3310 Line2D line = null;
3311 if (this.orientation == PlotOrientation.HORIZONTAL) {
3312 line = new Line2D.Double(java2D, dataArea.getMinY(), java2D,
3313 dataArea.getMaxY());
3314 }
3315 else if (this.orientation == PlotOrientation.VERTICAL) {
3316 line = new Line2D.Double(dataArea.getMinX(), java2D,
3317 dataArea.getMaxX(), java2D);
3318 }
3319 g2.setStroke(stroke);
3320 g2.setPaint(paint);
3321 g2.draw(line);
3322
3323 }
3324
3325 /**
3326 * Draws a range crosshair.
3327 *
3328 * @param g2 the graphics target.
3329 * @param dataArea the data area.
3330 * @param orientation the plot orientation.
3331 * @param value the crosshair value.
3332 * @param axis the axis against which the value is measured.
3333 * @param stroke the stroke used to draw the crosshair line.
3334 * @param paint the paint used to draw the crosshair line.
3335 *
3336 * @since 1.0.5
3337 */
3338 protected void drawRangeCrosshair(Graphics2D g2, Rectangle2D dataArea,
3339 PlotOrientation orientation, double value, ValueAxis axis,
3340 Stroke stroke, Paint paint) {
3341
3342 if (!axis.getRange().contains(value)) {
3343 return;
3344 }
3345 Line2D line = null;
3346 if (orientation == PlotOrientation.HORIZONTAL) {
3347 double xx = axis.valueToJava2D(value, dataArea,
3348 RectangleEdge.BOTTOM);
3349 line = new Line2D.Double(xx, dataArea.getMinY(), xx,
3350 dataArea.getMaxY());
3351 }
3352 else {
3353 double yy = axis.valueToJava2D(value, dataArea,
3354 RectangleEdge.LEFT);
3355 line = new Line2D.Double(dataArea.getMinX(), yy,
3356 dataArea.getMaxX(), yy);
3357 }
3358 g2.setStroke(stroke);
3359 g2.setPaint(paint);
3360 g2.draw(line);
3361
3362 }
3363
3364 /**
3365 * Returns the range of data values that will be plotted against the range
3366 * axis. If the dataset is <code>null</code>, this method returns
3367 * <code>null</code>.
3368 *
3369 * @param axis the axis.
3370 *
3371 * @return The data range.
3372 */
3373 public Range getDataRange(ValueAxis axis) {
3374
3375 Range result = null;
3376 List mappedDatasets = new ArrayList();
3377
3378 int rangeIndex = this.rangeAxes.indexOf(axis);
3379 if (rangeIndex >= 0) {
3380 mappedDatasets.addAll(datasetsMappedToRangeAxis(rangeIndex));
3381 }
3382 else if (axis == getRangeAxis()) {
3383 mappedDatasets.addAll(datasetsMappedToRangeAxis(0));
3384 }
3385
3386 // iterate through the datasets that map to the axis and get the union
3387 // of the ranges.
3388 Iterator iterator = mappedDatasets.iterator();
3389 while (iterator.hasNext()) {
3390 CategoryDataset d = (CategoryDataset) iterator.next();
3391 CategoryItemRenderer r = getRendererForDataset(d);
3392 if (r != null) {
3393 result = Range.combine(result, r.findRangeBounds(d));
3394 }
3395 }
3396 return result;
3397
3398 }
3399
3400 /**
3401 * Returns a list of the datasets that are mapped to the axis with the
3402 * specified index.
3403 *
3404 * @param axisIndex the axis index.
3405 *
3406 * @return The list (possibly empty, but never <code>null</code>).
3407 *
3408 * @since 1.0.3
3409 */
3410 private List datasetsMappedToDomainAxis(int axisIndex) {
3411 List result = new ArrayList();
3412 for (int datasetIndex = 0; datasetIndex < this.datasets.size();
3413 datasetIndex++) {
3414 Object dataset = this.datasets.get(datasetIndex);
3415 if (dataset != null) {
3416 Integer m = (Integer) this.datasetToDomainAxisMap.get(
3417 datasetIndex);
3418 if (m == null) { // a dataset with no mapping is assigned to
3419 // axis 0
3420 if (axisIndex == 0) {
3421 result.add(dataset);
3422 }
3423 }
3424 else {
3425 if (m.intValue() == axisIndex) {
3426 result.add(dataset);
3427 }
3428 }
3429 }
3430 }
3431 return result;
3432 }
3433
3434 /**
3435 * A utility method that returns a list of datasets that are mapped to a
3436 * given range axis.
3437 *
3438 * @param index the axis index.
3439 *
3440 * @return A list of datasets.
3441 */
3442 private List datasetsMappedToRangeAxis(int index) {
3443 List result = new ArrayList();
3444 for (int i = 0; i < this.datasets.size(); i++) {
3445 Object dataset = this.datasets.get(i);
3446 if (dataset != null) {
3447 Integer m = (Integer) this.datasetToRangeAxisMap.get(i);
3448 if (m == null) { // a dataset with no mapping is assigned to
3449 // axis 0
3450 if (index == 0) {
3451 result.add(dataset);
3452 }
3453 }
3454 else {
3455 if (m.intValue() == index) {
3456 result.add(dataset);
3457 }
3458 }
3459 }
3460 }
3461 return result;
3462 }
3463
3464 /**
3465 * Returns the weight for this plot when it is used as a subplot within a
3466 * combined plot.
3467 *
3468 * @return The weight.
3469 *
3470 * @see #setWeight(int)
3471 */
3472 public int getWeight() {
3473 return this.weight;
3474 }
3475
3476 /**
3477 * Sets the weight for the plot and sends a {@link PlotChangeEvent} to all
3478 * registered listeners.
3479 *
3480 * @param weight the weight.
3481 *
3482 * @see #getWeight()
3483 */
3484 public void setWeight(int weight) {
3485 this.weight = weight;
3486 fireChangeEvent();
3487 }
3488
3489 /**
3490 * Returns the fixed domain axis space.
3491 *
3492 * @return The fixed domain axis space (possibly <code>null</code>).
3493 *
3494 * @see #setFixedDomainAxisSpace(AxisSpace)
3495 */
3496 public AxisSpace getFixedDomainAxisSpace() {
3497 return this.fixedDomainAxisSpace;
3498 }
3499
3500 /**
3501 * Sets the fixed domain axis space and sends a {@link PlotChangeEvent} to
3502 * all registered listeners.
3503 *
3504 * @param space the space (<code>null</code> permitted).
3505 *
3506 * @see #getFixedDomainAxisSpace()
3507 */
3508 public void setFixedDomainAxisSpace(AxisSpace space) {
3509 setFixedDomainAxisSpace(space, true);
3510 }
3511
3512 /**
3513 * Sets the fixed domain axis space and sends a {@link PlotChangeEvent} to
3514 * all registered listeners.
3515 *
3516 * @param space the space (<code>null</code> permitted).
3517 * @param notify notify listeners?
3518 *
3519 * @see #getFixedDomainAxisSpace()
3520 *
3521 * @since 1.0.7
3522 */
3523 public void setFixedDomainAxisSpace(AxisSpace space, boolean notify) {
3524 this.fixedDomainAxisSpace = space;
3525 if (notify) {
3526 fireChangeEvent();
3527 }
3528 }
3529
3530 /**
3531 * Returns the fixed range axis space.
3532 *
3533 * @return The fixed range axis space (possibly <code>null</code>).
3534 *
3535 * @see #setFixedRangeAxisSpace(AxisSpace)
3536 */
3537 public AxisSpace getFixedRangeAxisSpace() {
3538 return this.fixedRangeAxisSpace;
3539 }
3540
3541 /**
3542 * Sets the fixed range axis space and sends a {@link PlotChangeEvent} to
3543 * all registered listeners.
3544 *
3545 * @param space the space (<code>null</code> permitted).
3546 *
3547 * @see #getFixedRangeAxisSpace()
3548 */
3549 public void setFixedRangeAxisSpace(AxisSpace space) {
3550 setFixedRangeAxisSpace(space, true);
3551 }
3552
3553 /**
3554 * Sets the fixed range axis space and sends a {@link PlotChangeEvent} to
3555 * all registered listeners.
3556 *
3557 * @param space the space (<code>null</code> permitted).
3558 * @param notify notify listeners?
3559 *
3560 * @see #getFixedRangeAxisSpace()
3561 *
3562 * @since 1.0.7
3563 */
3564 public void setFixedRangeAxisSpace(AxisSpace space, boolean notify) {
3565 this.fixedRangeAxisSpace = space;
3566 if (notify) {
3567 fireChangeEvent();
3568 }
3569 }
3570
3571 /**
3572 * Returns a list of the categories in the plot's primary dataset.
3573 *
3574 * @return A list of the categories in the plot's primary dataset.
3575 *
3576 * @see #getCategoriesForAxis(CategoryAxis)
3577 */
3578 public List getCategories() {
3579 List result = null;
3580 if (getDataset() != null) {
3581 result = Collections.unmodifiableList(getDataset().getColumnKeys());
3582 }
3583 return result;
3584 }
3585
3586 /**
3587 * Returns a list of the categories that should be displayed for the
3588 * specified axis.
3589 *
3590 * @param axis the axis (<code>null</code> not permitted)
3591 *
3592 * @return The categories.
3593 *
3594 * @since 1.0.3
3595 */
3596 public List getCategoriesForAxis(CategoryAxis axis) {
3597 List result = new ArrayList();
3598 int axisIndex = this.domainAxes.indexOf(axis);
3599 List datasets = datasetsMappedToDomainAxis(axisIndex);
3600 Iterator iterator = datasets.iterator();
3601 while (iterator.hasNext()) {
3602 CategoryDataset dataset = (CategoryDataset) iterator.next();
3603 // add the unique categories from this dataset
3604 for (int i = 0; i < dataset.getColumnCount(); i++) {
3605 Comparable category = dataset.getColumnKey(i);
3606 if (!result.contains(category)) {
3607 result.add(category);
3608 }
3609 }
3610 }
3611 return result;
3612 }
3613
3614 /**
3615 * Returns the flag that controls whether or not the shared domain axis is
3616 * drawn for each subplot.
3617 *
3618 * @return A boolean.
3619 *
3620 * @see #setDrawSharedDomainAxis(boolean)
3621 */
3622 public boolean getDrawSharedDomainAxis() {
3623 return this.drawSharedDomainAxis;
3624 }
3625
3626 /**
3627 * Sets the flag that controls whether the shared domain axis is drawn when
3628 * this plot is being used as a subplot.
3629 *
3630 * @param draw a boolean.
3631 *
3632 * @see #getDrawSharedDomainAxis()
3633 */
3634 public void setDrawSharedDomainAxis(boolean draw) {
3635 this.drawSharedDomainAxis = draw;
3636 fireChangeEvent();
3637 }
3638
3639 /**
3640 * Returns <code>false</code> to indicate that the domain axes are not
3641 * zoomable.
3642 *
3643 * @return A boolean.
3644 *
3645 * @see #isRangeZoomable()
3646 */
3647 public boolean isDomainZoomable() {
3648 return false;
3649 }
3650
3651 /**
3652 * Returns <code>true</code> to indicate that the range axes are zoomable.
3653 *
3654 * @return A boolean.
3655 *
3656 * @see #isDomainZoomable()
3657 */
3658 public boolean isRangeZoomable() {
3659 return true;
3660 }
3661
3662 /**
3663 * This method does nothing, because <code>CategoryPlot</code> doesn't
3664 * support zooming on the domain.
3665 *
3666 * @param factor the zoom factor.
3667 * @param state the plot state.
3668 * @param source the source point (in Java2D space) for the zoom.
3669 */
3670 public void zoomDomainAxes(double factor, PlotRenderingInfo state,
3671 Point2D source) {
3672 // can't zoom domain axis
3673 }
3674
3675 /**
3676 * This method does nothing, because <code>CategoryPlot</code> doesn't
3677 * support zooming on the domain.
3678 *
3679 * @param lowerPercent the lower bound.
3680 * @param upperPercent the upper bound.
3681 * @param state the plot state.
3682 * @param source the source point (in Java2D space) for the zoom.
3683 */
3684 public void zoomDomainAxes(double lowerPercent, double upperPercent,
3685 PlotRenderingInfo state, Point2D source) {
3686 // can't zoom domain axis
3687 }
3688
3689 /**
3690 * This method does nothing, because <code>CategoryPlot</code> doesn't
3691 * support zooming on the domain.
3692 *
3693 * @param factor the zoom factor.
3694 * @param info the plot rendering info.
3695 * @param source the source point (in Java2D space).
3696 * @param useAnchor use source point as zoom anchor?
3697 *
3698 * @see #zoomRangeAxes(double, PlotRenderingInfo, Point2D, boolean)
3699 *
3700 * @since 1.0.7
3701 */
3702 public void zoomDomainAxes(double factor, PlotRenderingInfo info,
3703 Point2D source, boolean useAnchor) {
3704 // can't zoom domain axis
3705 }
3706
3707 /**
3708 * Multiplies the range on the range axis/axes by the specified factor.
3709 *
3710 * @param factor the zoom factor.
3711 * @param state the plot state.
3712 * @param source the source point (in Java2D space) for the zoom.
3713 */
3714 public void zoomRangeAxes(double factor, PlotRenderingInfo state,
3715 Point2D source) {
3716 // delegate to other method
3717 zoomRangeAxes(factor, state, source, false);
3718 }
3719
3720 /**
3721 * Multiplies the range on the range axis/axes by the specified factor.
3722 *
3723 * @param factor the zoom factor.
3724 * @param info the plot rendering info.
3725 * @param source the source point.
3726 * @param useAnchor a flag that controls whether or not the source point
3727 * is used for the zoom anchor.
3728 *
3729 * @see #zoomDomainAxes(double, PlotRenderingInfo, Point2D, boolean)
3730 *
3731 * @since 1.0.7
3732 */
3733 public void zoomRangeAxes(double factor, PlotRenderingInfo info,
3734 Point2D source, boolean useAnchor) {
3735
3736 // perform the zoom on each range axis
3737 for (int i = 0; i < this.rangeAxes.size(); i++) {
3738 ValueAxis rangeAxis = (ValueAxis) this.rangeAxes.get(i);
3739 if (rangeAxis != null) {
3740 if (useAnchor) {
3741 // get the relevant source coordinate given the plot
3742 // orientation
3743 double sourceY = source.getY();
3744 if (this.orientation == PlotOrientation.HORIZONTAL) {
3745 sourceY = source.getX();
3746 }
3747 double anchorY = rangeAxis.java2DToValue(sourceY,
3748 info.getDataArea(), getRangeAxisEdge());
3749 rangeAxis.resizeRange(factor, anchorY);
3750 }
3751 else {
3752 rangeAxis.resizeRange(factor);
3753 }
3754 }
3755 }
3756 }
3757
3758 /**
3759 * Zooms in on the range axes.
3760 *
3761 * @param lowerPercent the lower bound.
3762 * @param upperPercent the upper bound.
3763 * @param state the plot state.
3764 * @param source the source point (in Java2D space) for the zoom.
3765 */
3766 public void zoomRangeAxes(double lowerPercent, double upperPercent,
3767 PlotRenderingInfo state, Point2D source) {
3768 for (int i = 0; i < this.rangeAxes.size(); i++) {
3769 ValueAxis rangeAxis = (ValueAxis) this.rangeAxes.get(i);
3770 if (rangeAxis != null) {
3771 rangeAxis.zoomRange(lowerPercent, upperPercent);
3772 }
3773 }
3774 }
3775
3776 /**
3777 * Returns the anchor value.
3778 *
3779 * @return The anchor value.
3780 *
3781 * @see #setAnchorValue(double)
3782 */
3783 public double getAnchorValue() {
3784 return this.anchorValue;
3785 }
3786
3787 /**
3788 * Sets the anchor value and sends a {@link PlotChangeEvent} to all
3789 * registered listeners.
3790 *
3791 * @param value the anchor value.
3792 *
3793 * @see #getAnchorValue()
3794 */
3795 public void setAnchorValue(double value) {
3796 setAnchorValue(value, true);
3797 }
3798
3799 /**
3800 * Sets the anchor value and, if requested, sends a {@link PlotChangeEvent}
3801 * to all registered listeners.
3802 *
3803 * @param value the value.
3804 * @param notify notify listeners?
3805 *
3806 * @see #getAnchorValue()
3807 */
3808 public void setAnchorValue(double value, boolean notify) {
3809 this.anchorValue = value;
3810 if (notify) {
3811 fireChangeEvent();
3812 }
3813 }
3814
3815 /**
3816 * Tests the plot for equality with an arbitrary object.
3817 *
3818 * @param obj the object to test against (<code>null</code> permitted).
3819 *
3820 * @return A boolean.
3821 */
3822 public boolean equals(Object obj) {
3823
3824 if (obj == this) {
3825 return true;
3826 }
3827 if (!(obj instanceof CategoryPlot)) {
3828 return false;
3829 }
3830 CategoryPlot that = (CategoryPlot) obj;
3831 if (this.orientation != that.orientation) {
3832 return false;
3833 }
3834 if (!ObjectUtilities.equal(this.axisOffset, that.axisOffset)) {
3835 return false;
3836 }
3837 if (!this.domainAxes.equals(that.domainAxes)) {
3838 return false;
3839 }
3840 if (!this.domainAxisLocations.equals(that.domainAxisLocations)) {
3841 return false;
3842 }
3843 if (this.drawSharedDomainAxis != that.drawSharedDomainAxis) {
3844 return false;
3845 }
3846 if (!this.rangeAxes.equals(that.rangeAxes)) {
3847 return false;
3848 }
3849 if (!this.rangeAxisLocations.equals(that.rangeAxisLocations)) {
3850 return false;
3851 }
3852 if (!ObjectUtilities.equal(this.datasetToDomainAxisMap,
3853 that.datasetToDomainAxisMap)) {
3854 return false;
3855 }
3856 if (!ObjectUtilities.equal(this.datasetToRangeAxisMap,
3857 that.datasetToRangeAxisMap)) {
3858 return false;
3859 }
3860 if (!ObjectUtilities.equal(this.renderers, that.renderers)) {
3861 return false;
3862 }
3863 if (this.renderingOrder != that.renderingOrder) {
3864 return false;
3865 }
3866 if (this.columnRenderingOrder != that.columnRenderingOrder) {
3867 return false;
3868 }
3869 if (this.rowRenderingOrder != that.rowRenderingOrder) {
3870 return false;
3871 }
3872 if (this.domainGridlinesVisible != that.domainGridlinesVisible) {
3873 return false;
3874 }
3875 if (this.domainGridlinePosition != that.domainGridlinePosition) {
3876 return false;
3877 }
3878 if (!ObjectUtilities.equal(this.domainGridlineStroke,
3879 that.domainGridlineStroke)) {
3880 return false;
3881 }
3882 if (!PaintUtilities.equal(this.domainGridlinePaint,
3883 that.domainGridlinePaint)) {
3884 return false;
3885 }
3886 if (this.rangeGridlinesVisible != that.rangeGridlinesVisible) {
3887 return false;
3888 }
3889 if (!ObjectUtilities.equal(this.rangeGridlineStroke,
3890 that.rangeGridlineStroke)) {
3891 return false;
3892 }
3893 if (!PaintUtilities.equal(this.rangeGridlinePaint,
3894 that.rangeGridlinePaint)) {
3895 return false;
3896 }
3897 if (this.anchorValue != that.anchorValue) {
3898 return false;
3899 }
3900 if (this.rangeCrosshairVisible != that.rangeCrosshairVisible) {
3901 return false;
3902 }
3903 if (this.rangeCrosshairValue != that.rangeCrosshairValue) {
3904 return false;
3905 }
3906 if (!ObjectUtilities.equal(this.rangeCrosshairStroke,
3907 that.rangeCrosshairStroke)) {
3908 return false;
3909 }
3910 if (!PaintUtilities.equal(this.rangeCrosshairPaint,
3911 that.rangeCrosshairPaint)) {
3912 return false;
3913 }
3914 if (this.rangeCrosshairLockedOnData
3915 != that.rangeCrosshairLockedOnData) {
3916 return false;
3917 }
3918 if (!ObjectUtilities.equal(this.foregroundDomainMarkers,
3919 that.foregroundDomainMarkers)) {
3920 return false;
3921 }
3922 if (!ObjectUtilities.equal(this.backgroundDomainMarkers,
3923 that.backgroundDomainMarkers)) {
3924 return false;
3925 }
3926 if (!ObjectUtilities.equal(this.foregroundRangeMarkers,
3927 that.foregroundRangeMarkers)) {
3928 return false;
3929 }
3930 if (!ObjectUtilities.equal(this.backgroundRangeMarkers,
3931 that.backgroundRangeMarkers)) {
3932 return false;
3933 }
3934 if (!ObjectUtilities.equal(this.annotations, that.annotations)) {
3935 return false;
3936 }
3937 if (this.weight != that.weight) {
3938 return false;
3939 }
3940 if (!ObjectUtilities.equal(this.fixedDomainAxisSpace,
3941 that.fixedDomainAxisSpace)) {
3942 return false;
3943 }
3944 if (!ObjectUtilities.equal(this.fixedRangeAxisSpace,
3945 that.fixedRangeAxisSpace)) {
3946 return false;
3947 }
3948 if (!ObjectUtilities.equal(this.fixedLegendItems,
3949 that.fixedLegendItems)) {
3950 return false;
3951 }
3952
3953 return super.equals(obj);
3954
3955 }
3956
3957 /**
3958 * Returns a clone of the plot.
3959 *
3960 * @return A clone.
3961 *
3962 * @throws CloneNotSupportedException if the cloning is not supported.
3963 */
3964 public Object clone() throws CloneNotSupportedException {
3965
3966 CategoryPlot clone = (CategoryPlot) super.clone();
3967
3968 clone.domainAxes = new ObjectList();
3969 for (int i = 0; i < this.domainAxes.size(); i++) {
3970 CategoryAxis xAxis = (CategoryAxis) this.domainAxes.get(i);
3971 if (xAxis != null) {
3972 CategoryAxis clonedAxis = (CategoryAxis) xAxis.clone();
3973 clone.setDomainAxis(i, clonedAxis);
3974 }
3975 }
3976 clone.domainAxisLocations
3977 = (ObjectList) this.domainAxisLocations.clone();
3978
3979 clone.rangeAxes = new ObjectList();
3980 for (int i = 0; i < this.rangeAxes.size(); i++) {
3981 ValueAxis yAxis = (ValueAxis) this.rangeAxes.get(i);
3982 if (yAxis != null) {
3983 ValueAxis clonedAxis = (ValueAxis) yAxis.clone();
3984 clone.setRangeAxis(i, clonedAxis);
3985 }
3986 }
3987 clone.rangeAxisLocations = (ObjectList) this.rangeAxisLocations.clone();
3988
3989 clone.datasets = (ObjectList) this.datasets.clone();
3990 for (int i = 0; i < clone.datasets.size(); i++) {
3991 CategoryDataset dataset = clone.getDataset(i);
3992 if (dataset != null) {
3993 dataset.addChangeListener(clone);
3994 }
3995 }
3996 clone.datasetToDomainAxisMap
3997 = (ObjectList) this.datasetToDomainAxisMap.clone();
3998 clone.datasetToRangeAxisMap
3999 = (ObjectList) this.datasetToRangeAxisMap.clone();
4000 clone.renderers = (ObjectList) this.renderers.clone();
4001 if (this.fixedDomainAxisSpace != null) {
4002 clone.fixedDomainAxisSpace = (AxisSpace) ObjectUtilities.clone(
4003 this.fixedDomainAxisSpace);
4004 }
4005 if (this.fixedRangeAxisSpace != null) {
4006 clone.fixedRangeAxisSpace = (AxisSpace) ObjectUtilities.clone(
4007 this.fixedRangeAxisSpace);
4008 }
4009
4010 clone.annotations = (List) ObjectUtilities.deepClone(this.annotations);
4011 clone.foregroundDomainMarkers = cloneMarkerMap(
4012 this.foregroundDomainMarkers);
4013 clone.backgroundDomainMarkers = cloneMarkerMap(
4014 this.backgroundDomainMarkers);
4015 clone.foregroundRangeMarkers = cloneMarkerMap(
4016 this.foregroundRangeMarkers);
4017 clone.backgroundRangeMarkers = cloneMarkerMap(
4018 this.backgroundRangeMarkers);
4019 if (this.fixedLegendItems != null) {
4020 clone.fixedLegendItems
4021 = (LegendItemCollection) this.fixedLegendItems.clone();
4022 }
4023 return clone;
4024
4025 }
4026
4027 /**
4028 * A utility method to clone the marker maps.
4029 *
4030 * @param map the map to clone.
4031 *
4032 * @return A clone of the map.
4033 *
4034 * @throws CloneNotSupportedException if there is some problem cloning the
4035 * map.
4036 */
4037 private Map cloneMarkerMap(Map map) throws CloneNotSupportedException {
4038 Map clone = new HashMap();
4039 Set keys = map.keySet();
4040 Iterator iterator = keys.iterator();
4041 while (iterator.hasNext()) {
4042 Object key = iterator.next();
4043 List entry = (List) map.get(key);
4044 Object toAdd = ObjectUtilities.deepClone(entry);
4045 clone.put(key, toAdd);
4046 }
4047 return clone;
4048 }
4049
4050 /**
4051 * Provides serialization support.
4052 *
4053 * @param stream the output stream.
4054 *
4055 * @throws IOException if there is an I/O error.
4056 */
4057 private void writeObject(ObjectOutputStream stream) throws IOException {
4058 stream.defaultWriteObject();
4059 SerialUtilities.writeStroke(this.domainGridlineStroke, stream);
4060 SerialUtilities.writePaint(this.domainGridlinePaint, stream);
4061 SerialUtilities.writeStroke(this.rangeGridlineStroke, stream);
4062 SerialUtilities.writePaint(this.rangeGridlinePaint, stream);
4063 SerialUtilities.writeStroke(this.rangeCrosshairStroke, stream);
4064 SerialUtilities.writePaint(this.rangeCrosshairPaint, stream);
4065 }
4066
4067 /**
4068 * Provides serialization support.
4069 *
4070 * @param stream the input stream.
4071 *
4072 * @throws IOException if there is an I/O error.
4073 * @throws ClassNotFoundException if there is a classpath problem.
4074 */
4075 private void readObject(ObjectInputStream stream)
4076 throws IOException, ClassNotFoundException {
4077
4078 stream.defaultReadObject();
4079 this.domainGridlineStroke = SerialUtilities.readStroke(stream);
4080 this.domainGridlinePaint = SerialUtilities.readPaint(stream);
4081 this.rangeGridlineStroke = SerialUtilities.readStroke(stream);
4082 this.rangeGridlinePaint = SerialUtilities.readPaint(stream);
4083 this.rangeCrosshairStroke = SerialUtilities.readStroke(stream);
4084 this.rangeCrosshairPaint = SerialUtilities.readPaint(stream);
4085
4086 for (int i = 0; i < this.domainAxes.size(); i++) {
4087 CategoryAxis xAxis = (CategoryAxis) this.domainAxes.get(i);
4088 if (xAxis != null) {
4089 xAxis.setPlot(this);
4090 xAxis.addChangeListener(this);
4091 }
4092 }
4093 for (int i = 0; i < this.rangeAxes.size(); i++) {
4094 ValueAxis yAxis = (ValueAxis) this.rangeAxes.get(i);
4095 if (yAxis != null) {
4096 yAxis.setPlot(this);
4097 yAxis.addChangeListener(this);
4098 }
4099 }
4100 int datasetCount = this.datasets.size();
4101 for (int i = 0; i < datasetCount; i++) {
4102 Dataset dataset = (Dataset) this.datasets.get(i);
4103 if (dataset != null) {
4104 dataset.addChangeListener(this);
4105 }
4106 }
4107 int rendererCount = this.renderers.size();
4108 for (int i = 0; i < rendererCount; i++) {
4109 CategoryItemRenderer renderer
4110 = (CategoryItemRenderer) this.renderers.get(i);
4111 if (renderer != null) {
4112 renderer.addChangeListener(this);
4113 }
4114 }
4115
4116 }
4117
4118 }