Source for org.jfree.chart.axis.CategoryAxis

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2006, 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:  * CategoryAxis.java
  29:  * -----------------
  30:  * (C) Copyright 2000-2006, by Object Refinery Limited and Contributors.
  31:  *
  32:  * Original Author:  David Gilbert;
  33:  * Contributor(s):   Pady Srinivasan (patch 1217634);
  34:  *
  35:  * $Id: CategoryAxis.java,v 1.18.2.7 2006/08/18 14:48:31 mungady Exp $
  36:  *
  37:  * Changes (from 21-Aug-2001)
  38:  * --------------------------
  39:  * 21-Aug-2001 : Added standard header. Fixed DOS encoding problem (DG);
  40:  * 18-Sep-2001 : Updated header (DG);
  41:  * 04-Dec-2001 : Changed constructors to protected, and tidied up default 
  42:  *               values (DG);
  43:  * 19-Apr-2002 : Updated import statements (DG);
  44:  * 05-Sep-2002 : Updated constructor for changes in Axis class (DG);
  45:  * 06-Nov-2002 : Moved margins from the CategoryPlot class (DG);
  46:  * 08-Nov-2002 : Moved to new package com.jrefinery.chart.axis (DG);
  47:  * 22-Jan-2002 : Removed monolithic constructor (DG);
  48:  * 26-Mar-2003 : Implemented Serializable (DG);
  49:  * 09-May-2003 : Merged HorizontalCategoryAxis and VerticalCategoryAxis into 
  50:  *               this class (DG);
  51:  * 13-Aug-2003 : Implemented Cloneable (DG);
  52:  * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG);
  53:  * 05-Nov-2003 : Fixed serialization bug (DG);
  54:  * 26-Nov-2003 : Added category label offset (DG);
  55:  * 06-Jan-2004 : Moved axis line attributes to Axis class, rationalised 
  56:  *               category label position attributes (DG);
  57:  * 07-Jan-2004 : Added new implementation for linewrapping of category 
  58:  *               labels (DG);
  59:  * 17-Feb-2004 : Moved deprecated code to bottom of source file (DG);
  60:  * 10-Mar-2004 : Changed Dimension --> Dimension2D in text classes (DG);
  61:  * 16-Mar-2004 : Added support for tooltips on category labels (DG);
  62:  * 01-Apr-2004 : Changed java.awt.geom.Dimension2D to org.jfree.ui.Size2D 
  63:  *               because of JDK bug 4976448 which persists on JDK 1.3.1 (DG);
  64:  * 03-Sep-2004 : Added 'maxCategoryLabelLines' attribute (DG);
  65:  * 04-Oct-2004 : Renamed ShapeUtils --> ShapeUtilities (DG);
  66:  * 11-Jan-2005 : Removed deprecated methods in preparation for 1.0.0 
  67:  *               release (DG);
  68:  * 21-Jan-2005 : Modified return type for RectangleAnchor.coordinates() 
  69:  *               method (DG);
  70:  * 21-Apr-2005 : Replaced Insets with RectangleInsets (DG);
  71:  * 26-Apr-2005 : Removed LOGGER (DG);
  72:  * 08-Jun-2005 : Fixed bug in axis layout (DG);
  73:  * 22-Nov-2005 : Added a method to access the tool tip text for a category
  74:  *               label (DG);
  75:  * 23-Nov-2005 : Added per-category font and paint options - see patch 
  76:  *               1217634 (DG);
  77:  * ------------- JFreeChart 1.0.0 ---------------------------------------------
  78:  * 11-Jan-2006 : Fixed null pointer exception in drawCategoryLabels - see bug
  79:  *               1403043 (DG);
  80:  * 18-Aug-2006 : Fix for bug drawing category labels, thanks to Adriaan
  81:  *               Joubert (1277726) (DG);
  82:  *
  83:  */
  84: 
  85: package org.jfree.chart.axis;
  86: 
  87: import java.awt.Font;
  88: import java.awt.Graphics2D;
  89: import java.awt.Paint;
  90: import java.awt.Shape;
  91: import java.awt.geom.Point2D;
  92: import java.awt.geom.Rectangle2D;
  93: import java.io.IOException;
  94: import java.io.ObjectInputStream;
  95: import java.io.ObjectOutputStream;
  96: import java.io.Serializable;
  97: import java.util.HashMap;
  98: import java.util.Iterator;
  99: import java.util.List;
 100: import java.util.Map;
 101: import java.util.Set;
 102: 
 103: import org.jfree.chart.entity.EntityCollection;
 104: import org.jfree.chart.entity.TickLabelEntity;
 105: import org.jfree.chart.event.AxisChangeEvent;
 106: import org.jfree.chart.plot.CategoryPlot;
 107: import org.jfree.chart.plot.Plot;
 108: import org.jfree.chart.plot.PlotRenderingInfo;
 109: import org.jfree.io.SerialUtilities;
 110: import org.jfree.text.G2TextMeasurer;
 111: import org.jfree.text.TextBlock;
 112: import org.jfree.text.TextUtilities;
 113: import org.jfree.ui.RectangleAnchor;
 114: import org.jfree.ui.RectangleEdge;
 115: import org.jfree.ui.RectangleInsets;
 116: import org.jfree.ui.Size2D;
 117: import org.jfree.util.ObjectUtilities;
 118: import org.jfree.util.PaintUtilities;
 119: import org.jfree.util.ShapeUtilities;
 120: 
 121: /**
 122:  * An axis that displays categories.
 123:  */
 124: public class CategoryAxis extends Axis implements Cloneable, Serializable {
 125: 
 126:     /** For serialization. */
 127:     private static final long serialVersionUID = 5886554608114265863L;
 128:     
 129:     /** 
 130:      * The default margin for the axis (used for both lower and upper margins).
 131:      */
 132:     public static final double DEFAULT_AXIS_MARGIN = 0.05;
 133: 
 134:     /** 
 135:      * The default margin between categories (a percentage of the overall axis
 136:      * length). 
 137:      */
 138:     public static final double DEFAULT_CATEGORY_MARGIN = 0.20;
 139: 
 140:     /** The amount of space reserved at the start of the axis. */
 141:     private double lowerMargin;
 142: 
 143:     /** The amount of space reserved at the end of the axis. */
 144:     private double upperMargin;
 145: 
 146:     /** The amount of space reserved between categories. */
 147:     private double categoryMargin;
 148:     
 149:     /** The maximum number of lines for category labels. */
 150:     private int maximumCategoryLabelLines;
 151: 
 152:     /** 
 153:      * A ratio that is multiplied by the width of one category to determine the 
 154:      * maximum label width. 
 155:      */
 156:     private float maximumCategoryLabelWidthRatio;
 157:     
 158:     /** The category label offset. */
 159:     private int categoryLabelPositionOffset; 
 160:     
 161:     /** 
 162:      * A structure defining the category label positions for each axis 
 163:      * location. 
 164:      */
 165:     private CategoryLabelPositions categoryLabelPositions;
 166:     
 167:     /** Storage for tick label font overrides (if any). */
 168:     private Map tickLabelFontMap;
 169:     
 170:     /** Storage for tick label paint overrides (if any). */
 171:     private transient Map tickLabelPaintMap;
 172:     
 173:     /** Storage for the category label tooltips (if any). */
 174:     private Map categoryLabelToolTips;
 175: 
 176:     /**
 177:      * Creates a new category axis with no label.
 178:      */
 179:     public CategoryAxis() {
 180:         this(null);    
 181:     }
 182:     
 183:     /**
 184:      * Constructs a category axis, using default values where necessary.
 185:      *
 186:      * @param label  the axis label (<code>null</code> permitted).
 187:      */
 188:     public CategoryAxis(String label) {
 189: 
 190:         super(label);
 191: 
 192:         this.lowerMargin = DEFAULT_AXIS_MARGIN;
 193:         this.upperMargin = DEFAULT_AXIS_MARGIN;
 194:         this.categoryMargin = DEFAULT_CATEGORY_MARGIN;
 195:         this.maximumCategoryLabelLines = 1;
 196:         this.maximumCategoryLabelWidthRatio = 0.0f;
 197:         
 198:         setTickMarksVisible(false);  // not supported by this axis type yet
 199:         
 200:         this.categoryLabelPositionOffset = 4;
 201:         this.categoryLabelPositions = CategoryLabelPositions.STANDARD;
 202:         this.tickLabelFontMap = new HashMap();
 203:         this.tickLabelPaintMap = new HashMap();
 204:         this.categoryLabelToolTips = new HashMap();
 205:         
 206:     }
 207: 
 208:     /**
 209:      * Returns the lower margin for the axis.
 210:      *
 211:      * @return The margin.
 212:      */
 213:     public double getLowerMargin() {
 214:         return this.lowerMargin;
 215:     }
 216: 
 217:     /**
 218:      * Sets the lower margin for the axis and sends an {@link AxisChangeEvent} 
 219:      * to all registered listeners.
 220:      *
 221:      * @param margin  the margin as a percentage of the axis length (for 
 222:      *                example, 0.05 is five percent).
 223:      */
 224:     public void setLowerMargin(double margin) {
 225:         this.lowerMargin = margin;
 226:         notifyListeners(new AxisChangeEvent(this));
 227:     }
 228: 
 229:     /**
 230:      * Returns the upper margin for the axis.
 231:      *
 232:      * @return The margin.
 233:      */
 234:     public double getUpperMargin() {
 235:         return this.upperMargin;
 236:     }
 237: 
 238:     /**
 239:      * Sets the upper margin for the axis and sends an {@link AxisChangeEvent}
 240:      * to all registered listeners.
 241:      *
 242:      * @param margin  the margin as a percentage of the axis length (for 
 243:      *                example, 0.05 is five percent).
 244:      */
 245:     public void setUpperMargin(double margin) {
 246:         this.upperMargin = margin;
 247:         notifyListeners(new AxisChangeEvent(this));
 248:     }
 249: 
 250:     /**
 251:      * Returns the category margin.
 252:      *
 253:      * @return The margin.
 254:      */
 255:     public double getCategoryMargin() {
 256:         return this.categoryMargin;
 257:     }
 258: 
 259:     /**
 260:      * Sets the category margin and sends an {@link AxisChangeEvent} to all 
 261:      * registered listeners.  The overall category margin is distributed over 
 262:      * N-1 gaps, where N is the number of categories on the axis.
 263:      *
 264:      * @param margin  the margin as a percentage of the axis length (for 
 265:      *                example, 0.05 is five percent).
 266:      */
 267:     public void setCategoryMargin(double margin) {
 268:         this.categoryMargin = margin;
 269:         notifyListeners(new AxisChangeEvent(this));
 270:     }
 271: 
 272:     /**
 273:      * Returns the maximum number of lines to use for each category label.
 274:      * 
 275:      * @return The maximum number of lines.
 276:      */
 277:     public int getMaximumCategoryLabelLines() {
 278:         return this.maximumCategoryLabelLines;
 279:     }
 280:     
 281:     /**
 282:      * Sets the maximum number of lines to use for each category label and
 283:      * sends an {@link AxisChangeEvent} to all registered listeners.
 284:      * 
 285:      * @param lines  the maximum number of lines.
 286:      */
 287:     public void setMaximumCategoryLabelLines(int lines) {
 288:         this.maximumCategoryLabelLines = lines;
 289:         notifyListeners(new AxisChangeEvent(this));
 290:     }
 291:     
 292:     /**
 293:      * Returns the category label width ratio.
 294:      * 
 295:      * @return The ratio.
 296:      */
 297:     public float getMaximumCategoryLabelWidthRatio() {
 298:         return this.maximumCategoryLabelWidthRatio;
 299:     }
 300:     
 301:     /**
 302:      * Sets the maximum category label width ratio and sends an 
 303:      * {@link AxisChangeEvent} to all registered listeners.
 304:      * 
 305:      * @param ratio  the ratio.
 306:      */
 307:     public void setMaximumCategoryLabelWidthRatio(float ratio) {
 308:         this.maximumCategoryLabelWidthRatio = ratio;
 309:         notifyListeners(new AxisChangeEvent(this));
 310:     }
 311:     
 312:     /**
 313:      * Returns the offset between the axis and the category labels (before 
 314:      * label positioning is taken into account).
 315:      * 
 316:      * @return The offset (in Java2D units).
 317:      */
 318:     public int getCategoryLabelPositionOffset() {
 319:         return this.categoryLabelPositionOffset;
 320:     }
 321:     
 322:     /**
 323:      * Sets the offset between the axis and the category labels (before label 
 324:      * positioning is taken into account).
 325:      * 
 326:      * @param offset  the offset (in Java2D units).
 327:      */
 328:     public void setCategoryLabelPositionOffset(int offset) {
 329:         this.categoryLabelPositionOffset = offset;
 330:         notifyListeners(new AxisChangeEvent(this));
 331:     }
 332:     
 333:     /**
 334:      * Returns the category label position specification (this contains label 
 335:      * positioning info for all four possible axis locations).
 336:      * 
 337:      * @return The positions (never <code>null</code>).
 338:      */
 339:     public CategoryLabelPositions getCategoryLabelPositions() {
 340:         return this.categoryLabelPositions;
 341:     }
 342:     
 343:     /**
 344:      * Sets the category label position specification for the axis and sends an 
 345:      * {@link AxisChangeEvent} to all registered listeners.
 346:      * 
 347:      * @param positions  the positions (<code>null</code> not permitted).
 348:      */
 349:     public void setCategoryLabelPositions(CategoryLabelPositions positions) {
 350:         if (positions == null) {
 351:             throw new IllegalArgumentException("Null 'positions' argument.");   
 352:         }
 353:         this.categoryLabelPositions = positions;
 354:         notifyListeners(new AxisChangeEvent(this));
 355:     }
 356:     
 357:     /**
 358:      * Returns the font for the tick label for the given category.
 359:      * 
 360:      * @param category  the category (<code>null</code> not permitted).
 361:      * 
 362:      * @return The font (never <code>null</code>).
 363:      */
 364:     public Font getTickLabelFont(Comparable category) {
 365:         if (category == null) {
 366:             throw new IllegalArgumentException("Null 'category' argument.");
 367:         }
 368:         Font result = (Font) this.tickLabelFontMap.get(category);
 369:         // if there is no specific font, use the general one...
 370:         if (result == null) {
 371:             result = getTickLabelFont();
 372:         }
 373:         return result;
 374:     }
 375:     
 376:     /**
 377:      * Sets the font for the tick label for the specified category and sends
 378:      * an {@link AxisChangeEvent} to all registered listeners.
 379:      * 
 380:      * @param category  the category (<code>null</code> not permitted).
 381:      * @param font  the font (<code>null</code> permitted).
 382:      */
 383:     public void setTickLabelFont(Comparable category, Font font) {
 384:         if (category == null) {
 385:             throw new IllegalArgumentException("Null 'category' argument.");
 386:         }
 387:         if (font == null) {
 388:             this.tickLabelFontMap.remove(category);
 389:         }
 390:         else {
 391:             this.tickLabelFontMap.put(category, font);
 392:         }
 393:         notifyListeners(new AxisChangeEvent(this));
 394:     }
 395:     
 396:     /**
 397:      * Returns the paint for the tick label for the given category.
 398:      * 
 399:      * @param category  the category (<code>null</code> not permitted).
 400:      * 
 401:      * @return The paint (never <code>null</code>).
 402:      */
 403:     public Paint getTickLabelPaint(Comparable category) {
 404:         if (category == null) {
 405:             throw new IllegalArgumentException("Null 'category' argument.");
 406:         }
 407:         Paint result = (Paint) this.tickLabelPaintMap.get(category);
 408:         // if there is no specific paint, use the general one...
 409:         if (result == null) {
 410:             result = getTickLabelPaint();
 411:         }
 412:         return result;
 413:     }
 414:     
 415:     /**
 416:      * Sets the paint for the tick label for the specified category and sends
 417:      * an {@link AxisChangeEvent} to all registered listeners.
 418:      * 
 419:      * @param category  the category (<code>null</code> not permitted).
 420:      * @param paint  the paint (<code>null</code> permitted).
 421:      */
 422:     public void setTickLabelPaint(Comparable category, Paint paint) {
 423:         if (category == null) {
 424:             throw new IllegalArgumentException("Null 'category' argument.");
 425:         }
 426:         if (paint == null) {
 427:             this.tickLabelPaintMap.remove(category);
 428:         }
 429:         else {
 430:             this.tickLabelPaintMap.put(category, paint);
 431:         }
 432:         notifyListeners(new AxisChangeEvent(this));
 433:     }
 434:     
 435:     /**
 436:      * Adds a tooltip to the specified category and sends an 
 437:      * {@link AxisChangeEvent} to all registered listeners.
 438:      * 
 439:      * @param category  the category (<code>null<code> not permitted).
 440:      * @param tooltip  the tooltip text (<code>null</code> permitted).
 441:      */
 442:     public void addCategoryLabelToolTip(Comparable category, String tooltip) {
 443:         if (category == null) {
 444:             throw new IllegalArgumentException("Null 'category' argument.");   
 445:         }
 446:         this.categoryLabelToolTips.put(category, tooltip);
 447:         notifyListeners(new AxisChangeEvent(this));
 448:     }
 449:     
 450:     /**
 451:      * Returns the tool tip text for the label belonging to the specified 
 452:      * category.
 453:      * 
 454:      * @param category  the category (<code>null</code> not permitted).
 455:      * 
 456:      * @return The tool tip text (possibly <code>null</code>).
 457:      */
 458:     public String getCategoryLabelToolTip(Comparable category) {
 459:         if (category == null) {
 460:             throw new IllegalArgumentException("Null 'category' argument.");
 461:         }
 462:         return (String) this.categoryLabelToolTips.get(category);
 463:     }
 464:     
 465:     /**
 466:      * Removes the tooltip for the specified category and sends an 
 467:      * {@link AxisChangeEvent} to all registered listeners.
 468:      * 
 469:      * @param category  the category (<code>null<code> not permitted).
 470:      */
 471:     public void removeCategoryLabelToolTip(Comparable category) {
 472:         if (category == null) {
 473:             throw new IllegalArgumentException("Null 'category' argument.");   
 474:         }
 475:         this.categoryLabelToolTips.remove(category);   
 476:         notifyListeners(new AxisChangeEvent(this));
 477:     }
 478:     
 479:     /**
 480:      * Clears the category label tooltips and sends an {@link AxisChangeEvent} 
 481:      * to all registered listeners.
 482:      */
 483:     public void clearCategoryLabelToolTips() {
 484:         this.categoryLabelToolTips.clear();
 485:         notifyListeners(new AxisChangeEvent(this));
 486:     }
 487:     
 488:     /**
 489:      * Returns the Java 2D coordinate for a category.
 490:      * 
 491:      * @param anchor  the anchor point.
 492:      * @param category  the category index.
 493:      * @param categoryCount  the category count.
 494:      * @param area  the data area.
 495:      * @param edge  the location of the axis.
 496:      * 
 497:      * @return The coordinate.
 498:      */
 499:     public double getCategoryJava2DCoordinate(CategoryAnchor anchor, 
 500:                                               int category, 
 501:                                               int categoryCount, 
 502:                                               Rectangle2D area,
 503:                                               RectangleEdge edge) {
 504:     
 505:         double result = 0.0;
 506:         if (anchor == CategoryAnchor.START) {
 507:             result = getCategoryStart(category, categoryCount, area, edge);
 508:         }
 509:         else if (anchor == CategoryAnchor.MIDDLE) {
 510:             result = getCategoryMiddle(category, categoryCount, area, edge);
 511:         }
 512:         else if (anchor == CategoryAnchor.END) {
 513:             result = getCategoryEnd(category, categoryCount, area, edge);
 514:         }
 515:         return result;
 516:                                                       
 517:     }
 518:                                               
 519:     /**
 520:      * Returns the starting coordinate for the specified category.
 521:      *
 522:      * @param category  the category.
 523:      * @param categoryCount  the number of categories.
 524:      * @param area  the data area.
 525:      * @param edge  the axis location.
 526:      *
 527:      * @return The coordinate.
 528:      */
 529:     public double getCategoryStart(int category, int categoryCount, 
 530:                                    Rectangle2D area,
 531:                                    RectangleEdge edge) {
 532: 
 533:         double result = 0.0;
 534:         if ((edge == RectangleEdge.TOP) || (edge == RectangleEdge.BOTTOM)) {
 535:             result = area.getX() + area.getWidth() * getLowerMargin();
 536:         }
 537:         else if ((edge == RectangleEdge.LEFT) 
 538:                 || (edge == RectangleEdge.RIGHT)) {
 539:             result = area.getMinY() + area.getHeight() * getLowerMargin();
 540:         }
 541: 
 542:         double categorySize = calculateCategorySize(categoryCount, area, edge);
 543:         double categoryGapWidth = calculateCategoryGapSize(
 544:             categoryCount, area, edge
 545:          );
 546: 
 547:         result = result + category * (categorySize + categoryGapWidth);
 548: 
 549:         return result;
 550:     }
 551: 
 552:     /**
 553:      * Returns the middle coordinate for the specified category.
 554:      *
 555:      * @param category  the category.
 556:      * @param categoryCount  the number of categories.
 557:      * @param area  the data area.
 558:      * @param edge  the axis location.
 559:      *
 560:      * @return The coordinate.
 561:      */
 562:     public double getCategoryMiddle(int category, int categoryCount, 
 563:                                     Rectangle2D area, RectangleEdge edge) {
 564: 
 565:         return getCategoryStart(category, categoryCount, area, edge)
 566:                + calculateCategorySize(categoryCount, area, edge) / 2;
 567: 
 568:     }
 569: 
 570:     /**
 571:      * Returns the end coordinate for the specified category.
 572:      *
 573:      * @param category  the category.
 574:      * @param categoryCount  the number of categories.
 575:      * @param area  the data area.
 576:      * @param edge  the axis location.
 577:      *
 578:      * @return The coordinate.
 579:      */
 580:     public double getCategoryEnd(int category, int categoryCount, 
 581:                                  Rectangle2D area, RectangleEdge edge) {
 582: 
 583:         return getCategoryStart(category, categoryCount, area, edge)
 584:                + calculateCategorySize(categoryCount, area, edge);
 585: 
 586:     }
 587: 
 588:     /**
 589:      * Calculates the size (width or height, depending on the location of the 
 590:      * axis) of a category.
 591:      *
 592:      * @param categoryCount  the number of categories.
 593:      * @param area  the area within which the categories will be drawn.
 594:      * @param edge  the axis location.
 595:      *
 596:      * @return The category size.
 597:      */
 598:     protected double calculateCategorySize(int categoryCount, Rectangle2D area,
 599:                                            RectangleEdge edge) {
 600: 
 601:         double result = 0.0;
 602:         double available = 0.0;
 603: 
 604:         if ((edge == RectangleEdge.TOP) || (edge == RectangleEdge.BOTTOM)) {
 605:             available = area.getWidth();
 606:         }
 607:         else if ((edge == RectangleEdge.LEFT) 
 608:                 || (edge == RectangleEdge.RIGHT)) {
 609:             available = area.getHeight();
 610:         }
 611:         if (categoryCount > 1) {
 612:             result = available * (1 - getLowerMargin() - getUpperMargin() 
 613:                      - getCategoryMargin());
 614:             result = result / categoryCount;
 615:         }
 616:         else {
 617:             result = available * (1 - getLowerMargin() - getUpperMargin());
 618:         }
 619:         return result;
 620: 
 621:     }
 622: 
 623:     /**
 624:      * Calculates the size (width or height, depending on the location of the 
 625:      * axis) of a category gap.
 626:      *
 627:      * @param categoryCount  the number of categories.
 628:      * @param area  the area within which the categories will be drawn.
 629:      * @param edge  the axis location.
 630:      *
 631:      * @return The category gap width.
 632:      */
 633:     protected double calculateCategoryGapSize(int categoryCount, 
 634:                                               Rectangle2D area,
 635:                                               RectangleEdge edge) {
 636: 
 637:         double result = 0.0;
 638:         double available = 0.0;
 639: 
 640:         if ((edge == RectangleEdge.TOP) || (edge == RectangleEdge.BOTTOM)) {
 641:             available = area.getWidth();
 642:         }
 643:         else if ((edge == RectangleEdge.LEFT) 
 644:                 || (edge == RectangleEdge.RIGHT)) {
 645:             available = area.getHeight();
 646:         }
 647: 
 648:         if (categoryCount > 1) {
 649:             result = available * getCategoryMargin() / (categoryCount - 1);
 650:         }
 651: 
 652:         return result;
 653: 
 654:     }
 655: 
 656:     /**
 657:      * Estimates the space required for the axis, given a specific drawing area.
 658:      *
 659:      * @param g2  the graphics device (used to obtain font information).
 660:      * @param plot  the plot that the axis belongs to.
 661:      * @param plotArea  the area within which the axis should be drawn.
 662:      * @param edge  the axis location (top or bottom).
 663:      * @param space  the space already reserved.
 664:      *
 665:      * @return The space required to draw the axis.
 666:      */
 667:     public AxisSpace reserveSpace(Graphics2D g2, Plot plot, 
 668:                                   Rectangle2D plotArea, 
 669:                                   RectangleEdge edge, AxisSpace space) {
 670: 
 671:         // create a new space object if one wasn't supplied...
 672:         if (space == null) {
 673:             space = new AxisSpace();
 674:         }
 675:         
 676:         // if the axis is not visible, no additional space is required...
 677:         if (!isVisible()) {
 678:             return space;
 679:         }
 680: 
 681:         // calculate the max size of the tick labels (if visible)...
 682:         double tickLabelHeight = 0.0;
 683:         double tickLabelWidth = 0.0;
 684:         if (isTickLabelsVisible()) {
 685:             g2.setFont(getTickLabelFont());
 686:             AxisState state = new AxisState();
 687:             // we call refresh ticks just to get the maximum width or height
 688:             refreshTicks(g2, state, plotArea, edge);
 689:             if (edge == RectangleEdge.TOP) {
 690:                 tickLabelHeight = state.getMax();
 691:             }
 692:             else if (edge == RectangleEdge.BOTTOM) {
 693:                 tickLabelHeight = state.getMax();
 694:             }
 695:             else if (edge == RectangleEdge.LEFT) {
 696:                 tickLabelWidth = state.getMax(); 
 697:             }
 698:             else if (edge == RectangleEdge.RIGHT) {
 699:                 tickLabelWidth = state.getMax(); 
 700:             }
 701:         }
 702:         
 703:         // get the axis label size and update the space object...
 704:         Rectangle2D labelEnclosure = getLabelEnclosure(g2, edge);
 705:         double labelHeight = 0.0;
 706:         double labelWidth = 0.0;
 707:         if (RectangleEdge.isTopOrBottom(edge)) {
 708:             labelHeight = labelEnclosure.getHeight();
 709:             space.add(
 710:                 labelHeight + tickLabelHeight 
 711:                 + this.categoryLabelPositionOffset, edge
 712:             );
 713:         }
 714:         else if (RectangleEdge.isLeftOrRight(edge)) {
 715:             labelWidth = labelEnclosure.getWidth();
 716:             space.add(
 717:                 labelWidth + tickLabelWidth + this.categoryLabelPositionOffset, 
 718:                 edge
 719:             );
 720:         }
 721:         return space;
 722: 
 723:     }
 724: 
 725:     /**
 726:      * Configures the axis against the current plot.
 727:      */
 728:     public void configure() {
 729:         // nothing required
 730:     }
 731: 
 732:     /**
 733:      * Draws the axis on a Java 2D graphics device (such as the screen or a 
 734:      * printer).
 735:      *
 736:      * @param g2  the graphics device (<code>null</code> not permitted).
 737:      * @param cursor  the cursor location.
 738:      * @param plotArea  the area within which the axis should be drawn 
 739:      *                  (<code>null</code> not permitted).
 740:      * @param dataArea  the area within which the plot is being drawn 
 741:      *                  (<code>null</code> not permitted).
 742:      * @param edge  the location of the axis (<code>null</code> not permitted).
 743:      * @param plotState  collects information about the plot 
 744:      *                   (<code>null</code> permitted).
 745:      * 
 746:      * @return The axis state (never <code>null</code>).
 747:      */
 748:     public AxisState draw(Graphics2D g2, 
 749:                           double cursor, 
 750:                           Rectangle2D plotArea, 
 751:                           Rectangle2D dataArea,
 752:                           RectangleEdge edge,
 753:                           PlotRenderingInfo plotState) {
 754:         
 755:         // if the axis is not visible, don't draw it...
 756:         if (!isVisible()) {
 757:             return new AxisState(cursor);
 758:         }
 759:         
 760:         if (isAxisLineVisible()) {
 761:             drawAxisLine(g2, cursor, dataArea, edge);
 762:         }
 763: 
 764:         // draw the category labels and axis label
 765:         AxisState state = new AxisState(cursor);
 766:         state = drawCategoryLabels(g2, dataArea, edge, state, plotState);
 767:         state = drawLabel(getLabel(), g2, plotArea, dataArea, edge, state);
 768:     
 769:         return state;
 770: 
 771:     }
 772: 
 773:     /**
 774:      * Draws the category labels and returns the updated axis state.
 775:      *
 776:      * @param g2  the graphics device (<code>null</code> not permitted).
 777:      * @param dataArea  the area inside the axes (<code>null</code> not 
 778:      *                  permitted).
 779:      * @param edge  the axis location (<code>null</code> not permitted).
 780:      * @param state  the axis state (<code>null</code> not permitted).
 781:      * @param plotState  collects information about the plot (<code>null</code>
 782:      *                   permitted).
 783:      * 
 784:      * @return The updated axis state (never <code>null</code>).
 785:      * 
 786:      * @deprecated Use {@link #drawCategoryLabels(Graphics2D, Rectangle2D, 
 787:      *     Rectangle2D, RectangleEdge, AxisState, PlotRenderingInfo)}.
 788:      */
 789:     protected AxisState drawCategoryLabels(Graphics2D g2,
 790:                                            Rectangle2D dataArea,
 791:                                            RectangleEdge edge,
 792:                                            AxisState state,
 793:                                            PlotRenderingInfo plotState) {
 794:         
 795:         // this method is deprecated because we really need the plotArea
 796:         // when drawing the labels - see bug 1277726
 797:         return drawCategoryLabels(g2, dataArea, dataArea, edge, state, 
 798:                 plotState);
 799:     }
 800:     
 801:     /**
 802:      * Draws the category labels and returns the updated axis state.
 803:      *
 804:      * @param g2  the graphics device (<code>null</code> not permitted).
 805:      * @param plotArea  the plot area (<code>null</code> not permitted).
 806:      * @param dataArea  the area inside the axes (<code>null</code> not 
 807:      *                  permitted).
 808:      * @param edge  the axis location (<code>null</code> not permitted).
 809:      * @param state  the axis state (<code>null</code> not permitted).
 810:      * @param plotState  collects information about the plot (<code>null</code>
 811:      *                   permitted).
 812:      * 
 813:      * @return The updated axis state (never <code>null</code>).
 814:      */
 815:     protected AxisState drawCategoryLabels(Graphics2D g2,
 816:                                            Rectangle2D plotArea,
 817:                                            Rectangle2D dataArea,
 818:                                            RectangleEdge edge,
 819:                                            AxisState state,
 820:                                            PlotRenderingInfo plotState) {
 821: 
 822:         if (state == null) {
 823:             throw new IllegalArgumentException("Null 'state' argument.");
 824:         }
 825: 
 826:         if (isTickLabelsVisible()) {       
 827:             List ticks = refreshTicks(g2, state, plotArea, edge);       
 828:             state.setTicks(ticks);        
 829:           
 830:             int categoryIndex = 0;
 831:             Iterator iterator = ticks.iterator();
 832:             while (iterator.hasNext()) {
 833:                 
 834:                 CategoryTick tick = (CategoryTick) iterator.next();
 835:                 g2.setFont(getTickLabelFont(tick.getCategory()));
 836:                 g2.setPaint(getTickLabelPaint(tick.getCategory()));
 837: 
 838:                 CategoryLabelPosition position 
 839:                     = this.categoryLabelPositions.getLabelPosition(edge);
 840:                 double x0 = 0.0;
 841:                 double x1 = 0.0;
 842:                 double y0 = 0.0;
 843:                 double y1 = 0.0;
 844:                 if (edge == RectangleEdge.TOP) {
 845:                     x0 = getCategoryStart(categoryIndex, ticks.size(), 
 846:                             dataArea, edge);
 847:                     x1 = getCategoryEnd(categoryIndex, ticks.size(), dataArea, 
 848:                             edge);
 849:                     y1 = state.getCursor() - this.categoryLabelPositionOffset;
 850:                     y0 = y1 - state.getMax();
 851:                 }
 852:                 else if (edge == RectangleEdge.BOTTOM) {
 853:                     x0 = getCategoryStart(categoryIndex, ticks.size(), 
 854:                             dataArea, edge);
 855:                     x1 = getCategoryEnd(categoryIndex, ticks.size(), dataArea, 
 856:                             edge); 
 857:                     y0 = state.getCursor() + this.categoryLabelPositionOffset;
 858:                     y1 = y0 + state.getMax();
 859:                 }
 860:                 else if (edge == RectangleEdge.LEFT) {
 861:                     y0 = getCategoryStart(categoryIndex, ticks.size(), 
 862:                             dataArea, edge);
 863:                     y1 = getCategoryEnd(categoryIndex, ticks.size(), dataArea, 
 864:                             edge);
 865:                     x1 = state.getCursor() - this.categoryLabelPositionOffset;
 866:                     x0 = x1 - state.getMax();
 867:                 }
 868:                 else if (edge == RectangleEdge.RIGHT) {
 869:                     y0 = getCategoryStart(categoryIndex, ticks.size(), 
 870:                             dataArea, edge);
 871:                     y1 = getCategoryEnd(categoryIndex, ticks.size(), dataArea, 
 872:                             edge);
 873:                     x0 = state.getCursor() + this.categoryLabelPositionOffset;
 874:                     x1 = x0 - state.getMax();
 875:                 }
 876:                 Rectangle2D area = new Rectangle2D.Double(x0, y0, (x1 - x0), 
 877:                         (y1 - y0));
 878:                 Point2D anchorPoint = RectangleAnchor.coordinates(area, 
 879:                         position.getCategoryAnchor());
 880:                 TextBlock block = tick.getLabel();
 881:                 block.draw(g2, (float) anchorPoint.getX(), 
 882:                         (float) anchorPoint.getY(), position.getLabelAnchor(), 
 883:                         (float) anchorPoint.getX(), (float) anchorPoint.getY(), 
 884:                         position.getAngle());
 885:                 Shape bounds = block.calculateBounds(g2, 
 886:                         (float) anchorPoint.getX(), (float) anchorPoint.getY(), 
 887:                         position.getLabelAnchor(), (float) anchorPoint.getX(), 
 888:                         (float) anchorPoint.getY(), position.getAngle());
 889:                 if (plotState != null && plotState.getOwner() != null) {
 890:                     EntityCollection entities 
 891:                         = plotState.getOwner().getEntityCollection();
 892:                     if (entities != null) {
 893:                         String tooltip = getCategoryLabelToolTip(
 894:                                 tick.getCategory());
 895:                         entities.add(new TickLabelEntity(bounds, tooltip, 
 896:                                 null));
 897:                     }
 898:                 }
 899:                 categoryIndex++;
 900:             }
 901: 
 902:             if (edge.equals(RectangleEdge.TOP)) {
 903:                 double h = state.getMax();
 904:                 state.cursorUp(h);
 905:             }
 906:             else if (edge.equals(RectangleEdge.BOTTOM)) {
 907:                 double h = state.getMax();
 908:                 state.cursorDown(h);
 909:             }
 910:             else if (edge == RectangleEdge.LEFT) {
 911:                 double w = state.getMax();
 912:                 state.cursorLeft(w);
 913:             }
 914:             else if (edge == RectangleEdge.RIGHT) {
 915:                 double w = state.getMax();
 916:                 state.cursorRight(w);
 917:             }
 918:         }
 919:         return state;
 920:     }
 921: 
 922:     /**
 923:      * Creates a temporary list of ticks that can be used when drawing the axis.
 924:      *
 925:      * @param g2  the graphics device (used to get font measurements).
 926:      * @param state  the axis state.
 927:      * @param dataArea  the area inside the axes.
 928:      * @param edge  the location of the axis.
 929:      * 
 930:      * @return A list of ticks.
 931:      */
 932:     public List refreshTicks(Graphics2D g2, 
 933:                              AxisState state,
 934:                              Rectangle2D dataArea,
 935:                              RectangleEdge edge) {
 936: 
 937:         List ticks = new java.util.ArrayList();
 938:         
 939:         // sanity check for data area...
 940:         if (dataArea.getHeight() <= 0.0 || dataArea.getWidth() < 0.0) {
 941:             return ticks;
 942:         }
 943: 
 944:         CategoryPlot plot = (CategoryPlot) getPlot();
 945:         List categories = plot.getCategories();
 946:         double max = 0.0;
 947:                 
 948:         if (categories != null) {
 949:             CategoryLabelPosition position 
 950:                 = this.categoryLabelPositions.getLabelPosition(edge);
 951:             float r = this.maximumCategoryLabelWidthRatio;
 952:             if (r <= 0.0) {
 953:                 r = position.getWidthRatio();   
 954:             }
 955:                   
 956:             float l = 0.0f;
 957:             if (position.getWidthType() == CategoryLabelWidthType.CATEGORY) {
 958:                 l = (float) calculateCategorySize(categories.size(), dataArea, 
 959:                         edge);  
 960:             }
 961:             else {
 962:                 if (RectangleEdge.isLeftOrRight(edge)) {
 963:                     l = (float) dataArea.getWidth();   
 964:                 }
 965:                 else {
 966:                     l = (float) dataArea.getHeight();   
 967:                 }
 968:             }
 969:             int categoryIndex = 0;
 970:             Iterator iterator = categories.iterator();
 971:             while (iterator.hasNext()) {
 972:                 Comparable category = (Comparable) iterator.next();
 973:                 TextBlock label = createLabel(category, l * r, edge, g2);
 974:                 if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) {
 975:                     max = Math.max(max, 
 976:                             calculateTextBlockHeight(label, position, g2));
 977:                 }
 978:                 else if (edge == RectangleEdge.LEFT 
 979:                         || edge == RectangleEdge.RIGHT) {
 980:                     max = Math.max(max, 
 981:                             calculateTextBlockWidth(label, position, g2));
 982:                 }
 983:                 Tick tick = new CategoryTick(category, label, 
 984:                         position.getLabelAnchor(), position.getRotationAnchor(), 
 985:                         position.getAngle());
 986:                 ticks.add(tick);
 987:                 categoryIndex = categoryIndex + 1;
 988:             }
 989:         }
 990:         state.setMax(max);
 991:         return ticks;
 992:         
 993:     }
 994: 
 995:     /**
 996:      * Creates a label.
 997:      *
 998:      * @param category  the category.
 999:      * @param width  the available width. 
1000:      * @param edge  the edge on which the axis appears.
1001:      * @param g2  the graphics device.
1002:      *
1003:      * @return A label.
1004:      */
1005:     protected TextBlock createLabel(Comparable category, float width, 
1006:                                     RectangleEdge edge, Graphics2D g2) {
1007:         TextBlock label = TextUtilities.createTextBlock(
1008:             category.toString(), getTickLabelFont(category), 
1009:             getTickLabelPaint(category), width, this.maximumCategoryLabelLines, 
1010:             new G2TextMeasurer(g2));  
1011:         return label; 
1012:     }
1013:     
1014:     /**
1015:      * A utility method for determining the width of a text block.
1016:      *
1017:      * @param block  the text block.
1018:      * @param position  the position.
1019:      * @param g2  the graphics device.
1020:      *
1021:      * @return The width.
1022:      */
1023:     protected double calculateTextBlockWidth(TextBlock block, 
1024:                                              CategoryLabelPosition position, 
1025:                                              Graphics2D g2) {
1026:                                                     
1027:         RectangleInsets insets = getTickLabelInsets();
1028:         Size2D size = block.calculateDimensions(g2);
1029:         Rectangle2D box = new Rectangle2D.Double(
1030:             0.0, 0.0, size.getWidth(), size.getHeight()
1031:         );
1032:         Shape rotatedBox = ShapeUtilities.rotateShape(
1033:             box, position.getAngle(), 0.0f, 0.0f
1034:         );
1035:         double w = rotatedBox.getBounds2D().getWidth() 
1036:                    + insets.getTop() + insets.getBottom();
1037:         return w;
1038:         
1039:     }
1040: 
1041:     /**
1042:      * A utility method for determining the height of a text block.
1043:      *
1044:      * @param block  the text block.
1045:      * @param position  the label position.
1046:      * @param g2  the graphics device.
1047:      *
1048:      * @return The height.
1049:      */
1050:     protected double calculateTextBlockHeight(TextBlock block, 
1051:                                               CategoryLabelPosition position, 
1052:                                               Graphics2D g2) {
1053:                                                     
1054:         RectangleInsets insets = getTickLabelInsets();
1055:         Size2D size = block.calculateDimensions(g2);
1056:         Rectangle2D box = new Rectangle2D.Double(
1057:             0.0, 0.0, size.getWidth(), size.getHeight()
1058:         );
1059:         Shape rotatedBox = ShapeUtilities.rotateShape(
1060:             box, position.getAngle(), 0.0f, 0.0f
1061:         );
1062:         double h = rotatedBox.getBounds2D().getHeight() 
1063:                    + insets.getTop() + insets.getBottom();
1064:         return h;
1065:         
1066:     }
1067: 
1068:     /**
1069:      * Creates a clone of the axis.
1070:      * 
1071:      * @return A clone.
1072:      * 
1073:      * @throws CloneNotSupportedException if some component of the axis does 
1074:      *         not support cloning.
1075:      */
1076:     public Object clone() throws CloneNotSupportedException {
1077:         CategoryAxis clone = (CategoryAxis) super.clone();
1078:         clone.tickLabelFontMap = new HashMap(this.tickLabelFontMap);
1079:         clone.tickLabelPaintMap = new HashMap(this.tickLabelPaintMap);
1080:         clone.categoryLabelToolTips = new HashMap(this.categoryLabelToolTips);
1081:         return clone;  
1082:     }
1083:     
1084:     /**
1085:      * Tests this axis for equality with an arbitrary object.
1086:      *
1087:      * @param obj  the object (<code>null</code> permitted).
1088:      *
1089:      * @return A boolean.
1090:      */
1091:     public boolean equals(Object obj) {
1092:         if (obj == this) {
1093:             return true;
1094:         }
1095:         if (!(obj instanceof CategoryAxis)) {
1096:             return false;
1097:         }
1098:         if (!super.equals(obj)) {
1099:             return false;
1100:         }
1101:         CategoryAxis that = (CategoryAxis) obj;
1102:         if (that.lowerMargin != this.lowerMargin) {
1103:             return false;
1104:         }
1105:         if (that.upperMargin != this.upperMargin) {
1106:             return false;
1107:         }
1108:         if (that.categoryMargin != this.categoryMargin) {
1109:             return false;
1110:         }
1111:         if (that.maximumCategoryLabelWidthRatio 
1112:                 != this.maximumCategoryLabelWidthRatio) {
1113:             return false;
1114:         }
1115:         if (that.categoryLabelPositionOffset 
1116:                 != this.categoryLabelPositionOffset) {
1117:             return false;
1118:         }
1119:         if (!ObjectUtilities.equal(that.categoryLabelPositions, 
1120:                 this.categoryLabelPositions)) {
1121:             return false;
1122:         }
1123:         if (!ObjectUtilities.equal(that.categoryLabelToolTips, 
1124:                 this.categoryLabelToolTips)) {
1125:             return false;
1126:         }
1127:         if (!ObjectUtilities.equal(this.tickLabelFontMap, 
1128:                 that.tickLabelFontMap)) {
1129:             return false;
1130:         }
1131:         if (!equalPaintMaps(this.tickLabelPaintMap, that.tickLabelPaintMap)) {
1132:             return false;
1133:         }
1134:         return true;
1135:     }
1136: 
1137:     /**
1138:      * Returns a hash code for this object.
1139:      * 
1140:      * @return A hash code.
1141:      */
1142:     public int hashCode() {
1143:         if (getLabel() != null) {
1144:             return getLabel().hashCode();
1145:         }
1146:         else {
1147:             return 0;
1148:         }
1149:     }
1150:     
1151:     /**
1152:      * Provides serialization support.
1153:      *
1154:      * @param stream  the output stream.
1155:      *
1156:      * @throws IOException  if there is an I/O error.
1157:      */
1158:     private void writeObject(ObjectOutputStream stream) throws IOException {
1159:         stream.defaultWriteObject();
1160:         writePaintMap(this.tickLabelPaintMap, stream);
1161:     }
1162: 
1163:     /**
1164:      * Provides serialization support.
1165:      *
1166:      * @param stream  the input stream.
1167:      *
1168:      * @throws IOException  if there is an I/O error.
1169:      * @throws ClassNotFoundException  if there is a classpath problem.
1170:      */
1171:     private void readObject(ObjectInputStream stream) 
1172:         throws IOException, ClassNotFoundException {
1173:         stream.defaultReadObject();
1174:         this.tickLabelPaintMap = readPaintMap(stream);
1175:     }
1176:  
1177:     /**
1178:      * Reads a <code>Map</code> of (<code>Comparable</code>, <code>Paint</code>)
1179:      * elements from a stream.
1180:      * 
1181:      * @param in  the input stream.
1182:      * 
1183:      * @return The map.
1184:      * 
1185:      * @throws IOException
1186:      * @throws ClassNotFoundException
1187:      * 
1188:      * @see #writePaintMap(Map, ObjectOutputStream)
1189:      */
1190:     private Map readPaintMap(ObjectInputStream in) 
1191:             throws IOException, ClassNotFoundException {
1192:         boolean isNull = in.readBoolean();
1193:         if (isNull) {
1194:             return null;
1195:         }
1196:         Map result = new HashMap();
1197:         int count = in.readInt();
1198:         for (int i = 0; i < count; i++) {
1199:             Comparable category = (Comparable) in.readObject();
1200:             Paint paint = SerialUtilities.readPaint(in);
1201:             result.put(category, paint);
1202:         }
1203:         return result;
1204:     }
1205:     
1206:     /**
1207:      * Writes a map of (<code>Comparable</code>, <code>Paint</code>)
1208:      * elements to a stream.
1209:      * 
1210:      * @param map  the map (<code>null</code> permitted).
1211:      * 
1212:      * @param out
1213:      * @throws IOException
1214:      * 
1215:      * @see #readPaintMap(ObjectInputStream)
1216:      */
1217:     private void writePaintMap(Map map, ObjectOutputStream out) 
1218:             throws IOException {
1219:         if (map == null) {
1220:             out.writeBoolean(true);
1221:         }
1222:         else {
1223:             out.writeBoolean(false);
1224:             Set keys = map.keySet();
1225:             int count = keys.size();
1226:             out.writeInt(count);
1227:             Iterator iterator = keys.iterator();
1228:             while (iterator.hasNext()) {
1229:                 Comparable key = (Comparable) iterator.next();
1230:                 out.writeObject(key);
1231:                 SerialUtilities.writePaint((Paint) map.get(key), out);
1232:             }
1233:         }
1234:     }
1235:     
1236:     /**
1237:      * Tests two maps containing (<code>Comparable</code>, <code>Paint</code>)
1238:      * elements for equality.
1239:      * 
1240:      * @param map1  the first map (<code>null</code> not permitted).
1241:      * @param map2  the second map (<code>null</code> not permitted).
1242:      * 
1243:      * @return A boolean.
1244:      */
1245:     private boolean equalPaintMaps(Map map1, Map map2) {
1246:         if (map1.size() != map2.size()) {
1247:             return false;
1248:         }
1249:         Set keys = map1.keySet();
1250:         Iterator iterator = keys.iterator();
1251:         while (iterator.hasNext()) {
1252:             Comparable key = (Comparable) iterator.next();
1253:             Paint p1 = (Paint) map1.get(key);
1254:             Paint p2 = (Paint) map2.get(key);
1255:             if (!PaintUtilities.equal(p1, p2)) {
1256:                 return false;  
1257:             }
1258:         }
1259:         return true;
1260:     }
1261: 
1262: }