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