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}