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