001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2008, 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-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}