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 * ColumnArrangement.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 column layout. This class is immutable. 051 */ 052public class ColumnArrangement implements Arrangement, Serializable { 053 054 /** For serialization. */ 055 private static final long serialVersionUID = -5315388482898581555L; 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 columns. */ 064 private double horizontalGap; 065 066 /** The vertical gap between items in a column. */ 067 private double verticalGap; 068 069 /** 070 * Creates a new instance. 071 */ 072 public ColumnArrangement() { 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 ColumnArrangement(HorizontalAlignment hAlign, 084 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, no information 103 // 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 g2 the graphics device. 114 * @param constraint the size constraint. 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 throw new RuntimeException("Not implemented."); 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 throw new RuntimeException("Not implemented."); 138 } 139 else if (h == LengthConstraintType.FIXED) { 140 return arrangeFF(container, g2, constraint); 141 } 142 else if (h == LengthConstraintType.RANGE) { 143 throw new RuntimeException("Not implemented."); 144 } 145 } 146 else if (w == LengthConstraintType.RANGE) { 147 if (h == LengthConstraintType.NONE) { 148 throw new RuntimeException("Not implemented."); 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 return new Size2D(); // TODO: complete this 158 159 } 160 161 /** 162 * Calculates and sets the bounds of all the items in the specified 163 * container, subject to the given constraint. The {@code Graphics2D} 164 * can be used by some items (particularly items containing text) to 165 * calculate sizing parameters. 166 * 167 * @param container the container whose items are being arranged. 168 * @param g2 the graphics device. 169 * @param constraint the size constraint. 170 * 171 * @return The container size after the arrangement. 172 */ 173 protected Size2D arrangeFF(BlockContainer container, Graphics2D g2, 174 RectangleConstraint constraint) { 175 // TODO: implement properly 176 return arrangeNF(container, g2, constraint); 177 } 178 179 /** 180 * Calculates and sets the bounds of all the items in the specified 181 * container, subject to the given constraint. The {@code Graphics2D} 182 * can be used by some items (particularly items containing text) to 183 * calculate sizing parameters. 184 * 185 * @param container the container whose items are being arranged. 186 * @param constraint the size constraint. 187 * @param g2 the graphics device. 188 * 189 * @return The container size after the arrangement. 190 */ 191 protected Size2D arrangeNF(BlockContainer container, Graphics2D g2, 192 RectangleConstraint constraint) { 193 194 List blocks = container.getBlocks(); 195 196 double height = constraint.getHeight(); 197 if (height <= 0.0) { 198 height = Double.POSITIVE_INFINITY; 199 } 200 201 double x = 0.0; 202 double y = 0.0; 203 double maxWidth = 0.0; 204 List itemsInColumn = new ArrayList(); 205 for (int i = 0; i < blocks.size(); i++) { 206 Block block = (Block) blocks.get(i); 207 Size2D size = block.arrange(g2, RectangleConstraint.NONE); 208 if (y + size.height <= height) { 209 itemsInColumn.add(block); 210 block.setBounds( 211 new Rectangle2D.Double(x, y, size.width, size.height) 212 ); 213 y = y + size.height + this.verticalGap; 214 maxWidth = Math.max(maxWidth, size.width); 215 } 216 else { 217 if (itemsInColumn.isEmpty()) { 218 // place in this column (truncated) anyway 219 block.setBounds( 220 new Rectangle2D.Double( 221 x, y, size.width, Math.min(size.height, height - y) 222 ) 223 ); 224 y = 0.0; 225 x = x + size.width + this.horizontalGap; 226 } 227 else { 228 // start new column 229 itemsInColumn.clear(); 230 x = x + maxWidth + this.horizontalGap; 231 y = 0.0; 232 maxWidth = size.width; 233 block.setBounds( 234 new Rectangle2D.Double( 235 x, y, size.width, Math.min(size.height, height) 236 ) 237 ); 238 y = size.height + this.verticalGap; 239 itemsInColumn.add(block); 240 } 241 } 242 } 243 return new Size2D(x + maxWidth, constraint.getHeight()); 244 } 245 246 /** 247 * Arranges a container with range constraints for both the horizontal 248 * and vertical. 249 * 250 * @param container the container. 251 * @param g2 the graphics device. 252 * @param constraint the constraint. 253 * 254 * @return The size of the container. 255 */ 256 protected Size2D arrangeRR(BlockContainer container, Graphics2D g2, 257 RectangleConstraint constraint) { 258 259 // first arrange without constraints, and see if this fits within 260 // the required ranges... 261 Size2D s1 = arrangeNN(container, g2); 262 if (constraint.getHeightRange().contains(s1.height)) { 263 return s1; // TODO: we didn't check the width yet 264 } 265 else { 266 RectangleConstraint c = constraint.toFixedHeight( 267 constraint.getHeightRange().getUpperBound() 268 ); 269 return arrangeRF(container, g2, c); 270 } 271 } 272 273 /** 274 * Arranges the blocks in the container using a fixed height and a 275 * range for the width. 276 * 277 * @param container the container. 278 * @param g2 the graphics device. 279 * @param constraint the constraint. 280 * 281 * @return The size of the container after arrangement. 282 */ 283 protected Size2D arrangeRF(BlockContainer container, Graphics2D g2, 284 RectangleConstraint constraint) { 285 286 Size2D s = arrangeNF(container, g2, constraint); 287 if (constraint.getWidthRange().contains(s.width)) { 288 return s; 289 } 290 else { 291 RectangleConstraint c = constraint.toFixedWidth( 292 constraint.getWidthRange().constrain(s.getWidth()) 293 ); 294 return arrangeFF(container, g2, c); 295 } 296 } 297 298 /** 299 * Arranges the blocks without any constraints. This puts all blocks 300 * into a single column. 301 * 302 * @param container the container. 303 * @param g2 the graphics device. 304 * 305 * @return The size after the arrangement. 306 */ 307 protected Size2D arrangeNN(BlockContainer container, Graphics2D g2) { 308 double y = 0.0; 309 double height = 0.0; 310 double maxWidth = 0.0; 311 List blocks = container.getBlocks(); 312 int blockCount = blocks.size(); 313 if (blockCount > 0) { 314 Size2D[] sizes = new Size2D[blocks.size()]; 315 for (int i = 0; i < blocks.size(); i++) { 316 Block block = (Block) blocks.get(i); 317 sizes[i] = block.arrange(g2, RectangleConstraint.NONE); 318 height = height + sizes[i].getHeight(); 319 maxWidth = Math.max(sizes[i].width, maxWidth); 320 block.setBounds( 321 new Rectangle2D.Double( 322 0.0, y, sizes[i].width, sizes[i].height 323 ) 324 ); 325 y = y + sizes[i].height + this.verticalGap; 326 } 327 if (blockCount > 1) { 328 height = height + this.verticalGap * (blockCount - 1); 329 } 330 if (this.horizontalAlignment != HorizontalAlignment.LEFT) { 331 for (int i = 0; i < blocks.size(); i++) { 332 //Block b = (Block) blocks.get(i); 333 if (this.horizontalAlignment 334 == HorizontalAlignment.CENTER) { 335 //TODO: shift block right by half 336 } 337 else if (this.horizontalAlignment 338 == HorizontalAlignment.RIGHT) { 339 //TODO: shift block over to right 340 } 341 } 342 } 343 } 344 return new Size2D(maxWidth, height); 345 } 346 347 /** 348 * Clears any cached information. 349 */ 350 @Override 351 public void clear() { 352 // no action required. 353 } 354 355 /** 356 * Tests this instance for equality with an arbitrary object. 357 * 358 * @param obj the object ({@code null} permitted). 359 * 360 * @return A boolean. 361 */ 362 @Override 363 public boolean equals(Object obj) { 364 if (obj == this) { 365 return true; 366 } 367 if (!(obj instanceof ColumnArrangement)) { 368 return false; 369 } 370 ColumnArrangement that = (ColumnArrangement) obj; 371 if (!Objects.equals(this.horizontalAlignment, that.horizontalAlignment)) { 372 return false; 373 } 374 if (!Objects.equals(this.verticalAlignment, that.verticalAlignment)) { 375 return false; 376 } 377 if (Double.doubleToLongBits(this.horizontalGap) != 378 Double.doubleToLongBits(that.horizontalGap)) { 379 return false; 380 } 381 if (Double.doubleToLongBits(this.verticalGap) != 382 Double.doubleToLongBits(that.verticalGap)) { 383 return false; 384 } 385 return true; 386 } 387 388 @Override 389 public int hashCode() { 390 int hash = 7; 391 hash = 79 * hash + Objects.hashCode(this.horizontalAlignment); 392 hash = 79 * hash + Objects.hashCode(this.verticalAlignment); 393 hash = 79 * hash + (int) (Double.doubleToLongBits(this.horizontalGap) ^ 394 (Double.doubleToLongBits(this.horizontalGap) >>> 32)); 395 hash = 79 * hash + (int) (Double.doubleToLongBits(this.verticalGap) ^ 396 (Double.doubleToLongBits(this.verticalGap) >>> 32)); 397 return hash; 398 } 399}