    001/* ===========================================================
    002 * JFreeChart : a free chart library for the Java(tm) platform
    003 * ===========================================================
    004 *
    005 * (C) Copyright 2000-2011, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    025 * Other names may be trademarks of their respective owners.]
    026 *
    027 * ------------------
    028 * LegendGraphic.java
    029 * ------------------
    030 * (C) Copyright 2004-2008, 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
    051package org.jfree.chart.title;
    052
    053import java.awt.GradientPaint;
    054import java.awt.Graphics2D;
    055import java.awt.Paint;
    056import java.awt.Shape;
    057import java.awt.Stroke;
    058import java.awt.geom.Point2D;
    059import java.awt.geom.Rectangle2D;
    060import java.io.IOException;
    061import java.io.ObjectInputStream;
    062import java.io.ObjectOutputStream;
    063
    064import org.jfree.chart.block.AbstractBlock;
    065import org.jfree.chart.block.Block;
    066import org.jfree.chart.block.LengthConstraintType;
    067import org.jfree.chart.block.RectangleConstraint;
    068import org.jfree.io.SerialUtilities;
    069import org.jfree.ui.GradientPaintTransformer;
    070import org.jfree.ui.RectangleAnchor;
    071import org.jfree.ui.Size2D;
    072import org.jfree.ui.StandardGradientPaintTransformer;
    073import org.jfree.util.ObjectUtilities;
    074import org.jfree.util.PaintUtilities;
    075import org.jfree.util.PublicCloneable;
    076import org.jfree.util.ShapeUtilities;
    077
    078/**
    079 * The graphical item within a legend item.
    080 */
    081public 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}