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