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