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}