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 * WaferMapPlot.java
029 * -----------------
030 *
031 * (C) Copyright 2003, 2004, by Robert Redburn and Contributors.
032 *
033 * Original Author:  Robert Redburn;
034 * Contributor(s):   David Gilbert (for Object Refinery Limited);
035 *
036 * Changes
037 * -------
038 * 25-Nov-2003 : Version 1 contributed by Robert Redburn (DG);
039 * 05-May-2005 : Updated draw() method parameters (DG);
040 * 10-Jun-2005 : Changed private --> protected for drawChipGrid(), 
041 *               drawWaferEdge() and getWafterEdge() (DG);
042 * 16-Jun-2005 : Added default constructor and setDataset() method (DG);
043 *
044 */
045 
046package org.jfree.chart.plot;
047
048import java.awt.BasicStroke;
049import java.awt.Color;
050import java.awt.Graphics2D;
051import java.awt.Paint;
052import java.awt.Shape;
053import java.awt.Stroke;
054import java.awt.geom.Arc2D;
055import java.awt.geom.Ellipse2D;
056import java.awt.geom.Point2D;
057import java.awt.geom.Rectangle2D;
058import java.io.Serializable;
059import java.util.ResourceBundle;
060
061import org.jfree.chart.LegendItemCollection;
062import org.jfree.chart.event.PlotChangeEvent;
063import org.jfree.chart.event.RendererChangeEvent;
064import org.jfree.chart.event.RendererChangeListener;
065import org.jfree.chart.renderer.WaferMapRenderer;
066import org.jfree.data.general.DatasetChangeEvent;
067import org.jfree.data.general.WaferMapDataset;
068import org.jfree.ui.RectangleInsets;
069
070/**
071 * A wafer map plot.
072 */
073public class WaferMapPlot extends Plot implements RendererChangeListener,
074                                                  Cloneable,
075                                                  Serializable {
076
077    /** For serialization. */
078    private static final long serialVersionUID = 4668320403707308155L;
079    
080    /** The default grid line stroke. */
081    public static final Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke(0.5f,
082        BasicStroke.CAP_BUTT,
083        BasicStroke.JOIN_BEVEL,
084        0.0f,
085        new float[] {2.0f, 2.0f},
086        0.0f);
087
088    /** The default grid line paint. */
089    public static final Paint DEFAULT_GRIDLINE_PAINT = Color.lightGray;
090
091    /** The default crosshair visibility. */
092    public static final boolean DEFAULT_CROSSHAIR_VISIBLE = false;
093
094    /** The default crosshair stroke. */
095    public static final Stroke DEFAULT_CROSSHAIR_STROKE 
096        = DEFAULT_GRIDLINE_STROKE;
097
098    /** The default crosshair paint. */
099    public static final Paint DEFAULT_CROSSHAIR_PAINT = Color.blue;
100
101    /** The resourceBundle for the localization. */
102    protected static ResourceBundle localizationResources = 
103        ResourceBundle.getBundle("org.jfree.chart.plot.LocalizationBundle");
104
105    /** The plot orientation. 
106     *  vertical = notch down
107     *  horizontal = notch right
108     */
109    private PlotOrientation orientation;
110
111    /** The dataset. */
112    private WaferMapDataset dataset;
113
114    /** 
115     * Object responsible for drawing the visual representation of each point 
116     * on the plot. 
117     */
118    private WaferMapRenderer renderer;
119
120    /**
121     * Creates a new plot with no dataset.
122     */
123    public WaferMapPlot() {
124        this(null);   
125    }
126    
127    /**
128     * Creates a new plot.
129     * 
130     * @param dataset  the dataset (<code>null</code> permitted).
131     */
132    public WaferMapPlot(WaferMapDataset dataset) {
133        this(dataset, null);
134    }
135
136    /**
137     * Creates a new plot.
138     *
139     * @param dataset  the dataset (<code>null</code> permitted).
140     * @param renderer  the renderer (<code>null</code> permitted).
141     */
142    public WaferMapPlot(WaferMapDataset dataset, WaferMapRenderer renderer) {
143
144        super();
145
146        this.orientation = PlotOrientation.VERTICAL;
147        
148        this.dataset = dataset;
149        if (dataset != null) {
150            dataset.addChangeListener(this);
151        }
152
153        this.renderer = renderer;
154        if (renderer != null) {
155            renderer.setPlot(this);
156            renderer.addChangeListener(this);
157        }
158
159    }
160
161    /**
162     * Returns the plot type as a string.
163     *
164     * @return A short string describing the type of plot.
165     */
166    public String getPlotType() {
167        return ("WMAP_Plot");
168    }
169
170    /**
171     * Returns the dataset
172     * 
173     * @return The dataset (possibly <code>null</code>).
174     */
175    public WaferMapDataset getDataset() {
176        return this.dataset;
177    }
178
179    /**
180     * Sets the dataset used by the plot and sends a {@link PlotChangeEvent}
181     * to all registered listeners.
182     * 
183     * @param dataset  the dataset (<code>null</code> permitted).
184     */
185    public void setDataset(WaferMapDataset dataset) {
186        // if there is an existing dataset, remove the plot from the list of 
187        // change listeners...
188        if (this.dataset != null) {
189            this.dataset.removeChangeListener(this);
190        }
191
192        // set the new dataset, and register the chart as a change listener...
193        this.dataset = dataset;
194        if (dataset != null) {
195            setDatasetGroup(dataset.getGroup());
196            dataset.addChangeListener(this);
197        }
198
199        // send a dataset change event to self to trigger plot change event
200        datasetChanged(new DatasetChangeEvent(this, dataset));
201    }
202    
203    /**
204     * Sets the item renderer, and notifies all listeners of a change to the 
205     * plot.  If the renderer is set to <code>null</code>, no chart will be 
206     * drawn.
207     *
208     * @param renderer  the new renderer (<code>null</code> permitted).
209     */
210    public void setRenderer(WaferMapRenderer renderer) {
211
212        if (this.renderer != null) {
213            this.renderer.removeChangeListener(this);
214        }
215
216        this.renderer = renderer;
217        if (renderer != null) {
218            renderer.setPlot(this);
219        }
220
221        notifyListeners(new PlotChangeEvent(this));
222
223    }
224    
225    /**
226     * Draws the wafermap view.
227     * 
228     * @param g2  the graphics device.
229     * @param area  the plot area.
230     * @param anchor  the anchor point (<code>null</code> permitted).
231     * @param state  the plot state.
232     * @param info  the plot rendering info.
233     */
234    public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor,
235                     PlotState state, 
236                     PlotRenderingInfo info) {
237
238        // if the plot area is too small, just return...
239        boolean b1 = (area.getWidth() <= MINIMUM_WIDTH_TO_DRAW);
240        boolean b2 = (area.getHeight() <= MINIMUM_HEIGHT_TO_DRAW);
241        if (b1 || b2) {
242            return;
243        }
244
245        // record the plot area...
246        if (info != null) {
247            info.setPlotArea(area);
248        }
249
250        // adjust the drawing area for the plot insets (if any)...
251        RectangleInsets insets = getInsets();
252        insets.trim(area);
253
254        drawChipGrid(g2, area);       
255        drawWaferEdge(g2, area);
256        
257    }
258
259    /**
260     * Calculates and draws the chip locations on the wafer.
261     * 
262     * @param g2  the graphics device.
263     * @param plotArea  the plot area.
264     */
265    protected void drawChipGrid(Graphics2D g2, Rectangle2D plotArea) {
266        
267        Shape savedClip = g2.getClip();
268        g2.setClip(getWaferEdge(plotArea));
269        Rectangle2D chip = new Rectangle2D.Double();
270        int xchips = 35;
271        int ychips = 20;
272        double space = 1d;
273        if (this.dataset != null) {
274            xchips = this.dataset.getMaxChipX() + 2;
275            ychips = this.dataset.getMaxChipY() + 2;
276            space = this.dataset.getChipSpace();
277        }
278        double startX = plotArea.getX();
279        double startY = plotArea.getY();
280        double chipWidth = 1d;
281        double chipHeight = 1d;
282        if (plotArea.getWidth() != plotArea.getHeight()) {
283            double major = 0d;
284            double minor = 0d;
285            if (plotArea.getWidth() > plotArea.getHeight()) {
286                major = plotArea.getWidth();
287                minor = plotArea.getHeight();
288            } 
289            else {
290                major = plotArea.getHeight();
291                minor = plotArea.getWidth();
292            } 
293            //set upperLeft point
294            if (plotArea.getWidth() == minor) { // x is minor
295                startY += (major - minor) / 2;
296                chipWidth = (plotArea.getWidth() - (space * xchips - 1)) 
297                    / xchips;
298                chipHeight = (plotArea.getWidth() - (space * ychips - 1)) 
299                    / ychips;
300            }
301            else { // y is minor
302                startX += (major - minor) / 2;
303                chipWidth = (plotArea.getHeight() - (space * xchips - 1)) 
304                    / xchips;
305                chipHeight = (plotArea.getHeight() - (space * ychips - 1)) 
306                    / ychips;
307            }
308        }
309        
310        for (int x = 1; x <= xchips; x++) {
311            double upperLeftX = (startX - chipWidth) + (chipWidth * x) 
312                + (space * (x - 1));
313            for (int y = 1; y <= ychips; y++) {
314                double upperLeftY = (startY - chipHeight) + (chipHeight * y) 
315                    + (space * (y - 1));
316                chip.setFrame(upperLeftX, upperLeftY, chipWidth, chipHeight);
317                g2.setColor(Color.white);
318                if (this.dataset.getChipValue(x - 1, ychips - y - 1) != null) {
319                    g2.setPaint(
320                        this.renderer.getChipColor(
321                            this.dataset.getChipValue(x - 1, ychips - y - 1)
322                        )
323                    );
324                } 
325                g2.fill(chip);
326                g2.setColor(Color.lightGray);
327                g2.draw(chip);
328            }
329        }
330        g2.setClip(savedClip);
331    }
332
333    /**
334     * Calculates the location of the waferedge.
335     * 
336     * @param plotArea  the plot area.
337     * 
338     * @return The wafer edge.
339     */
340    protected Ellipse2D getWaferEdge(Rectangle2D plotArea) {
341        Ellipse2D edge = new Ellipse2D.Double();
342        double diameter = plotArea.getWidth();
343        double upperLeftX = plotArea.getX();
344        double upperLeftY = plotArea.getY();
345        //get major dimension
346        if (plotArea.getWidth() != plotArea.getHeight()) {
347            double major = 0d;
348            double minor = 0d;
349            if (plotArea.getWidth() > plotArea.getHeight()) {
350                major = plotArea.getWidth();
351                minor = plotArea.getHeight();
352            } 
353            else {
354                major = plotArea.getHeight();
355                minor = plotArea.getWidth();
356            } 
357            //ellipse diameter is the minor dimension
358            diameter = minor;
359            //set upperLeft point
360            if (plotArea.getWidth() == minor) { // x is minor
361                upperLeftY = plotArea.getY() + (major - minor) / 2;
362            }
363            else { // y is minor
364                upperLeftX = plotArea.getX() + (major - minor) / 2;
365            }
366        }
367        edge.setFrame(upperLeftX, upperLeftY, diameter, diameter); 
368        return edge;        
369    }
370
371    /**
372     * Draws the waferedge, including the notch.
373     * 
374     * @param g2  the graphics device.
375     * @param plotArea  the plot area.
376     */
377    protected void drawWaferEdge(Graphics2D g2, Rectangle2D plotArea) {
378        // draw the wafer
379        Ellipse2D waferEdge = getWaferEdge(plotArea);
380        g2.setColor(Color.black);
381        g2.draw(waferEdge);
382        // calculate and draw the notch
383        // horizontal orientation is considered notch right
384        // vertical orientation is considered notch down
385        Arc2D notch = null;
386        Rectangle2D waferFrame = waferEdge.getFrame();
387        double notchDiameter = waferFrame.getWidth() * 0.04;
388        if (this.orientation == PlotOrientation.HORIZONTAL) {
389            Rectangle2D notchFrame = 
390                new Rectangle2D.Double(
391                    waferFrame.getX() + waferFrame.getWidth() 
392                    - (notchDiameter / 2), waferFrame.getY()
393                    + (waferFrame.getHeight() / 2) - (notchDiameter / 2),
394                    notchDiameter, notchDiameter
395                );
396            notch = new Arc2D.Double(notchFrame, 90d, 180d, Arc2D.OPEN);
397        }
398        else {
399            Rectangle2D notchFrame = 
400                new Rectangle2D.Double(
401                    waferFrame.getX() + (waferFrame.getWidth() / 2) 
402                    - (notchDiameter / 2), waferFrame.getY() 
403                    + waferFrame.getHeight() - (notchDiameter / 2),
404                    notchDiameter, notchDiameter
405                );
406            notch = new Arc2D.Double(notchFrame, 0d, 180d, Arc2D.OPEN);        
407        }
408        g2.setColor(Color.white);
409        g2.fill(notch);
410        g2.setColor(Color.black);
411        g2.draw(notch);
412        
413    }
414
415    /**
416     * Return the legend items from the renderer.
417     * 
418     * @return The legend items.
419     */
420    public LegendItemCollection getLegendItems() {
421        return this.renderer.getLegendCollection();
422    }
423
424    /**
425     * Notifies all registered listeners of a renderer change.
426     *
427     * @param event  the event.
428     */
429    public void rendererChanged(RendererChangeEvent event) {
430        notifyListeners(new PlotChangeEvent(this));
431    }
432
433}