    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.CategoryItemEntity;
    065    import org.jfree.chart.entity.EntityCollection;
    066    import org.jfree.chart.labels.CategoryItemLabelGenerator;
    067    import org.jfree.chart.labels.CategoryToolTipGenerator;
    068    import org.jfree.chart.plot.CategoryPlot;
    069    import org.jfree.chart.plot.PlotOrientation;
    070    import org.jfree.data.category.CategoryDataset;
    071    import org.jfree.ui.GradientPaintTransformer;
    072    import org.jfree.ui.RectangleEdge;
    073    import org.jfree.util.ObjectList;
    074
    075    /**
    076     * A {@link CategoryItemRenderer} that represents data using bars which are
    077     * superimposed.
    078     */
    079    public class LayeredBarRenderer extends BarRenderer implements Serializable {
    080
    081        /** For serialization. */
    082        private static final long serialVersionUID = -8716572894780469487L;
    083
    084        /** A list of the width of each series bar. */
    085        protected ObjectList seriesBarWidthList;
    086
    087        /**
    088         * Default constructor.
    089         */
    090        public LayeredBarRenderer() {
    091            super();
    092            this.seriesBarWidthList = new ObjectList();
    093        }
    094
    095        /**
    096         * Returns the bar width for a series, or <code>Double.NaN</code> if no
    097         * width has been set.
    098         *
    099         * @param series  the series index (zero based).
    100         *
    101         * @return The width for the series (1.0=100%, it is the maximum).
    102         */
    103        public double getSeriesBarWidth(int series) {
    104            double result = Double.NaN;
    105            Number n = (Number) this.seriesBarWidthList.get(series);
    106            if (n != null) {
    107                result = n.doubleValue();
    108            }
    109            return result;
    110        }
    111
    112        /**
    113         * Sets the width of the bars of a series.
    114         *
    115         * @param series  the series index (zero based).
    116         * @param width  the width of the series bar in percentage (1.0=100%, it is
    117         *               the maximum).
    118         */
    119        public void setSeriesBarWidth(int series, double width) {
    120            this.seriesBarWidthList.set(series, new Double(width));
    121        }
    122
    123        /**
    124         * Calculates the bar width and stores it in the renderer state.
    125         *
    126         * @param plot  the plot.
    127         * @param dataArea  the data area.
    128         * @param rendererIndex  the renderer index.
    129         * @param state  the renderer state.
    130         */
    131        protected void calculateBarWidth(CategoryPlot plot,
    132                                         Rectangle2D dataArea,
    133                                         int rendererIndex,
    134                                         CategoryItemRendererState state) {
    135
    136            // calculate the bar width - this calculation differs from the
    137            // BarRenderer calculation because the bars are layered on top of one
    138            // another, so there is effectively only one bar per category for
    139            // the purpose of the bar width calculation
    140            CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex);
    141            CategoryDataset dataset = plot.getDataset(rendererIndex);
    142            if (dataset != null) {
    143                int columns = dataset.getColumnCount();
    144                int rows = dataset.getRowCount();
    145                double space = 0.0;
    146                PlotOrientation orientation = plot.getOrientation();
    147                if (orientation == PlotOrientation.HORIZONTAL) {
    148                    space = dataArea.getHeight();
    149                }
    150                else if (orientation == PlotOrientation.VERTICAL) {
    151                    space = dataArea.getWidth();
    152                }
    153                double maxWidth = space * getMaximumBarWidth();
    154                double categoryMargin = 0.0;
    155                if (columns > 1) {
    156                    categoryMargin = domainAxis.getCategoryMargin();
    157                }
    158                double used = space * (1 - domainAxis.getLowerMargin()
    159                    - domainAxis.getUpperMargin() - categoryMargin);
    160                if ((rows * columns) > 0) {
    161                    state.setBarWidth(Math.min(used / (dataset.getColumnCount()),
    162                            maxWidth));
    163                }
    164                else {
    165                    state.setBarWidth(Math.min(used, maxWidth));
    166                }
    167            }
    168        }
    169
    170        /**
    171         * Draws the bar for one item in the dataset.
    172         *
    173         * @param g2  the graphics device.
    174         * @param state  the renderer state.
    175         * @param dataArea  the plot area.
    176         * @param plot  the plot.
    177         * @param domainAxis  the domain (category) axis.
    178         * @param rangeAxis  the range (value) axis.
    179         * @param data  the data.
    180         * @param row  the row index (zero-based).
    181         * @param column  the column index (zero-based).
    182         * @param pass  the pass index.
    183         */
    184        public void drawItem(Graphics2D g2,
    185                             CategoryItemRendererState state,
    186                             Rectangle2D dataArea,
    187                             CategoryPlot plot,
    188                             CategoryAxis domainAxis,
    189                             ValueAxis rangeAxis,
    190                             CategoryDataset data,
    191                             int row,
    192                             int column,
    193                             int pass) {
    194
    195            PlotOrientation orientation = plot.getOrientation();
    196            if (orientation == PlotOrientation.HORIZONTAL) {
    197                drawHorizontalItem(g2, state, dataArea, plot, domainAxis,
    198                        rangeAxis, data, row, column);
    199            }
    200            else if (orientation == PlotOrientation.VERTICAL) {
    201                drawVerticalItem(g2, state, dataArea, plot, domainAxis, rangeAxis,
    202                        data, row, column);
    203            }
    204
    205        }
    206
    207        /**
    208         * Draws the bar for a single (series, category) data item.
    209         *
    210         * @param g2  the graphics device.
    211         * @param state  the renderer state.
    212         * @param dataArea  the data area.
    213         * @param plot  the plot.
    214         * @param domainAxis  the domain axis.
    215         * @param rangeAxis  the range axis.
    216         * @param data  the data.
    217         * @param row  the row index (zero-based).
    218         * @param column  the column index (zero-based).
    219         */
    220        protected void drawHorizontalItem(Graphics2D g2,
    221                                          CategoryItemRendererState state,
    222                                          Rectangle2D dataArea,
    223                                          CategoryPlot plot,
    224                                          CategoryAxis domainAxis,
    225                                          ValueAxis rangeAxis,
    226                                          CategoryDataset data,
    227                                          int row,
    228                                          int column) {
    229
    230            // nothing is drawn for null values...
    231            Number dataValue = data.getValue(row, column);
    232            if (dataValue == null) {
    233                return;
    234            }
    235
    236            // X
    237            double value = dataValue.doubleValue();
    238            double base = 0.0;
    239            double lclip = getLowerClip();
    240            double uclip = getUpperClip();
    241            if (uclip <= 0.0) {  // cases 1, 2, 3 and 4
    242                if (value >= uclip) {
    243                    return; // bar is not visible
    244                }
    245                base = uclip;
    246                if (value <= lclip) {
    247                    value = lclip;
    248                }
    249            }
    250            else if (lclip <= 0.0) { // cases 5, 6, 7 and 8
    251                if (value >= uclip) {
    252                    value = uclip;
    253                }
    254                else {
    255                    if (value <= lclip) {
    256                        value = lclip;
    257                    }
    258                }
    259            }
    260            else { // cases 9, 10, 11 and 12
    261                if (value <= lclip) {
    262                    return; // bar is not visible
    263                }
    264                base = lclip;
    265                if (value >= uclip) {
    266                    value = uclip;
    267                }
    268            }
    269
    270            RectangleEdge edge = plot.getRangeAxisEdge();
    271            double transX1 = rangeAxis.valueToJava2D(base, dataArea, edge);
    272            double transX2 = rangeAxis.valueToJava2D(value, dataArea, edge);
    273            double rectX = Math.min(transX1, transX2);
    274            double rectWidth = Math.abs(transX2 - transX1);
    275
    276            // Y
    277            double rectY = domainAxis.getCategoryMiddle(column, getColumnCount(),
    278                    dataArea, plot.getDomainAxisEdge()) - state.getBarWidth() / 2.0;
    279
    280            int seriesCount = getRowCount();
    281
    282            // draw the bar...
    283            double shift = 0.0;
    284            double rectHeight = 0.0;
    285            double widthFactor = 1.0;
    286            double seriesBarWidth = getSeriesBarWidth(row);
    287            if (!Double.isNaN(seriesBarWidth)) {
    288                widthFactor = seriesBarWidth;
    289            }
    290            rectHeight = widthFactor * state.getBarWidth();
    291            rectY = rectY + (1 - widthFactor) * state.getBarWidth() / 2.0;
    292            if (seriesCount > 1) {
    293                shift = rectHeight * 0.20 / (seriesCount - 1);
    294            }
    295
    296            Rectangle2D bar = new Rectangle2D.Double(rectX,
    297                    (rectY + ((seriesCount - 1 - row) * shift)), rectWidth,
    298                    (rectHeight - (seriesCount - 1 - row) * shift * 2));
    299
    300            Paint itemPaint = getItemPaint(row, column);
    301            GradientPaintTransformer t = getGradientPaintTransformer();
    302            if (t != null && itemPaint instanceof GradientPaint) {
    303                itemPaint = t.transform((GradientPaint) itemPaint, bar);
    304            }
    305            g2.setPaint(itemPaint);
    306            g2.fill(bar);
    307
    308            // draw the outline...
    309            if (isDrawBarOutline()
    310                    && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
    311                Stroke stroke = getItemOutlineStroke(row, column);
    312                Paint paint = getItemOutlinePaint(row, column);
    313                if (stroke != null && paint != null) {
    314                    g2.setStroke(stroke);
    315                    g2.setPaint(paint);
    316                    g2.draw(bar);
    317                }
    318            }
    319
    320            CategoryItemLabelGenerator generator
    321                = getItemLabelGenerator(row, column);
    322            if (generator != null && isItemLabelVisible(row, column)) {
    323                drawItemLabel(g2, data, row, column, plot, generator, bar,
    324                        (transX1 > transX2));
    325            }
    326
    327            // collect entity and tool tip information...
    328            if (state.getInfo() != null) {
    329                EntityCollection entities = state.getEntityCollection();
    330                if (entities != null) {
    331                    String tip = null;
    332                    CategoryToolTipGenerator tipster
    333                        = getToolTipGenerator(row, column);
    334                    if (tipster != null) {
    335                        tip = tipster.generateToolTip(data, row, column);
    336                    }
    337                    String url = null;
    338                    if (getItemURLGenerator(row, column) != null) {
    339                        url = getItemURLGenerator(row, column).generateURL(data,
    340                                row, column);
    341                    }
    342                    CategoryItemEntity entity = new CategoryItemEntity(bar, tip,
    343                            url, data, data.getRowKey(row),
    344                            data.getColumnKey(column));
    345                    entities.add(entity);
    346                }
    347            }
    348        }
    349
    350        /**
    351         * Draws the bar for a single (series, category) data item.
    352         *
    353         * @param g2  the graphics device.
    354         * @param state  the renderer state.
    355         * @param dataArea  the data area.
    356         * @param plot  the plot.
    357         * @param domainAxis  the domain axis.
    358         * @param rangeAxis  the range axis.
    359         * @param data  the data.
    360         * @param row  the row index (zero-based).
    361         * @param column  the column index (zero-based).
    362         */
    363        protected void drawVerticalItem(Graphics2D g2,
    364                                        CategoryItemRendererState state,
    365                                        Rectangle2D dataArea,
    366                                        CategoryPlot plot,
    367                                        CategoryAxis domainAxis,
    368                                        ValueAxis rangeAxis,
    369                                        CategoryDataset data,
    370                                        int row,
    371                                        int column) {
    372
    373            // nothing is drawn for null values...
    374            Number dataValue = data.getValue(row, column);
    375            if (dataValue == null) {
    376                return;
    377            }
    378
    379            // BAR X
    380            double rectX = domainAxis.getCategoryMiddle(column, getColumnCount(),
    381                    dataArea, plot.getDomainAxisEdge()) - state.getBarWidth() / 2.0;
    382
    383            int seriesCount = getRowCount();
    384
    385            // BAR Y
    386            double value = dataValue.doubleValue();
    387            double base = 0.0;
    388            double lclip = getLowerClip();
    389            double uclip = getUpperClip();
    390
    391            if (uclip <= 0.0) {  // cases 1, 2, 3 and 4
    392                if (value >= uclip) {
    393                    return; // bar is not visible
    394                }
    395                base = uclip;
    396                if (value <= lclip) {
    397                    value = lclip;
    398                }
    399            }
    400            else if (lclip <= 0.0) { // cases 5, 6, 7 and 8
    401                if (value >= uclip) {
    402                    value = uclip;
    403                }
    404                else {
    405                    if (value <= lclip) {
    406                        value = lclip;
    407                    }
    408                }
    409            }
    410            else { // cases 9, 10, 11 and 12
    411                if (value <= lclip) {
    412                    return; // bar is not visible
    413                }
    414                base = getLowerClip();
    415                if (value >= uclip) {
    416                   value = uclip;
    417                }
    418            }
    419
    420            RectangleEdge edge = plot.getRangeAxisEdge();
    421            double transY1 = rangeAxis.valueToJava2D(base, dataArea, edge);
    422            double transY2 = rangeAxis.valueToJava2D(value, dataArea, edge);
    423            double rectY = Math.min(transY2, transY1);
    424
    425            double rectWidth = state.getBarWidth();
    426            double rectHeight = Math.abs(transY2 - transY1);
    427
    428            // draw the bar...
    429            double shift = 0.0;
    430            rectWidth = 0.0;
    431            double widthFactor = 1.0;
    432            double seriesBarWidth = getSeriesBarWidth(row);
    433            if (!Double.isNaN(seriesBarWidth)) {
    434                widthFactor = seriesBarWidth;
    435            }
    436            rectWidth = widthFactor * state.getBarWidth();
    437            rectX = rectX + (1 - widthFactor) * state.getBarWidth() / 2.0;
    438            if (seriesCount > 1) {
    439                // needs to be improved !!!
    440                shift = rectWidth * 0.20 / (seriesCount - 1);
    441            }
    442
    443            Rectangle2D bar = new Rectangle2D.Double(
    444                (rectX + ((seriesCount - 1 - row) * shift)), rectY,
    445                (rectWidth - (seriesCount - 1 - row) * shift * 2), rectHeight);
    446            Paint itemPaint = getItemPaint(row, column);
    447            GradientPaintTransformer t = getGradientPaintTransformer();
    448            if (t != null && itemPaint instanceof GradientPaint) {
    449                itemPaint = t.transform((GradientPaint) itemPaint, bar);
    450            }
    451            g2.setPaint(itemPaint);
    452            g2.fill(bar);
    453
    454            // draw the outline...
    455            if (isDrawBarOutline()
    456                    && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
    457                Stroke stroke = getItemOutlineStroke(row, column);
    458                Paint paint = getItemOutlinePaint(row, column);
    459                if (stroke != null && paint != null) {
    460                    g2.setStroke(stroke);
    461                    g2.setPaint(paint);
    462                    g2.draw(bar);
    463                }
    464            }
    465
    466            // draw the item labels if there are any...
    467            double transX1 = rangeAxis.valueToJava2D(base, dataArea, edge);
    468            double transX2 = rangeAxis.valueToJava2D(value, dataArea, edge);
    469
    470            CategoryItemLabelGenerator generator
    471                = getItemLabelGenerator(row, column);
    472            if (generator != null && isItemLabelVisible(row, column)) {
    473                drawItemLabel(g2, data, row, column, plot, generator, bar,
    474                        (transX1 > transX2));
    475            }
    476
    477            // collect entity and tool tip information...
    478            if (state.getInfo() != null) {
    479                EntityCollection entities = state.getEntityCollection();
    480                if (entities != null) {
    481                    String tip = null;
    482                    CategoryToolTipGenerator tipster
    483                        = getToolTipGenerator(row, column);
    484                    if (tipster != null) {
    485                        tip = tipster.generateToolTip(data, row, column);
    486                    }
    487                    String url = null;
    488                    if (getItemURLGenerator(row, column) != null) {
    489                        url = getItemURLGenerator(row, column).generateURL(
    490                            data, row, column);
    491                    }
    492                    CategoryItemEntity entity = new CategoryItemEntity(bar, tip,
    493                            url, data, data.getRowKey(row),
    494                            data.getColumnKey(column));
    495                    entities.add(entity);
    496                }
    497            }
    498        }
    499
    500    }