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.table; 034 035import java.awt.Color; 036import java.awt.Dimension; 037import java.awt.Graphics2D; 038import java.awt.geom.Dimension2D; 039import java.awt.geom.Rectangle2D; 040import java.io.Serializable; 041import java.util.Map; 042import java.util.ArrayList; 043import java.util.List; 044import java.awt.Insets; 045 046import com.orsoncharts.data.DefaultKeyedValues2D; 047 048/** 049 * A table element that contains a grid of elements. 050 * <br><br> 051 * NOTE: This class is serializable, but the serialization format is subject 052 * to change in future releases and should not be relied upon for persisting 053 * instances of this class. 054 */ 055@SuppressWarnings("serial") 056public class GridElement<R extends Comparable<R>, C extends Comparable<C>> 057 extends AbstractTableElement 058 implements TableElement, Serializable { 059 060 private static final Color TRANSPARENT_COLOR = new Color(0, 0, 0, 0); 061 062 /** Storage for the cell elements. */ 063 private DefaultKeyedValues2D<R, C, TableElement> elements; 064 065 /** 066 * Creates a new empty grid. 067 */ 068 public GridElement() { 069 this.elements = new DefaultKeyedValues2D<R, C, TableElement>(); 070 setBackgroundColor(TRANSPARENT_COLOR); 071 } 072 073 /** 074 * Adds (or updates) a cell in the grid. 075 * 076 * @param element the element ({@code null} permitted). 077 * @param rowKey the row key ({@code null} not permitted). 078 * @param columnKey the column key ({@code null} not permitted). 079 */ 080 public void setElement(TableElement element, R rowKey, C columnKey) { 081 // defer argument checking 082 this.elements.setValue(element, rowKey, columnKey); 083 } 084 085 /** 086 * Receives a visitor by calling the visitor's {@code visit()} method 087 * for each of the children in the grid, and finally for the grid itself. 088 * 089 * @param visitor the visitor ({@code null} not permitted). 090 * 091 * @since 1.2 092 */ 093 @Override 094 public void receive(TableElementVisitor visitor) { 095 for (int r = 0; r < this.elements.getRowCount(); r++) { 096 for (int c = 0; c < this.elements.getColumnCount(); c++) { 097 TableElement element = this.elements.getValue(r, c); 098 if (element != null) { 099 element.receive(visitor); 100 } 101 } 102 } 103 visitor.visit(this); 104 } 105 106 /** 107 * Finds the cell dimensions. 108 * 109 * @param g2 the graphics target (required to calculate font sizes). 110 * @param bounds the bounds. 111 * 112 * @return The cell dimensions (result[0] is the widths, result[1] is the 113 * heights). 114 */ 115 private double[][] findCellDimensions(Graphics2D g2, Rectangle2D bounds) { 116 int rowCount = this.elements.getRowCount(); 117 int columnCount = this.elements.getColumnCount(); 118 double[] widths = new double[columnCount]; 119 double[] heights = new double[rowCount]; 120 // calculate the maximum width for each column 121 for (int r = 0; r < elements.getRowCount(); r++) { 122 for (int c = 0; c < this.elements.getColumnCount(); c++) { 123 TableElement element = this.elements.getValue(r, c); 124 if (element == null) { 125 continue; 126 } 127 Dimension2D dim = element.preferredSize(g2, bounds); 128 widths[c] = Math.max(widths[c], dim.getWidth()); 129 heights[r] = Math.max(heights[r], dim.getHeight()); 130 } 131 } 132 return new double[][] { widths, heights }; 133 } 134 135 136 /** 137 * Returns the preferred size of the element (including insets). 138 * 139 * @param g2 the graphics target. 140 * @param bounds the bounds. 141 * @param constraints the constraints (ignored for now). 142 * 143 * @return The preferred size. 144 */ 145 @Override 146 public Dimension2D preferredSize(Graphics2D g2, Rectangle2D bounds, 147 Map<String, Object> constraints) { 148 Insets insets = getInsets(); 149 double[][] cellDimensions = findCellDimensions(g2, bounds); 150 double[] widths = cellDimensions[0]; 151 double[] heights = cellDimensions[1]; 152 double w = insets.left + insets.right; 153 for (int i = 0; i < widths.length; i++) { 154 w = w + widths[i]; 155 } 156 double h = insets.top + insets.bottom; 157 for (int i = 0; i < heights.length; i++) { 158 h = h + heights[i]; 159 } 160 return new Dimension((int) w, (int) h); 161 } 162 163 /** 164 * Performs a layout of this table element, returning a list of bounding 165 * rectangles for the element and its subelements. 166 * 167 * @param g2 the graphics target. 168 * @param bounds the bounds. 169 * @param constraints the constraints (if any). 170 * 171 * @return A list of bounding rectangles. 172 */ 173 @Override 174 public List<Rectangle2D> layoutElements(Graphics2D g2, Rectangle2D bounds, 175 Map<String, Object> constraints) { 176 double[][] cellDimensions = findCellDimensions(g2, bounds); 177 double[] widths = cellDimensions[0]; 178 double[] heights = cellDimensions[1]; 179 List<Rectangle2D> result = new ArrayList<Rectangle2D>( 180 this.elements.getRowCount() * this.elements.getColumnCount()); 181 double y = bounds.getY() + getInsets().top; 182 for (int r = 0; r < elements.getRowCount(); r++) { 183 double x = bounds.getX() + getInsets().left; 184 for (int c = 0; c < this.elements.getColumnCount(); c++) { 185 result.add(new Rectangle2D.Double(x, y, widths[c], heights[r])); 186 x += widths[c]; 187 } 188 y = y + heights[r]; 189 } 190 return result; 191 } 192 193 /** 194 * Draws the element within the specified bounds. 195 * 196 * @param g2 the graphics target. 197 * @param bounds the bounds. 198 */ 199 @Override 200 public void draw(Graphics2D g2, Rectangle2D bounds) { 201 draw(g2, bounds, null); 202 } 203 204 /** 205 * Draws the element within the specified bounds. If the 206 * {@code recordBounds} flag is set, this element and each of its 207 * children will have their {@code BOUNDS_2D} property updated with 208 * the current bounds. 209 * 210 * @param g2 the graphics target ({@code null} not permitted). 211 * @param bounds the bounds ({@code null} not permitted). 212 * @param onDrawHandler an object that will receive notification before 213 * and after the element is drawn ({@code null} permitted). 214 * 215 * @since 1.3 216 */ 217 @Override 218 public void draw(Graphics2D g2, Rectangle2D bounds, 219 TableElementOnDraw onDrawHandler) { 220 if (onDrawHandler != null) { 221 onDrawHandler.beforeDraw(this, g2, bounds); 222 } 223 if (getBackground() != null) { 224 getBackground().fill(g2, bounds); 225 } 226 List<Rectangle2D> positions = layoutElements(g2, bounds, null); 227 for (int r = 0; r < this.elements.getRowCount(); r++) { 228 for (int c = 0; c < this.elements.getColumnCount(); c++) { 229 TableElement element = this.elements.getValue(r, c); 230 if (element == null) { 231 continue; 232 } 233 Rectangle2D pos = positions.get(r * elements.getColumnCount() 234 + c); 235 element.draw(g2, pos, onDrawHandler); 236 } 237 } 238 if (onDrawHandler != null) { 239 onDrawHandler.afterDraw(this, g2, bounds); 240 } 241 } 242 243 /** 244 * Tests this element for equality with an arbitrary object. 245 * 246 * @param obj the object ({@code null} permitted). 247 * 248 * @return A boolean. 249 */ 250 @Override 251 public boolean equals(Object obj) { 252 if (obj == this) { 253 return true; 254 } 255 if (!(obj instanceof GridElement)) { 256 return false; 257 } 258 GridElement that = (GridElement) obj; 259 if (!this.elements.equals(that.elements)) { 260 return false; 261 } 262 return true; 263 } 264 265 /** 266 * Returns a string representation of this element, primarily for 267 * debugging purposes. 268 * 269 * @return A string representation of this element. 270 */ 271 @Override 272 public String toString() { 273 StringBuilder sb = new StringBuilder(); 274 sb.append("GridElement[rowCount=").append(this.elements.getRowCount()); 275 sb.append(", columnCount=").append(this.elements.getColumnCount()); 276 sb.append("]"); 277 return sb.toString(); 278 } 279 280}