001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors.
006 *
007 * Project Info:  http://www.jfree.org/jfreechart/index.html
008 *
009 * This library is free software; you can redistribute it and/or modify it
010 * under the terms of the GNU Lesser General Public License as published by
011 * the Free Software Foundation; either version 2.1 of the License, or
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
022 * USA.
023 *
024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025 * in the United States and other countries.]
026 *
027 * ---------------------------
028 * CategoryLabelPositions.java
029 * ---------------------------
030 * (C) Copyright 2004-2008, by Object Refinery Limited and Contributors.
031 *
032 * Original Author:  David Gilbert (for Object Refinery Limited);
033 * Contributor(s):   -;
034 *
035 * Changes
036 * -------
037 * 06-Jan-2004 : Version 1 (DG);
038 * 17-Feb-2004 : Added equals() method (DG);
039 * 05-Nov-2004 : Adjusted settings for UP_90 and DOWN_90 (DG);
040 *
041 */
042
043package org.jfree.chart.axis;
044
045import java.io.Serializable;
046
047import org.jfree.text.TextBlockAnchor;
048import org.jfree.ui.RectangleAnchor;
049import org.jfree.ui.RectangleEdge;
050import org.jfree.ui.TextAnchor;
051
052/**
053 * Records the label positions for a category axis.  Instances of this class
054 * are immutable.
055 */
056public class CategoryLabelPositions implements Serializable {
057
058    /** For serialization. */
059    private static final long serialVersionUID = -8999557901920364580L;
060
061    /** STANDARD category label positions. */
062    public static final CategoryLabelPositions
063        STANDARD = new CategoryLabelPositions(
064            new CategoryLabelPosition(
065                RectangleAnchor.BOTTOM, TextBlockAnchor.BOTTOM_CENTER
066            ), // TOP
067            new CategoryLabelPosition(
068                RectangleAnchor.TOP, TextBlockAnchor.TOP_CENTER
069            ), // BOTTOM
070            new CategoryLabelPosition(
071                RectangleAnchor.RIGHT, TextBlockAnchor.CENTER_RIGHT,
072                CategoryLabelWidthType.RANGE, 0.30f
073            ), // LEFT
074            new CategoryLabelPosition(
075                RectangleAnchor.LEFT, TextBlockAnchor.CENTER_LEFT,
076                CategoryLabelWidthType.RANGE, 0.30f
077            ) // RIGHT
078        );
079
080    /** UP_90 category label positions. */
081    public static final CategoryLabelPositions
082        UP_90 = new CategoryLabelPositions(
083            new CategoryLabelPosition(
084                RectangleAnchor.BOTTOM, TextBlockAnchor.CENTER_LEFT,
085                TextAnchor.CENTER_LEFT, -Math.PI / 2.0,
086                CategoryLabelWidthType.RANGE, 0.30f
087            ), // TOP
088            new CategoryLabelPosition(
089                RectangleAnchor.TOP, TextBlockAnchor.CENTER_RIGHT,
090                TextAnchor.CENTER_RIGHT, -Math.PI / 2.0,
091                CategoryLabelWidthType.RANGE, 0.30f
092            ), // BOTTOM
093            new CategoryLabelPosition(
094                RectangleAnchor.RIGHT, TextBlockAnchor.BOTTOM_CENTER,
095                TextAnchor.BOTTOM_CENTER, -Math.PI / 2.0,
096                CategoryLabelWidthType.CATEGORY, 0.9f
097            ), // LEFT
098            new CategoryLabelPosition(
099                RectangleAnchor.LEFT, TextBlockAnchor.TOP_CENTER,
100                TextAnchor.TOP_CENTER, -Math.PI / 2.0,
101                CategoryLabelWidthType.CATEGORY, 0.90f
102            ) // RIGHT
103        );
104
105    /** DOWN_90 category label positions. */
106    public static final CategoryLabelPositions
107        DOWN_90 = new CategoryLabelPositions(
108            new CategoryLabelPosition(
109                RectangleAnchor.BOTTOM, TextBlockAnchor.CENTER_RIGHT,
110                TextAnchor.CENTER_RIGHT, Math.PI / 2.0,
111                CategoryLabelWidthType.RANGE, 0.30f
112            ), // TOP
113            new CategoryLabelPosition(
114                RectangleAnchor.TOP, TextBlockAnchor.CENTER_LEFT,
115                TextAnchor.CENTER_LEFT, Math.PI / 2.0,
116                CategoryLabelWidthType.RANGE, 0.30f
117            ), // BOTTOM
118            new CategoryLabelPosition(
119                RectangleAnchor.RIGHT, TextBlockAnchor.TOP_CENTER,
120                TextAnchor.TOP_CENTER, Math.PI / 2.0,
121                CategoryLabelWidthType.CATEGORY, 0.90f
122            ), // LEFT
123            new CategoryLabelPosition(
124                RectangleAnchor.LEFT, TextBlockAnchor.BOTTOM_CENTER,
125                TextAnchor.BOTTOM_CENTER, Math.PI / 2.0,
126                CategoryLabelWidthType.CATEGORY, 0.90f
127            ) // RIGHT
128        );
129
130    /** UP_45 category label positions. */
131    public static final CategoryLabelPositions UP_45
132        = createUpRotationLabelPositions(Math.PI / 4.0);
133
134    /** DOWN_45 category label positions. */
135    public static final CategoryLabelPositions DOWN_45
136        = createDownRotationLabelPositions(Math.PI / 4.0);
137
138    /**
139     * Creates a new instance where the category labels angled upwards by the
140     * specified amount.
141     *
142     * @param angle  the rotation angle (should be < Math.PI / 2.0).
143     *
144     * @return A category label position specification.
145     */
146    public static CategoryLabelPositions createUpRotationLabelPositions(
147            double angle) {
148        return new CategoryLabelPositions(
149            new CategoryLabelPosition(
150                RectangleAnchor.BOTTOM, TextBlockAnchor.BOTTOM_LEFT,
151                TextAnchor.BOTTOM_LEFT, -angle,
152                CategoryLabelWidthType.RANGE, 0.50f
153            ), // TOP
154            new CategoryLabelPosition(
155                RectangleAnchor.TOP, TextBlockAnchor.TOP_RIGHT,
156                TextAnchor.TOP_RIGHT, -angle,
157                CategoryLabelWidthType.RANGE, 0.50f
158            ), // BOTTOM
159            new CategoryLabelPosition(
160                RectangleAnchor.RIGHT, TextBlockAnchor.BOTTOM_RIGHT,
161                TextAnchor.BOTTOM_RIGHT, -angle,
162                CategoryLabelWidthType.RANGE, 0.50f
163            ), // LEFT
164            new CategoryLabelPosition(
165                RectangleAnchor.LEFT, TextBlockAnchor.TOP_LEFT,
166                TextAnchor.TOP_LEFT, -angle,
167                CategoryLabelWidthType.RANGE, 0.50f
168            ) // RIGHT
169        );
170    }
171
172    /**
173     * Creates a new instance where the category labels angled downwards by the
174     * specified amount.
175     *
176     * @param angle  the rotation angle (should be < Math.PI / 2.0).
177     *
178     * @return A category label position specification.
179     */
180    public static CategoryLabelPositions createDownRotationLabelPositions(
181            double angle) {
182        return new CategoryLabelPositions(
183            new CategoryLabelPosition(
184                RectangleAnchor.BOTTOM, TextBlockAnchor.BOTTOM_RIGHT,
185                TextAnchor.BOTTOM_RIGHT, angle,
186                CategoryLabelWidthType.RANGE, 0.50f
187            ), // TOP
188            new CategoryLabelPosition(
189                RectangleAnchor.TOP, TextBlockAnchor.TOP_LEFT,
190                TextAnchor.TOP_LEFT, angle,
191                CategoryLabelWidthType.RANGE, 0.50f
192            ), // BOTTOM
193            new CategoryLabelPosition(
194                RectangleAnchor.RIGHT, TextBlockAnchor.TOP_RIGHT,
195                TextAnchor.TOP_RIGHT, angle,
196                CategoryLabelWidthType.RANGE, 0.50f
197            ), // LEFT
198            new CategoryLabelPosition(
199                RectangleAnchor.LEFT, TextBlockAnchor.BOTTOM_LEFT,
200                TextAnchor.BOTTOM_LEFT, angle,
201                CategoryLabelWidthType.RANGE, 0.50f
202            ) // RIGHT
203        );
204    }
205
206    /**
207     * The label positioning details used when an axis is at the top of a
208     * chart.
209     */
210    private CategoryLabelPosition positionForAxisAtTop;
211
212    /**
213     * The label positioning details used when an axis is at the bottom of a
214     * chart.
215     */
216    private CategoryLabelPosition positionForAxisAtBottom;
217
218    /**
219     * The label positioning details used when an axis is at the left of a
220     * chart.
221     */
222    private CategoryLabelPosition positionForAxisAtLeft;
223
224    /**
225     * The label positioning details used when an axis is at the right of a
226     * chart.
227     */
228    private CategoryLabelPosition positionForAxisAtRight;
229
230    /**
231     * Default constructor.
232     */
233    public CategoryLabelPositions() {
234        this.positionForAxisAtTop = new CategoryLabelPosition();
235        this.positionForAxisAtBottom = new CategoryLabelPosition();
236        this.positionForAxisAtLeft = new CategoryLabelPosition();
237        this.positionForAxisAtRight = new CategoryLabelPosition();
238    }
239
240    /**
241     * Creates a new position specification.
242     *
243     * @param top  the label position info used when an axis is at the top
244     *             (<code>null</code> not permitted).
245     * @param bottom  the label position info used when an axis is at the
246     *                bottom (<code>null</code> not permitted).
247     * @param left  the label position info used when an axis is at the left
248     *              (<code>null</code> not permitted).
249     * @param right  the label position info used when an axis is at the right
250     *               (<code>null</code> not permitted).
251     */
252    public CategoryLabelPositions(CategoryLabelPosition top,
253                                  CategoryLabelPosition bottom,
254                                  CategoryLabelPosition left,
255                                  CategoryLabelPosition right) {
256
257        if (top == null) {
258            throw new IllegalArgumentException("Null 'top' argument.");
259        }
260        if (bottom == null) {
261            throw new IllegalArgumentException("Null 'bottom' argument.");
262        }
263        if (left == null) {
264            throw new IllegalArgumentException("Null 'left' argument.");
265        }
266        if (right == null) {
267            throw new IllegalArgumentException("Null 'right' argument.");
268        }
269
270        this.positionForAxisAtTop = top;
271        this.positionForAxisAtBottom = bottom;
272        this.positionForAxisAtLeft = left;
273        this.positionForAxisAtRight = right;
274
275    }
276
277    /**
278     * Returns the category label position specification for an axis at the
279     * given location.
280     *
281     * @param edge  the axis location.
282     *
283     * @return The category label position specification.
284     */
285    public CategoryLabelPosition getLabelPosition(RectangleEdge edge) {
286        CategoryLabelPosition result = null;
287        if (edge == RectangleEdge.TOP) {
288            result = this.positionForAxisAtTop;
289        }
290        else if (edge == RectangleEdge.BOTTOM) {
291            result = this.positionForAxisAtBottom;
292        }
293        else if (edge == RectangleEdge.LEFT) {
294            result = this.positionForAxisAtLeft;
295        }
296        else if (edge == RectangleEdge.RIGHT) {
297            result = this.positionForAxisAtRight;
298        }
299        return result;
300    }
301
302    /**
303     * Returns a new instance based on an existing instance but with the top
304     * position changed.
305     *
306     * @param base  the base (<code>null</code> not permitted).
307     * @param top  the top position (<code>null</code> not permitted).
308     *
309     * @return A new instance (never <code>null</code>).
310     */
311    public static CategoryLabelPositions replaceTopPosition(
312            CategoryLabelPositions base, CategoryLabelPosition top) {
313
314        if (base == null) {
315            throw new IllegalArgumentException("Null 'base' argument.");
316        }
317        if (top == null) {
318            throw new IllegalArgumentException("Null 'top' argument.");
319        }
320
321        return new CategoryLabelPositions(
322            top,
323            base.getLabelPosition(RectangleEdge.BOTTOM),
324            base.getLabelPosition(RectangleEdge.LEFT),
325            base.getLabelPosition(RectangleEdge.RIGHT)
326        );
327    }
328
329    /**
330     * Returns a new instance based on an existing instance but with the bottom
331     * position changed.
332     *
333     * @param base  the base (<code>null</code> not permitted).
334     * @param bottom  the bottom position (<code>null</code> not permitted).
335     *
336     * @return A new instance (never <code>null</code>).
337     */
338    public static CategoryLabelPositions replaceBottomPosition(
339            CategoryLabelPositions base, CategoryLabelPosition bottom) {
340
341        if (base == null) {
342            throw new IllegalArgumentException("Null 'base' argument.");
343        }
344        if (bottom == null) {
345            throw new IllegalArgumentException("Null 'bottom' argument.");
346        }
347
348        return new CategoryLabelPositions(
349            base.getLabelPosition(RectangleEdge.TOP),
350            bottom,
351            base.getLabelPosition(RectangleEdge.LEFT),
352            base.getLabelPosition(RectangleEdge.RIGHT)
353        );
354    }
355
356    /**
357     * Returns a new instance based on an existing instance but with the left
358     * position changed.
359     *
360     * @param base  the base (<code>null</code> not permitted).
361     * @param left  the left position (<code>null</code> not permitted).
362     *
363     * @return A new instance (never <code>null</code>).
364     */
365    public static CategoryLabelPositions replaceLeftPosition(
366            CategoryLabelPositions base, CategoryLabelPosition left) {
367
368        if (base == null) {
369            throw new IllegalArgumentException("Null 'base' argument.");
370        }
371        if (left == null) {
372            throw new IllegalArgumentException("Null 'left' argument.");
373        }
374
375        return new CategoryLabelPositions(
376            base.getLabelPosition(RectangleEdge.TOP),
377            base.getLabelPosition(RectangleEdge.BOTTOM),
378            left,
379            base.getLabelPosition(RectangleEdge.RIGHT)
380        );
381    }
382
383    /**
384     * Returns a new instance based on an existing instance but with the right
385     * position changed.
386     *
387     * @param base  the base (<code>null</code> not permitted).
388     * @param right  the right position (<code>null</code> not permitted).
389     *
390     * @return A new instance (never <code>null</code>).
391     */
392    public static CategoryLabelPositions replaceRightPosition(
393            CategoryLabelPositions base, CategoryLabelPosition right) {
394
395        if (base == null) {
396            throw new IllegalArgumentException("Null 'base' argument.");
397        }
398        if (right == null) {
399            throw new IllegalArgumentException("Null 'right' argument.");
400        }
401
402        return new CategoryLabelPositions(
403            base.getLabelPosition(RectangleEdge.TOP),
404            base.getLabelPosition(RectangleEdge.BOTTOM),
405            base.getLabelPosition(RectangleEdge.LEFT),
406            right
407        );
408    }
409
410    /**
411     * Returns <code>true</code> if this object is equal to the specified
412     * object, and <code>false</code> otherwise.
413     *
414     * @param obj  the other object.
415     *
416     * @return A boolean.
417     */
418    public boolean equals(Object obj) {
419
420        if (this == obj) {
421            return true;
422        }
423        if (!(obj instanceof CategoryLabelPositions)) {
424            return false;
425        }
426
427        CategoryLabelPositions that = (CategoryLabelPositions) obj;
428        if (!this.positionForAxisAtTop.equals(that.positionForAxisAtTop)) {
429            return false;
430        }
431        if (!this.positionForAxisAtBottom.equals(
432                that.positionForAxisAtBottom)) {
433            return false;
434        }
435        if (!this.positionForAxisAtLeft.equals(that.positionForAxisAtLeft)) {
436            return false;
437        }
438        if (!this.positionForAxisAtRight.equals(that.positionForAxisAtRight)) {
439            return false;
440        }
441
442        return true;
443
444    }
445
446    /**
447     * Returns a hash code for this object.
448     *
449     * @return A hash code.
450     */
451    public int hashCode() {
452        int result = 19;
453        result = 37 * result + this.positionForAxisAtTop.hashCode();
454        result = 37 * result + this.positionForAxisAtBottom.hashCode();
455        result = 37 * result + this.positionForAxisAtLeft.hashCode();
456        result = 37 * result + this.positionForAxisAtRight.hashCode();
457        return result;
458    }
459}