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