    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.renderer.category;
    034
    035import org.jfree.chart3d.Chart3DFactory;
    036import org.jfree.chart3d.data.DataUtils;
    037import org.jfree.chart3d.data.KeyedValues3DItemKey;
    038import org.jfree.chart3d.data.Range;
    039import org.jfree.chart3d.data.Values3D;
    040import org.jfree.chart3d.data.category.CategoryDataset3D;
    041import org.jfree.chart3d.graphics3d.Dimension3D;
    042import org.jfree.chart3d.graphics3d.Object3D;
    043import org.jfree.chart3d.graphics3d.Offset3D;
    044import org.jfree.chart3d.graphics3d.World;
    045import org.jfree.chart3d.label.ItemLabelPositioning;
    046import org.jfree.chart3d.plot.CategoryPlot3D;
    047
    048/**
    049 * A renderer that can be used with the {@link CategoryPlot3D} class to create
    050 * 3D stacked bar charts from data in a {@link CategoryDataset3D}.  The
    051 * {@code createStackedBarChart()} method in the {@link Chart3DFactory}
    052 * class will construct a chart that uses this renderer.  Here is a sample:
    053 * <div>
    054 * <img src="../../../../../../doc-files/StackedBarChart3DDemo1.svg"
    055 * alt="StackedBarChart3DDemo1.svg" width="500" height="359">
    056 * </div>
    057 * (refer to {@code StackedBarChart3DDemo1.java} for the code to generate
    058 * the above chart).
    059 * <br><br>
    060 * There is a factory method to create a chart using this renderer - see
    061 * {@link Chart3DFactory#createStackedBarChart(String, String, CategoryDataset3D, String, String, String)}.
    062 * <br><br>
    063 * NOTE: This class is serializable, but the serialization format is subject
    064 * to change in future releases and should not be relied upon for persisting
    065 * instances of this class.
    066 */
    067@SuppressWarnings("serial")
    068public class StackedBarRenderer3D extends BarRenderer3D {
    069
    070    /**
    071     * Creates a default constructor.
    072     */
    073    public StackedBarRenderer3D() {
    074        super();
    075        setItemLabelPositioning(ItemLabelPositioning.FRONT_AND_BACK);
    076        setItemLabelOffsets(new Offset3D(0.0, 0.0, -1.0));
    077    }
    078
    079    /**
    080     * Returns the range of values that will be required on the value axis
    081     * to see all the data from the dataset.  We override the method to
    082     * account for the bars from each series being stacked on top of one
    083     * another.
    084     *
    085     * @param data  the data ({@code null} not permitted).
    086     *
    087     * @return The range (possibly {@code null})
    088     */
    089    @Override
    090    public Range findValueRange(Values3D<? extends Number> data) {
    091        return DataUtils.findStackedValueRange(data);
    092    }
    093
    094    /**
    095     * Constructs and places one item from the specified dataset into the given
    096     * world.  This method will be called by the {@link CategoryPlot3D} class
    097     * while iterating over the items in the dataset.
    098     *
    099     * @param dataset  the dataset ({@code null} not permitted).
    100     * @param series  the series index.
    101     * @param row  the row index.
    102     * @param column  the column index.
    103     * @param world  the world ({@code null} not permitted).
    104     * @param dimensions  the plot dimensions ({@code null} not permitted).
    105     * @param xOffset  the x-offset.
    106     * @param yOffset  the y-offset.
    107     * @param zOffset  the z-offset.
    108     */
    109    @Override
    110    @SuppressWarnings("unchecked")
    111    public void composeItem(CategoryDataset3D dataset, int series, int row,
    112            int column, World world, Dimension3D dimensions,
    113            double xOffset, double yOffset, double zOffset) {
    114
    115        double value = dataset.getDoubleValue(series, row, column);
    116        if (Double.isNaN(value)) {
    117            return;
    118        }
    119        double[] stack = DataUtils.stackSubTotal(dataset, getBase(), series,
    120                row, column);
    121        double lower = stack[1];
    122        if (value < 0.0) {
    123            lower = stack[0];
    124        }
    125        double upper = lower + value;
    126        composeItem(upper, lower, dataset, series, row, column, world,
    127                dimensions, xOffset, yOffset, zOffset);
    128
    129    }
    130
    131    /**
    132     * Draws the item labels.
    133     *
    134     * @param world  the world.
    135     * @param dataset  the dataset.
    136     * @param itemKey  the item key.
    137     * @param xw  the x-coordinate.
    138     * @param yw  the y-coordinate.
    139     * @param zw  the z-coordinate.
    140     * @param basew  the base coordinate.
    141     * @param inverted  is the y-axis inverted?
    142     */
    143    @Override
    144    protected void drawItemLabels(World world, CategoryDataset3D dataset,
    145            KeyedValues3DItemKey itemKey, double xw, double yw, double zw,
    146            double basew, boolean inverted) {
    147        ItemLabelPositioning positioning = getItemLabelPositioning();
    148        if (getItemLabelGenerator() != null) {
    149            String label = getItemLabelGenerator().generateItemLabel(dataset,
    150                   itemKey.getSeriesKey(), itemKey.getRowKey(),
    151                   itemKey.getColumnKey());
    152            if (label != null) {
    153                Dimension3D dimensions = getPlot().getDimensions();
    154                double dx = getItemLabelOffsets().getDX();
    155                double dy = getItemLabelOffsets().getDY()
    156                        * dimensions.getHeight();
    157                double dz = getItemLabelOffsets().getDZ() * getBarZWidth();
    158                if (positioning.equals(ItemLabelPositioning.CENTRAL)) {
    159                    double yy = yw;
    160                    if (inverted) {
    161                        yy = basew;
    162                        dy = -dy;
    163                    }
    164                    Object3D labelObj = Object3D.createLabelObject(label,
    165                            getItemLabelFont(), getItemLabelColor(),
    166                            getItemLabelBackgroundColor(), xw + dx,
    167                            yy + dy, zw, false, true);
    168                    labelObj.setProperty(Object3D.ITEM_KEY, itemKey);
    169                    world.add(labelObj);
    170                } else if (positioning.equals(
    171                        ItemLabelPositioning.FRONT_AND_BACK)) {
    172                    double yy = (yw + basew) / 2.0;
    173                    Object3D labelObj1 = Object3D.createLabelObject(label,
    174                            getItemLabelFont(), getItemLabelColor(),
    175                            getItemLabelBackgroundColor(), xw + dx,
    176                            yy + dy, zw + dz, false, false);
    177                    labelObj1.setProperty(Object3D.ITEM_KEY, itemKey);
    178                    world.add(labelObj1);
    179                    Object3D labelObj2 = Object3D.createLabelObject(label,
    180                            getItemLabelFont(), getItemLabelColor(),
    181                            getItemLabelBackgroundColor(), xw + dx,
    182                            yy + dy, zw - dz, true, false);
    183                    labelObj2.setProperty(Object3D.ITEM_KEY, itemKey);
    184                    world.add(labelObj2);
    185                }
    186            }
    187        }
    188    }
    189
    190    /**
    191     * Tests this renderer for equality with an arbitrary object.
    192     *
    193     * @param obj  the object ({@code null} permitted).
    194     *
    195     * @return A boolean.
    196     */
    197    @Override
    198    public boolean equals(Object obj) {
    199        if (obj == this) {
    200            return true;
    201        }
    202        if (!(obj instanceof StackedBarRenderer3D)) {
    203            return false;
    204        }
    205        return super.equals(obj);
    206    }
    207
    208}