    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
    054package org.jfree.chart.renderer.category;
    055
    056import java.awt.GradientPaint;
    057import java.awt.Graphics2D;
    058import java.awt.Paint;
    059import java.awt.Stroke;
    060import java.awt.geom.Rectangle2D;
    061import java.io.Serializable;
    062
    063import org.jfree.chart.axis.CategoryAxis;
    064import org.jfree.chart.axis.ValueAxis;
    065import org.jfree.chart.entity.EntityCollection;
    066import org.jfree.chart.labels.CategoryItemLabelGenerator;
    067import org.jfree.chart.plot.CategoryPlot;
    068import org.jfree.chart.plot.PlotOrientation;
    069import org.jfree.data.category.CategoryDataset;
    070import org.jfree.ui.GradientPaintTransformer;
    071import org.jfree.ui.RectangleEdge;
    072import 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 */
    083public 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}