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