001/* =========================================================== 002 * Orson Charts : a 3D chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C)opyright 2013-2022, by David Gilbert. All rights reserved. 006 * 007 * https://github.com/jfree/orson-charts 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 org.jfree.chart3d.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 org.jfree.chart3d.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<>(); 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<>( 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 Rectangle2D cellBounds = new Rectangle2D.Double(x, y, widths[c], heights[r]); 186 TableElement element = this.elements.getValue(r, c); 187 element.layoutElements(g2, cellBounds, null); 188 result.add(cellBounds); 189 x += widths[c]; 190 } 191 y = y + heights[r]; 192 } 193 return result; 194 } 195 196 /** 197 * Draws the element within the specified bounds. 198 * 199 * @param g2 the graphics target. 200 * @param bounds the bounds. 201 */ 202 @Override 203 public void draw(Graphics2D g2, Rectangle2D bounds) { 204 draw(g2, bounds, null); 205 } 206 207 /** 208 * Draws the element within the specified bounds. If the 209 * {@code recordBounds} flag is set, this element and each of its 210 * children will have their {@code BOUNDS_2D} property updated with 211 * the current bounds. 212 * 213 * @param g2 the graphics target ({@code null} not permitted). 214 * @param bounds the bounds ({@code null} not permitted). 215 * @param onDrawHandler an object that will receive notification before 216 * and after the element is drawn ({@code null} permitted). 217 * 218 * @since 1.3 219 */ 220 @Override 221 public void draw(Graphics2D g2, Rectangle2D bounds, 222 TableElementOnDraw onDrawHandler) { 223 if (onDrawHandler != null) { 224 onDrawHandler.beforeDraw(this, g2, bounds); 225 } 226 if (getBackground() != null) { 227 getBackground().fill(g2, bounds); 228 } 229 List<Rectangle2D> positions = layoutElements(g2, bounds, null); 230 for (int r = 0; r < this.elements.getRowCount(); r++) { 231 for (int c = 0; c < this.elements.getColumnCount(); c++) { 232 TableElement element = this.elements.getValue(r, c); 233 if (element == null) { 234 continue; 235 } 236 Rectangle2D pos = positions.get(r * elements.getColumnCount() 237 + c); 238 element.draw(g2, pos, onDrawHandler); 239 } 240 } 241 if (onDrawHandler != null) { 242 onDrawHandler.afterDraw(this, g2, bounds); 243 } 244 } 245 246 /** 247 * Tests this element for equality with an arbitrary object. 248 * 249 * @param obj the object ({@code null} permitted). 250 * 251 * @return A boolean. 252 */ 253 @Override 254 public boolean equals(Object obj) { 255 if (obj == this) { 256 return true; 257 } 258 if (!(obj instanceof GridElement)) { 259 return false; 260 } 261 GridElement that = (GridElement) obj; 262 if (!this.elements.equals(that.elements)) { 263 return false; 264 } 265 return true; 266 } 267 268 /** 269 * Returns a string representation of this element, primarily for 270 * debugging purposes. 271 * 272 * @return A string representation of this element. 273 */ 274 @Override 275 public String toString() { 276 return "GridElement[rowCount=" + this.elements.getRowCount() 277 + ", columnCount=" + this.elements.getColumnCount() + "]"; 278 } 279 280}