001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2005, 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, 2005, 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.1 2005/10/25 20:54:16 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     * 
045     */
046    
047    package org.jfree.chart.renderer.category;
048    
049    import java.awt.Graphics2D;
050    import java.awt.geom.Line2D;
051    import java.awt.geom.Rectangle2D;
052    import java.io.Serializable;
053    
054    import org.jfree.chart.axis.CategoryAxis;
055    import org.jfree.chart.axis.ValueAxis;
056    import org.jfree.chart.event.RendererChangeEvent;
057    import org.jfree.chart.plot.CategoryPlot;
058    import org.jfree.chart.plot.PlotOrientation;
059    import org.jfree.chart.renderer.xy.XYStepRenderer;
060    import org.jfree.data.category.CategoryDataset;
061    import org.jfree.util.PublicCloneable;
062    
063    /**
064     * A "step" renderer similar to {@link XYStepRenderer} but
065     * that can be used with the {@link CategoryPlot} class.
066     *
067     * @author Brian Cole
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            Number value = dataset.getValue(row, column);
176            if (value == null) {
177                return;
178            }
179            PlotOrientation orientation = plot.getOrientation();
180    
181            // current data point...
182            double x1s = domainAxis.getCategoryStart(
183                column, getColumnCount(), dataArea, plot.getDomainAxisEdge()
184            );
185            double x1 = domainAxis.getCategoryMiddle(
186                column, getColumnCount(), dataArea, plot.getDomainAxisEdge()
187            );
188            double x1e = 2 * x1 - x1s; // or: x1s + 2*(x1-x1s)
189            double y1 = rangeAxis.valueToJava2D(
190                value.doubleValue(), dataArea, plot.getRangeAxisEdge()
191            );
192            g2.setPaint(getItemPaint(row, column));
193            g2.setStroke(getItemStroke(row, column));
194    
195            if (column != 0) {
196                Number previousValue = dataset.getValue(row, column - 1);
197                if (previousValue != null) {
198                    // previous data point...
199                    double previous = previousValue.doubleValue();
200                    double x0s = domainAxis.getCategoryStart(
201                        column - 1, getColumnCount(), dataArea, 
202                        plot.getDomainAxisEdge()
203                    );
204                    double x0 = domainAxis.getCategoryMiddle(
205                        column - 1, getColumnCount(), dataArea, 
206                        plot.getDomainAxisEdge()
207                    );
208                    double x0e = 2 * x0 - x0s; // or: x0s + 2*(x0-x0s)
209                    double y0 = rangeAxis.valueToJava2D(
210                        previous, dataArea, plot.getRangeAxisEdge()
211                    );
212                    if (getStagger()) {
213                        int xStagger = row * STAGGER_WIDTH;
214                        if (xStagger > (x1s - x0e)) {
215                            xStagger = (int) (x1s - x0e);
216                        }
217                        x1s = x0e + xStagger;
218                    }
219                    drawLine(g2, orientation, x0e, y0, x1s, y0); 
220                        // extend x0's flat bar
221    
222                    drawLine(g2, orientation, x1s, y0, x1s, y1); // upright bar
223               }
224           }
225           drawLine(g2, orientation, x1s, y1, x1e, y1); // x1's flat bar
226    
227           // draw the item labels if there are any...
228           if (isItemLabelVisible(row, column)) {
229                drawItemLabel(
230                    g2, orientation, dataset, row, column, x1, y1, 
231                    (value.doubleValue() < 0.0)
232                );
233           }
234        /* This is how LineAndShapeRenderer.drawItem() handles tips and URLs, but
235           I omit it due to time pressure. It shouldn't be hard to put back
236     in.
237    
238           // collect entity and tool tip information...
239           if (state.getInfo() != null) {
240               EntityCollection entities =
241     state.getInfo().getOwner().getEntityCollection();
242               if (entities != null && shape != null) {
243                   String tip = null;
244                   CategoryItemLabelGenerator generator =
245     getItemLabelGenerator(row, column);
246                   if (generator != null) {
247                       tip = generator.generateToolTip(dataset, row, column);
248                   }
249                   String url = null;
250                   if (getItemURLGenerator(row, column) != null)                    
251                   url = getItemURLGenerator(row, column).generateURL(dataset, row, 
252                     column);
253                   }
254                   CategoryItemEntity entity = new CategoryItemEntity(
255                       shape, tip, url, dataset, row,
256     dataset.getColumnKey(column), column);
257                   entities.addEntity(entity);
258               }
259           }
260        */
261    
262        }
263        
264        /**
265         * Tests this renderer for equality with an arbitrary object.
266         * 
267         * @param obj  the object (<code>null</code> permitted).
268         * 
269         * @return A boolean.
270         */
271        public boolean equals(Object obj) {
272            if (obj == this) {
273                return true;   
274            }
275            if (!(obj instanceof CategoryStepRenderer)) {
276                return false;   
277            }
278            if (!super.equals(obj)) {
279                return false;   
280            }
281            CategoryStepRenderer that = (CategoryStepRenderer) obj;
282            if (this.stagger != that.stagger) {
283                return false;   
284            }
285            return true;
286        }
287    
288    }