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