001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-present, by David Gilbert 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025 * Other names may be trademarks of their respective owners.]
026 *
027 * ---------------------------
028 * CategoryLabelPositions.java
029 * ---------------------------
030 * (C) Copyright 2004-present, by David Gilbert and Contributors.
031 *
032 * Original Author:  David Gilbert;
033 * Contributor(s):   Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier);
034 *
035 */
036
037package org.jfree.chart.axis;
038
039import java.io.Serializable;
040import java.util.Objects;
041import org.jfree.chart.text.TextBlockAnchor;
042import org.jfree.chart.ui.RectangleAnchor;
043import org.jfree.chart.ui.RectangleEdge;
044import org.jfree.chart.ui.TextAnchor;
045import org.jfree.chart.util.Args;
046
047
048/**
049 * Records the label positions for a category axis.  Instances of this class
050 * are immutable.
051 */
052public class CategoryLabelPositions implements Serializable {
053
054    /** For serialization. */
055    private static final long serialVersionUID = -8999557901920364580L;
056
057    /** STANDARD category label positions. */
058    public static final CategoryLabelPositions
059        STANDARD = new CategoryLabelPositions(
060            new CategoryLabelPosition(
061                RectangleAnchor.BOTTOM, TextBlockAnchor.BOTTOM_CENTER), // TOP
062            new CategoryLabelPosition(
063                RectangleAnchor.TOP, TextBlockAnchor.TOP_CENTER), // BOTTOM
064            new CategoryLabelPosition(
065                RectangleAnchor.RIGHT, TextBlockAnchor.CENTER_RIGHT,
066                CategoryLabelWidthType.RANGE, 0.30f), // LEFT
067            new CategoryLabelPosition(
068                RectangleAnchor.LEFT, TextBlockAnchor.CENTER_LEFT,
069                CategoryLabelWidthType.RANGE, 0.30f) // RIGHT
070        );
071
072    /** UP_90 category label positions. */
073    public static final CategoryLabelPositions
074        UP_90 = new CategoryLabelPositions(
075            new CategoryLabelPosition(
076                RectangleAnchor.BOTTOM, TextBlockAnchor.CENTER_LEFT,
077                TextAnchor.CENTER_LEFT, -Math.PI / 2.0,
078                CategoryLabelWidthType.RANGE, 0.30f), // TOP
079            new CategoryLabelPosition(
080                RectangleAnchor.TOP, TextBlockAnchor.CENTER_RIGHT,
081                TextAnchor.CENTER_RIGHT, -Math.PI / 2.0,
082                CategoryLabelWidthType.RANGE, 0.30f), // BOTTOM
083            new CategoryLabelPosition(
084                RectangleAnchor.RIGHT, TextBlockAnchor.BOTTOM_CENTER,
085                TextAnchor.BOTTOM_CENTER, -Math.PI / 2.0,
086                CategoryLabelWidthType.CATEGORY, 0.9f), // LEFT
087            new CategoryLabelPosition(
088                RectangleAnchor.LEFT, TextBlockAnchor.TOP_CENTER,
089                TextAnchor.TOP_CENTER, -Math.PI / 2.0,
090                CategoryLabelWidthType.CATEGORY, 0.90f) // RIGHT
091        );
092
093    /** DOWN_90 category label positions. */
094    public static final CategoryLabelPositions
095        DOWN_90 = new CategoryLabelPositions(
096            new CategoryLabelPosition(
097                RectangleAnchor.BOTTOM, TextBlockAnchor.CENTER_RIGHT,
098                TextAnchor.CENTER_RIGHT, Math.PI / 2.0,
099                CategoryLabelWidthType.RANGE, 0.30f), // TOP
100            new CategoryLabelPosition(
101                RectangleAnchor.TOP, TextBlockAnchor.CENTER_LEFT,
102                TextAnchor.CENTER_LEFT, Math.PI / 2.0,
103                CategoryLabelWidthType.RANGE, 0.30f), // BOTTOM
104            new CategoryLabelPosition(
105                RectangleAnchor.RIGHT, TextBlockAnchor.TOP_CENTER,
106                TextAnchor.TOP_CENTER, Math.PI / 2.0,
107                CategoryLabelWidthType.CATEGORY, 0.90f), // LEFT
108            new CategoryLabelPosition(
109                RectangleAnchor.LEFT, TextBlockAnchor.BOTTOM_CENTER,
110                TextAnchor.BOTTOM_CENTER, Math.PI / 2.0,
111                CategoryLabelWidthType.CATEGORY, 0.90f) // RIGHT
112        );
113
114    /** UP_45 category label positions. */
115    public static final CategoryLabelPositions UP_45
116        = createUpRotationLabelPositions(Math.PI / 4.0);
117
118    /** DOWN_45 category label positions. */
119    public static final CategoryLabelPositions DOWN_45
120        = createDownRotationLabelPositions(Math.PI / 4.0);
121
122    /**
123     * Creates a new instance where the category labels angled upwards by the
124     * specified amount.
125     *
126     * @param angle  the rotation angle (should be < Math.PI / 2.0).
127     *
128     * @return A category label position specification.
129     */
130    public static CategoryLabelPositions createUpRotationLabelPositions(
131            double angle) {
132        return new CategoryLabelPositions(
133            new CategoryLabelPosition(
134                RectangleAnchor.BOTTOM, TextBlockAnchor.BOTTOM_LEFT,
135                TextAnchor.BOTTOM_LEFT, -angle,
136                CategoryLabelWidthType.RANGE, 0.50f), // TOP
137            new CategoryLabelPosition(
138                RectangleAnchor.TOP, TextBlockAnchor.TOP_RIGHT,
139                TextAnchor.TOP_RIGHT, -angle,
140                CategoryLabelWidthType.RANGE, 0.50f), // BOTTOM
141            new CategoryLabelPosition(
142                RectangleAnchor.RIGHT, TextBlockAnchor.BOTTOM_RIGHT,
143                TextAnchor.BOTTOM_RIGHT, -angle,
144                CategoryLabelWidthType.RANGE, 0.50f), // LEFT
145            new CategoryLabelPosition(
146                RectangleAnchor.LEFT, TextBlockAnchor.TOP_LEFT,
147                TextAnchor.TOP_LEFT, -angle,
148                CategoryLabelWidthType.RANGE, 0.50f) // RIGHT
149        );
150    }
151
152    /**
153     * Creates a new instance where the category labels angled downwards by the
154     * specified amount.
155     *
156     * @param angle  the rotation angle (should be < Math.PI / 2.0).
157     *
158     * @return A category label position specification.
159     */
160    public static CategoryLabelPositions createDownRotationLabelPositions(
161            double angle) {
162        return new CategoryLabelPositions(
163            new CategoryLabelPosition(
164                RectangleAnchor.BOTTOM, TextBlockAnchor.BOTTOM_RIGHT,
165                TextAnchor.BOTTOM_RIGHT, angle,
166                CategoryLabelWidthType.RANGE, 0.50f), // TOP
167            new CategoryLabelPosition(
168                RectangleAnchor.TOP, TextBlockAnchor.TOP_LEFT,
169                TextAnchor.TOP_LEFT, angle,
170                CategoryLabelWidthType.RANGE, 0.50f), // BOTTOM
171            new CategoryLabelPosition(
172                RectangleAnchor.RIGHT, TextBlockAnchor.TOP_RIGHT,
173                TextAnchor.TOP_RIGHT, angle,
174                CategoryLabelWidthType.RANGE, 0.50f), // LEFT
175            new CategoryLabelPosition(
176                RectangleAnchor.LEFT, TextBlockAnchor.BOTTOM_LEFT,
177                TextAnchor.BOTTOM_LEFT, angle,
178                CategoryLabelWidthType.RANGE, 0.50f) // RIGHT
179        );
180    }
181
182    /**
183     * The label positioning details used when an axis is at the top of a
184     * chart.
185     */
186    private final CategoryLabelPosition positionForAxisAtTop;
187
188    /**
189     * The label positioning details used when an axis is at the bottom of a
190     * chart.
191     */
192    private final CategoryLabelPosition positionForAxisAtBottom;
193
194    /**
195     * The label positioning details used when an axis is at the left of a
196     * chart.
197     */
198    private final CategoryLabelPosition positionForAxisAtLeft;
199
200    /**
201     * The label positioning details used when an axis is at the right of a
202     * chart.
203     */
204    private final CategoryLabelPosition positionForAxisAtRight;
205
206    /**
207     * Default constructor.
208     */
209    public CategoryLabelPositions() {
210        this.positionForAxisAtTop = new CategoryLabelPosition();
211        this.positionForAxisAtBottom = new CategoryLabelPosition();
212        this.positionForAxisAtLeft = new CategoryLabelPosition();
213        this.positionForAxisAtRight = new CategoryLabelPosition();
214    }
215
216    /**
217     * Creates a new position specification.
218     *
219     * @param top  the label position info used when an axis is at the top
220     *             ({@code null} not permitted).
221     * @param bottom  the label position info used when an axis is at the
222     *                bottom ({@code null} not permitted).
223     * @param left  the label position info used when an axis is at the left
224     *              ({@code null} not permitted).
225     * @param right  the label position info used when an axis is at the right
226     *               ({@code null} not permitted).
227     */
228    public CategoryLabelPositions(CategoryLabelPosition top,
229            CategoryLabelPosition bottom, CategoryLabelPosition left,
230            CategoryLabelPosition right) {
231
232        Args.nullNotPermitted(top, "top");
233        Args.nullNotPermitted(bottom, "bottom");
234        Args.nullNotPermitted(left, "left");
235        Args.nullNotPermitted(right, "right");
236
237        this.positionForAxisAtTop = top;
238        this.positionForAxisAtBottom = bottom;
239        this.positionForAxisAtLeft = left;
240        this.positionForAxisAtRight = right;
241    }
242
243    /**
244     * Returns the category label position specification for an axis at the
245     * given location.
246     *
247     * @param edge  the axis location.
248     *
249     * @return The category label position specification.
250     */
251    public CategoryLabelPosition getLabelPosition(RectangleEdge edge) {
252        CategoryLabelPosition result = null;
253        if (edge == RectangleEdge.TOP) {
254            result = this.positionForAxisAtTop;
255        }
256        else if (edge == RectangleEdge.BOTTOM) {
257            result = this.positionForAxisAtBottom;
258        }
259        else if (edge == RectangleEdge.LEFT) {
260            result = this.positionForAxisAtLeft;
261        }
262        else if (edge == RectangleEdge.RIGHT) {
263            result = this.positionForAxisAtRight;
264        }
265        return result;
266    }
267
268    /**
269     * Returns a new instance based on an existing instance but with the top
270     * position changed.
271     *
272     * @param base  the base ({@code null} not permitted).
273     * @param top  the top position ({@code null} not permitted).
274     *
275     * @return A new instance (never {@code null}).
276     */
277    public static CategoryLabelPositions replaceTopPosition(
278            CategoryLabelPositions base, CategoryLabelPosition top) {
279
280        Args.nullNotPermitted(base, "base");
281        Args.nullNotPermitted(top, "top");
282
283        return new CategoryLabelPositions(top,
284            base.getLabelPosition(RectangleEdge.BOTTOM),
285            base.getLabelPosition(RectangleEdge.LEFT),
286            base.getLabelPosition(RectangleEdge.RIGHT));
287    }
288
289    /**
290     * Returns a new instance based on an existing instance but with the bottom
291     * position changed.
292     *
293     * @param base  the base ({@code null} not permitted).
294     * @param bottom  the bottom position ({@code null} not permitted).
295     *
296     * @return A new instance (never {@code null}).
297     */
298    public static CategoryLabelPositions replaceBottomPosition(
299            CategoryLabelPositions base, CategoryLabelPosition bottom) {
300
301        Args.nullNotPermitted(base, "base");
302        Args.nullNotPermitted(bottom, "bottom");
303
304        return new CategoryLabelPositions(
305            base.getLabelPosition(RectangleEdge.TOP),
306            bottom,
307            base.getLabelPosition(RectangleEdge.LEFT),
308            base.getLabelPosition(RectangleEdge.RIGHT));
309    }
310
311    /**
312     * Returns a new instance based on an existing instance but with the left
313     * position changed.
314     *
315     * @param base  the base ({@code null} not permitted).
316     * @param left  the left position ({@code null} not permitted).
317     *
318     * @return A new instance (never {@code null}).
319     */
320    public static CategoryLabelPositions replaceLeftPosition(
321            CategoryLabelPositions base, CategoryLabelPosition left) {
322
323        Args.nullNotPermitted(base, "base");
324        Args.nullNotPermitted(left, "left");
325
326        return new CategoryLabelPositions(
327            base.getLabelPosition(RectangleEdge.TOP),
328            base.getLabelPosition(RectangleEdge.BOTTOM),
329            left,
330            base.getLabelPosition(RectangleEdge.RIGHT));
331    }
332
333    /**
334     * Returns a new instance based on an existing instance but with the right
335     * position changed.
336     *
337     * @param base  the base ({@code null} not permitted).
338     * @param right  the right position ({@code null} not permitted).
339     *
340     * @return A new instance (never {@code null}).
341     */
342    public static CategoryLabelPositions replaceRightPosition(
343            CategoryLabelPositions base, CategoryLabelPosition right) {
344
345        Args.nullNotPermitted(base, "base");
346        Args.nullNotPermitted(right, "right");
347        return new CategoryLabelPositions(
348            base.getLabelPosition(RectangleEdge.TOP),
349            base.getLabelPosition(RectangleEdge.BOTTOM),
350            base.getLabelPosition(RectangleEdge.LEFT),
351            right);
352    }
353
354    /**
355     * Returns {@code true} if this object is equal to the specified
356     * object, and {@code false} otherwise.
357     *
358     * @param obj  the other object.
359     *
360     * @return A boolean.
361     */
362    @Override
363    public boolean equals(Object obj) {
364        if (this == obj) {
365            return true;
366        }
367        if (!(obj instanceof CategoryLabelPositions)) {
368            return false;
369        }
370
371        CategoryLabelPositions that = (CategoryLabelPositions) obj;
372        if (!Objects.equals(this.positionForAxisAtTop, 
373                            that.positionForAxisAtTop)) {
374            return false;
375        }
376        if (!Objects.equals(this.positionForAxisAtBottom, 
377                            that.positionForAxisAtBottom)) {
378            return false;
379        }
380        if (!Objects.equals(this.positionForAxisAtLeft,
381                            that.positionForAxisAtLeft)) {
382            return false;
383        }
384        if (!Objects.equals(this.positionForAxisAtRight,
385                            that.positionForAxisAtRight)) {
386            return false;
387        }
388        return true;
389    }
390
391    /**
392     * Returns a hash code for this object.
393     *
394     * @return A hash code.
395     */
396    @Override
397    public int hashCode() {
398        int result = 19;
399        result = 19 * result + Objects.hashCode(this.positionForAxisAtTop);
400        result = 19 * result + Objects.hashCode(this.positionForAxisAtBottom);
401        result = 19 * result + Objects.hashCode(this.positionForAxisAtLeft);
402        result = 19 * result + Objects.hashCode(this.positionForAxisAtRight);
403        return result;
404    }
405}