001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2011, 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     * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025     * Other names may be trademarks of their respective owners.]
026     *
027     * -----------------------
028     * LayeredBarRenderer.java
029     * -----------------------
030     * (C) Copyright 2003-2009, by Arnaud Lelievre and Contributors.
031     *
032     * Original Author:  Arnaud Lelievre (for Garden);
033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
034     *                   Zoheb Borbora;
035     *
036     * Changes
037     * -------
038     * 28-Aug-2003 : Version 1 (AL);
039     * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
040     * 07-Oct-2003 : Added renderer state (DG);
041     * 21-Oct-2003 : Bar width moved to renderer state (DG);
042     * 05-Nov-2004 : Modified drawItem() signature (DG);
043     * 20-Apr-2005 : Renamed CategoryLabelGenerator
044     *               --> CategoryItemLabelGenerator (DG);
045     * 17-Nov-2005 : Added support for gradient paint (DG);
046     * ------------- JFREECHART 1.0.x ---------------------------------------------
047     * 18-Aug-2006 : Fixed the bar width calculation to respect the maximum bar
048     *               width setting (thanks to Zoheb Borbora) (DG);
049     * 02-Feb-2007 : Removed author tags all over JFreeChart sources (DG);
050     * 19-May-2009 : Fixed FindBugs warnings, patch by Michal Wozniak (DG);
051     *
052     */
053    
054    package org.jfree.chart.renderer.category;
055    
056    import java.awt.GradientPaint;
057    import java.awt.Graphics2D;
058    import java.awt.Paint;
059    import java.awt.Stroke;
060    import java.awt.geom.Rectangle2D;
061    import java.io.Serializable;
062    
063    import org.jfree.chart.axis.CategoryAxis;
064    import org.jfree.chart.axis.ValueAxis;
065    import org.jfree.chart.entity.EntityCollection;
066    import org.jfree.chart.labels.CategoryItemLabelGenerator;
067    import org.jfree.chart.plot.CategoryPlot;
068    import org.jfree.chart.plot.PlotOrientation;
069    import org.jfree.data.category.CategoryDataset;
070    import org.jfree.ui.GradientPaintTransformer;
071    import org.jfree.ui.RectangleEdge;
072    import org.jfree.util.ObjectList;
073    
074    /**
075     * A {@link CategoryItemRenderer} that represents data using bars which are
076     * superimposed.  The example shown here is generated by the
077     * <code>LayeredBarChartDemo1.java</code> program included in the JFreeChart
078     * Demo Collection:
079     * <br><br>
080     * <img src="../../../../../images/LayeredBarRendererSample.png"
081     * alt="LayeredBarRendererSample.png" />
082     */
083    public class LayeredBarRenderer extends BarRenderer implements Serializable {
084    
085        /** For serialization. */
086        private static final long serialVersionUID = -8716572894780469487L;
087    
088        /** A list of the width of each series bar. */
089        protected ObjectList seriesBarWidthList;
090    
091        /**
092         * Default constructor.
093         */
094        public LayeredBarRenderer() {
095            super();
096            this.seriesBarWidthList = new ObjectList();
097        }
098    
099        /**
100         * Returns the bar width for a series, or <code>Double.NaN</code> if no
101         * width has been set.
102         *
103         * @param series  the series index (zero based).
104         *
105         * @return The width for the series (1.0=100%, it is the maximum).
106         */
107        public double getSeriesBarWidth(int series) {
108            double result = Double.NaN;
109            Number n = (Number) this.seriesBarWidthList.get(series);
110            if (n != null) {
111                result = n.doubleValue();
112            }
113            return result;
114        }
115    
116        /**
117         * Sets the width of the bars of a series.
118         *
119         * @param series  the series index (zero based).
120         * @param width  the width of the series bar in percentage (1.0=100%, it is
121         *               the maximum).
122         */
123        public void setSeriesBarWidth(int series, double width) {
124            this.seriesBarWidthList.set(series, new Double(width));
125        }
126    
127        /**
128         * Calculates the bar width and stores it in the renderer state.
129         *
130         * @param plot  the plot.
131         * @param dataArea  the data area.
132         * @param rendererIndex  the renderer index.
133         * @param state  the renderer state.
134         */
135        protected void calculateBarWidth(CategoryPlot plot,
136                                         Rectangle2D dataArea,
137                                         int rendererIndex,
138                                         CategoryItemRendererState state) {
139    
140            // calculate the bar width - this calculation differs from the
141            // BarRenderer calculation because the bars are layered on top of one
142            // another, so there is effectively only one bar per category for
143            // the purpose of the bar width calculation
144            CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex);
145            CategoryDataset dataset = plot.getDataset(rendererIndex);
146            if (dataset != null) {
147                int columns = dataset.getColumnCount();
148                int rows = dataset.getRowCount();
149                double space = 0.0;
150                PlotOrientation orientation = plot.getOrientation();
151                if (orientation == PlotOrientation.HORIZONTAL) {
152                    space = dataArea.getHeight();
153                }
154                else if (orientation == PlotOrientation.VERTICAL) {
155                    space = dataArea.getWidth();
156                }
157                double maxWidth = space * getMaximumBarWidth();
158                double categoryMargin = 0.0;
159                if (columns > 1) {
160                    categoryMargin = domainAxis.getCategoryMargin();
161                }
162                double used = space * (1 - domainAxis.getLowerMargin()
163                    - domainAxis.getUpperMargin() - categoryMargin);
164                if ((rows * columns) > 0) {
165                    state.setBarWidth(Math.min(used / (dataset.getColumnCount()),
166                            maxWidth));
167                }
168                else {
169                    state.setBarWidth(Math.min(used, maxWidth));
170                }
171            }
172        }
173    
174        /**
175         * Draws the bar for one item in the dataset.
176         *
177         * @param g2  the graphics device.
178         * @param state  the renderer state.
179         * @param dataArea  the plot area.
180         * @param plot  the plot.
181         * @param domainAxis  the domain (category) axis.
182         * @param rangeAxis  the range (value) axis.
183         * @param data  the data.
184         * @param row  the row index (zero-based).
185         * @param column  the column index (zero-based).
186         * @param pass  the pass index.
187         */
188        public void drawItem(Graphics2D g2,
189                             CategoryItemRendererState state,
190                             Rectangle2D dataArea,
191                             CategoryPlot plot,
192                             CategoryAxis domainAxis,
193                             ValueAxis rangeAxis,
194                             CategoryDataset data,
195                             int row,
196                             int column,
197                             int pass) {
198    
199            PlotOrientation orientation = plot.getOrientation();
200            if (orientation == PlotOrientation.HORIZONTAL) {
201                drawHorizontalItem(g2, state, dataArea, plot, domainAxis,
202                        rangeAxis, data, row, column);
203            }
204            else if (orientation == PlotOrientation.VERTICAL) {
205                drawVerticalItem(g2, state, dataArea, plot, domainAxis, rangeAxis,
206                        data, row, column);
207            }
208    
209        }
210    
211        /**
212         * Draws the bar for a single (series, category) data item.
213         *
214         * @param g2  the graphics device.
215         * @param state  the renderer state.
216         * @param dataArea  the data area.
217         * @param plot  the plot.
218         * @param domainAxis  the domain axis.
219         * @param rangeAxis  the range axis.
220         * @param dataset  the dataset.
221         * @param row  the row index (zero-based).
222         * @param column  the column index (zero-based).
223         */
224        protected void drawHorizontalItem(Graphics2D g2,
225                                          CategoryItemRendererState state,
226                                          Rectangle2D dataArea,
227                                          CategoryPlot plot,
228                                          CategoryAxis domainAxis,
229                                          ValueAxis rangeAxis,
230                                          CategoryDataset dataset,
231                                          int row,
232                                          int column) {
233    
234            // nothing is drawn for null values...
235            Number dataValue = dataset.getValue(row, column);
236            if (dataValue == null) {
237                return;
238            }
239    
240            // X
241            double value = dataValue.doubleValue();
242            double base = 0.0;
243            double lclip = getLowerClip();
244            double uclip = getUpperClip();
245            if (uclip <= 0.0) {  // cases 1, 2, 3 and 4
246                if (value >= uclip) {
247                    return; // bar is not visible
248                }
249                base = uclip;
250                if (value <= lclip) {
251                    value = lclip;
252                }
253            }
254            else if (lclip <= 0.0) { // cases 5, 6, 7 and 8
255                if (value >= uclip) {
256                    value = uclip;
257                }
258                else {
259                    if (value <= lclip) {
260                        value = lclip;
261                    }
262                }
263            }
264            else { // cases 9, 10, 11 and 12
265                if (value <= lclip) {
266                    return; // bar is not visible
267                }
268                base = lclip;
269                if (value >= uclip) {
270                    value = uclip;
271                }
272            }
273    
274            RectangleEdge edge = plot.getRangeAxisEdge();
275            double transX1 = rangeAxis.valueToJava2D(base, dataArea, edge);
276            double transX2 = rangeAxis.valueToJava2D(value, dataArea, edge);
277            double rectX = Math.min(transX1, transX2);
278            double rectWidth = Math.abs(transX2 - transX1);
279    
280            // Y
281            double rectY = domainAxis.getCategoryMiddle(column, getColumnCount(),
282                    dataArea, plot.getDomainAxisEdge()) - state.getBarWidth() / 2.0;
283    
284            int seriesCount = getRowCount();
285    
286            // draw the bar...
287            double shift = 0.0;
288            double rectHeight = 0.0;
289            double widthFactor = 1.0;
290            double seriesBarWidth = getSeriesBarWidth(row);
291            if (!Double.isNaN(seriesBarWidth)) {
292                widthFactor = seriesBarWidth;
293            }
294            rectHeight = widthFactor * state.getBarWidth();
295            rectY = rectY + (1 - widthFactor) * state.getBarWidth() / 2.0;
296            if (seriesCount > 1) {
297                shift = rectHeight * 0.20 / (seriesCount - 1);
298            }
299    
300            Rectangle2D bar = new Rectangle2D.Double(rectX,
301                    (rectY + ((seriesCount - 1 - row) * shift)), rectWidth,
302                    (rectHeight - (seriesCount - 1 - row) * shift * 2));
303    
304            Paint itemPaint = getItemPaint(row, column);
305            GradientPaintTransformer t = getGradientPaintTransformer();
306            if (t != null && itemPaint instanceof GradientPaint) {
307                itemPaint = t.transform((GradientPaint) itemPaint, bar);
308            }
309            g2.setPaint(itemPaint);
310            g2.fill(bar);
311    
312            // draw the outline...
313            if (isDrawBarOutline()
314                    && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
315                Stroke stroke = getItemOutlineStroke(row, column);
316                Paint paint = getItemOutlinePaint(row, column);
317                if (stroke != null && paint != null) {
318                    g2.setStroke(stroke);
319                    g2.setPaint(paint);
320                    g2.draw(bar);
321                }
322            }
323    
324            CategoryItemLabelGenerator generator
325                = getItemLabelGenerator(row, column);
326            if (generator != null && isItemLabelVisible(row, column)) {
327                drawItemLabel(g2, dataset, row, column, plot, generator, bar,
328                        (transX1 > transX2));
329            }
330    
331            // collect entity and tool tip information...
332            EntityCollection entities = state.getEntityCollection();
333            if (entities != null) {
334                addItemEntity(entities, dataset, row, column, bar);
335            }
336        }
337    
338        /**
339         * Draws the bar for a single (series, category) data item.
340         *
341         * @param g2  the graphics device.
342         * @param state  the renderer state.
343         * @param dataArea  the data area.
344         * @param plot  the plot.
345         * @param domainAxis  the domain axis.
346         * @param rangeAxis  the range axis.
347         * @param dataset  the dataset.
348         * @param row  the row index (zero-based).
349         * @param column  the column index (zero-based).
350         */
351        protected void drawVerticalItem(Graphics2D g2,
352                                        CategoryItemRendererState state,
353                                        Rectangle2D dataArea,
354                                        CategoryPlot plot,
355                                        CategoryAxis domainAxis,
356                                        ValueAxis rangeAxis,
357                                        CategoryDataset dataset,
358                                        int row,
359                                        int column) {
360    
361            // nothing is drawn for null values...
362            Number dataValue = dataset.getValue(row, column);
363            if (dataValue == null) {
364                return;
365            }
366    
367            // BAR X
368            double rectX = domainAxis.getCategoryMiddle(column, getColumnCount(),
369                    dataArea, plot.getDomainAxisEdge()) - state.getBarWidth() / 2.0;
370    
371            int seriesCount = getRowCount();
372    
373            // BAR Y
374            double value = dataValue.doubleValue();
375            double base = 0.0;
376            double lclip = getLowerClip();
377            double uclip = getUpperClip();
378    
379            if (uclip <= 0.0) {  // cases 1, 2, 3 and 4
380                if (value >= uclip) {
381                    return; // bar is not visible
382                }
383                base = uclip;
384                if (value <= lclip) {
385                    value = lclip;
386                }
387            }
388            else if (lclip <= 0.0) { // cases 5, 6, 7 and 8
389                if (value >= uclip) {
390                    value = uclip;
391                }
392                else {
393                    if (value <= lclip) {
394                        value = lclip;
395                    }
396                }
397            }
398            else { // cases 9, 10, 11 and 12
399                if (value <= lclip) {
400                    return; // bar is not visible
401                }
402                base = getLowerClip();
403                if (value >= uclip) {
404                   value = uclip;
405                }
406            }
407    
408            RectangleEdge edge = plot.getRangeAxisEdge();
409            double transY1 = rangeAxis.valueToJava2D(base, dataArea, edge);
410            double transY2 = rangeAxis.valueToJava2D(value, dataArea, edge);
411            double rectY = Math.min(transY2, transY1);
412    
413            double rectWidth = 0.0;
414            double rectHeight = Math.abs(transY2 - transY1);
415    
416            // draw the bar...
417            double shift = 0.0;
418            double widthFactor = 1.0;
419            double seriesBarWidth = getSeriesBarWidth(row);
420            if (!Double.isNaN(seriesBarWidth)) {
421                widthFactor = seriesBarWidth;
422            }
423            rectWidth = widthFactor * state.getBarWidth();
424            rectX = rectX + (1 - widthFactor) * state.getBarWidth() / 2.0;
425            if (seriesCount > 1) {
426                // needs to be improved !!!
427                shift = rectWidth * 0.20 / (seriesCount - 1);
428            }
429    
430            Rectangle2D bar = new Rectangle2D.Double(
431                (rectX + ((seriesCount - 1 - row) * shift)), rectY,
432                (rectWidth - (seriesCount - 1 - row) * shift * 2), rectHeight);
433            Paint itemPaint = getItemPaint(row, column);
434            GradientPaintTransformer t = getGradientPaintTransformer();
435            if (t != null && itemPaint instanceof GradientPaint) {
436                itemPaint = t.transform((GradientPaint) itemPaint, bar);
437            }
438            g2.setPaint(itemPaint);
439            g2.fill(bar);
440    
441            // draw the outline...
442            if (isDrawBarOutline()
443                    && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
444                Stroke stroke = getItemOutlineStroke(row, column);
445                Paint paint = getItemOutlinePaint(row, column);
446                if (stroke != null && paint != null) {
447                    g2.setStroke(stroke);
448                    g2.setPaint(paint);
449                    g2.draw(bar);
450                }
451            }
452    
453            // draw the item labels if there are any...
454            double transX1 = rangeAxis.valueToJava2D(base, dataArea, edge);
455            double transX2 = rangeAxis.valueToJava2D(value, dataArea, edge);
456    
457            CategoryItemLabelGenerator generator
458                = getItemLabelGenerator(row, column);
459            if (generator != null && isItemLabelVisible(row, column)) {
460                drawItemLabel(g2, dataset, row, column, plot, generator, bar,
461                        (transX1 > transX2));
462            }
463    
464            // collect entity and tool tip information...
465            EntityCollection entities = state.getEntityCollection();
466            if (entities != null) {
467                addItemEntity(entities, dataset, row, column, bar);
468            }
469        }
470    
471    }