    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    }