001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2020, 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 * [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-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 management 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(Integer.valueOf(paintLimit), Integer.valueOf(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;
123        }
124
125        this.paintIndexMethod = VALUE_INDEX;
126        if (paintIndexMethod != null) {
127            if (isMethodValid(paintIndexMethod)) {
128                this.paintIndexMethod = paintIndexMethod;
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} 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    @Override
154    public DrawingSupplier getDrawingSupplier() {
155        DrawingSupplier result = null;
156        WaferMapPlot p = getPlot();
157        if (p != null) {
158            result = p.getDrawingSupplier();
159        }
160        return result;
161    }
162
163    /**
164     * Returns the plot.
165     *
166     * @return The plot.
167     */
168    public WaferMapPlot getPlot() {
169        return this.plot;
170    }
171
172    /**
173     * Sets the plot and build the paint index.
174     *
175     * @param plot  the plot.
176     */
177    public void setPlot(WaferMapPlot plot) {
178        this.plot = plot;
179        makePaintIndex();
180    }
181
182    /**
183     * Returns the paint for a given chip value.
184     *
185     * @param value  the value.
186     *
187     * @return The paint.
188     */
189    public Paint getChipColor(Number value) {
190        return getSeriesPaint(getPaintIndex(value));
191    }
192
193    /**
194     * Returns the paint index for a given chip value.
195     *
196     * @param value  the value.
197     *
198     * @return The paint index.
199     */
200    private int getPaintIndex(Number value) {
201        return ((Integer) this.paintIndex.get(value));
202    }
203
204    /**
205     * Builds a map of chip values to paint colors.
206     * paintlimit is the maximum allowed number of colors.
207     */
208    private void makePaintIndex() {
209        if (this.plot == null) {
210            return;
211        }
212        WaferMapDataset data = this.plot.getDataset();
213        Number dataMin = data.getMinValue();
214        Number dataMax = data.getMaxValue();
215        Set uniqueValues = data.getUniqueValues();
216        if (uniqueValues.size() <= this.paintLimit) {
217            int count = 0; // assign a color for each unique value
218            for (Iterator i = uniqueValues.iterator(); i.hasNext();) {
219                this.paintIndex.put(i.next(), count++);
220            }
221        }
222        else {
223            // more values than paints so map
224            // multiple values to the same color
225            switch (this.paintIndexMethod) {
226                case POSITION_INDEX:
227                    makePositionIndex(uniqueValues);
228                    break;
229                case VALUE_INDEX:
230                    makeValueIndex(dataMax, dataMin, uniqueValues);
231                    break;
232                default:
233                    break;
234            }
235        }
236    }
237
238    /**
239     * Builds the paintindex by assigning colors based on the number
240     * of unique values: totalvalues/totalcolors.
241     *
242     * @param uniqueValues  the set of unique values.
243     */
244    private void makePositionIndex(Set uniqueValues) {
245        int valuesPerColor = (int) Math.ceil(
246            (double) uniqueValues.size() / this.paintLimit
247        );
248        int count = 0; // assign a color for each unique value
249        int paint = 0;
250        for (Iterator i = uniqueValues.iterator(); i.hasNext();) {
251            this.paintIndex.put(i.next(), paint);
252            if (++count % valuesPerColor == 0) {
253                paint++;
254            }
255            if (paint > this.paintLimit) {
256                paint = this.paintLimit;
257            }
258        }
259    }
260
261    /**
262     * Builds the paintindex by assigning colors evenly across the range
263     * of values:  maxValue-minValue/totalcolors
264     *
265     * @param max  the maximum value.
266     * @param min  the minumum value.
267     * @param uniqueValues  the unique values.
268     */
269    private void makeValueIndex(Number max, Number min, Set uniqueValues) {
270        double valueRange = max.doubleValue() - min.doubleValue();
271        double valueStep = valueRange / this.paintLimit;
272        int paint = 0;
273        double cutPoint = min.doubleValue() + valueStep;
274        for (Iterator i = uniqueValues.iterator(); i.hasNext();) {
275            Number value = (Number) i.next();
276            while (value.doubleValue() > cutPoint) {
277                cutPoint += valueStep;
278                paint++;
279                if (paint > this.paintLimit) {
280                    paint = this.paintLimit;
281                }
282            }
283            this.paintIndex.put(value, paint);
284        }
285    }
286
287    /**
288     * Builds the list of legend entries.  called by getLegendItems in
289     * WaferMapPlot to populate the plot legend.
290     *
291     * @return The legend items.
292     */
293    public LegendItemCollection getLegendCollection() {
294        LegendItemCollection result = new LegendItemCollection();
295        if (this.paintIndex != null && this.paintIndex.size() > 0) {
296            if (this.paintIndex.size() <= this.paintLimit) {
297                for (Iterator i = this.paintIndex.entrySet().iterator();
298                     i.hasNext();) {
299                    // in this case, every color has a unique value
300                    Map.Entry entry =  (Map.Entry) i.next();
301                    String label = entry.getKey().toString();
302                    String description = label;
303                    Shape shape = new Rectangle2D.Double(1d, 1d, 1d, 1d);
304                    Paint paint = lookupSeriesPaint(((Integer) entry.getValue()));
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(((Integer) entry.getValue()));
327                        Paint outlinePaint = Color.BLACK;
328                        Stroke outlineStroke = DEFAULT_STROKE;
329
330                        result.add(new LegendItem(label, description,
331                                null, null, shape, paint, outlineStroke,
332                                outlinePaint));
333                    }
334                } // end foreach map entry
335            } // end else
336        }
337        return result;
338    }
339
340    /**
341     * Returns the minimum chip value assigned to a color
342     * in the paintIndex
343     *
344     * @param index  the index.
345     *
346     * @return The value.
347     */
348    private Number getMinPaintValue(Integer index) {
349        double minValue = Double.POSITIVE_INFINITY;
350        for (Iterator i = this.paintIndex.entrySet().iterator(); i.hasNext();) {
351            Map.Entry entry = (Map.Entry) i.next();
352            if (((Integer) entry.getValue()).equals(index)) {
353                if (((Number) entry.getKey()).doubleValue() < minValue) {
354                    minValue = ((Number) entry.getKey()).doubleValue();
355                }
356            }
357        }
358        return minValue;
359    }
360
361    /**
362     * Returns the maximum chip value assigned to a color
363     * in the paintIndex
364     *
365     * @param index  the index.
366     *
367     * @return The value
368     */
369    private Number getMaxPaintValue(Integer index) {
370        double maxValue = Double.NEGATIVE_INFINITY;
371        for (Iterator i = this.paintIndex.entrySet().iterator(); i.hasNext();) {
372            Map.Entry entry = (Map.Entry) i.next();
373            if (((Integer) entry.getValue()).equals(index)) {
374                if (((Number) entry.getKey()).doubleValue() > maxValue) {
375                    maxValue = ((Number) entry.getKey()).doubleValue();
376                }
377            }
378        }
379        return maxValue;
380    }
381
382
383}