001/* ===========================================================
002 * Orson Charts : a 3D chart library for the Java(tm) platform
003 * ===========================================================
004 * 
005 * (C)opyright 2013-2022, by David Gilbert.  All rights reserved.
006 * 
007 * https://github.com/jfree/orson-charts
008 * 
009 * This program is free software: you can redistribute it and/or modify
010 * it under the terms of the GNU General Public License as published by
011 * the Free Software Foundation, either version 3 of the License, or
012 * (at your option) any later version.
013 *
014 * This program is distributed in the hope that it will be useful,
015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
017 * GNU General Public License for more details.
018 *
019 * You should have received a copy of the GNU General Public License
020 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
021 * 
022 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
023 * Other names may be trademarks of their respective owners.]
024 * 
025 * If you do not wish to be bound by the terms of the GPL, an alternative
026 * commercial license can be purchased.  For details, please see visit the
027 * Orson Charts home page:
028 * 
029 * http://www.object-refinery.com/orsoncharts/index.html
030 * 
031 */
032
033package org.jfree.chart3d.graphics2d;
034
035import java.awt.geom.Point2D;
036import java.awt.geom.Rectangle2D;
037import java.io.Serializable;
038import org.jfree.chart3d.TitleAnchor;
039import org.jfree.chart3d.graphics3d.Offset2D;
040import org.jfree.chart3d.internal.Args;
041import org.jfree.chart3d.legend.LegendAnchor;
042
043/**
044 * Represents an anchor point for a chart title and/or legend.  The anchor
045 * point is defined relative to a reference rectangle, the dimensions of which
046 * are not known in advance (typically the reference rectangle is the bounding
047 * rectangle of a chart that is being drawn).  Some predefined anchor points 
048 * are provided in the {@link TitleAnchor} and {@link LegendAnchor} classes.  
049 * <br><br>
050 * Instances of this class are immutable.
051 * <br><br>
052 * NOTE: This class is serializable, but the serialization format is subject 
053 * to change in future releases and should not be relied upon for persisting 
054 * instances of this class.
055 */
056@SuppressWarnings("serial")
057public final class Anchor2D implements Serializable {
058
059    /** 
060     * An anchor point at the top left with zero offset from the target
061     * rectangle bounds.
062     * 
063     * @since 1.1
064     */
065    public static final Anchor2D TOP_LEFT = new Anchor2D(RefPt2D.TOP_LEFT, 
066            Offset2D.ZERO_OFFSET);
067
068    /** 
069     * An anchor point at the top center with zero offset from the target
070     * rectangle bounds.
071     * 
072     * @since 1.1
073     */
074    public static final Anchor2D TOP_CENTER = new Anchor2D(RefPt2D.TOP_CENTER, 
075            Offset2D.ZERO_OFFSET);
076
077    /** 
078     * An anchor point at the top right with zero offset from the target
079     * rectangle bounds.
080     * 
081     * @since 1.1
082     */
083    public static final Anchor2D TOP_RIGHT = new Anchor2D(RefPt2D.TOP_RIGHT, 
084            Offset2D.ZERO_OFFSET);
085
086    /** 
087     * An anchor point at the center left with zero offset from the target
088     * rectangle bounds.
089     * 
090     * @since 1.1
091     */
092    public static final Anchor2D CENTER_LEFT = new Anchor2D(RefPt2D.CENTER_LEFT,
093            Offset2D.ZERO_OFFSET);
094    
095    /** 
096     * An anchor point at the center of the target rectangle.
097     * 
098     * @since 1.1
099     */
100    public static final Anchor2D CENTER = new Anchor2D(RefPt2D.CENTER,
101            Offset2D.ZERO_OFFSET);
102
103    /** 
104     * An anchor point at the center right with zero offset from the target
105     * rectangle bounds.
106     * 
107     * @since 1.1
108     */
109    public static final Anchor2D CENTER_RIGHT 
110            = new Anchor2D(RefPt2D.CENTER_RIGHT, Offset2D.ZERO_OFFSET);
111
112    /** 
113     * An anchor point at the bottom left with zero offset from the target
114     * rectangle bounds.
115     * 
116     * @since 1.1
117     */
118    public static final Anchor2D BOTTOM_LEFT = new Anchor2D(RefPt2D.BOTTOM_LEFT, 
119            Offset2D.ZERO_OFFSET);
120
121    /** 
122     * An anchor point at the bottom center with zero offset from the target
123     * rectangle bounds.
124     * 
125     * @since 1.1
126     */
127    public static final Anchor2D BOTTOM_CENTER 
128            = new Anchor2D(RefPt2D.BOTTOM_CENTER, Offset2D.ZERO_OFFSET);
129
130    /** 
131     * An anchor point at the bottom right with zero offset from the target
132     * rectangle bounds.
133     * 
134     * @since 1.1
135     */
136    public static final Anchor2D BOTTOM_RIGHT 
137            = new Anchor2D(RefPt2D.BOTTOM_RIGHT, Offset2D.ZERO_OFFSET);
138
139    /** 
140     * The reference point relative to some bounding rectangle, normally the 
141     * bounds of the chart (never {@code null}). 
142     */
143    private final RefPt2D refPt;
144    
145    /**
146     * The offsets to apply (never {@code null}).
147     */
148    private final Offset2D offset;
149    
150    /**
151     * Creates a default instance.
152     */
153    public Anchor2D() {
154        this(RefPt2D.TOP_LEFT);
155    }
156    
157    /**
158     * Creates a new {@code Anchor2D} instance with the specified 
159     * reference point and offsets of {@code (4.0, 4.0)}.
160     * 
161     * @param refPt  the reference point ({@code null} not permitted).
162     */
163    public Anchor2D(RefPt2D refPt) {
164        this(refPt, new Offset2D(4.0, 4.0));    
165    }
166    
167    /**
168     * Creates a new anchor.
169     * 
170     * @param refPt  the reference point ({@code null} not permitted).
171     * @param offset  the offset ({@code null} not permitted).
172     */
173    public Anchor2D(RefPt2D refPt, Offset2D offset) {
174        Args.nullNotPermitted(refPt, "refPt");
175        Args.nullNotPermitted(offset, "offset");
176        this.refPt = refPt;
177        this.offset = offset;
178    }
179
180    /**
181     * Returns the reference point.
182     * 
183     * @return The reference point (never {@code null}). 
184     */
185    public RefPt2D getRefPt() {
186        return this.refPt;
187    }
188    
189    /**
190     * Returns the offsets.
191     * 
192     * @return The offsets (never {@code null}). 
193     */
194    public Offset2D getOffset() {
195        return this.offset;
196    }
197    
198    /**
199     * Returns the anchor point for the given rectangle.
200     * 
201     * @param rect  the reference rectangle ({@code null} not permitted).
202     * 
203     * @return The anchor point. 
204     */
205    public Point2D getAnchorPoint(Rectangle2D rect) {
206        Args.nullNotPermitted(rect, "rect");
207        double x = 0.0;
208        double y = 0.0;
209        if (this.refPt.isLeft()) {
210            x = rect.getX() + this.offset.getDX();
211        } else if (this.refPt.isHorizontalCenter()) {
212            x = rect.getCenterX();
213        } else if (this.refPt.isRight()) {
214            x = rect.getMaxX() - this.offset.getDX();
215        }
216        if (this.refPt.isTop()) {
217            y = rect.getMinY() + this.offset.getDY();
218        } else if (this.refPt.isVerticalCenter()) {
219            y = rect.getCenterY();
220        } else if (this.refPt.isBottom()) {
221            y = rect.getMaxY() - this.offset.getDY();
222        }
223        return new Point2D.Double(x, y);
224    }
225    
226    /**
227     * Resolves the anchor to a specific point relative to a rectangle defined
228     * by the points (startX, startY) and (endX, endY).
229     * 
230     * @param startX  the x-coordinate for the bottom left corner of the target 
231     *     rect.
232     * @param startY  the y-coordinate for the bottom left corner of the target 
233     *     rect.
234     * @param endX  the x-coordinate for the top right corner of the target 
235     *     rect.
236     * @param endY  the y-coordinate for the top right corner of the target
237     *     rect.
238     * 
239     * @return The resolved point.
240     * 
241     * @since 1.2
242     */
243    public Point2D resolveAnchorWithPercentOffset(double startX, double startY, 
244            double endX, double endY) {
245        double x = 0.0;
246        double y = 0.0;
247        if (this.refPt.isLeft()) {
248            x = startX + this.offset.getDX() * (endX - startX);
249        } else if (this.refPt.isHorizontalCenter()) {
250            x = (startX + endX) / 2.0;
251        } else if (this.refPt.isRight()) {
252            x = endX - this.offset.getDX() * (endX - startX);            
253        }
254        if (this.refPt.isTop()) {
255            y = endY - this.offset.getDY() * (endY - startY); 
256        } else if (this.refPt.isVerticalCenter()) {
257            y = (startY + endY) / 2.0;
258        } else if (this.refPt.isBottom()) {
259            y = startY + this.offset.getDY() * (endY - startY);
260        }
261        return new Point2D.Double(x, y);
262    }
263    
264    /**
265     * Tests this instance for equality with an arbitrary object.
266     * 
267     * @param obj  the object ({@code null} not permitted).
268     * 
269     * @return A boolean. 
270     */
271    @Override
272    public boolean equals(Object obj) {
273        if (obj == this) {
274            return true;
275        }
276        if (!(obj instanceof Anchor2D)) {
277            return false;
278        }
279        Anchor2D that = (Anchor2D) obj;
280        if (!this.refPt.equals(that.refPt)) {
281            return false;
282        }
283        if (!this.offset.equals(that.offset)) {
284            return false;
285        }
286        return true;
287    }
288}