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