001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2022, 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-2021, 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;
052import org.jfree.chart.ChartElementVisitor;
053
054import org.jfree.chart.legend.LegendItemCollection;
055import org.jfree.chart.event.PlotChangeEvent;
056import org.jfree.chart.event.RendererChangeEvent;
057import org.jfree.chart.event.RendererChangeListener;
058import org.jfree.chart.renderer.WaferMapRenderer;
059import org.jfree.chart.api.RectangleInsets;
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            = ResourceBundle.getBundle("org.jfree.chart.plot.LocalizationBundle");
096
097    /** The plot orientation.
098     *  vertical = notch down
099     *  horizontal = notch right
100     */
101    private final PlotOrientation orientation;
102
103    /** The dataset. */
104    private WaferMapDataset dataset;
105
106    /**
107     * Object responsible for drawing the visual representation of each point
108     * on the plot.
109     */
110    private WaferMapRenderer renderer;
111
112    /**
113     * Creates a new plot with no dataset.
114     */
115    public WaferMapPlot() {
116        this(null);
117    }
118
119    /**
120     * Creates a new plot.
121     *
122     * @param dataset  the dataset ({@code null} permitted).
123     */
124    public WaferMapPlot(WaferMapDataset dataset) {
125        this(dataset, null);
126    }
127
128    /**
129     * Creates a new plot.
130     *
131     * @param dataset  the dataset ({@code null} permitted).
132     * @param renderer  the renderer ({@code null} permitted).
133     */
134    public WaferMapPlot(WaferMapDataset dataset, WaferMapRenderer renderer) {
135
136        super();
137
138        this.orientation = PlotOrientation.VERTICAL;
139
140        this.dataset = dataset;
141        if (dataset != null) {
142            dataset.addChangeListener(this);
143        }
144
145        this.renderer = renderer;
146        if (renderer != null) {
147            renderer.setPlot(this);
148            renderer.addChangeListener(this);
149        }
150
151    }
152
153    /**
154     * Returns the plot type as a string.
155     *
156     * @return A short string describing the type of plot.
157     */
158    @Override
159    public String getPlotType() {
160        return ("WMAP_Plot");
161    }
162
163    /**
164     * Returns the dataset
165     *
166     * @return The dataset (possibly {@code null}).
167     */
168    public WaferMapDataset getDataset() {
169        return this.dataset;
170    }
171
172    /**
173     * Sets the dataset used by the plot and sends a {@link PlotChangeEvent}
174     * to all registered listeners.
175     *
176     * @param dataset  the dataset ({@code null} permitted).
177     */
178    public void setDataset(WaferMapDataset dataset) {
179        // if there is an existing dataset, remove the plot from the list of
180        // change listeners...
181        if (this.dataset != null) {
182            this.dataset.removeChangeListener(this);
183        }
184
185        // set the new dataset, and register the chart as a change listener...
186        this.dataset = dataset;
187        if (dataset != null) {
188            dataset.addChangeListener(this);
189        }
190
191        // send a dataset change event to self to trigger plot change event
192        datasetChanged(new DatasetChangeEvent(this, dataset));
193    }
194
195    /**
196     * Sets the item renderer, and notifies all listeners of a change to the
197     * plot.  If the renderer is set to {@code null}, no chart will be
198     * drawn.
199     *
200     * @param renderer  the new renderer ({@code null} permitted).
201     */
202    public void setRenderer(WaferMapRenderer renderer) {
203        if (this.renderer != null) {
204            this.renderer.removeChangeListener(this);
205        }
206        this.renderer = renderer;
207        if (renderer != null) {
208            renderer.setPlot(this);
209        }
210        fireChangeEvent();
211    }
212
213    /**
214     * Receives a chart element visitor.  Many plot subclasses will override
215     * this method to handle their subcomponents.
216     * 
217     * @param visitor  the visitor ({@code null} not permitted).
218     */
219    @Override
220    public void receive(ChartElementVisitor visitor) {
221        // FIXME : handle the renderer
222        super.receive(visitor);
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} permitted).
231     * @param state  the plot state.
232     * @param info  the plot rendering info.
233     */
234    @Override
235    public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor,
236            PlotState state, 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, minor;
284            if (plotArea.getWidth() > plotArea.getHeight()) {
285                major = plotArea.getWidth();
286                minor = plotArea.getHeight();
287            }
288            else {
289                major = plotArea.getHeight();
290                minor = plotArea.getWidth();
291            }
292            //set upperLeft point
293            if (plotArea.getWidth() == minor) { // x is minor
294                startY += (major - minor) / 2;
295                chipWidth = (plotArea.getWidth() - (space * xchips - 1))
296                    / xchips;
297                chipHeight = (plotArea.getWidth() - (space * ychips - 1))
298                    / ychips;
299            }
300            else { // y is minor
301                startX += (major - minor) / 2;
302                chipWidth = (plotArea.getHeight() - (space * xchips - 1))
303                    / xchips;
304                chipHeight = (plotArea.getHeight() - (space * ychips - 1))
305                    / ychips;
306            }
307        }
308
309        for (int x = 1; x <= xchips; x++) {
310            double upperLeftX = (startX - chipWidth) + (chipWidth * x)
311                + (space * (x - 1));
312            for (int y = 1; y <= ychips; y++) {
313                double upperLeftY = (startY - chipHeight) + (chipHeight * y)
314                    + (space * (y - 1));
315                chip.setFrame(upperLeftX, upperLeftY, chipWidth, chipHeight);
316                g2.setColor(Color.WHITE);
317                if (this.dataset.getChipValue(x - 1, ychips - y - 1) != null) {
318                    g2.setPaint(
319                        this.renderer.getChipColor(
320                            this.dataset.getChipValue(x - 1, ychips - y - 1)
321                        )
322                    );
323                }
324                g2.fill(chip);
325                g2.setColor(Color.LIGHT_GRAY);
326                g2.draw(chip);
327            }
328        }
329        g2.setClip(savedClip);
330    }
331
332    /**
333     * Calculates the location of the waferedge.
334     *
335     * @param plotArea  the plot area.
336     *
337     * @return The wafer edge.
338     */
339    protected Ellipse2D getWaferEdge(Rectangle2D plotArea) {
340        Ellipse2D edge = new Ellipse2D.Double();
341        double diameter = plotArea.getWidth();
342        double upperLeftX = plotArea.getX();
343        double upperLeftY = plotArea.getY();
344        //get major dimension
345        if (plotArea.getWidth() != plotArea.getHeight()) {
346            double major, minor;
347            if (plotArea.getWidth() > plotArea.getHeight()) {
348                major = plotArea.getWidth();
349                minor = plotArea.getHeight();
350            }
351            else {
352                major = plotArea.getHeight();
353                minor = plotArea.getWidth();
354            }
355            //ellipse diameter is the minor dimension
356            diameter = minor;
357            //set upperLeft point
358            if (plotArea.getWidth() == minor) { // x is minor
359                upperLeftY = plotArea.getY() + (major - minor) / 2;
360            }
361            else { // y is minor
362                upperLeftX = plotArea.getX() + (major - minor) / 2;
363            }
364        }
365        edge.setFrame(upperLeftX, upperLeftY, diameter, diameter);
366        return edge;
367    }
368
369    /**
370     * Draws the waferedge, including the notch.
371     *
372     * @param g2  the graphics device.
373     * @param plotArea  the plot area.
374     */
375    protected void drawWaferEdge(Graphics2D g2, Rectangle2D plotArea) {
376        // draw the wafer
377        Ellipse2D waferEdge = getWaferEdge(plotArea);
378        g2.setColor(Color.BLACK);
379        g2.draw(waferEdge);
380        // calculate and draw the notch
381        // horizontal orientation is considered notch right
382        // vertical orientation is considered notch down
383        Arc2D notch;
384        Rectangle2D waferFrame = waferEdge.getFrame();
385        double notchDiameter = waferFrame.getWidth() * 0.04;
386        if (this.orientation == PlotOrientation.HORIZONTAL) {
387            Rectangle2D notchFrame =
388                new Rectangle2D.Double(
389                    waferFrame.getX() + waferFrame.getWidth()
390                    - (notchDiameter / 2), waferFrame.getY()
391                    + (waferFrame.getHeight() / 2) - (notchDiameter / 2),
392                    notchDiameter, notchDiameter
393                );
394            notch = new Arc2D.Double(notchFrame, 90d, 180d, Arc2D.OPEN);
395        }
396        else {
397            Rectangle2D notchFrame =
398                new Rectangle2D.Double(
399                    waferFrame.getX() + (waferFrame.getWidth() / 2)
400                    - (notchDiameter / 2), waferFrame.getY()
401                    + waferFrame.getHeight() - (notchDiameter / 2),
402                    notchDiameter, notchDiameter
403                );
404            notch = new Arc2D.Double(notchFrame, 0d, 180d, Arc2D.OPEN);
405        }
406        g2.setColor(Color.WHITE);
407        g2.fill(notch);
408        g2.setColor(Color.BLACK);
409        g2.draw(notch);
410
411    }
412
413    /**
414     * Return the legend items from the renderer.
415     *
416     * @return The legend items.
417     */
418    @Override
419    public LegendItemCollection getLegendItems() {
420        return this.renderer.getLegendCollection();
421    }
422
423    /**
424     * Notifies all registered listeners of a renderer change.
425     *
426     * @param event  the event.
427     */
428    @Override
429    public void rendererChanged(RendererChangeEvent event) {
430        fireChangeEvent();
431    }
432
433}