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     * $Id: BarRenderer3D.java,v 1.10.2.6 2007/01/17 14:16:11 mungady Exp $
041     *
042     * Changes
043     * -------
044     * 31-Oct-2001 : First version, contributed by Serge V. Grachov (DG);
045     * 15-Nov-2001 : Modified to allow for null data values (DG);
046     * 13-Dec-2001 : Added tooltips (DG);
047     * 16-Jan-2002 : Added fix for single category or single series datasets, 
048     *               pointed out by Taoufik Romdhane (DG);
049     * 24-May-2002 : Incorporated tooltips into chart entities (DG);
050     * 11-Jun-2002 : Added check for (permitted) null info object, bug and fix 
051     *               reported by David Basten.  Also updated Javadocs. (DG);
052     * 19-Jun-2002 : Added code to draw labels on bars (TL);
053     * 26-Jun-2002 : Added bar clipping to avoid PRExceptions (DG);
054     * 05-Aug-2002 : Small modification to drawCategoryItem method to support URLs 
055     *               for HTML image maps (RA);
056     * 06-Aug-2002 : Value labels now use number formatter, thanks to Milo 
057     *               Simpson (DG);
058     * 08-Aug-2002 : Applied fixed in bug id 592218 (DG);
059     * 20-Sep-2002 : Added fix for categoryPaint by Rich Unger, and fixed errors 
060     *               reported by Checkstyle (DG);
061     * 24-Oct-2002 : Amendments for changes in CategoryDataset interface and 
062     *               CategoryToolTipGenerator interface (DG);
063     * 05-Nov-2002 : Replaced references to CategoryDataset with TableDataset (DG);
064     * 06-Nov-2002 : Moved to the com.jrefinery.chart.renderer package (DG);
065     * 28-Jan-2003 : Added an attribute to control the shading of the left and 
066     *               bottom walls in the plot background (DG);
067     * 25-Mar-2003 : Implemented Serializable (DG);
068     * 10-Apr-2003 : Removed category paint usage (DG);
069     * 13-May-2003 : Renamed VerticalBarRenderer3D --> BarRenderer3D and merged with
070     *               HorizontalBarRenderer3D (DG);
071     * 30-Jul-2003 : Modified entity constructor (CZ);
072     * 19-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
073     * 07-Oct-2003 : Added renderer state (DG);
074     * 08-Oct-2003 : Removed clipping (replaced with flag in CategoryPlot to 
075     *               control order in which the data items are processed) (DG);
076     * 20-Oct-2003 : Fixed bug (outline stroke not being used for bar 
077     *               outlines) (DG);
078     * 21-Oct-2003 : Bar width moved into CategoryItemRendererState (DG);
079     * 24-Nov-2003 : Fixed bug 846324 (item labels not showing) (DG);
080     * 27-Nov-2003 : Added code to respect maxBarWidth setting (DG);
081     * 02-Feb-2004 : Fixed bug where 'drawBarOutline' flag is not respected (DG);
082     * 10-Feb-2004 : Small change to drawItem() method to make cut-and-paste 
083     *               overriding easier (DG);
084     * 04-Oct-2004 : Fixed bug with item label positioning when plot alignment is 
085     *               horizontal (DG);
086     * 05-Nov-2004 : Modified drawItem() signature (DG);
087     * 20-Apr-2005 : Renamed CategoryLabelGenerator 
088     *               --> CategoryItemLabelGenerator (DG);
089     * 25-Apr-2005 : Override initialise() method to fix bug 1189642 (DG);
090     * 09-Jun-2005 : Use addEntityItem from super class (DG);
091     * ------------- JFREECHART 1.0.x ---------------------------------------------
092     * 07-Dec-2006 : Implemented equals() override (DG);
093     * 17-Jan-2007 : Fixed bug in drawDomainGridline() method (DG);
094     * 
095     */
096    
097    package org.jfree.chart.renderer.category;
098    
099    import java.awt.AlphaComposite;
100    import java.awt.Color;
101    import java.awt.Composite;
102    import java.awt.Font;
103    import java.awt.Graphics2D;
104    import java.awt.Image;
105    import java.awt.Paint;
106    import java.awt.Stroke;
107    import java.awt.geom.GeneralPath;
108    import java.awt.geom.Line2D;
109    import java.awt.geom.Point2D;
110    import java.awt.geom.Rectangle2D;
111    import java.io.IOException;
112    import java.io.ObjectInputStream;
113    import java.io.ObjectOutputStream;
114    import java.io.Serializable;
115    
116    import org.jfree.chart.Effect3D;
117    import org.jfree.chart.axis.CategoryAxis;
118    import org.jfree.chart.axis.ValueAxis;
119    import org.jfree.chart.entity.EntityCollection;
120    import org.jfree.chart.event.RendererChangeEvent;
121    import org.jfree.chart.labels.CategoryItemLabelGenerator;
122    import org.jfree.chart.labels.ItemLabelAnchor;
123    import org.jfree.chart.labels.ItemLabelPosition;
124    import org.jfree.chart.plot.CategoryPlot;
125    import org.jfree.chart.plot.Marker;
126    import org.jfree.chart.plot.Plot;
127    import org.jfree.chart.plot.PlotOrientation;
128    import org.jfree.chart.plot.PlotRenderingInfo;
129    import org.jfree.chart.plot.ValueMarker;
130    import org.jfree.data.Range;
131    import org.jfree.data.category.CategoryDataset;
132    import org.jfree.io.SerialUtilities;
133    import org.jfree.text.TextUtilities;
134    import org.jfree.ui.LengthAdjustmentType;
135    import org.jfree.ui.RectangleAnchor;
136    import org.jfree.ui.RectangleEdge;
137    import org.jfree.ui.TextAnchor;
138    import org.jfree.util.PaintUtilities;
139    import 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     */
145    public 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            setPositiveItemLabelPosition(p1);
193            ItemLabelPosition p2 = new ItemLabelPosition(ItemLabelAnchor.INSIDE12, 
194                    TextAnchor.TOP_CENTER);
195            setNegativeItemLabelPosition(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            // fill background...
307            Paint backgroundPaint = plot.getBackgroundPaint();
308            if (backgroundPaint != null) {
309                g2.setPaint(backgroundPaint);
310                g2.fill(clip);
311            }
312    
313            GeneralPath leftWall = new GeneralPath();
314            leftWall.moveTo(x0, y0);
315            leftWall.lineTo(x0, y2);
316            leftWall.lineTo(x1, y3);
317            leftWall.lineTo(x1, y1);
318            leftWall.closePath();
319            g2.setPaint(getWallPaint());
320            g2.fill(leftWall);
321    
322            GeneralPath bottomWall = new GeneralPath();
323            bottomWall.moveTo(x0, y0);
324            bottomWall.lineTo(x1, y1);
325            bottomWall.lineTo(x3, y1);
326            bottomWall.lineTo(x2, y0);
327            bottomWall.closePath();
328            g2.setPaint(getWallPaint());
329            g2.fill(bottomWall);
330    
331            // higlight the background corners...
332            g2.setPaint(Color.lightGray);
333            Line2D corner = new Line2D.Double(x0, y0, x1, y1);
334            g2.draw(corner);
335            corner.setLine(x1, y1, x1, y3);
336            g2.draw(corner);
337            corner.setLine(x1, y1, x3, y1);
338            g2.draw(corner);
339    
340            // draw background image, if there is one...
341            Image backgroundImage = plot.getBackgroundImage();
342            if (backgroundImage != null) {
343                Composite originalComposite = g2.getComposite();
344                g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, 
345                        plot.getBackgroundAlpha()));
346                g2.drawImage(backgroundImage, (int) x1, (int) y3, 
347                        (int) (x3 - x1 + 1), (int) (y1 - y3 + 1), null);
348                g2.setComposite(originalComposite);
349            }
350    
351        }
352    
353        /**
354         * Draws the outline for the plot.
355         *
356         * @param g2  the graphics device.
357         * @param plot  the plot.
358         * @param dataArea  the area inside the axes.
359         */
360        public void drawOutline(Graphics2D g2, CategoryPlot plot, 
361                                Rectangle2D dataArea) {
362    
363            float x0 = (float) dataArea.getX();
364            float x1 = x0 + (float) Math.abs(this.xOffset);
365            float x3 = (float) dataArea.getMaxX();
366            float x2 = x3 - (float) Math.abs(this.xOffset);
367    
368            float y0 = (float) dataArea.getMaxY();
369            float y1 = y0 - (float) Math.abs(this.yOffset);
370            float y3 = (float) dataArea.getMinY();
371            float y2 = y3 + (float) Math.abs(this.yOffset);
372    
373            GeneralPath clip = new GeneralPath();
374            clip.moveTo(x0, y0);
375            clip.lineTo(x0, y2);
376            clip.lineTo(x1, y3);
377            clip.lineTo(x3, y3);
378            clip.lineTo(x3, y1);
379            clip.lineTo(x2, y0);
380            clip.closePath();
381    
382            // put an outline around the data area...
383            Stroke outlineStroke = plot.getOutlineStroke();
384            Paint outlinePaint = plot.getOutlinePaint();
385            if ((outlineStroke != null) && (outlinePaint != null)) {
386                g2.setStroke(outlineStroke);
387                g2.setPaint(outlinePaint);
388                g2.draw(clip);
389            }
390    
391        }
392    
393        /**
394         * Draws a grid line against the domain axis.
395         *
396         * @param g2  the graphics device.
397         * @param plot  the plot.
398         * @param dataArea  the area for plotting data (not yet adjusted for any 
399         *                  3D effect).
400         * @param value  the Java2D value at which the grid line should be drawn.
401         *
402         */
403        public void drawDomainGridline(Graphics2D g2,
404                                       CategoryPlot plot,
405                                       Rectangle2D dataArea,
406                                       double value) {
407    
408            Line2D line1 = null;
409            Line2D line2 = null;
410            PlotOrientation orientation = plot.getOrientation();
411            if (orientation == PlotOrientation.HORIZONTAL) {
412                double y0 = value;
413                double y1 = value - getYOffset();
414                double x0 = dataArea.getMinX();
415                double x1 = x0 + getXOffset();
416                double x2 = dataArea.getMaxX();
417                line1 = new Line2D.Double(x0, y0, x1, y1);
418                line2 = new Line2D.Double(x1, y1, x2, y1);
419            }
420            else if (orientation == PlotOrientation.VERTICAL) {
421                double x0 = value;
422                double x1 = value + getXOffset();
423                double y0 = dataArea.getMaxY();
424                double y1 = y0 - getYOffset();
425                double y2 = dataArea.getMinY();
426                line1 = new Line2D.Double(x0, y0, x1, y1);
427                line2 = new Line2D.Double(x1, y1, x1, y2);
428            }
429            Paint paint = plot.getDomainGridlinePaint();
430            Stroke stroke = plot.getDomainGridlineStroke();
431            g2.setPaint(paint != null ? paint : Plot.DEFAULT_OUTLINE_PAINT);
432            g2.setStroke(stroke != null ? stroke : Plot.DEFAULT_OUTLINE_STROKE);
433            g2.draw(line1);
434            g2.draw(line2);
435    
436        }
437    
438        /**
439         * Draws a grid line against the range axis.
440         *
441         * @param g2  the graphics device.
442         * @param plot  the plot.
443         * @param axis  the value axis.
444         * @param dataArea  the area for plotting data (not yet adjusted for any 
445         *                  3D effect).
446         * @param value  the value at which the grid line should be drawn.
447         *
448         */
449        public void drawRangeGridline(Graphics2D g2,
450                                      CategoryPlot plot,
451                                      ValueAxis axis,
452                                      Rectangle2D dataArea,
453                                      double value) {
454    
455            Range range = axis.getRange();
456    
457            if (!range.contains(value)) {
458                return;
459            }
460    
461            Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(),
462                    dataArea.getY() + getYOffset(), dataArea.getWidth() 
463                    - getXOffset(), dataArea.getHeight() - getYOffset());
464    
465            Line2D line1 = null;
466            Line2D line2 = null;
467            PlotOrientation orientation = plot.getOrientation();
468            if (orientation == PlotOrientation.HORIZONTAL) {
469                double x0 = axis.valueToJava2D(value, adjusted, 
470                        plot.getRangeAxisEdge());
471                double x1 = x0 + getXOffset();
472                double y0 = dataArea.getMaxY();
473                double y1 = y0 - getYOffset();
474                double y2 = dataArea.getMinY();
475                line1 = new Line2D.Double(x0, y0, x1, y1);
476                line2 = new Line2D.Double(x1, y1, x1, y2);
477            }
478            else if (orientation == PlotOrientation.VERTICAL) {
479                double y0 = axis.valueToJava2D(value, adjusted, 
480                        plot.getRangeAxisEdge());
481                double y1 = y0 - getYOffset();
482                double x0 = dataArea.getMinX();
483                double x1 = x0 + getXOffset();
484                double x2 = dataArea.getMaxX();
485                line1 = new Line2D.Double(x0, y0, x1, y1);
486                line2 = new Line2D.Double(x1, y1, x2, y1);
487            }
488            Paint paint = plot.getRangeGridlinePaint();
489            Stroke stroke = plot.getRangeGridlineStroke();
490            g2.setPaint(paint != null ? paint : Plot.DEFAULT_OUTLINE_PAINT);
491            g2.setStroke(stroke != null ? stroke : Plot.DEFAULT_OUTLINE_STROKE);
492            g2.draw(line1);
493            g2.draw(line2);
494    
495        }
496    
497        /**
498         * Draws a range marker.
499         *
500         * @param g2  the graphics device.
501         * @param plot  the plot.
502         * @param axis  the value axis.
503         * @param marker  the marker.
504         * @param dataArea  the area for plotting data (not including 3D effect).
505         */
506        public void drawRangeMarker(Graphics2D g2,
507                                    CategoryPlot plot,
508                                    ValueAxis axis,
509                                    Marker marker,
510                                    Rectangle2D dataArea) {
511    
512            if (marker instanceof ValueMarker) {
513                ValueMarker vm = (ValueMarker) marker;
514                double value = vm.getValue();
515                Range range = axis.getRange();
516                if (!range.contains(value)) {
517                    return;
518                }
519    
520                Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(), 
521                        dataArea.getY() + getYOffset(), dataArea.getWidth() 
522                        - getXOffset(), dataArea.getHeight() - getYOffset());
523    
524                GeneralPath path = null;
525                PlotOrientation orientation = plot.getOrientation();
526                if (orientation == PlotOrientation.HORIZONTAL) {
527                    float x = (float) axis.valueToJava2D(value, adjusted, 
528                            plot.getRangeAxisEdge());
529                    float y = (float) adjusted.getMaxY();
530                    path = new GeneralPath();
531                    path.moveTo(x, y);
532                    path.lineTo((float) (x + getXOffset()), 
533                            y - (float) getYOffset());
534                    path.lineTo((float) (x + getXOffset()), 
535                            (float) (adjusted.getMinY() - getYOffset()));
536                    path.lineTo(x, (float) adjusted.getMinY());
537                    path.closePath();
538                }
539                else if (orientation == PlotOrientation.VERTICAL) {
540                    float y = (float) axis.valueToJava2D(value, adjusted, 
541                            plot.getRangeAxisEdge());
542                    float x = (float) dataArea.getX();
543                    path = new GeneralPath();
544                    path.moveTo(x, y);
545                    path.lineTo(x + (float) this.xOffset, y - (float) this.yOffset);
546                    path.lineTo((float) (adjusted.getMaxX() + this.xOffset), 
547                            y - (float) this.yOffset);
548                    path.lineTo((float) (adjusted.getMaxX()), y);
549                    path.closePath();
550                }
551                g2.setPaint(marker.getPaint());
552                g2.fill(path);
553                g2.setPaint(marker.getOutlinePaint());
554                g2.draw(path);
555            
556                String label = marker.getLabel();
557                RectangleAnchor anchor = marker.getLabelAnchor();
558                if (label != null) {
559                    Font labelFont = marker.getLabelFont();
560                    g2.setFont(labelFont);
561                    g2.setPaint(marker.getLabelPaint());
562                    Point2D coordinates = calculateRangeMarkerTextAnchorPoint(
563                            g2, orientation, dataArea, path.getBounds2D(), 
564                            marker.getLabelOffset(), LengthAdjustmentType.EXPAND, 
565                            anchor);
566                    TextUtilities.drawAlignedString(label, g2, 
567                            (float) coordinates.getX(), (float) coordinates.getY(), 
568                            marker.getLabelTextAnchor());
569                }
570            
571            }
572            else {
573                super.drawRangeMarker(g2, plot, axis, marker, dataArea);
574                // TODO: draw the interval marker with a 3D effect
575            }
576        }
577    
578        /**
579         * Draws a 3D bar to represent one data item.
580         *
581         * @param g2  the graphics device.
582         * @param state  the renderer state.
583         * @param dataArea  the area for plotting the data.
584         * @param plot  the plot.
585         * @param domainAxis  the domain axis.
586         * @param rangeAxis  the range axis.
587         * @param dataset  the dataset.
588         * @param row  the row index (zero-based).
589         * @param column  the column index (zero-based).
590         * @param pass  the pass index.
591         */
592        public void drawItem(Graphics2D g2,
593                             CategoryItemRendererState state,
594                             Rectangle2D dataArea,
595                             CategoryPlot plot,
596                             CategoryAxis domainAxis,
597                             ValueAxis rangeAxis,
598                             CategoryDataset dataset,
599                             int row,
600                             int column,
601                             int pass) {
602        
603            // check the value we are plotting...
604            Number dataValue = dataset.getValue(row, column);
605            if (dataValue == null) {
606                return;
607            }
608            
609            double value = dataValue.doubleValue();
610            
611            Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(),
612                    dataArea.getY() + getYOffset(), 
613                    dataArea.getWidth() - getXOffset(), 
614                    dataArea.getHeight() - getYOffset());
615    
616            PlotOrientation orientation = plot.getOrientation();
617            
618            double barW0 = calculateBarW0(plot, orientation, adjusted, domainAxis, 
619                    state, row, column);
620            double[] barL0L1 = calculateBarL0L1(value);
621            if (barL0L1 == null) {
622                return;  // the bar is not visible
623            }
624    
625            RectangleEdge edge = plot.getRangeAxisEdge();
626            double transL0 = rangeAxis.valueToJava2D(barL0L1[0], adjusted, edge);
627            double transL1 = rangeAxis.valueToJava2D(barL0L1[1], adjusted, edge);
628            double barL0 = Math.min(transL0, transL1);
629            double barLength = Math.abs(transL1 - transL0);
630            
631            // draw the bar...
632            Rectangle2D bar = null;
633            if (orientation == PlotOrientation.HORIZONTAL) {
634                bar = new Rectangle2D.Double(barL0, barW0, barLength, 
635                        state.getBarWidth());
636            }
637            else {
638                bar = new Rectangle2D.Double(barW0, barL0, state.getBarWidth(), 
639                        barLength);
640            }
641            Paint itemPaint = getItemPaint(row, column);
642            g2.setPaint(itemPaint);
643            g2.fill(bar);
644    
645            double x0 = bar.getMinX();
646            double x1 = x0 + getXOffset();
647            double x2 = bar.getMaxX();
648            double x3 = x2 + getXOffset();
649            
650            double y0 = bar.getMinY() - getYOffset();
651            double y1 = bar.getMinY();
652            double y2 = bar.getMaxY() - getYOffset();
653            double y3 = bar.getMaxY();
654            
655            GeneralPath bar3dRight = null;
656            GeneralPath bar3dTop = null;
657            if (barLength > 0.0) {
658                bar3dRight = new GeneralPath();
659                bar3dRight.moveTo((float) x2, (float) y3);
660                bar3dRight.lineTo((float) x2, (float) y1);
661                bar3dRight.lineTo((float) x3, (float) y0);
662                bar3dRight.lineTo((float) x3, (float) y2);
663                bar3dRight.closePath();
664    
665                if (itemPaint instanceof Color) {
666                    g2.setPaint(((Color) itemPaint).darker());
667                }
668                g2.fill(bar3dRight);
669            }
670    
671            bar3dTop = new GeneralPath();
672            bar3dTop.moveTo((float) x0, (float) y1);
673            bar3dTop.lineTo((float) x1, (float) y0);
674            bar3dTop.lineTo((float) x3, (float) y0);
675            bar3dTop.lineTo((float) x2, (float) y1);
676            bar3dTop.closePath();
677            g2.fill(bar3dTop);
678    
679            if (isDrawBarOutline() 
680                    && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
681                g2.setStroke(getItemOutlineStroke(row, column));
682                g2.setPaint(getItemOutlinePaint(row, column));
683                g2.draw(bar);
684                if (bar3dRight != null) {
685                    g2.draw(bar3dRight);
686                }
687                if (bar3dTop != null) {
688                    g2.draw(bar3dTop);
689                }
690            }
691    
692            CategoryItemLabelGenerator generator 
693                = getItemLabelGenerator(row, column);
694            if (generator != null && isItemLabelVisible(row, column)) {
695                drawItemLabel(g2, dataset, row, column, plot, generator, bar, 
696                        (value < 0.0));
697            }        
698    
699            // add an item entity, if this information is being collected
700            EntityCollection entities = state.getEntityCollection();
701            if (entities != null) {
702                GeneralPath barOutline = new GeneralPath();
703                barOutline.moveTo((float) x0, (float) y3);
704                barOutline.lineTo((float) x0, (float) y1);
705                barOutline.lineTo((float) x1, (float) y0);
706                barOutline.lineTo((float) x3, (float) y0);
707                barOutline.lineTo((float) x3, (float) y2);
708                barOutline.lineTo((float) x2, (float) y3);
709                barOutline.closePath();
710                addItemEntity(entities, dataset, row, column, barOutline);
711            }
712    
713        }
714        
715        /**
716         * Tests this renderer for equality with an arbitrary object.
717         * 
718         * @param obj  the object (<code>null</code> permitted).
719         * 
720         * @return A boolean.
721         */
722        public boolean equals(Object obj) {
723            if (obj == this) {
724                return true;
725            }
726            if (!(obj instanceof BarRenderer3D)) {
727                return false;
728            }
729            BarRenderer3D that = (BarRenderer3D) obj;
730            if (this.xOffset != that.xOffset) {
731                return false;
732            }
733            if (this.yOffset != that.yOffset) {
734                return false;
735            }
736            if (!PaintUtilities.equal(this.wallPaint, that.wallPaint)) {
737                return false;
738            }
739            return super.equals(obj);
740        }
741    
742        /**
743         * Provides serialization support.
744         *
745         * @param stream  the output stream.
746         *
747         * @throws IOException  if there is an I/O error.
748         */
749        private void writeObject(ObjectOutputStream stream) throws IOException {
750            stream.defaultWriteObject();
751            SerialUtilities.writePaint(this.wallPaint, stream);
752        }
753    
754        /**
755         * Provides serialization support.
756         *
757         * @param stream  the input stream.
758         *
759         * @throws IOException  if there is an I/O error.
760         * @throws ClassNotFoundException  if there is a classpath problem.
761         */
762        private void readObject(ObjectInputStream stream) 
763            throws IOException, ClassNotFoundException {
764            stream.defaultReadObject();
765            this.wallPaint = SerialUtilities.readPaint(stream);
766        }
767    
768    }