    001/* ===========================================================
    002 * JFreeChart : a free chart library for the Java(tm) platform
    003 * ===========================================================
    004 *
    005 * (C) Copyright 2000-2011, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    025 * Other names may be trademarks of their respective owners.]
    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
    052package org.jfree.chart.renderer.category;
    053
    054import java.awt.Graphics2D;
    055import java.awt.Paint;
    056import java.awt.Shape;
    057import java.awt.geom.Line2D;
    058import java.awt.geom.Rectangle2D;
    059import java.io.Serializable;
    060
    061import org.jfree.chart.LegendItem;
    062import org.jfree.chart.axis.CategoryAxis;
    063import org.jfree.chart.axis.ValueAxis;
    064import org.jfree.chart.entity.EntityCollection;
    065import org.jfree.chart.event.RendererChangeEvent;
    066import org.jfree.chart.plot.CategoryPlot;
    067import org.jfree.chart.plot.PlotOrientation;
    068import org.jfree.chart.plot.PlotRenderingInfo;
    069import org.jfree.chart.renderer.xy.XYStepRenderer;
    070import org.jfree.data.category.CategoryDataset;
    071import 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 */
    082public 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}