KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jfree > chart > axis > CategoryAxis


1 /* ===========================================================
2  * JFreeChart : a free chart library for the Java(tm) platform
3  * ===========================================================
4  *
5  * (C) Copyright 2000-2005, 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 License
20  * along with this library; if not, write to the Free Software Foundation,
21  * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
22  *
23  * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
24  * in the United States and other countries.]
25  *
26  * -----------------
27  * CategoryAxis.java
28  * -----------------
29  * (C) Copyright 2000-2005, by Object Refinery Limited.
30  *
31  * Original Author: David Gilbert;
32  * Contributor(s): -;
33  *
34  * $Id: CategoryAxis.java,v 1.18 2005/05/19 13:58:11 mungady Exp $
35  *
36  * Changes (from 21-Aug-2001)
37  * --------------------------
38  * 21-Aug-2001 : Added standard header. Fixed DOS encoding problem (DG);
39  * 18-Sep-2001 : Updated header (DG);
40  * 04-Dec-2001 : Changed constructors to protected, and tidied up default
41  * values (DG);
42  * 19-Apr-2002 : Updated import statements (DG);
43  * 05-Sep-2002 : Updated constructor for changes in Axis class (DG);
44  * 06-Nov-2002 : Moved margins from the CategoryPlot class (DG);
45  * 08-Nov-2002 : Moved to new package com.jrefinery.chart.axis (DG);
46  * 22-Jan-2002 : Removed monolithic constructor (DG);
47  * 26-Mar-2003 : Implemented Serializable (DG);
48  * 09-May-2003 : Merged HorizontalCategoryAxis and VerticalCategoryAxis into
49  * this class (DG);
50  * 13-Aug-2003 : Implemented Cloneable (DG);
51  * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG);
52  * 05-Nov-2003 : Fixed serialization bug (DG);
53  * 26-Nov-2003 : Added category label offset (DG);
54  * 06-Jan-2004 : Moved axis line attributes to Axis class, rationalised
55  * category label position attributes (DG);
56  * 07-Jan-2004 : Added new implementation for linewrapping of category
57  * labels (DG);
58  * 17-Feb-2004 : Moved deprecated code to bottom of source file (DG);
59  * 10-Mar-2004 : Changed Dimension --> Dimension2D in text classes (DG);
60  * 16-Mar-2004 : Added support for tooltips on category labels (DG);
61  * 01-Apr-2004 : Changed java.awt.geom.Dimension2D to org.jfree.ui.Size2D
62  * because of JDK bug 4976448 which persists on JDK 1.3.1 (DG);
63  * 03-Sep-2004 : Added 'maxCategoryLabelLines' attribute (DG);
64  * 04-Oct-2004 : Renamed ShapeUtils --> ShapeUtilities (DG);
65  * 11-Jan-2005 : Removed deprecated methods in preparation for 1.0.0
66  * release (DG);
67  * 21-Jan-2005 : Modified return type for RectangleAnchor.coordinates()
68  * method (DG);
69  * 21-Apr-2005 : Replaced Insets with RectangleInsets (DG);
70  * 26-Apr-2005 : Removed LOGGER (DG);
71  *
72  */

73
74 package org.jfree.chart.axis;
75
76 import java.awt.Graphics2D JavaDoc;
77 import java.awt.Shape JavaDoc;
78 import java.awt.geom.Point2D JavaDoc;
79 import java.awt.geom.Rectangle2D JavaDoc;
80 import java.io.IOException JavaDoc;
81 import java.io.ObjectInputStream JavaDoc;
82 import java.io.ObjectOutputStream JavaDoc;
83 import java.io.Serializable JavaDoc;
84 import java.util.HashMap JavaDoc;
85 import java.util.Iterator JavaDoc;
86 import java.util.List JavaDoc;
87 import java.util.Map JavaDoc;
88
89 import org.jfree.chart.entity.EntityCollection;
90 import org.jfree.chart.entity.TickLabelEntity;
91 import org.jfree.chart.event.AxisChangeEvent;
92 import org.jfree.chart.plot.CategoryPlot;
93 import org.jfree.chart.plot.Plot;
94 import org.jfree.chart.plot.PlotRenderingInfo;
95 import org.jfree.text.G2TextMeasurer;
96 import org.jfree.text.TextBlock;
97 import org.jfree.text.TextUtilities;
98 import org.jfree.ui.RectangleAnchor;
99 import org.jfree.ui.RectangleEdge;
100 import org.jfree.ui.RectangleInsets;
101 import org.jfree.ui.Size2D;
102 import org.jfree.util.ObjectUtilities;
103 import org.jfree.util.ShapeUtilities;
104
105 /**
106  * An axis that displays categories.
107  */

