001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-present, by David Gilbert and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 025 * Other names may be trademarks of their respective owners.] 026 * 027 * -------------------- 028 * FlowArrangement.java 029 * -------------------- 030 * (C) Copyright 2004-present, by David Gilbert. 031 * 032 * Original Author: David Gilbert; 033 * Contributor(s): Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier); 034 * 035 */ 036 037package org.jfree.chart.block; 038 039import java.awt.Graphics2D; 040import java.awt.geom.Rectangle2D; 041import java.io.Serializable; 042import java.util.ArrayList; 043import java.util.List; 044import java.util.Objects; 045import org.jfree.chart.ui.HorizontalAlignment; 046import org.jfree.chart.ui.Size2D; 047import org.jfree.chart.ui.VerticalAlignment; 048 049/** 050 * Arranges blocks in a flow layout. This class is immutable. 051 */ 052public class FlowArrangement implements Arrangement, Serializable { 053 054 /** For serialization. */ 055 private static final long serialVersionUID = 4543632485478613800L; 056 057 /** The horizontal alignment of blocks. */ 058 private HorizontalAlignment horizontalAlignment; 059 060 /** The vertical alignment of blocks within each row. */ 061 private VerticalAlignment verticalAlignment; 062 063 /** The horizontal gap between items within rows. */ 064 private double horizontalGap; 065 066 /** The vertical gap between rows. */ 067 private double verticalGap; 068 069 /** 070 * Creates a new instance. 071 */ 072 public FlowArrangement() { 073 this(HorizontalAlignment.CENTER, VerticalAlignment.CENTER, 2.0, 2.0); 074 } 075 076 /** 077 * Creates a new instance. 078 * 079 * @param hAlign the horizontal alignment (currently ignored). 080 * @param vAlign the vertical alignment (currently ignored). 081 * @param hGap the horizontal gap. 082 * @param vGap the vertical gap. 083 */ 084 public FlowArrangement(HorizontalAlignment hAlign, VerticalAlignment vAlign, 085 double hGap, double vGap) { 086 this.horizontalAlignment = hAlign; 087 this.verticalAlignment = vAlign; 088 this.horizontalGap = hGap; 089 this.verticalGap = vGap; 090 } 091 092 /** 093 * Adds a block to be managed by this instance. This method is usually 094 * called by the {@link BlockContainer}, you shouldn't need to call it 095 * directly. 096 * 097 * @param block the block. 098 * @param key a key that controls the position of the block. 099 */ 100 @Override 101 public void add(Block block, Object key) { 102 // since the flow layout is relatively straightforward, 103 // no information needs to be recorded here 104 } 105 106 /** 107 * Calculates and sets the bounds of all the items in the specified 108 * container, subject to the given constraint. The {@code Graphics2D} 109 * can be used by some items (particularly items containing text) to 110 * calculate sizing parameters. 111 * 112 * @param container the container whose items are being arranged. 113 * @param constraint the size constraint. 114 * @param g2 the graphics device. 115 * 116 * @return The size of the container after arrangement of the contents. 117 */ 118 @Override 119 public Size2D arrange(BlockContainer container, Graphics2D g2, 120 RectangleConstraint constraint) { 121 122 LengthConstraintType w = constraint.getWidthConstraintType(); 123 LengthConstraintType h = constraint.getHeightConstraintType(); 124 if (w == LengthConstraintType.NONE) { 125 if (h == LengthConstraintType.NONE) { 126 return arrangeNN(container, g2); 127 } 128 else if (h == LengthConstraintType.FIXED) { 129 return arrangeNF(container, g2, constraint); 130 } 131 else if (h == LengthConstraintType.RANGE) { 132 throw new RuntimeException("Not implemented."); 133 } 134 } 135 else if (w == LengthConstraintType.FIXED) { 136 if (h == LengthConstraintType.NONE) { 137 return arrangeFN(container, g2, constraint); 138 } 139 else if (h == LengthConstraintType.FIXED) { 140 return arrangeFF(container, g2, constraint); 141 } 142 else if (h == LengthConstraintType.RANGE) { 143 return arrangeFR(container, g2, constraint); 144 } 145 } 146 else if (w == LengthConstraintType.RANGE) { 147 if (h == LengthConstraintType.NONE) { 148 return arrangeRN(container, g2, constraint); 149 } 150 else if (h == LengthConstraintType.FIXED) { 151 return arrangeRF(container, g2, constraint); 152 } 153 else if (h == LengthConstraintType.RANGE) { 154 return arrangeRR(container, g2, constraint); 155 } 156 } 157 throw new RuntimeException("Unrecognised constraint type."); 158 159 } 160 161 /** 162 * Arranges the blocks in the container with a fixed width and no height 163 * constraint. 164 * 165 * @param container the container. 166 * @param constraint the constraint. 167 * @param g2 the graphics device. 168 * 169 * @return The size. 170 */ 171 protected Size2D arrangeFN(BlockContainer container, Graphics2D g2, 172 RectangleConstraint constraint) { 173 174 List blocks = container.getBlocks(); 175 double width = constraint.getWidth(); 176 177 double x = 0.0; 178 double y = 0.0; 179 double maxHeight = 0.0; 180 List itemsInRow = new ArrayList(); 181 for (int i = 0; i < blocks.size(); i++) { 182 Block block = (Block) blocks.get(i); 183 Size2D size = block.arrange(g2, RectangleConstraint.NONE); 184 if (x + size.width <= width) { 185 itemsInRow.add(block); 186 block.setBounds( 187 new Rectangle2D.Double(x, y, size.width, size.height) 188 ); 189 x = x + size.width + this.horizontalGap; 190 maxHeight = Math.max(maxHeight, size.height); 191 } 192 else { 193 if (itemsInRow.isEmpty()) { 194 // place in this row (truncated) anyway 195 block.setBounds( 196 new Rectangle2D.Double( 197 x, y, Math.min(size.width, width - x), size.height 198 ) 199 ); 200 x = 0.0; 201 y = y + size.height + this.verticalGap; 202 } 203 else { 204 // start new row 205 itemsInRow.clear(); 206 x = 0.0; 207 y = y + maxHeight + this.verticalGap; 208 maxHeight = size.height; 209 block.setBounds( 210 new Rectangle2D.Double( 211 x, y, Math.min(size.width, width), size.height 212 ) 213 ); 214 x = size.width + this.horizontalGap; 215 itemsInRow.add(block); 216 } 217 } 218 } 219 return new Size2D(constraint.getWidth(), y + maxHeight); 220 } 221 222 /** 223 * Arranges the blocks in the container with a fixed width and a range 224 * constraint on the height. 225 * 226 * @param container the container. 227 * @param constraint the constraint. 228 * @param g2 the graphics device. 229 * 230 * @return The size following the arrangement. 231 */ 232 protected Size2D arrangeFR(BlockContainer container, Graphics2D g2, 233 RectangleConstraint constraint) { 234 235 Size2D s = arrangeFN(container, g2, constraint); 236 if (constraint.getHeightRange().contains(s.height)) { 237 return s; 238 } 239 else { 240 RectangleConstraint c = constraint.toFixedHeight( 241 constraint.getHeightRange().constrain(s.getHeight()) 242 ); 243 return arrangeFF(container, g2, c); 244 } 245 } 246 247 /** 248 * Arranges the blocks in the container with the overall height and width 249 * specified as fixed constraints. 250 * 251 * @param container the container. 252 * @param constraint the constraint. 253 * @param g2 the graphics device. 254 * 255 * @return The size following the arrangement. 256 */ 257 protected Size2D arrangeFF(BlockContainer container, Graphics2D g2, 258 RectangleConstraint constraint) { 259 260 // TODO: implement this properly 261 return arrangeFN(container, g2, constraint); 262 } 263 264 /** 265 * Arranges the blocks with the overall width and height to fit within 266 * specified ranges. 267 * 268 * @param container the container. 269 * @param constraint the constraint. 270 * @param g2 the graphics device. 271 * 272 * @return The size after the arrangement. 273 */ 274 protected Size2D arrangeRR(BlockContainer container, Graphics2D g2, 275 RectangleConstraint constraint) { 276 277 // first arrange without constraints, and see if this fits within 278 // the required ranges... 279 Size2D s1 = arrangeNN(container, g2); 280 if (constraint.getWidthRange().contains(s1.width)) { 281 return s1; // TODO: we didn't check the height yet 282 } 283 else { 284 RectangleConstraint c = constraint.toFixedWidth( 285 constraint.getWidthRange().getUpperBound() 286 ); 287 return arrangeFR(container, g2, c); 288 } 289 } 290 291 /** 292 * Arranges the blocks in the container with a range constraint on the 293 * width and a fixed height. 294 * 295 * @param container the container. 296 * @param constraint the constraint. 297 * @param g2 the graphics device. 298 * 299 * @return The size following the arrangement. 300 */ 301 protected Size2D arrangeRF(BlockContainer container, Graphics2D g2, 302 RectangleConstraint constraint) { 303 304 Size2D s = arrangeNF(container, g2, constraint); 305 if (constraint.getWidthRange().contains(s.width)) { 306 return s; 307 } 308 else { 309 RectangleConstraint c = constraint.toFixedWidth( 310 constraint.getWidthRange().constrain(s.getWidth()) 311 ); 312 return arrangeFF(container, g2, c); 313 } 314 } 315 316 /** 317 * Arranges the block with a range constraint on the width, and no 318 * constraint on the height. 319 * 320 * @param container the container. 321 * @param constraint the constraint. 322 * @param g2 the graphics device. 323 * 324 * @return The size following the arrangement. 325 */ 326 protected Size2D arrangeRN(BlockContainer container, Graphics2D g2, 327 RectangleConstraint constraint) { 328 // first arrange without constraints, then see if the width fits 329 // within the required range...if not, call arrangeFN() at max width 330 Size2D s1 = arrangeNN(container, g2); 331 if (constraint.getWidthRange().contains(s1.width)) { 332 return s1; 333 } 334 else { 335 RectangleConstraint c = constraint.toFixedWidth( 336 constraint.getWidthRange().getUpperBound() 337 ); 338 return arrangeFN(container, g2, c); 339 } 340 } 341 342 /** 343 * Arranges the blocks without any constraints. This puts all blocks 344 * into a single row. 345 * 346 * @param container the container. 347 * @param g2 the graphics device. 348 * 349 * @return The size after the arrangement. 350 */ 351 protected Size2D arrangeNN(BlockContainer container, Graphics2D g2) { 352 double x = 0.0; 353 double width = 0.0; 354 double maxHeight = 0.0; 355 List blocks = container.getBlocks(); 356 int blockCount = blocks.size(); 357 if (blockCount > 0) { 358 Size2D[] sizes = new Size2D[blocks.size()]; 359 for (int i = 0; i < blocks.size(); i++) { 360 Block block = (Block) blocks.get(i); 361 sizes[i] = block.arrange(g2, RectangleConstraint.NONE); 362 width = width + sizes[i].getWidth(); 363 maxHeight = Math.max(sizes[i].height, maxHeight); 364 block.setBounds( 365 new Rectangle2D.Double( 366 x, 0.0, sizes[i].width, sizes[i].height 367 ) 368 ); 369 x = x + sizes[i].width + this.horizontalGap; 370 } 371 if (blockCount > 1) { 372 width = width + this.horizontalGap * (blockCount - 1); 373 } 374 if (this.verticalAlignment != VerticalAlignment.TOP) { 375 for (int i = 0; i < blocks.size(); i++) { 376 //Block b = (Block) blocks.get(i); 377 if (this.verticalAlignment == VerticalAlignment.CENTER) { 378 //TODO: shift block down by half 379 } 380 else if (this.verticalAlignment 381 == VerticalAlignment.BOTTOM) { 382 //TODO: shift block down to bottom 383 } 384 } 385 } 386 } 387 return new Size2D(width, maxHeight); 388 } 389 390 /** 391 * Arranges the blocks with no width constraint and a fixed height 392 * constraint. This puts all blocks into a single row. 393 * 394 * @param container the container. 395 * @param constraint the constraint. 396 * @param g2 the graphics device. 397 * 398 * @return The size after the arrangement. 399 */ 400 protected Size2D arrangeNF(BlockContainer container, Graphics2D g2, 401 RectangleConstraint constraint) { 402 // TODO: for now we are ignoring the height constraint 403 return arrangeNN(container, g2); 404 } 405 406 /** 407 * Clears any cached information. 408 */ 409 @Override 410 public void clear() { 411 // no action required. 412 } 413 414 /** 415 * Tests this instance for equality with an arbitrary object. 416 * 417 * @param obj the object ({@code null} permitted). 418 * 419 * @return A boolean. 420 */ 421 @Override 422 public boolean equals(Object obj) { 423 if (obj == this) { 424 return true; 425 } 426 if (!(obj instanceof FlowArrangement)) { 427 return false; 428 } 429 FlowArrangement that = (FlowArrangement) obj; 430 if (!Objects.equals(this.horizontalAlignment, that.horizontalAlignment)) { 431 return false; 432 } 433 if (!Objects.equals(this.verticalAlignment, that.verticalAlignment)) { 434 return false; 435 } 436 if (Double.doubleToLongBits(this.horizontalGap) != 437 Double.doubleToLongBits(that.horizontalGap)) { 438 return false; 439 } 440 if (Double.doubleToLongBits(this.verticalGap) != 441 Double.doubleToLongBits(that.verticalGap)) { 442 return false; 443 } 444 return true; 445 } 446 447 @Override 448 public int hashCode() { 449 int hash = 7; 450 hash = 17 * hash + Objects.hashCode(this.horizontalAlignment); 451 hash = 17 * hash + Objects.hashCode(this.verticalAlignment); 452 hash = 17 * hash + (int) (Double.doubleToLongBits(this.horizontalGap) ^ 453 (Double.doubleToLongBits(this.horizontalGap) >>> 32)); 454 hash = 17 * hash + (int) (Double.doubleToLongBits(this.verticalGap) ^ 455 (Double.doubleToLongBits(this.verticalGap) >>> 32)); 456 return hash; 457 } 458}