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