    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2005, 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     * LineRenderer3D.java
    029     * -------------------
    030     * (C) Copyright 2004, 2005, by Tobias Selb and Contributors.
    031     *
    032     * Original Author:  Tobias Selb;
    033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
    034     *
    035     * $Id: LineRenderer3D.java,v 1.10.2.4 2005/10/25 20:54:16 mungady Exp $
    036     *
    037     * Changes
    038     * -------
    039     * 15-Oct-2004 : Version 1 (TS);
    040     * 05-Nov-2004 : Modified drawItem() signature (DG);
    041     * 11-Nov-2004 : Now uses ShapeUtilities class to translate shapes (DG);
    042     * 26-Jan-2005 : Update for changes in super class (DG);
    043     * 13-Apr-2005 : Check item visibility in drawItem() method (DG);
    044     * 09-Jun-2005 : Use addItemEntity() in drawItem() method (DG);
    045     * 10-Jun-2005 : Fixed capitalisation of setXOffset() and setYOffset() (DG);
    046     *
    047     */
    048
    049    package org.jfree.chart.renderer.category;
    050
    051    import java.awt.AlphaComposite;
    052    import java.awt.Color;
    053    import java.awt.Composite;
    054    import java.awt.Graphics2D;
    055    import java.awt.Image;
    056    import java.awt.Paint;
    057    import java.awt.Shape;
    058    import java.awt.Stroke;
    059    import java.awt.geom.GeneralPath;
    060    import java.awt.geom.Line2D;
    061    import java.awt.geom.Rectangle2D;
    062    import java.io.Serializable;
    063
    064    import org.jfree.chart.Effect3D;
    065    import org.jfree.chart.axis.CategoryAxis;
    066    import org.jfree.chart.axis.ValueAxis;
    067    import org.jfree.chart.entity.EntityCollection;
    068    import org.jfree.chart.event.RendererChangeEvent;
    069    import org.jfree.chart.plot.CategoryPlot;
    070    import org.jfree.chart.plot.Marker;
    071    import org.jfree.chart.plot.Plot;
    072    import org.jfree.chart.plot.PlotOrientation;
    073    import org.jfree.chart.plot.ValueMarker;
    074    import org.jfree.data.Range;
    075    import org.jfree.data.category.CategoryDataset;
    076    import org.jfree.util.ShapeUtilities;
    077
    078    /**
    079     * A line renderer with a 3D effect.
    080     *
    081     * @author Tobias Selb (http://www.uepselon.com)
    082     */
    083    public class LineRenderer3D extends LineAndShapeRenderer
    084                                implements Effect3D, Serializable {
    085
    086        /** For serialization. */
    087        private static final long serialVersionUID = 5467931468380928736L;
    088
    089        /** The default x-offset for the 3D effect. */
    090        public static final double DEFAULT_X_OFFSET = 12.0;
    091
    092        /** The default y-offset for the 3D effect. */
    093        public static final double DEFAULT_Y_OFFSET = 8.0;
    094
    095        /** The default wall paint. */
    096        public static final Paint DEFAULT_WALL_PAINT = new Color(0xDD, 0xDD, 0xDD);
    097
    098        /** The size of x-offset for the 3D effect. */
    099        private double xOffset;
    100
    101        /** The size of y-offset for the 3D effect. */
    102        private double yOffset;
    103
    104        /** The paint used to shade the left and lower 3D wall. */
    105        private transient Paint wallPaint;
    106
    107        /**
    108         * Creates a new renderer.
    109         */
    110        public LineRenderer3D() {
    111            super(true, false);  //Create a line renderer only
    112            this.xOffset = DEFAULT_X_OFFSET;
    113            this.yOffset = DEFAULT_Y_OFFSET;
    114            this.wallPaint = DEFAULT_WALL_PAINT;
    115        }
    116
    117        /**
    118         * Returns the x-offset for the 3D effect.
    119         *
    120         * @return The x-offset.
    121         */
    122        public double getXOffset() {
    123            return this.xOffset;
    124        }
    125
    126        /**
    127         * Returns the y-offset for the 3D effect.
    128         *
    129         * @return The y-offset.
    130         */
    131        public double getYOffset() {
    132            return this.yOffset;
    133        }
    134
    135        /**
    136         * Sets the x-offset.
    137         *
    138         * @param xOffset  the x-offset.
    139         */
    140        public void setXOffset(double xOffset) {
    141            this.xOffset = xOffset;
    142            notifyListeners(new RendererChangeEvent(this));
    143        }
    144
    145        /**
    146         * Sets the y-offset.
    147         *
    148         * @param yOffset  the y-offset.
    149         */
    150        public void setYOffset(double yOffset) {
    151            this.yOffset = yOffset;
    152            notifyListeners(new RendererChangeEvent(this));
    153        }
    154
    155        /**
    156         * Returns the paint used to highlight the left and bottom wall in the plot
    157         * background.
    158         *
    159         * @return The paint.
    160         */
    161        public Paint getWallPaint() {
    162            return this.wallPaint;
    163        }
    164
    165        /**
    166         * Sets the paint used to hightlight the left and bottom walls in the plot
    167         * background.
    168         *
    169         * @param paint  the paint.
    170         */
    171        public void setWallPaint(Paint paint) {
    172            this.wallPaint = paint;
    173            notifyListeners(new RendererChangeEvent(this));
    174        }
    175
    176        /**
    177         * Draws the background for the plot.
    178         *
    179         * @param g2  the graphics device.
    180         * @param plot  the plot.
    181         * @param dataArea  the area inside the axes.
    182         */
    183        public void drawBackground(Graphics2D g2, CategoryPlot plot,
    184                                   Rectangle2D dataArea) {
    185
    186            float x0 = (float) dataArea.getX();
    187            float x1 = x0 + (float) Math.abs(this.xOffset);
    188            float x3 = (float) dataArea.getMaxX();
    189            float x2 = x3 - (float) Math.abs(this.xOffset);
    190
    191            float y0 = (float) dataArea.getMaxY();
    192            float y1 = y0 - (float) Math.abs(this.yOffset);
    193            float y3 = (float) dataArea.getMinY();
    194            float y2 = y3 + (float) Math.abs(this.yOffset);
    195
    196            GeneralPath clip = new GeneralPath();
    197            clip.moveTo(x0, y0);
    198            clip.lineTo(x0, y2);
    199            clip.lineTo(x1, y3);
    200            clip.lineTo(x3, y3);
    201            clip.lineTo(x3, y1);
    202            clip.lineTo(x2, y0);
    203            clip.closePath();
    204
    205            // fill background...
    206            Paint backgroundPaint = plot.getBackgroundPaint();
    207            if (backgroundPaint != null) {
    208                g2.setPaint(backgroundPaint);
    209                g2.fill(clip);
    210            }
    211
    212            GeneralPath leftWall = new GeneralPath();
    213            leftWall.moveTo(x0, y0);
    214            leftWall.lineTo(x0, y2);
    215            leftWall.lineTo(x1, y3);
    216            leftWall.lineTo(x1, y1);
    217            leftWall.closePath();
    218            g2.setPaint(getWallPaint());
    219            g2.fill(leftWall);
    220
    221            GeneralPath bottomWall = new GeneralPath();
    222            bottomWall.moveTo(x0, y0);
    223            bottomWall.lineTo(x1, y1);
    224            bottomWall.lineTo(x3, y1);
    225            bottomWall.lineTo(x2, y0);
    226            bottomWall.closePath();
    227            g2.setPaint(getWallPaint());
    228            g2.fill(bottomWall);
    229
    230            // higlight the background corners...
    231            g2.setPaint(Color.lightGray);
    232            Line2D corner = new Line2D.Double(x0, y0, x1, y1);
    233            g2.draw(corner);
    234            corner.setLine(x1, y1, x1, y3);
    235            g2.draw(corner);
    236            corner.setLine(x1, y1, x3, y1);
    237            g2.draw(corner);
    238
    239            // draw background image, if there is one...
    240            Image backgroundImage = plot.getBackgroundImage();
    241            if (backgroundImage != null) {
    242                Composite originalComposite = g2.getComposite();
    243                g2.setComposite(AlphaComposite.getInstance(
    244                    AlphaComposite.SRC, plot.getBackgroundAlpha())
    245                );
    246                g2.drawImage(
    247                    backgroundImage,
    248                    (int) x1, (int) y3,
    249                    (int) (x3 - x1 + 1), (int) (y1 - y3 + 1),
    250                    null
    251                );
    252                g2.setComposite(originalComposite);
    253            }
    254
    255        }
    256
    257        /**
    258         * Draws the outline for the plot.
    259         *
    260         * @param g2  the graphics device.
    261         * @param plot  the plot.
    262         * @param dataArea  the area inside the axes.
    263         */
    264        public void drawOutline(Graphics2D g2, CategoryPlot plot,
    265                                Rectangle2D dataArea) {
    266
    267            float x0 = (float) dataArea.getX();
    268            float x1 = x0 + (float) Math.abs(this.xOffset);
    269            float x3 = (float) dataArea.getMaxX();
    270            float x2 = x3 - (float) Math.abs(this.xOffset);
    271
    272            float y0 = (float) dataArea.getMaxY();
    273            float y1 = y0 - (float) Math.abs(this.yOffset);
    274            float y3 = (float) dataArea.getMinY();
    275            float y2 = y3 + (float) Math.abs(this.yOffset);
    276
    277            GeneralPath clip = new GeneralPath();
    278            clip.moveTo(x0, y0);
    279            clip.lineTo(x0, y2);
    280            clip.lineTo(x1, y3);
    281            clip.lineTo(x3, y3);
    282            clip.lineTo(x3, y1);
    283            clip.lineTo(x2, y0);
    284            clip.closePath();
    285
    286            // put an outline around the data area...
    287            Stroke outlineStroke = plot.getOutlineStroke();
    288            Paint outlinePaint = plot.getOutlinePaint();
    289            if ((outlineStroke != null) && (outlinePaint != null)) {
    290                g2.setStroke(outlineStroke);
    291                g2.setPaint(outlinePaint);
    292                g2.draw(clip);
    293            }
    294
    295        }
    296
    297        /**
    298         * Draws a grid line against the domain axis.
    299         *
    300         * @param g2  the graphics device.
    301         * @param plot  the plot.
    302         * @param dataArea  the area for plotting data (not yet adjusted for any
    303         *                  3D effect).
    304         * @param value  the Java2D value at which the grid line should be drawn.
    305         *
    306         */
    307        public void drawDomainGridline(Graphics2D g2,
    308                                       CategoryPlot plot,
    309                                       Rectangle2D dataArea,
    310                                       double value) {
    311
    312            Line2D line1 = null;
    313            Line2D line2 = null;
    314            PlotOrientation orientation = plot.getOrientation();
    315            if (orientation == PlotOrientation.HORIZONTAL) {
    316                double y0 = value;
    317                double y1 = value - getYOffset();
    318                double x0 = dataArea.getMinX();
    319                double x1 = x0 + getXOffset();
    320                double x2 = dataArea.getMaxY();
    321                line1 = new Line2D.Double(x0, y0, x1, y1);
    322                line2 = new Line2D.Double(x1, y1, x2, y1);
    323            }
    324            else if (orientation == PlotOrientation.VERTICAL) {
    325                double x0 = value;
    326                double x1 = value + getXOffset();
    327                double y0 = dataArea.getMaxY();
    328                double y1 = y0 - getYOffset();
    329                double y2 = dataArea.getMinY();
    330                line1 = new Line2D.Double(x0, y0, x1, y1);
    331                line2 = new Line2D.Double(x1, y1, x1, y2);
    332            }
    333            g2.setPaint(plot.getDomainGridlinePaint());
    334            g2.setStroke(plot.getDomainGridlineStroke());
    335            g2.draw(line1);
    336            g2.draw(line2);
    337
    338        }
    339
    340        /**
    341         * Draws a grid line against the range axis.
    342         *
    343         * @param g2  the graphics device.
    344         * @param plot  the plot.
    345         * @param axis  the value axis.
    346         * @param dataArea  the area for plotting data (not yet adjusted for any
    347         *                  3D effect).
    348         * @param value  the value at which the grid line should be drawn.
    349         *
    350         */
    351        public void drawRangeGridline(Graphics2D g2,
    352                                      CategoryPlot plot,
    353                                      ValueAxis axis,
    354                                      Rectangle2D dataArea,
    355                                      double value) {
    356
    357            Range range = axis.getRange();
    358
    359            if (!range.contains(value)) {
    360                return;
    361            }
    362
    363            Rectangle2D adjusted = new Rectangle2D.Double(
    364                dataArea.getX(),
    365                dataArea.getY() + getYOffset(),
    366                dataArea.getWidth() - getXOffset(),
    367                dataArea.getHeight() - getYOffset()
    368            );
    369
    370            Line2D line1 = null;
    371            Line2D line2 = null;
    372            PlotOrientation orientation = plot.getOrientation();
    373            if (orientation == PlotOrientation.HORIZONTAL) {
    374                double x0 = axis.valueToJava2D(value, adjusted,
    375                        plot.getRangeAxisEdge());
    376                double x1 = x0 + getXOffset();
    377                double y0 = dataArea.getMaxY();
    378                double y1 = y0 - getYOffset();
    379                double y2 = dataArea.getMinY();
    380                line1 = new Line2D.Double(x0, y0, x1, y1);
    381                line2 = new Line2D.Double(x1, y1, x1, y2);
    382            }
    383            else if (orientation == PlotOrientation.VERTICAL) {
    384                double y0 = axis.valueToJava2D(value, adjusted,
    385                        plot.getRangeAxisEdge());
    386                double y1 = y0 - getYOffset();
    387                double x0 = dataArea.getMinX();
    388                double x1 = x0 + getXOffset();
    389                double x2 = dataArea.getMaxX();
    390                line1 = new Line2D.Double(x0, y0, x1, y1);
    391                line2 = new Line2D.Double(x1, y1, x2, y1);
    392            }
    393            g2.setPaint(plot.getRangeGridlinePaint());
    394            g2.setStroke(plot.getRangeGridlineStroke());
    395            g2.draw(line1);
    396            g2.draw(line2);
    397
    398        }
    399
    400        /**
    401         * Draws a range marker.
    402         *
    403         * @param g2  the graphics device.
    404         * @param plot  the plot.
    405         * @param axis  the value axis.
    406         * @param marker  the marker.
    407         * @param dataArea  the area for plotting data (not including 3D effect).
    408         */
    409        public void drawRangeMarker(Graphics2D g2,
    410                                    CategoryPlot plot,
    411                                    ValueAxis axis,
    412                                    Marker marker,
    413                                    Rectangle2D dataArea) {
    414
    415            if (marker instanceof ValueMarker) {
    416                ValueMarker vm = (ValueMarker) marker;
    417                double value = vm.getValue();
    418                Range range = axis.getRange();
    419                if (!range.contains(value)) {
    420                    return;
    421                }
    422
    423                Rectangle2D adjusted = new Rectangle2D.Double(
    424                    dataArea.getX(), dataArea.getY() + getYOffset(),
    425                    dataArea.getWidth() - getXOffset(),
    426                    dataArea.getHeight() - getYOffset()
    427                );
    428
    429                GeneralPath path = null;
    430                PlotOrientation orientation = plot.getOrientation();
    431                if (orientation == PlotOrientation.HORIZONTAL) {
    432                    float x = (float) axis.valueToJava2D(
    433                        value, adjusted, plot.getRangeAxisEdge()
    434                    );
    435                    float y = (float) adjusted.getMaxY();
    436                    path = new GeneralPath();
    437                    path.moveTo(x, y);
    438                    path.lineTo((float) (x + getXOffset()),
    439                            y - (float) getYOffset());
    440                    path.lineTo(
    441                        (float) (x + getXOffset()),
    442                        (float) (adjusted.getMinY() - getYOffset())
    443                    );
    444                    path.lineTo(x, (float) adjusted.getMinY());
    445                    path.closePath();
    446                }
    447                else if (orientation == PlotOrientation.VERTICAL) {
    448                    float y = (float) axis.valueToJava2D(
    449                        value, adjusted, plot.getRangeAxisEdge()
    450                    );
    451                    float x = (float) dataArea.getX();
    452                    path = new GeneralPath();
    453                    path.moveTo(x, y);
    454                    path.lineTo(x + (float) this.xOffset, y - (float) this.yOffset);
    455                    path.lineTo((float) (adjusted.getMaxX() + this.xOffset),
    456                            y - (float) this.yOffset);
    457                    path.lineTo((float) (adjusted.getMaxX()), y);
    458                    path.closePath();
    459                }
    460                g2.setPaint(marker.getPaint());
    461                g2.fill(path);
    462                g2.setPaint(marker.getOutlinePaint());
    463                g2.draw(path);
    464            }
    465        }
    466
    467       /**
    468         * Draw a single data item.
    469         *
    470         * @param g2  the graphics device.
    471         * @param state  the renderer state.
    472         * @param dataArea  the area in which the data is drawn.
    473         * @param plot  the plot.
    474         * @param domainAxis  the domain axis.
    475         * @param rangeAxis  the range axis.
    476         * @param dataset  the dataset.
    477         * @param row  the row index (zero-based).
    478         * @param column  the column index (zero-based).
    479         * @param pass  the pass index.
    480         */
    481        public void drawItem(Graphics2D g2,
    482                             CategoryItemRendererState state,
    483                             Rectangle2D dataArea,
    484                             CategoryPlot plot,
    485                             CategoryAxis domainAxis,
    486                             ValueAxis rangeAxis,
    487                             CategoryDataset dataset,
    488                             int row,
    489                             int column,
    490                             int pass) {
    491
    492            if (!getItemVisible(row, column)) {
    493                return;
    494            }
    495
    496            // nothing is drawn for null...
    497            Number v = dataset.getValue(row, column);
    498            if (v == null) {
    499                return;
    500            }
    501
    502            Rectangle2D adjusted = new Rectangle2D.Double(
    503                dataArea.getX(),
    504                dataArea.getY() + getYOffset(),
    505                dataArea.getWidth() - getXOffset(),
    506                dataArea.getHeight() - getYOffset()
    507            );
    508
    509            PlotOrientation orientation = plot.getOrientation();
    510
    511            // current data point...
    512            double x1 = domainAxis.getCategoryMiddle(
    513                column, getColumnCount(), adjusted, plot.getDomainAxisEdge()
    514            );
    515            double value = v.doubleValue();
    516            double y1 = rangeAxis.valueToJava2D(value, adjusted,
    517                    plot.getRangeAxisEdge());
    518
    519            Shape shape = getItemShape(row, column);
    520            if (orientation == PlotOrientation.HORIZONTAL) {
    521                shape = ShapeUtilities.createTranslatedShape(shape, y1, x1);
    522            }
    523            else if (orientation == PlotOrientation.VERTICAL) {
    524                shape = ShapeUtilities.createTranslatedShape(shape, x1, y1);
    525            }
    526
    527            if (getItemLineVisible(row, column)) {
    528                if (column != 0) {
    529
    530                    Number previousValue = dataset.getValue(row, column - 1);
    531                    if (previousValue != null) {
    532
    533                        // previous data point...
    534                        double previous = previousValue.doubleValue();
    535                        double x0 = domainAxis.getCategoryMiddle(
    536                            column - 1, getColumnCount(), adjusted,
    537                            plot.getDomainAxisEdge()
    538                        );
    539                        double y0 = rangeAxis.valueToJava2D(
    540                            previous, adjusted, plot.getRangeAxisEdge()
    541                        );
    542
    543                        double x2 = x0 + getXOffset();
    544                        double y2 = y0 - getYOffset();
    545                        double x3 = x1 + getXOffset();
    546                        double y3 = y1 - getYOffset();
    547
    548                        GeneralPath clip = new GeneralPath();
    549
    550                        if (orientation == PlotOrientation.HORIZONTAL) {
    551                            clip.moveTo((float) y0, (float) x0);
    552                            clip.lineTo((float) y1, (float) x1);
    553                            clip.lineTo((float) y3, (float) x3);
    554                            clip.lineTo((float) y2, (float) x2);
    555                            clip.lineTo((float) y0, (float) x0);
    556                            clip.closePath();
    557                        }
    558                        else if (orientation == PlotOrientation.VERTICAL) {
    559                            clip.moveTo((float) x0, (float) y0);
    560                            clip.lineTo((float) x1, (float) y1);
    561                            clip.lineTo((float) x3, (float) y3);
    562                            clip.lineTo((float) x2, (float) y2);
    563                            clip.lineTo((float) x0, (float) y0);
    564                            clip.closePath();
    565                        }
    566
    567                        g2.setPaint(getItemPaint(row, column));
    568                        g2.fill(clip);
    569                        g2.setStroke(getItemOutlineStroke(row, column));
    570                        g2.setPaint(getItemOutlinePaint(row, column));
    571                        g2.draw(clip);
    572                    }
    573                }
    574            }
    575
    576            // draw the item label if there is one...
    577            if (isItemLabelVisible(row, column)) {
    578                drawItemLabel(
    579                    g2, orientation, dataset, row, column, x1, y1, (value < 0.0)
    580                );
    581            }
    582
    583            // add an item entity, if this information is being collected
    584            EntityCollection entities = state.getEntityCollection();
    585            if (entities != null) {
    586                addItemEntity(entities, dataset, row, column, shape);
    587            }
    588
    589        }
    590
    591    }