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 compositeLegend = new GridElement(); 278 compositeLegend.setBackground(null); 279 if (header != null) { 280 TextElement he = new TextElement(this.header, 281 style.getLegendHeaderFont()); 282 he.setHorizontalAligment(this.headerAlignment); 283 he.setBackgroundColor(style.getLegendHeaderBackgroundColor()); 284 compositeLegend.setElement(he, "R0", "C1"); 285 } 286 compositeLegend.setElement(legend, "R1", "C1"); 287 if (this.footer != null) { 288 TextElement fe = new TextElement(this.footer, 289 style.getLegendFooterFont()); 290 fe.setHorizontalAligment(this.footerAlignment); 291 fe.setBackgroundColor(style.getLegendFooterBackgroundColor()); 292 compositeLegend.setElement(fe, "R2", "C1"); 293 } 294 return compositeLegend; 295 } else { 296 return legend; 297 } 298 } 299 300 /** 301 * Creates a simple legend based on a flow layout of the individual legend 302 * items. 303 * 304 * @param items the items to be added to the legend ({@code null} 305 * not permitted). 306 * @param plot the plot ({@code null} not permitted). 307 * @param anchor the anchor point ({@code null} not permitted). 308 * @param orientation the orientation ({@code null} not permitted). 309 * 310 * @return The simple legend. 311 */ 312 private TableElement createSimpleLegend(List<LegendItemInfo> items, 313 Anchor2D anchor, Orientation orientation, ChartStyle style) { 314 ArgChecks.nullNotPermitted(items, "items"); 315 ArgChecks.nullNotPermitted(orientation, "orientation"); 316 ContainerElement legend; 317 if (orientation == Orientation.HORIZONTAL) { 318 FlowElement fe = new FlowElement(horizontalAlignment(anchor), 2); 319 fe.setRefPoint(anchor.getRefPt()); 320 legend = fe; 321 } else { 322 VerticalFlowElement vfe = new VerticalFlowElement( 323 verticalAlignment(anchor), 2); 324 vfe.setRefPoint(anchor.getRefPt()); 325 legend = vfe; 326 } 327 for (LegendItemInfo item : items) { 328 Shape shape = item.getShape(); 329 if (shape == null) { 330 shape = style.getLegendItemShape(); 331 } 332 TableElement legendItem = createLegendItem(item.getLabel(), 333 style.getLegendItemFont(), style.getLegendItemColor(), 334 shape, item.getColor(), 335 style.getLegendItemBackgroundColor()); 336 legendItem.setProperty(TableElement.CLASS, 337 InteractiveElementType.LEGEND_ITEM); 338 legendItem.setProperty(Chart3D.SERIES_KEY, item.getSeriesKey()); 339 legend.addElement(legendItem); 340 } 341 return legend; 342 } 343 344 /** 345 * Returns the horizontal alignment that should be used. 346 * 347 * @param anchor the anchor ({@code null} not permitted). 348 * 349 * @return The horizontal alignment. 350 */ 351 private HAlign horizontalAlignment(Anchor2D anchor) { 352 if (this.rowAlignment != null) { 353 return this.rowAlignment; 354 } 355 if (anchor.getRefPt().isLeft()) { 356 return HAlign.LEFT; 357 } 358 if (anchor.getRefPt().isRight()) { 359 return HAlign.RIGHT; 360 } 361 return HAlign.CENTER; 362 } 363 364 /** 365 * Returns the vertical alignment that should be used. 366 * 367 * @param anchor the anchor ({@code null} not permitted). 368 * 369 * @return The vertical alignment. 370 */ 371 private VAlign verticalAlignment(Anchor2D anchor) { 372 if (this.columnAlignment != null) { 373 return this.columnAlignment; 374 } 375 if (anchor.getRefPt().isTop()) { 376 return VAlign.TOP; 377 } 378 if (anchor.getRefPt().isBottom()) { 379 return VAlign.BOTTOM; 380 } 381 return VAlign.MIDDLE; 382 } 383 384 /** 385 * Creates a single item in the legend (normally this represents one 386 * data series from the dataset). 387 * 388 * @param text the legend item text ({@code null} not permitted). 389 * @param font the font ({@code null} not permitted). 390 * @param textcolor the text color ({@code null} not permitted). 391 * @param shape the shape ({@code null} not permitted). 392 * @param shapeColor the shape color ({@code null} not permitted). 393 * @param background the background color ({@code null} not 394 * permitted). 395 * 396 * @return A legend item (never {@code null}). 397 */ 398 private TableElement createLegendItem(String text, Font font, 399 Color textColor, Shape shape, Color shapeColor, Color background) { 400 // defer argument checks... 401 ShapeElement se = new ShapeElement(shape, shapeColor); 402 se.setBackgroundColor(background); 403 TextElement te = new TextElement(text, font); 404 te.setColor(textColor); 405 te.setBackgroundColor(background); 406 GridElement ge = new GridElement(); 407 ge.setElement(se, "R1", "C1"); 408 ge.setElement(te, "R1", "C2"); 409 return ge; 410 } 411 412 /** 413 * Tests this legend builder for equality with an arbitrary object. 414 * 415 * @param obj the object ({@code null} permitted). 416 * 417 * @return A boolean. 418 */ 419 @Override 420 public boolean equals(Object obj) { 421 if (obj == this) { 422 return true; 423 } 424 if (!(obj instanceof StandardLegendBuilder)) { 425 return false; 426 } 427 StandardLegendBuilder that = (StandardLegendBuilder) obj; 428 if (!ObjectUtils.equals(this.header, that.header)) { 429 return false; 430 } 431 if (this.headerAlignment != that.headerAlignment) { 432 return false; 433 } 434 if (!ObjectUtils.equals(this.footer, that.footer)) { 435 return false; 436 } 437 if (this.footerAlignment != that.footerAlignment) { 438 return false; 439 } 440 return true; 441 } 442 443}