    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     * CategoryStepRenderer.java
    029     * -------------------------
    030     *
    031     * (C) Copyright 2004-2008, by Brian Cole and Contributors.
    032     *
    033     * Original Author:  Brian Cole;
    034     * Contributor(s):   David Gilbert (for Object Refinery Limited);
    035     *
    036     * Changes
    037     * -------
    038     * 21-Apr-2004 : Version 1, contributed by Brian Cole (DG);
    039     * 22-Apr-2004 : Fixed Checkstyle complaints (DG);
    040     * 05-Nov-2004 : Modified drawItem() signature (DG);
    041     * 08-Mar-2005 : Added equals() method (DG);
    042     * ------------- JFREECHART 1.0.x ---------------------------------------------
    043     * 30-Nov-2006 : Added checks for series visibility (DG);
    044     * 22-Feb-2007 : Use new state object for reusable line, enable chart entities
    045     *               (for tooltips, URLs), added new getLegendItem() override (DG);
    046     * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG);
    047     * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
    048     *
    049     */
    050
    051    package org.jfree.chart.renderer.category;
    052
    053    import java.awt.Graphics2D;
    054    import java.awt.Paint;
    055    import java.awt.Shape;
    056    import java.awt.geom.Line2D;
    057    import java.awt.geom.Rectangle2D;
    058    import java.io.Serializable;
    059
    060    import org.jfree.chart.LegendItem;
    061    import org.jfree.chart.axis.CategoryAxis;
    062    import org.jfree.chart.axis.ValueAxis;
    063    import org.jfree.chart.entity.EntityCollection;
    064    import org.jfree.chart.event.RendererChangeEvent;
    065    import org.jfree.chart.plot.CategoryPlot;
    066    import org.jfree.chart.plot.PlotOrientation;
    067    import org.jfree.chart.plot.PlotRenderingInfo;
    068    import org.jfree.chart.renderer.xy.XYStepRenderer;
    069    import org.jfree.data.category.CategoryDataset;
    070    import org.jfree.util.PublicCloneable;
    071
    072    /**
    073     * A "step" renderer similar to {@link XYStepRenderer} but
    074     * that can be used with the {@link CategoryPlot} class.
    075     */
    076    public class CategoryStepRenderer extends AbstractCategoryItemRenderer
    077            implements Cloneable, PublicCloneable, Serializable {
    078
    079        /**
    080         * State information for the renderer.
    081         */
    082        protected static class State extends CategoryItemRendererState {
    083
    084            /**
    085             * A working line for re-use to avoid creating large numbers of
    086             * objects.
    087             */
    088            public Line2D line;
    089
    090            /**
    091             * Creates a new state instance.
    092             *
    093             * @param info  collects plot rendering information (<code>null</code>
    094             *              permitted).
    095             */
    096            public State(PlotRenderingInfo info) {
    097                super(info);
    098                this.line = new Line2D.Double();
    099            }
    100
    101        }
    102
    103        /** For serialization. */
    104        private static final long serialVersionUID = -5121079703118261470L;
    105
    106        /** The stagger width. */
    107        public static final int STAGGER_WIDTH = 5; // could make this configurable
    108
    109        /**
    110         * A flag that controls whether or not the steps for multiple series are
    111         * staggered.
    112         */
    113        private boolean stagger = false;
    114
    115        /**
    116         * Creates a new renderer (stagger defaults to <code>false</code>).
    117         */
    118        public CategoryStepRenderer() {
    119            this(false);
    120        }
    121
    122        /**
    123         * Creates a new renderer.
    124         *
    125         * @param stagger  should the horizontal part of the step be staggered by
    126         *                 series?
    127         */
    128        public CategoryStepRenderer(boolean stagger) {
    129            this.stagger = stagger;
    130        }
    131
    132        /**
    133         * Returns the flag that controls whether the series steps are staggered.
    134         *
    135         * @return A boolean.
    136         */
    137        public boolean getStagger() {
    138            return this.stagger;
    139        }
    140
    141        /**
    142         * Sets the flag that controls whether or not the series steps are
    143         * staggered and sends a {@link RendererChangeEvent} to all registered
    144         * listeners.
    145         *
    146         * @param shouldStagger  a boolean.
    147         */
    148        public void setStagger(boolean shouldStagger) {
    149            this.stagger = shouldStagger;
    150            fireChangeEvent();
    151        }
    152
    153        /**
    154         * Returns a legend item for a series.
    155         *
    156         * @param datasetIndex  the dataset index (zero-based).
    157         * @param series  the series index (zero-based).
    158         *
    159         * @return The legend item.
    160         */
    161        public LegendItem getLegendItem(int datasetIndex, int series) {
    162
    163            CategoryPlot p = getPlot();
    164            if (p == null) {
    165                return null;
    166            }
    167
    168            // check that a legend item needs to be displayed...
    169            if (!isSeriesVisible(series) || !isSeriesVisibleInLegend(series)) {
    170                return null;
    171            }
    172
    173            CategoryDataset dataset = p.getDataset(datasetIndex);
    174            String label = getLegendItemLabelGenerator().generateLabel(dataset,
    175                    series);
    176            String description = label;
    177            String toolTipText = null;
    178            if (getLegendItemToolTipGenerator() != null) {
    179                toolTipText = getLegendItemToolTipGenerator().generateLabel(
    180                        dataset, series);
    181            }
    182            String urlText = null;
    183            if (getLegendItemURLGenerator() != null) {
    184                urlText = getLegendItemURLGenerator().generateLabel(dataset,
    185                        series);
    186            }
    187            Shape shape = new Rectangle2D.Double(-4.0, -3.0, 8.0, 6.0);
    188            Paint paint = lookupSeriesPaint(series);
    189
    190            LegendItem item = new LegendItem(label, description, toolTipText,
    191                    urlText, shape, paint);
    192            item.setSeriesKey(dataset.getRowKey(series));
    193            item.setSeriesIndex(series);
    194            item.setDataset(dataset);
    195            item.setDatasetIndex(datasetIndex);
    196            return item;
    197        }
    198
    199        /**
    200         * Creates a new state instance.  This method is called from
    201         * {@link #initialise(Graphics2D, Rectangle2D, CategoryPlot, int,
    202         * PlotRenderingInfo)}, and we override it to ensure that the state
    203         * contains a working Line2D instance.
    204         *
    205         * @param info  the plot rendering info (<code>null</code> is permitted).
    206         *
    207         * @return A new state instance.
    208         */
    209        protected CategoryItemRendererState createState(PlotRenderingInfo info) {
    210            return new State(info);
    211        }
    212
    213        /**
    214         * Draws a line taking into account the specified orientation.
    215         * <p>
    216         * In version 1.0.5, the signature of this method was changed by the
    217         * addition of the 'state' parameter.  This is an incompatible change, but
    218         * is considered a low risk because it is unlikely that anyone has
    219         * subclassed this renderer.  If this *does* cause trouble for you, please
    220         * report it as a bug.
    221         *
    222         * @param g2  the graphics device.
    223         * @param state  the renderer state.
    224         * @param orientation  the plot orientation.
    225         * @param x0  the x-coordinate for the start of the line.
    226         * @param y0  the y-coordinate for the start of the line.
    227         * @param x1  the x-coordinate for the end of the line.
    228         * @param y1  the y-coordinate for the end of the line.
    229         */
    230        protected void drawLine(Graphics2D g2, State state,
    231                PlotOrientation orientation, double x0, double y0, double x1,
    232                double y1) {
    233
    234            if (orientation == PlotOrientation.VERTICAL) {
    235                state.line.setLine(x0, y0, x1, y1);
    236                g2.draw(state.line);
    237            }
    238            else if (orientation == PlotOrientation.HORIZONTAL) {
    239                state.line.setLine(y0, x0, y1, x1); // switch x and y
    240                g2.draw(state.line);
    241            }
    242
    243        }
    244
    245        /**
    246         * Draw a single data item.
    247         *
    248         * @param g2  the graphics device.
    249         * @param state  the renderer state.
    250         * @param dataArea  the area in which the data is drawn.
    251         * @param plot  the plot.
    252         * @param domainAxis  the domain axis.
    253         * @param rangeAxis  the range axis.
    254         * @param dataset  the dataset.
    255         * @param row  the row index (zero-based).
    256         * @param column  the column index (zero-based).
    257         * @param pass  the pass index.
    258         */
    259        public void drawItem(Graphics2D g2,
    260                             CategoryItemRendererState state,
    261                             Rectangle2D dataArea,
    262                             CategoryPlot plot,
    263                             CategoryAxis domainAxis,
    264                             ValueAxis rangeAxis,
    265                             CategoryDataset dataset,
    266                             int row,
    267                             int column,
    268                             int pass) {
    269
    270            // do nothing if item is not visible
    271            if (!getItemVisible(row, column)) {
    272                return;
    273            }
    274
    275            Number value = dataset.getValue(row, column);
    276            if (value == null) {
    277                return;
    278            }
    279            PlotOrientation orientation = plot.getOrientation();
    280
    281            // current data point...
    282            double x1s = domainAxis.getCategoryStart(column, getColumnCount(),
    283                    dataArea, plot.getDomainAxisEdge());
    284            double x1 = domainAxis.getCategoryMiddle(column, getColumnCount(),
    285                    dataArea, plot.getDomainAxisEdge());
    286            double x1e = 2 * x1 - x1s; // or: x1s + 2*(x1-x1s)
    287            double y1 = rangeAxis.valueToJava2D(value.doubleValue(), dataArea,
    288                    plot.getRangeAxisEdge());
    289            g2.setPaint(getItemPaint(row, column));
    290            g2.setStroke(getItemStroke(row, column));
    291
    292            if (column != 0) {
    293                Number previousValue = dataset.getValue(row, column - 1);
    294                if (previousValue != null) {
    295                    // previous data point...
    296                    double previous = previousValue.doubleValue();
    297                    double x0s = domainAxis.getCategoryStart(column - 1,
    298                            getColumnCount(), dataArea, plot.getDomainAxisEdge());
    299                    double x0 = domainAxis.getCategoryMiddle(column - 1,
    300                            getColumnCount(), dataArea, plot.getDomainAxisEdge());
    301                    double x0e = 2 * x0 - x0s; // or: x0s + 2*(x0-x0s)
    302                    double y0 = rangeAxis.valueToJava2D(previous, dataArea,
    303                            plot.getRangeAxisEdge());
    304                    if (getStagger()) {
    305                        int xStagger = row * STAGGER_WIDTH;
    306                        if (xStagger > (x1s - x0e)) {
    307                            xStagger = (int) (x1s - x0e);
    308                        }
    309                        x1s = x0e + xStagger;
    310                    }
    311                    drawLine(g2, (State) state, orientation, x0e, y0, x1s, y0);
    312                    // extend x0's flat bar
    313
    314                    drawLine(g2, (State) state, orientation, x1s, y0, x1s, y1);
    315                    // upright bar
    316               }
    317           }
    318           drawLine(g2, (State) state, orientation, x1s, y1, x1e, y1);
    319           // x1's flat bar
    320
    321           // draw the item labels if there are any...
    322           if (isItemLabelVisible(row, column)) {
    323                drawItemLabel(g2, orientation, dataset, row, column, x1, y1,
    324                        (value.doubleValue() < 0.0));
    325           }
    326
    327           // add an item entity, if this information is being collected
    328           EntityCollection entities = state.getEntityCollection();
    329           if (entities != null) {
    330               Rectangle2D hotspot = new Rectangle2D.Double();
    331               if (orientation == PlotOrientation.VERTICAL) {
    332                   hotspot.setRect(x1s, y1, x1e - x1s, 4.0);
    333               }
    334               else {
    335                   hotspot.setRect(y1 - 2.0, x1s, 4.0, x1e - x1s);
    336               }
    337               addItemEntity(entities, dataset, row, column, hotspot);
    338           }
    339
    340        }
    341
    342        /**
    343         * Tests this renderer for equality with an arbitrary object.
    344         *
    345         * @param obj  the object (<code>null</code> permitted).
    346         *
    347         * @return A boolean.
    348         */
    349        public boolean equals(Object obj) {
    350            if (obj == this) {
    351                return true;
    352            }
    353            if (!(obj instanceof CategoryStepRenderer)) {
    354                return false;
    355            }
    356            CategoryStepRenderer that = (CategoryStepRenderer) obj;
    357            if (this.stagger != that.stagger) {
    358                return false;
    359            }
    360            return super.equals(obj);
    361        }
    362
    363    }