    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, Serializable {
    147
    148        /** For serialization. */
    149        private static final long serialVersionUID = 7686976503536003636L;
    150
    151        /** The default x-offset for the 3D effect. */
    152        public static final double DEFAULT_X_OFFSET = 12.0;
    153
    154        /** The default y-offset for the 3D effect. */
    155        public static final double DEFAULT_Y_OFFSET = 8.0;
    156
    157        /** The default wall paint. */
    158        public static final Paint DEFAULT_WALL_PAINT = new Color(0xDD, 0xDD, 0xDD);
    159
    160        /** The size of x-offset for the 3D effect. */
    161        private double xOffset;
    162
    163        /** The size of y-offset for the 3D effect. */
    164        private double yOffset;
    165
    166        /** The paint used to shade the left and lower 3D wall. */
    167        private transient Paint wallPaint;
    168
    169        /**
    170         * Default constructor, creates a renderer with a default '3D effect'.
    171         */
    172        public BarRenderer3D() {
    173            this(DEFAULT_X_OFFSET, DEFAULT_Y_OFFSET);
    174        }
    175
    176        /**
    177         * Constructs a new renderer with the specified '3D effect'.
    178         *
    179         * @param xOffset  the x-offset for the 3D effect.
    180         * @param yOffset  the y-offset for the 3D effect.
    181         */
    182        public BarRenderer3D(double xOffset, double yOffset) {
    183
    184            super();
    185            this.xOffset = xOffset;
    186            this.yOffset = yOffset;
    187            this.wallPaint = DEFAULT_WALL_PAINT;
    188            // set the default item label positions
    189            ItemLabelPosition p1 = new ItemLabelPosition(ItemLabelAnchor.INSIDE12,
    190                    TextAnchor.TOP_CENTER);
    191            setBasePositiveItemLabelPosition(p1);
    192            ItemLabelPosition p2 = new ItemLabelPosition(ItemLabelAnchor.INSIDE12,
    193                    TextAnchor.TOP_CENTER);
    194            setBaseNegativeItemLabelPosition(p2);
    195
    196        }
    197
    198        /**
    199         * Returns the x-offset for the 3D effect.
    200         *
    201         * @return The 3D effect.
    202         *
    203         * @see #getYOffset()
    204         */
    205        public double getXOffset() {
    206            return this.xOffset;
    207        }
    208
    209        /**
    210         * Returns the y-offset for the 3D effect.
    211         *
    212         * @return The 3D effect.
    213         */
    214        public double getYOffset() {
    215            return this.yOffset;
    216        }
    217
    218        /**
    219         * Returns the paint used to highlight the left and bottom wall in the plot
    220         * background.
    221         *
    222         * @return The paint.
    223         *
    224         * @see #setWallPaint(Paint)
    225         */
    226        public Paint getWallPaint() {
    227            return this.wallPaint;
    228        }
    229
    230        /**
    231         * Sets the paint used to hightlight the left and bottom walls in the plot
    232         * background, and sends a {@link RendererChangeEvent} to all registered
    233         * listeners.
    234         *
    235         * @param paint  the paint (<code>null</code> not permitted).
    236         *
    237         * @see #getWallPaint()
    238         */
    239        public void setWallPaint(Paint paint) {
    240            if (paint == null) {
    241                throw new IllegalArgumentException("Null 'paint' argument.");
    242            }
    243            this.wallPaint = paint;
    244            fireChangeEvent();
    245        }
    246
    247
    248        /**
    249         * Initialises the renderer and returns a state object that will be passed
    250         * to subsequent calls to the drawItem method.  This method gets called
    251         * once at the start of the process of drawing a chart.
    252         *
    253         * @param g2  the graphics device.
    254         * @param dataArea  the area in which the data is to be plotted.
    255         * @param plot  the plot.
    256         * @param rendererIndex  the renderer index.
    257         * @param info  collects chart rendering information for return to caller.
    258         *
    259         * @return The renderer state.
    260         */
    261        public CategoryItemRendererState initialise(Graphics2D g2,
    262                                                    Rectangle2D dataArea,
    263                                                    CategoryPlot plot,
    264                                                    int rendererIndex,
    265                                                    PlotRenderingInfo info) {
    266
    267            Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(),
    268                    dataArea.getY() + getYOffset(), dataArea.getWidth()
    269                    - getXOffset(), dataArea.getHeight() - getYOffset());
    270            CategoryItemRendererState state = super.initialise(g2, adjusted, plot,
    271                    rendererIndex, info);
    272            return state;
    273
    274        }
    275
    276        /**
    277         * Draws the background for the plot.
    278         *
    279         * @param g2  the graphics device.
    280         * @param plot  the plot.
    281         * @param dataArea  the area inside the axes.
    282         */
    283        public void drawBackground(Graphics2D g2, CategoryPlot plot,
    284                                   Rectangle2D dataArea) {
    285
    286            float x0 = (float) dataArea.getX();
    287            float x1 = x0 + (float) Math.abs(this.xOffset);
    288            float x3 = (float) dataArea.getMaxX();
    289            float x2 = x3 - (float) Math.abs(this.xOffset);
    290
    291            float y0 = (float) dataArea.getMaxY();
    292            float y1 = y0 - (float) Math.abs(this.yOffset);
    293            float y3 = (float) dataArea.getMinY();
    294            float y2 = y3 + (float) Math.abs(this.yOffset);
    295
    296            GeneralPath clip = new GeneralPath();
    297            clip.moveTo(x0, y0);
    298            clip.lineTo(x0, y2);
    299            clip.lineTo(x1, y3);
    300            clip.lineTo(x3, y3);
    301            clip.lineTo(x3, y1);
    302            clip.lineTo(x2, y0);
    303            clip.closePath();
    304
    305            Composite originalComposite = g2.getComposite();
    306            g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
    307                    plot.getBackgroundAlpha()));
    308
    309            // fill background...
    310            Paint backgroundPaint = plot.getBackgroundPaint();
    311            if (backgroundPaint != null) {
    312                g2.setPaint(backgroundPaint);
    313                g2.fill(clip);
    314            }
    315
    316            GeneralPath leftWall = new GeneralPath();
    317            leftWall.moveTo(x0, y0);
    318            leftWall.lineTo(x0, y2);
    319            leftWall.lineTo(x1, y3);
    320            leftWall.lineTo(x1, y1);
    321            leftWall.closePath();
    322            g2.setPaint(getWallPaint());
    323            g2.fill(leftWall);
    324
    325            GeneralPath bottomWall = new GeneralPath();
    326            bottomWall.moveTo(x0, y0);
    327            bottomWall.lineTo(x1, y1);
    328            bottomWall.lineTo(x3, y1);
    329            bottomWall.lineTo(x2, y0);
    330            bottomWall.closePath();
    331            g2.setPaint(getWallPaint());
    332            g2.fill(bottomWall);
    333
    334            // highlight the background corners...
    335            g2.setPaint(Color.lightGray);
    336            Line2D corner = new Line2D.Double(x0, y0, x1, y1);
    337            g2.draw(corner);
    338            corner.setLine(x1, y1, x1, y3);
    339            g2.draw(corner);
    340            corner.setLine(x1, y1, x3, y1);
    341            g2.draw(corner);
    342
    343            // draw background image, if there is one...
    344            Image backgroundImage = plot.getBackgroundImage();
    345            if (backgroundImage != null) {
    346                Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX()
    347                        + getXOffset(), dataArea.getY(),
    348                        dataArea.getWidth() - getXOffset(),
    349                        dataArea.getHeight() - getYOffset());
    350                plot.drawBackgroundImage(g2, adjusted);
    351            }
    352
    353            g2.setComposite(originalComposite);
    354
    355        }
    356
    357        /**
    358         * Draws the outline for the plot.
    359         *
    360         * @param g2  the graphics device.
    361         * @param plot  the plot.
    362         * @param dataArea  the area inside the axes.
    363         */
    364        public void drawOutline(Graphics2D g2, CategoryPlot plot,
    365                                Rectangle2D dataArea) {
    366
    367            float x0 = (float) dataArea.getX();
    368            float x1 = x0 + (float) Math.abs(this.xOffset);
    369            float x3 = (float) dataArea.getMaxX();
    370            float x2 = x3 - (float) Math.abs(this.xOffset);
    371
    372            float y0 = (float) dataArea.getMaxY();
    373            float y1 = y0 - (float) Math.abs(this.yOffset);
    374            float y3 = (float) dataArea.getMinY();
    375            float y2 = y3 + (float) Math.abs(this.yOffset);
    376
    377            GeneralPath clip = new GeneralPath();
    378            clip.moveTo(x0, y0);
    379            clip.lineTo(x0, y2);
    380            clip.lineTo(x1, y3);
    381            clip.lineTo(x3, y3);
    382            clip.lineTo(x3, y1);
    383            clip.lineTo(x2, y0);
    384            clip.closePath();
    385
    386            // put an outline around the data area...
    387            Stroke outlineStroke = plot.getOutlineStroke();
    388            Paint outlinePaint = plot.getOutlinePaint();
    389            if ((outlineStroke != null) && (outlinePaint != null)) {
    390                g2.setStroke(outlineStroke);
    391                g2.setPaint(outlinePaint);
    392                g2.draw(clip);
    393            }
    394
    395        }
    396
    397        /**
    398         * Draws a grid line against the domain axis.
    399         *
    400         * @param g2  the graphics device.
    401         * @param plot  the plot.
    402         * @param dataArea  the area for plotting data (not yet adjusted for any
    403         *                  3D effect).
    404         * @param value  the Java2D value at which the grid line should be drawn.
    405         *
    406         */
    407        public void drawDomainGridline(Graphics2D g2,
    408                                       CategoryPlot plot,
    409                                       Rectangle2D dataArea,
    410                                       double value) {
    411
    412            Line2D line1 = null;
    413            Line2D line2 = null;
    414            PlotOrientation orientation = plot.getOrientation();
    415            if (orientation == PlotOrientation.HORIZONTAL) {
    416                double y0 = value;
    417                double y1 = value - getYOffset();
    418                double x0 = dataArea.getMinX();
    419                double x1 = x0 + getXOffset();
    420                double x2 = dataArea.getMaxX();
    421                line1 = new Line2D.Double(x0, y0, x1, y1);
    422                line2 = new Line2D.Double(x1, y1, x2, y1);
    423            }
    424            else if (orientation == PlotOrientation.VERTICAL) {
    425                double x0 = value;
    426                double x1 = value + getXOffset();
    427                double y0 = dataArea.getMaxY();
    428                double y1 = y0 - getYOffset();
    429                double y2 = dataArea.getMinY();
    430                line1 = new Line2D.Double(x0, y0, x1, y1);
    431                line2 = new Line2D.Double(x1, y1, x1, y2);
    432            }
    433            Paint paint = plot.getDomainGridlinePaint();
    434            Stroke stroke = plot.getDomainGridlineStroke();
    435            g2.setPaint(paint != null ? paint : Plot.DEFAULT_OUTLINE_PAINT);
    436            g2.setStroke(stroke != null ? stroke : Plot.DEFAULT_OUTLINE_STROKE);
    437            g2.draw(line1);
    438            g2.draw(line2);
    439
    440        }
    441
    442        /**
    443         * Draws a grid line against the range axis.
    444         *
    445         * @param g2  the graphics device.
    446         * @param plot  the plot.
    447         * @param axis  the value axis.
    448         * @param dataArea  the area for plotting data (not yet adjusted for any
    449         *                  3D effect).
    450         * @param value  the value at which the grid line should be drawn.
    451         *
    452         */
    453        public void drawRangeGridline(Graphics2D g2,
    454                                      CategoryPlot plot,
    455                                      ValueAxis axis,
    456                                      Rectangle2D dataArea,
    457                                      double value) {
    458
    459            Range range = axis.getRange();
    460
    461            if (!range.contains(value)) {
    462                return;
    463            }
    464
    465            Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(),
    466                    dataArea.getY() + getYOffset(), dataArea.getWidth()
    467                    - getXOffset(), dataArea.getHeight() - getYOffset());
    468
    469            Line2D line1 = null;
    470            Line2D line2 = null;
    471            PlotOrientation orientation = plot.getOrientation();
    472            if (orientation == PlotOrientation.HORIZONTAL) {
    473                double x0 = axis.valueToJava2D(value, adjusted,
    474                        plot.getRangeAxisEdge());
    475                double x1 = x0 + getXOffset();
    476                double y0 = dataArea.getMaxY();
    477                double y1 = y0 - getYOffset();
    478                double y2 = dataArea.getMinY();
    479                line1 = new Line2D.Double(x0, y0, x1, y1);
    480                line2 = new Line2D.Double(x1, y1, x1, y2);
    481            }
    482            else if (orientation == PlotOrientation.VERTICAL) {
    483                double y0 = axis.valueToJava2D(value, adjusted,
    484                        plot.getRangeAxisEdge());
    485                double y1 = y0 - getYOffset();
    486                double x0 = dataArea.getMinX();
    487                double x1 = x0 + getXOffset();
    488                double x2 = dataArea.getMaxX();
    489                line1 = new Line2D.Double(x0, y0, x1, y1);
    490                line2 = new Line2D.Double(x1, y1, x2, y1);
    491            }
    492            Paint paint = plot.getRangeGridlinePaint();
    493            Stroke stroke = plot.getRangeGridlineStroke();
    494            g2.setPaint(paint != null ? paint : Plot.DEFAULT_OUTLINE_PAINT);
    495            g2.setStroke(stroke != null ? stroke : Plot.DEFAULT_OUTLINE_STROKE);
    496            g2.draw(line1);
    497            g2.draw(line2);
    498
    499        }
    500
    501        /**
    502         * Draws a range marker.
    503         *
    504         * @param g2  the graphics device.
    505         * @param plot  the plot.
    506         * @param axis  the value axis.
    507         * @param marker  the marker.
    508         * @param dataArea  the area for plotting data (not including 3D effect).
    509         */
    510        public void drawRangeMarker(Graphics2D g2,
    511                                    CategoryPlot plot,
    512                                    ValueAxis axis,
    513                                    Marker marker,
    514                                    Rectangle2D dataArea) {
    515
    516
    517            Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(),
    518                    dataArea.getY() + getYOffset(), dataArea.getWidth()
    519                    - getXOffset(), dataArea.getHeight() - getYOffset());
    520            if (marker instanceof ValueMarker) {
    521                ValueMarker vm = (ValueMarker) marker;
    522                double value = vm.getValue();
    523                Range range = axis.getRange();
    524                if (!range.contains(value)) {
    525                    return;
    526                }
    527
    528                GeneralPath path = null;
    529                PlotOrientation orientation = plot.getOrientation();
    530                if (orientation == PlotOrientation.HORIZONTAL) {
    531                    float x = (float) axis.valueToJava2D(value, adjusted,
    532                            plot.getRangeAxisEdge());
    533                    float y = (float) adjusted.getMaxY();
    534                    path = new GeneralPath();
    535                    path.moveTo(x, y);
    536                    path.lineTo((float) (x + getXOffset()),
    537                            y - (float) getYOffset());
    538                    path.lineTo((float) (x + getXOffset()),
    539                            (float) (adjusted.getMinY() - getYOffset()));
    540                    path.lineTo(x, (float) adjusted.getMinY());
    541                    path.closePath();
    542                }
    543                else if (orientation == PlotOrientation.VERTICAL) {
    544                    float y = (float) axis.valueToJava2D(value, adjusted,
    545                            plot.getRangeAxisEdge());
    546                    float x = (float) dataArea.getX();
    547                    path = new GeneralPath();
    548                    path.moveTo(x, y);
    549                    path.lineTo(x + (float) this.xOffset, y - (float) this.yOffset);
    550                    path.lineTo((float) (adjusted.getMaxX() + this.xOffset),
    551                            y - (float) this.yOffset);
    552                    path.lineTo((float) (adjusted.getMaxX()), y);
    553                    path.closePath();
    554                }
    555                g2.setPaint(marker.getPaint());
    556                g2.fill(path);
    557                g2.setPaint(marker.getOutlinePaint());
    558                g2.draw(path);
    559
    560                String label = marker.getLabel();
    561                RectangleAnchor anchor = marker.getLabelAnchor();
    562                if (label != null) {
    563                    Font labelFont = marker.getLabelFont();
    564                    g2.setFont(labelFont);
    565                    g2.setPaint(marker.getLabelPaint());
    566                    Point2D coordinates = calculateRangeMarkerTextAnchorPoint(
    567                            g2, orientation, dataArea, path.getBounds2D(),
    568                            marker.getLabelOffset(), LengthAdjustmentType.EXPAND,
    569                            anchor);
    570                    TextUtilities.drawAlignedString(label, g2,
    571                            (float) coordinates.getX(), (float) coordinates.getY(),
    572                            marker.getLabelTextAnchor());
    573                }
    574
    575            }
    576            else {
    577                super.drawRangeMarker(g2, plot, axis, marker, adjusted);
    578                // TODO: draw the interval marker with a 3D effect
    579            }
    580        }
    581
    582        /**
    583         * Draws a 3D bar to represent one data item.
    584         *
    585         * @param g2  the graphics device.
    586         * @param state  the renderer state.
    587         * @param dataArea  the area for plotting the data.
    588         * @param plot  the plot.
    589         * @param domainAxis  the domain axis.
    590         * @param rangeAxis  the range axis.
    591         * @param dataset  the dataset.
    592         * @param row  the row index (zero-based).
    593         * @param column  the column index (zero-based).
    594         * @param pass  the pass index.
    595         */
    596        public void drawItem(Graphics2D g2,
    597                             CategoryItemRendererState state,
    598                             Rectangle2D dataArea,
    599                             CategoryPlot plot,
    600                             CategoryAxis domainAxis,
    601                             ValueAxis rangeAxis,
    602                             CategoryDataset dataset,
    603                             int row,
    604                             int column,
    605                             int pass) {
    606
    607            // check the value we are plotting...
    608            Number dataValue = dataset.getValue(row, column);
    609            if (dataValue == null) {
    610                return;
    611            }
    612
    613            double value = dataValue.doubleValue();
    614
    615            Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(),
    616                    dataArea.getY() + getYOffset(),
    617                    dataArea.getWidth() - getXOffset(),
    618                    dataArea.getHeight() - getYOffset());
    619
    620            PlotOrientation orientation = plot.getOrientation();
    621
    622            double barW0 = calculateBarW0(plot, orientation, adjusted, domainAxis,
    623                    state, row, column);
    624            double[] barL0L1 = calculateBarL0L1(value);
    625            if (barL0L1 == null) {
    626                return;  // the bar is not visible
    627            }
    628
    629            RectangleEdge edge = plot.getRangeAxisEdge();
    630            double transL0 = rangeAxis.valueToJava2D(barL0L1[0], adjusted, edge);
    631            double transL1 = rangeAxis.valueToJava2D(barL0L1[1], adjusted, edge);
    632            double barL0 = Math.min(transL0, transL1);
    633            double barLength = Math.abs(transL1 - transL0);
    634
    635            // draw the bar...
    636            Rectangle2D bar = null;
    637            if (orientation == PlotOrientation.HORIZONTAL) {
    638                bar = new Rectangle2D.Double(barL0, barW0, barLength,
    639                        state.getBarWidth());
    640            }
    641            else {
    642                bar = new Rectangle2D.Double(barW0, barL0, state.getBarWidth(),
    643                        barLength);
    644            }
    645            Paint itemPaint = getItemPaint(row, column);
    646            g2.setPaint(itemPaint);
    647            g2.fill(bar);
    648
    649            double x0 = bar.getMinX();
    650            double x1 = x0 + getXOffset();
    651            double x2 = bar.getMaxX();
    652            double x3 = x2 + getXOffset();
    653
    654            double y0 = bar.getMinY() - getYOffset();
    655            double y1 = bar.getMinY();
    656            double y2 = bar.getMaxY() - getYOffset();
    657            double y3 = bar.getMaxY();
    658
    659            GeneralPath bar3dRight = null;
    660            GeneralPath bar3dTop = null;
    661            if (barLength > 0.0) {
    662                bar3dRight = new GeneralPath();
    663                bar3dRight.moveTo((float) x2, (float) y3);
    664                bar3dRight.lineTo((float) x2, (float) y1);
    665                bar3dRight.lineTo((float) x3, (float) y0);
    666                bar3dRight.lineTo((float) x3, (float) y2);
    667                bar3dRight.closePath();
    668
    669                if (itemPaint instanceof Color) {
    670                    g2.setPaint(((Color) itemPaint).darker());
    671                }
    672                g2.fill(bar3dRight);
    673            }
    674
    675            bar3dTop = new GeneralPath();
    676            bar3dTop.moveTo((float) x0, (float) y1);
    677            bar3dTop.lineTo((float) x1, (float) y0);
    678            bar3dTop.lineTo((float) x3, (float) y0);
    679            bar3dTop.lineTo((float) x2, (float) y1);
    680            bar3dTop.closePath();
    681            g2.fill(bar3dTop);
    682
    683            if (isDrawBarOutline()
    684                    && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
    685                g2.setStroke(getItemOutlineStroke(row, column));
    686                g2.setPaint(getItemOutlinePaint(row, column));
    687                g2.draw(bar);
    688                if (bar3dRight != null) {
    689                    g2.draw(bar3dRight);
    690                }
    691                if (bar3dTop != null) {
    692                    g2.draw(bar3dTop);
    693                }
    694            }
    695
    696            CategoryItemLabelGenerator generator
    697                = getItemLabelGenerator(row, column);
    698            if (generator != null && isItemLabelVisible(row, column)) {
    699                drawItemLabel(g2, dataset, row, column, plot, generator, bar,
    700                        (value < 0.0));
    701            }
    702
    703            // add an item entity, if this information is being collected
    704            EntityCollection entities = state.getEntityCollection();
    705            if (entities != null) {
    706                GeneralPath barOutline = new GeneralPath();
    707                barOutline.moveTo((float) x0, (float) y3);
    708                barOutline.lineTo((float) x0, (float) y1);
    709                barOutline.lineTo((float) x1, (float) y0);
    710                barOutline.lineTo((float) x3, (float) y0);
    711                barOutline.lineTo((float) x3, (float) y2);
    712                barOutline.lineTo((float) x2, (float) y3);
    713                barOutline.closePath();
    714                addItemEntity(entities, dataset, row, column, barOutline);
    715            }
    716
    717        }
    718
    719        /**
    720         * Tests this renderer for equality with an arbitrary object.
    721         *
    722         * @param obj  the object (<code>null</code> permitted).
    723         *
    724         * @return A boolean.
    725         */
    726        public boolean equals(Object obj) {
    727            if (obj == this) {
    728                return true;
    729            }
    730            if (!(obj instanceof BarRenderer3D)) {
    731                return false;
    732            }
    733            BarRenderer3D that = (BarRenderer3D) obj;
    734            if (this.xOffset != that.xOffset) {
    735                return false;
    736            }
    737            if (this.yOffset != that.yOffset) {
    738                return false;
    739            }
    740            if (!PaintUtilities.equal(this.wallPaint, that.wallPaint)) {
    741                return false;
    742            }
    743            return super.equals(obj);
    744        }
    745
    746        /**
    747         * Provides serialization support.
    748         *
    749         * @param stream  the output stream.
    750         *
    751         * @throws IOException  if there is an I/O error.
    752         */
    753        private void writeObject(ObjectOutputStream stream) throws IOException {
    754            stream.defaultWriteObject();
    755            SerialUtilities.writePaint(this.wallPaint, stream);
    756        }
    757
    758        /**
    759         * Provides serialization support.
    760         *
    761         * @param stream  the input stream.
    762         *
    763         * @throws IOException  if there is an I/O error.
    764         * @throws ClassNotFoundException  if there is a classpath problem.
    765         */
    766        private void readObject(ObjectInputStream stream)
    767            throws IOException, ClassNotFoundException {
    768            stream.defaultReadObject();
    769            this.wallPaint = SerialUtilities.readPaint(stream);
    770        }
    771
    772    }