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