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