108 public class CategoryAxis extends Axis implements Cloneable JavaDoc, Serializable JavaDoc {
109
110     /** For serialization. */
111     private static final long serialVersionUID = 5886554608114265863L;
112     
113     /**
114      * The default margin for the axis (used for both lower and upper margins).
115      */

116     public static final double DEFAULT_AXIS_MARGIN = 0.05;
117
118     /**
119      * The default margin between categories (a percentage of the overall axis
120      * length).
121      */

122     public static final double DEFAULT_CATEGORY_MARGIN = 0.20;
123
124     /** The amount of space reserved at the start of the axis. */
125     private double lowerMargin;
126
127     /** The amount of space reserved at the end of the axis. */
128     private double upperMargin;
129
130     /** The amount of space reserved between categories. */
131     private double categoryMargin;
132     
133     /** The maximum number of lines for category labels. */
134     private int maximumCategoryLabelLines;
135
136     /**
137      * A ratio that is multiplied by the width of one category to determine the
138      * maximum label width.
139      */

140     private float maximumCategoryLabelWidthRatio;
141     
142     /** The category label offset. */
143     private int categoryLabelPositionOffset;
144     
145     /**
146      * A structure defining the category label positions for each axis
147      * location.
148      */

149     private CategoryLabelPositions categoryLabelPositions;
150     
151     /** Storage for the category label tooltips (if any). */
152     private Map JavaDoc categoryLabelToolTips;
153
154     /**
155      * Creates a new category axis with no label.
156      */

157     public CategoryAxis() {
158         this(null);
159     }
160     
161     /**
162      * Constructs a category axis, using default values where necessary.
163      *
164      * @param label the axis label (<code>null</code> permitted).
165      */

166     public CategoryAxis(String JavaDoc label) {
167
168         super(label);
169
170         this.lowerMargin = DEFAULT_AXIS_MARGIN;
171         this.upperMargin = DEFAULT_AXIS_MARGIN;
172         this.categoryMargin = DEFAULT_CATEGORY_MARGIN;
173         this.maximumCategoryLabelLines = 1;
174         this.maximumCategoryLabelWidthRatio = 0.0f;
175         
176         setTickMarksVisible(false); // not supported by this axis type yet
177

178         this.categoryLabelPositionOffset = 4;
179         this.categoryLabelPositions = CategoryLabelPositions.STANDARD;
180         this.categoryLabelToolTips = new HashMap JavaDoc();
181         
182     }
183
184     /**
185      * Returns the lower margin for the axis.
186      *
187      * @return The margin.
188      */

189     public double getLowerMargin() {
190         return this.lowerMargin;
191     }
192
193     /**
194      * Sets the lower margin for the axis and sends an {@link AxisChangeEvent}
195      * to all registered listeners.
196      *
197      * @param margin the margin as a percentage of the axis length (for
198      * example, 0.05 is five percent).
199      */

200     public void setLowerMargin(double margin) {
201         this.lowerMargin = margin;
202         notifyListeners(new AxisChangeEvent(this));
203     }
204
205     /**
206      * Returns the upper margin for the axis.
207      *
208      * @return The margin.
209      */

210     public double getUpperMargin() {
211         return this.upperMargin;
212     }
213
214     /**
215      * Sets the upper margin for the axis and sends an {@link AxisChangeEvent}
216      * to all registered listeners.
217      *
218      * @param margin the margin as a percentage of the axis length (for
219      * example, 0.05 is five percent).
220      */

221     public void setUpperMargin(double margin) {
222         this.upperMargin = margin;
223         notifyListeners(new AxisChangeEvent(this));
224     }
225
226     /**
227      * Returns the category margin.
228      *
229      * @return The margin.
230      */

231     public double getCategoryMargin() {
232         return this.categoryMargin;
233     }
234
235     /**
236      * Sets the category margin and sends an {@link AxisChangeEvent} to all
237      * registered listeners. The overall category margin is distributed over
238      * N-1 gaps, where N is the number of categories on the axis.
239      *
240      * @param margin the margin as a percentage of the axis length (for
241      * example, 0.05 is five percent).
242      */

243     public void setCategoryMargin(double margin) {
244         this.categoryMargin = margin;
245         notifyListeners(new AxisChangeEvent(this));
246     }
247
248     /**
249      * Returns the maximum number of lines to use for each category label.
250      *
251      * @return The maximum number of lines.
252      */

253     public int getMaximumCategoryLabelLines() {
254         return this.maximumCategoryLabelLines;
255     }
256     
257     /**
258      * Sets the maximum number of lines to use for each category label and
259      * sends an {@link AxisChangeEvent} to all registered listeners.
260      *
261      * @param lines the maximum number of lines.
262      */

263     public void setMaximumCategoryLabelLines(int lines) {
264         this.maximumCategoryLabelLines = lines;
265         notifyListeners(new AxisChangeEvent(this));
266     }
267     
268     /**
269      * Returns the category label width ratio.
270      *
271      * @return The ratio.
272      */

273     public float getMaximumCategoryLabelWidthRatio() {
274         return this.maximumCategoryLabelWidthRatio;
275     }
276     
277     /**
278      * Sets the maximum category label width ratio and sends an
279      * {@link AxisChangeEvent} to all registered listeners.
280      *
281      * @param ratio the ratio.
282      */

283     public void setMaximumCategoryLabelWidthRatio(float ratio) {
284         this.maximumCategoryLabelWidthRatio = ratio;
285         notifyListeners(new AxisChangeEvent(this));
286     }
287     
288     /**
289      * Returns the offset between the axis and the category labels (before
290      * label positioning is taken into account).
291      *
292      * @return The offset (in Java2D units).
293      */

294     public int getCategoryLabelPositionOffset() {
295         return this.categoryLabelPositionOffset;
296     }
297     
298     /**
299      * Sets the offset between the axis and the category labels (before label
300      * positioning is taken into account).
301      *
302      * @param offset the offset (in Java2D units).
303      */

304     public void setCategoryLabelPositionOffset(int offset) {
305         this.categoryLabelPositionOffset = offset;
306         notifyListeners(new AxisChangeEvent(this));
307     }
308     
309     /**
310      * Returns the category label position specification (this contains label
311      * positioning info for all four possible axis locations).
312      *
313      * @return The positions (never <code>null</code>).
314      */

315     public CategoryLabelPositions getCategoryLabelPositions() {
316         return this.categoryLabelPositions;
317     }
318     
319     /**
320      * Sets the category label position specification for the axis and sends an
321      * {@link AxisChangeEvent} to all registered listeners.
322      *
323      * @param positions the positions (<code>null</code> not permitted).
324      */

325     public void setCategoryLabelPositions(CategoryLabelPositions positions) {
326         if (positions == null) {
327             throw new IllegalArgumentException JavaDoc("Null 'positions' argument.");
328         }
329         this.categoryLabelPositions = positions;
330         notifyListeners(new AxisChangeEvent(this));
331     }
332     
333     /**
334      * Adds a tooltip to the specified category and sends an
335      * {@link AxisChangeEvent} to all registered listeners.
336      *
337      * @param category the category (<code>null<code> not permitted).
338      * @param tooltip the tooltip text (<code>null</code> permitted).
339      */

340     public void addCategoryLabelToolTip(Comparable JavaDoc category, String JavaDoc tooltip) {
341         if (category == null) {
342             throw new IllegalArgumentException JavaDoc("Null 'category' argument.");
343         }
344         this.categoryLabelToolTips.put(category, tooltip);
345         notifyListeners(new AxisChangeEvent(this));
346     }
347     
348     /**
349      * Removes the tooltip for the specified category and sends an
350      * {@link AxisChangeEvent} to all registered listeners.
351      *
352      * @param category the category (<code>null<code> not permitted).
353      */

354     public void removeCategoryLabelToolTip(Comparable JavaDoc category) {
355         if (category == null) {
356             throw new IllegalArgumentException JavaDoc("Null 'category' argument.");
357         }
358         this.categoryLabelToolTips.remove(category);
359         notifyListeners(new AxisChangeEvent(this));
360     }
361     
362     /**
363      * Clears the category label tooltips and sends an {@link AxisChangeEvent}
364      * to all registered listeners.
365      */

366     public void clearCategoryLabelToolTips() {
367         this.categoryLabelToolTips.clear();
368         notifyListeners(new AxisChangeEvent(this));
369     }
370     
371     /**
372      * Returns the Java 2D coordinate for a category.
373      *
374      * @param anchor the anchor point.
375      * @param category the category index.
376      * @param categoryCount the category count.
377      * @param area the data area.
378      * @param edge the location of the axis.
379      *
380      * @return The coordinate.
381      */

382     public double getCategoryJava2DCoordinate(CategoryAnchor anchor,
383                                               int category,
384                                               int categoryCount,
385                                               Rectangle2D JavaDoc area,
386                                               RectangleEdge edge) {
387     
388         double result = 0.0;
389         if (anchor == CategoryAnchor.START) {
390             result = getCategoryStart(category, categoryCount, area, edge);
391         }
392         else if (anchor == CategoryAnchor.MIDDLE) {
393             result = getCategoryMiddle(category, categoryCount, area, edge);
394         }
395         else if (anchor == CategoryAnchor.END) {
396             result = getCategoryEnd(category, categoryCount, area, edge);
397         }
398         return result;
399                                                       
400     }
401                                               
402     /**
403      * Returns the starting coordinate for the specified category.
404      *
405      * @param category the category.
406      * @param categoryCount the number of categories.
407      * @param area the data area.
408      * @param edge the axis location.
409      *
410      * @return The coordinate.
411      */

412     public double getCategoryStart(int category, int categoryCount,
413                                    Rectangle2D JavaDoc area,
414                                    RectangleEdge edge) {
415
416         double result = 0.0;
417         if ((edge == RectangleEdge.TOP) || (edge == RectangleEdge.BOTTOM)) {
418             result = area.getX() + area.getWidth() * getLowerMargin();
419         }
420         else if ((edge == RectangleEdge.LEFT)
421                 || (edge == RectangleEdge.RIGHT)) {
422             result = area.getMinY() + area.getHeight() * getLowerMargin();
423         }
424
425         double categorySize = calculateCategorySize(categoryCount, area, edge);
426         double categoryGapWidth = calculateCategoryGapSize(
427             categoryCount, area, edge
428          );
429
430         result = result + category * (categorySize + categoryGapWidth);
431
432         return result;
433     }
434
435     /**
436      * Returns the middle coordinate for the specified category.
437      *
438      * @param category the category.
439      * @param categoryCount the number of categories.
440      * @param area the data area.
441      * @param edge the axis location.
442      *
443      * @return The coordinate.
444      */

445     public double getCategoryMiddle(int category, int categoryCount,
446                                     Rectangle2D JavaDoc area, RectangleEdge edge) {
447
448         return getCategoryStart(category, categoryCount, area, edge)
449                + calculateCategorySize(categoryCount, area, edge) / 2;
450
451     }
452
453     /**
454      * Returns the end coordinate for the specified category.
455      *
456      * @param category the category.
457      * @param categoryCount the number of categories.
458      * @param area the data area.
459      * @param edge the axis location.
460      *
461      * @return The coordinate.
462      */

463     public double getCategoryEnd(int category, int categoryCount,
464                                  Rectangle2D JavaDoc area, RectangleEdge edge) {
465
466         return getCategoryStart(category, categoryCount, area, edge)
467                + calculateCategorySize(categoryCount, area, edge);
468
469     }
470
471     /**
472      * Calculates the size (width or height, depending on the location of the
473      * axis) of a category.
474      *
475      * @param categoryCount the number of categories.
476      * @param area the area within which the categories will be drawn.
477      * @param edge the axis location.
478      *
479      * @return The category size.
480      */

481     protected double calculateCategorySize(int categoryCount, Rectangle2D JavaDoc area,
482                                            RectangleEdge edge) {
483
484         double result = 0.0;
485         double available = 0.0;
486
487         if ((edge == RectangleEdge.TOP) || (edge == RectangleEdge.BOTTOM)) {
488             available = area.getWidth();
489         }
490         else if ((edge == RectangleEdge.LEFT)
491                 || (edge == RectangleEdge.RIGHT)) {
492             available = area.getHeight();
493         }
494         if (categoryCount > 1) {
495             result = available * (1 - getLowerMargin() - getUpperMargin()
496                      - getCategoryMargin());
497             result = result / categoryCount;
498         }
499         else {
500             result = available * (1 - getLowerMargin() - getUpperMargin());
501         }
502         return result;
503
504     }
505
506     /**
507      * Calculates the size (width or height, depending on the location of the
508      * axis) of a category gap.
509      *
510      * @param categoryCount the number of categories.
511      * @param area the area within which the categories will be drawn.
512      * @param edge the axis location.
513      *
514      * @return The category gap width.
515      */

516     protected double calculateCategoryGapSize(int categoryCount,
517                                               Rectangle2D JavaDoc area,
518                                               RectangleEdge edge) {
519
520         double result = 0.0;
521         double available = 0.0;
522
523         if ((edge == RectangleEdge.TOP) || (edge == RectangleEdge.BOTTOM)) {
524             available = area.getWidth();
525         }
526         else if ((edge == RectangleEdge.LEFT)
527                 || (edge == RectangleEdge.RIGHT)) {
528             available = area.getHeight();
529         }
530
531         if (categoryCount > 1) {
532             result = available * getCategoryMargin() / (categoryCount - 1);
533         }
534
535         return result;
536
537     }
538
539     /**
540      * Estimates the space required for the axis, given a specific drawing area.
541      *
542      * @param g2 the graphics device (used to obtain font information).
543      * @param plot the plot that the axis belongs to.
544      * @param plotArea the area within which the axis should be drawn.
545      * @param edge the axis location (top or bottom).
546      * @param space the space already reserved.
547      *
548      * @return The space required to draw the axis.
549      */

550     public AxisSpace reserveSpace(Graphics2D JavaDoc g2, Plot plot,
551                                   Rectangle2D JavaDoc plotArea,
552                                   RectangleEdge edge, AxisSpace space) {
553
554         // create a new space object if one wasn't supplied...
555
if (space == null) {
556             space = new AxisSpace();
557         }
558         
559         // if the axis is not visible, no additional space is required...
560
if (!isVisible()) {
561             return space;
562         }
563
564         // calculate the max size of the tick labels (if visible)...
565
double tickLabelHeight = 0.0;
566         double tickLabelWidth = 0.0;
567         if (isTickLabelsVisible()) {
568             g2.setFont(getTickLabelFont());
569             AxisState state = new AxisState();
570             refreshTicks(g2, state, plotArea, edge);
571             if (edge == RectangleEdge.TOP) {
572                 tickLabelHeight = state.getMax();
573             }
574             else if (edge == RectangleEdge.BOTTOM) {
575                 tickLabelHeight = state.getMax();
576             }
577             else if (edge == RectangleEdge.LEFT) {
578                 tickLabelWidth = state.getMax();
579             }
580             else if (edge == RectangleEdge.RIGHT) {
581                 tickLabelWidth = state.getMax();
582             }
583         }
584         
585         // get the axis label size and update the space object...
586
Rectangle2D JavaDoc labelEnclosure = getLabelEnclosure(g2, edge);
587         double labelHeight = 0.0;
588         double labelWidth = 0.0;
589         if (RectangleEdge.isTopOrBottom(edge)) {
590             labelHeight = labelEnclosure.getHeight();
591             space.add(
592                 labelHeight + tickLabelHeight
593                 + this.categoryLabelPositionOffset, edge
594             );
595         }
596         else if (RectangleEdge.isLeftOrRight(edge)) {
597             labelWidth = labelEnclosure.getWidth();
598             space.add(
599                 labelWidth + tickLabelWidth + this.categoryLabelPositionOffset,
600                 edge
601             );
602         }
603         return space;
604
605     }
606
607     /**
608      * Configures the axis against the current plot.
609      */

610     public void configure() {
611         // nothing required
612
}
613
614     /**
615      * Draws the axis on a Java 2D graphics device (such as the screen or a
616      * printer).
617      *
618      * @param g2 the graphics device (<code>null</code> not permitted).
619      * @param cursor the cursor location.
620      * @param plotArea the area within which the axis should be drawn
621      * (<code>null</code> not permitted).
622      * @param dataArea the area within which the plot is being drawn
623      * (<code>null</code> not permitted).
624      * @param edge the location of the axis (<code>null</code> not permitted).
625      * @param plotState collects information about the plot
626      * (<code>null</code> permitted).
627      *
628      * @return The axis state (never <code>null</code>).
629      */

630     public AxisState draw(Graphics2D JavaDoc g2,
631                           double cursor,
632                           Rectangle2D JavaDoc plotArea,
633                           Rectangle2D JavaDoc dataArea,
634                           RectangleEdge edge,
635                           PlotRenderingInfo plotState) {
636         
637         // if the axis is not visible, don't draw it...
638
if (!isVisible()) {
639             return new AxisState(cursor);
640         }
641         
642         if (isAxisLineVisible()) {
643             drawAxisLine(g2, cursor, dataArea, edge);
644         }
645
646         // draw the category labels and axis label
647
AxisState state = new AxisState(cursor);
648         state = drawCategoryLabels(g2, dataArea, edge, state, plotState);
649         state = drawLabel(getLabel(), g2, plotArea, dataArea, edge, state);
650     
651         return state;
652
653     }
654
655     /**
656      * Draws the category labels and returns the updated axis state.
657      *
658      * @param g2 the graphics device (<code>null</code> not permitted).
659      * @param dataArea the area inside the axes (<code>null</code> not
660      * permitted).
661      * @param edge the axis location (<code>null</code> not permitted).
662      * @param state the axis state (<code>null</code> not permitted).
663      * @param plotState collects information about the plot (<code>null</code>
664      * permitted).
665      *
666      * @return The updated axis state (never <code>null</code>).
667      */

668     protected AxisState drawCategoryLabels(Graphics2D JavaDoc g2,
669                                            Rectangle2D JavaDoc dataArea,
670                                            RectangleEdge edge,
671                                            AxisState state,
672                                            PlotRenderingInfo plotState) {
673
674         if (state == null) {
675             throw new IllegalArgumentException JavaDoc("Null 'state' argument.");
676         }
677
678         if (isTickLabelsVisible()) {
679             g2.setFont(getTickLabelFont());
680             g2.setPaint(getTickLabelPaint());
681             List JavaDoc ticks = refreshTicks(g2, state, dataArea, edge);
682             state.setTicks(ticks);
683           
684             int categoryIndex = 0;
685             Iterator JavaDoc iterator = ticks.iterator();
686             while (iterator.hasNext()) {
687                 
688                 CategoryTick tick = (CategoryTick) iterator.next();
689                 g2.setPaint(getTickLabelPaint());
690
691                 CategoryLabelPosition position
692                     = this.categoryLabelPositions.getLabelPosition(edge);
693                 double x0 = 0.0;
694                 double x1 = 0.0;
695                 double y0 = 0.0;
696                 double y1 = 0.0;
697                 if (edge == RectangleEdge.TOP) {
698                     x0 = getCategoryStart(
699                         categoryIndex, ticks.size(), dataArea, edge
700                     );
701                     x1 = getCategoryEnd(
702                         categoryIndex, ticks.size(), dataArea, edge
703                     );
704                     y1 = state.getCursor() - this.categoryLabelPositionOffset;
705                     y0 = y1 - state.getMax();
706                 }
707                 else if (edge == RectangleEdge.BOTTOM) {
708                     x0 = getCategoryStart(
709                         categoryIndex, ticks.size(), dataArea, edge
710                     );
711                     x1 = getCategoryEnd(
712                         categoryIndex, ticks.size(), dataArea, edge
713                     );
714                     y0 = state.getCursor() + this.categoryLabelPositionOffset;
715                     y1 = y0 + state.getMax();
716                 }
717                 else if (edge == RectangleEdge.LEFT) {
718                     y0 = getCategoryStart(
719                         categoryIndex, ticks.size(), dataArea, edge
720                     );
721                     y1 = getCategoryEnd(
722                         categoryIndex, ticks.size(), dataArea, edge
723                     );
724                     x1 = state.getCursor() - this.categoryLabelPositionOffset;
725                     x0 = x1 - state.getMax();
726                 }
727                 else if (edge == RectangleEdge.RIGHT) {
728                     y0 = getCategoryStart(
729                         categoryIndex, ticks.size(), dataArea, edge
730                     );
731                     y1 = getCategoryEnd(
732                         categoryIndex, ticks.size(), dataArea, edge
733                     );
734                     x0 = state.getCursor() + this.categoryLabelPositionOffset;
735                     x1 = x0 - state.getMax();
736                 }
737                 Rectangle2D JavaDoc area = new Rectangle2D.Double JavaDoc(
738                     x0, y0, (x1 - x0), (y1 - y0)
739                 );
740                 Point2D JavaDoc anchorPoint = RectangleAnchor.coordinates(
741                     area, position.getCategoryAnchor()
742                 );
743                 TextBlock block = tick.getLabel();
744                 block.draw(
745                     g2,
746                     (float) anchorPoint.getX(), (float) anchorPoint.getY(),
747                     position.getLabelAnchor(),
748                     (float) anchorPoint.getX(), (float) anchorPoint.getY(),
749                     position.getAngle()
750                 );
751                 Shape JavaDoc bounds = block.calculateBounds(
752                     g2, (float) anchorPoint.getX(), (float) anchorPoint.getY(),
753                     position.getLabelAnchor(),
754                     (float) anchorPoint.getX(), (float) anchorPoint.getY(),
755                     position.getAngle()
756                 );
757                 if (plotState != null) {
758                     EntityCollection entities
759                         = plotState.getOwner().getEntityCollection();
760                     if (entities != null) {
761                         String JavaDoc tooltip
762                             = (String JavaDoc) this.categoryLabelToolTips.get(
763                                 tick.getCategory()
764                             );
765                         entities.add(
766                             new TickLabelEntity(bounds, tooltip, null)
767                         );
768                     }
769                 }
770                 categoryIndex++;
771             }
772
773             if (edge.equals(RectangleEdge.TOP)) {
774                 double h = state.getMax();
775                 state.cursorUp(h);
776             }
777             else if (edge.equals(RectangleEdge.BOTTOM)) {
778                 double h = state.getMax();
779                 state.cursorDown(h);
780             }
781             else if (edge == RectangleEdge.LEFT) {
782                 double w = state.getMax();
783                 state.cursorLeft(w);
784             }
785             else if (edge == RectangleEdge.RIGHT) {
786                 double w = state.getMax();
787                 state.cursorRight(w);
788             }
789         }
790         return state;
791     }
792
793     /**
794      * Creates a temporary list of ticks that can be used when drawing the axis.
795      *
796      * @param g2 the graphics device (used to get font measurements).
797      * @param state the axis state.
798      * @param dataArea the area inside the axes.
799      * @param edge the location of the axis.
800      *
801      * @return A list of ticks.
802      */

803     public List JavaDoc refreshTicks(Graphics2D JavaDoc g2,
804                              AxisState state,
805                              Rectangle2D JavaDoc dataArea,
806                              RectangleEdge edge) {
807
808         List JavaDoc ticks = new java.util.ArrayList JavaDoc();
809         
810         // sanity check for data area...
811
if (dataArea.getHeight() <= 0.0 || dataArea.getWidth() < 0.0) {
812             return ticks;
813         }
814
815         CategoryPlot plot = (CategoryPlot) getPlot();
816         List JavaDoc categories = plot.getCategories();
817         double max = 0.0;
818                 
819         if (categories != null) {
820             CategoryLabelPosition position
821                 = this.categoryLabelPositions.getLabelPosition(edge);
822             float r = this.maximumCategoryLabelWidthRatio;
823             if (r <= 0.0) {
824                 r = position.getWidthRatio();
825             }
826                   
827             float l = 0.0f;
828             if (position.getWidthType() == CategoryLabelWidthType.CATEGORY) {
829                 l = (float) calculateCategorySize(
830                     categories.size(), dataArea, edge
831                 );
832             }
833             else {
834                 if (RectangleEdge.isLeftOrRight(edge)) {
835                     l = (float) dataArea.getWidth();
836                 }
837                 else {
838                     l = (float) dataArea.getHeight();
839                 }
840             }
841             int categoryIndex = 0;
842             Iterator JavaDoc iterator = categories.iterator();
843             while (iterator.hasNext()) {
844                 Comparable JavaDoc category = (Comparable JavaDoc) iterator.next();
845                 TextBlock label = createLabel(category, l * r, edge, g2);
846                 if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) {
847                     max = Math.max(
848                         max, calculateTextBlockHeight(label, position, g2)
849                     );
850                 }
851                 else if (edge == RectangleEdge.LEFT
852                         || edge == RectangleEdge.RIGHT) {
853                     max = Math.max(
854                         max, calculateTextBlockWidth(label, position, g2)
855                     );
856                 }
857                 Tick tick = new CategoryTick(
858                     category, label,
859                     position.getLabelAnchor(), position.getRotationAnchor(),
860                     position.getAngle()
861                 );
862                 ticks.add(tick);
863                 categoryIndex = categoryIndex + 1;
864             }
865         }
866         state.setMax(max);
867         return ticks;
868         
869     }
870
871     /**
872      * Creates a label.
873      *
874      * @param category the category.
875      * @param width the available width.
876      * @param edge the edge on which the axis appears.
877      * @param g2 the graphics device.
878      *
879      * @return A label.
880      */

