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    
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 = 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