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