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}