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