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