881     protected TextBlock createLabel(Comparable JavaDoc category, float width,
882                                     RectangleEdge edge, Graphics2D JavaDoc g2) {
883         TextBlock label = TextUtilities.createTextBlock(
884             category.toString(), getTickLabelFont(), getTickLabelPaint(),
885             width, this.maximumCategoryLabelLines, new G2TextMeasurer(g2)
886         );
887         return label;
888     }
889     
890     /**
891      * A utility method for determining the width of a text block.
892      *
893      * @param block the text block.
894      * @param position the position.
895      * @param g2 the graphics device.
896      *
897      * @return The width.
898      */

899     protected double calculateTextBlockWidth(TextBlock block,
900                                              CategoryLabelPosition position,
901                                              Graphics2D JavaDoc g2) {
902                                                     
903         RectangleInsets insets = getTickLabelInsets();
904         Size2D size = block.calculateDimensions(g2);
905         Rectangle2D JavaDoc box = new Rectangle2D.Double JavaDoc(
906             0.0, 0.0, size.getWidth(), size.getHeight()
907         );
908         Shape JavaDoc rotatedBox = ShapeUtilities.rotateShape(
909             box, position.getAngle(), 0.0f, 0.0f
910         );
911         double w = rotatedBox.getBounds2D().getWidth()
912                    + insets.getTop() + insets.getBottom();
913         return w;
914         
915     }
916
917     /**
918      * A utility method for determining the height of a text block.
919      *
920      * @param block the text block.
921      * @param position the label position.
922      * @param g2 the graphics device.
923      *
924      * @return The height.
925      */

