001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
006     *
007     * Project Info:  http://www.jfree.org/jfreechart/index.html
008     *
009     * This library is free software; you can redistribute it and/or modify it 
010     * under the terms of the GNU Lesser General Public License as published by 
011     * the Free Software Foundation; either version 2.1 of the License, or 
012     * (at your option) any later version.
013     *
014     * This library is distributed in the hope that it will be useful, but 
015     * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
016     * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
017     * License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public
020     * License along with this library; if not, write to the Free Software
021     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
022     * USA.  
023     *
024     * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
025     * in the United States and other countries.]
026     *
027     * -------------------------
028     * StackedXYBarRenderer.java
029     * -------------------------
030     * (C) Copyright 2004, 2005, by Andreas Schroeder and Contributors.
031     *
032     * Original Author:  Andreas Schroeder;
033     * Contributor(s):   David Gilbert (for Object Refinery Limited);          
034     *
035     * $Id: StackedXYBarRenderer.java,v 1.10.2.1 2005/10/25 20:56:21 mungady Exp $
036     *
037     * Changes
038     * -------
039     * 01-Apr-2004 : Version 1 (AS);
040     * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 
041     *               getYValue() (DG);
042     * 15-Aug-2004 : Added drawBarOutline to control draw/don't-draw bar 
043     *               outlines (BN);
044     * 10-Sep-2004 : drawBarOutline attribute is now inherited from XYBarRenderer 
045     *               and double primitives are retrieved from the dataset rather 
046     *               than Number objects (DG);
047     * 07-Jan-2005 : Updated for method name change in DatasetUtilities (DG);
048     * 25-Jan-2005 : Modified to handle negative values correctly (DG);
049     * 
050     */
051    
052    package org.jfree.chart.renderer.xy;
053    
054    import java.awt.Graphics2D;
055    import java.awt.geom.Rectangle2D;
056    import java.io.Serializable;
057    
058    import org.jfree.chart.axis.ValueAxis;
059    import org.jfree.chart.entity.EntityCollection;
060    import org.jfree.chart.entity.XYItemEntity;
061    import org.jfree.chart.labels.XYToolTipGenerator;
062    import org.jfree.chart.plot.CrosshairState;
063    import org.jfree.chart.plot.PlotOrientation;
064    import org.jfree.chart.plot.PlotRenderingInfo;
065    import org.jfree.chart.plot.XYPlot;
066    import org.jfree.data.Range;
067    import org.jfree.data.general.DatasetUtilities;
068    import org.jfree.data.xy.IntervalXYDataset;
069    import org.jfree.data.xy.TableXYDataset;
070    import org.jfree.data.xy.XYDataset;
071    import org.jfree.ui.RectangleEdge;
072    
073    /**
074     * A bar renderer that displays the series items stacked.
075     * The dataset used together with this renderer must be a
076     * {@link org.jfree.data.xy.IntervalXYDataset} and a
077     * {@link org.jfree.data.xy.TableXYDataset}. For example, the
078     * dataset class {@link org.jfree.data.xy.CategoryTableXYDataset}
079     * implements both interfaces.
080     *
081     * @author andreas.schroeder
082     */
083    public class StackedXYBarRenderer extends XYBarRenderer 
084                                      implements Serializable {
085      
086        /** For serialization. */
087        private static final long serialVersionUID = -7049101055533436444L;
088        
089        /**
090         * Creates a new renderer.
091         */
092        public StackedXYBarRenderer() {
093            super();
094        }
095    
096        /**
097         * Creates a new renderer.
098         *
099         * @param margin  the percentual amount of the bars that are cut away.
100         */
101        public StackedXYBarRenderer(double margin) {
102            super(margin);
103        }
104    
105        /**
106         * Initialises the renderer and returns a state object that should be 
107         * passed to all subsequent calls to the drawItem() method. Here there is 
108         * nothing to do.
109         *
110         * @param g2  the graphics device.
111         * @param dataArea  the area inside the axes.
112         * @param plot  the plot.
113         * @param data  the data.
114         * @param info  an optional info collection object to return data back to
115         *              the caller.
116         *
117         * @return A state object.
118         */
119        public XYItemRendererState initialise(Graphics2D g2,
120                                              Rectangle2D dataArea,
121                                              XYPlot plot,
122                                              XYDataset data,
123                                              PlotRenderingInfo info) {
124            return new XYBarRendererState(info);
125        }
126    
127        /**
128         * Returns the range of values the renderer requires to display all the 
129         * items from the specified dataset.
130         * 
131         * @param dataset  the dataset (<code>null</code> permitted).
132         * 
133         * @return The range (<code>null</code> if the dataset is <code>null</code>
134         *         or empty).
135         */
136        public Range findRangeBounds(XYDataset dataset) {
137            if (dataset != null) {
138                return DatasetUtilities.findStackedRangeBounds(
139                    (TableXYDataset) dataset
140                );
141            }
142            else {
143                return null;
144            }
145        }
146    
147        /**
148         * Draws the visual representation of a single data item.
149         *
150         * @param g2  the graphics device.
151         * @param state  the renderer state.
152         * @param dataArea  the area within which the plot is being drawn.
153         * @param info  collects information about the drawing.
154         * @param plot  the plot (can be used to obtain standard color information 
155         *              etc).
156         * @param domainAxis  the domain axis.
157         * @param rangeAxis  the range axis.
158         * @param dataset  the dataset.
159         * @param series  the series index (zero-based).
160         * @param item  the item index (zero-based).
161         * @param crosshairState  crosshair information for the plot 
162         *                        (<code>null</code> permitted).
163         * @param pass  the pass index.
164         */
165        public void drawItem(Graphics2D g2, 
166                             XYItemRendererState state,
167                             Rectangle2D dataArea,
168                             PlotRenderingInfo info,
169                             XYPlot plot,
170                             ValueAxis domainAxis,
171                             ValueAxis rangeAxis,
172                             XYDataset dataset,
173                             int series,
174                             int item,
175                             CrosshairState crosshairState,
176                             int pass) {
177            if (!(dataset instanceof IntervalXYDataset 
178                    && dataset instanceof TableXYDataset)) {
179                String message = "dataset (type " + dataset.getClass().getName() 
180                    + ") has wrong type:";
181                boolean and = false;
182                if (!IntervalXYDataset.class.isAssignableFrom(dataset.getClass())) {
183                    message += " it is no IntervalXYDataset";
184                    and = true;
185                }
186                if (!TableXYDataset.class.isAssignableFrom(dataset.getClass())) {
187                    if (and) {
188                        message += " and";
189                    }
190                    message += " it is no TableXYDataset";
191                }
192    
193                throw new IllegalArgumentException(message);
194            }
195    
196            IntervalXYDataset intervalDataset = (IntervalXYDataset) dataset;
197            double value = intervalDataset.getYValue(series, item);
198            if (Double.isNaN(value)) {
199                return;
200            }
201    
202            double positiveBase = 0.0;
203            double negativeBase = 0.0;
204    
205            for (int i = 0; i < series; i++) {
206                double v = dataset.getYValue(i, item);
207                if (!Double.isNaN(v)) {
208                    if (v > 0) {
209                        positiveBase = positiveBase + v;
210                    }
211                    else {
212                        negativeBase = negativeBase + v;
213                    }
214                }
215            }
216    
217            double translatedBase;
218            double translatedValue;
219            RectangleEdge edgeR = plot.getRangeAxisEdge();
220            if (value > 0.0) {
221                translatedBase = rangeAxis.valueToJava2D(
222                    positiveBase, dataArea, edgeR
223                );
224                translatedValue = rangeAxis.valueToJava2D(
225                    positiveBase + value, dataArea, edgeR
226                );
227            }
228            else {
229                translatedBase = rangeAxis.valueToJava2D(
230                    negativeBase, dataArea, edgeR
231                );
232                translatedValue = rangeAxis.valueToJava2D(
233                    negativeBase + value, dataArea, edgeR
234                );
235            }
236    
237            RectangleEdge edgeD = plot.getDomainAxisEdge();
238            double startX = intervalDataset.getStartXValue(series, item);
239            if (Double.isNaN(startX)) {
240                return;
241            }
242            double translatedStartX = domainAxis.valueToJava2D(
243                startX, dataArea, edgeD
244            );
245    
246            double endX = intervalDataset.getEndXValue(series, item);
247            if (Double.isNaN(endX)) {
248                return;
249            }
250            double translatedEndX = domainAxis.valueToJava2D(endX, dataArea, edgeD);
251    
252            double translatedWidth = Math.max(
253                1, Math.abs(translatedEndX - translatedStartX)
254            );
255            double translatedHeight = Math.abs(translatedValue - translatedBase);
256            if (getMargin() > 0.0) {
257                double cut = translatedWidth * getMargin();
258                translatedWidth = translatedWidth - cut;
259                translatedStartX = translatedStartX + cut / 2;
260            }
261    
262            Rectangle2D bar = null;
263            PlotOrientation orientation = plot.getOrientation();
264            if (orientation == PlotOrientation.HORIZONTAL) {
265                bar = new Rectangle2D.Double(
266                    Math.min(translatedBase, translatedValue),
267                    translatedEndX,
268                    translatedHeight,
269                    translatedWidth
270                );
271            }
272            else if (orientation == PlotOrientation.VERTICAL) {
273                bar = new Rectangle2D.Double(
274                    translatedStartX,
275                    Math.min(translatedBase, translatedValue),
276                    translatedWidth,
277                    translatedHeight
278                );
279            }
280    
281            g2.setPaint(getItemPaint(series, item));
282            g2.fill(bar);
283            if (isDrawBarOutline() 
284                    && Math.abs(translatedEndX - translatedStartX) > 3) {
285                g2.setStroke(getItemStroke(series, item));
286                g2.setPaint(getItemOutlinePaint(series, item));
287                g2.draw(bar);
288            }
289    
290            // add an entity for the item...
291            if (info != null) {
292                EntityCollection entities = info.getOwner().getEntityCollection();
293                if (entities != null) {
294                    String tip = null;
295                    XYToolTipGenerator generator 
296                        = getToolTipGenerator(series, item);
297                    if (generator != null) {
298                        tip = generator.generateToolTip(dataset, series, item);
299                    }
300                    String url = null;
301                    if (getURLGenerator() != null) {
302                        url = getURLGenerator().generateURL(dataset, series, item);
303                    }
304                    XYItemEntity entity = new XYItemEntity(
305                        bar, dataset, series, item, tip, url
306                    );
307                    entities.add(entity);
308                }
309            }
310        }
311        
312    }