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     * LayeredBarRenderer.java
029     * -----------------------
030     * (C) Copyright 2003-2005, by Arnaud Lelievre and Contributors.
031     *
032     * Original Author:  Arnaud Lelievre (for Garden);
033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
034     *
035     * Changes
036     * -------
037     * 28-Aug-2003 : Version 1 (AL);
038     * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
039     * 07-Oct-2003 : Added renderer state (DG);
040     * 21-Oct-2003 : Bar width moved to renderer state (DG);
041     * 05-Nov-2004 : Modified drawItem() signature (DG);
042     * 20-Apr-2005 : Renamed CategoryLabelGenerator 
043     *               --> CategoryItemLabelGenerator (DG);
044     * 17-Nov-2005 : Added support for gradient paint (DG);
045     *
046     */
047    
048    package org.jfree.chart.renderer.category;
049    
050    import java.awt.GradientPaint;
051    import java.awt.Graphics2D;
052    import java.awt.Paint;
053    import java.awt.Stroke;
054    import java.awt.geom.Rectangle2D;
055    import java.io.Serializable;
056    
057    import org.jfree.chart.axis.CategoryAxis;
058    import org.jfree.chart.axis.ValueAxis;
059    import org.jfree.chart.entity.CategoryItemEntity;
060    import org.jfree.chart.entity.EntityCollection;
061    import org.jfree.chart.labels.CategoryItemLabelGenerator;
062    import org.jfree.chart.labels.CategoryToolTipGenerator;
063    import org.jfree.chart.plot.CategoryPlot;
064    import org.jfree.chart.plot.PlotOrientation;
065    import org.jfree.data.category.CategoryDataset;
066    import org.jfree.ui.GradientPaintTransformer;
067    import org.jfree.ui.RectangleEdge;
068    import org.jfree.util.ObjectList;
069    
070    /**
071     * A {@link CategoryItemRenderer} that represents data using bars which are 
072     * superimposed.
073     *
074     * @author Arnaud Lelievre
075     */
076    public class LayeredBarRenderer extends BarRenderer 
077                                    implements Serializable {
078        
079        /** For serialization. */
080        private static final long serialVersionUID = -8716572894780469487L;
081    
082        /** A list of the width of each series bar. */
083        protected ObjectList seriesBarWidthList;
084    
085        /**
086         * Default constructor.
087         */
088        public LayeredBarRenderer() {
089            super();
090            this.seriesBarWidthList = new ObjectList();
091        }
092    
093        /**
094         * Returns the bar width for a series, or <code>Double.NaN</code> if no
095         * width has been set.
096         *
097         * @param series  the series index (zero based).
098         *
099         * @return The width for the series (1.0=100%, it is the maximum).
100         */
101        public double getSeriesBarWidth(int series) {
102            double result = Double.NaN;
103            Number n = (Number) this.seriesBarWidthList.get(series);
104            if (n != null) {
105                result = n.doubleValue();
106            }
107            return result;
108        }
109    
110        /**
111         * Sets the width of the bars of a series.
112         *
113         * @param series  the series index (zero based).
114         * @param width  the width of the series bar in percentage (1.0=100%, it is 
115         *               the maximum).
116         */ 
117        public void setSeriesBarWidth(int series, double width) {
118            this.seriesBarWidthList.set(series, new Double(width));
119        }
120    
121        /**
122         * Calculates the bar width and stores it in the renderer state.
123         * 
124         * @param plot  the plot.
125         * @param dataArea  the data area.
126         * @param rendererIndex  the renderer index.
127         * @param state  the renderer state.
128         */
129        protected void calculateBarWidth(CategoryPlot plot, 
130                                         Rectangle2D dataArea, 
131                                         int rendererIndex,
132                                         CategoryItemRendererState state) {
133    
134            // calculate the bar width
135            CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex);
136            CategoryDataset dataset = plot.getDataset(rendererIndex);
137            if (dataset != null) {
138                int columns = dataset.getColumnCount();
139                int rows = dataset.getRowCount();
140                double space = 0.0;
141                PlotOrientation orientation = plot.getOrientation();
142                if (orientation == PlotOrientation.HORIZONTAL) {
143                    space = dataArea.getHeight();
144                }
145                else if (orientation == PlotOrientation.VERTICAL) {
146                    space = dataArea.getWidth();
147                }
148                double categoryMargin = 0.0;
149                if (columns > 1) {
150                    categoryMargin = domainAxis.getCategoryMargin();
151                }
152                double used = space * (1 - domainAxis.getLowerMargin() 
153                    - domainAxis.getUpperMargin() - categoryMargin);
154                if ((rows * columns) > 0) {
155                    state.setBarWidth(used / (dataset.getColumnCount()));
156                } 
157                else {
158                    state.setBarWidth(used);
159                }
160            }
161        }
162    
163        /**
164         * Draws the bar for one item in the dataset.
165         *
166         * @param g2  the graphics device.
167         * @param state  the renderer state.
168         * @param dataArea  the plot area.
169         * @param plot  the plot.
170         * @param domainAxis  the domain (category) axis.
171         * @param rangeAxis  the range (value) axis.
172         * @param data  the data.
173         * @param row  the row index (zero-based).
174         * @param column  the column index (zero-based).
175         * @param pass  the pass index.
176         */
177        public void drawItem(Graphics2D g2,
178                             CategoryItemRendererState state,
179                             Rectangle2D dataArea,
180                             CategoryPlot plot,
181                             CategoryAxis domainAxis,
182                             ValueAxis rangeAxis,
183                             CategoryDataset data,
184                             int row,
185                             int column,
186                             int pass) {
187    
188            PlotOrientation orientation = plot.getOrientation();
189            if (orientation == PlotOrientation.HORIZONTAL) {
190                drawHorizontalItem(g2, state, dataArea, plot, domainAxis, rangeAxis, 
191                    data, row, column);
192            }
193            else if (orientation == PlotOrientation.VERTICAL) {
194                drawVerticalItem(g2, state, dataArea, plot, domainAxis, rangeAxis, 
195                    data, row, column);
196            }
197    
198        }
199    
200        /**
201         * Draws the bar for a single (series, category) data item.
202         *
203         * @param g2  the graphics device.
204         * @param state  the renderer state.
205         * @param dataArea  the data area.
206         * @param plot  the plot.
207         * @param domainAxis  the domain axis.
208         * @param rangeAxis  the range axis.
209         * @param data  the data.
210         * @param row  the row index (zero-based).
211         * @param column  the column index (zero-based).
212         */
213        protected void drawHorizontalItem(Graphics2D g2,
214                                          CategoryItemRendererState state,
215                                          Rectangle2D dataArea,
216                                          CategoryPlot plot,
217                                          CategoryAxis domainAxis,
218                                          ValueAxis rangeAxis,
219                                          CategoryDataset data,
220                                          int row,
221                                          int column) {
222    
223            // nothing is drawn for null values...
224            Number dataValue = data.getValue(row, column);
225            if (dataValue == null) {
226                return;
227            }
228    
229            // X
230            double value = dataValue.doubleValue();
231            double base = 0.0;
232            double lclip = getLowerClip();
233            double uclip = getUpperClip();
234            if (uclip <= 0.0) {  // cases 1, 2, 3 and 4
235                if (value >= uclip) {
236                    return; // bar is not visible
237                }
238                base = uclip;
239                if (value <= lclip) {
240                    value = lclip;
241                }
242            }
243            else if (lclip <= 0.0) { // cases 5, 6, 7 and 8
244                if (value >= uclip) {
245                    value = uclip;
246                }
247                else {
248                    if (value <= lclip) {
249                        value = lclip;
250                    }
251                }
252            }
253            else { // cases 9, 10, 11 and 12
254                if (value <= lclip) {
255                    return; // bar is not visible
256                }
257                base = lclip;
258                if (value >= uclip) {
259                    value = uclip;
260                }
261            }
262    
263            RectangleEdge edge = plot.getRangeAxisEdge();
264            double transX1 = rangeAxis.valueToJava2D(base, dataArea, edge);
265            double transX2 = rangeAxis.valueToJava2D(value, dataArea, edge);
266            double rectX = Math.min(transX1, transX2);
267            double rectWidth = Math.abs(transX2 - transX1);
268    
269            // Y
270            double rectY = domainAxis.getCategoryMiddle(column, getColumnCount(), 
271                    dataArea, plot.getDomainAxisEdge()) - state.getBarWidth() / 2.0;
272    
273            int seriesCount = getRowCount();
274    
275            // draw the bar...
276            double shift = 0.0;
277            double rectHeight = 0.0;
278            double widthFactor = 1.0;
279            double seriesBarWidth = getSeriesBarWidth(row);
280            if (!Double.isNaN(seriesBarWidth)) {
281                widthFactor = seriesBarWidth;
282            } 
283            rectHeight = widthFactor * state.getBarWidth();
284            rectY = rectY + (1 - widthFactor) * state.getBarWidth() / 2.0;
285            if (seriesCount > 1) {
286                shift = rectHeight * 0.20 / (seriesCount - 1);
287            }
288    
289            Rectangle2D bar = new Rectangle2D.Double(rectX, 
290                    (rectY + ((seriesCount - 1 - row) * shift)), rectWidth, 
291                    (rectHeight - (seriesCount - 1 - row) * shift * 2));
292    
293            Paint itemPaint = getItemPaint(row, column);
294            GradientPaintTransformer t = getGradientPaintTransformer();
295            if (t != null && itemPaint instanceof GradientPaint) {
296                itemPaint = t.transform((GradientPaint) itemPaint, bar);
297            }
298            g2.setPaint(itemPaint);
299            g2.fill(bar);
300    
301            // draw the outline...
302            if (isDrawBarOutline() 
303                    && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
304                Stroke stroke = getItemOutlineStroke(row, column);
305                Paint paint = getItemOutlinePaint(row, column);
306                if (stroke != null && paint != null) {
307                    g2.setStroke(stroke);
308                    g2.setPaint(paint);
309                    g2.draw(bar);
310                }
311            }
312    
313            CategoryItemLabelGenerator generator 
314                = getItemLabelGenerator(row, column);
315            if (generator != null && isItemLabelVisible(row, column)) {
316                drawItemLabel(g2, data, row, column, plot, generator, bar, 
317                        (transX1 > transX2));
318            }        
319    
320            // collect entity and tool tip information...
321            if (state.getInfo() != null) {
322                EntityCollection entities = state.getEntityCollection();
323                if (entities != null) {
324                    String tip = null;
325                    CategoryToolTipGenerator tipster 
326                        = getToolTipGenerator(row, column);
327                    if (tipster != null) {
328                        tip = tipster.generateToolTip(data, row, column);
329                    }
330                    String url = null;
331                    if (getItemURLGenerator(row, column) != null) {
332                        url = getItemURLGenerator(row, column).generateURL(data, 
333                                row, column);
334                    }
335                    CategoryItemEntity entity = new CategoryItemEntity(bar, tip, 
336                            url, data, row, data.getColumnKey(column), column);
337                    entities.add(entity);
338                }
339            }
340        }
341    
342        /**
343         * Draws the bar for a single (series, category) data item.
344         *
345         * @param g2  the graphics device.
346         * @param state  the renderer state.
347         * @param dataArea  the data area.
348         * @param plot  the plot.
349         * @param domainAxis  the domain axis.
350         * @param rangeAxis  the range axis.
351         * @param data  the data.
352         * @param row  the row index (zero-based).
353         * @param column  the column index (zero-based).
354         */
355        protected void drawVerticalItem(Graphics2D g2,
356                                        CategoryItemRendererState state,
357                                        Rectangle2D dataArea,
358                                        CategoryPlot plot,
359                                        CategoryAxis domainAxis,
360                                        ValueAxis rangeAxis,
361                                        CategoryDataset data,
362                                        int row,
363                                        int column) {
364    
365            // nothing is drawn for null values...
366            Number dataValue = data.getValue(row, column);
367            if (dataValue == null) {
368                return;
369            }
370    
371            // BAR X
372            double rectX = domainAxis.getCategoryMiddle(column, getColumnCount(), 
373                    dataArea, plot.getDomainAxisEdge()) - state.getBarWidth() / 2.0;
374    
375            int seriesCount = getRowCount();
376    
377            // BAR Y
378            double value = dataValue.doubleValue();
379            double base = 0.0;
380            double lclip = getLowerClip();
381            double uclip = getUpperClip();
382    
383            if (uclip <= 0.0) {  // cases 1, 2, 3 and 4
384                if (value >= uclip) {
385                    return; // bar is not visible
386                }
387                base = uclip;
388                if (value <= lclip) {
389                    value = lclip;
390                }
391            }
392            else if (lclip <= 0.0) { // cases 5, 6, 7 and 8
393                if (value >= uclip) {
394                    value = uclip;
395                }
396                else {
397                    if (value <= lclip) {
398                        value = lclip;
399                    }
400                }
401            }
402            else { // cases 9, 10, 11 and 12
403                if (value <= lclip) {
404                    return; // bar is not visible
405                }
406                base = getLowerClip();
407                if (value >= uclip) {
408                   value = uclip;
409                }
410            }
411    
412            RectangleEdge edge = plot.getRangeAxisEdge();
413            double transY1 = rangeAxis.valueToJava2D(base, dataArea, edge);
414            double transY2 = rangeAxis.valueToJava2D(value, dataArea, edge);
415            double rectY = Math.min(transY2, transY1);
416    
417            double rectWidth = state.getBarWidth();
418            double rectHeight = Math.abs(transY2 - transY1);
419    
420            // draw the bar...
421            double shift = 0.0;
422            rectWidth = 0.0;
423            double widthFactor = 1.0;
424            double seriesBarWidth = getSeriesBarWidth(row);
425            if (!Double.isNaN(seriesBarWidth)) {
426                widthFactor = seriesBarWidth;
427            } 
428            rectWidth = widthFactor * state.getBarWidth();
429            rectX = rectX + (1 - widthFactor) * state.getBarWidth() / 2.0;
430            if (seriesCount > 1) {
431                // needs to be improved !!!
432                shift = rectWidth * 0.20 / (seriesCount - 1);
433            }
434    
435            Rectangle2D bar = new Rectangle2D.Double(
436                (rectX + ((seriesCount - 1 - row) * shift)), rectY,
437                (rectWidth - (seriesCount - 1 - row) * shift * 2), rectHeight);
438            Paint itemPaint = getItemPaint(row, column);
439            GradientPaintTransformer t = getGradientPaintTransformer();
440            if (t != null && itemPaint instanceof GradientPaint) {
441                itemPaint = t.transform((GradientPaint) itemPaint, bar);
442            }
443            g2.setPaint(itemPaint);
444            g2.fill(bar);
445    
446            // draw the outline...
447            if (isDrawBarOutline() 
448                    && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
449                Stroke stroke = getItemOutlineStroke(row, column);
450                Paint paint = getItemOutlinePaint(row, column);
451                if (stroke != null && paint != null) {
452                    g2.setStroke(stroke);
453                    g2.setPaint(paint);
454                    g2.draw(bar);
455                }
456            }
457    
458            // draw the item labels if there are any...
459            double transX1 = rangeAxis.valueToJava2D(base, dataArea, edge);
460            double transX2 = rangeAxis.valueToJava2D(value, dataArea, edge);
461    
462            CategoryItemLabelGenerator generator 
463                = getItemLabelGenerator(row, column);
464            if (generator != null && isItemLabelVisible(row, column)) {
465                drawItemLabel(g2, data, row, column, plot, generator, bar, 
466                        (transX1 > transX2));
467            }        
468    
469            // collect entity and tool tip information...
470            if (state.getInfo() != null) {
471                EntityCollection entities = state.getEntityCollection();
472                if (entities != null) {
473                    String tip = null;
474                    CategoryToolTipGenerator tipster 
475                        = getToolTipGenerator(row, column);
476                    if (tipster != null) {
477                        tip = tipster.generateToolTip(data, row, column);
478                    }
479                    String url = null;
480                    if (getItemURLGenerator(row, column) != null) {
481                        url = getItemURLGenerator(row, column).generateURL(
482                            data, row, column);
483                    }
484                    CategoryItemEntity entity = new CategoryItemEntity(bar, tip, 
485                            url, data, row, data.getColumnKey(column), column);
486                    entities.add(entity);
487                }
488            }
489        }
490    
491    }