001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2014, 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-2014, 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    @Override
169    public LegendItem getLegendItem(int datasetIndex, int series) {
170
171        CategoryPlot p = getPlot();
172        if (p == null) {
173            return null;
174        }
175
176        // check that a legend item needs to be displayed...
177        if (!isSeriesVisible(series) || !isSeriesVisibleInLegend(series)) {
178            return null;
179        }
180
181        CategoryDataset dataset = p.getDataset(datasetIndex);
182        String label = getLegendItemLabelGenerator().generateLabel(dataset,
183                series);
184        String description = label;
185        String toolTipText = null;
186        if (getLegendItemToolTipGenerator() != null) {
187            toolTipText = getLegendItemToolTipGenerator().generateLabel(
188                    dataset, series);
189        }
190        String urlText = null;
191        if (getLegendItemURLGenerator() != null) {
192            urlText = getLegendItemURLGenerator().generateLabel(dataset,
193                    series);
194        }
195        Shape shape = lookupLegendShape(series);
196        Paint paint = lookupSeriesPaint(series);
197
198        LegendItem item = new LegendItem(label, description, toolTipText,
199                urlText, shape, paint);
200        item.setLabelFont(lookupLegendTextFont(series));
201        Paint labelPaint = lookupLegendTextPaint(series);
202        if (labelPaint != null) {
203            item.setLabelPaint(labelPaint);
204        }
205        item.setSeriesKey(dataset.getRowKey(series));
206        item.setSeriesIndex(series);
207        item.setDataset(dataset);
208        item.setDatasetIndex(datasetIndex);
209        return item;
210    }
211
212    /**
213     * Creates a new state instance.  This method is called from
214     * {@link #initialise(Graphics2D, Rectangle2D, CategoryPlot, int,
215     * PlotRenderingInfo)}, and we override it to ensure that the state
216     * contains a working Line2D instance.
217     *
218     * @param info  the plot rendering info (<code>null</code> is permitted).
219     *
220     * @return A new state instance.
221     */
222    @Override
223    protected CategoryItemRendererState createState(PlotRenderingInfo info) {
224        return new State(info);
225    }
226
227    /**
228     * Draws a line taking into account the specified orientation.
229     * <p>
230     * In version 1.0.5, the signature of this method was changed by the
231     * addition of the 'state' parameter.  This is an incompatible change, but
232     * is considered a low risk because it is unlikely that anyone has
233     * subclassed this renderer.  If this *does* cause trouble for you, please
234     * report it as a bug.
235     *
236     * @param g2  the graphics device.
237     * @param state  the renderer state.
238     * @param orientation  the plot orientation.
239     * @param x0  the x-coordinate for the start of the line.
240     * @param y0  the y-coordinate for the start of the line.
241     * @param x1  the x-coordinate for the end of the line.
242     * @param y1  the y-coordinate for the end of the line.
243     */
244    protected void drawLine(Graphics2D g2, State state,
245            PlotOrientation orientation, double x0, double y0, double x1,
246            double y1) {
247
248        if (orientation == PlotOrientation.VERTICAL) {
249            state.line.setLine(x0, y0, x1, y1);
250            g2.draw(state.line);
251        }
252        else if (orientation == PlotOrientation.HORIZONTAL) {
253            state.line.setLine(y0, x0, y1, x1); // switch x and y
254            g2.draw(state.line);
255        }
256
257    }
258
259    /**
260     * Draw a single data item.
261     *
262     * @param g2  the graphics device.
263     * @param state  the renderer state.
264     * @param dataArea  the area in which the data is drawn.
265     * @param plot  the plot.
266     * @param domainAxis  the domain axis.
267     * @param rangeAxis  the range axis.
268     * @param dataset  the dataset.
269     * @param row  the row index (zero-based).
270     * @param column  the column index (zero-based).
271     * @param pass  the pass index.
272     */
273    @Override
274    public void drawItem(Graphics2D g2, CategoryItemRendererState state,
275            Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis,
276            ValueAxis rangeAxis, CategoryDataset dataset, int row,
277            int column, int pass) {
278
279        // do nothing if item is not visible
280        if (!getItemVisible(row, column)) {
281            return;
282        }
283
284        Number value = dataset.getValue(row, column);
285        if (value == null) {
286            return;
287        }
288        PlotOrientation orientation = plot.getOrientation();
289
290        // current data point...
291        double x1s = domainAxis.getCategoryStart(column, getColumnCount(),
292                dataArea, plot.getDomainAxisEdge());
293        double x1 = domainAxis.getCategoryMiddle(column, getColumnCount(),
294                dataArea, plot.getDomainAxisEdge());
295        double x1e = 2 * x1 - x1s; // or: x1s + 2*(x1-x1s)
296        double y1 = rangeAxis.valueToJava2D(value.doubleValue(), dataArea,
297                plot.getRangeAxisEdge());
298        g2.setPaint(getItemPaint(row, column));
299        g2.setStroke(getItemStroke(row, column));
300
301        if (column != 0) {
302            Number previousValue = dataset.getValue(row, column - 1);
303            if (previousValue != null) {
304                // previous data point...
305                double previous = previousValue.doubleValue();
306                double x0s = domainAxis.getCategoryStart(column - 1,
307                        getColumnCount(), dataArea, plot.getDomainAxisEdge());
308                double x0 = domainAxis.getCategoryMiddle(column - 1,
309                        getColumnCount(), dataArea, plot.getDomainAxisEdge());
310                double x0e = 2 * x0 - x0s; // or: x0s + 2*(x0-x0s)
311                double y0 = rangeAxis.valueToJava2D(previous, dataArea,
312                        plot.getRangeAxisEdge());
313                if (getStagger()) {
314                    int xStagger = row * STAGGER_WIDTH;
315                    if (xStagger > (x1s - x0e)) {
316                        xStagger = (int) (x1s - x0e);
317                    }
318                    x1s = x0e + xStagger;
319                }
320                drawLine(g2, (State) state, orientation, x0e, y0, x1s, y0);
321                // extend x0's flat bar
322
323                drawLine(g2, (State) state, orientation, x1s, y0, x1s, y1);
324                // upright bar
325           }
326       }
327       drawLine(g2, (State) state, orientation, x1s, y1, x1e, y1);
328       // x1's flat bar
329
330       // draw the item labels if there are any...
331       if (isItemLabelVisible(row, column)) {
332            drawItemLabel(g2, orientation, dataset, row, column, x1, y1,
333                    (value.doubleValue() < 0.0));
334       }
335
336       // add an item entity, if this information is being collected
337       EntityCollection entities = state.getEntityCollection();
338       if (entities != null) {
339           Rectangle2D hotspot = new Rectangle2D.Double();
340           if (orientation == PlotOrientation.VERTICAL) {
341               hotspot.setRect(x1s, y1, x1e - x1s, 4.0);
342           }
343           else {
344               hotspot.setRect(y1 - 2.0, x1s, 4.0, x1e - x1s);
345           }
346           addItemEntity(entities, dataset, row, column, hotspot);
347       }
348
349    }
350
351    /**
352     * Tests this renderer for equality with an arbitrary object.
353     *
354     * @param obj  the object (<code>null</code> permitted).
355     *
356     * @return A boolean.
357     */
358    @Override
359    public boolean equals(Object obj) {
360        if (obj == this) {
361            return true;
362        }
363        if (!(obj instanceof CategoryStepRenderer)) {
364            return false;
365        }
366        CategoryStepRenderer that = (CategoryStepRenderer) obj;
367        if (this.stagger != that.stagger) {
368            return false;
369        }
370        return super.equals(obj);
371    }
372
373}