001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 025 * in the United States and other countries.] 026 * 027 * ------------------------- 028 * CombinedDomainXYPlot.java 029 * ------------------------- 030 * (C) Copyright 2001-2008, by Bill Kelemen and Contributors. 031 * 032 * Original Author: Bill Kelemen; 033 * Contributor(s): David Gilbert (for Object Refinery Limited); 034 * Anthony Boulestreau; 035 * David Basten; 036 * Kevin Frechette (for ISTI); 037 * Nicolas Brodu; 038 * Petr Kubanek (bug 1606205); 039 * 040 * Changes: 041 * -------- 042 * 06-Dec-2001 : Version 1 (BK); 043 * 12-Dec-2001 : Removed unnecessary 'throws' clause from constructor (DG); 044 * 18-Dec-2001 : Added plotArea attribute and get/set methods (BK); 045 * 22-Dec-2001 : Fixed bug in chartChanged with multiple combinations of 046 * CombinedPlots (BK); 047 * 08-Jan-2002 : Moved to new package com.jrefinery.chart.combination (DG); 048 * 25-Feb-2002 : Updated import statements (DG); 049 * 28-Feb-2002 : Readded "this.plotArea = plotArea" that was deleted from 050 * draw() method (BK); 051 * 26-Mar-2002 : Added an empty zoom method (this method needs to be written so 052 * that combined plots will support zooming (DG); 053 * 29-Mar-2002 : Changed the method createCombinedAxis adding the creation of 054 * OverlaidSymbolicAxis and CombinedSymbolicAxis(AB); 055 * 23-Apr-2002 : Renamed CombinedPlot-->MultiXYPlot, and simplified the 056 * structure (DG); 057 * 23-May-2002 : Renamed (again) MultiXYPlot-->CombinedXYPlot (DG); 058 * 19-Jun-2002 : Added get/setGap() methods suggested by David Basten (DG); 059 * 25-Jun-2002 : Removed redundant imports (DG); 060 * 16-Jul-2002 : Draws shared axis after subplots (to fix missing gridlines), 061 * added overrides of 'setSeriesPaint()' and 'setXYItemRenderer()' 062 * that pass changes down to subplots (KF); 063 * 09-Oct-2002 : Added add(XYPlot) method (DG); 064 * 26-Mar-2003 : Implemented Serializable (DG); 065 * 16-May-2003 : Renamed CombinedXYPlot --> CombinedDomainXYPlot (DG); 066 * 04-Aug-2003 : Removed leftover code that was causing domain axis drawing 067 * problem (DG); 068 * 08-Aug-2003 : Adjusted totalWeight in remove() method (DG); 069 * 21-Aug-2003 : Implemented Cloneable (DG); 070 * 11-Sep-2003 : Fix cloning support (subplots) (NB); 071 * 15-Sep-2003 : Fixed error in cloning (DG); 072 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG); 073 * 17-Sep-2003 : Updated handling of 'clicks' (DG); 074 * 12-Nov-2004 : Implemented the new Zoomable interface (DG); 075 * 25-Nov-2004 : Small update to clone() implementation (DG); 076 * 21-Feb-2005 : The getLegendItems() method now returns the fixed legend 077 * items if set (DG); 078 * 05-May-2005 : Removed unused draw() method (DG); 079 * ------------- JFREECHART 1.0.x --------------------------------------------- 080 * 23-Aug-2006 : Override setFixedRangeAxisSpace() to update subplots (DG); 081 * 06-Feb-2007 : Fixed bug 1606205, draw shared axis after subplots (DG); 082 * 23-Mar-2007 : Reverted previous patch (bug fix 1606205) (DG); 083 * 17-Apr-2007 : Added null argument checks to findSubplot() (DG); 084 * 27-Nov-2007 : Modified setFixedRangeAxisSpaceForSubplots() so as not to 085 * trigger change event in subplots (DG); 086 * 28-Jan-2008 : Reset fixed range axis space in subplots for each call to 087 * draw() (DG); 088 * 27-Mar-2008 : Add documentation for getDataRange() method (DG); 089 * 31-Mar-2008 : Updated getSubplots() to return EMPTY_LIST for null 090 * subplots, as suggested by Richard West (DG); 091 * 28-Apr-2008 : Fixed zooming problem (see bug 1950037) (DG); 092 * 11-Aug-2008 : Don't store totalWeight of subplots, calculate it as 093 * required (DG); 094 * 095 */ 096 097 package org.jfree.chart.plot; 098 099 import java.awt.Graphics2D; 100 import java.awt.geom.Point2D; 101 import java.awt.geom.Rectangle2D; 102 import java.util.Collections; 103 import java.util.Iterator; 104 import java.util.List; 105 106 import org.jfree.chart.LegendItemCollection; 107 import org.jfree.chart.axis.AxisSpace; 108 import org.jfree.chart.axis.AxisState; 109 import org.jfree.chart.axis.NumberAxis; 110 import org.jfree.chart.axis.ValueAxis; 111 import org.jfree.chart.event.PlotChangeEvent; 112 import org.jfree.chart.event.PlotChangeListener; 113 import org.jfree.chart.renderer.xy.XYItemRenderer; 114 import org.jfree.data.Range; 115 import org.jfree.ui.RectangleEdge; 116 import org.jfree.ui.RectangleInsets; 117 import org.jfree.util.ObjectUtilities; 118 119 /** 120 * An extension of {@link XYPlot} that contains multiple subplots that share a 121 * common domain axis. 122 */ 123 public class CombinedDomainXYPlot extends XYPlot 124 implements PlotChangeListener { 125 126 /** For serialization. */ 127 private static final long serialVersionUID = -7765545541261907383L; 128 129 /** Storage for the subplot references. */ 130 private List subplots; 131 132 /** The gap between subplots. */ 133 private double gap = 5.0; 134 135 /** Temporary storage for the subplot areas. */ 136 private transient Rectangle2D[] subplotAreas; 137 // TODO: the subplot areas needs to be moved out of the plot into the plot 138 // state 139 140 /** 141 * Default constructor. 142 */ 143 public CombinedDomainXYPlot() { 144 this(new NumberAxis()); 145 } 146 147 /** 148 * Creates a new combined plot that shares a domain axis among multiple 149 * subplots. 150 * 151 * @param domainAxis the shared axis. 152 */ 153 public CombinedDomainXYPlot(ValueAxis domainAxis) { 154 155 super( 156 null, // no data in the parent plot 157 domainAxis, 158 null, // no range axis 159 null // no rendereer 160 ); 161 162 this.subplots = new java.util.ArrayList(); 163 164 } 165 166 /** 167 * Returns a string describing the type of plot. 168 * 169 * @return The type of plot. 170 */ 171 public String getPlotType() { 172 return "Combined_Domain_XYPlot"; 173 } 174 175 /** 176 * Sets the orientation for the plot (also changes the orientation for all 177 * the subplots to match). 178 * 179 * @param orientation the orientation (<code>null</code> not allowed). 180 */ 181 public void setOrientation(PlotOrientation orientation) { 182 183 super.setOrientation(orientation); 184 Iterator iterator = this.subplots.iterator(); 185 while (iterator.hasNext()) { 186 XYPlot plot = (XYPlot) iterator.next(); 187 plot.setOrientation(orientation); 188 } 189 190 } 191 192 /** 193 * Returns a range representing the extent of the data values in this plot 194 * (obtained from the subplots) that will be rendered against the specified 195 * axis. NOTE: This method is intended for internal JFreeChart use, and 196 * is public only so that code in the axis classes can call it. Since 197 * only the domain axis is shared between subplots, the JFreeChart code 198 * will only call this method for the domain values (although this is not 199 * checked/enforced). 200 * 201 * @param axis the axis. 202 * 203 * @return The range (possibly <code>null</code>). 204 */ 205 public Range getDataRange(ValueAxis axis) { 206 Range result = null; 207 if (this.subplots != null) { 208 Iterator iterator = this.subplots.iterator(); 209 while (iterator.hasNext()) { 210 XYPlot subplot = (XYPlot) iterator.next(); 211 result = Range.combine(result, subplot.getDataRange(axis)); 212 } 213 } 214 return result; 215 } 216 217 /** 218 * Returns the gap between subplots, measured in Java2D units. 219 * 220 * @return The gap (in Java2D units). 221 */ 222 public double getGap() { 223 return this.gap; 224 } 225 226 /** 227 * Sets the amount of space between subplots and sends a 228 * {@link PlotChangeEvent} to all registered listeners. 229 * 230 * @param gap the gap between subplots (in Java2D units). 231 */ 232 public void setGap(double gap) { 233 this.gap = gap; 234 fireChangeEvent(); 235 } 236 237 /** 238 * Adds a subplot (with a default 'weight' of 1) and sends a 239 * {@link PlotChangeEvent} to all registered listeners. 240 * <P> 241 * The domain axis for the subplot will be set to <code>null</code>. You 242 * must ensure that the subplot has a non-null range axis. 243 * 244 * @param subplot the subplot (<code>null</code> not permitted). 245 */ 246 public void add(XYPlot subplot) { 247 // defer argument checking 248 add(subplot, 1); 249 } 250 251 /** 252 * Adds a subplot with the specified weight and sends a 253 * {@link PlotChangeEvent} to all registered listeners. The weight 254 * determines how much space is allocated to the subplot relative to all 255 * the other subplots. 256 * <P> 257 * The domain axis for the subplot will be set to <code>null</code>. You 258 * must ensure that the subplot has a non-null range axis. 259 * 260 * @param subplot the subplot (<code>null</code> not permitted). 261 * @param weight the weight (must be >= 1). 262 */ 263 public void add(XYPlot subplot, int weight) { 264 265 if (subplot == null) { 266 throw new IllegalArgumentException("Null 'subplot' argument."); 267 } 268 if (weight <= 0) { 269 throw new IllegalArgumentException("Require weight >= 1."); 270 } 271 272 // store the plot and its weight 273 subplot.setParent(this); 274 subplot.setWeight(weight); 275 subplot.setInsets(new RectangleInsets(0.0, 0.0, 0.0, 0.0), false); 276 subplot.setDomainAxis(null); 277 subplot.addChangeListener(this); 278 this.subplots.add(subplot); 279 280 ValueAxis axis = getDomainAxis(); 281 if (axis != null) { 282 axis.configure(); 283 } 284 fireChangeEvent(); 285 } 286 287 /** 288 * Removes a subplot from the combined chart and sends a 289 * {@link PlotChangeEvent} to all registered listeners. 290 * 291 * @param subplot the subplot (<code>null</code> not permitted). 292 */ 293 public void remove(XYPlot subplot) { 294 if (subplot == null) { 295 throw new IllegalArgumentException(" Null 'subplot' argument."); 296 } 297 int position = -1; 298 int size = this.subplots.size(); 299 int i = 0; 300 while (position == -1 && i < size) { 301 if (this.subplots.get(i) == subplot) { 302 position = i; 303 } 304 i++; 305 } 306 if (position != -1) { 307 this.subplots.remove(position); 308 subplot.setParent(null); 309 subplot.removeChangeListener(this); 310 ValueAxis domain = getDomainAxis(); 311 if (domain != null) { 312 domain.configure(); 313 } 314 fireChangeEvent(); 315 } 316 } 317 318 /** 319 * Returns the list of subplots. The returned list may be empty, but is 320 * never <code>null</code>. 321 * 322 * @return An unmodifiable list of subplots. 323 */ 324 public List getSubplots() { 325 if (this.subplots != null) { 326 return Collections.unmodifiableList(this.subplots); 327 } 328 else { 329 return Collections.EMPTY_LIST; 330 } 331 } 332 333 /** 334 * Calculates the axis space required. 335 * 336 * @param g2 the graphics device. 337 * @param plotArea the plot area. 338 * 339 * @return The space. 340 */ 341 protected AxisSpace calculateAxisSpace(Graphics2D g2, 342 Rectangle2D plotArea) { 343 344 AxisSpace space = new AxisSpace(); 345 PlotOrientation orientation = getOrientation(); 346 347 // work out the space required by the domain axis... 348 AxisSpace fixed = getFixedDomainAxisSpace(); 349 if (fixed != null) { 350 if (orientation == PlotOrientation.HORIZONTAL) { 351 space.setLeft(fixed.getLeft()); 352 space.setRight(fixed.getRight()); 353 } 354 else if (orientation == PlotOrientation.VERTICAL) { 355 space.setTop(fixed.getTop()); 356 space.setBottom(fixed.getBottom()); 357 } 358 } 359 else { 360 ValueAxis xAxis = getDomainAxis(); 361 RectangleEdge xEdge = Plot.resolveDomainAxisLocation( 362 getDomainAxisLocation(), orientation); 363 if (xAxis != null) { 364 space = xAxis.reserveSpace(g2, this, plotArea, xEdge, space); 365 } 366 } 367 368 Rectangle2D adjustedPlotArea = space.shrink(plotArea, null); 369 370 // work out the maximum height or width of the non-shared axes... 371 int n = this.subplots.size(); 372 int totalWeight = 0; 373 for (int i = 0; i < n; i++) { 374 XYPlot sub = (XYPlot) this.subplots.get(i); 375 totalWeight += sub.getWeight(); 376 } 377 this.subplotAreas = new Rectangle2D[n]; 378 double x = adjustedPlotArea.getX(); 379 double y = adjustedPlotArea.getY(); 380 double usableSize = 0.0; 381 if (orientation == PlotOrientation.HORIZONTAL) { 382 usableSize = adjustedPlotArea.getWidth() - this.gap * (n - 1); 383 } 384 else if (orientation == PlotOrientation.VERTICAL) { 385 usableSize = adjustedPlotArea.getHeight() - this.gap * (n - 1); 386 } 387 388 for (int i = 0; i < n; i++) { 389 XYPlot plot = (XYPlot) this.subplots.get(i); 390 391 // calculate sub-plot area 392 if (orientation == PlotOrientation.HORIZONTAL) { 393 double w = usableSize * plot.getWeight() / totalWeight; 394 this.subplotAreas[i] = new Rectangle2D.Double(x, y, w, 395 adjustedPlotArea.getHeight()); 396 x = x + w + this.gap; 397 } 398 else if (orientation == PlotOrientation.VERTICAL) { 399 double h = usableSize * plot.getWeight() / totalWeight; 400 this.subplotAreas[i] = new Rectangle2D.Double(x, y, 401 adjustedPlotArea.getWidth(), h); 402 y = y + h + this.gap; 403 } 404 405 AxisSpace subSpace = plot.calculateRangeAxisSpace(g2, 406 this.subplotAreas[i], null); 407 space.ensureAtLeast(subSpace); 408 409 } 410 411 return space; 412 } 413 414 /** 415 * Draws the plot within the specified area on a graphics device. 416 * 417 * @param g2 the graphics device. 418 * @param area the plot area (in Java2D space). 419 * @param anchor an anchor point in Java2D space (<code>null</code> 420 * permitted). 421 * @param parentState the state from the parent plot, if there is one 422 * (<code>null</code> permitted). 423 * @param info collects chart drawing information (<code>null</code> 424 * permitted). 425 */ 426 public void draw(Graphics2D g2, 427 Rectangle2D area, 428 Point2D anchor, 429 PlotState parentState, 430 PlotRenderingInfo info) { 431 432 // set up info collection... 433 if (info != null) { 434 info.setPlotArea(area); 435 } 436 437 // adjust the drawing area for plot insets (if any)... 438 RectangleInsets insets = getInsets(); 439 insets.trim(area); 440 441 setFixedRangeAxisSpaceForSubplots(null); 442 AxisSpace space = calculateAxisSpace(g2, area); 443 Rectangle2D dataArea = space.shrink(area, null); 444 445 // set the width and height of non-shared axis of all sub-plots 446 setFixedRangeAxisSpaceForSubplots(space); 447 448 // draw the shared axis 449 ValueAxis axis = getDomainAxis(); 450 RectangleEdge edge = getDomainAxisEdge(); 451 double cursor = RectangleEdge.coordinate(dataArea, edge); 452 AxisState axisState = axis.draw(g2, cursor, area, dataArea, edge, info); 453 if (parentState == null) { 454 parentState = new PlotState(); 455 } 456 parentState.getSharedAxisStates().put(axis, axisState); 457 458 // draw all the subplots 459 for (int i = 0; i < this.subplots.size(); i++) { 460 XYPlot plot = (XYPlot) this.subplots.get(i); 461 PlotRenderingInfo subplotInfo = null; 462 if (info != null) { 463 subplotInfo = new PlotRenderingInfo(info.getOwner()); 464 info.addSubplotInfo(subplotInfo); 465 } 466 plot.draw(g2, this.subplotAreas[i], anchor, parentState, 467 subplotInfo); 468 } 469 470 if (info != null) { 471 info.setDataArea(dataArea); 472 } 473 474 } 475 476 /** 477 * Returns a collection of legend items for the plot. 478 * 479 * @return The legend items. 480 */ 481 public LegendItemCollection getLegendItems() { 482 LegendItemCollection result = getFixedLegendItems(); 483 if (result == null) { 484 result = new LegendItemCollection(); 485 if (this.subplots != null) { 486 Iterator iterator = this.subplots.iterator(); 487 while (iterator.hasNext()) { 488 XYPlot plot = (XYPlot) iterator.next(); 489 LegendItemCollection more = plot.getLegendItems(); 490 result.addAll(more); 491 } 492 } 493 } 494 return result; 495 } 496 497 /** 498 * Multiplies the range on the range axis/axes by the specified factor. 499 * 500 * @param factor the zoom factor. 501 * @param info the plot rendering info (<code>null</code> not permitted). 502 * @param source the source point (<code>null</code> not permitted). 503 */ 504 public void zoomRangeAxes(double factor, PlotRenderingInfo info, 505 Point2D source) { 506 zoomRangeAxes(factor, info, source, false); 507 } 508 509 /** 510 * Multiplies the range on the range axis/axes by the specified factor. 511 * 512 * @param factor the zoom factor. 513 * @param state the plot state. 514 * @param source the source point (in Java2D coordinates). 515 * @param useAnchor use source point as zoom anchor? 516 */ 517 public void zoomRangeAxes(double factor, PlotRenderingInfo state, 518 Point2D source, boolean useAnchor) { 519 // delegate 'state' and 'source' argument checks... 520 XYPlot subplot = findSubplot(state, source); 521 if (subplot != null) { 522 subplot.zoomRangeAxes(factor, state, source, useAnchor); 523 } 524 else { 525 // if the source point doesn't fall within a subplot, we do the 526 // zoom on all subplots... 527 Iterator iterator = getSubplots().iterator(); 528 while (iterator.hasNext()) { 529 subplot = (XYPlot) iterator.next(); 530 subplot.zoomRangeAxes(factor, state, source, useAnchor); 531 } 532 } 533 } 534 535 /** 536 * Zooms in on the range axes. 537 * 538 * @param lowerPercent the lower bound. 539 * @param upperPercent the upper bound. 540 * @param info the plot rendering info (<code>null</code> not permitted). 541 * @param source the source point (<code>null</code> not permitted). 542 */ 543 public void zoomRangeAxes(double lowerPercent, double upperPercent, 544 PlotRenderingInfo info, Point2D source) { 545 // delegate 'info' and 'source' argument checks... 546 XYPlot subplot = findSubplot(info, source); 547 if (subplot != null) { 548 subplot.zoomRangeAxes(lowerPercent, upperPercent, info, source); 549 } 550 else { 551 // if the source point doesn't fall within a subplot, we do the 552 // zoom on all subplots... 553 Iterator iterator = getSubplots().iterator(); 554 while (iterator.hasNext()) { 555 subplot = (XYPlot) iterator.next(); 556 subplot.zoomRangeAxes(lowerPercent, upperPercent, info, source); 557 } 558 } 559 } 560 561 /** 562 * Returns the subplot (if any) that contains the (x, y) point (specified 563 * in Java2D space). 564 * 565 * @param info the chart rendering info (<code>null</code> not permitted). 566 * @param source the source point (<code>null</code> not permitted). 567 * 568 * @return A subplot (possibly <code>null</code>). 569 */ 570 public XYPlot findSubplot(PlotRenderingInfo info, Point2D source) { 571 if (info == null) { 572 throw new IllegalArgumentException("Null 'info' argument."); 573 } 574 if (source == null) { 575 throw new IllegalArgumentException("Null 'source' argument."); 576 } 577 XYPlot result = null; 578 int subplotIndex = info.getSubplotIndex(source); 579 if (subplotIndex >= 0) { 580 result = (XYPlot) this.subplots.get(subplotIndex); 581 } 582 return result; 583 } 584 585 /** 586 * Sets the item renderer FOR ALL SUBPLOTS. Registered listeners are 587 * notified that the plot has been modified. 588 * <P> 589 * Note: usually you will want to set the renderer independently for each 590 * subplot, which is NOT what this method does. 591 * 592 * @param renderer the new renderer. 593 */ 594 public void setRenderer(XYItemRenderer renderer) { 595 596 super.setRenderer(renderer); // not strictly necessary, since the 597 // renderer set for the 598 // parent plot is not used 599 600 Iterator iterator = this.subplots.iterator(); 601 while (iterator.hasNext()) { 602 XYPlot plot = (XYPlot) iterator.next(); 603 plot.setRenderer(renderer); 604 } 605 606 } 607 608 /** 609 * Sets the fixed range axis space and sends a {@link PlotChangeEvent} to 610 * all registered listeners. 611 * 612 * @param space the space (<code>null</code> permitted). 613 */ 614 public void setFixedRangeAxisSpace(AxisSpace space) { 615 super.setFixedRangeAxisSpace(space); 616 setFixedRangeAxisSpaceForSubplots(space); 617 fireChangeEvent(); 618 } 619 620 /** 621 * Sets the size (width or height, depending on the orientation of the 622 * plot) for the domain axis of each subplot. 623 * 624 * @param space the space. 625 */ 626 protected void setFixedRangeAxisSpaceForSubplots(AxisSpace space) { 627 Iterator iterator = this.subplots.iterator(); 628 while (iterator.hasNext()) { 629 XYPlot plot = (XYPlot) iterator.next(); 630 plot.setFixedRangeAxisSpace(space, false); 631 } 632 } 633 634 /** 635 * Handles a 'click' on the plot by updating the anchor values. 636 * 637 * @param x x-coordinate, where the click occured. 638 * @param y y-coordinate, where the click occured. 639 * @param info object containing information about the plot dimensions. 640 */ 641 public void handleClick(int x, int y, PlotRenderingInfo info) { 642 Rectangle2D dataArea = info.getDataArea(); 643 if (dataArea.contains(x, y)) { 644 for (int i = 0; i < this.subplots.size(); i++) { 645 XYPlot subplot = (XYPlot) this.subplots.get(i); 646 PlotRenderingInfo subplotInfo = info.getSubplotInfo(i); 647 subplot.handleClick(x, y, subplotInfo); 648 } 649 } 650 } 651 652 /** 653 * Receives a {@link PlotChangeEvent} and responds by notifying all 654 * listeners. 655 * 656 * @param event the event. 657 */ 658 public void plotChanged(PlotChangeEvent event) { 659 notifyListeners(event); 660 } 661 662 /** 663 * Tests this plot for equality with another object. 664 * 665 * @param obj the other object. 666 * 667 * @return <code>true</code> or <code>false</code>. 668 */ 669 public boolean equals(Object obj) { 670 if (obj == this) { 671 return true; 672 } 673 if (!(obj instanceof CombinedDomainXYPlot)) { 674 return false; 675 } 676 CombinedDomainXYPlot that = (CombinedDomainXYPlot) obj; 677 if (this.gap != that.gap) { 678 return false; 679 } 680 if (!ObjectUtilities.equal(this.subplots, that.subplots)) { 681 return false; 682 } 683 return super.equals(obj); 684 } 685 686 /** 687 * Returns a clone of the annotation. 688 * 689 * @return A clone. 690 * 691 * @throws CloneNotSupportedException this class will not throw this 692 * exception, but subclasses (if any) might. 693 */ 694 public Object clone() throws CloneNotSupportedException { 695 696 CombinedDomainXYPlot result = (CombinedDomainXYPlot) super.clone(); 697 result.subplots = (List) ObjectUtilities.deepClone(this.subplots); 698 for (Iterator it = result.subplots.iterator(); it.hasNext();) { 699 Plot child = (Plot) it.next(); 700 child.setParent(result); 701 } 702 703 // after setting up all the subplots, the shared domain axis may need 704 // reconfiguring 705 ValueAxis domainAxis = result.getDomainAxis(); 706 if (domainAxis != null) { 707 domainAxis.configure(); 708 } 709 710 return result; 711 712 } 713 714 }