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