    001/* ===========================================================
    002 * Orson Charts : a 3D chart library for the Java(tm) platform
    003 * ===========================================================
    004 *
    005 * (C)opyright 2013-2016, by Object Refinery Limited.  All rights reserved.
    006 *
    007 * http://www.object-refinery.com/orsoncharts/index.html
    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 com.orsoncharts.legend;
    034
    035import java.awt.Shape;
    036import java.util.ArrayList;
    037import java.awt.Font;
    038import java.awt.Graphics2D;
    039import java.awt.Insets;
    040import java.awt.BasicStroke;
    041import java.awt.Color;
    042import java.awt.geom.Dimension2D;
    043import java.awt.geom.Rectangle2D;
    044import java.util.List;
    045import java.util.Map;
    046import java.awt.FontMetrics;
    047import java.text.DecimalFormat;
    048import java.text.NumberFormat;
    049
    050import com.orsoncharts.renderer.ColorScale;
    051import com.orsoncharts.table.AbstractTableElement;
    052import com.orsoncharts.table.ElementDimension;
    053import com.orsoncharts.table.TableElement;
    054import com.orsoncharts.util.Orientation;
    055import com.orsoncharts.util.TextAnchor;
    056import com.orsoncharts.util.TextUtils;
    057import com.orsoncharts.Range;
    058import com.orsoncharts.table.TableElementOnDraw;
    059import com.orsoncharts.table.TableElementVisitor;
    060import com.orsoncharts.util.ArgChecks;
    061import com.orsoncharts.util.Fit2D;
    062
    063/**
    064 * A {@link TableElement} that displays a {@link ColorScale}.
    065 * <br><br>
    066 * NOTE: This class is serializable, but the serialization format is subject
    067 * to change in future releases and should not be relied upon for persisting
    068 * instances of this class.
    069 *
    070 * @since 1.1
    071 */
    072@SuppressWarnings("serial")
    073public class ColorScaleElement extends AbstractTableElement
    074        implements TableElement {
    075
    076    /** The color scale. */
    077    private ColorScale scale;
    078
    079    /** The orientation (horizontal or vertical). */
    080    private Orientation orientation;
    081
    082    /** The length of the bar. */
    083    private double barLength;
    084
    085    /** The width of the bar. */
    086    private double barWidth;
    087
    088    /** The gap between the color scale bar and the text labels. */
    089    private double textOffset;
    090
    091    /** The font for the text labels. */
    092    private Font font;
    093
    094    /** The text color. */
    095    private Color textColor;
    096
    097    /** The number formatter. */
    098    private NumberFormat formatter;
    099
    100    /**
    101     * Creates a new {@code ColorScaleElement} with the specified
    102     * attributes.
    103     *
    104     * @param scale  the color scale ({@code null} not permitted).
    105     * @param orientation  the orientation ({@code null} not permitted).
    106     * @param barWidth  the bar width (in Java2D units).
    107     * @param barLength  the bar length (in Java2D units).
    108     * @param font  the font ({@code null} not permitted).
    109     * @param textColor  the text color ({@code null} not permitted).
    110     *
    111     * @since 1.2
    112     */
    113    public ColorScaleElement(ColorScale scale, Orientation orientation,
    114            double barWidth, double barLength, Font font, Color textColor) {
    115        super();
    116        ArgChecks.nullNotPermitted(scale, "scale");
    117        ArgChecks.nullNotPermitted(orientation, "orientation");
    118        ArgChecks.nullNotPermitted(font, "font");
    119        this.scale = scale;
    120        this.orientation = orientation;
    121        this.barWidth = barWidth;
    122        this.barLength = barLength;
    123        this.textOffset = 2;
    124        this.font = font;
    125        this.textColor = textColor;
    126        this.formatter = new DecimalFormat("0.00");
    127    }
    128
    129    /**
    130     * Returns the color scale.
    131     *
    132     * @return The color scale (never {@code null}).
    133     */
    134    public ColorScale getColorScale() {
    135        return this.scale;
    136    }
    137
    138    /**
    139     * Returns the orientation.
    140     *
    141     * @return The orientation (never {@code null}).
    142     */
    143    public Orientation getOrientation() {
    144        return this.orientation;
    145    }
    146
    147    /**
    148     * Returns the bar width.
    149     *
    150     * @return The bar width.
    151     */
    152    public double getBarWidth() {
    153        return this.barWidth;
    154    }
    155
    156    /**
    157     * Returns the bar length.
    158     *
    159     * @return The bar length.
    160     */
    161    public double getBarLength() {
    162        return this.barLength;
    163    }
    164
    165    /**
    166     * Returns the font used to display the labels on the color scale.
    167     *
    168     * @return The font (never {@code null}).
    169     */
    170    public Font getFont() {
    171        return this.font;
    172    }
    173
    174    /**
    175     * Returns the text color.
    176     *
    177     * @return The text color (never {@code null}).
    178     */
    179    public Color getTextColor() {
    180        return this.textColor;
    181    }
    182
    183    /**
    184     * Receives a visitor.  This is part of a general mechanism to perform
    185     * operations on an arbitrary hierarchy of table elements.  You will not
    186     * normally call this method directly.
    187     *
    188     * @param visitor  the visitor ({@code null} not permitted).
    189     *
    190     * @since 1.2
    191     */
    192    @Override
    193    public void receive(TableElementVisitor visitor) {
    194        visitor.visit(this);
    195    }
    196
    197    /**
    198     * Returns the preferred size for this element.
    199     *
    200     * @param g2  the graphics target.
    201     * @param bounds  the available drawing space.
    202     *
    203     * @return The preferred size (never {@code null}).
    204     */
    205    @Override
    206    public Dimension2D preferredSize(Graphics2D g2, Rectangle2D bounds) {
    207        return preferredSize(g2, bounds, null);
    208    }
    209
    210    /**
    211     * Returns the preferred size for this element.
    212     *
    213     * @param g2  the graphics target.
    214     * @param bounds  the available drawing space.
    215     * @param constraints  layout constraints (ignored here).
    216     *
    217     * @return The preferred size (never {@code null}).
    218     */
    219    @Override
    220    public Dimension2D preferredSize(Graphics2D g2, Rectangle2D bounds,
    221            Map<String, Object> constraints) {
    222        g2.setFont(this.font);
    223        FontMetrics fm = g2.getFontMetrics();
    224        Range r = this.scale.getRange();
    225        String minStr = this.formatter.format(r.getMin());
    226        String maxStr = this.formatter.format(r.getMax());
    227        Rectangle2D minStrBounds = TextUtils.getTextBounds(minStr, fm);
    228        Rectangle2D maxStrBounds = TextUtils.getTextBounds(maxStr, fm);
    229        double maxStrWidth = Math.max(minStrBounds.getWidth(),
    230                maxStrBounds.getWidth());
    231        Insets insets = getInsets();
    232        double w, h;
    233        if (this.orientation == Orientation.HORIZONTAL) {
    234            w = Math.min(this.barLength + insets.left + insets.right,
    235                bounds.getWidth());
    236            h = Math.min(insets.top + this.barWidth + this.textOffset
    237                    + minStrBounds.getHeight() + insets.bottom,
    238                bounds.getHeight());
    239        } else {
    240            w = Math.min(insets.left + this.barWidth + this.textOffset
    241                    + maxStrWidth + insets.right, bounds.getWidth());
    242            h = Math.min(insets.top + this.barLength + this.textOffset
    243                    + minStrBounds.getHeight() + insets.bottom,
    244                bounds.getHeight());
    245
    246        }
    247        return new ElementDimension(w, h);
    248    }
    249
    250    @Override
    251    public List<Rectangle2D> layoutElements(Graphics2D g2, Rectangle2D bounds,
    252            Map<String, Object> constraints) {
    253        List<Rectangle2D> result = new ArrayList<Rectangle2D>(1);
    254        Dimension2D prefDim = preferredSize(g2, bounds);
    255        Fit2D fitter = Fit2D.getNoScalingFitter(getRefPoint());
    256        Rectangle2D dest = fitter.fit(prefDim, bounds);
    257        result.add(dest);
    258        return result;
    259    }
    260
    261    /**
    262     * Draws the element within the specified bounds.
    263     *
    264     * @param g2  the graphics target ({@code null} not permitted).
    265     * @param bounds  the bounds ({@code null} not permitted).
    266     */
    267    @Override
    268    public void draw(Graphics2D g2, Rectangle2D bounds) {
    269        draw(g2, bounds, null);
    270    }
    271
    272    /**
    273     * Draws the element within the specified bounds.
    274     *
    275     * @param g2  the graphics target ({@code null} not permitted).
    276     * @param bounds  the bounds ({@code null} not permitted).
    277     * @param onDrawHandler  receives notification before and after the element
    278     *     is drawn ({@code null} permitted);
    279     *
    280     * @since 1.3
    281     */
    282    @Override
    283    public void draw(Graphics2D g2, Rectangle2D bounds,
    284            TableElementOnDraw onDrawHandler) {
    285
    286        if (onDrawHandler != null) {
    287            onDrawHandler.beforeDraw(this, g2, bounds);
    288        }
    289
    290        Shape savedClip = g2.getClip();
    291        g2.clip(bounds);
    292        List<Rectangle2D> layoutInfo = layoutElements(g2, bounds, null);
    293        Rectangle2D dest = layoutInfo.get(0);
    294        if (getBackground() != null) {
    295            getBackground().fill(g2, dest);
    296        }
    297        g2.setFont(this.font);
    298        FontMetrics fm = g2.getFontMetrics();
    299        Range r = this.scale.getRange();
    300        String minStr = this.formatter.format(r.getMin());
    301        String maxStr = this.formatter.format(r.getMax());
    302        Rectangle2D minStrBounds = TextUtils.getTextBounds(minStr, fm);
    303        Rectangle2D maxStrBounds = TextUtils.getTextBounds(maxStr, fm);
    304        Insets insets = getInsets();
    305        if (this.orientation == Orientation.HORIZONTAL) {
    306            double x0 = dest.getX() + insets.left
    307                    + minStrBounds.getWidth() / 2.0;
    308            double x1 = dest.getMaxX() - insets.right
    309                    - maxStrBounds.getWidth() / 2.0;
    310            double y0 = dest.getY() + insets.top;
    311            double y1 = y0 + this.barWidth;
    312
    313            drawHorizontalScale(this.scale, g2, new Rectangle2D.Double(
    314                    (int) x0, (int) y0, (int) (x1 - x0), (int) this.barWidth));
    315            // fill the bar with the color scale
    316            g2.setPaint(this.textColor);
    317            TextUtils.drawAlignedString(minStr, g2, (float) x0,
    318                    (float) (y1 + this.textOffset), TextAnchor.TOP_CENTER);
    319            TextUtils.drawAlignedString(maxStr, g2, (float) x1,
    320                    (float) (y1 + this.textOffset), TextAnchor.TOP_CENTER);
    321
    322        } else { // VERTICAL
    323            double maxStrWidth = Math.max(minStrBounds.getWidth(),
    324                    maxStrBounds.getWidth());
    325            double x1 = dest.getMaxX() - insets.right - maxStrWidth
    326                    - this.textOffset;
    327            double x0 = x1 - this.barWidth;
    328            double y0 = dest.getY() + insets.top
    329                    + maxStrBounds.getHeight() / 2.0;
    330            double y1 = y0 + this.barLength;
    331
    332            drawVerticalScale(this.scale, g2, new Rectangle2D.Double(
    333                    (int) x0, (int) y0, (int) (x1 - x0), (int) this.barLength));
    334            g2.setPaint(this.textColor);
    335            TextUtils.drawAlignedString(minStr, g2,
    336                    (float) (x1 + this.textOffset), (float) y1,
    337                    TextAnchor.HALF_ASCENT_LEFT);
    338            TextUtils.drawAlignedString(maxStr, g2,
    339                    (float) (x1 + this.textOffset), (float) y0,
    340                    TextAnchor.HALF_ASCENT_LEFT);
    341        }
    342        g2.setClip(savedClip);
    343
    344        if (onDrawHandler != null) {
    345            onDrawHandler.afterDraw(this, g2, bounds);
    346        }
    347    }
    348
    349    /**
    350     * Draws the color scale horizontally within the specified bounds.
    351     *
    352     * @param colorScale  the color scale.
    353     * @param g2  the graphics target.
    354     * @param bounds  the bounds.
    355     */
    356    private void drawHorizontalScale(ColorScale colorScale, Graphics2D g2,
    357            Rectangle2D bounds) {
    358        g2.setStroke(new BasicStroke(1.0f));
    359        for (int x = (int) bounds.getX(); x < bounds.getMaxX(); x++) {
    360            double p = (x - bounds.getX()) / bounds.getWidth();
    361            double value = colorScale.getRange().value(p);
    362            g2.setColor(colorScale.valueToColor(value));
    363            g2.drawLine(x, (int) bounds.getMinY(), x, (int) bounds.getMaxY());
    364        }
    365    }
    366
    367    /**
    368     * Draws the color scale vertically within the specified bounds.
    369     *
    370     * @param colorScale  the color scale.
    371     * @param g2  the graphics target.
    372     * @param bounds  the bounds.
    373     */
    374    private void drawVerticalScale(ColorScale colorScale, Graphics2D g2,
    375            Rectangle2D bounds) {
    376        g2.setStroke(new BasicStroke(1.0f));
    377        for (int y = (int) bounds.getY(); y < bounds.getMaxY(); y++) {
    378            double p = (y - bounds.getY()) / bounds.getHeight();
    379            double value = colorScale.getRange().value(1 - p);
    380            g2.setColor(this.scale.valueToColor(value));
    381            g2.drawLine((int) bounds.getX(), y, (int) bounds.getMaxX(), y);
    382        }
    383    }
    384
    385    @Override
    386    public boolean equals(Object obj) {
    387        if (obj == this) {
    388            return true;
    389        }
    390        if (!(obj instanceof ColorScaleElement)) {
    391            return false;
    392        }
    393        ColorScaleElement that = (ColorScaleElement) obj;
    394        if (!this.scale.equals(that.scale)) {
    395            return false;
    396        }
    397        if (!this.orientation.equals(that.orientation)) {
    398            return false;
    399        }
    400        if (this.barLength != that.barLength) {
    401            return false;
    402        }
    403        if (this.barWidth != that.barWidth) {
    404            return false;
    405        }
    406        if (!this.font.equals(that.font)) {
    407            return false;
    408        }
    409        return super.equals(obj);
    410    }
    411}