001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2006, 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-2006, 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.2 2006/12/01 13:57:09 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     * 
047     */
048    
049    package org.jfree.chart.renderer.category;
050    
051    import java.awt.Graphics2D;
052    import java.awt.geom.Line2D;
053    import java.awt.geom.Rectangle2D;
054    import java.io.Serializable;
055    
056    import org.jfree.chart.axis.CategoryAxis;
057    import org.jfree.chart.axis.ValueAxis;
058    import org.jfree.chart.event.RendererChangeEvent;
059    import org.jfree.chart.plot.CategoryPlot;
060    import org.jfree.chart.plot.PlotOrientation;
061    import org.jfree.chart.renderer.xy.XYStepRenderer;
062    import org.jfree.data.category.CategoryDataset;
063    import org.jfree.util.PublicCloneable;
064    
065    /**
066     * A "step" renderer similar to {@link XYStepRenderer} but
067     * that can be used with the {@link CategoryPlot} class.
068     */
069    public class CategoryStepRenderer extends AbstractCategoryItemRenderer
070                                      implements Cloneable, PublicCloneable, 
071                                                 Serializable {
072    
073        /** For serialization. */
074        private static final long serialVersionUID = -5121079703118261470L;
075        
076        /** The stagger width. */
077        public static final int STAGGER_WIDTH = 5; // could make this configurable
078      
079        /** 
080         * A flag that controls whether or not the steps for multiple series are 
081         * staggered. 
082         */
083        private boolean stagger = false;
084        
085        /** A working line - need to remove this. */
086        private transient Line2D line = new Line2D.Double(0.0, 0.0, 0.0, 0.0);
087    
088        /** 
089         * Creates a new renderer (stagger defaults to <code>false</code>).
090         */
091        public CategoryStepRenderer() {
092            this(false);
093        }
094        
095        /**
096         * Creates a new renderer.
097         *  
098         * @param stagger  should the horizontal part of the step be staggered by 
099         *                 series? 
100         */
101        public CategoryStepRenderer(boolean stagger) {
102            this.stagger = stagger;
103        }
104      
105        /**
106         * Returns the flag that controls whether the series steps are staggered.
107         * 
108         * @return A boolean.
109         */
110        public boolean getStagger() {
111            return this.stagger;
112        }
113        
114        /**
115         * Sets the flag that controls whether or not the series steps are 
116         * staggered and sends a {@link RendererChangeEvent} to all registered
117         * listeners.
118         * 
119         * @param shouldStagger  a boolean.
120         */
121        public void setStagger(boolean shouldStagger) {
122            this.stagger = shouldStagger;
123            notifyListeners(new RendererChangeEvent(this));
124        }
125       
126        /**
127         * Draws the line.
128         * 
129         * @param g2  the graphics device.
130         * @param orientation  the plot orientation.
131         * @param x0  the x-coordinate for the start of the line.
132         * @param y0  the y-coordinate for the start of the line.
133         * @param x1  the x-coordinate for the end of the line.
134         * @param y1  the y-coordinate for the end of the line.
135         */
136        protected void drawLine(Graphics2D g2, PlotOrientation orientation,
137                                double x0, double y0, double x1, double y1) {
138         
139            if (orientation == PlotOrientation.VERTICAL) {
140                this.line.setLine(x0, y0, x1, y1);
141                g2.draw(this.line);
142            }
143            else if (orientation == PlotOrientation.HORIZONTAL) {
144                this.line.setLine(y0, x0, y1, x1); // switch x and y
145                g2.draw(this.line);
146            }
147            // else unknown orientation (complain?)
148        }
149    
150        /**
151         * Draw a single data item.
152         *
153         * @param g2  the graphics device.
154         * @param state  the renderer state.
155         * @param dataArea  the area in which the data is drawn.
156         * @param plot  the plot.
157         * @param domainAxis  the domain axis.
158         * @param rangeAxis  the range axis.
159         * @param dataset  the dataset.
160         * @param row  the row index (zero-based).
161         * @param column  the column index (zero-based).
162         * @param pass  the pass index.
163         */
164        public void drawItem(Graphics2D g2,
165                             CategoryItemRendererState state,
166                             Rectangle2D dataArea,
167                             CategoryPlot plot,
168                             CategoryAxis domainAxis,
169                             ValueAxis rangeAxis,
170                             CategoryDataset dataset,
171                             int row,
172                             int column,
173                             int pass) {
174    
175            // do nothing if item is not visible
176            if (!getItemVisible(row, column)) {
177                return;   
178            }
179    
180            Number value = dataset.getValue(row, column);
181            if (value == null) {
182                return;
183            }
184            PlotOrientation orientation = plot.getOrientation();
185    
186            // current data point...
187            double x1s = domainAxis.getCategoryStart(
188                column, getColumnCount(), dataArea, plot.getDomainAxisEdge()
189            );
190            double x1 = domainAxis.getCategoryMiddle(
191                column, getColumnCount(), dataArea, plot.getDomainAxisEdge()
192            );
193            double x1e = 2 * x1 - x1s; // or: x1s + 2*(x1-x1s)
194            double y1 = rangeAxis.valueToJava2D(
195                value.doubleValue(), dataArea, plot.getRangeAxisEdge()
196            );
197            g2.setPaint(getItemPaint(row, column));
198            g2.setStroke(getItemStroke(row, column));
199    
200            if (column != 0) {
201                Number previousValue = dataset.getValue(row, column - 1);
202                if (previousValue != null) {
203                    // previous data point...
204                    double previous = previousValue.doubleValue();
205                    double x0s = domainAxis.getCategoryStart(
206                        column - 1, getColumnCount(), dataArea, 
207                        plot.getDomainAxisEdge()
208                    );
209                    double x0 = domainAxis.getCategoryMiddle(
210                        column - 1, getColumnCount(), dataArea, 
211                        plot.getDomainAxisEdge()
212                    );
213                    double x0e = 2 * x0 - x0s; // or: x0s + 2*(x0-x0s)
214                    double y0 = rangeAxis.valueToJava2D(
215                        previous, dataArea, plot.getRangeAxisEdge()
216                    );
217                    if (getStagger()) {
218                        int xStagger = row * STAGGER_WIDTH;
219                        if (xStagger > (x1s - x0e)) {
220                            xStagger = (int) (x1s - x0e);
221                        }
222                        x1s = x0e + xStagger;
223                    }
224                    drawLine(g2, orientation, x0e, y0, x1s, y0); 
225                        // extend x0's flat bar
226    
227                    drawLine(g2, orientation, x1s, y0, x1s, y1); // upright bar
228               }
229           }
230           drawLine(g2, orientation, x1s, y1, x1e, y1); // x1's flat bar
231    
232           // draw the item labels if there are any...
233           if (isItemLabelVisible(row, column)) {
234                drawItemLabel(
235                    g2, orientation, dataset, row, column, x1, y1, 
236                    (value.doubleValue() < 0.0)
237                );
238           }
239        /* This is how LineAndShapeRenderer.drawItem() handles tips and URLs, but
240           I omit it due to time pressure. It shouldn't be hard to put back
241     in.
242    
243           // collect entity and tool tip information...
244           if (state.getInfo() != null) {
245               EntityCollection entities =
246     state.getInfo().getOwner().getEntityCollection();
247               if (entities != null && shape != null) {
248                   String tip = null;
249                   CategoryItemLabelGenerator generator =
250     getItemLabelGenerator(row, column);
251                   if (generator != null) {
252                       tip = generator.generateToolTip(dataset, row, column);
253                   }
254                   String url = null;
255                   if (getItemURLGenerator(row, column) != null)                    
256                   url = getItemURLGenerator(row, column).generateURL(dataset, row, 
257                     column);
258                   }
259                   CategoryItemEntity entity = new CategoryItemEntity(
260                       shape, tip, url, dataset, row,
261     dataset.getColumnKey(column), column);
262                   entities.addEntity(entity);
263               }
264           }
265        */
266    
267        }
268        
269        /**
270         * Tests this renderer for equality with an arbitrary object.
271         * 
272         * @param obj  the object (<code>null</code> permitted).
273         * 
274         * @return A boolean.
275         */
276        public boolean equals(Object obj) {
277            if (obj == this) {
278                return true;   
279            }
280            if (!(obj instanceof CategoryStepRenderer)) {
281                return false;   
282            }
283            if (!super.equals(obj)) {
284                return false;   
285            }
286            CategoryStepRenderer that = (CategoryStepRenderer) obj;
287            if (this.stagger != that.stagger) {
288                return false;   
289            }
290            return true;
291        }
292    
293    }