    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
    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            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            fireChangeEvent();
    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    }