Source for org.jfree.chart.plot.CombinedDomainCategoryPlot

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2008, 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:  * CombinedDomainCategoryPlot.java
  29:  * -------------------------------
  30:  * (C) Copyright 2003-2008, by Object Refinery Limited.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   Nicolas Brodu;
  34:  *
  35:  * Changes:
  36:  * --------
  37:  * 16-May-2003 : Version 1 (DG);
  38:  * 08-Aug-2003 : Adjusted totalWeight in remove() method (DG);
  39:  * 19-Aug-2003 : Added equals() method, implemented Cloneable and
  40:  *               Serializable (DG);
  41:  * 11-Sep-2003 : Fix cloning support (subplots) (NB);
  42:  * 15-Sep-2003 : Implemented PublicCloneable (DG);
  43:  * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
  44:  * 17-Sep-2003 : Updated handling of 'clicks' (DG);
  45:  * 04-May-2004 : Added getter/setter methods for 'gap' attribute (DG);
  46:  * 12-Nov-2004 : Implemented the Zoomable interface (DG);
  47:  * 25-Nov-2004 : Small update to clone() implementation (DG);
  48:  * 21-Feb-2005 : The getLegendItems() method now returns the fixed legend
  49:  *               items if set (DG);
  50:  * 05-May-2005 : Updated draw() method parameters (DG);
  51:  * ------------- JFREECHART 1.0.x ---------------------------------------------
  52:  * 13-Sep-2006 : Updated API docs (DG);
  53:  * 30-Oct-2006 : Added new getCategoriesForAxis() override (DG);
  54:  * 17-Apr-2007 : Added null argument checks to findSubplot() (DG);
  55:  * 14-Nov-2007 : Updated setFixedRangeAxisSpaceForSubplots() method (DG);
  56:  * 27-Mar-2008 : Add documentation for getDataRange() method (DG);
  57:  * 31-Mar-2008 : Updated getSubplots() to return EMPTY_LIST for null
  58:  *               subplots, as suggested by Richard West (DG);
  59:  * 28-Apr-2008 : Fixed zooming problem (see bug 1950037) (DG);
  60:  *
  61:  */
  62: 
  63: package org.jfree.chart.plot;
  64: 
  65: import java.awt.Graphics2D;
  66: import java.awt.geom.Point2D;
  67: import java.awt.geom.Rectangle2D;
  68: import java.util.Collections;
  69: import java.util.Iterator;
  70: import java.util.List;
  71: 
  72: import org.jfree.chart.LegendItemCollection;
  73: import org.jfree.chart.axis.AxisSpace;
  74: import org.jfree.chart.axis.AxisState;
  75: import org.jfree.chart.axis.CategoryAxis;
  76: import org.jfree.chart.axis.ValueAxis;
  77: import org.jfree.chart.event.PlotChangeEvent;
  78: import org.jfree.chart.event.PlotChangeListener;
  79: import org.jfree.data.Range;
  80: import org.jfree.ui.RectangleEdge;
  81: import org.jfree.ui.RectangleInsets;
  82: import org.jfree.util.ObjectUtilities;
  83: 
  84: /**
  85:  * A combined category plot where the domain axis is shared.
  86:  */
  87: public class CombinedDomainCategoryPlot extends CategoryPlot
  88:         implements PlotChangeListener {
  89: 
  90:     /** For serialization. */
  91:     private static final long serialVersionUID = 8207194522653701572L;
  92: 
  93:     /** Storage for the subplot references. */
  94:     private List subplots;
  95: 
  96:     /** Total weight of all charts. */
  97:     private int totalWeight;
  98: 
  99:     /** The gap between subplots. */
 100:     private double gap;
 101: 
 102:     /** Temporary storage for the subplot areas. */
 103:     private transient Rectangle2D[] subplotAreas;
 104:     // TODO:  move the above to the plot state
 105: 
 106:     /**
 107:      * Default constructor.
 108:      */
 109:     public CombinedDomainCategoryPlot() {
 110:         this(new CategoryAxis());
 111:     }
 112: 
 113:     /**
 114:      * Creates a new plot.
 115:      *
 116:      * @param domainAxis  the shared domain axis (<code>null</code> not
 117:      *                    permitted).
 118:      */
 119:     public CombinedDomainCategoryPlot(CategoryAxis domainAxis) {
 120:         super(null, domainAxis, null, null);
 121:         this.subplots = new java.util.ArrayList();
 122:         this.totalWeight = 0;
 123:         this.gap = 5.0;
 124:     }
 125: 
 126:     /**
 127:      * Returns the space between subplots.
 128:      *
 129:      * @return The gap (in Java2D units).
 130:      */
 131:     public double getGap() {
 132:         return this.gap;
 133:     }
 134: 
 135:     /**
 136:      * Sets the amount of space between subplots and sends a
 137:      * {@link PlotChangeEvent} to all registered listeners.
 138:      *
 139:      * @param gap  the gap between subplots (in Java2D units).
 140:      */
 141:     public void setGap(double gap) {
 142:         this.gap = gap;
 143:         fireChangeEvent();
 144:     }
 145: 
 146:     /**
 147:      * Adds a subplot to the combined chart and sends a {@link PlotChangeEvent}
 148:      * to all registered listeners.
 149:      * <br><br>
 150:      * The domain axis for the subplot will be set to <code>null</code>.  You
 151:      * must ensure that the subplot has a non-null range axis.
 152:      *
 153:      * @param subplot  the subplot (<code>null</code> not permitted).
 154:      */
 155:     public void add(CategoryPlot subplot) {
 156:         add(subplot, 1);
 157:     }
 158: 
 159:     /**
 160:      * Adds a subplot to the combined chart and sends a {@link PlotChangeEvent}
 161:      * to all registered listeners.
 162:      * <br><br>
 163:      * The domain axis for the subplot will be set to <code>null</code>.  You
 164:      * must ensure that the subplot has a non-null range axis.
 165:      *
 166:      * @param subplot  the subplot (<code>null</code> not permitted).
 167:      * @param weight  the weight (must be >= 1).
 168:      */
 169:     public void add(CategoryPlot subplot, int weight) {
 170:         if (subplot == null) {
 171:             throw new IllegalArgumentException("Null 'subplot' argument.");
 172:         }
 173:         if (weight < 1) {
 174:             throw new IllegalArgumentException("Require weight >= 1.");
 175:         }
 176:         subplot.setParent(this);
 177:         subplot.setWeight(weight);
 178:         subplot.setInsets(new RectangleInsets(0.0, 0.0, 0.0, 0.0));
 179:         subplot.setDomainAxis(null);
 180:         subplot.setOrientation(getOrientation());
 181:         subplot.addChangeListener(this);
 182:         this.subplots.add(subplot);
 183:         this.totalWeight += weight;
 184:         CategoryAxis axis = getDomainAxis();
 185:         if (axis != null) {
 186:             axis.configure();
 187:         }
 188:         fireChangeEvent();
 189:     }
 190: 
 191:     /**
 192:      * Removes a subplot from the combined chart.  Potentially, this removes
 193:      * some unique categories from the overall union of the datasets...so the
 194:      * domain axis is reconfigured, then a {@link PlotChangeEvent} is sent to
 195:      * all registered listeners.
 196:      *
 197:      * @param subplot  the subplot (<code>null</code> not permitted).
 198:      */
 199:     public void remove(CategoryPlot subplot) {
 200:         if (subplot == null) {
 201:             throw new IllegalArgumentException("Null 'subplot' argument.");
 202:         }
 203:         int position = -1;
 204:         int size = this.subplots.size();
 205:         int i = 0;
 206:         while (position == -1 && i < size) {
 207:             if (this.subplots.get(i) == subplot) {
 208:                 position = i;
 209:             }
 210:             i++;
 211:         }
 212:         if (position != -1) {
 213:             this.subplots.remove(position);
 214:             subplot.setParent(null);
 215:             subplot.removeChangeListener(this);
 216:             this.totalWeight -= subplot.getWeight();
 217: 
 218:             CategoryAxis domain = getDomainAxis();
 219:             if (domain != null) {
 220:                 domain.configure();
 221:             }
 222:             fireChangeEvent();
 223:         }
 224:     }
 225: 
 226:     /**
 227:      * Returns the list of subplots.  The returned list may be empty, but is
 228:      * never <code>null</code>.
 229:      *
 230:      * @return An unmodifiable list of subplots.
 231:      */
 232:     public List getSubplots() {
 233:         if (this.subplots != null) {
 234:             return Collections.unmodifiableList(this.subplots);
 235:         }
 236:         else {
 237:             return Collections.EMPTY_LIST;
 238:         }
 239:     }
 240: 
 241:     /**
 242:      * Returns the subplot (if any) that contains the (x, y) point (specified
 243:      * in Java2D space).
 244:      *
 245:      * @param info  the chart rendering info (<code>null</code> not permitted).
 246:      * @param source  the source point (<code>null</code> not permitted).
 247:      *
 248:      * @return A subplot (possibly <code>null</code>).
 249:      */
 250:     public CategoryPlot findSubplot(PlotRenderingInfo info, Point2D source) {
 251:         if (info == null) {
 252:             throw new IllegalArgumentException("Null 'info' argument.");
 253:         }
 254:         if (source == null) {
 255:             throw new IllegalArgumentException("Null 'source' argument.");
 256:         }
 257:         CategoryPlot result = null;
 258:         int subplotIndex = info.getSubplotIndex(source);
 259:         if (subplotIndex >= 0) {
 260:             result =  (CategoryPlot) this.subplots.get(subplotIndex);
 261:         }
 262:         return result;
 263:     }
 264: 
 265:     /**
 266:      * Multiplies the range on the range axis/axes by the specified factor.
 267:      *
 268:      * @param factor  the zoom factor.
 269:      * @param info  the plot rendering info (<code>null</code> not permitted).
 270:      * @param source  the source point (<code>null</code> not permitted).
 271:      */
 272:     public void zoomRangeAxes(double factor, PlotRenderingInfo info,
 273:                               Point2D source) {
 274:         zoomRangeAxes(factor, info, source, false);
 275:     }
 276: 
 277:     /**
 278:      * Multiplies the range on the range axis/axes by the specified factor.
 279:      *
 280:      * @param factor  the zoom factor.
 281:      * @param info  the plot rendering info (<code>null</code> not permitted).
 282:      * @param source  the source point (<code>null</code> not permitted).
 283:      * @param useAnchor  zoom about the anchor point?
 284:      */
 285:     public void zoomRangeAxes(double factor, PlotRenderingInfo info,
 286:                               Point2D source, boolean useAnchor) {
 287:         // delegate 'info' and 'source' argument checks...
 288:         CategoryPlot subplot = findSubplot(info, source);
 289:         if (subplot != null) {
 290:             subplot.zoomRangeAxes(factor, info, source, useAnchor);
 291:         }
 292:         else {
 293:             // if the source point doesn't fall within a subplot, we do the
 294:             // zoom on all subplots...
 295:             Iterator iterator = getSubplots().iterator();
 296:             while (iterator.hasNext()) {
 297:                 subplot = (CategoryPlot) iterator.next();
 298:                 subplot.zoomRangeAxes(factor, info, source, useAnchor);
 299:             }
 300:         }
 301:     }
 302: 
 303:     /**
 304:      * Zooms in on the range axes.
 305:      *
 306:      * @param lowerPercent  the lower bound.
 307:      * @param upperPercent  the upper bound.
 308:      * @param info  the plot rendering info (<code>null</code> not permitted).
 309:      * @param source  the source point (<code>null</code> not permitted).
 310:      */
 311:     public void zoomRangeAxes(double lowerPercent, double upperPercent,
 312:                               PlotRenderingInfo info, Point2D source) {
 313:         // delegate 'info' and 'source' argument checks...
 314:         CategoryPlot subplot = findSubplot(info, source);
 315:         if (subplot != null) {
 316:             subplot.zoomRangeAxes(lowerPercent, upperPercent, info, source);
 317:         }
 318:         else {
 319:             // if the source point doesn't fall within a subplot, we do the
 320:             // zoom on all subplots...
 321:             Iterator iterator = getSubplots().iterator();
 322:             while (iterator.hasNext()) {
 323:                 subplot = (CategoryPlot) iterator.next();
 324:                 subplot.zoomRangeAxes(lowerPercent, upperPercent, info, source);
 325:             }
 326:         }
 327:     }
 328: 
 329:     /**
 330:      * Calculates the space required for the axes.
 331:      *
 332:      * @param g2  the graphics device.
 333:      * @param plotArea  the plot area.
 334:      *
 335:      * @return The space required for the axes.
 336:      */
 337:     protected AxisSpace calculateAxisSpace(Graphics2D g2,
 338:                                            Rectangle2D plotArea) {
 339: 
 340:         AxisSpace space = new AxisSpace();
 341:         PlotOrientation orientation = getOrientation();
 342: 
 343:         // work out the space required by the domain axis...
 344:         AxisSpace fixed = getFixedDomainAxisSpace();
 345:         if (fixed != null) {
 346:             if (orientation == PlotOrientation.HORIZONTAL) {
 347:                 space.setLeft(fixed.getLeft());
 348:                 space.setRight(fixed.getRight());
 349:             }
 350:             else if (orientation == PlotOrientation.VERTICAL) {
 351:                 space.setTop(fixed.getTop());
 352:                 space.setBottom(fixed.getBottom());
 353:             }
 354:         }
 355:         else {
 356:             CategoryAxis categoryAxis = getDomainAxis();
 357:             RectangleEdge categoryEdge = Plot.resolveDomainAxisLocation(
 358:                     getDomainAxisLocation(), orientation);
 359:             if (categoryAxis != null) {
 360:                 space = categoryAxis.reserveSpace(g2, this, plotArea,
 361:                         categoryEdge, space);
 362:             }
 363:             else {
 364:                 if (getDrawSharedDomainAxis()) {
 365:                     space = getDomainAxis().reserveSpace(g2, this, plotArea,
 366:                             categoryEdge, space);
 367:                 }
 368:             }
 369:         }
 370: 
 371:         Rectangle2D adjustedPlotArea = space.shrink(plotArea, null);
 372: 
 373:         // work out the maximum height or width of the non-shared axes...
 374:         int n = this.subplots.size();
 375:         this.subplotAreas = new Rectangle2D[n];
 376:         double x = adjustedPlotArea.getX();
 377:         double y = adjustedPlotArea.getY();
 378:         double usableSize = 0.0;
 379:         if (orientation == PlotOrientation.HORIZONTAL) {
 380:             usableSize = adjustedPlotArea.getWidth() - this.gap * (n - 1);
 381:         }
 382:         else if (orientation == PlotOrientation.VERTICAL) {
 383:             usableSize = adjustedPlotArea.getHeight() - this.gap * (n - 1);
 384:         }
 385: 
 386:         for (int i = 0; i < n; i++) {
 387:             CategoryPlot plot = (CategoryPlot) this.subplots.get(i);
 388: 
 389:             // calculate sub-plot area
 390:             if (orientation == PlotOrientation.HORIZONTAL) {
 391:                 double w = usableSize * plot.getWeight() / this.totalWeight;
 392:                 this.subplotAreas[i] = new Rectangle2D.Double(x, y, w,
 393:                         adjustedPlotArea.getHeight());
 394:                 x = x + w + this.gap;
 395:             }
 396:             else if (orientation == PlotOrientation.VERTICAL) {
 397:                 double h = usableSize * plot.getWeight() / this.totalWeight;
 398:                 this.subplotAreas[i] = new Rectangle2D.Double(x, y,
 399:                         adjustedPlotArea.getWidth(), h);
 400:                 y = y + h + this.gap;
 401:             }
 402: 
 403:             AxisSpace subSpace = plot.calculateRangeAxisSpace(g2,
 404:                     this.subplotAreas[i], null);
 405:             space.ensureAtLeast(subSpace);
 406: 
 407:         }
 408: 
 409:         return space;
 410:     }
 411: 
 412:     /**
 413:      * Draws the plot on a Java 2D graphics device (such as the screen or a
 414:      * printer).  Will perform all the placement calculations for each of the
 415:      * sub-plots and then tell these to draw themselves.
 416:      *
 417:      * @param g2  the graphics device.
 418:      * @param area  the area within which the plot (including axis labels)
 419:      *              should be drawn.
 420:      * @param anchor  the anchor point (<code>null</code> permitted).
 421:      * @param parentState  the state from the parent plot, if there is one.
 422:      * @param info  collects information about the drawing (<code>null</code>
 423:      *              permitted).
 424:      */
 425:     public void draw(Graphics2D g2,
 426:                      Rectangle2D area,
 427:                      Point2D anchor,
 428:                      PlotState parentState,
 429:                      PlotRenderingInfo info) {
 430: 
 431:         // set up info collection...
 432:         if (info != null) {
 433:             info.setPlotArea(area);
 434:         }
 435: 
 436:         // adjust the drawing area for plot insets (if any)...
 437:         RectangleInsets insets = getInsets();
 438:         area.setRect(area.getX() + insets.getLeft(),
 439:                 area.getY() + insets.getTop(),
 440:                 area.getWidth() - insets.getLeft() - insets.getRight(),
 441:                 area.getHeight() - insets.getTop() - insets.getBottom());
 442: 
 443: 
 444:         // calculate the data area...
 445:         setFixedRangeAxisSpaceForSubplots(null);
 446:         AxisSpace space = calculateAxisSpace(g2, area);
 447:         Rectangle2D dataArea = space.shrink(area, null);
 448: 
 449:         // set the width and height of non-shared axis of all sub-plots
 450:         setFixedRangeAxisSpaceForSubplots(space);
 451: 
 452:         // draw the shared axis
 453:         CategoryAxis axis = getDomainAxis();
 454:         RectangleEdge domainEdge = getDomainAxisEdge();
 455:         double cursor = RectangleEdge.coordinate(dataArea, domainEdge);
 456:         AxisState axisState = axis.draw(g2, cursor, area, dataArea,
 457:                 domainEdge, info);
 458:         if (parentState == null) {
 459:             parentState = new PlotState();
 460:         }
 461:         parentState.getSharedAxisStates().put(axis, axisState);
 462: 
 463:         // draw all the subplots
 464:         for (int i = 0; i < this.subplots.size(); i++) {
 465:             CategoryPlot plot = (CategoryPlot) this.subplots.get(i);
 466:             PlotRenderingInfo subplotInfo = null;
 467:             if (info != null) {
 468:                 subplotInfo = new PlotRenderingInfo(info.getOwner());
 469:                 info.addSubplotInfo(subplotInfo);
 470:             }
 471:             plot.draw(g2, this.subplotAreas[i], null, parentState, subplotInfo);
 472:         }
 473: 
 474:         if (info != null) {
 475:             info.setDataArea(dataArea);
 476:         }
 477: 
 478:     }
 479: 
 480:     /**
 481:      * Sets the size (width or height, depending on the orientation of the
 482:      * plot) for the range axis of each subplot.
 483:      *
 484:      * @param space  the space (<code>null</code> permitted).
 485:      */
 486:     protected void setFixedRangeAxisSpaceForSubplots(AxisSpace space) {
 487:         Iterator iterator = this.subplots.iterator();
 488:         while (iterator.hasNext()) {
 489:             CategoryPlot plot = (CategoryPlot) iterator.next();
 490:             plot.setFixedRangeAxisSpace(space, false);
 491:         }
 492:     }
 493: 
 494:     /**
 495:      * Sets the orientation of the plot (and all subplots).
 496:      *
 497:      * @param orientation  the orientation (<code>null</code> not permitted).
 498:      */
 499:     public void setOrientation(PlotOrientation orientation) {
 500: 
 501:         super.setOrientation(orientation);
 502: 
 503:         Iterator iterator = this.subplots.iterator();
 504:         while (iterator.hasNext()) {
 505:             CategoryPlot plot = (CategoryPlot) iterator.next();
 506:             plot.setOrientation(orientation);
 507:         }
 508: 
 509:     }
 510: 
 511:     /**
 512:      * Returns a range representing the extent of the data values in this plot
 513:      * (obtained from the subplots) that will be rendered against the specified
 514:      * axis.  NOTE: This method is intended for internal JFreeChart use, and
 515:      * is public only so that code in the axis classes can call it.  Since,
 516:      * for this class, the domain axis is a {@link CategoryAxis}
 517:      * (not a <code>ValueAxis</code}) and subplots have independent range axes,
 518:      * the JFreeChart code will never call this method (although this is not
 519:      * checked/enforced).
 520:       *
 521:       * @param axis  the axis.
 522:       *
 523:       * @return The range.
 524:       */
 525:      public Range getDataRange(ValueAxis axis) {
 526:          // override is only for documentation purposes
 527:          return super.getDataRange(axis);
 528:      }
 529: 
 530:      /**
 531:      * Returns a collection of legend items for the plot.
 532:      *
 533:      * @return The legend items.
 534:      */
 535:     public LegendItemCollection getLegendItems() {
 536:         LegendItemCollection result = getFixedLegendItems();
 537:         if (result == null) {
 538:             result = new LegendItemCollection();
 539:             if (this.subplots != null) {
 540:                 Iterator iterator = this.subplots.iterator();
 541:                 while (iterator.hasNext()) {
 542:                     CategoryPlot plot = (CategoryPlot) iterator.next();
 543:                     LegendItemCollection more = plot.getLegendItems();
 544:                     result.addAll(more);
 545:                 }
 546:             }
 547:         }
 548:         return result;
 549:     }
 550: 
 551:     /**
 552:      * Returns an unmodifiable list of the categories contained in all the
 553:      * subplots.
 554:      *
 555:      * @return The list.
 556:      */
 557:     public List getCategories() {
 558:         List result = new java.util.ArrayList();
 559:         if (this.subplots != null) {
 560:             Iterator iterator = this.subplots.iterator();
 561:             while (iterator.hasNext()) {
 562:                 CategoryPlot plot = (CategoryPlot) iterator.next();
 563:                 List more = plot.getCategories();
 564:                 Iterator moreIterator = more.iterator();
 565:                 while (moreIterator.hasNext()) {
 566:                     Comparable category = (Comparable) moreIterator.next();
 567:                     if (!result.contains(category)) {
 568:                         result.add(category);
 569:                     }
 570:                 }
 571:             }
 572:         }
 573:         return Collections.unmodifiableList(result);
 574:     }
 575: 
 576:     /**
 577:      * Overridden to return the categories in the subplots.
 578:      *
 579:      * @param axis  ignored.
 580:      *
 581:      * @return A list of the categories in the subplots.
 582:      *
 583:      * @since 1.0.3
 584:      */
 585:     public List getCategoriesForAxis(CategoryAxis axis) {
 586:         // FIXME:  this code means that it is not possible to use more than
 587:         // one domain axis for the combined plots...
 588:         return getCategories();
 589:     }
 590: 
 591:     /**
 592:      * Handles a 'click' on the plot.
 593:      *
 594:      * @param x  x-coordinate of the click.
 595:      * @param y  y-coordinate of the click.
 596:      * @param info  information about the plot's dimensions.
 597:      *
 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:                 CategoryPlot subplot = (CategoryPlot) 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 the plot for equality with an arbitrary object.
 624:      *
 625:      * @param obj  the object (<code>null</code> permitted).
 626:      *
 627:      * @return A boolean.
 628:      */
 629:     public boolean equals(Object obj) {
 630:         if (obj == this) {
 631:             return true;
 632:         }
 633:         if (!(obj instanceof CombinedDomainCategoryPlot)) {
 634:             return false;
 635:         }
 636:         if (!super.equals(obj)) {
 637:             return false;
 638:         }
 639:         CombinedDomainCategoryPlot plot = (CombinedDomainCategoryPlot) obj;
 640:         if (!ObjectUtilities.equal(this.subplots, plot.subplots)) {
 641:             return false;
 642:         }
 643:         if (this.totalWeight != plot.totalWeight) {
 644:             return false;
 645:         }
 646:         if (this.gap != plot.gap) {
 647:             return false;
 648:         }
 649:         return true;
 650:     }
 651: 
 652:     /**
 653:      * Returns a clone of the plot.
 654:      *
 655:      * @return A clone.
 656:      *
 657:      * @throws CloneNotSupportedException  this class will not throw this
 658:      *         exception, but subclasses (if any) might.
 659:      */
 660:     public Object clone() throws CloneNotSupportedException {
 661: 
 662:         CombinedDomainCategoryPlot result
 663:             = (CombinedDomainCategoryPlot) super.clone();
 664:         result.subplots = (List) ObjectUtilities.deepClone(this.subplots);
 665:         for (Iterator it = result.subplots.iterator(); it.hasNext();) {
 666:             Plot child = (Plot) it.next();
 667:             child.setParent(result);
 668:         }
 669:         return result;
 670: 
 671:     }
 672: 
 673: }