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