001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2007, 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-2007, 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    
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    public 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 = getSeriesPaint(
304                            ((Integer) entry.getValue()).intValue()
305                        );
306                        Paint outlinePaint = Color.black;
307                        Stroke outlineStroke = DEFAULT_STROKE;
308    
309                        result.add(new LegendItem(label, description, null, 
310                                null, shape, paint, outlineStroke, outlinePaint));
311                        
312                    }               
313                }
314                else {
315                    // in this case, every color has a range of values
316                    Set unique = new HashSet();
317                    for (Iterator i = this.paintIndex.entrySet().iterator(); 
318                         i.hasNext();) {
319                        Map.Entry entry = (Map.Entry) i.next();
320                        if (unique.add(entry.getValue())) {
321                            String label = getMinPaintValue(
322                                (Integer) entry.getValue()).toString()
323                                + " - " + getMaxPaintValue(
324                                    (Integer) entry.getValue()).toString();
325                            String description = label;
326                            Shape shape = new Rectangle2D.Double(1d, 1d, 1d, 1d);
327                            Paint paint = getSeriesPaint(
328                                ((Integer) entry.getValue()).intValue()
329                            );
330                            Paint outlinePaint = Color.black;
331                            Stroke outlineStroke = DEFAULT_STROKE;
332    
333                            result.add(new LegendItem(label, description, 
334                                    null, null, shape, paint, outlineStroke, 
335                                    outlinePaint));
336                        }
337                    } // end foreach map entry
338                } // end else
339            }
340            return result;
341        }
342    
343        /**
344         * Returns the minimum chip value assigned to a color
345         * in the paintIndex
346         * 
347         * @param index  the index.
348         * 
349         * @return The value.
350         */
351        private Number getMinPaintValue(Integer index) {
352            double minValue = Double.POSITIVE_INFINITY;
353            for (Iterator i = this.paintIndex.entrySet().iterator(); i.hasNext();) {
354                Map.Entry entry = (Map.Entry) i.next();
355                if (((Integer) entry.getValue()).equals(index)) {
356                    if (((Number) entry.getKey()).doubleValue() < minValue) {
357                        minValue = ((Number) entry.getKey()).doubleValue();
358                    }
359                }
360            }               
361            return new Double(minValue);
362        }
363        
364        /**
365         * Returns the maximum chip value assigned to a color
366         * in the paintIndex
367         * 
368         * @param index  the index.
369         * 
370         * @return The value
371         */
372        private Number getMaxPaintValue(Integer index) {
373            double maxValue = Double.NEGATIVE_INFINITY;
374            for (Iterator i = this.paintIndex.entrySet().iterator(); i.hasNext();) {
375                Map.Entry entry = (Map.Entry) i.next();
376                if (((Integer) entry.getValue()).equals(index)) {
377                    if (((Number) entry.getKey()).doubleValue() > maxValue) {
378                        maxValue = ((Number) entry.getKey()).doubleValue();
379                    }
380                }
381            }               
382            return new Double(maxValue);
383        }
384    
385    
386    } // end class wafermaprenderer