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 * LayeredBarRenderer.java
029 * -----------------------
030 * (C) Copyright 2003-2008, 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 *
051 */
052
053package org.jfree.chart.renderer.category;
054
055import java.awt.GradientPaint;
056import java.awt.Graphics2D;
057import java.awt.Paint;
058import java.awt.Stroke;
059import java.awt.geom.Rectangle2D;
060import java.io.Serializable;
061
062import org.jfree.chart.axis.CategoryAxis;
063import org.jfree.chart.axis.ValueAxis;
064import org.jfree.chart.entity.EntityCollection;
065import org.jfree.chart.labels.CategoryItemLabelGenerator;
066import org.jfree.chart.plot.CategoryPlot;
067import org.jfree.chart.plot.PlotOrientation;
068import org.jfree.data.category.CategoryDataset;
069import org.jfree.ui.GradientPaintTransformer;
070import org.jfree.ui.RectangleEdge;
071import org.jfree.util.ObjectList;
072
073/**
074 * A {@link CategoryItemRenderer} that represents data using bars which are
075 * superimposed.  The example shown here is generated by the
076 * <code>LayeredBarChartDemo1.java</code> program included in the JFreeChart
077 * Demo Collection:
078 * <br><br>
079 * <img src="../../../../../images/LayeredBarRendererSample.png"
080 * alt="LayeredBarRendererSample.png" />
081 */
082public class LayeredBarRenderer extends BarRenderer implements Serializable {
083
084    /** For serialization. */
085    private static final long serialVersionUID = -8716572894780469487L;
086
087    /** A list of the width of each series bar. */
088    protected ObjectList seriesBarWidthList;
089
090    /**
091     * Default constructor.
092     */
093    public LayeredBarRenderer() {
094        super();
095        this.seriesBarWidthList = new ObjectList();
096    }
097
098    /**
099     * Returns the bar width for a series, or <code>Double.NaN</code> if no
100     * width has been set.
101     *
102     * @param series  the series index (zero based).
103     *
104     * @return The width for the series (1.0=100%, it is the maximum).
105     */
106    public double getSeriesBarWidth(int series) {
107        double result = Double.NaN;
108        Number n = (Number) this.seriesBarWidthList.get(series);
109        if (n != null) {
110            result = n.doubleValue();
111        }
112        return result;
113    }
114
115    /**
116     * Sets the width of the bars of a series.
117     *
118     * @param series  the series index (zero based).
119     * @param width  the width of the series bar in percentage (1.0=100%, it is
120     *               the maximum).
121     */
122    public void setSeriesBarWidth(int series, double width) {
123        this.seriesBarWidthList.set(series, new Double(width));
124    }
125
126    /**
127     * Calculates the bar width and stores it in the renderer state.
128     *
129     * @param plot  the plot.
130     * @param dataArea  the data area.
131     * @param rendererIndex  the renderer index.
132     * @param state  the renderer state.
133     */
134    protected void calculateBarWidth(CategoryPlot plot,
135                                     Rectangle2D dataArea,
136                                     int rendererIndex,
137                                     CategoryItemRendererState state) {
138
139        // calculate the bar width - this calculation differs from the
140        // BarRenderer calculation because the bars are layered on top of one
141        // another, so there is effectively only one bar per category for
142        // the purpose of the bar width calculation
143        CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex);
144        CategoryDataset dataset = plot.getDataset(rendererIndex);
145        if (dataset != null) {
146            int columns = dataset.getColumnCount();
147            int rows = dataset.getRowCount();
148            double space = 0.0;
149            PlotOrientation orientation = plot.getOrientation();
150            if (orientation == PlotOrientation.HORIZONTAL) {
151                space = dataArea.getHeight();
152            }
153            else if (orientation == PlotOrientation.VERTICAL) {
154                space = dataArea.getWidth();
155            }
156            double maxWidth = space * getMaximumBarWidth();
157            double categoryMargin = 0.0;
158            if (columns > 1) {
159                categoryMargin = domainAxis.getCategoryMargin();
160            }
161            double used = space * (1 - domainAxis.getLowerMargin()
162                - domainAxis.getUpperMargin() - categoryMargin);
163            if ((rows * columns) > 0) {
164                state.setBarWidth(Math.min(used / (dataset.getColumnCount()),
165                        maxWidth));
166            }
167            else {
168                state.setBarWidth(Math.min(used, maxWidth));
169            }
170        }
171    }
172
173    /**
174     * Draws the bar for one item in the dataset.
175     *
176     * @param g2  the graphics device.
177     * @param state  the renderer state.
178     * @param dataArea  the plot area.
179     * @param plot  the plot.
180     * @param domainAxis  the domain (category) axis.
181     * @param rangeAxis  the range (value) axis.
182     * @param data  the data.
183     * @param row  the row index (zero-based).
184     * @param column  the column index (zero-based).
185     * @param pass  the pass index.
186     */
187    public void drawItem(Graphics2D g2,
188                         CategoryItemRendererState state,
189                         Rectangle2D dataArea,
190                         CategoryPlot plot,
191                         CategoryAxis domainAxis,
192                         ValueAxis rangeAxis,
193                         CategoryDataset data,
194                         int row,
195                         int column,
196                         int pass) {
197
198        PlotOrientation orientation = plot.getOrientation();
199        if (orientation == PlotOrientation.HORIZONTAL) {
200            drawHorizontalItem(g2, state, dataArea, plot, domainAxis,
201                    rangeAxis, data, row, column);
202        }
203        else if (orientation == PlotOrientation.VERTICAL) {
204            drawVerticalItem(g2, state, dataArea, plot, domainAxis, rangeAxis,
205                    data, row, column);
206        }
207
208    }
209
210    /**
211     * Draws the bar for a single (series, category) data item.
212     *
213     * @param g2  the graphics device.
214     * @param state  the renderer state.
215     * @param dataArea  the data area.
216     * @param plot  the plot.
217     * @param domainAxis  the domain axis.
218     * @param rangeAxis  the range axis.
219     * @param dataset  the dataset.
220     * @param row  the row index (zero-based).
221     * @param column  the column index (zero-based).
222     */
223    protected void drawHorizontalItem(Graphics2D g2,
224                                      CategoryItemRendererState state,
225                                      Rectangle2D dataArea,
226                                      CategoryPlot plot,
227                                      CategoryAxis domainAxis,
228                                      ValueAxis rangeAxis,
229                                      CategoryDataset dataset,
230                                      int row,
231                                      int column) {
232
233        // nothing is drawn for null values...
234        Number dataValue = dataset.getValue(row, column);
235        if (dataValue == null) {
236            return;
237        }
238
239        // X
240        double value = dataValue.doubleValue();
241        double base = 0.0;
242        double lclip = getLowerClip();
243        double uclip = getUpperClip();
244        if (uclip <= 0.0) {  // cases 1, 2, 3 and 4
245            if (value >= uclip) {
246                return; // bar is not visible
247            }
248            base = uclip;
249            if (value <= lclip) {
250                value = lclip;
251            }
252        }
253        else if (lclip <= 0.0) { // cases 5, 6, 7 and 8
254            if (value >= uclip) {
255                value = uclip;
256            }
257            else {
258                if (value <= lclip) {
259                    value = lclip;
260                }
261            }
262        }
263        else { // cases 9, 10, 11 and 12
264            if (value <= lclip) {
265                return; // bar is not visible
266            }
267            base = lclip;
268            if (value >= uclip) {
269                value = uclip;
270            }
271        }
272
273        RectangleEdge edge = plot.getRangeAxisEdge();
274        double transX1 = rangeAxis.valueToJava2D(base, dataArea, edge);
275        double transX2 = rangeAxis.valueToJava2D(value, dataArea, edge);
276        double rectX = Math.min(transX1, transX2);
277        double rectWidth = Math.abs(transX2 - transX1);
278
279        // Y
280        double rectY = domainAxis.getCategoryMiddle(column, getColumnCount(),
281                dataArea, plot.getDomainAxisEdge()) - state.getBarWidth() / 2.0;
282
283        int seriesCount = getRowCount();
284
285        // draw the bar...
286        double shift = 0.0;
287        double rectHeight = 0.0;
288        double widthFactor = 1.0;
289        double seriesBarWidth = getSeriesBarWidth(row);
290        if (!Double.isNaN(seriesBarWidth)) {
291            widthFactor = seriesBarWidth;
292        }
293        rectHeight = widthFactor * state.getBarWidth();
294        rectY = rectY + (1 - widthFactor) * state.getBarWidth() / 2.0;
295        if (seriesCount > 1) {
296            shift = rectHeight * 0.20 / (seriesCount - 1);
297        }
298
299        Rectangle2D bar = new Rectangle2D.Double(rectX,
300                (rectY + ((seriesCount - 1 - row) * shift)), rectWidth,
301                (rectHeight - (seriesCount - 1 - row) * shift * 2));
302
303        Paint itemPaint = getItemPaint(row, column);
304        GradientPaintTransformer t = getGradientPaintTransformer();
305        if (t != null && itemPaint instanceof GradientPaint) {
306            itemPaint = t.transform((GradientPaint) itemPaint, bar);
307        }
308        g2.setPaint(itemPaint);
309        g2.fill(bar);
310
311        // draw the outline...
312        if (isDrawBarOutline()
313                && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
314            Stroke stroke = getItemOutlineStroke(row, column);
315            Paint paint = getItemOutlinePaint(row, column);
316            if (stroke != null && paint != null) {
317                g2.setStroke(stroke);
318                g2.setPaint(paint);
319                g2.draw(bar);
320            }
321        }
322
323        CategoryItemLabelGenerator generator
324            = getItemLabelGenerator(row, column);
325        if (generator != null && isItemLabelVisible(row, column)) {
326            drawItemLabel(g2, dataset, row, column, plot, generator, bar,
327                    (transX1 > transX2));
328        }
329
330        // collect entity and tool tip information...
331        EntityCollection entities = state.getEntityCollection();
332        if (entities != null) {
333            addItemEntity(entities, dataset, row, column, bar);
334        }
335    }
336
337    /**
338     * Draws the bar for a single (series, category) data item.
339     *
340     * @param g2  the graphics device.
341     * @param state  the renderer state.
342     * @param dataArea  the data area.
343     * @param plot  the plot.
344     * @param domainAxis  the domain axis.
345     * @param rangeAxis  the range axis.
346     * @param dataset  the dataset.
347     * @param row  the row index (zero-based).
348     * @param column  the column index (zero-based).
349     */
350    protected void drawVerticalItem(Graphics2D g2,
351                                    CategoryItemRendererState state,
352                                    Rectangle2D dataArea,
353                                    CategoryPlot plot,
354                                    CategoryAxis domainAxis,
355                                    ValueAxis rangeAxis,
356                                    CategoryDataset dataset,
357                                    int row,
358                                    int column) {
359
360        // nothing is drawn for null values...
361        Number dataValue = dataset.getValue(row, column);
362        if (dataValue == null) {
363            return;
364        }
365
366        // BAR X
367        double rectX = domainAxis.getCategoryMiddle(column, getColumnCount(),
368                dataArea, plot.getDomainAxisEdge()) - state.getBarWidth() / 2.0;
369
370        int seriesCount = getRowCount();
371
372        // BAR Y
373        double value = dataValue.doubleValue();
374        double base = 0.0;
375        double lclip = getLowerClip();
376        double uclip = getUpperClip();
377
378        if (uclip <= 0.0) {  // cases 1, 2, 3 and 4
379            if (value >= uclip) {
380                return; // bar is not visible
381            }
382            base = uclip;
383            if (value <= lclip) {
384                value = lclip;
385            }
386        }
387        else if (lclip <= 0.0) { // cases 5, 6, 7 and 8
388            if (value >= uclip) {
389                value = uclip;
390            }
391            else {
392                if (value <= lclip) {
393                    value = lclip;
394                }
395            }
396        }
397        else { // cases 9, 10, 11 and 12
398            if (value <= lclip) {
399                return; // bar is not visible
400            }
401            base = getLowerClip();
402            if (value >= uclip) {
403               value = uclip;
404            }
405        }
406
407        RectangleEdge edge = plot.getRangeAxisEdge();
408        double transY1 = rangeAxis.valueToJava2D(base, dataArea, edge);
409        double transY2 = rangeAxis.valueToJava2D(value, dataArea, edge);
410        double rectY = Math.min(transY2, transY1);
411
412        double rectWidth = state.getBarWidth();
413        double rectHeight = Math.abs(transY2 - transY1);
414
415        // draw the bar...
416        double shift = 0.0;
417        rectWidth = 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}