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}