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     * LevelRenderer.java
029     * ------------------
030     * (C) Copyright 2004-2008, by Object Refinery Limited.
031     *
032     * Original Author:  David Gilbert (for Object Refinery Limited);
033     * Contributor(s):   -;
034     *
035     * Changes
036     * -------
037     * 09-Jan-2004 : Version 1 (DG);
038     * 05-Nov-2004 : Modified drawItem() signature (DG);
039     * 20-Apr-2005 : Renamed CategoryLabelGenerator
040     *               --> CategoryItemLabelGenerator (DG);
041     * ------------- JFREECHART 1.0.x ---------------------------------------------
042     * 23-Jan-2006 : Renamed getMaxItemWidth() --> getMaximumItemWidth() (DG);
043     * 13-May-2008 : Code clean-up (DG);
044     *
045     */
046    
047    package org.jfree.chart.renderer.category;
048    
049    import java.awt.Graphics2D;
050    import java.awt.Paint;
051    import java.awt.Stroke;
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.entity.EntityCollection;
059    import org.jfree.chart.event.RendererChangeEvent;
060    import org.jfree.chart.labels.CategoryItemLabelGenerator;
061    import org.jfree.chart.plot.CategoryPlot;
062    import org.jfree.chart.plot.PlotOrientation;
063    import org.jfree.chart.plot.PlotRenderingInfo;
064    import org.jfree.data.category.CategoryDataset;
065    import org.jfree.ui.RectangleEdge;
066    import org.jfree.util.PublicCloneable;
067    
068    /**
069     * A {@link CategoryItemRenderer} that draws individual data items as
070     * horizontal lines, spaced in the same way as bars in a bar chart.
071     */
072    public class LevelRenderer extends AbstractCategoryItemRenderer
073            implements Cloneable, PublicCloneable, Serializable {
074    
075        /** For serialization. */
076        private static final long serialVersionUID = -8204856624355025117L;
077    
078        /** The default item margin percentage. */
079        public static final double DEFAULT_ITEM_MARGIN = 0.20;
080    
081        /** The margin between items within a category. */
082        private double itemMargin;
083    
084        /** The maximum item width as a percentage of the available space. */
085        private double maxItemWidth;
086    
087        /**
088         * Creates a new renderer with default settings.
089         */
090        public LevelRenderer() {
091            super();
092            this.itemMargin = DEFAULT_ITEM_MARGIN;
093            this.maxItemWidth = 1.0;  // 100 percent, so it will not apply unless
094                                      // changed
095        }
096    
097        /**
098         * Returns the item margin.
099         *
100         * @return The margin.
101         *
102         * @see #setItemMargin(double)
103         */
104        public double getItemMargin() {
105            return this.itemMargin;
106        }
107    
108        /**
109         * Sets the item margin and sends a {@link RendererChangeEvent} to all
110         * registered listeners.  The value is expressed as a percentage of the
111         * available width for plotting all the bars, with the resulting amount to
112         * be distributed between all the bars evenly.
113         *
114         * @param percent  the new margin.
115         *
116         * @see #getItemMargin()
117         */
118        public void setItemMargin(double percent) {
119            this.itemMargin = percent;
120            fireChangeEvent();
121        }
122    
123        /**
124         * Returns the maximum width, as a percentage of the available drawing
125         * space.
126         *
127         * @return The maximum width.
128         *
129         * @see #setMaximumItemWidth(double)
130         */
131        public double getMaximumItemWidth() {
132            return getMaxItemWidth();
133        }
134    
135        /**
136         * Sets the maximum item width, which is specified as a percentage of the
137         * available space for all items, and sends a {@link RendererChangeEvent}
138         * to all registered listeners.
139         *
140         * @param percent  the percent.
141         *
142         * @see #getMaximumItemWidth()
143         */
144        public void setMaximumItemWidth(double percent) {
145            setMaxItemWidth(percent);
146        }
147    
148        /**
149         * Initialises the renderer and returns a state object that will be passed
150         * to subsequent calls to the drawItem method.
151         * <p>
152         * This method gets called once at the start of the process of drawing a
153         * chart.
154         *
155         * @param g2  the graphics device.
156         * @param dataArea  the area in which the data is to be plotted.
157         * @param plot  the plot.
158         * @param rendererIndex  the renderer index.
159         * @param info  collects chart rendering information for return to caller.
160         *
161         * @return The renderer state.
162         */
163        public CategoryItemRendererState initialise(Graphics2D g2,
164                Rectangle2D dataArea, CategoryPlot plot, int rendererIndex,
165                PlotRenderingInfo info) {
166    
167            CategoryItemRendererState state = super.initialise(g2, dataArea, plot,
168                    rendererIndex, info);
169            calculateItemWidth(plot, dataArea, rendererIndex, state);
170            return state;
171    
172        }
173    
174        /**
175         * Calculates the bar width and stores it in the renderer state.
176         *
177         * @param plot  the plot.
178         * @param dataArea  the data area.
179         * @param rendererIndex  the renderer index.
180         * @param state  the renderer state.
181         */
182        protected void calculateItemWidth(CategoryPlot plot,
183                Rectangle2D dataArea, int rendererIndex,
184                CategoryItemRendererState state) {
185    
186            CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex);
187            CategoryDataset dataset = plot.getDataset(rendererIndex);
188            if (dataset != null) {
189                int columns = dataset.getColumnCount();
190                int rows = dataset.getRowCount();
191                double space = 0.0;
192                PlotOrientation orientation = plot.getOrientation();
193                if (orientation == PlotOrientation.HORIZONTAL) {
194                    space = dataArea.getHeight();
195                }
196                else if (orientation == PlotOrientation.VERTICAL) {
197                    space = dataArea.getWidth();
198                }
199                double maxWidth = space * getMaximumItemWidth();
200                double categoryMargin = 0.0;
201                double currentItemMargin = 0.0;
202                if (columns > 1) {
203                    categoryMargin = domainAxis.getCategoryMargin();
204                }
205                if (rows > 1) {
206                    currentItemMargin = getItemMargin();
207                }
208                double used = space * (1 - domainAxis.getLowerMargin()
209                                         - domainAxis.getUpperMargin()
210                                         - categoryMargin - currentItemMargin);
211                if ((rows * columns) > 0) {
212                    state.setBarWidth(Math.min(used / (rows * columns), maxWidth));
213                }
214                else {
215                    state.setBarWidth(Math.min(used, maxWidth));
216                }
217            }
218        }
219    
220        /**
221         * Calculates the coordinate of the first "side" of a bar.  This will be
222         * the minimum x-coordinate for a vertical bar, and the minimum
223         * y-coordinate for a horizontal bar.
224         *
225         * @param plot  the plot.
226         * @param orientation  the plot orientation.
227         * @param dataArea  the data area.
228         * @param domainAxis  the domain axis.
229         * @param state  the renderer state (has the bar width precalculated).
230         * @param row  the row index.
231         * @param column  the column index.
232         *
233         * @return The coordinate.
234         */
235        protected double calculateBarW0(CategoryPlot plot,
236                                        PlotOrientation orientation,
237                                        Rectangle2D dataArea,
238                                        CategoryAxis domainAxis,
239                                        CategoryItemRendererState state,
240                                        int row,
241                                        int column) {
242            // calculate bar width...
243            double space = 0.0;
244            if (orientation == PlotOrientation.HORIZONTAL) {
245                space = dataArea.getHeight();
246            }
247            else {
248                space = dataArea.getWidth();
249            }
250            double barW0 = domainAxis.getCategoryStart(column, getColumnCount(),
251                    dataArea, plot.getDomainAxisEdge());
252            int seriesCount = getRowCount();
253            int categoryCount = getColumnCount();
254            if (seriesCount > 1) {
255                double seriesGap = space * getItemMargin()
256                        / (categoryCount * (seriesCount - 1));
257                double seriesW = calculateSeriesWidth(space, domainAxis,
258                        categoryCount, seriesCount);
259                barW0 = barW0 + row * (seriesW + seriesGap)
260                              + (seriesW / 2.0) - (state.getBarWidth() / 2.0);
261            }
262            else {
263                barW0 = domainAxis.getCategoryMiddle(column, getColumnCount(),
264                        dataArea, plot.getDomainAxisEdge()) - state.getBarWidth()
265                        / 2.0;
266            }
267            return barW0;
268        }
269    
270        /**
271         * Draws the bar for a single (series, category) data item.
272         *
273         * @param g2  the graphics device.
274         * @param state  the renderer state.
275         * @param dataArea  the data area.
276         * @param plot  the plot.
277         * @param domainAxis  the domain axis.
278         * @param rangeAxis  the range axis.
279         * @param dataset  the dataset.
280         * @param row  the row index (zero-based).
281         * @param column  the column index (zero-based).
282         * @param pass  the pass index.
283         */
284        public void drawItem(Graphics2D g2, CategoryItemRendererState state,
285                Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis,
286                ValueAxis rangeAxis, CategoryDataset dataset, int row, int column,
287                int pass) {
288    
289            // nothing is drawn for null values...
290            Number dataValue = dataset.getValue(row, column);
291            if (dataValue == null) {
292                return;
293            }
294    
295            double value = dataValue.doubleValue();
296    
297            PlotOrientation orientation = plot.getOrientation();
298            double barW0 = calculateBarW0(plot, orientation, dataArea, domainAxis,
299                    state, row, column);
300            RectangleEdge edge = plot.getRangeAxisEdge();
301            double barL = rangeAxis.valueToJava2D(value, dataArea, edge);
302    
303            // draw the bar...
304            Line2D line = null;
305            double x = 0.0;
306            double y = 0.0;
307            if (orientation == PlotOrientation.HORIZONTAL) {
308                x = barL;
309                y = barW0 + state.getBarWidth() / 2.0;
310                line = new Line2D.Double(barL, barW0, barL,
311                        barW0 + state.getBarWidth());
312            }
313            else {
314                x = barW0 + state.getBarWidth() / 2.0;
315                y = barL;
316                line = new Line2D.Double(barW0, barL, barW0 + state.getBarWidth(),
317                        barL);
318            }
319            Stroke itemStroke = getItemStroke(row, column);
320            Paint itemPaint = getItemPaint(row, column);
321            g2.setStroke(itemStroke);
322            g2.setPaint(itemPaint);
323            g2.draw(line);
324    
325            CategoryItemLabelGenerator generator = getItemLabelGenerator(row,
326                    column);
327            if (generator != null && isItemLabelVisible(row, column)) {
328                drawItemLabel(g2, orientation, dataset, row, column, x, y,
329                        (value < 0.0));
330            }
331    
332            // add an item entity, if this information is being collected
333            EntityCollection entities = state.getEntityCollection();
334            if (entities != null) {
335                addItemEntity(entities, dataset, row, column, line.getBounds());
336            }
337    
338        }
339    
340        /**
341         * Calculates the available space for each series.
342         *
343         * @param space  the space along the entire axis (in Java2D units).
344         * @param axis  the category axis.
345         * @param categories  the number of categories.
346         * @param series  the number of series.
347         *
348         * @return The width of one series.
349         */
350        protected double calculateSeriesWidth(double space, CategoryAxis axis,
351                                              int categories, int series) {
352            double factor = 1.0 - getItemMargin() - axis.getLowerMargin()
353                            - axis.getUpperMargin();
354            if (categories > 1) {
355                factor = factor - axis.getCategoryMargin();
356            }
357            return (space * factor) / (categories * series);
358        }
359    
360        /**
361         * Tests an object for equality with this instance.
362         *
363         * @param obj  the object (<code>null</code> permitted).
364         *
365         * @return A boolean.
366         */
367        public boolean equals(Object obj) {
368            if (obj == this) {
369                return true;
370            }
371            if (!(obj instanceof LevelRenderer)) {
372                return false;
373            }
374            LevelRenderer that = (LevelRenderer) obj;
375            if (this.itemMargin != that.itemMargin) {
376                return false;
377            }
378            if (this.maxItemWidth != that.maxItemWidth) {
379                return false;
380            }
381            return super.equals(obj);
382        }
383    
384        /**
385         * Returns the maximum width, as a percentage of the available drawing
386         * space.
387         *
388         * @return The maximum width.
389         *
390         * @deprecated Use {@link #getMaximumItemWidth()} instead.
391         */
392        public double getMaxItemWidth() {
393            return this.maxItemWidth;
394        }
395    
396        /**
397         * Sets the maximum item width, which is specified as a percentage of the
398         * available space for all items, and sends a {@link RendererChangeEvent}
399         * to all registered listeners.
400         *
401         * @param percent  the percent.
402         *
403         * @deprecated Use {@link #setMaximumItemWidth(double)} instead.
404         */
405        public void setMaxItemWidth(double percent) {
406            this.maxItemWidth = percent;
407            fireChangeEvent();
408        }
409    
410    }