001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2006, 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-2006, 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.3 2006/12/06 22:26:35 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     * ------------- JFREECHART 1.0.x ---------------------------------------------
050     * 06-Dec-2006 : Added support for GradientPaint (DG);
051     * 
052     */
053    
054    package org.jfree.chart.renderer.xy;
055    
056    import java.awt.GradientPaint;
057    import java.awt.Graphics2D;
058    import java.awt.Paint;
059    import java.awt.geom.Rectangle2D;
060    
061    import org.jfree.chart.axis.ValueAxis;
062    import org.jfree.chart.entity.EntityCollection;
063    import org.jfree.chart.entity.XYItemEntity;
064    import org.jfree.chart.labels.XYToolTipGenerator;
065    import org.jfree.chart.plot.CrosshairState;
066    import org.jfree.chart.plot.PlotOrientation;
067    import org.jfree.chart.plot.PlotRenderingInfo;
068    import org.jfree.chart.plot.XYPlot;
069    import org.jfree.data.Range;
070    import org.jfree.data.general.DatasetUtilities;
071    import org.jfree.data.xy.IntervalXYDataset;
072    import org.jfree.data.xy.TableXYDataset;
073    import org.jfree.data.xy.XYDataset;
074    import org.jfree.ui.RectangleEdge;
075    
076    /**
077     * A bar renderer that displays the series items stacked.
078     * The dataset used together with this renderer must be a
079     * {@link org.jfree.data.xy.IntervalXYDataset} and a
080     * {@link org.jfree.data.xy.TableXYDataset}. For example, the
081     * dataset class {@link org.jfree.data.xy.CategoryTableXYDataset}
082     * implements both interfaces.
083     */
084    public class StackedXYBarRenderer extends XYBarRenderer {
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            else {
142                return null;
143            }
144        }
145    
146        /**
147         * Draws the visual representation of a single data item.
148         *
149         * @param g2  the graphics device.
150         * @param state  the renderer state.
151         * @param dataArea  the area within which the plot is being drawn.
152         * @param info  collects information about the drawing.
153         * @param plot  the plot (can be used to obtain standard color information 
154         *              etc).
155         * @param domainAxis  the domain axis.
156         * @param rangeAxis  the range axis.
157         * @param dataset  the dataset.
158         * @param series  the series index (zero-based).
159         * @param item  the item index (zero-based).
160         * @param crosshairState  crosshair information for the plot 
161         *                        (<code>null</code> permitted).
162         * @param pass  the pass index.
163         */
164        public void drawItem(Graphics2D g2, 
165                             XYItemRendererState state,
166                             Rectangle2D dataArea,
167                             PlotRenderingInfo info,
168                             XYPlot plot,
169                             ValueAxis domainAxis,
170                             ValueAxis rangeAxis,
171                             XYDataset dataset,
172                             int series,
173                             int item,
174                             CrosshairState crosshairState,
175                             int pass) {
176            
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(positiveBase, dataArea, 
222                        edgeR);
223                translatedValue = rangeAxis.valueToJava2D(positiveBase + value, 
224                        dataArea, edgeR);
225            }
226            else {
227                translatedBase = rangeAxis.valueToJava2D(negativeBase, dataArea, 
228                        edgeR);
229                translatedValue = rangeAxis.valueToJava2D(negativeBase + value, 
230                        dataArea, edgeR);
231            }
232    
233            RectangleEdge edgeD = plot.getDomainAxisEdge();
234            double startX = intervalDataset.getStartXValue(series, item);
235            if (Double.isNaN(startX)) {
236                return;
237            }
238            double translatedStartX = domainAxis.valueToJava2D(startX, dataArea, 
239                    edgeD);
240    
241            double endX = intervalDataset.getEndXValue(series, item);
242            if (Double.isNaN(endX)) {
243                return;
244            }
245            double translatedEndX = domainAxis.valueToJava2D(endX, dataArea, edgeD);
246    
247            double translatedWidth = Math.max(1, Math.abs(translatedEndX 
248                    - translatedStartX));
249            double translatedHeight = Math.abs(translatedValue - translatedBase);
250            if (getMargin() > 0.0) {
251                double cut = translatedWidth * getMargin();
252                translatedWidth = translatedWidth - cut;
253                translatedStartX = translatedStartX + cut / 2;
254            }
255    
256            Rectangle2D bar = null;
257            PlotOrientation orientation = plot.getOrientation();
258            if (orientation == PlotOrientation.HORIZONTAL) {
259                bar = new Rectangle2D.Double(Math.min(translatedBase, 
260                        translatedValue), translatedEndX, translatedHeight,
261                        translatedWidth);
262            }
263            else if (orientation == PlotOrientation.VERTICAL) {
264                bar = new Rectangle2D.Double(translatedStartX,
265                        Math.min(translatedBase, translatedValue),
266                        translatedWidth, translatedHeight);
267            }
268    
269            Paint itemPaint = getItemPaint(series, item);
270            if (getGradientPaintTransformer() 
271                    != null && itemPaint instanceof GradientPaint) {
272                GradientPaint gp = (GradientPaint) itemPaint;
273                itemPaint = getGradientPaintTransformer().transform(gp, bar);
274            }
275            g2.setPaint(itemPaint);
276            g2.fill(bar);
277            if (isDrawBarOutline() 
278                    && Math.abs(translatedEndX - translatedStartX) > 3) {
279                g2.setStroke(getItemStroke(series, item));
280                g2.setPaint(getItemOutlinePaint(series, item));
281                g2.draw(bar);
282            }
283    
284            // add an entity for the item...
285            if (info != null) {
286                EntityCollection entities = info.getOwner().getEntityCollection();
287                if (entities != null) {
288                    String tip = null;
289                    XYToolTipGenerator generator 
290                        = getToolTipGenerator(series, item);
291                    if (generator != null) {
292                        tip = generator.generateToolTip(dataset, series, item);
293                    }
294                    String url = null;
295                    if (getURLGenerator() != null) {
296                        url = getURLGenerator().generateURL(dataset, series, item);
297                    }
298                    XYItemEntity entity = new XYItemEntity(bar, dataset, series, 
299                            item, tip, url);
300                    entities.add(entity);
301                }
302            }
303        }
304        
305    }