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