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