926     protected double calculateTextBlockHeight(TextBlock block,
927                                               CategoryLabelPosition position,
928                                               Graphics2D JavaDoc g2) {
929                                                     
930         RectangleInsets insets = getTickLabelInsets();
931         Size2D size = block.calculateDimensions(g2);
932         Rectangle2D JavaDoc box = new Rectangle2D.Double JavaDoc(
933             0.0, 0.0, size.getWidth(), size.getHeight()
934         );
935         Shape JavaDoc rotatedBox = ShapeUtilities.rotateShape(
936             box, position.getAngle(), 0.0f, 0.0f
937         );
938         double h = rotatedBox.getBounds2D().getHeight()
939                    + insets.getTop() + insets.getBottom();
940         return h;
941         
942     }
943
944     /**
945      * Creates a clone of the axis.
946      *
947      * @return A clone.
948      *
949      * @throws CloneNotSupportedException if some component of the axis does
950      * not support cloning.
951      */

952     public Object JavaDoc clone() throws CloneNotSupportedException JavaDoc {
953     
954         Object JavaDoc clone = super.clone();
955         return clone;
956             
957     }
958     
959     /**
960      * Tests this axis for equality with an arbitrary object.
961      *
962      * @param obj the object (<code>null</code> permitted).
963      *
964      * @return A boolean.
965      */

