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