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    }