    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     * LookupPaintScale.java
    029     * ---------------------
    030     * (C) Copyright 2006-2009, by Object Refinery Limited.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   -;
    034     *
    035     * Changes
    036     * -------
    037     * 05-Jul-2006 : Version 1 (DG);
    038     * 31-Jan-2007 : Fixed serialization support (DG);
    039     * 09-Mar-2007 : Fixed cloning (DG);
    040     * 14-Jun-2007 : Use double primitive in PaintItem (DG);
    041     * 28-Mar-2009 : Made PaintItem inner class static (DG);
    042     *
    043     */
    044
    045    package org.jfree.chart.renderer;
    046
    047    import java.awt.Color;
    048    import java.awt.Paint;
    049    import java.io.IOException;
    050    import java.io.ObjectInputStream;
    051    import java.io.ObjectOutputStream;
    052    import java.io.Serializable;
    053    import java.util.Collections;
    054    import java.util.List;
    055
    056    import org.jfree.io.SerialUtilities;
    057    import org.jfree.util.PaintUtilities;
    058    import org.jfree.util.PublicCloneable;
    059
    060    /**
    061     * A paint scale that uses a lookup table to associate paint instances
    062     * with data value ranges.
    063     *
    064     * @since 1.0.4
    065     */
    066    public class LookupPaintScale
    067            implements PaintScale, PublicCloneable, Serializable {
    068
    069        /**
    070         * Stores the paint for a value.
    071         */
    072        static class PaintItem implements Comparable, Serializable {
    073
    074            /** For serialization. */
    075            static final long serialVersionUID = 698920578512361570L;
    076
    077            /** The value. */
    078            double value;
    079
    080            /** The paint. */
    081            transient Paint paint;
    082
    083            /**
    084             * Creates a new instance.
    085             *
    086             * @param value  the value.
    087             * @param paint  the paint.
    088             */
    089            public PaintItem(double value, Paint paint) {
    090                this.value = value;
    091                this.paint = paint;
    092            }
    093
    094            /**
    095             * Compares this item to an arbitrary object.
    096             *
    097             * @param obj  the object.
    098             *
    099             * @return An int defining the relative order of the objects.
    100             */
    101            public int compareTo(Object obj) {
    102                PaintItem that = (PaintItem) obj;
    103                double d1 = this.value;
    104                double d2 = that.value;
    105                if (d1 > d2) {
    106                    return 1;
    107                }
    108                if (d1 < d2) {
    109                    return -1;
    110                }
    111                return 0;
    112            }
    113
    114            /**
    115             * Tests this item for equality with an arbitrary object.
    116             *
    117             * @param obj  the object (<code>null</code> permitted).
    118             *
    119             * @return A boolean.
    120             */
    121            public boolean equals(Object obj) {
    122                if (obj == this) {
    123                    return true;
    124                }
    125                if (!(obj instanceof PaintItem)) {
    126                    return false;
    127                }
    128                PaintItem that = (PaintItem) obj;
    129                if (this.value != that.value) {
    130                    return false;
    131                }
    132                if (!PaintUtilities.equal(this.paint, that.paint)) {
    133                    return false;
    134                }
    135                return true;
    136            }
    137
    138            /**
    139             * Provides serialization support.
    140             *
    141             * @param stream  the output stream.
    142             *
    143             * @throws IOException  if there is an I/O error.
    144             */
    145            private void writeObject(ObjectOutputStream stream) throws IOException {
    146                stream.defaultWriteObject();
    147                SerialUtilities.writePaint(this.paint, stream);
    148            }
    149
    150            /**
    151             * Provides serialization support.
    152             *
    153             * @param stream  the input stream.
    154             *
    155             * @throws IOException  if there is an I/O error.
    156             * @throws ClassNotFoundException  if there is a classpath problem.
    157             */
    158            private void readObject(ObjectInputStream stream)
    159                    throws IOException, ClassNotFoundException {
    160                stream.defaultReadObject();
    161                this.paint = SerialUtilities.readPaint(stream);
    162            }
    163
    164        }
    165
    166        /** For serialization. */
    167        static final long serialVersionUID = -5239384246251042006L;
    168
    169        /** The lower bound. */
    170        private double lowerBound;
    171
    172        /** The upper bound. */
    173        private double upperBound;
    174
    175        /** The default paint. */
    176        private transient Paint defaultPaint;
    177
    178        /** The lookup table. */
    179        private List lookupTable;
    180
    181        /**
    182         * Creates a new paint scale.
    183         */
    184        public LookupPaintScale() {
    185            this(0.0, 1.0, Color.lightGray);
    186        }
    187
    188        /**
    189         * Creates a new paint scale with the specified default paint.
    190         *
    191         * @param lowerBound  the lower bound.
    192         * @param upperBound  the upper bound.
    193         * @param defaultPaint  the default paint (<code>null</code> not
    194         *     permitted).
    195         */
    196        public LookupPaintScale(double lowerBound, double upperBound,
    197                Paint defaultPaint) {
    198            if (lowerBound >= upperBound) {
    199                throw new IllegalArgumentException(
    200                        "Requires lowerBound < upperBound.");
    201            }
    202            if (defaultPaint == null) {
    203                throw new IllegalArgumentException("Null 'paint' argument.");
    204            }
    205            this.lowerBound = lowerBound;
    206            this.upperBound = upperBound;
    207            this.defaultPaint = defaultPaint;
    208            this.lookupTable = new java.util.ArrayList();
    209        }
    210
    211        /**
    212         * Returns the default paint (never <code>null</code>).
    213         *
    214         * @return The default paint.
    215         */
    216        public Paint getDefaultPaint() {
    217            return this.defaultPaint;
    218        }
    219
    220        /**
    221         * Returns the lower bound.
    222         *
    223         * @return The lower bound.
    224         *
    225         * @see #getUpperBound()
    226         */
    227        public double getLowerBound() {
    228            return this.lowerBound;
    229        }
    230
    231        /**
    232         * Returns the upper bound.
    233         *
    234         * @return The upper bound.
    235         *
    236         * @see #getLowerBound()
    237         */
    238        public double getUpperBound() {
    239            return this.upperBound;
    240        }
    241
    242        /**
    243         * Adds an entry to the lookup table.  Any values from <code>n</code> up
    244         * to but not including the next value in the table take on the specified
    245         * <code>paint</code>.
    246         *
    247         * @param value  the data value (<code>null</code> not permitted).
    248         * @param paint  the paint.
    249         *
    250         * @deprecated Use {@link #add(double, Paint)}.
    251         */
    252        public void add(Number value, Paint paint) {
    253            add(value.doubleValue(), paint);
    254        }
    255
    256        /**
    257         * Adds an entry to the lookup table.  Any values from <code>n</code> up
    258         * to but not including the next value in the table take on the specified
    259         * <code>paint</code>.
    260         *
    261         * @param value  the data value.
    262         * @param paint  the paint.
    263         *
    264         * @since 1.0.6
    265         */
    266        public void add(double value, Paint paint) {
    267            PaintItem item = new PaintItem(value, paint);
    268            int index = Collections.binarySearch(this.lookupTable, item);
    269            if (index >= 0) {
    270                this.lookupTable.set(index, item);
    271            }
    272            else {
    273                this.lookupTable.add(-(index + 1), item);
    274            }
    275        }
    276
    277        /**
    278         * Returns the paint associated with the specified value.
    279         *
    280         * @param value  the value.
    281         *
    282         * @return The paint.
    283         *
    284         * @see #getDefaultPaint()
    285         */
    286        public Paint getPaint(double value) {
    287
    288            // handle value outside bounds...
    289            if (value < this.lowerBound) {
    290                return this.defaultPaint;
    291            }
    292            if (value > this.upperBound) {
    293                return this.defaultPaint;
    294            }
    295
    296            int count = this.lookupTable.size();
    297            if (count == 0) {
    298                return this.defaultPaint;
    299            }
    300
    301            // handle special case where value is less that item zero
    302            PaintItem item = (PaintItem) this.lookupTable.get(0);
    303            if (value < item.value) {
    304                return this.defaultPaint;
    305            }
    306
    307            // for value in bounds, do the lookup...
    308            int low = 0;
    309            int high = this.lookupTable.size() - 1;
    310            while (high - low > 1) {
    311                int current = (low + high) / 2;
    312                item = (PaintItem) this.lookupTable.get(current);
    313                if (value >= item.value) {
    314                    low = current;
    315                }
    316                else {
    317                    high = current;
    318                }
    319            }
    320            if (high > low) {
    321                item = (PaintItem) this.lookupTable.get(high);
    322                if (value < item.value) {
    323                    item = (PaintItem) this.lookupTable.get(low);
    324                }
    325            }
    326            return (item != null ? item.paint : this.defaultPaint);
    327        }
    328
    329
    330        /**
    331         * Tests this instance for equality with an arbitrary object.
    332         *
    333         * @param obj  the object (<code>null</code> permitted).
    334         *
    335         * @return A boolean.
    336         */
    337        public boolean equals(Object obj) {
    338            if (obj == this) {
    339                return true;
    340            }
    341            if (!(obj instanceof LookupPaintScale)) {
    342                return false;
    343            }
    344            LookupPaintScale that = (LookupPaintScale) obj;
    345            if (this.lowerBound != that.lowerBound) {
    346                return false;
    347            }
    348            if (this.upperBound != that.upperBound) {
    349                return false;
    350            }
    351            if (!PaintUtilities.equal(this.defaultPaint, that.defaultPaint)) {
    352                return false;
    353            }
    354            if (!this.lookupTable.equals(that.lookupTable)) {
    355                return false;
    356            }
    357            return true;
    358        }
    359
    360        /**
    361         * Returns a clone of the instance.
    362         *
    363         * @return A clone.
    364         *
    365         * @throws CloneNotSupportedException if there is a problem cloning the
    366         *     instance.
    367         */
    368        public Object clone() throws CloneNotSupportedException {
    369            LookupPaintScale clone = (LookupPaintScale) super.clone();
    370            clone.lookupTable = new java.util.ArrayList(this.lookupTable);
    371            return clone;
    372        }
    373
    374        /**
    375         * Provides serialization support.
    376         *
    377         * @param stream  the output stream.
    378         *
    379         * @throws IOException  if there is an I/O error.
    380         */
    381        private void writeObject(ObjectOutputStream stream) throws IOException {
    382            stream.defaultWriteObject();
    383            SerialUtilities.writePaint(this.defaultPaint, stream);
    384        }
    385
    386        /**
    387         * Provides serialization support.
    388         *
    389         * @param stream  the input stream.
    390         *
    391         * @throws IOException  if there is an I/O error.
    392         * @throws ClassNotFoundException  if there is a classpath problem.
    393         */
    394        private void readObject(ObjectInputStream stream)
    395                throws IOException, ClassNotFoundException {
    396            stream.defaultReadObject();
    397            this.defaultPaint = SerialUtilities.readPaint(stream);
    398        }
    399
    400    }