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}