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     * LevelRenderer.java
029     * ------------------
030     * (C) Copyright 2004, by Object Refinery Limited.
031     *
032     * Original Author:  David Gilbert (for Object Refinery Limited);
033     * Contributor(s):   -;
034     *
035     * $Id: LevelRenderer.java,v 1.7.2.2 2005/12/02 09:56:00 mungady Exp $
036     *
037     * Changes
038     * -------
039     * 09-Jan-2004 : Version 1 (DG);
040     * 05-Nov-2004 : Modified drawItem() signature (DG);
041     * 20-Apr-2005 : Renamed CategoryLabelGenerator 
042     *               --> CategoryItemLabelGenerator (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.  The value is expressed as a percentage of the 
109         * available width for plotting all the bars, with the resulting amount to 
110         * be distributed between all the bars evenly.
111         *
112         * @param percent  the new margin.
113         */
114        public void setItemMargin(double percent) {
115            this.itemMargin = percent;
116            notifyListeners(new RendererChangeEvent(this));
117        }
118        
119        /**
120         * Returns the maximum width, as a percentage of the available drawing 
121         * space.
122         * 
123         * @return The maximum width.
124         */
125        public double getMaxItemWidth() {
126            return this.maxItemWidth;
127        }
128        
129        /**
130         * Sets the maximum item width, which is specified as a percentage of the 
131         * available space for all items, and sends a {@link RendererChangeEvent} 
132         * to all registered listeners.
133         * 
134         * @param percent  the percent.
135         */
136        public void setMaxItemWidth(double percent) {
137            this.maxItemWidth = percent;
138            notifyListeners(new RendererChangeEvent(this));
139        }
140    
141        /**
142         * Initialises the renderer and returns a state object that will be passed 
143         * to subsequent calls to the drawItem method.
144         * <p>
145         * This method gets called once at the start of the process of drawing a 
146         * chart.
147         *
148         * @param g2  the graphics device.
149         * @param dataArea  the area in which the data is to be plotted.
150         * @param plot  the plot.
151         * @param rendererIndex  the renderer index.
152         * @param info  collects chart rendering information for return to caller.
153         * 
154         * @return The renderer state.
155         *
156         */
157        public CategoryItemRendererState initialise(Graphics2D g2,
158                                                    Rectangle2D dataArea,
159                                                    CategoryPlot plot,
160                                                    int rendererIndex,
161                                                    PlotRenderingInfo info) {
162    
163            CategoryItemRendererState state = super.initialise(
164                g2, dataArea, plot, rendererIndex, info
165            );
166            calculateItemWidth(plot, dataArea, rendererIndex, state);
167            return state;
168            
169        }
170        
171        /**
172         * Calculates the bar width and stores it in the renderer state.
173         * 
174         * @param plot  the plot.
175         * @param dataArea  the data area.
176         * @param rendererIndex  the renderer index.
177         * @param state  the renderer state.
178         */
179        protected void calculateItemWidth(CategoryPlot plot, 
180                                          Rectangle2D dataArea, 
181                                          int rendererIndex,
182                                          CategoryItemRendererState state) {
183                                             
184            CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex);
185            CategoryDataset dataset = plot.getDataset(rendererIndex);
186            if (dataset != null) {
187                int columns = dataset.getColumnCount();
188                int rows = dataset.getRowCount();
189                double space = 0.0;
190                PlotOrientation orientation = plot.getOrientation();
191                if (orientation == PlotOrientation.HORIZONTAL) {
192                    space = dataArea.getHeight();
193                }
194                else if (orientation == PlotOrientation.VERTICAL) {
195                    space = dataArea.getWidth();
196                }
197                double maxWidth = space * getMaxItemWidth();
198                double categoryMargin = 0.0;
199                double currentItemMargin = 0.0;
200                if (columns > 1) {
201                    categoryMargin = domainAxis.getCategoryMargin();
202                }
203                if (rows > 1) {
204                    currentItemMargin = getItemMargin();
205                }
206                double used = space * (1 - domainAxis.getLowerMargin() 
207                                         - domainAxis.getUpperMargin()
208                                         - categoryMargin - currentItemMargin);
209                if ((rows * columns) > 0) {
210                    state.setBarWidth(Math.min(used / (rows * columns), maxWidth));
211                }
212                else {
213                    state.setBarWidth(Math.min(used, maxWidth));
214                }
215            }
216        }
217    
218        /**
219         * Calculates the coordinate of the first "side" of a bar.  This will be 
220         * the minimum x-coordinate for a vertical bar, and the minimum 
221         * y-coordinate for a horizontal bar.
222         * 
223         * @param plot  the plot.
224         * @param orientation  the plot orientation.
225         * @param dataArea  the data area.
226         * @param domainAxis  the domain axis.
227         * @param state  the renderer state (has the bar width precalculated).
228         * @param row  the row index.
229         * @param column  the column index.
230         * 
231         * @return The coordinate.
232         */
233        protected double calculateBarW0(CategoryPlot plot, 
234                                        PlotOrientation orientation, 
235                                        Rectangle2D dataArea,
236                                        CategoryAxis domainAxis,
237                                        CategoryItemRendererState state,
238                                        int row,
239                                        int column) {
240            // calculate bar width...
241            double space = 0.0;
242            if (orientation == PlotOrientation.HORIZONTAL) {
243                space = dataArea.getHeight();
244            }
245            else {
246                space = dataArea.getWidth();
247            }
248            double barW0 = domainAxis.getCategoryStart(
249                column, getColumnCount(), dataArea, plot.getDomainAxisEdge()
250            );
251            int seriesCount = getRowCount();
252            int categoryCount = getColumnCount();
253            if (seriesCount > 1) {
254                double seriesGap = space * getItemMargin() 
255                    / (categoryCount * (seriesCount - 1));
256                double seriesW = calculateSeriesWidth(
257                    space, domainAxis, categoryCount, seriesCount
258                );
259                barW0 = barW0 + row * (seriesW + seriesGap) 
260                              + (seriesW / 2.0) - (state.getBarWidth() / 2.0);
261            }
262            else {
263                barW0 = domainAxis.getCategoryMiddle(
264                    column, getColumnCount(), dataArea,
265                    plot.getDomainAxisEdge()
266                )
267                - state.getBarWidth() / 2.0;
268            }
269            return barW0;
270        }
271        
272        /**
273         * Draws the bar for a single (series, category) data item.
274         *
275         * @param g2  the graphics device.
276         * @param state  the renderer state.
277         * @param dataArea  the data area.
278         * @param plot  the plot.
279         * @param domainAxis  the domain axis.
280         * @param rangeAxis  the range axis.
281         * @param dataset  the dataset.
282         * @param row  the row index (zero-based).
283         * @param column  the column index (zero-based).
284         * @param pass  the pass index.
285         */
286        public void drawItem(Graphics2D g2,
287                             CategoryItemRendererState state,
288                             Rectangle2D dataArea,
289                             CategoryPlot plot,
290                             CategoryAxis domainAxis,
291                             ValueAxis rangeAxis,
292                             CategoryDataset dataset,
293                             int row,
294                             int column,
295                             int pass) {
296    
297            // nothing is drawn for null values...
298            Number dataValue = dataset.getValue(row, column);
299            if (dataValue == null) {
300                return;
301            }
302            
303            double value = dataValue.doubleValue();
304            
305            PlotOrientation orientation = plot.getOrientation();
306            double barW0 = calculateBarW0(
307                plot, orientation, dataArea, domainAxis, state, row, column
308            );
309            RectangleEdge edge = plot.getRangeAxisEdge();
310            double barL = rangeAxis.valueToJava2D(value, dataArea, edge);
311    
312            // draw the bar...
313            Line2D line = null;
314            double x = 0.0;
315            double y = 0.0;
316            if (orientation == PlotOrientation.HORIZONTAL) {
317                x = barL;
318                y = barW0 + state.getBarWidth() / 2.0;
319                line = new Line2D.Double(
320                    barL, barW0, barL, barW0 + state.getBarWidth()
321                );
322            }
323            else {
324                x = barW0 + state.getBarWidth() / 2.0;
325                y = barL;
326                line = new Line2D.Double(
327                    barW0, barL, barW0 + state.getBarWidth(), barL
328                );
329            }
330            Stroke itemStroke = getItemStroke(row, column);
331            Paint itemPaint = getItemPaint(row, column);
332            g2.setStroke(itemStroke);
333            g2.setPaint(itemPaint);
334            g2.draw(line);
335    
336            CategoryItemLabelGenerator generator 
337                = getItemLabelGenerator(row, column);
338            if (generator != null && isItemLabelVisible(row, column)) {
339                drawItemLabel(
340                    g2, orientation, dataset, row, column, x, y, (value < 0.0)
341                );
342            }        
343                    
344            // collect entity and tool tip information...
345            if (state.getInfo() != null) {
346                EntityCollection entities = state.getEntityCollection();
347                if (entities != null) {
348                    String tip = null;
349                    CategoryToolTipGenerator tipster 
350                        = getToolTipGenerator(row, column);
351                    if (tipster != null) {
352                        tip = tipster.generateToolTip(dataset, row, column);
353                    }
354                    String url = null;
355                    if (getItemURLGenerator(row, column) != null) {
356                        url = getItemURLGenerator(row, column).generateURL(
357                            dataset, row, column
358                        );
359                    }
360                    CategoryItemEntity entity = new CategoryItemEntity(
361                        line.getBounds(), tip, url, dataset, row, 
362                        dataset.getColumnKey(column), column
363                    );
364                    entities.add(entity);
365                }
366    
367            }
368    
369        }
370    
371        /**
372         * Calculates the available space for each series.
373         * 
374         * @param space  the space along the entire axis (in Java2D units).
375         * @param axis  the category axis.
376         * @param categories  the number of categories.
377         * @param series  the number of series.
378         * 
379         * @return The width of one series.
380         */
381        protected double calculateSeriesWidth(double space, CategoryAxis axis, 
382                                              int categories, int series) {
383            double factor = 1.0 - getItemMargin() - axis.getLowerMargin() 
384                            - axis.getUpperMargin();
385            if (categories > 1) {
386                factor = factor - axis.getCategoryMargin();
387            }
388            return (space * factor) / (categories * series);
389        }
390        
391        /**
392         * Tests an object for equality with this instance.
393         * 
394         * @param obj  the object.
395         * 
396         * @return A boolean.
397         */
398        public boolean equals(Object obj) {
399            if (obj == this) {
400                return true;
401            }
402            if (!(obj instanceof LevelRenderer)) {
403                return false;
404            }
405            if (!super.equals(obj)) {
406                return false;
407            }
408            LevelRenderer that = (LevelRenderer) obj;
409            if (this.itemMargin != that.itemMargin) {              
410                return false;
411            }
412            if (this.maxItemWidth != that.maxItemWidth) {
413                return false;
414            }
415            return true;
416        }
417    
418    }