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
098package org.jfree.chart.renderer.category;
099
100import java.awt.AlphaComposite;
101import java.awt.Color;
102import java.awt.Composite;
103import java.awt.Font;
104import java.awt.Graphics2D;
105import java.awt.Image;
106import java.awt.Paint;
107import java.awt.Stroke;
108import java.awt.geom.GeneralPath;
109import java.awt.geom.Line2D;
110import java.awt.geom.Point2D;
111import java.awt.geom.Rectangle2D;
112import java.io.IOException;
113import java.io.ObjectInputStream;
114import java.io.ObjectOutputStream;
115import java.io.Serializable;
116
117import org.jfree.chart.Effect3D;
118import org.jfree.chart.axis.CategoryAxis;
119import org.jfree.chart.axis.ValueAxis;
120import org.jfree.chart.entity.EntityCollection;
121import org.jfree.chart.event.RendererChangeEvent;
122import org.jfree.chart.labels.CategoryItemLabelGenerator;
123import org.jfree.chart.labels.ItemLabelAnchor;
124import org.jfree.chart.labels.ItemLabelPosition;
125import org.jfree.chart.plot.CategoryPlot;
126import org.jfree.chart.plot.Marker;
127import org.jfree.chart.plot.Plot;
128import org.jfree.chart.plot.PlotOrientation;
129import org.jfree.chart.plot.PlotRenderingInfo;
130import org.jfree.chart.plot.ValueMarker;
131import org.jfree.data.Range;
132import org.jfree.data.category.CategoryDataset;
133import org.jfree.io.SerialUtilities;
134import org.jfree.text.TextUtilities;
135import org.jfree.ui.LengthAdjustmentType;
136import org.jfree.ui.RectangleAnchor;
137import org.jfree.ui.RectangleEdge;
138import org.jfree.ui.TextAnchor;
139import org.jfree.util.PaintUtilities;
140import 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 */
151public 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}