    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 org.jfree.chart3d.internal.Args;
    036import java.awt.geom.Dimension2D;
    037import java.awt.geom.Point2D;
    038import java.awt.geom.Rectangle2D;
    039import java.io.Serializable;
    040
    041/**
    042 * A specification for the alignment and fitting of one rectangle (the source
    043 * rectangle) with reference to another (the target rectangle).  Instances of
    044 * this class are immutable.
    045 * <br><br>
    046 * One application for this is to specify how the background image for a chart
    047 * should be aligned and scaled.
    048 * <br><br>
    049 * NOTE: This class is serializable, but the serialization format is subject
    050 * to change in future releases and should not be relied upon for persisting
    051 * instances of this class.
    052 */
    053@SuppressWarnings("serial")
    054public class Fit2D implements Serializable {
    055
    056    /**
    057     * Aligns a source rectangle to the center of a target rectangle, without
    058     * resizing it.
    059     *
    060     * @since 1.1
    061     */
    062    public static final Fit2D CENTER_NO_SCALING = new Fit2D(Anchor2D.CENTER,
    063            Scale2D.NONE);
    064
    065    /**
    066     * Fits a source rectangle to the top left of a target rectangle, without
    067     * resizing it.
    068     *
    069     * @since 1.1
    070     */
    071    public static final Fit2D TOP_LEFT_NO_SCALING
    072            = new Fit2D(Anchor2D.TOP_LEFT, Scale2D.NONE);
    073
    074    /**
    075     * Fits a source rectangle to the top center of a target rectangle, without
    076     * resizing it.
    077     *
    078     * @since 1.1
    079     */
    080    public static final Fit2D TOP_CENTER_NO_SCALING
    081            = new Fit2D(Anchor2D.TOP_CENTER, Scale2D.NONE);
    082
    083    /**
    084     * Fits a source rectangle to the top right of a target rectangle, without
    085     * resizing it.
    086     *
    087     * @since 1.1
    088     */
    089    public static final Fit2D TOP_RIGHT_NO_SCALING
    090            = new Fit2D(Anchor2D.TOP_RIGHT, Scale2D.NONE);
    091
    092    /**
    093     * Fits a source rectangle to the center left of a target rectangle,
    094     * without resizing it.
    095     *
    096     * @since 1.1
    097     */
    098    public static final Fit2D CENTER_LEFT_NO_SCALING
    099            = new Fit2D(Anchor2D.CENTER_LEFT, Scale2D.NONE);
    100
    101    /**
    102     * Fits a source rectangle to the center right of a target rectangle,
    103     * without resizing it.
    104     *
    105     * @since 1.1
    106     */
    107    public static final Fit2D CENTER_RIGHT_NO_SCALING
    108            = new Fit2D(Anchor2D.CENTER_RIGHT, Scale2D.NONE);
    109
    110    /**
    111     * Fits a source rectangle to the bottom left of a target rectangle,
    112     * without resizing it.
    113     *
    114     * @since 1.1
    115     */
    116    public static final Fit2D BOTTOM_LEFT_NO_SCALING
    117            = new Fit2D(Anchor2D.BOTTOM_LEFT, Scale2D.NONE);
    118
    119    /**
    120     * Fits a source rectangle to the bottom center of a target rectangle,
    121     * without resizing it.
    122     *
    123     * @since 1.1
    124     */
    125    public static final Fit2D BOTTOM_CENTER_NO_SCALING
    126            = new Fit2D(Anchor2D.BOTTOM_CENTER, Scale2D.NONE);
    127
    128    /**
    129     * Fits a source rectangle to the bottom right of a target rectangle,
    130     * without resizing it.
    131     *
    132     * @since 1.1
    133     */
    134    public static final Fit2D BOTTOM_RIGHT_NO_SCALING
    135            = new Fit2D(Anchor2D.BOTTOM_RIGHT, Scale2D.NONE);
    136
    137    /**
    138     * Returns a fitter for the specified reference point.
    139     *
    140     * @param refPt  the reference point ({@code null} not permitted).
    141     *
    142     * @return A fitter.
    143     *
    144     * @since 1.1
    145     */
    146    public static Fit2D getNoScalingFitter(RefPt2D refPt) {
    147        switch (refPt) {
    148            case TOP_LEFT : return Fit2D.TOP_LEFT_NO_SCALING;
    149            case TOP_CENTER : return Fit2D.TOP_CENTER_NO_SCALING;
    150            case TOP_RIGHT : return Fit2D.TOP_RIGHT_NO_SCALING;
    151            case CENTER_LEFT : return Fit2D.CENTER_LEFT_NO_SCALING;
    152            case CENTER : return Fit2D.CENTER_NO_SCALING;
    153            case CENTER_RIGHT : return Fit2D.CENTER_RIGHT_NO_SCALING;
    154            case BOTTOM_LEFT : return Fit2D.BOTTOM_LEFT_NO_SCALING;
    155            case BOTTOM_CENTER : return Fit2D.BOTTOM_CENTER_NO_SCALING;
    156            case BOTTOM_RIGHT : return Fit2D.BOTTOM_RIGHT_NO_SCALING;
    157        }
    158        throw new IllegalStateException("RefPt2D not recognised : " + refPt);
    159    }
    160
    161    /**
    162     * Scale the source rectangle to fit the target rectangle.
    163     *
    164     * @since 1.1
    165     */
    166    public static final Fit2D SCALE_TO_FIT_TARGET
    167            = new Fit2D(Anchor2D.CENTER, Scale2D.SCALE_BOTH);
    168
    169    /** The anchor point for alignment. */
    170    private final Anchor2D anchor;
    171
    172    /** The scaling to apply. */
    173    private final Scale2D scale;
    174
    175    /**
    176     * Creates a new instance.
    177     *
    178     * @param anchor  the anchor point ({@code null} not permitted).
    179     * @param scale  the scaling ({@code null} not permitted).
    180     */
    181    public Fit2D(Anchor2D anchor, Scale2D scale) {
    182        Args.nullNotPermitted(anchor, "anchor");
    183        Args.nullNotPermitted(scale, "scale");
    184        this.anchor = anchor;
    185        this.scale = scale;
    186    }
    187
    188    /**
    189     * Returns the anchor.
    190     *
    191     * @return The anchor (never {@code null}).
    192     *
    193     * @since 1.1
    194     */
    195    public Anchor2D getAnchor() {
    196        return this.anchor;
    197    }
    198
    199    /**
    200     * Returns the scaling.
    201     *
    202     * @return The scaling (never {@code null}).
    203     *
    204     * @since 1.1
    205     */
    206    public Scale2D getScale() {
    207        return this.scale;
    208    }
    209
    210    /**
    211     * Fits a rectangle of the specified dimension to the target rectangle,
    212     * aligning and scaling according to the attributes of this instance.
    213     *
    214     * @param srcDim  the dimensions of the source rectangle ({@code null}
    215     *     not permitted).
    216     * @param target  the target rectangle ({@code null} not permitted).
    217     *
    218     * @return The bounds of the fitted rectangle (never {@code null}).
    219     */
    220    public Rectangle2D fit(Dimension2D srcDim, Rectangle2D target) {
    221        Rectangle2D result = new Rectangle2D.Double();
    222        if (this.scale == Scale2D.SCALE_BOTH) {
    223            result.setFrame(target);
    224            return result;
    225        }
    226        double width = srcDim.getWidth();
    227        if (this.scale == Scale2D.SCALE_HORIZONTAL) {
    228            width = target.getWidth();
    229            if (!this.anchor.getRefPt().isHorizontalCenter()) {
    230                width -= 2 * this.anchor.getOffset().getDX();
    231            }
    232        }
    233        double height = srcDim.getHeight();
    234        if (this.scale == Scale2D.SCALE_VERTICAL) {
    235            height = target.getHeight();
    236            if (!this.anchor.getRefPt().isVerticalCenter()) {
    237                height -= 2 * this.anchor.getOffset().getDY();
    238            }
    239        }
    240        Point2D pt = this.anchor.getAnchorPoint(target);
    241        double x = Double.NaN;
    242        if (this.anchor.getRefPt().isLeft()) {
    243            x = pt.getX();
    244        } else if (this.anchor.getRefPt().isHorizontalCenter()) {
    245            x = target.getCenterX() - width / 2;
    246        } else if (this.anchor.getRefPt().isRight()) {
    247            x = pt.getX() - width;
    248        }
    249        double y = Double.NaN;
    250        if (this.anchor.getRefPt().isTop()) {
    251            y = pt.getY();
    252        } else if (this.anchor.getRefPt().isVerticalCenter()) {
    253            y = target.getCenterY() - height / 2;
    254        } else if (this.anchor.getRefPt().isBottom()) {
    255            y = pt.getY() - height;
    256        }
    257        result.setRect(x, y, width, height);
    258        return result;
    259    }
    260
    261    /**
    262     * Tests this instance for equality with an arbitrary object.
    263     *
    264     * @param obj  the object ({@code null} permitted).
    265     *
    266     * @return A boolean.
    267     */
    268    @Override
    269    public boolean equals(Object obj) {
    270        if (obj == this) {
    271            return true;
    272        }
    273        if (!(obj instanceof Fit2D)) {
    274            return false;
    275        }
    276        Fit2D that = (Fit2D) obj;
    277        if (!this.anchor.equals(that.anchor)) {
    278            return false;
    279        }
    280        if (!this.scale.equals(that.scale)) {
    281            return false;
    282        }
    283        return true;
    284    }
    285}