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