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