    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     * PaintScaleLegend.java
    029     * ---------------------
    030     * (C) Copyright 2007, by Object Refinery Limited.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   -;
    034     *
    035     * $Id: PaintScaleLegend.java,v 1.1.2.1 2007/01/31 14:15:16 mungady Exp $
    036     *
    037     * Changes
    038     * -------
    039     * 22-Jan-2007 : Version 1 (DG);
    040     *
    041     */
    042
    043    package org.jfree.chart.title;
    044
    045    import java.awt.BasicStroke;
    046    import java.awt.Color;
    047    import java.awt.Graphics2D;
    048    import java.awt.Paint;
    049    import java.awt.Stroke;
    050    import java.awt.geom.Rectangle2D;
    051    import java.io.IOException;
    052    import java.io.ObjectInputStream;
    053    import java.io.ObjectOutputStream;
    054
    055    import org.jfree.chart.axis.AxisLocation;
    056    import org.jfree.chart.axis.AxisSpace;
    057    import org.jfree.chart.axis.ValueAxis;
    058    import org.jfree.chart.block.LengthConstraintType;
    059    import org.jfree.chart.block.RectangleConstraint;
    060    import org.jfree.chart.event.TitleChangeEvent;
    061    import org.jfree.chart.plot.Plot;
    062    import org.jfree.chart.plot.PlotOrientation;
    063    import org.jfree.chart.renderer.PaintScale;
    064    import org.jfree.data.Range;
    065    import org.jfree.io.SerialUtilities;
    066    import org.jfree.ui.RectangleEdge;
    067    import org.jfree.ui.Size2D;
    068    import org.jfree.util.PaintUtilities;
    069    import org.jfree.util.PublicCloneable;
    070
    071    /**
    072     * A legend that shows a range of values and their associated colors, driven
    073     * by an underlying {@link PaintScale} implementation.
    074     *
    075     * @since 1.0.4
    076     */
    077    public class PaintScaleLegend extends Title implements PublicCloneable {
    078
    079        /** The paint scale (never <code>null</code>). */
    080        private PaintScale scale;
    081
    082        /** The value axis (never <code>null</code>). */
    083        private ValueAxis axis;
    084
    085        /**
    086         * The axis location (handles both orientations, never
    087         * <code>null</code>).
    088         */
    089        private AxisLocation axisLocation;
    090
    091        /** The offset between the axis and the paint strip (in Java2D units). */
    092        private double axisOffset;
    093
    094        /** The thickness of the paint strip (in Java2D units). */
    095        private double stripWidth;
    096
    097        /**
    098         * A flag that controls whether or not an outline is drawn around the
    099         * paint strip.
    100         */
    101        private boolean stripOutlineVisible;
    102
    103        /** The paint used to draw an outline around the paint strip. */
    104        private transient Paint stripOutlinePaint;
    105
    106        /** The stroke used to draw an outline around the paint strip. */
    107        private transient Stroke stripOutlineStroke;
    108
    109        /** The background paint (never <code>null</code>). */
    110        private transient Paint backgroundPaint;
    111
    112        /**
    113         * Creates a new instance.
    114         *
    115         * @param scale  the scale (<code>null</code> not permitted).
    116         * @param axis  the axis (<code>null</code> not permitted).
    117         */
    118        public PaintScaleLegend(PaintScale scale, ValueAxis axis) {
    119            if (axis == null) {
    120                throw new IllegalArgumentException("Null 'axis' argument.");
    121            }
    122            this.scale = scale;
    123            this.axis = axis;
    124            this.axisLocation = AxisLocation.BOTTOM_OR_LEFT;
    125            this.axisOffset = 0.0;
    126            this.stripWidth = 15.0;
    127            this.stripOutlineVisible = false;
    128            this.stripOutlinePaint = Color.gray;
    129            this.stripOutlineStroke = new BasicStroke(0.5f);
    130            this.backgroundPaint = Color.white;
    131        }
    132
    133        /**
    134         * Returns the scale used to convert values to colors.
    135         *
    136         * @return The scale (never <code>null</code>).
    137         *
    138         * @see #setScale(PaintScale)
    139         */
    140        public PaintScale getScale() {
    141            return this.scale;
    142        }
    143
    144        /**
    145         * Sets the scale and sends a {@link TitleChangeEvent} to all registered
    146         * listeners.
    147         *
    148         * @param scale  the scale (<code>null</code> not permitted).
    149         *
    150         * @see #getScale()
    151         */
    152        public void setScale(PaintScale scale) {
    153            if (scale == null) {
    154                throw new IllegalArgumentException("Null 'scale' argument.");
    155            }
    156            this.scale = scale;
    157            notifyListeners(new TitleChangeEvent(this));
    158        }
    159
    160        /**
    161         * Returns the axis for the paint scale.
    162         *
    163         * @return The axis (never <code>null</code>).
    164         *
    165         * @see #setAxis(ValueAxis)
    166         */
    167        public ValueAxis getAxis() {
    168            return this.axis;
    169        }
    170
    171        /**
    172         * Sets the axis for the paint scale and sends a {@link TitleChangeEvent}
    173         * to all registered listeners.
    174         *
    175         * @param axis  the axis (<code>null</code> not permitted).
    176         *
    177         * @see #getAxis()
    178         */
    179        public void setAxis(ValueAxis axis) {
    180            if (axis == null) {
    181                throw new IllegalArgumentException("Null 'axis' argument.");
    182            }
    183            this.axis = axis;
    184            notifyListeners(new TitleChangeEvent(this));
    185        }
    186
    187        /**
    188         * Returns the axis location.
    189         *
    190         * @return The axis location (never <code>null</code>).
    191         *
    192         * @see #setAxisLocation(AxisLocation)
    193         */
    194        public AxisLocation getAxisLocation() {
    195            return this.axisLocation;
    196        }
    197
    198        /**
    199         * Sets the axis location and sends a {@link TitleChangeEvent} to all
    200         * registered listeners.
    201         *
    202         * @param location  the location (<code>null</code> not permitted).
    203         *
    204         * @see #getAxisLocation()
    205         */
    206        public void setAxisLocation(AxisLocation location) {
    207            if (location == null) {
    208                throw new IllegalArgumentException("Null 'location' argument.");
    209            }
    210            this.axisLocation = location;
    211            notifyListeners(new TitleChangeEvent(this));
    212        }
    213
    214        /**
    215         * Returns the offset between the axis and the paint strip.
    216         *
    217         * @return The offset between the axis and the paint strip.
    218         *
    219         * @see #setAxisOffset(double)
    220         */
    221        public double getAxisOffset() {
    222            return this.axisOffset;
    223        }
    224
    225        /**
    226         * Sets the offset between the axis and the paint strip and sends a
    227         * {@link TitleChangeEvent} to all registered listeners.
    228         *
    229         * @param offset  the offset.
    230         */
    231        public void setAxisOffset(double offset) {
    232            this.axisOffset = offset;
    233            notifyListeners(new TitleChangeEvent(this));
    234        }
    235
    236        /**
    237         * Returns the width of the paint strip, in Java2D units.
    238         *
    239         * @return The width of the paint strip.
    240         *
    241         * @see #setStripWidth(double)
    242         */
    243        public double getStripWidth() {
    244            return this.stripWidth;
    245        }
    246
    247        /**
    248         * Sets the width of the paint strip and sends a {@link TitleChangeEvent}
    249         * to all registered listeners.
    250         *
    251         * @param width  the width.
    252         *
    253         * @see #getStripWidth()
    254         */
    255        public void setStripWidth(double width) {
    256            this.stripWidth = width;
    257            notifyListeners(new TitleChangeEvent(this));
    258        }
    259
    260        /**
    261         * Returns the flag that controls whether or not an outline is drawn
    262         * around the paint strip.
    263         *
    264         * @return A boolean.
    265         *
    266         * @see #setStripOutlineVisible(boolean)
    267         */
    268        public boolean isStripOutlineVisible() {
    269            return this.stripOutlineVisible;
    270        }
    271
    272        /**
    273         * Sets the flag that controls whether or not an outline is drawn around
    274         * the paint strip, and sends a {@link TitleChangeEvent} to all registered
    275         * listeners.
    276         *
    277         * @param visible  the flag.
    278         *
    279         * @see #isStripOutlineVisible()
    280         */
    281        public void setStripOutlineVisible(boolean visible) {
    282            this.stripOutlineVisible = visible;
    283            notifyListeners(new TitleChangeEvent(this));
    284        }
    285
    286        /**
    287         * Returns the paint used to draw the outline of the paint strip.
    288         *
    289         * @return The paint (never <code>null</code>).
    290         *
    291         * @see #setStripOutlinePaint(Paint)
    292         */
    293        public Paint getStripOutlinePaint() {
    294            return this.stripOutlinePaint;
    295        }
    296
    297        /**
    298         * Sets the paint used to draw the outline of the paint strip, and sends
    299         * a {@link TitleChangeEvent} to all registered listeners.
    300         *
    301         * @param paint  the paint (<code>null</code> not permitted).
    302         *
    303         * @see #getStripOutlinePaint()
    304         */
    305        public void setStripOutlinePaint(Paint paint) {
    306            if (paint == null) {
    307                throw new IllegalArgumentException("Null 'paint' argument.");
    308            }
    309            this.stripOutlinePaint = paint;
    310            notifyListeners(new TitleChangeEvent(this));
    311        }
    312
    313        /**
    314         * Returns the stroke used to draw the outline around the paint strip.
    315         *
    316         * @return The stroke (never <code>null</code>).
    317         *
    318         * @see #setStripOutlineStroke(Stroke)
    319         */
    320        public Stroke getStripOutlineStroke() {
    321            return this.stripOutlineStroke;
    322        }
    323
    324        /**
    325         * Sets the stroke used to draw the outline around the paint strip and
    326         * sends a {@link TitleChangeEvent} to all registered listeners.
    327         *
    328         * @param stroke  the stroke (<code>null</code> not permitted).
    329         *
    330         * @see #getStripOutlineStroke()
    331         */
    332        public void setStripOutlineStroke(Stroke stroke) {
    333            if (stroke == null) {
    334                throw new IllegalArgumentException("Null 'stroke' argument.");
    335            }
    336            this.stripOutlineStroke = stroke;
    337            notifyListeners(new TitleChangeEvent(this));
    338        }
    339
    340        /**
    341         * Returns the background paint.
    342         *
    343         * @return The background paint.
    344         */
    345        public Paint getBackgroundPaint() {
    346            return this.backgroundPaint;
    347        }
    348
    349        /**
    350         * Sets the background paint and sends a {@link TitleChangeEvent} to all
    351         * registered listeners.
    352         *
    353         * @param paint  the paint (<code>null</code> permitted).
    354         */
    355        public void setBackgroundPaint(Paint paint) {
    356            this.backgroundPaint = paint;
    357            notifyListeners(new TitleChangeEvent(this));
    358        }
    359
    360        /**
    361         * Arranges the contents of the block, within the given constraints, and
    362         * returns the block size.
    363         *
    364         * @param g2  the graphics device.
    365         * @param constraint  the constraint (<code>null</code> not permitted).
    366         *
    367         * @return The block size (in Java2D units, never <code>null</code>).
    368         */
    369        public Size2D arrange(Graphics2D g2, RectangleConstraint constraint) {
    370            RectangleConstraint cc = toContentConstraint(constraint);
    371            LengthConstraintType w = cc.getWidthConstraintType();
    372            LengthConstraintType h = cc.getHeightConstraintType();
    373            Size2D contentSize = null;
    374            if (w == LengthConstraintType.NONE) {
    375                if (h == LengthConstraintType.NONE) {
    376                    contentSize = new Size2D(getWidth(), getHeight());
    377                }
    378                else if (h == LengthConstraintType.RANGE) {
    379                    throw new RuntimeException("Not yet implemented.");
    380                }
    381                else if (h == LengthConstraintType.FIXED) {
    382                    throw new RuntimeException("Not yet implemented.");
    383                }
    384            }
    385            else if (w == LengthConstraintType.RANGE) {
    386                if (h == LengthConstraintType.NONE) {
    387                    throw new RuntimeException("Not yet implemented.");
    388                }
    389                else if (h == LengthConstraintType.RANGE) {
    390                    contentSize = arrangeRR(g2, cc.getWidthRange(),
    391                            cc.getHeightRange());
    392                }
    393                else if (h == LengthConstraintType.FIXED) {
    394                    throw new RuntimeException("Not yet implemented.");
    395                }
    396            }
    397            else if (w == LengthConstraintType.FIXED) {
    398                if (h == LengthConstraintType.NONE) {
    399                    throw new RuntimeException("Not yet implemented.");
    400                }
    401                else if (h == LengthConstraintType.RANGE) {
    402                    throw new RuntimeException("Not yet implemented.");
    403                }
    404                else if (h == LengthConstraintType.FIXED) {
    405                    throw new RuntimeException("Not yet implemented.");
    406                }
    407            }
    408            return new Size2D(calculateTotalWidth(contentSize.getWidth()),
    409                    calculateTotalHeight(contentSize.getHeight()));
    410        }
    411
    412        /**
    413         * Returns the content size for the title.  This will reflect the fact that
    414         * a text title positioned on the left or right of a chart will be rotated
    415         * 90 degrees.
    416         *
    417         * @param g2  the graphics device.
    418         * @param widthRange  the width range.
    419         * @param heightRange  the height range.
    420         *
    421         * @return The content size.
    422         */
    423        protected Size2D arrangeRR(Graphics2D g2, Range widthRange,
    424                Range heightRange) {
    425
    426            RectangleEdge position = getPosition();
    427            if (position == RectangleEdge.TOP || position == RectangleEdge.BOTTOM) {
    428
    429
    430                float maxWidth = (float) widthRange.getUpperBound();
    431
    432                // determine the space required for the axis
    433                AxisSpace space = this.axis.reserveSpace(g2, null,
    434                        new Rectangle2D.Double(0, 0, maxWidth, 100),
    435                        RectangleEdge.BOTTOM, null);
    436
    437                return new Size2D(maxWidth, this.stripWidth + this.axisOffset
    438                        + space.getTop() + space.getBottom());
    439            }
    440            else if (position == RectangleEdge.LEFT || position
    441                    == RectangleEdge.RIGHT) {
    442                float maxHeight = (float) heightRange.getUpperBound();
    443                AxisSpace space = this.axis.reserveSpace(g2, null,
    444                        new Rectangle2D.Double(0, 0, 100, maxHeight),
    445                        RectangleEdge.RIGHT, null);
    446                return new Size2D(this.stripWidth + this.axisOffset
    447                        + space.getLeft() + space.getRight(), maxHeight);
    448            }
    449            else {
    450                throw new RuntimeException("Unrecognised position.");
    451            }
    452        }
    453
    454        /**
    455         * Draws the legend within the specified area.
    456         *
    457         * @param g2  the graphics target (<code>null</code> not permitted).
    458         * @param area  the drawing area (<code>null</code> not permitted).
    459         */
    460        public void draw(Graphics2D g2, Rectangle2D area) {
    461            draw(g2, area, null);
    462        }
    463
    464        /**
    465         * The number of subdivisions to use when drawing the paint strip.  Maybe
    466         * this need to be user controllable?
    467         */
    468        private static final int SUBDIVISIONS = 200;
    469
    470        /**
    471         * Draws the legend within the specified area.
    472         *
    473         * @param g2  the graphics target (<code>null</code> not permitted).
    474         * @param area  the drawing area (<code>null</code> not permitted).
    475         * @param params  drawing parameters (ignored here).
    476         *
    477         * @return <code>null</code>.
    478         */
    479        public Object draw(Graphics2D g2, Rectangle2D area, Object params) {
    480
    481            Rectangle2D target = (Rectangle2D) area.clone();
    482            target = trimMargin(target);
    483            if (this.backgroundPaint != null) {
    484                g2.setPaint(this.backgroundPaint);
    485                g2.fill(target);
    486            }
    487            getBorder().draw(g2, target);
    488            getBorder().getInsets().trim(target);
    489            target = trimPadding(target);
    490            double base = this.axis.getLowerBound();
    491            double increment = this.axis.getRange().getLength() / SUBDIVISIONS;
    492            Rectangle2D r = new Rectangle2D.Double();
    493
    494
    495            if (RectangleEdge.isTopOrBottom(getPosition())) {
    496                RectangleEdge axisEdge = Plot.resolveRangeAxisLocation(
    497                        this.axisLocation, PlotOrientation.HORIZONTAL);
    498                double ww = Math.ceil(target.getWidth() / SUBDIVISIONS);
    499                if (axisEdge == RectangleEdge.TOP) {
    500                    for (int i = 0; i < SUBDIVISIONS; i++) {
    501                        double v = base + (i * increment);
    502                        Paint p = this.scale.getPaint(v);
    503                        double vv = this.axis.valueToJava2D(v, target,
    504                                RectangleEdge.BOTTOM);
    505                        r.setRect(vv, target.getMaxY() - this.stripWidth, ww,
    506                                this.stripWidth);
    507                        g2.setPaint(p);
    508                        g2.fill(r);
    509                    }
    510                    g2.setPaint(this.stripOutlinePaint);
    511                    g2.setStroke(this.stripOutlineStroke);
    512                    g2.draw(new Rectangle2D.Double(target.getMinX(),
    513                            target.getMaxY() - this.stripWidth, target.getWidth(),
    514                            this.stripWidth));
    515                    this.axis.draw(g2, target.getMaxY() - this.stripWidth
    516                            - this.axisOffset, target, target, RectangleEdge.TOP,
    517                            null);
    518                }
    519                else if (axisEdge == RectangleEdge.BOTTOM) {
    520                    for (int i = 0; i < SUBDIVISIONS; i++) {
    521                        double v = base + (i * increment);
    522                        Paint p = this.scale.getPaint(v);
    523                        double vv = this.axis.valueToJava2D(v, target,
    524                                RectangleEdge.BOTTOM);
    525                        r.setRect(vv, target.getMinY(), ww, this.stripWidth);
    526                        g2.setPaint(p);
    527                        g2.fill(r);
    528                    }
    529                    g2.setPaint(this.stripOutlinePaint);
    530                    g2.setStroke(this.stripOutlineStroke);
    531                    g2.draw(new Rectangle2D.Double(target.getMinX(),
    532                            target.getMinY(), target.getWidth(), this.stripWidth));
    533                    this.axis.draw(g2, target.getMinY() + this.stripWidth
    534                            + this.axisOffset, target, target,
    535                            RectangleEdge.BOTTOM, null);
    536                }
    537            }
    538            else {
    539                RectangleEdge axisEdge = Plot.resolveRangeAxisLocation(
    540                        this.axisLocation, PlotOrientation.VERTICAL);
    541                double hh = Math.ceil(target.getHeight() / SUBDIVISIONS);
    542                if (axisEdge == RectangleEdge.LEFT) {
    543                    for (int i = 0; i < SUBDIVISIONS; i++) {
    544                        double v = base + (i * increment);
    545                        Paint p = this.scale.getPaint(v);
    546                        double vv = this.axis.valueToJava2D(v, target,
    547                                RectangleEdge.LEFT);
    548                        r.setRect(target.getMaxX() - this.stripWidth, vv - hh,
    549                                this.stripWidth, hh);
    550                        g2.setPaint(p);
    551                        g2.fill(r);
    552                    }
    553                    g2.setPaint(this.stripOutlinePaint);
    554                    g2.setStroke(this.stripOutlineStroke);
    555                    g2.draw(new Rectangle2D.Double(target.getMaxX()
    556                            - this.stripWidth, target.getMinY(), this.stripWidth,
    557                            target.getHeight()));
    558                    this.axis.draw(g2, target.getMaxX() - this.stripWidth
    559                            - this.axisOffset, target, target, RectangleEdge.LEFT,
    560                            null);
    561                }
    562                else if (axisEdge == RectangleEdge.RIGHT) {
    563                    for (int i = 0; i < SUBDIVISIONS; i++) {
    564                        double v = base + (i * increment);
    565                        Paint p = this.scale.getPaint(v);
    566                        double vv = this.axis.valueToJava2D(v, target,
    567                                RectangleEdge.LEFT);
    568                        r.setRect(target.getMinX(), vv - hh, this.stripWidth, hh);
    569                        g2.setPaint(p);
    570                        g2.fill(r);
    571                    }
    572                    g2.setPaint(this.stripOutlinePaint);
    573                    g2.setStroke(this.stripOutlineStroke);
    574                    g2.draw(new Rectangle2D.Double(target.getMinX(),
    575                            target.getMinY(), this.stripWidth, target.getHeight()));
    576                    this.axis.draw(g2, target.getMinX() + this.stripWidth
    577                            + this.axisOffset, target, target, RectangleEdge.RIGHT,
    578                            null);
    579                }
    580            }
    581            return null;
    582        }
    583
    584        /**
    585         * Tests this legend for equality with an arbitrary object.
    586         *
    587         * @param obj  the object (<code>null</code> permitted).
    588         *
    589         * @return A boolean.
    590         */
    591        public boolean equals(Object obj) {
    592            if (!(obj instanceof PaintScaleLegend)) {
    593                return false;
    594            }
    595            PaintScaleLegend that = (PaintScaleLegend) obj;
    596            if (!this.scale.equals(that.scale)) {
    597                return false;
    598            }
    599            if (!this.axis.equals(that.axis)) {
    600                return false;
    601            }
    602            if (!this.axisLocation.equals(that.axisLocation)) {
    603                return false;
    604            }
    605            if (this.axisOffset != that.axisOffset) {
    606                return false;
    607            }
    608            if (this.stripWidth != that.stripWidth) {
    609                return false;
    610            }
    611            if (this.stripOutlineVisible != that.stripOutlineVisible) {
    612                return false;
    613            }
    614            if (!PaintUtilities.equal(this.stripOutlinePaint,
    615                    that.stripOutlinePaint)) {
    616                return false;
    617            }
    618            if (!this.stripOutlineStroke.equals(that.stripOutlineStroke)) {
    619                return false;
    620            }
    621            if (!PaintUtilities.equal(this.backgroundPaint, that.backgroundPaint)) {
    622                return false;
    623            }
    624            return super.equals(obj);
    625        }
    626
    627        /**
    628         * Provides serialization support.
    629         *
    630         * @param stream  the output stream.
    631         *
    632         * @throws IOException  if there is an I/O error.
    633         */
    634        private void writeObject(ObjectOutputStream stream) throws IOException {
    635            stream.defaultWriteObject();
    636            SerialUtilities.writePaint(this.backgroundPaint, stream);
    637            SerialUtilities.writePaint(this.stripOutlinePaint, stream);
    638            SerialUtilities.writeStroke(this.stripOutlineStroke, stream);
    639        }
    640
    641        /**
    642         * Provides serialization support.
    643         *
    644         * @param stream  the input stream.
    645         *
    646         * @throws IOException  if there is an I/O error.
    647         * @throws ClassNotFoundException  if there is a classpath problem.
    648         */
    649        private void readObject(ObjectInputStream stream)
    650                throws IOException, ClassNotFoundException {
    651            stream.defaultReadObject();
    652            this.backgroundPaint = SerialUtilities.readPaint(stream);
    653            this.stripOutlinePaint = SerialUtilities.readPaint(stream);
    654            this.stripOutlineStroke = SerialUtilities.readStroke(stream);
    655        }
    656
    657    }