    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.Color;
    036import java.awt.Font;
    037import java.awt.Shape;
    038import java.util.List;
    039import java.io.Serializable;
    040
    041import com.orsoncharts.Chart3D;
    042import com.orsoncharts.interaction.InteractiveElementType;
    043import com.orsoncharts.plot.Plot3D;
    044import com.orsoncharts.plot.CategoryPlot3D;
    045import com.orsoncharts.plot.PiePlot3D;
    046import com.orsoncharts.plot.XYZPlot;
    047import com.orsoncharts.style.ChartStyle;
    048import com.orsoncharts.table.ContainerElement;
    049import com.orsoncharts.table.FlowElement;
    050import com.orsoncharts.table.GridElement;
    051import com.orsoncharts.table.HAlign;
    052import com.orsoncharts.table.ShapeElement;
    053import com.orsoncharts.table.TableElement;
    054import com.orsoncharts.table.TextElement;
    055import com.orsoncharts.table.VAlign;
    056import com.orsoncharts.table.VerticalFlowElement;
    057import com.orsoncharts.util.Anchor2D;
    058import com.orsoncharts.util.ArgChecks;
    059import com.orsoncharts.util.ObjectUtils;
    060import com.orsoncharts.util.Orientation;
    061
    062/**
    063 * The standard legend builder, which creates a simple legend
    064 * with a flow layout and optional header and footer text.
    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@SuppressWarnings("serial")
    071public final class StandardLegendBuilder implements LegendBuilder,
    072        Serializable {
    073
    074    /** An optional header/title for the legend (can be {@code null}). */
    075    private String header;
    076
    077    /** The header alignment (never {@code null}). */
    078    private HAlign headerAlignment;
    079
    080    /** An optional footer for the legend (can be {@code null}). */
    081    private String footer;
    082
    083    /** The footer alignment (never {@code null}). */
    084    private HAlign footerAlignment;
    085
    086    /**
    087     * The row alignment (if {@code null}, the row alignment will be
    088     * derived from the anchor point).
    089     */
    090    private HAlign rowAlignment;
    091
    092    /**
    093     * The column alignment (if {@code null}, the column alignment will
    094     * be derived from the anchor point).
    095     */
    096    private VAlign columnAlignment;
    097
    098    /**
    099     * Creates a builder for a simple legend with no header and no footer.
    100     */
    101    public StandardLegendBuilder() {
    102        this(null, null);
    103    }
    104
    105    /**
    106     * Creates a builder for a simple legend with the specified header and/or
    107     * footer.
    108     *
    109     * @param header  the legend header ({@code null} permitted).
    110     * @param footer  the legend footer ({@code null} permitted).
    111     */
    112    public StandardLegendBuilder(String header, String footer) {
    113        this.header = header;
    114        this.headerAlignment = HAlign.LEFT;
    115        this.footer = footer;
    116        this.footerAlignment = HAlign.RIGHT;
    117        this.rowAlignment = null;
    118        this.columnAlignment = null;
    119    }
    120
    121    /**
    122     * Returns the header text.
    123     *
    124     * @return The header text (possibly {@code null}).
    125     */
    126    public String getHeader() {
    127        return this.header;
    128    }
    129
    130    /**
    131     * Sets the header text.
    132     *
    133     * @param header  the header ({@code null} permitted).
    134     */
    135    public void setHeader(String header) {
    136        this.header = header;
    137    }
    138
    139    /**
    140     * Returns the header alignment.
    141     *
    142     * @return The header alignment (never {@code null}).
    143     */
    144    public HAlign getHeaderAlignment() {
    145        return this.headerAlignment;
    146    }
    147
    148    /**
    149     * Sets the header alignment.
    150     *
    151     * @param align  the header alignment ({@code null} not permitted).
    152     */
    153    public void setHeaderAlignment(HAlign align) {
    154        ArgChecks.nullNotPermitted(align, "align");
    155        this.headerAlignment = align;
    156    }
    157
    158    /**
    159     * Returns the footer text.
    160     *
    161     * @return The footer text (possibly {@code null}).
    162     */
    163    public String getFooter() {
    164        return this.footer;
    165    }
    166
    167    /**
    168     * Sets the footer text.
    169     *
    170     * @param footer  the footer ({@code null} permitted).
    171     */
    172    public void setFooter(String footer) {
    173        this.footer = footer;
    174    }
    175
    176    /**
    177     * Returns the footer alignment.
    178     *
    179     * @return The footer alignment (never {@code null}).
    180     */
    181    public HAlign getFooterAlignment() {
    182        return this.footerAlignment;
    183    }
    184
    185    /**
    186     * Sets the footer alignment.
    187     *
    188     * @param align  the footer alignment ({@code null} not permitted).
    189     */
    190    public void setFooterAlignment(HAlign align) {
    191        ArgChecks.nullNotPermitted(align, "align");
    192        this.footerAlignment = align;
    193    }
    194
    195    /**
    196     * Returns the row alignment.  The default value is {@code null}
    197     * which means that the row alignment is derived from the anchor point
    198     * (left aligned for anchors on the left side, center alignment for
    199     * anchors in the middle, and right aligned for anchors on the right side).
    200     *
    201     * @return The row alignment (possibly {@code null}).
    202     *
    203     * @since 1.1
    204     */
    205    public HAlign getRowAlignment() {
    206        return this.rowAlignment;
    207    }
    208
    209    /**
    210     * Sets the row alignment (to override the default alignment that is
    211     * derived from the legend anchor point).  In most circumstances you
    212     * should be able to rely on the default behaviour, so leave this
    213     * attribute set to {@code null}.
    214     *
    215     * @param alignment  the row alignment ({@code null} permitted).
    216     *
    217     * @since 1.1
    218     */
    219    public void setRowAlignment(HAlign alignment) {
    220        this.rowAlignment = alignment;
    221    }
    222
    223    /**
    224     * Returns the column alignment.  The default value is {@code null}
    225     * which means that the column alignment is derived from the anchor point
    226     * (top aligned for anchors at the top, center alignment for
    227     * anchors in the middle, and bottom aligned for anchors at the bottom).
    228     *
    229     * @return The column alignment (possibly {@code null}).
    230     *
    231     * @since 1.1
    232     */
    233    public VAlign getColumnAlignment() {
    234        return this.columnAlignment;
    235    }
    236
    237    /**
    238     * Sets the column alignment (to override the default alignment that is
    239     * derived from the legend anchor point).  In most circumstances you
    240     * should be able to rely on the default behaviour, so leave this
    241     * attribute set to {@code null}.
    242     *
    243     * @param alignment  the column alignment ({@code null} permitted).
    244     *
    245     * @since 1.1
    246     */
    247    public void setColumnAlignment(VAlign alignment) {
    248        this.columnAlignment = alignment;
    249    }
    250
    251    /**
    252     * Creates and returns a legend (instance of {@link TableElement}) that
    253     * provides a visual key for the data series in the specified plot.  The
    254     * plot can be any of the built-in plot types: {@link PiePlot3D},
    255     * {@link CategoryPlot3D} or {@link XYZPlot}.
    256     * <br><br>
    257     * Certain subelements will have the following properties set so that
    258     * downstream code is able to identify which elements relate to particular
    259     * data series: CLASS : 'LegendItem', SERIES_KEY : the series key.
    260     *
    261     * @param plot  the plot ({@code null} not permitted).
    262     * @param anchor  the anchor ({@code null} not permitted).
    263     * @param orientation  the orientation ({@code null} not permitted).
    264     * @param style  the chart style ({@code null} not permitted).
    265     *
    266     * @return The legend.
    267     *
    268     * @since 1.2
    269     */
    270    @Override
    271    public TableElement createLegend(Plot3D plot, Anchor2D anchor,
    272            Orientation orientation, ChartStyle style) {
    273
    274        TableElement legend = createSimpleLegend(plot.getLegendInfo(), anchor,
    275                orientation, style);
    276        if (this.header != null || this.footer != null) {
    277            GridElement<String, String> compositeLegend
    278                    = new GridElement<String, String>();
    279            compositeLegend.setBackground(null);
    280            if (header != null) {
    281                TextElement he = new TextElement(this.header,
    282                        style.getLegendHeaderFont());
    283                he.setHorizontalAligment(this.headerAlignment);
    284                he.setBackgroundColor(style.getLegendHeaderBackgroundColor());
    285                compositeLegend.setElement(he, "R0", "C1");
    286            }
    287            compositeLegend.setElement(legend, "R1", "C1");
    288            if (this.footer != null) {
    289                TextElement fe = new TextElement(this.footer,
    290                        style.getLegendFooterFont());
    291                fe.setHorizontalAligment(this.footerAlignment);
    292                fe.setBackgroundColor(style.getLegendFooterBackgroundColor());
    293                compositeLegend.setElement(fe, "R2", "C1");
    294            }
    295            return compositeLegend;
    296        } else {
    297            return legend;
    298        }
    299    }
    300
    301    /**
    302     * Creates a simple legend based on a flow layout of the individual legend
    303     * items.
    304     *
    305     * @param items  the items to be added to the legend ({@code null}
    306     *     not permitted).
    307     * @param plot  the plot ({@code null} not permitted).
    308     * @param anchor  the anchor point ({@code null} not permitted).
    309     * @param orientation  the orientation ({@code null} not permitted).
    310     *
    311     * @return The simple legend.
    312     */
    313    private TableElement createSimpleLegend(List<LegendItemInfo> items,
    314            Anchor2D anchor, Orientation orientation, ChartStyle style) {
    315        ArgChecks.nullNotPermitted(items, "items");
    316        ArgChecks.nullNotPermitted(orientation, "orientation");
    317        ContainerElement legend;
    318        if (orientation == Orientation.HORIZONTAL) {
    319            FlowElement fe = new FlowElement(horizontalAlignment(anchor), 2);
    320            fe.setRefPoint(anchor.getRefPt());
    321            legend = fe;
    322        } else {
    323            VerticalFlowElement vfe = new VerticalFlowElement(
    324                    verticalAlignment(anchor), 2);
    325            vfe.setRefPoint(anchor.getRefPt());
    326            legend = vfe;
    327        }
    328        for (LegendItemInfo item : items) {
    329            Shape shape = item.getShape();
    330            if (shape == null) {
    331                shape = style.getLegendItemShape();
    332            }
    333            TableElement legendItem = createLegendItem(item.getLabel(),
    334                    style.getLegendItemFont(), style.getLegendItemColor(),
    335                    shape, item.getColor(),
    336                    style.getLegendItemBackgroundColor());
    337            legendItem.setProperty(TableElement.CLASS,
    338                    InteractiveElementType.LEGEND_ITEM);
    339            legendItem.setProperty(Chart3D.SERIES_KEY, item.getSeriesKey());
    340            legend.addElement(legendItem);
    341        }
    342        return legend;
    343    }
    344
    345    /**
    346     * Returns the horizontal alignment that should be used.
    347     *
    348     * @param anchor  the anchor ({@code null} not permitted).
    349     *
    350     * @return The horizontal alignment.
    351     */
    352    private HAlign horizontalAlignment(Anchor2D anchor) {
    353        if (this.rowAlignment != null) {
    354            return this.rowAlignment;
    355        }
    356        if (anchor.getRefPt().isLeft()) {
    357            return HAlign.LEFT;
    358        }
    359        if (anchor.getRefPt().isRight()) {
    360            return HAlign.RIGHT;
    361        }
    362        return HAlign.CENTER;
    363    }
    364
    365    /**
    366     * Returns the vertical alignment that should be used.
    367     *
    368     * @param anchor  the anchor ({@code null} not permitted).
    369     *
    370     * @return The vertical alignment.
    371     */
    372    private VAlign verticalAlignment(Anchor2D anchor) {
    373        if (this.columnAlignment != null) {
    374            return this.columnAlignment;
    375        }
    376        if (anchor.getRefPt().isTop()) {
    377            return VAlign.TOP;
    378        }
    379        if (anchor.getRefPt().isBottom()) {
    380            return VAlign.BOTTOM;
    381        }
    382        return VAlign.MIDDLE;
    383    }
    384
    385    /**
    386     * Creates a single item in the legend (normally this represents one
    387     * data series from the dataset).
    388     *
    389     * @param text  the legend item text ({@code null} not permitted).
    390     * @param font  the font ({@code null} not permitted).
    391     * @param textcolor  the text color ({@code null} not permitted).
    392     * @param shape  the shape ({@code null} not permitted).
    393     * @param shapeColor  the shape color ({@code null} not permitted).
    394     * @param background  the background color ({@code null} not
    395     *     permitted).
    396     *
    397     * @return A legend item (never {@code null}).
    398     */
    399    private TableElement createLegendItem(String text, Font font,
    400            Color textColor, Shape shape, Color shapeColor, Color background) {
    401        // defer argument checks...
    402        ShapeElement se = new ShapeElement(shape, shapeColor);
    403        se.setBackgroundColor(background);
    404        TextElement te = new TextElement(text, font);
    405        te.setColor(textColor);
    406        te.setBackgroundColor(background);
    407        GridElement<String, String> ge = new GridElement<String, String>();
    408        ge.setElement(se, "R1", "C1");
    409        ge.setElement(te, "R1", "C2");
    410        return ge;
    411    }
    412
    413    /**
    414     * Tests this legend builder for equality with an arbitrary object.
    415     *
    416     * @param obj  the object ({@code null} permitted).
    417     *
    418     * @return A boolean.
    419     */
    420    @Override
    421    public boolean equals(Object obj) {
    422        if (obj == this) {
    423            return true;
    424        }
    425        if (!(obj instanceof StandardLegendBuilder)) {
    426            return false;
    427        }
    428        StandardLegendBuilder that = (StandardLegendBuilder) obj;
    429        if (!ObjectUtils.equals(this.header, that.header)) {
    430            return false;
    431        }
    432        if (this.headerAlignment != that.headerAlignment) {
    433            return false;
    434        }
    435        if (!ObjectUtils.equals(this.footer, that.footer)) {
    436            return false;
    437        }
    438        if (this.footerAlignment != that.footerAlignment) {
    439            return false;
    440        }
    441        return true;
    442    }
    443
    444}