    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}