001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2020, by Object Refinery Limited 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-2008, by Object Refinery Limited. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): -; 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 org.jfree.chart.ui.HorizontalAlignment; 045import org.jfree.chart.ui.Size2D; 046import org.jfree.chart.ui.VerticalAlignment; 047 048/** 049 * Arranges blocks in a flow layout. This class is immutable. 050 */ 051public class FlowArrangement implements Arrangement, Serializable { 052 053 /** For serialization. */ 054 private static final long serialVersionUID = 4543632485478613800L; 055 056 /** The horizontal alignment of blocks. */ 057 private HorizontalAlignment horizontalAlignment; 058 059 /** The vertical alignment of blocks within each row. */ 060 private VerticalAlignment verticalAlignment; 061 062 /** The horizontal gap between items within rows. */ 063 private double horizontalGap; 064 065 /** The vertical gap between rows. */ 066 private double verticalGap; 067 068 /** 069 * Creates a new instance. 070 */ 071 public FlowArrangement() { 072 this(HorizontalAlignment.CENTER, VerticalAlignment.CENTER, 2.0, 2.0); 073 } 074 075 /** 076 * Creates a new instance. 077 * 078 * @param hAlign the horizontal alignment (currently ignored). 079 * @param vAlign the vertical alignment (currently ignored). 080 * @param hGap the horizontal gap. 081 * @param vGap the vertical gap. 082 */ 083 public FlowArrangement(HorizontalAlignment hAlign, VerticalAlignment vAlign, 084 double hGap, double vGap) { 085 this.horizontalAlignment = hAlign; 086 this.verticalAlignment = vAlign; 087 this.horizontalGap = hGap; 088 this.verticalGap = vGap; 089 } 090 091 /** 092 * Adds a block to be managed by this instance. This method is usually 093 * called by the {@link BlockContainer}, you shouldn't need to call it 094 * directly. 095 * 096 * @param block the block. 097 * @param key a key that controls the position of the block. 098 */ 099 @Override 100 public void add(Block block, Object key) { 101 // since the flow layout is relatively straightforward, 102 // no information needs to be recorded here 103 } 104 105 /** 106 * Calculates and sets the bounds of all the items in the specified 107 * container, subject to the given constraint. The {@code Graphics2D} 108 * can be used by some items (particularly items containing text) to 109 * calculate sizing parameters. 110 * 111 * @param container the container whose items are being arranged. 112 * @param constraint the size constraint. 113 * @param g2 the graphics device. 114 * 115 * @return The size of the container after arrangement of the contents. 116 */ 117 @Override 118 public Size2D arrange(BlockContainer container, Graphics2D g2, 119 RectangleConstraint constraint) { 120 121 LengthConstraintType w = constraint.getWidthConstraintType(); 122 LengthConstraintType h = constraint.getHeightConstraintType(); 123 if (w == LengthConstraintType.NONE) { 124 if (h == LengthConstraintType.NONE) { 125 return arrangeNN(container, g2); 126 } 127 else if (h == LengthConstraintType.FIXED) { 128 return arrangeNF(container, g2, constraint); 129 } 130 else if (h == LengthConstraintType.RANGE) { 131 throw new RuntimeException("Not implemented."); 132 } 133 } 134 else if (w == LengthConstraintType.FIXED) { 135 if (h == LengthConstraintType.NONE) { 136 return arrangeFN(container, g2, constraint); 137 } 138 else if (h == LengthConstraintType.FIXED) { 139 return arrangeFF(container, g2, constraint); 140 } 141 else if (h == LengthConstraintType.RANGE) { 142 return arrangeFR(container, g2, constraint); 143 } 144 } 145 else if (w == LengthConstraintType.RANGE) { 146 if (h == LengthConstraintType.NONE) { 147 return arrangeRN(container, g2, constraint); 148 } 149 else if (h == LengthConstraintType.FIXED) { 150 return arrangeRF(container, g2, constraint); 151 } 152 else if (h == LengthConstraintType.RANGE) { 153 return arrangeRR(container, g2, constraint); 154 } 155 } 156 throw new RuntimeException("Unrecognised constraint type."); 157 158 } 159 160 /** 161 * Arranges the blocks in the container with a fixed width and no height 162 * constraint. 163 * 164 * @param container the container. 165 * @param constraint the constraint. 166 * @param g2 the graphics device. 167 * 168 * @return The size. 169 */ 170 protected Size2D arrangeFN(BlockContainer container, Graphics2D g2, 171 RectangleConstraint constraint) { 172 173 List blocks = container.getBlocks(); 174 double width = constraint.getWidth(); 175 176 double x = 0.0; 177 double y = 0.0; 178 double maxHeight = 0.0; 179 List itemsInRow = new ArrayList(); 180 for (int i = 0; i < blocks.size(); i++) { 181 Block block = (Block) blocks.get(i); 182 Size2D size = block.arrange(g2, RectangleConstraint.NONE); 183 if (x + size.width <= width) { 184 itemsInRow.add(block); 185 block.setBounds( 186 new Rectangle2D.Double(x, y, size.width, size.height) 187 ); 188 x = x + size.width + this.horizontalGap; 189 maxHeight = Math.max(maxHeight, size.height); 190 } 191 else { 192 if (itemsInRow.isEmpty()) { 193 // place in this row (truncated) anyway 194 block.setBounds( 195 new Rectangle2D.Double( 196 x, y, Math.min(size.width, width - x), size.height 197 ) 198 ); 199 x = 0.0; 200 y = y + size.height + this.verticalGap; 201 } 202 else { 203 // start new row 204 itemsInRow.clear(); 205 x = 0.0; 206 y = y + maxHeight + this.verticalGap; 207 maxHeight = size.height; 208 block.setBounds( 209 new Rectangle2D.Double( 210 x, y, Math.min(size.width, width), size.height 211 ) 212 ); 213 x = size.width + this.horizontalGap; 214 itemsInRow.add(block); 215 } 216 } 217 } 218 return new Size2D(constraint.getWidth(), y + maxHeight); 219 } 220 221 /** 222 * Arranges the blocks in the container with a fixed width and a range 223 * constraint on the height. 224 * 225 * @param container the container. 226 * @param constraint the constraint. 227 * @param g2 the graphics device. 228 * 229 * @return The size following the arrangement. 230 */ 231 protected Size2D arrangeFR(BlockContainer container, Graphics2D g2, 232 RectangleConstraint constraint) { 233 234 Size2D s = arrangeFN(container, g2, constraint); 235 if (constraint.getHeightRange().contains(s.height)) { 236 return s; 237 } 238 else { 239 RectangleConstraint c = constraint.toFixedHeight( 240 constraint.getHeightRange().constrain(s.getHeight()) 241 ); 242 return arrangeFF(container, g2, c); 243 } 244 } 245 246 /** 247 * Arranges the blocks in the container with the overall height and width 248 * specified as fixed constraints. 249 * 250 * @param container the container. 251 * @param constraint the constraint. 252 * @param g2 the graphics device. 253 * 254 * @return The size following the arrangement. 255 */ 256 protected Size2D arrangeFF(BlockContainer container, Graphics2D g2, 257 RectangleConstraint constraint) { 258 259 // TODO: implement this properly 260 return arrangeFN(container, g2, constraint); 261 } 262 263 /** 264 * Arranges the blocks with the overall width and height to fit within 265 * specified ranges. 266 * 267 * @param container the container. 268 * @param constraint the constraint. 269 * @param g2 the graphics device. 270 * 271 * @return The size after the arrangement. 272 */ 273 protected Size2D arrangeRR(BlockContainer container, Graphics2D g2, 274 RectangleConstraint constraint) { 275 276 // first arrange without constraints, and see if this fits within 277 // the required ranges... 278 Size2D s1 = arrangeNN(container, g2); 279 if (constraint.getWidthRange().contains(s1.width)) { 280 return s1; // TODO: we didn't check the height yet 281 } 282 else { 283 RectangleConstraint c = constraint.toFixedWidth( 284 constraint.getWidthRange().getUpperBound() 285 ); 286 return arrangeFR(container, g2, c); 287 } 288 } 289 290 /** 291 * Arranges the blocks in the container with a range constraint on the 292 * width and a fixed height. 293 * 294 * @param container the container. 295 * @param constraint the constraint. 296 * @param g2 the graphics device. 297 * 298 * @return The size following the arrangement. 299 */ 300 protected Size2D arrangeRF(BlockContainer container, Graphics2D g2, 301 RectangleConstraint constraint) { 302 303 Size2D s = arrangeNF(container, g2, constraint); 304 if (constraint.getWidthRange().contains(s.width)) { 305 return s; 306 } 307 else { 308 RectangleConstraint c = constraint.toFixedWidth( 309 constraint.getWidthRange().constrain(s.getWidth()) 310 ); 311 return arrangeFF(container, g2, c); 312 } 313 } 314 315 /** 316 * Arranges the block with a range constraint on the width, and no 317 * constraint on the height. 318 * 319 * @param container the container. 320 * @param constraint the constraint. 321 * @param g2 the graphics device. 322 * 323 * @return The size following the arrangement. 324 */ 325 protected Size2D arrangeRN(BlockContainer container, Graphics2D g2, 326 RectangleConstraint constraint) { 327 // first arrange without constraints, then see if the width fits 328 // within the required range...if not, call arrangeFN() at max width 329 Size2D s1 = arrangeNN(container, g2); 330 if (constraint.getWidthRange().contains(s1.width)) { 331 return s1; 332 } 333 else { 334 RectangleConstraint c = constraint.toFixedWidth( 335 constraint.getWidthRange().getUpperBound() 336 ); 337 return arrangeFN(container, g2, c); 338 } 339 } 340 341 /** 342 * Arranges the blocks without any constraints. This puts all blocks 343 * into a single row. 344 * 345 * @param container the container. 346 * @param g2 the graphics device. 347 * 348 * @return The size after the arrangement. 349 */ 350 protected Size2D arrangeNN(BlockContainer container, Graphics2D g2) { 351 double x = 0.0; 352 double width = 0.0; 353 double maxHeight = 0.0; 354 List blocks = container.getBlocks(); 355 int blockCount = blocks.size(); 356 if (blockCount > 0) { 357 Size2D[] sizes = new Size2D[blocks.size()]; 358 for (int i = 0; i < blocks.size(); i++) { 359 Block block = (Block) blocks.get(i); 360 sizes[i] = block.arrange(g2, RectangleConstraint.NONE); 361 width = width + sizes[i].getWidth(); 362 maxHeight = Math.max(sizes[i].height, maxHeight); 363 block.setBounds( 364 new Rectangle2D.Double( 365 x, 0.0, sizes[i].width, sizes[i].height 366 ) 367 ); 368 x = x + sizes[i].width + this.horizontalGap; 369 } 370 if (blockCount > 1) { 371 width = width + this.horizontalGap * (blockCount - 1); 372 } 373 if (this.verticalAlignment != VerticalAlignment.TOP) { 374 for (int i = 0; i < blocks.size(); i++) { 375 //Block b = (Block) blocks.get(i); 376 if (this.verticalAlignment == VerticalAlignment.CENTER) { 377 //TODO: shift block down by half 378 } 379 else if (this.verticalAlignment 380 == VerticalAlignment.BOTTOM) { 381 //TODO: shift block down to bottom 382 } 383 } 384 } 385 } 386 return new Size2D(width, maxHeight); 387 } 388 389 /** 390 * Arranges the blocks with no width constraint and a fixed height 391 * constraint. This puts all blocks into a single row. 392 * 393 * @param container the container. 394 * @param constraint the constraint. 395 * @param g2 the graphics device. 396 * 397 * @return The size after the arrangement. 398 */ 399 protected Size2D arrangeNF(BlockContainer container, Graphics2D g2, 400 RectangleConstraint constraint) { 401 // TODO: for now we are ignoring the height constraint 402 return arrangeNN(container, g2); 403 } 404 405 /** 406 * Clears any cached information. 407 */ 408 @Override 409 public void clear() { 410 // no action required. 411 } 412 413 /** 414 * Tests this instance 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 FlowArrangement)) { 426 return false; 427 } 428 FlowArrangement that = (FlowArrangement) obj; 429 if (this.horizontalAlignment != that.horizontalAlignment) { 430 return false; 431 } 432 if (this.verticalAlignment != that.verticalAlignment) { 433 return false; 434 } 435 if (this.horizontalGap != that.horizontalGap) { 436 return false; 437 } 438 if (this.verticalGap != that.verticalGap) { 439 return false; 440 } 441 return true; 442 } 443 444}