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