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 * BarRenderer3D.java
029 * ------------------
030 * (C) Copyright 2001-2007, by Serge V. Grachov and Contributors.
031 *
032 * Original Author:  Serge V. Grachov;
033 * Contributor(s):   David Gilbert (for Object Refinery Limited);
034 *                   Tin Luu;
035 *                   Milo Simpson;
036 *                   Richard Atkinson;
037 *                   Rich Unger;
038 *                   Christian W. Zuckschwerdt;
039 *
040 * Changes
041 * -------
042 * 31-Oct-2001 : First version, contributed by Serge V. Grachov (DG);
043 * 15-Nov-2001 : Modified to allow for null data values (DG);
044 * 13-Dec-2001 : Added tooltips (DG);
045 * 16-Jan-2002 : Added fix for single category or single series datasets, 
046 *               pointed out by Taoufik Romdhane (DG);
047 * 24-May-2002 : Incorporated tooltips into chart entities (DG);
048 * 11-Jun-2002 : Added check for (permitted) null info object, bug and fix 
049 *               reported by David Basten.  Also updated Javadocs. (DG);
050 * 19-Jun-2002 : Added code to draw labels on bars (TL);
051 * 26-Jun-2002 : Added bar clipping to avoid PRExceptions (DG);
052 * 05-Aug-2002 : Small modification to drawCategoryItem method to support URLs 
053 *               for HTML image maps (RA);
054 * 06-Aug-2002 : Value labels now use number formatter, thanks to Milo 
055 *               Simpson (DG);
056 * 08-Aug-2002 : Applied fixed in bug id 592218 (DG);
057 * 20-Sep-2002 : Added fix for categoryPaint by Rich Unger, and fixed errors 
058 *               reported by Checkstyle (DG);
059 * 24-Oct-2002 : Amendments for changes in CategoryDataset interface and 
060 *               CategoryToolTipGenerator interface (DG);
061 * 05-Nov-2002 : Replaced references to CategoryDataset with TableDataset (DG);
062 * 06-Nov-2002 : Moved to the com.jrefinery.chart.renderer package (DG);
063 * 28-Jan-2003 : Added an attribute to control the shading of the left and 
064 *               bottom walls in the plot background (DG);
065 * 25-Mar-2003 : Implemented Serializable (DG);
066 * 10-Apr-2003 : Removed category paint usage (DG);
067 * 13-May-2003 : Renamed VerticalBarRenderer3D --> BarRenderer3D and merged with
068 *               HorizontalBarRenderer3D (DG);
069 * 30-Jul-2003 : Modified entity constructor (CZ);
070 * 19-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
071 * 07-Oct-2003 : Added renderer state (DG);
072 * 08-Oct-2003 : Removed clipping (replaced with flag in CategoryPlot to 
073 *               control order in which the data items are processed) (DG);
074 * 20-Oct-2003 : Fixed bug (outline stroke not being used for bar 
075 *               outlines) (DG);
076 * 21-Oct-2003 : Bar width moved into CategoryItemRendererState (DG);
077 * 24-Nov-2003 : Fixed bug 846324 (item labels not showing) (DG);
078 * 27-Nov-2003 : Added code to respect maxBarWidth setting (DG);
079 * 02-Feb-2004 : Fixed bug where 'drawBarOutline' flag is not respected (DG);
080 * 10-Feb-2004 : Small change to drawItem() method to make cut-and-paste 
081 *               overriding easier (DG);
082 * 04-Oct-2004 : Fixed bug with item label positioning when plot alignment is 
083 *               horizontal (DG);
084 * 05-Nov-2004 : Modified drawItem() signature (DG);
085 * 20-Apr-2005 : Renamed CategoryLabelGenerator 
086 *               --> CategoryItemLabelGenerator (DG);
087 * 25-Apr-2005 : Override initialise() method to fix bug 1189642 (DG);
088 * 09-Jun-2005 : Use addEntityItem from super class (DG);
089 * ------------- JFREECHART 1.0.x ---------------------------------------------
090 * 07-Dec-2006 : Implemented equals() override (DG);
091 * 17-Jan-2007 : Fixed bug in drawDomainGridline() method (DG);
092 * 03-Apr-2007 : Fixed bugs in drawBackground() method (DG);
093 * 16-Oct-2007 : Fixed bug in range marker drawing (DG);
094 * 
095 */
096
097package org.jfree.chart.renderer.category;
098
099import java.awt.AlphaComposite;
100import java.awt.Color;
101import java.awt.Composite;
102import java.awt.Font;
103import java.awt.Graphics2D;
104import java.awt.Image;
105import java.awt.Paint;
106import java.awt.Stroke;
107import java.awt.geom.GeneralPath;
108import java.awt.geom.Line2D;
109import java.awt.geom.Point2D;
110import java.awt.geom.Rectangle2D;
111import java.io.IOException;
112import java.io.ObjectInputStream;
113import java.io.ObjectOutputStream;
114import java.io.Serializable;
115
116import org.jfree.chart.Effect3D;
117import org.jfree.chart.axis.CategoryAxis;
118import org.jfree.chart.axis.ValueAxis;
119import org.jfree.chart.entity.EntityCollection;
120import org.jfree.chart.event.RendererChangeEvent;
121import org.jfree.chart.labels.CategoryItemLabelGenerator;
122import org.jfree.chart.labels.ItemLabelAnchor;
123import org.jfree.chart.labels.ItemLabelPosition;
124import org.jfree.chart.plot.CategoryPlot;
125import org.jfree.chart.plot.Marker;
126import org.jfree.chart.plot.Plot;
127import org.jfree.chart.plot.PlotOrientation;
128import org.jfree.chart.plot.PlotRenderingInfo;
129import org.jfree.chart.plot.ValueMarker;
130import org.jfree.data.Range;
131import org.jfree.data.category.CategoryDataset;
132import org.jfree.io.SerialUtilities;
133import org.jfree.text.TextUtilities;
134import org.jfree.ui.LengthAdjustmentType;
135import org.jfree.ui.RectangleAnchor;
136import org.jfree.ui.RectangleEdge;
137import org.jfree.ui.TextAnchor;
138import org.jfree.util.PaintUtilities;
139import org.jfree.util.PublicCloneable;
140
141/**
142 * A renderer for bars with a 3D effect, for use with the 
143 * {@link org.jfree.chart.plot.CategoryPlot} class.
144 */
145public class BarRenderer3D extends BarRenderer 
146                           implements Effect3D, Cloneable, PublicCloneable, 
147                                      Serializable {
148
149    /** For serialization. */
150    private static final long serialVersionUID = 7686976503536003636L;
151    
152    /** The default x-offset for the 3D effect. */
153    public static final double DEFAULT_X_OFFSET = 12.0;
154
155    /** The default y-offset for the 3D effect. */
156    public static final double DEFAULT_Y_OFFSET = 8.0;
157
158    /** The default wall paint. */
159    public static final Paint DEFAULT_WALL_PAINT = new Color(0xDD, 0xDD, 0xDD);
160
161    /** The size of x-offset for the 3D effect. */
162    private double xOffset;
163
164    /** The size of y-offset for the 3D effect. */
165    private double yOffset;
166
167    /** The paint used to shade the left and lower 3D wall. */
168    private transient Paint wallPaint;
169
170    /**
171     * Default constructor, creates a renderer with a default '3D effect'.
172     */
173    public BarRenderer3D() {
174        this(DEFAULT_X_OFFSET, DEFAULT_Y_OFFSET);
175    }
176
177    /**
178     * Constructs a new renderer with the specified '3D effect'.
179     *
180     * @param xOffset  the x-offset for the 3D effect.
181     * @param yOffset  the y-offset for the 3D effect.
182     */
183    public BarRenderer3D(double xOffset, double yOffset) {
184
185        super();
186        this.xOffset = xOffset;
187        this.yOffset = yOffset;
188        this.wallPaint = DEFAULT_WALL_PAINT;
189        // set the default item label positions
190        ItemLabelPosition p1 = new ItemLabelPosition(ItemLabelAnchor.INSIDE12, 
191                TextAnchor.TOP_CENTER);
192        setBasePositiveItemLabelPosition(p1);
193        ItemLabelPosition p2 = new ItemLabelPosition(ItemLabelAnchor.INSIDE12, 
194                TextAnchor.TOP_CENTER);
195        setBaseNegativeItemLabelPosition(p2);
196
197    }
198
199    /**
200     * Returns the x-offset for the 3D effect.
201     *
202     * @return The 3D effect.
203     * 
204     * @see #getYOffset()
205     */
206    public double getXOffset() {
207        return this.xOffset;
208    }
209
210    /**
211     * Returns the y-offset for the 3D effect.
212     *
213     * @return The 3D effect.
214     */
215    public double getYOffset() {
216        return this.yOffset;
217    }
218
219    /**
220     * Returns the paint used to highlight the left and bottom wall in the plot
221     * background.
222     *
223     * @return The paint.
224     * 
225     * @see #setWallPaint(Paint)
226     */
227    public Paint getWallPaint() {
228        return this.wallPaint;
229    }
230
231    /**
232     * Sets the paint used to hightlight the left and bottom walls in the plot
233     * background, and sends a {@link RendererChangeEvent} to all registered
234     * listeners.
235     *
236     * @param paint  the paint (<code>null</code> not permitted).
237     * 
238     * @see #getWallPaint()
239     */
240    public void setWallPaint(Paint paint) {
241        if (paint == null) {
242            throw new IllegalArgumentException("Null 'paint' argument.");
243        }
244        this.wallPaint = paint;
245        notifyListeners(new RendererChangeEvent(this));
246    }
247
248
249    /**
250     * Initialises the renderer and returns a state object that will be passed 
251     * to subsequent calls to the drawItem method.  This method gets called 
252     * once at the start of the process of drawing a chart.
253     *
254     * @param g2  the graphics device.
255     * @param dataArea  the area in which the data is to be plotted.
256     * @param plot  the plot.
257     * @param rendererIndex  the renderer index.
258     * @param info  collects chart rendering information for return to caller.
259     * 
260     * @return The renderer state.
261     */
262    public CategoryItemRendererState initialise(Graphics2D g2,
263                                                Rectangle2D dataArea,
264                                                CategoryPlot plot,
265                                                int rendererIndex,
266                                                PlotRenderingInfo info) {
267
268        Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(), 
269                dataArea.getY() + getYOffset(), dataArea.getWidth() 
270                - getXOffset(), dataArea.getHeight() - getYOffset());
271        CategoryItemRendererState state = super.initialise(g2, adjusted, plot, 
272                rendererIndex, info);
273        return state;
274        
275    }
276    
277    /**
278     * Draws the background for the plot.
279     *
280     * @param g2  the graphics device.
281     * @param plot  the plot.
282     * @param dataArea  the area inside the axes.
283     */
284    public void drawBackground(Graphics2D g2, CategoryPlot plot, 
285                               Rectangle2D dataArea) {
286
287        float x0 = (float) dataArea.getX();
288        float x1 = x0 + (float) Math.abs(this.xOffset);
289        float x3 = (float) dataArea.getMaxX();
290        float x2 = x3 - (float) Math.abs(this.xOffset);
291
292        float y0 = (float) dataArea.getMaxY();
293        float y1 = y0 - (float) Math.abs(this.yOffset);
294        float y3 = (float) dataArea.getMinY();
295        float y2 = y3 + (float) Math.abs(this.yOffset);
296
297        GeneralPath clip = new GeneralPath();
298        clip.moveTo(x0, y0);
299        clip.lineTo(x0, y2);
300        clip.lineTo(x1, y3);
301        clip.lineTo(x3, y3);
302        clip.lineTo(x3, y1);
303        clip.lineTo(x2, y0);
304        clip.closePath();
305
306        Composite originalComposite = g2.getComposite();
307        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
308                plot.getBackgroundAlpha()));
309        
310        // fill background...
311        Paint backgroundPaint = plot.getBackgroundPaint();
312        if (backgroundPaint != null) {
313            g2.setPaint(backgroundPaint);
314            g2.fill(clip);
315        }
316
317        GeneralPath leftWall = new GeneralPath();
318        leftWall.moveTo(x0, y0);
319        leftWall.lineTo(x0, y2);
320        leftWall.lineTo(x1, y3);
321        leftWall.lineTo(x1, y1);
322        leftWall.closePath();
323        g2.setPaint(getWallPaint());
324        g2.fill(leftWall);
325
326        GeneralPath bottomWall = new GeneralPath();
327        bottomWall.moveTo(x0, y0);
328        bottomWall.lineTo(x1, y1);
329        bottomWall.lineTo(x3, y1);
330        bottomWall.lineTo(x2, y0);
331        bottomWall.closePath();
332        g2.setPaint(getWallPaint());
333        g2.fill(bottomWall);
334
335        // highlight the background corners...
336        g2.setPaint(Color.lightGray);
337        Line2D corner = new Line2D.Double(x0, y0, x1, y1);
338        g2.draw(corner);
339        corner.setLine(x1, y1, x1, y3);
340        g2.draw(corner);
341        corner.setLine(x1, y1, x3, y1);
342        g2.draw(corner);
343                
344        // draw background image, if there is one...
345        Image backgroundImage = plot.getBackgroundImage();
346        if (backgroundImage != null) {
347            Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX() 
348                    + getXOffset(), dataArea.getY(), 
349                    dataArea.getWidth() - getXOffset(), 
350                    dataArea.getHeight() - getYOffset());
351            plot.drawBackgroundImage(g2, adjusted);
352        }
353        
354        g2.setComposite(originalComposite);
355
356    }
357
358    /**
359     * Draws the outline for the plot.
360     *
361     * @param g2  the graphics device.
362     * @param plot  the plot.
363     * @param dataArea  the area inside the axes.
364     */
365    public void drawOutline(Graphics2D g2, CategoryPlot plot, 
366                            Rectangle2D dataArea) {
367
368        float x0 = (float) dataArea.getX();
369        float x1 = x0 + (float) Math.abs(this.xOffset);
370        float x3 = (float) dataArea.getMaxX();
371        float x2 = x3 - (float) Math.abs(this.xOffset);
372
373        float y0 = (float) dataArea.getMaxY();
374        float y1 = y0 - (float) Math.abs(this.yOffset);
375        float y3 = (float) dataArea.getMinY();
376        float y2 = y3 + (float) Math.abs(this.yOffset);
377
378        GeneralPath clip = new GeneralPath();
379        clip.moveTo(x0, y0);
380        clip.lineTo(x0, y2);
381        clip.lineTo(x1, y3);
382        clip.lineTo(x3, y3);
383        clip.lineTo(x3, y1);
384        clip.lineTo(x2, y0);
385        clip.closePath();
386
387        // put an outline around the data area...
388        Stroke outlineStroke = plot.getOutlineStroke();
389        Paint outlinePaint = plot.getOutlinePaint();
390        if ((outlineStroke != null) && (outlinePaint != null)) {
391            g2.setStroke(outlineStroke);
392            g2.setPaint(outlinePaint);
393            g2.draw(clip);
394        }
395
396    }
397
398    /**
399     * Draws a grid line against the domain axis.
400     *
401     * @param g2  the graphics device.
402     * @param plot  the plot.
403     * @param dataArea  the area for plotting data (not yet adjusted for any 
404     *                  3D effect).
405     * @param value  the Java2D value at which the grid line should be drawn.
406     *
407     */
408    public void drawDomainGridline(Graphics2D g2,
409                                   CategoryPlot plot,
410                                   Rectangle2D dataArea,
411                                   double value) {
412
413        Line2D line1 = null;
414        Line2D line2 = null;
415        PlotOrientation orientation = plot.getOrientation();
416        if (orientation == PlotOrientation.HORIZONTAL) {
417            double y0 = value;
418            double y1 = value - getYOffset();
419            double x0 = dataArea.getMinX();
420            double x1 = x0 + getXOffset();
421            double x2 = dataArea.getMaxX();
422            line1 = new Line2D.Double(x0, y0, x1, y1);
423            line2 = new Line2D.Double(x1, y1, x2, y1);
424        }
425        else if (orientation == PlotOrientation.VERTICAL) {
426            double x0 = value;
427            double x1 = value + getXOffset();
428            double y0 = dataArea.getMaxY();
429            double y1 = y0 - getYOffset();
430            double y2 = dataArea.getMinY();
431            line1 = new Line2D.Double(x0, y0, x1, y1);
432            line2 = new Line2D.Double(x1, y1, x1, y2);
433        }
434        Paint paint = plot.getDomainGridlinePaint();
435        Stroke stroke = plot.getDomainGridlineStroke();
436        g2.setPaint(paint != null ? paint : Plot.DEFAULT_OUTLINE_PAINT);
437        g2.setStroke(stroke != null ? stroke : Plot.DEFAULT_OUTLINE_STROKE);
438        g2.draw(line1);
439        g2.draw(line2);
440
441    }
442
443    /**
444     * Draws a grid line against the range axis.
445     *
446     * @param g2  the graphics device.
447     * @param plot  the plot.
448     * @param axis  the value axis.
449     * @param dataArea  the area for plotting data (not yet adjusted for any 
450     *                  3D effect).
451     * @param value  the value at which the grid line should be drawn.
452     *
453     */
454    public void drawRangeGridline(Graphics2D g2,
455                                  CategoryPlot plot,
456                                  ValueAxis axis,
457                                  Rectangle2D dataArea,
458                                  double value) {
459
460        Range range = axis.getRange();
461
462        if (!range.contains(value)) {
463            return;
464        }
465
466        Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(),
467                dataArea.getY() + getYOffset(), dataArea.getWidth() 
468                - getXOffset(), dataArea.getHeight() - getYOffset());
469
470        Line2D line1 = null;
471        Line2D line2 = null;
472        PlotOrientation orientation = plot.getOrientation();
473        if (orientation == PlotOrientation.HORIZONTAL) {
474            double x0 = axis.valueToJava2D(value, adjusted, 
475                    plot.getRangeAxisEdge());
476            double x1 = x0 + getXOffset();
477            double y0 = dataArea.getMaxY();
478            double y1 = y0 - getYOffset();
479            double y2 = dataArea.getMinY();
480            line1 = new Line2D.Double(x0, y0, x1, y1);
481            line2 = new Line2D.Double(x1, y1, x1, y2);
482        }
483        else if (orientation == PlotOrientation.VERTICAL) {
484            double y0 = axis.valueToJava2D(value, adjusted, 
485                    plot.getRangeAxisEdge());
486            double y1 = y0 - getYOffset();
487            double x0 = dataArea.getMinX();
488            double x1 = x0 + getXOffset();
489            double x2 = dataArea.getMaxX();
490            line1 = new Line2D.Double(x0, y0, x1, y1);
491            line2 = new Line2D.Double(x1, y1, x2, y1);
492        }
493        Paint paint = plot.getRangeGridlinePaint();
494        Stroke stroke = plot.getRangeGridlineStroke();
495        g2.setPaint(paint != null ? paint : Plot.DEFAULT_OUTLINE_PAINT);
496        g2.setStroke(stroke != null ? stroke : Plot.DEFAULT_OUTLINE_STROKE);
497        g2.draw(line1);
498        g2.draw(line2);
499
500    }
501
502    /**
503     * Draws a range marker.
504     *
505     * @param g2  the graphics device.
506     * @param plot  the plot.
507     * @param axis  the value axis.
508     * @param marker  the marker.
509     * @param dataArea  the area for plotting data (not including 3D effect).
510     */
511    public void drawRangeMarker(Graphics2D g2,
512                                CategoryPlot plot,
513                                ValueAxis axis,
514                                Marker marker,
515                                Rectangle2D dataArea) {
516
517
518        Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(), 
519                dataArea.getY() + getYOffset(), dataArea.getWidth() 
520                - getXOffset(), dataArea.getHeight() - getYOffset());
521        if (marker instanceof ValueMarker) {
522            ValueMarker vm = (ValueMarker) marker;
523            double value = vm.getValue();
524            Range range = axis.getRange();
525            if (!range.contains(value)) {
526                return;
527            }
528
529            GeneralPath path = null;
530            PlotOrientation orientation = plot.getOrientation();
531            if (orientation == PlotOrientation.HORIZONTAL) {
532                float x = (float) axis.valueToJava2D(value, adjusted, 
533                        plot.getRangeAxisEdge());
534                float y = (float) adjusted.getMaxY();
535                path = new GeneralPath();
536                path.moveTo(x, y);
537                path.lineTo((float) (x + getXOffset()), 
538                        y - (float) getYOffset());
539                path.lineTo((float) (x + getXOffset()), 
540                        (float) (adjusted.getMinY() - getYOffset()));
541                path.lineTo(x, (float) adjusted.getMinY());
542                path.closePath();
543            }
544            else if (orientation == PlotOrientation.VERTICAL) {
545                float y = (float) axis.valueToJava2D(value, adjusted, 
546                        plot.getRangeAxisEdge());
547                float x = (float) dataArea.getX();
548                path = new GeneralPath();
549                path.moveTo(x, y);
550                path.lineTo(x + (float) this.xOffset, y - (float) this.yOffset);
551                path.lineTo((float) (adjusted.getMaxX() + this.xOffset), 
552                        y - (float) this.yOffset);
553                path.lineTo((float) (adjusted.getMaxX()), y);
554                path.closePath();
555            }
556            g2.setPaint(marker.getPaint());
557            g2.fill(path);
558            g2.setPaint(marker.getOutlinePaint());
559            g2.draw(path);
560        
561            String label = marker.getLabel();
562            RectangleAnchor anchor = marker.getLabelAnchor();
563            if (label != null) {
564                Font labelFont = marker.getLabelFont();
565                g2.setFont(labelFont);
566                g2.setPaint(marker.getLabelPaint());
567                Point2D coordinates = calculateRangeMarkerTextAnchorPoint(
568                        g2, orientation, dataArea, path.getBounds2D(), 
569                        marker.getLabelOffset(), LengthAdjustmentType.EXPAND, 
570                        anchor);
571                TextUtilities.drawAlignedString(label, g2, 
572                        (float) coordinates.getX(), (float) coordinates.getY(), 
573                        marker.getLabelTextAnchor());
574            }
575        
576        }
577        else {
578            super.drawRangeMarker(g2, plot, axis, marker, adjusted);
579            // TODO: draw the interval marker with a 3D effect
580        }
581    }
582
583    /**
584     * Draws a 3D bar to represent one data item.
585     *
586     * @param g2  the graphics device.
587     * @param state  the renderer state.
588     * @param dataArea  the area for plotting the data.
589     * @param plot  the plot.
590     * @param domainAxis  the domain axis.
591     * @param rangeAxis  the range axis.
592     * @param dataset  the dataset.
593     * @param row  the row index (zero-based).
594     * @param column  the column index (zero-based).
595     * @param pass  the pass index.
596     */
597    public void drawItem(Graphics2D g2,
598                         CategoryItemRendererState state,
599                         Rectangle2D dataArea,
600                         CategoryPlot plot,
601                         CategoryAxis domainAxis,
602                         ValueAxis rangeAxis,
603                         CategoryDataset dataset,
604                         int row,
605                         int column,
606                         int pass) {
607    
608        // check the value we are plotting...
609        Number dataValue = dataset.getValue(row, column);
610        if (dataValue == null) {
611            return;
612        }
613        
614        double value = dataValue.doubleValue();
615        
616        Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(),
617                dataArea.getY() + getYOffset(), 
618                dataArea.getWidth() - getXOffset(), 
619                dataArea.getHeight() - getYOffset());
620
621        PlotOrientation orientation = plot.getOrientation();
622        
623        double barW0 = calculateBarW0(plot, orientation, adjusted, domainAxis, 
624                state, row, column);
625        double[] barL0L1 = calculateBarL0L1(value);
626        if (barL0L1 == null) {
627            return;  // the bar is not visible
628        }
629
630        RectangleEdge edge = plot.getRangeAxisEdge();
631        double transL0 = rangeAxis.valueToJava2D(barL0L1[0], adjusted, edge);
632        double transL1 = rangeAxis.valueToJava2D(barL0L1[1], adjusted, edge);
633        double barL0 = Math.min(transL0, transL1);
634        double barLength = Math.abs(transL1 - transL0);
635        
636        // draw the bar...
637        Rectangle2D bar = null;
638        if (orientation == PlotOrientation.HORIZONTAL) {
639            bar = new Rectangle2D.Double(barL0, barW0, barLength, 
640                    state.getBarWidth());
641        }
642        else {
643            bar = new Rectangle2D.Double(barW0, barL0, state.getBarWidth(), 
644                    barLength);
645        }
646        Paint itemPaint = getItemPaint(row, column);
647        g2.setPaint(itemPaint);
648        g2.fill(bar);
649
650        double x0 = bar.getMinX();
651        double x1 = x0 + getXOffset();
652        double x2 = bar.getMaxX();
653        double x3 = x2 + getXOffset();
654        
655        double y0 = bar.getMinY() - getYOffset();
656        double y1 = bar.getMinY();
657        double y2 = bar.getMaxY() - getYOffset();
658        double y3 = bar.getMaxY();
659        
660        GeneralPath bar3dRight = null;
661        GeneralPath bar3dTop = null;
662        if (barLength > 0.0) {
663            bar3dRight = new GeneralPath();
664            bar3dRight.moveTo((float) x2, (float) y3);
665            bar3dRight.lineTo((float) x2, (float) y1);
666            bar3dRight.lineTo((float) x3, (float) y0);
667            bar3dRight.lineTo((float) x3, (float) y2);
668            bar3dRight.closePath();
669
670            if (itemPaint instanceof Color) {
671                g2.setPaint(((Color) itemPaint).darker());
672            }
673            g2.fill(bar3dRight);
674        }
675
676        bar3dTop = new GeneralPath();
677        bar3dTop.moveTo((float) x0, (float) y1);
678        bar3dTop.lineTo((float) x1, (float) y0);
679        bar3dTop.lineTo((float) x3, (float) y0);
680        bar3dTop.lineTo((float) x2, (float) y1);
681        bar3dTop.closePath();
682        g2.fill(bar3dTop);
683
684        if (isDrawBarOutline() 
685                && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
686            g2.setStroke(getItemOutlineStroke(row, column));
687            g2.setPaint(getItemOutlinePaint(row, column));
688            g2.draw(bar);
689            if (bar3dRight != null) {
690                g2.draw(bar3dRight);
691            }
692            if (bar3dTop != null) {
693                g2.draw(bar3dTop);
694            }
695        }
696
697        CategoryItemLabelGenerator generator 
698            = getItemLabelGenerator(row, column);
699        if (generator != null && isItemLabelVisible(row, column)) {
700            drawItemLabel(g2, dataset, row, column, plot, generator, bar, 
701                    (value < 0.0));
702        }        
703
704        // add an item entity, if this information is being collected
705        EntityCollection entities = state.getEntityCollection();
706        if (entities != null) {
707            GeneralPath barOutline = new GeneralPath();
708            barOutline.moveTo((float) x0, (float) y3);
709            barOutline.lineTo((float) x0, (float) y1);
710            barOutline.lineTo((float) x1, (float) y0);
711            barOutline.lineTo((float) x3, (float) y0);
712            barOutline.lineTo((float) x3, (float) y2);
713            barOutline.lineTo((float) x2, (float) y3);
714            barOutline.closePath();
715            addItemEntity(entities, dataset, row, column, barOutline);
716        }
717
718    }
719    
720    /**
721     * Tests this renderer for equality with an arbitrary object.
722     * 
723     * @param obj  the object (<code>null</code> permitted).
724     * 
725     * @return A boolean.
726     */
727    public boolean equals(Object obj) {
728        if (obj == this) {
729            return true;
730        }
731        if (!(obj instanceof BarRenderer3D)) {
732            return false;
733        }
734        BarRenderer3D that = (BarRenderer3D) obj;
735        if (this.xOffset != that.xOffset) {
736            return false;
737        }
738        if (this.yOffset != that.yOffset) {
739            return false;
740        }
741        if (!PaintUtilities.equal(this.wallPaint, that.wallPaint)) {
742            return false;
743        }
744        return super.equals(obj);
745    }
746
747    /**
748     * Provides serialization support.
749     *
750     * @param stream  the output stream.
751     *
752     * @throws IOException  if there is an I/O error.
753     */
754    private void writeObject(ObjectOutputStream stream) throws IOException {
755        stream.defaultWriteObject();
756        SerialUtilities.writePaint(this.wallPaint, stream);
757    }
758
759    /**
760     * Provides serialization support.
761     *
762     * @param stream  the input stream.
763     *
764     * @throws IOException  if there is an I/O error.
765     * @throws ClassNotFoundException  if there is a classpath problem.
766     */
767    private void readObject(ObjectInputStream stream) 
768        throws IOException, ClassNotFoundException {
769        stream.defaultReadObject();
770        this.wallPaint = SerialUtilities.readPaint(stream);
771    }
772
773}