    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    }