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 * WaferMapRenderer.java
029 * ---------------------
030 * (C) Copyright 2003-present, by Robert Redburn and Contributors.
031 *
032 * Original Author:  Robert Redburn;
033 * Contributor(s):   David Gilbert;
034 *
035 */
036
037package org.jfree.chart.renderer;
038
039import java.awt.Color;
040import java.awt.Paint;
041import java.awt.Shape;
042import java.awt.Stroke;
043import java.awt.geom.Rectangle2D;
044import java.util.HashMap;
045import java.util.HashSet;
046import java.util.Iterator;
047import java.util.Map;
048import java.util.Set;
049
050import org.jfree.chart.LegendItem;
051import org.jfree.chart.LegendItemCollection;
052import org.jfree.chart.plot.DrawingSupplier;
053import org.jfree.chart.plot.WaferMapPlot;
054import org.jfree.data.general.WaferMapDataset;
055
056/**
057 * A renderer for wafer map plots.  Provides color management facilities.
058 */
059public class WaferMapRenderer extends AbstractRenderer {
060
061    /** paint index */
062    private Map paintIndex;
063
064    /** plot */
065    private WaferMapPlot plot;
066
067    /** paint limit */
068    private int paintLimit;
069
070    /** default paint limit */
071    private static final int DEFAULT_PAINT_LIMIT = 35;
072
073    /** default multivalue paint calculation */
074    public static final int POSITION_INDEX = 0;
075
076    /** The default value index. */
077    public static final int VALUE_INDEX = 1;
078
079    /** paint index method */
080    private int paintIndexMethod;
081
082    /**
083     * Creates a new renderer.
084     */
085    public WaferMapRenderer() {
086        this(null, null);
087    }
088
089    /**
090     * Creates a new renderer.
091     *
092     * @param paintLimit  the paint limit.
093     * @param paintIndexMethod  the paint index method.
094     */
095    public WaferMapRenderer(int paintLimit, int paintIndexMethod) {
096        this(Integer.valueOf(paintLimit), Integer.valueOf(paintIndexMethod));
097    }
098
099    /**
100     * Creates a new renderer.
101     *
102     * @param paintLimit  the paint limit.
103     * @param paintIndexMethod  the paint index method.
104     */
105    public WaferMapRenderer(Integer paintLimit, Integer paintIndexMethod) {
106
107        super();
108        this.paintIndex = new HashMap();
109
110        if (paintLimit == null) {
111            this.paintLimit = DEFAULT_PAINT_LIMIT;
112        }
113        else {
114            this.paintLimit = paintLimit;
115        }
116
117        this.paintIndexMethod = VALUE_INDEX;
118        if (paintIndexMethod != null) {
119            if (isMethodValid(paintIndexMethod)) {
120                this.paintIndexMethod = paintIndexMethod;
121            }
122        }
123    }
124
125    /**
126     * Verifies that the passed paint index method is valid.
127     *
128     * @param method  the method.
129     *
130     * @return {@code true} or </code>false</code>.
131     */
132    private boolean isMethodValid(int method) {
133        switch (method) {
134            case POSITION_INDEX: return true;
135            case VALUE_INDEX:    return true;
136            default: return false;
137        }
138    }
139
140    /**
141     * Returns the drawing supplier from the plot.
142     *
143     * @return The drawing supplier.
144     */
145    @Override
146    public DrawingSupplier getDrawingSupplier() {
147        DrawingSupplier result = null;
148        WaferMapPlot p = getPlot();
149        if (p != null) {
150            result = p.getDrawingSupplier();
151        }
152        return result;
153    }
154
155    /**
156     * Returns the plot.
157     *
158     * @return The plot.
159     */
160    public WaferMapPlot getPlot() {
161        return this.plot;
162    }
163
164    /**
165     * Sets the plot and build the paint index.
166     *
167     * @param plot  the plot.
168     */
169    public void setPlot(WaferMapPlot plot) {
170        this.plot = plot;
171        makePaintIndex();
172    }
173
174    /**
175     * Returns the paint for a given chip value.
176     *
177     * @param value  the value.
178     *
179     * @return The paint.
180     */
181    public Paint getChipColor(Number value) {
182        return getSeriesPaint(getPaintIndex(value));
183    }
184
185    /**
186     * Returns the paint index for a given chip value.
187     *
188     * @param value  the value.
189     *
190     * @return The paint index.
191     */
192    private int getPaintIndex(Number value) {
193        return ((Integer) this.paintIndex.get(value));
194    }
195
196    /**
197     * Builds a map of chip values to paint colors.
198     * paintlimit is the maximum allowed number of colors.
199     */
200    private void makePaintIndex() {
201        if (this.plot == null) {
202            return;
203        }
204        WaferMapDataset data = this.plot.getDataset();
205        Number dataMin = data.getMinValue();
206        Number dataMax = data.getMaxValue();
207        Set uniqueValues = data.getUniqueValues();
208        if (uniqueValues.size() <= this.paintLimit) {
209            int count = 0; // assign a color for each unique value
210            for (Iterator i = uniqueValues.iterator(); i.hasNext();) {
211                this.paintIndex.put(i.next(), count++);
212            }
213        }
214        else {
215            // more values than paints so map
216            // multiple values to the same color
217            switch (this.paintIndexMethod) {
218                case POSITION_INDEX:
219                    makePositionIndex(uniqueValues);
220                    break;
221                case VALUE_INDEX:
222                    makeValueIndex(dataMax, dataMin, uniqueValues);
223                    break;
224                default:
225                    break;
226            }
227        }
228    }
229
230    /**
231     * Builds the paintindex by assigning colors based on the number
232     * of unique values: totalvalues/totalcolors.
233     *
234     * @param uniqueValues  the set of unique values.
235     */
236    private void makePositionIndex(Set uniqueValues) {
237        int valuesPerColor = (int) Math.ceil(
238            (double) uniqueValues.size() / this.paintLimit
239        );
240        int count = 0; // assign a color for each unique value
241        int paint = 0;
242        for (Iterator i = uniqueValues.iterator(); i.hasNext();) {
243            this.paintIndex.put(i.next(), paint);
244            if (++count % valuesPerColor == 0) {
245                paint++;
246            }
247            if (paint > this.paintLimit) {
248                paint = this.paintLimit;
249            }
250        }
251    }
252
253    /**
254     * Builds the paintindex by assigning colors evenly across the range
255     * of values:  maxValue-minValue/totalcolors
256     *
257     * @param max  the maximum value.
258     * @param min  the minumum value.
259     * @param uniqueValues  the unique values.
260     */
261    private void makeValueIndex(Number max, Number min, Set uniqueValues) {
262        double valueRange = max.doubleValue() - min.doubleValue();
263        double valueStep = valueRange / this.paintLimit;
264        int paint = 0;
265        double cutPoint = min.doubleValue() + valueStep;
266        for (Iterator i = uniqueValues.iterator(); i.hasNext();) {
267            Number value = (Number) i.next();
268            while (value.doubleValue() > cutPoint) {
269                cutPoint += valueStep;
270                paint++;
271                if (paint > this.paintLimit) {
272                    paint = this.paintLimit;
273                }
274            }
275            this.paintIndex.put(value, paint);
276        }
277    }
278
279    /**
280     * Builds the list of legend entries.  called by getLegendItems in
281     * WaferMapPlot to populate the plot legend.
282     *
283     * @return The legend items.
284     */
285    public LegendItemCollection getLegendCollection() {
286        LegendItemCollection result = new LegendItemCollection();
287        if (this.paintIndex != null && this.paintIndex.size() > 0) {
288            if (this.paintIndex.size() <= this.paintLimit) {
289                for (Iterator i = this.paintIndex.entrySet().iterator();
290                     i.hasNext();) {
291                    // in this case, every color has a unique value
292                    Map.Entry entry =  (Map.Entry) i.next();
293                    String label = entry.getKey().toString();
294                    String description = label;
295                    Shape shape = new Rectangle2D.Double(1d, 1d, 1d, 1d);
296                    Paint paint = lookupSeriesPaint(((Integer) entry.getValue()));
297                    Paint outlinePaint = Color.BLACK;
298                    Stroke outlineStroke = DEFAULT_STROKE;
299
300                    result.add(new LegendItem(label, description, null,
301                            null, shape, paint, outlineStroke, outlinePaint));
302
303                }
304            }
305            else {
306                // in this case, every color has a range of values
307                Set unique = new HashSet();
308                for (Iterator i = this.paintIndex.entrySet().iterator();
309                     i.hasNext();) {
310                    Map.Entry entry = (Map.Entry) i.next();
311                    if (unique.add(entry.getValue())) {
312                        String label = getMinPaintValue(
313                            (Integer) entry.getValue()).toString()
314                            + " - " + getMaxPaintValue(
315                                (Integer) entry.getValue()).toString();
316                        String description = label;
317                        Shape shape = new Rectangle2D.Double(1d, 1d, 1d, 1d);
318                        Paint paint = getSeriesPaint(((Integer) entry.getValue()));
319                        Paint outlinePaint = Color.BLACK;
320                        Stroke outlineStroke = DEFAULT_STROKE;
321
322                        result.add(new LegendItem(label, description,
323                                null, null, shape, paint, outlineStroke,
324                                outlinePaint));
325                    }
326                } // end foreach map entry
327            } // end else
328        }
329        return result;
330    }
331
332    /**
333     * Returns the minimum chip value assigned to a color
334     * in the paintIndex
335     *
336     * @param index  the index.
337     *
338     * @return The value.
339     */
340    private Number getMinPaintValue(Integer index) {
341        double minValue = Double.POSITIVE_INFINITY;
342        for (Iterator i = this.paintIndex.entrySet().iterator(); i.hasNext();) {
343            Map.Entry entry = (Map.Entry) i.next();
344            if (((Integer) entry.getValue()).equals(index)) {
345                if (((Number) entry.getKey()).doubleValue() < minValue) {
346                    minValue = ((Number) entry.getKey()).doubleValue();
347                }
348            }
349        }
350        return minValue;
351    }
352
353    /**
354     * Returns the maximum chip value assigned to a color
355     * in the paintIndex
356     *
357     * @param index  the index.
358     *
359     * @return The value
360     */
361    private Number getMaxPaintValue(Integer index) {
362        double maxValue = Double.NEGATIVE_INFINITY;
363        for (Iterator i = this.paintIndex.entrySet().iterator(); i.hasNext();) {
364            Map.Entry entry = (Map.Entry) i.next();
365            if (((Integer) entry.getValue()).equals(index)) {
366                if (((Number) entry.getKey()).doubleValue() > maxValue) {
367                    maxValue = ((Number) entry.getKey()).doubleValue();
368                }
369            }
370        }
371        return maxValue;
372    }
373
374
375}