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}