    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
    006     *
    007     * Project Info:  http://www.jfree.org/jfreechart/index.html
    008     *
    009     * This library is free software; you can redistribute it and/or modify it
    010     * under the terms of the GNU Lesser General Public License as published by
    011     * the Free Software Foundation; either version 2.1 of the License, or
    012     * (at your option) any later version.
    013     *
    014     * This library is distributed in the hope that it will be useful, but
    015     * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    016     * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
    017     * License for more details.
    018     *
    019     * You should have received a copy of the GNU Lesser General Public
    020     * License along with this library; if not, write to the Free Software
    021     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
    022     * USA.
    023     *
    024     * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
    025     * in the United States and other countries.]
    026     *
    027     * ------------------
    028     * LegendGraphic.java
    029     * ------------------
    030     * (C) Copyright 2004-2007, by Object Refinery Limited.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   -;
    034     *
    035     * Changes
    036     * -------
    037     * 26-Oct-2004 : Version 1 (DG);
    038     * 21-Jan-2005 : Modified return type of RectangleAnchor.coordinates()
    039     *               method (DG);
    040     * 20-Apr-2005 : Added new draw() method (DG);
    041     * 13-May-2005 : Fixed to respect margin, border and padding settings (DG);
    042     * 01-Sep-2005 : Implemented PublicCloneable (DG);
    043     * ------------- JFREECHART 1.0.x ---------------------------------------------
    044     * 13-Dec-2006 : Added fillPaintTransformer attribute, so legend graphics can
    045     *               display gradient paint correctly, updated equals() and
    046     *               corrected clone() (DG);
    047     * 01-Aug-2007 : Updated API docs (DG);
    048     *
    049     */
    050
    051    package org.jfree.chart.title;
    052
    053    import java.awt.GradientPaint;
    054    import java.awt.Graphics2D;
    055    import java.awt.Paint;
    056    import java.awt.Shape;
    057    import java.awt.Stroke;
    058    import java.awt.geom.Point2D;
    059    import java.awt.geom.Rectangle2D;
    060    import java.io.IOException;
    061    import java.io.ObjectInputStream;
    062    import java.io.ObjectOutputStream;
    063
    064    import org.jfree.chart.block.AbstractBlock;
    065    import org.jfree.chart.block.Block;
    066    import org.jfree.chart.block.LengthConstraintType;
    067    import org.jfree.chart.block.RectangleConstraint;
    068    import org.jfree.io.SerialUtilities;
    069    import org.jfree.ui.GradientPaintTransformer;
    070    import org.jfree.ui.RectangleAnchor;
    071    import org.jfree.ui.Size2D;
    072    import org.jfree.ui.StandardGradientPaintTransformer;
    073    import org.jfree.util.ObjectUtilities;
    074    import org.jfree.util.PaintUtilities;
    075    import org.jfree.util.PublicCloneable;
    076    import org.jfree.util.ShapeUtilities;
    077
    078    /**
    079     * The graphical item within a legend item.
    080     */
    081    public class LegendGraphic extends AbstractBlock
    082                               implements Block, PublicCloneable {
    083
    084        /** For serialization. */
    085        static final long serialVersionUID = -1338791523854985009L;
    086
    087        /**
    088         * A flag that controls whether or not the shape is visible - see also
    089         * lineVisible.
    090         */
    091        private boolean shapeVisible;
    092
    093        /**
    094         * The shape to display.  To allow for accurate positioning, the center
    095         * of the shape should be at (0, 0).
    096         */
    097        private transient Shape shape;
    098
    099        /**
    100         * Defines the location within the block to which the shape will be aligned.
    101         */
    102        private RectangleAnchor shapeLocation;
    103
    104        /**
    105         * Defines the point on the shape's bounding rectangle that will be
    106         * aligned to the drawing location when the shape is rendered.
    107         */
    108        private RectangleAnchor shapeAnchor;
    109
    110        /** A flag that controls whether or not the shape is filled. */
    111        private boolean shapeFilled;
    112
    113        /** The fill paint for the shape. */
    114        private transient Paint fillPaint;
    115
    116        /**
    117         * The fill paint transformer (used if the fillPaint is an instance of
    118         * GradientPaint).
    119         *
    120         * @since 1.0.4
    121         */
    122        private GradientPaintTransformer fillPaintTransformer;
    123
    124        /** A flag that controls whether or not the shape outline is visible. */
    125        private boolean shapeOutlineVisible;
    126
    127        /** The outline paint for the shape. */
    128        private transient Paint outlinePaint;
    129
    130        /** The outline stroke for the shape. */
    131        private transient Stroke outlineStroke;
    132
    133        /**
    134         * A flag that controls whether or not the line is visible - see also
    135         * shapeVisible.
    136         */
    137        private boolean lineVisible;
    138
    139        /** The line. */
    140        private transient Shape line;
    141
    142        /** The line stroke. */
    143        private transient Stroke lineStroke;
    144
    145        /** The line paint. */
    146        private transient Paint linePaint;
    147
    148        /**
    149         * Creates a new legend graphic.
    150         *
    151         * @param shape  the shape (<code>null</code> not permitted).
    152         * @param fillPaint  the fill paint (<code>null</code> not permitted).
    153         */
    154        public LegendGraphic(Shape shape, Paint fillPaint) {
    155            if (shape == null) {
    156                throw new IllegalArgumentException("Null 'shape' argument.");
    157            }
    158            if (fillPaint == null) {
    159                throw new IllegalArgumentException("Null 'fillPaint' argument.");
    160            }
    161            this.shapeVisible = true;
    162            this.shape = shape;
    163            this.shapeAnchor = RectangleAnchor.CENTER;
    164            this.shapeLocation = RectangleAnchor.CENTER;
    165            this.shapeFilled = true;
    166            this.fillPaint = fillPaint;
    167            this.fillPaintTransformer = new StandardGradientPaintTransformer();
    168            setPadding(2.0, 2.0, 2.0, 2.0);
    169        }
    170
    171        /**
    172         * Returns a flag that controls whether or not the shape
    173         * is visible.
    174         *
    175         * @return A boolean.
    176         *
    177         * @see #setShapeVisible(boolean)
    178         */
    179        public boolean isShapeVisible() {
    180            return this.shapeVisible;
    181        }
    182
    183        /**
    184         * Sets a flag that controls whether or not the shape is
    185         * visible.
    186         *
    187         * @param visible  the flag.
    188         *
    189         * @see #isShapeVisible()
    190         */
    191        public void setShapeVisible(boolean visible) {
    192            this.shapeVisible = visible;
    193        }
    194
    195        /**
    196         * Returns the shape.
    197         *
    198         * @return The shape.
    199         *
    200         * @see #setShape(Shape)
    201         */
    202        public Shape getShape() {
    203            return this.shape;
    204        }
    205
    206        /**
    207         * Sets the shape.
    208         *
    209         * @param shape  the shape.
    210         *
    211         * @see #getShape()
    212         */
    213        public void setShape(Shape shape) {
    214            this.shape = shape;
    215        }
    216
    217        /**
    218         * Returns a flag that controls whether or not the shapes
    219         * are filled.
    220         *
    221         * @return A boolean.
    222         *
    223         * @see #setShapeFilled(boolean)
    224         */
    225        public boolean isShapeFilled() {
    226            return this.shapeFilled;
    227        }
    228
    229        /**
    230         * Sets a flag that controls whether or not the shape is
    231         * filled.
    232         *
    233         * @param filled  the flag.
    234         *
    235         * @see #isShapeFilled()
    236         */
    237        public void setShapeFilled(boolean filled) {
    238            this.shapeFilled = filled;
    239        }
    240
    241        /**
    242         * Returns the paint used to fill the shape.
    243         *
    244         * @return The fill paint.
    245         *
    246         * @see #setFillPaint(Paint)
    247         */
    248        public Paint getFillPaint() {
    249            return this.fillPaint;
    250        }
    251
    252        /**
    253         * Sets the paint used to fill the shape.
    254         *
    255         * @param paint  the paint.
    256         *
    257         * @see #getFillPaint()
    258         */
    259        public void setFillPaint(Paint paint) {
    260            this.fillPaint = paint;
    261        }
    262
    263        /**
    264         * Returns the transformer used when the fill paint is an instance of
    265         * <code>GradientPaint</code>.
    266         *
    267         * @return The transformer (never <code>null</code>).
    268         *
    269         * @since 1.0.4.
    270         *
    271         * @see #setFillPaintTransformer(GradientPaintTransformer)
    272         */
    273        public GradientPaintTransformer getFillPaintTransformer() {
    274            return this.fillPaintTransformer;
    275        }
    276
    277        /**
    278         * Sets the transformer used when the fill paint is an instance of
    279         * <code>GradientPaint</code>.
    280         *
    281         * @param transformer  the transformer (<code>null</code> not permitted).
    282         *
    283         * @since 1.0.4
    284         *
    285         * @see #getFillPaintTransformer()
    286         */
    287        public void setFillPaintTransformer(GradientPaintTransformer transformer) {
    288            if (transformer == null) {
    289                throw new IllegalArgumentException("Null 'transformer' argument.");
    290            }
    291            this.fillPaintTransformer = transformer;
    292        }
    293
    294        /**
    295         * Returns a flag that controls whether the shape outline is visible.
    296         *
    297         * @return A boolean.
    298         *
    299         * @see #setShapeOutlineVisible(boolean)
    300         */
    301        public boolean isShapeOutlineVisible() {
    302            return this.shapeOutlineVisible;
    303        }
    304
    305        /**
    306         * Sets a flag that controls whether or not the shape outline
    307         * is visible.
    308         *
    309         * @param visible  the flag.
    310         *
    311         * @see #isShapeOutlineVisible()
    312         */
    313        public void setShapeOutlineVisible(boolean visible) {
    314            this.shapeOutlineVisible = visible;
    315        }
    316
    317        /**
    318         * Returns the outline paint.
    319         *
    320         * @return The paint.
    321         *
    322         * @see #setOutlinePaint(Paint)
    323         */
    324        public Paint getOutlinePaint() {
    325            return this.outlinePaint;
    326        }
    327
    328        /**
    329         * Sets the outline paint.
    330         *
    331         * @param paint  the paint.
    332         *
    333         * @see #getOutlinePaint()
    334         */
    335        public void setOutlinePaint(Paint paint) {
    336            this.outlinePaint = paint;
    337        }
    338
    339        /**
    340         * Returns the outline stroke.
    341         *
    342         * @return The stroke.
    343         *
    344         * @see #setOutlineStroke(Stroke)
    345         */
    346        public Stroke getOutlineStroke() {
    347            return this.outlineStroke;
    348        }
    349
    350        /**
    351         * Sets the outline stroke.
    352         *
    353         * @param stroke  the stroke.
    354         *
    355         * @see #getOutlineStroke()
    356         */
    357        public void setOutlineStroke(Stroke stroke) {
    358            this.outlineStroke = stroke;
    359        }
    360
    361        /**
    362         * Returns the shape anchor.
    363         *
    364         * @return The shape anchor.
    365         *
    366         * @see #getShapeAnchor()
    367         */
    368        public RectangleAnchor getShapeAnchor() {
    369            return this.shapeAnchor;
    370        }
    371
    372        /**
    373         * Sets the shape anchor.  This defines a point on the shapes bounding
    374         * rectangle that will be used to align the shape to a location.
    375         *
    376         * @param anchor  the anchor (<code>null</code> not permitted).
    377         *
    378         * @see #setShapeAnchor(RectangleAnchor)
    379         */
    380        public void setShapeAnchor(RectangleAnchor anchor) {
    381            if (anchor == null) {
    382                throw new IllegalArgumentException("Null 'anchor' argument.");
    383            }
    384            this.shapeAnchor = anchor;
    385        }
    386
    387        /**
    388         * Returns the shape location.
    389         *
    390         * @return The shape location.
    391         *
    392         * @see #setShapeLocation(RectangleAnchor)
    393         */
    394        public RectangleAnchor getShapeLocation() {
    395            return this.shapeLocation;
    396        }
    397
    398        /**
    399         * Sets the shape location.  This defines a point within the drawing
    400         * area that will be used to align the shape to.
    401         *
    402         * @param location  the location (<code>null</code> not permitted).
    403         *
    404         * @see #getShapeLocation()
    405         */
    406        public void setShapeLocation(RectangleAnchor location) {
    407            if (location == null) {
    408                throw new IllegalArgumentException("Null 'location' argument.");
    409            }
    410            this.shapeLocation = location;
    411        }
    412
    413        /**
    414         * Returns the flag that controls whether or not the line is visible.
    415         *
    416         * @return A boolean.
    417         *
    418         * @see #setLineVisible(boolean)
    419         */
    420        public boolean isLineVisible() {
    421            return this.lineVisible;
    422        }
    423
    424        /**
    425         * Sets the flag that controls whether or not the line is visible.
    426         *
    427         * @param visible  the flag.
    428         *
    429         * @see #isLineVisible()
    430         */
    431        public void setLineVisible(boolean visible) {
    432            this.lineVisible = visible;
    433        }
    434
    435        /**
    436         * Returns the line centered about (0, 0).
    437         *
    438         * @return The line.
    439         *
    440         * @see #setLine(Shape)
    441         */
    442        public Shape getLine() {
    443            return this.line;
    444        }
    445
    446        /**
    447         * Sets the line.  A Shape is used here, because then you can use Line2D,
    448         * GeneralPath or any other Shape to represent the line.
    449         *
    450         * @param line  the line.
    451         *
    452         * @see #getLine()
    453         */
    454        public void setLine(Shape line) {
    455            this.line = line;
    456        }
    457
    458        /**
    459         * Returns the line paint.
    460         *
    461         * @return The paint.
    462         *
    463         * @see #setLinePaint(Paint)
    464         */
    465        public Paint getLinePaint() {
    466            return this.linePaint;
    467        }
    468
    469        /**
    470         * Sets the line paint.
    471         *
    472         * @param paint  the paint.
    473         *
    474         * @see #getLinePaint()
    475         */
    476        public void setLinePaint(Paint paint) {
    477            this.linePaint = paint;
    478        }
    479
    480        /**
    481         * Returns the line stroke.
    482         *
    483         * @return The stroke.
    484         *
    485         * @see #setLineStroke(Stroke)
    486         */
    487        public Stroke getLineStroke() {
    488            return this.lineStroke;
    489        }
    490
    491        /**
    492         * Sets the line stroke.
    493         *
    494         * @param stroke  the stroke.
    495         *
    496         * @see #getLineStroke()
    497         */
    498        public void setLineStroke(Stroke stroke) {
    499            this.lineStroke = stroke;
    500        }
    501
    502        /**
    503         * Arranges the contents of the block, within the given constraints, and
    504         * returns the block size.
    505         *
    506         * @param g2  the graphics device.
    507         * @param constraint  the constraint (<code>null</code> not permitted).
    508         *
    509         * @return The block size (in Java2D units, never <code>null</code>).
    510         */
    511        public Size2D arrange(Graphics2D g2, RectangleConstraint constraint) {
    512            RectangleConstraint contentConstraint = toContentConstraint(constraint);
    513            LengthConstraintType w = contentConstraint.getWidthConstraintType();
    514            LengthConstraintType h = contentConstraint.getHeightConstraintType();
    515            Size2D contentSize = null;
    516            if (w == LengthConstraintType.NONE) {
    517                if (h == LengthConstraintType.NONE) {
    518                    contentSize = arrangeNN(g2);
    519                }
    520                else if (h == LengthConstraintType.RANGE) {
    521                    throw new RuntimeException("Not yet implemented.");
    522                }
    523                else if (h == LengthConstraintType.FIXED) {
    524                    throw new RuntimeException("Not yet implemented.");
    525                }
    526            }
    527            else if (w == LengthConstraintType.RANGE) {
    528                if (h == LengthConstraintType.NONE) {
    529                    throw new RuntimeException("Not yet implemented.");
    530                }
    531                else if (h == LengthConstraintType.RANGE) {
    532                    throw new RuntimeException("Not yet implemented.");
    533                }
    534                else if (h == LengthConstraintType.FIXED) {
    535                    throw new RuntimeException("Not yet implemented.");
    536                }
    537            }
    538            else if (w == LengthConstraintType.FIXED) {
    539                if (h == LengthConstraintType.NONE) {
    540                    throw new RuntimeException("Not yet implemented.");
    541                }
    542                else if (h == LengthConstraintType.RANGE) {
    543                    throw new RuntimeException("Not yet implemented.");
    544                }
    545                else if (h == LengthConstraintType.FIXED) {
    546                    contentSize = new Size2D(
    547                        contentConstraint.getWidth(),
    548                        contentConstraint.getHeight()
    549                    );
    550                }
    551            }
    552            return new Size2D(
    553                calculateTotalWidth(contentSize.getWidth()),
    554                calculateTotalHeight(contentSize.getHeight())
    555            );
    556        }
    557
    558        /**
    559         * Performs the layout with no constraint, so the content size is
    560         * determined by the bounds of the shape and/or line drawn to represent
    561         * the series.
    562         *
    563         * @param g2  the graphics device.
    564         *
    565         * @return  The content size.
    566         */
    567        protected Size2D arrangeNN(Graphics2D g2) {
    568            Rectangle2D contentSize = new Rectangle2D.Double();
    569            if (this.line != null) {
    570                contentSize.setRect(this.line.getBounds2D());
    571            }
    572            if (this.shape != null) {
    573                contentSize = contentSize.createUnion(this.shape.getBounds2D());
    574            }
    575            return new Size2D(contentSize.getWidth(), contentSize.getHeight());
    576        }
    577
    578        /**
    579         * Draws the graphic item within the specified area.
    580         *
    581         * @param g2  the graphics device.
    582         * @param area  the area.
    583         */
    584        public void draw(Graphics2D g2, Rectangle2D area) {
    585
    586            area = trimMargin(area);
    587            drawBorder(g2, area);
    588            area = trimBorder(area);
    589            area = trimPadding(area);
    590
    591            if (this.lineVisible) {
    592                Point2D location = RectangleAnchor.coordinates(area,
    593                        this.shapeLocation);
    594                Shape aLine = ShapeUtilities.createTranslatedShape(getLine(),
    595                        this.shapeAnchor, location.getX(), location.getY());
    596                g2.setPaint(this.linePaint);
    597                g2.setStroke(this.lineStroke);
    598                g2.draw(aLine);
    599            }
    600
    601            if (this.shapeVisible) {
    602                Point2D location = RectangleAnchor.coordinates(area,
    603                        this.shapeLocation);
    604
    605                Shape s = ShapeUtilities.createTranslatedShape(this.shape,
    606                        this.shapeAnchor, location.getX(), location.getY());
    607                if (this.shapeFilled) {
    608                    Paint p = this.fillPaint;
    609                    if (p instanceof GradientPaint) {
    610                        GradientPaint gp = (GradientPaint) this.fillPaint;
    611                        p = this.fillPaintTransformer.transform(gp, s);
    612                    }
    613                    g2.setPaint(p);
    614                    g2.fill(s);
    615                }
    616                if (this.shapeOutlineVisible) {
    617                    g2.setPaint(this.outlinePaint);
    618                    g2.setStroke(this.outlineStroke);
    619                    g2.draw(s);
    620                }
    621            }
    622
    623        }
    624
    625        /**
    626         * Draws the block within the specified area.
    627         *
    628         * @param g2  the graphics device.
    629         * @param area  the area.
    630         * @param params  ignored (<code>null</code> permitted).
    631         *
    632         * @return Always <code>null</code>.
    633         */
    634        public Object draw(Graphics2D g2, Rectangle2D area, Object params) {
    635            draw(g2, area);
    636            return null;
    637        }
    638
    639        /**
    640         * Tests this <code>LegendGraphic</code> instance for equality with an
    641         * arbitrary object.
    642         *
    643         * @param obj  the object (<code>null</code> permitted).
    644         *
    645         * @return A boolean.
    646         */
    647        public boolean equals(Object obj) {
    648            if (!(obj instanceof LegendGraphic)) {
    649                return false;
    650            }
    651            LegendGraphic that = (LegendGraphic) obj;
    652            if (this.shapeVisible != that.shapeVisible) {
    653                return false;
    654            }
    655            if (!ShapeUtilities.equal(this.shape, that.shape)) {
    656                return false;
    657            }
    658            if (this.shapeFilled != that.shapeFilled) {
    659                return false;
    660            }
    661            if (!PaintUtilities.equal(this.fillPaint, that.fillPaint)) {
    662                return false;
    663            }
    664            if (!ObjectUtilities.equal(this.fillPaintTransformer,
    665                    that.fillPaintTransformer)) {
    666                return false;
    667            }
    668            if (this.shapeOutlineVisible != that.shapeOutlineVisible) {
    669                return false;
    670            }
    671            if (!PaintUtilities.equal(this.outlinePaint, that.outlinePaint)) {
    672                return false;
    673            }
    674            if (!ObjectUtilities.equal(this.outlineStroke, that.outlineStroke)) {
    675                return false;
    676            }
    677            if (this.shapeAnchor != that.shapeAnchor) {
    678                return false;
    679            }
    680            if (this.shapeLocation != that.shapeLocation) {
    681                return false;
    682            }
    683            if (this.lineVisible != that.lineVisible) {
    684                return false;
    685            }
    686            if (!ShapeUtilities.equal(this.line, that.line)) {
    687                return false;
    688            }
    689            if (!PaintUtilities.equal(this.linePaint, that.linePaint)) {
    690                return false;
    691            }
    692            if (!ObjectUtilities.equal(this.lineStroke, that.lineStroke)) {
    693                return false;
    694            }
    695            return super.equals(obj);
    696        }
    697
    698        /**
    699         * Returns a hash code for this instance.
    700         *
    701         * @return A hash code.
    702         */
    703        public int hashCode() {
    704            int result = 193;
    705            result = 37 * result + ObjectUtilities.hashCode(this.fillPaint);
    706            // FIXME: use other fields too
    707            return result;
    708        }
    709
    710        /**
    711         * Returns a clone of this <code>LegendGraphic</code> instance.
    712         *
    713         * @return A clone of this <code>LegendGraphic</code> instance.
    714         *
    715         * @throws CloneNotSupportedException if there is a problem cloning.
    716         */
    717        public Object clone() throws CloneNotSupportedException {
    718            LegendGraphic clone = (LegendGraphic) super.clone();
    719            clone.shape = ShapeUtilities.clone(this.shape);
    720            clone.line = ShapeUtilities.clone(this.line);
    721            return clone;
    722        }
    723
    724        /**
    725         * Provides serialization support.
    726         *
    727         * @param stream  the output stream.
    728         *
    729         * @throws IOException  if there is an I/O error.
    730         */
    731        private void writeObject(ObjectOutputStream stream) throws IOException {
    732            stream.defaultWriteObject();
    733            SerialUtilities.writeShape(this.shape, stream);
    734            SerialUtilities.writePaint(this.fillPaint, stream);
    735            SerialUtilities.writePaint(this.outlinePaint, stream);
    736            SerialUtilities.writeStroke(this.outlineStroke, stream);
    737            SerialUtilities.writeShape(this.line, stream);
    738            SerialUtilities.writePaint(this.linePaint, stream);
    739            SerialUtilities.writeStroke(this.lineStroke, stream);
    740        }
    741
    742        /**
    743         * Provides serialization support.
    744         *
    745         * @param stream  the input stream.
    746         *
    747         * @throws IOException  if there is an I/O error.
    748         * @throws ClassNotFoundException  if there is a classpath problem.
    749         */
    750        private void readObject(ObjectInputStream stream)
    751                throws IOException, ClassNotFoundException {
    752            stream.defaultReadObject();
    753            this.shape = SerialUtilities.readShape(stream);
    754            this.fillPaint = SerialUtilities.readPaint(stream);
    755            this.outlinePaint = SerialUtilities.readPaint(stream);
    756            this.outlineStroke = SerialUtilities.readStroke(stream);
    757            this.line = SerialUtilities.readShape(stream);
    758            this.linePaint = SerialUtilities.readPaint(stream);
    759            this.lineStroke = SerialUtilities.readStroke(stream);
    760        }
    761
    762    }