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