Source for org.jfree.chart.plot.CombinedRangeXYPlot

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
   6:  *
   7:  * Project Info:  http://www.jfree.org/jfreechart/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  *
  27:  * ------------------------
  28:  * CombinedRangeXYPlot.java
  29:  * ------------------------
  30:  * (C) Copyright 2001-2007, by Bill Kelemen and Contributors.
  31:  *
  32:  * Original Author:  Bill Kelemen;
  33:  * Contributor(s):   David Gilbert (for Object Refinery Limited);
  34:  *                   Anthony Boulestreau;
  35:  *                   David Basten;
  36:  *                   Kevin Frechette (for ISTI);
  37:  *                   Arnaud Lelievre;
  38:  *                   Nicolas Brodu;
  39:  *                   Petr Kubanek (bug 1606205);
  40:  *
  41:  * Changes:
  42:  * --------
  43:  * 06-Dec-2001 : Version 1 (BK);
  44:  * 12-Dec-2001 : Removed unnecessary 'throws' clause from constructor (DG);
  45:  * 18-Dec-2001 : Added plotArea attribute and get/set methods (BK);
  46:  * 22-Dec-2001 : Fixed bug in chartChanged with multiple combinations of 
  47:  *               CombinedPlots (BK);
  48:  * 08-Jan-2002 : Moved to new package com.jrefinery.chart.combination (DG);
  49:  * 25-Feb-2002 : Updated import statements (DG);
  50:  * 28-Feb-2002 : Readded "this.plotArea = plotArea" that was deleted from 
  51:  *               draw() method (BK);
  52:  * 26-Mar-2002 : Added an empty zoom method (this method needs to be written 
  53:  *               so that combined plots will support zooming (DG);
  54:  * 29-Mar-2002 : Changed the method createCombinedAxis adding the creation of 
  55:  *               OverlaidSymbolicAxis and CombinedSymbolicAxis(AB);
  56:  * 23-Apr-2002 : Renamed CombinedPlot-->MultiXYPlot, and simplified the 
  57:  *               structure (DG);
  58:  * 23-May-2002 : Renamed (again) MultiXYPlot-->CombinedXYPlot (DG);
  59:  * 19-Jun-2002 : Added get/setGap() methods suggested by David Basten (DG);
  60:  * 25-Jun-2002 : Removed redundant imports (DG);
  61:  * 16-Jul-2002 : Draws shared axis after subplots (to fix missing gridlines),
  62:  *               added overrides of 'setSeriesPaint()' and 'setXYItemRenderer()'
  63:  *               that pass changes down to subplots (KF);
  64:  * 09-Oct-2002 : Added add(XYPlot) method (DG);
  65:  * 26-Mar-2003 : Implemented Serializable (DG);
  66:  * 16-May-2003 : Renamed CombinedXYPlot --> CombinedRangeXYPlot (DG);
  67:  * 26-Jun-2003 : Fixed bug 755547 (DG);
  68:  * 16-Jul-2003 : Removed getSubPlots() method (duplicate of getSubplots()) (DG);
  69:  * 08-Aug-2003 : Adjusted totalWeight in remove() method (DG);
  70:  * 21-Aug-2003 : Implemented Cloneable (DG);
  71:  * 08-Sep-2003 : Added internationalization via use of properties 
  72:  *               resourceBundle (RFE 690236) (AL); 
  73:  * 11-Sep-2003 : Fix cloning support (subplots) (NB);
  74:  * 15-Sep-2003 : Fixed error in cloning (DG);
  75:  * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
  76:  * 17-Sep-2003 : Updated handling of 'clicks' (DG);
  77:  * 12-Nov-2004 : Implements the new Zoomable interface (DG);
  78:  * 25-Nov-2004 : Small update to clone() implementation (DG);
  79:  * 21-Feb-2005 : The getLegendItems() method now returns the fixed legend
  80:  *               items if set (DG);
  81:  * 05-May-2005 : Removed unused draw() method (DG);
  82:  * ------------- JFREECHART 1.0.x ---------------------------------------------
  83:  * 13-Sep-2006 : Updated API docs (DG);
  84:  * 06-Feb-2007 : Fixed bug 1606205, draw shared axis after subplots (DG);
  85:  * 23-Mar-2007 : Reverted previous patch (DG);
  86:  * 17-Apr-2007 : Added null argument checks to findSubplot() (DG);
  87:  * 18-Jul-2007 : Fixed bug in removeSubplot (DG);
  88:  * 
  89:  */
  90: 
  91: package org.jfree.chart.plot;
  92: 
  93: import java.awt.Graphics2D;
  94: import java.awt.geom.Point2D;
  95: import java.awt.geom.Rectangle2D;
  96: import java.io.Serializable;
  97: import java.util.Collections;
  98: import java.util.Iterator;
  99: import java.util.List;
 100: 
 101: import org.jfree.chart.LegendItemCollection;
 102: import org.jfree.chart.axis.AxisSpace;
 103: import org.jfree.chart.axis.AxisState;
 104: import org.jfree.chart.axis.NumberAxis;
 105: import org.jfree.chart.axis.ValueAxis;
 106: import org.jfree.chart.event.PlotChangeEvent;
 107: import org.jfree.chart.event.PlotChangeListener;
 108: import org.jfree.chart.renderer.xy.XYItemRenderer;
 109: import org.jfree.data.Range;
 110: import org.jfree.ui.RectangleEdge;
 111: import org.jfree.ui.RectangleInsets;
 112: import org.jfree.util.ObjectUtilities;
 113: import org.jfree.util.PublicCloneable;
 114: 
 115: /**
 116:  * An extension of {@link XYPlot} that contains multiple subplots that share a 
 117:  * common range axis.
 118:  */
 119: public class CombinedRangeXYPlot extends XYPlot 
 120:                                  implements Zoomable,
 121:                                             Cloneable, PublicCloneable, 
 122:                                             Serializable,
 123:                                             PlotChangeListener {
 124: 
 125:     /** For serialization. */
 126:     private static final long serialVersionUID = -5177814085082031168L;
 127:     
 128:     /** Storage for the subplot references. */
 129:     private List subplots;
 130: 
 131:     /** Total weight of all charts. */
 132:     private int totalWeight = 0;
 133: 
 134:     /** The gap between subplots. */
 135:     private double gap = 5.0;
 136: 
 137:     /** Temporary storage for the subplot areas. */
 138:     private transient Rectangle2D[] subplotAreas;
 139: 
 140:     /**
 141:      * Default constructor.
 142:      */
 143:     public CombinedRangeXYPlot() {
 144:         this(new NumberAxis());
 145:     }
 146:     
 147:     /**
 148:      * Creates a new plot.
 149:      *
 150:      * @param rangeAxis  the shared axis.
 151:      */
 152:     public CombinedRangeXYPlot(ValueAxis rangeAxis) {
 153: 
 154:         super(null, // no data in the parent plot
 155:               null,
 156:               rangeAxis,
 157:               null);
 158: 
 159:         this.subplots = new java.util.ArrayList();
 160: 
 161:     }
 162: 
 163:     /**
 164:      * Returns a string describing the type of plot.
 165:      *
 166:      * @return The type of plot.
 167:      */
 168:     public String getPlotType() {
 169:         return localizationResources.getString("Combined_Range_XYPlot");
 170:     }
 171: 
 172:     /**
 173:      * Returns the space between subplots.
 174:      *
 175:      * @return The gap
 176:      */
 177:     public double getGap() {
 178:         return this.gap;
 179:     }
 180: 
 181:     /**
 182:      * Sets the amount of space between subplots.
 183:      *
 184:      * @param gap  the gap between subplots
 185:      */
 186:     public void setGap(double gap) {
 187:         this.gap = gap;
 188:     }
 189: 
 190:     /**
 191:      * Adds a subplot, with a default 'weight' of 1.
 192:      * <br><br>
 193:      * You must ensure that the subplot has a non-null domain axis.  The range
 194:      * axis for the subplot will be set to <code>null</code>.  
 195:      *
 196:      * @param subplot  the subplot.
 197:      */
 198:     public void add(XYPlot subplot) {
 199:         add(subplot, 1);
 200:     }
 201: 
 202:     /**
 203:      * Adds a subplot with a particular weight (greater than or equal to one).  
 204:      * The weight determines how much space is allocated to the subplot 
 205:      * relative to all the other subplots.
 206:      * <br><br>
 207:      * You must ensure that the subplot has a non-null domain axis.  The range
 208:      * axis for the subplot will be set to <code>null</code>.  
 209:      *
 210:      * @param subplot  the subplot.
 211:      * @param weight  the weight (must be 1 or greater).
 212:      */
 213:     public void add(XYPlot subplot, int weight) {
 214: 
 215:         // verify valid weight
 216:         if (weight <= 0) {
 217:             String msg = "The 'weight' must be positive.";
 218:             throw new IllegalArgumentException(msg);
 219:         }
 220: 
 221:         // store the plot and its weight
 222:         subplot.setParent(this);
 223:         subplot.setWeight(weight);
 224:         subplot.setInsets(new RectangleInsets(0.0, 0.0, 0.0, 0.0));
 225:         subplot.setRangeAxis(null);
 226:         subplot.addChangeListener(this);
 227:         this.subplots.add(subplot);
 228: 
 229:         // keep track of total weights
 230:         this.totalWeight += weight;
 231:         configureRangeAxes();
 232:         notifyListeners(new PlotChangeEvent(this));
 233: 
 234:     }
 235: 
 236:     /**
 237:      * Removes a subplot from the combined chart.
 238:      *
 239:      * @param subplot  the subplot (<code>null</code> not permitted).
 240:      */
 241:     public void remove(XYPlot subplot) {
 242:         if (subplot == null) {
 243:             throw new IllegalArgumentException(" Null 'subplot' argument.");   
 244:         }
 245:         int position = -1;
 246:         int size = this.subplots.size();
 247:         int i = 0;
 248:         while (position == -1 && i < size) {
 249:             if (this.subplots.get(i) == subplot) {
 250:                 position = i;
 251:             }
 252:             i++;
 253:         }
 254:         if (position != -1) {
 255:             this.subplots.remove(position);
 256:             subplot.setParent(null);
 257:             subplot.removeChangeListener(this);
 258:             this.totalWeight -= subplot.getWeight();
 259:             configureRangeAxes();
 260:             notifyListeners(new PlotChangeEvent(this));
 261:         }
 262:     }
 263: 
 264:     /**
 265:      * Returns a list of the subplots.
 266:      *
 267:      * @return The list (unmodifiable).
 268:      */
 269:     public List getSubplots() {
 270:         return Collections.unmodifiableList(this.subplots);
 271:     }
 272: 
 273:     /**
 274:      * Calculates the space required for the axes.
 275:      * 
 276:      * @param g2  the graphics device.
 277:      * @param plotArea  the plot area.
 278:      * 
 279:      * @return The space required for the axes.
 280:      */
 281:     protected AxisSpace calculateAxisSpace(Graphics2D g2, 
 282:                                            Rectangle2D plotArea) {
 283:         
 284:         AxisSpace space = new AxisSpace();
 285:         PlotOrientation orientation = getOrientation();
 286:         
 287:         // work out the space required by the domain axis...
 288:         AxisSpace fixed = getFixedRangeAxisSpace();
 289:         if (fixed != null) {
 290:             if (orientation == PlotOrientation.VERTICAL) {
 291:                 space.setLeft(fixed.getLeft());
 292:                 space.setRight(fixed.getRight());
 293:             }
 294:             else if (orientation == PlotOrientation.HORIZONTAL) {
 295:                 space.setTop(fixed.getTop());
 296:                 space.setBottom(fixed.getBottom());                
 297:             }
 298:         }
 299:         else {
 300:             ValueAxis valueAxis = getRangeAxis();
 301:             RectangleEdge valueEdge = Plot.resolveRangeAxisLocation(
 302:                 getRangeAxisLocation(), orientation
 303:             );
 304:             if (valueAxis != null) {
 305:                 space = valueAxis.reserveSpace(g2, this, plotArea, valueEdge, 
 306:                         space);
 307:             }
 308:         }
 309:         
 310:         Rectangle2D adjustedPlotArea = space.shrink(plotArea, null);
 311:         // work out the maximum height or width of the non-shared axes...
 312:         int n = this.subplots.size();
 313: 
 314:         // calculate plotAreas of all sub-plots, maximum vertical/horizontal 
 315:         // axis width/height
 316:         this.subplotAreas = new Rectangle2D[n];
 317:         double x = adjustedPlotArea.getX();
 318:         double y = adjustedPlotArea.getY();
 319:         double usableSize = 0.0;
 320:         if (orientation == PlotOrientation.VERTICAL) {
 321:             usableSize = adjustedPlotArea.getWidth() - this.gap * (n - 1);
 322:         }
 323:         else if (orientation == PlotOrientation.HORIZONTAL) {
 324:             usableSize = adjustedPlotArea.getHeight() - this.gap * (n - 1);
 325:         }
 326: 
 327:         for (int i = 0; i < n; i++) {
 328:             XYPlot plot = (XYPlot) this.subplots.get(i);
 329: 
 330:             // calculate sub-plot area
 331:             if (orientation == PlotOrientation.VERTICAL) {
 332:                 double w = usableSize * plot.getWeight() / this.totalWeight;
 333:                 this.subplotAreas[i] = new Rectangle2D.Double(x, y, w, 
 334:                         adjustedPlotArea.getHeight());
 335:                 x = x + w + this.gap;
 336:             }
 337:             else if (orientation == PlotOrientation.HORIZONTAL) {
 338:                 double h = usableSize * plot.getWeight() / this.totalWeight;
 339:                 this.subplotAreas[i] = new Rectangle2D.Double(x, y, 
 340:                         adjustedPlotArea.getWidth(), h);
 341:                 y = y + h + this.gap;
 342:             }
 343: 
 344:             AxisSpace subSpace = plot.calculateDomainAxisSpace(g2, 
 345:                     this.subplotAreas[i], null);
 346:             space.ensureAtLeast(subSpace);
 347: 
 348:         }
 349: 
 350:         return space;
 351:     }
 352:     
 353:     /**
 354:      * Draws the plot within the specified area on a graphics device.
 355:      * 
 356:      * @param g2  the graphics device.
 357:      * @param area  the plot area (in Java2D space).
 358:      * @param anchor  an anchor point in Java2D space (<code>null</code> 
 359:      *                permitted).
 360:      * @param parentState  the state from the parent plot, if there is one 
 361:      *                     (<code>null</code> permitted).
 362:      * @param info  collects chart drawing information (<code>null</code> 
 363:      *              permitted).
 364:      */
 365:     public void draw(Graphics2D g2,
 366:                      Rectangle2D area,
 367:                      Point2D anchor,
 368:                      PlotState parentState,
 369:                      PlotRenderingInfo info) {
 370:         
 371:         // set up info collection...
 372:         if (info != null) {
 373:             info.setPlotArea(area);
 374:         }
 375: 
 376:         // adjust the drawing area for plot insets (if any)...
 377:         RectangleInsets insets = getInsets();
 378:         insets.trim(area);
 379: 
 380:         AxisSpace space = calculateAxisSpace(g2, area);
 381:         Rectangle2D dataArea = space.shrink(area, null);
 382:         //this.axisOffset.trim(dataArea);
 383: 
 384:         // set the width and height of non-shared axis of all sub-plots
 385:         setFixedDomainAxisSpaceForSubplots(space);
 386: 
 387:         // draw the shared axis
 388:         ValueAxis axis = getRangeAxis();
 389:         RectangleEdge edge = getRangeAxisEdge();
 390:         double cursor = RectangleEdge.coordinate(dataArea, edge);
 391:         AxisState axisState = axis.draw(g2, cursor, area, dataArea, edge, info);
 392: 
 393:         if (parentState == null) {
 394:             parentState = new PlotState();
 395:         }
 396:         parentState.getSharedAxisStates().put(axis, axisState);
 397:         
 398:         // draw all the charts
 399:         for (int i = 0; i < this.subplots.size(); i++) {
 400:             XYPlot plot = (XYPlot) this.subplots.get(i);
 401:             PlotRenderingInfo subplotInfo = null;
 402:             if (info != null) {
 403:                 subplotInfo = new PlotRenderingInfo(info.getOwner());
 404:                 info.addSubplotInfo(subplotInfo);
 405:             }
 406:             plot.draw(g2, this.subplotAreas[i], anchor, parentState, 
 407:                     subplotInfo);
 408:         }
 409: 
 410:         if (info != null) {
 411:             info.setDataArea(dataArea);
 412:         }
 413: 
 414:     }
 415: 
 416:     /**
 417:      * Returns a collection of legend items for the plot.
 418:      *
 419:      * @return The legend items.
 420:      */
 421:     public LegendItemCollection getLegendItems() {
 422:         LegendItemCollection result = getFixedLegendItems();
 423:         if (result == null) {
 424:             result = new LegendItemCollection();
 425:         
 426:             if (this.subplots != null) {
 427:                 Iterator iterator = this.subplots.iterator();
 428:                 while (iterator.hasNext()) {
 429:                     XYPlot plot = (XYPlot) iterator.next();
 430:                     LegendItemCollection more = plot.getLegendItems();
 431:                     result.addAll(more);
 432:                 }
 433:             }
 434:         }
 435:         return result;
 436:     }
 437: 
 438:     /**
 439:      * Multiplies the range on the domain axis/axes by the specified factor.
 440:      *
 441:      * @param factor  the zoom factor.
 442:      * @param info  the plot rendering info (<code>null</code> not permitted).
 443:      * @param source  the source point (<code>null</code> not permitted).
 444:      */
 445:     public void zoomDomainAxes(double factor, PlotRenderingInfo info, 
 446:                                Point2D source) {
 447:         // delegate 'info' and 'source' argument checks...
 448:         XYPlot subplot = findSubplot(info, source);
 449:         if (subplot != null) {
 450:             subplot.zoomDomainAxes(factor, info, source);
 451:         }
 452:         else {
 453:             // if the source point doesn't fall within a subplot, we do the
 454:             // zoom on all subplots...
 455:             Iterator iterator = getSubplots().iterator();
 456:             while (iterator.hasNext()) {
 457:                 subplot = (XYPlot) iterator.next();
 458:                 subplot.zoomDomainAxes(factor, info, source);
 459:             }
 460:         }
 461:     }
 462: 
 463:     /**
 464:      * Zooms in on the domain axes.
 465:      *
 466:      * @param lowerPercent  the lower bound.
 467:      * @param upperPercent  the upper bound.
 468:      * @param info  the plot rendering info (<code>null</code> not permitted).
 469:      * @param source  the source point (<code>null</code> not permitted).
 470:      */
 471:     public void zoomDomainAxes(double lowerPercent, double upperPercent, 
 472:                                PlotRenderingInfo info, Point2D source) {
 473:         // delegate 'info' and 'source' argument checks...
 474:         XYPlot subplot = findSubplot(info, source);
 475:         if (subplot != null) {
 476:             subplot.zoomDomainAxes(lowerPercent, upperPercent, info, source);
 477:         }
 478:         else {
 479:             // if the source point doesn't fall within a subplot, we do the
 480:             // zoom on all subplots...
 481:             Iterator iterator = getSubplots().iterator();
 482:             while (iterator.hasNext()) {
 483:                 subplot = (XYPlot) iterator.next();
 484:                 subplot.zoomDomainAxes(lowerPercent, upperPercent, info, 
 485:                         source);
 486:             }
 487:         }
 488:     }
 489: 
 490:     /**
 491:      * Returns the subplot (if any) that contains the (x, y) point (specified 
 492:      * in Java2D space).
 493:      * 
 494:      * @param info  the chart rendering info (<code>null</code> not permitted).
 495:      * @param source  the source point (<code>null</code> not permitted).
 496:      * 
 497:      * @return A subplot (possibly <code>null</code>).
 498:      */
 499:     public XYPlot findSubplot(PlotRenderingInfo info, Point2D source) {
 500:         if (info == null) {
 501:             throw new IllegalArgumentException("Null 'info' argument.");
 502:         }
 503:         if (source == null) {
 504:             throw new IllegalArgumentException("Null 'source' argument.");
 505:         }
 506:         XYPlot result = null;
 507:         int subplotIndex = info.getSubplotIndex(source);
 508:         if (subplotIndex >= 0) {
 509:             result =  (XYPlot) this.subplots.get(subplotIndex);
 510:         }
 511:         return result;
 512:     }
 513: 
 514:     /**
 515:      * Sets the item renderer FOR ALL SUBPLOTS.  Registered listeners are 
 516:      * notified that the plot has been modified.
 517:      * <P>
 518:      * Note: usually you will want to set the renderer independently for each 
 519:      * subplot, which is NOT what this method does.
 520:      *
 521:      * @param renderer the new renderer.
 522:      */
 523:     public void setRenderer(XYItemRenderer renderer) {
 524: 
 525:         super.setRenderer(renderer);  // not strictly necessary, since the 
 526:                                       // renderer set for the
 527:                                       // parent plot is not used
 528: 
 529:         Iterator iterator = this.subplots.iterator();
 530:         while (iterator.hasNext()) {
 531:             XYPlot plot = (XYPlot) iterator.next();
 532:             plot.setRenderer(renderer);
 533:         }
 534: 
 535:     }
 536: 
 537:     /**
 538:      * Sets the orientation for the plot (and all its subplots).
 539:      * 
 540:      * @param orientation  the orientation.
 541:      */
 542:     public void setOrientation(PlotOrientation orientation) {
 543: 
 544:         super.setOrientation(orientation);
 545: 
 546:         Iterator iterator = this.subplots.iterator();
 547:         while (iterator.hasNext()) {
 548:             XYPlot plot = (XYPlot) iterator.next();
 549:             plot.setOrientation(orientation);
 550:         }
 551: 
 552:     }
 553: 
 554:     /**
 555:      * Returns the range for the axis.  This is the combined range of all the 
 556:      * subplots.
 557:      *
 558:      * @param axis  the axis.
 559:      *
 560:      * @return The range.
 561:      */
 562:     public Range getDataRange(ValueAxis axis) {
 563: 
 564:         Range result = null;
 565:         if (this.subplots != null) {
 566:             Iterator iterator = this.subplots.iterator();
 567:             while (iterator.hasNext()) {
 568:                 XYPlot subplot = (XYPlot) iterator.next();
 569:                 result = Range.combine(result, subplot.getDataRange(axis));
 570:             }
 571:         }
 572:         return result;
 573: 
 574:     }
 575: 
 576:     /**
 577:      * Sets the space (width or height, depending on the orientation of the 
 578:      * plot) for the domain axis of each subplot.
 579:      *
 580:      * @param space  the space.
 581:      */
 582:     protected void setFixedDomainAxisSpaceForSubplots(AxisSpace space) {
 583: 
 584:         Iterator iterator = this.subplots.iterator();
 585:         while (iterator.hasNext()) {
 586:             XYPlot plot = (XYPlot) iterator.next();
 587:             plot.setFixedDomainAxisSpace(space);
 588:         }
 589: 
 590:     }
 591: 
 592:     /**
 593:      * Handles a 'click' on the plot by updating the anchor values...
 594:      *
 595:      * @param x  x-coordinate, where the click occured.
 596:      * @param y  y-coordinate, where the click occured.
 597:      * @param info  object containing information about the plot dimensions.
 598:      */
 599:     public void handleClick(int x, int y, PlotRenderingInfo info) {
 600: 
 601:         Rectangle2D dataArea = info.getDataArea();
 602:         if (dataArea.contains(x, y)) {
 603:             for (int i = 0; i < this.subplots.size(); i++) {
 604:                 XYPlot subplot = (XYPlot) this.subplots.get(i);
 605:                 PlotRenderingInfo subplotInfo = info.getSubplotInfo(i);
 606:                 subplot.handleClick(x, y, subplotInfo);
 607:             }
 608:         }
 609: 
 610:     }
 611: 
 612:     /**
 613:      * Receives a {@link PlotChangeEvent} and responds by notifying all 
 614:      * listeners.
 615:      * 
 616:      * @param event  the event.
 617:      */
 618:     public void plotChanged(PlotChangeEvent event) {
 619:         notifyListeners(event);
 620:     }
 621: 
 622:     /**
 623:      * Tests this plot for equality with another object.
 624:      *
 625:      * @param obj  the other object.
 626:      *
 627:      * @return <code>true</code> or <code>false</code>.
 628:      */
 629:     public boolean equals(Object obj) {
 630: 
 631:         if (obj == this) {
 632:             return true;
 633:         }
 634: 
 635:         if (!(obj instanceof CombinedRangeXYPlot)) {
 636:             return false;
 637:         }
 638:         if (!super.equals(obj)) {
 639:             return false;
 640:         }
 641:         CombinedRangeXYPlot that = (CombinedRangeXYPlot) obj;
 642:         if (!ObjectUtilities.equal(this.subplots, that.subplots)) {
 643:             return false;
 644:         }
 645:         if (this.totalWeight != that.totalWeight) {
 646:             return false;
 647:         }
 648:         if (this.gap != that.gap) {
 649:             return false;
 650:         }
 651:         return true;
 652:     }
 653:     
 654:     /**
 655:      * Returns a clone of the plot.
 656:      * 
 657:      * @return A clone.
 658:      * 
 659:      * @throws CloneNotSupportedException  this class will not throw this 
 660:      *         exception, but subclasses (if any) might.
 661:      */
 662:     public Object clone() throws CloneNotSupportedException {
 663:         
 664:         CombinedRangeXYPlot result = (CombinedRangeXYPlot) super.clone(); 
 665:         result.subplots = (List) ObjectUtilities.deepClone(this.subplots);
 666:         for (Iterator it = result.subplots.iterator(); it.hasNext();) {
 667:             Plot child = (Plot) it.next();
 668:             child.setParent(result);
 669:         }
 670:         
 671:         // after setting up all the subplots, the shared range axis may need 
 672:         // reconfiguring
 673:         ValueAxis rangeAxis = result.getRangeAxis();
 674:         if (rangeAxis != null) {
 675:             rangeAxis.configure();
 676:         }
 677:         
 678:         return result;
 679:     }
 680: 
 681: }