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