966     public boolean equals(Object JavaDoc obj) {
967         if (obj == this) {
968             return true;
969         }
970         if (!(obj instanceof CategoryAxis)) {
971             return false;
972         }
973         if (!super.equals(obj)) {
974             return false;
975         }
976         CategoryAxis that = (CategoryAxis) obj;
977         if (that.lowerMargin != this.lowerMargin) {
978             return false;
979         }
980         if (that.upperMargin != this.upperMargin) {
981             return false;
982         }
983         if (that.categoryMargin != this.categoryMargin) {
984             return false;
985         }
986         if (that.maximumCategoryLabelWidthRatio
987                 != this.maximumCategoryLabelWidthRatio) {
988             return false;
989         }
990         if (that.categoryLabelPositionOffset
991                 != this.categoryLabelPositionOffset) {
992             return false;
993         }
994         if (!ObjectUtilities.equal(
995                 that.categoryLabelPositions, this.categoryLabelPositions
996             )) {
997             return false;
998         }
999         if (!ObjectUtilities.equal(
1000                that.categoryLabelToolTips, this.categoryLabelToolTips
1001            )) {
1002            return false;
1003        }
1004        return true;
1005    }
1006
1007    /**
1008     * Returns a hash code for this object.
1009     *
1010     * @return A hash code.
1011     */

1012    public int hashCode() {
1013        if (getLabel() != null) {
1014            return getLabel().hashCode();
1015        }
1016        else {
1017            return 0;
1018        }
1019    }
1020    
1021    /**
1022     * Provides serialization support.
1023     *
1024     * @param stream the output stream.
1025     *
1026     * @throws IOException if there is an I/O error.
1027     */

1028    private void writeObject(ObjectOutputStream JavaDoc stream) throws IOException JavaDoc {
1029        stream.defaultWriteObject();
1030    }
1031
1032    /**
1033     * Provides serialization support.
1034     *
1035     * @param stream the input stream.
1036     *
1037     * @throws IOException if there is an I/O error.
1038     * @throws ClassNotFoundException if there is a classpath problem.
1039     */

1040    private void readObject(ObjectInputStream JavaDoc stream)
1041        throws IOException JavaDoc, ClassNotFoundException JavaDoc {
1042        stream.defaultReadObject();
1043    }
1044 
1045}
1046
Popular Tags