    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2006, 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     * LevelRenderer.java
    029     * ------------------
    030     * (C) Copyright 2004, 2006, by Object Refinery Limited.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   -;
    034     *
    035     * $Id: LevelRenderer.java,v 1.7.2.3 2006/01/23 09:53:58 mungady Exp $
    036     *
    037     * Changes
    038     * -------
    039     * 09-Jan-2004 : Version 1 (DG);
    040     * 05-Nov-2004 : Modified drawItem() signature (DG);
    041     * 20-Apr-2005 : Renamed CategoryLabelGenerator
    042     *               --> CategoryItemLabelGenerator (DG);
    043     * ------------- JFREECHART 1.0.0 ---------------------------------------------
    044     * 23-Jan-2006 : Renamed getMaxItemWidth() --> getMaximumItemWidth() (DG);
    045     *
    046     */
    047
    048    package org.jfree.chart.renderer.category;
    049
    050    import java.awt.Graphics2D;
    051    import java.awt.Paint;
    052    import java.awt.Stroke;
    053    import java.awt.geom.Line2D;
    054    import java.awt.geom.Rectangle2D;
    055    import java.io.Serializable;
    056
    057    import org.jfree.chart.axis.CategoryAxis;
    058    import org.jfree.chart.axis.ValueAxis;
    059    import org.jfree.chart.entity.CategoryItemEntity;
    060    import org.jfree.chart.entity.EntityCollection;
    061    import org.jfree.chart.event.RendererChangeEvent;
    062    import org.jfree.chart.labels.CategoryItemLabelGenerator;
    063    import org.jfree.chart.labels.CategoryToolTipGenerator;
    064    import org.jfree.chart.plot.CategoryPlot;
    065    import org.jfree.chart.plot.PlotOrientation;
    066    import org.jfree.chart.plot.PlotRenderingInfo;
    067    import org.jfree.data.category.CategoryDataset;
    068    import org.jfree.ui.RectangleEdge;
    069    import org.jfree.util.PublicCloneable;
    070
    071    /**
    072     * A {@link CategoryItemRenderer} that draws individual data items as
    073     * horizontal lines, spaced in the same way as bars in a bar chart.
    074     */
    075    public class LevelRenderer extends AbstractCategoryItemRenderer
    076                               implements Cloneable, PublicCloneable, Serializable {
    077
    078        /** For serialization. */
    079        private static final long serialVersionUID = -8204856624355025117L;
    080
    081        /** The default item margin percentage. */
    082        public static final double DEFAULT_ITEM_MARGIN = 0.20;
    083
    084        /** The margin between items within a category. */
    085        private double itemMargin;
    086
    087        /** The maximum item width as a percentage of the available space. */
    088        private double maxItemWidth;
    089
    090        /**
    091         * Creates a new renderer with default settings.
    092         */
    093        public LevelRenderer() {
    094            super();
    095            this.itemMargin = DEFAULT_ITEM_MARGIN;
    096            this.maxItemWidth = 1.0;  // 100 percent, so it will not apply unless
    097                                      // changed
    098        }
    099
    100        /**
    101         * Returns the item margin.
    102         *
    103         * @return The margin.
    104         */
    105        public double getItemMargin() {
    106            return this.itemMargin;
    107        }
    108
    109        /**
    110         * Sets the item margin.  The value is expressed as a percentage of the
    111         * available width for plotting all the bars, with the resulting amount to
    112         * be distributed between all the bars evenly.
    113         *
    114         * @param percent  the new margin.
    115         */
    116        public void setItemMargin(double percent) {
    117            this.itemMargin = percent;
    118            notifyListeners(new RendererChangeEvent(this));
    119        }
    120
    121        /**
    122         * Returns the maximum width, as a percentage of the available drawing
    123         * space.
    124         *
    125         * @return The maximum width.
    126         *
    127         * @deprecated Use {@link #getMaximumItemWidth()} instead.
    128         */
    129        public double getMaxItemWidth() {
    130            return this.maxItemWidth;
    131        }
    132
    133        /**
    134         * Sets the maximum item width, which is specified as a percentage of the
    135         * available space for all items, and sends a {@link RendererChangeEvent}
    136         * to all registered listeners.
    137         *
    138         * @param percent  the percent.
    139         *
    140         * @deprecated Use {@link #setMaximumItemWidth(double)} instead.
    141         */
    142        public void setMaxItemWidth(double percent) {
    143            this.maxItemWidth = percent;
    144            notifyListeners(new RendererChangeEvent(this));
    145        }
    146
    147        /**
    148         * Returns the maximum width, as a percentage of the available drawing
    149         * space.
    150         *
    151         * @return The maximum width.
    152         */
    153        public double getMaximumItemWidth() {
    154            return getMaxItemWidth();
    155        }
    156
    157        /**
    158         * Sets the maximum item width, which is specified as a percentage of the
    159         * available space for all items, and sends a {@link RendererChangeEvent}
    160         * to all registered listeners.
    161         *
    162         * @param percent  the percent.
    163         */
    164        public void setMaximumItemWidth(double percent) {
    165            setMaxItemWidth(percent);
    166        }
    167
    168        /**
    169         * Initialises the renderer and returns a state object that will be passed
    170         * to subsequent calls to the drawItem method.
    171         * <p>
    172         * This method gets called once at the start of the process of drawing a
    173         * chart.
    174         *
    175         * @param g2  the graphics device.
    176         * @param dataArea  the area in which the data is to be plotted.
    177         * @param plot  the plot.
    178         * @param rendererIndex  the renderer index.
    179         * @param info  collects chart rendering information for return to caller.
    180         *
    181         * @return The renderer state.
    182         *
    183         */
    184        public CategoryItemRendererState initialise(Graphics2D g2,
    185                                                    Rectangle2D dataArea,
    186                                                    CategoryPlot plot,
    187                                                    int rendererIndex,
    188                                                    PlotRenderingInfo info) {
    189
    190            CategoryItemRendererState state = super.initialise(g2, dataArea, plot,
    191                    rendererIndex, info);
    192            calculateItemWidth(plot, dataArea, rendererIndex, state);
    193            return state;
    194
    195        }
    196
    197        /**
    198         * Calculates the bar width and stores it in the renderer state.
    199         *
    200         * @param plot  the plot.
    201         * @param dataArea  the data area.
    202         * @param rendererIndex  the renderer index.
    203         * @param state  the renderer state.
    204         */
    205        protected void calculateItemWidth(CategoryPlot plot,
    206                                          Rectangle2D dataArea,
    207                                          int rendererIndex,
    208                                          CategoryItemRendererState state) {
    209
    210            CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex);
    211            CategoryDataset dataset = plot.getDataset(rendererIndex);
    212            if (dataset != null) {
    213                int columns = dataset.getColumnCount();
    214                int rows = dataset.getRowCount();
    215                double space = 0.0;
    216                PlotOrientation orientation = plot.getOrientation();
    217                if (orientation == PlotOrientation.HORIZONTAL) {
    218                    space = dataArea.getHeight();
    219                }
    220                else if (orientation == PlotOrientation.VERTICAL) {
    221                    space = dataArea.getWidth();
    222                }
    223                double maxWidth = space * getMaxItemWidth();
    224                double categoryMargin = 0.0;
    225                double currentItemMargin = 0.0;
    226                if (columns > 1) {
    227                    categoryMargin = domainAxis.getCategoryMargin();
    228                }
    229                if (rows > 1) {
    230                    currentItemMargin = getItemMargin();
    231                }
    232                double used = space * (1 - domainAxis.getLowerMargin()
    233                                         - domainAxis.getUpperMargin()
    234                                         - categoryMargin - currentItemMargin);
    235                if ((rows * columns) > 0) {
    236                    state.setBarWidth(Math.min(used / (rows * columns), maxWidth));
    237                }
    238                else {
    239                    state.setBarWidth(Math.min(used, maxWidth));
    240                }
    241            }
    242        }
    243
    244        /**
    245         * Calculates the coordinate of the first "side" of a bar.  This will be
    246         * the minimum x-coordinate for a vertical bar, and the minimum
    247         * y-coordinate for a horizontal bar.
    248         *
    249         * @param plot  the plot.
    250         * @param orientation  the plot orientation.
    251         * @param dataArea  the data area.
    252         * @param domainAxis  the domain axis.
    253         * @param state  the renderer state (has the bar width precalculated).
    254         * @param row  the row index.
    255         * @param column  the column index.
    256         *
    257         * @return The coordinate.
    258         */
    259        protected double calculateBarW0(CategoryPlot plot,
    260                                        PlotOrientation orientation,
    261                                        Rectangle2D dataArea,
    262                                        CategoryAxis domainAxis,
    263                                        CategoryItemRendererState state,
    264                                        int row,
    265                                        int column) {
    266            // calculate bar width...
    267            double space = 0.0;
    268            if (orientation == PlotOrientation.HORIZONTAL) {
    269                space = dataArea.getHeight();
    270            }
    271            else {
    272                space = dataArea.getWidth();
    273            }
    274            double barW0 = domainAxis.getCategoryStart(column, getColumnCount(),
    275                    dataArea, plot.getDomainAxisEdge());
    276            int seriesCount = getRowCount();
    277            int categoryCount = getColumnCount();
    278            if (seriesCount > 1) {
    279                double seriesGap = space * getItemMargin()
    280                        / (categoryCount * (seriesCount - 1));
    281                double seriesW = calculateSeriesWidth(space, domainAxis,
    282                        categoryCount, seriesCount);
    283                barW0 = barW0 + row * (seriesW + seriesGap)
    284                              + (seriesW / 2.0) - (state.getBarWidth() / 2.0);
    285            }
    286            else {
    287                barW0 = domainAxis.getCategoryMiddle(column, getColumnCount(),
    288                        dataArea, plot.getDomainAxisEdge()) - state.getBarWidth()
    289                        / 2.0;
    290            }
    291            return barW0;
    292        }
    293
    294        /**
    295         * Draws the bar for a single (series, category) data item.
    296         *
    297         * @param g2  the graphics device.
    298         * @param state  the renderer state.
    299         * @param dataArea  the data area.
    300         * @param plot  the plot.
    301         * @param domainAxis  the domain axis.
    302         * @param rangeAxis  the range axis.
    303         * @param dataset  the dataset.
    304         * @param row  the row index (zero-based).
    305         * @param column  the column index (zero-based).
    306         * @param pass  the pass index.
    307         */
    308        public void drawItem(Graphics2D g2, CategoryItemRendererState state,
    309                Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis,
    310                ValueAxis rangeAxis, CategoryDataset dataset, int row, int column,
    311                int pass) {
    312
    313            // nothing is drawn for null values...
    314            Number dataValue = dataset.getValue(row, column);
    315            if (dataValue == null) {
    316                return;
    317            }
    318
    319            double value = dataValue.doubleValue();
    320
    321            PlotOrientation orientation = plot.getOrientation();
    322            double barW0 = calculateBarW0(plot, orientation, dataArea, domainAxis,
    323                    state, row, column);
    324            RectangleEdge edge = plot.getRangeAxisEdge();
    325            double barL = rangeAxis.valueToJava2D(value, dataArea, edge);
    326
    327            // draw the bar...
    328            Line2D line = null;
    329            double x = 0.0;
    330            double y = 0.0;
    331            if (orientation == PlotOrientation.HORIZONTAL) {
    332                x = barL;
    333                y = barW0 + state.getBarWidth() / 2.0;
    334                line = new Line2D.Double(barL, barW0, barL,
    335                        barW0 + state.getBarWidth());
    336            }
    337            else {
    338                x = barW0 + state.getBarWidth() / 2.0;
    339                y = barL;
    340                line = new Line2D.Double(barW0, barL, barW0 + state.getBarWidth(),
    341                        barL);
    342            }
    343            Stroke itemStroke = getItemStroke(row, column);
    344            Paint itemPaint = getItemPaint(row, column);
    345            g2.setStroke(itemStroke);
    346            g2.setPaint(itemPaint);
    347            g2.draw(line);
    348
    349            CategoryItemLabelGenerator generator = getItemLabelGenerator(row,
    350                    column);
    351            if (generator != null && isItemLabelVisible(row, column)) {
    352                drawItemLabel(g2, orientation, dataset, row, column, x, y,
    353                        (value < 0.0));
    354            }
    355
    356            // collect entity and tool tip information...
    357            if (state.getInfo() != null) {
    358                EntityCollection entities = state.getEntityCollection();
    359                if (entities != null) {
    360                    String tip = null;
    361                    CategoryToolTipGenerator tipster = getToolTipGenerator(row,
    362                            column);
    363                    if (tipster != null) {
    364                        tip = tipster.generateToolTip(dataset, row, column);
    365                    }
    366                    String url = null;
    367                    if (getItemURLGenerator(row, column) != null) {
    368                        url = getItemURLGenerator(row, column).generateURL(dataset,
    369                                row, column);
    370                    }
    371                    CategoryItemEntity entity = new CategoryItemEntity(
    372                            line.getBounds(), tip, url, dataset, row,
    373                            dataset.getColumnKey(column), column);
    374                    entities.add(entity);
    375                }
    376
    377            }
    378
    379        }
    380
    381        /**
    382         * Calculates the available space for each series.
    383         *
    384         * @param space  the space along the entire axis (in Java2D units).
    385         * @param axis  the category axis.
    386         * @param categories  the number of categories.
    387         * @param series  the number of series.
    388         *
    389         * @return The width of one series.
    390         */
    391        protected double calculateSeriesWidth(double space, CategoryAxis axis,
    392                                              int categories, int series) {
    393            double factor = 1.0 - getItemMargin() - axis.getLowerMargin()
    394                            - axis.getUpperMargin();
    395            if (categories > 1) {
    396                factor = factor - axis.getCategoryMargin();
    397            }
    398            return (space * factor) / (categories * series);
    399        }
    400
    401        /**
    402         * Tests an object for equality with this instance.
    403         *
    404         * @param obj  the object (<code>null</code> permitted).
    405         *
    406         * @return A boolean.
    407         */
    408        public boolean equals(Object obj) {
    409            if (obj == this) {
    410                return true;
    411            }
    412            if (!(obj instanceof LevelRenderer)) {
    413                return false;
    414            }
    415            if (!super.equals(obj)) {
    416                return false;
    417            }
    418            LevelRenderer that = (LevelRenderer) obj;
    419            if (this.itemMargin != that.itemMargin) {
    420                return false;
    421            }
    422            if (this.maxItemWidth != that.maxItemWidth) {
    423                return false;
    424            }
    425            return true;
    426        }
    427
    428    }