    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    }