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
051package org.jfree.chart.renderer.category;
052
053import java.awt.Graphics2D;
054import java.awt.Paint;
055import java.awt.Shape;
056import java.awt.geom.Line2D;
057import java.awt.geom.Rectangle2D;
058import java.io.Serializable;
059
060import org.jfree.chart.LegendItem;
061import org.jfree.chart.axis.CategoryAxis;
062import org.jfree.chart.axis.ValueAxis;
063import org.jfree.chart.entity.EntityCollection;
064import org.jfree.chart.event.RendererChangeEvent;
065import org.jfree.chart.plot.CategoryPlot;
066import org.jfree.chart.plot.PlotOrientation;
067import org.jfree.chart.plot.PlotRenderingInfo;
068import org.jfree.chart.renderer.xy.XYStepRenderer;
069import org.jfree.data.category.CategoryDataset;
070import 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 */
076public 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        notifyListeners(new RendererChangeEvent(this));
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}