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