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