/*
* Copyright (C) 2011-2012 Dr. John Lindsay <jlindsay@uoguelph.ca>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package plugins;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.geom.AffineTransform;
import java.text.DecimalFormat;
import javax.imageio.ImageIO;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JMenuItem;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import java.io.*;
import java.awt.print.*;
import javax.print.attribute.*;
import whitebox.structures.ExtensionFileFilter;
import whitebox.geospatialfiles.WhiteboxRasterInfo;
import whitebox.interfaces.WhiteboxPluginHost;
import whitebox.interfaces.WhiteboxPlugin;
import java.util.ArrayList;
/**
* A feature space plot is similar to a scattergram, it plots the frequency of occurrance of brightness value pairs in the two-dimensional feature space of defined by two images.
* @author Dr. John Lindsay email: jlindsay@uoguelph.ca
*/
public class FeatureSpacePlot implements WhiteboxPlugin {
private WhiteboxPluginHost myHost = null;
private String[] args;
/**
* Used to retrieve the plugin tool's name. This is a short, unique name containing no spaces.
* @return String containing plugin name.
*/
@Override
public String getName() {
return "FeatureSpacePlot";
}
/**
* Used to retrieve the plugin tool's descriptive name. This can be a longer name (containing spaces) and is used in the interface to list the tool.
* @return String containing the plugin descriptive name.
*/
@Override
public String getDescriptiveName() {
return "Feature Space Plot";
}
/**
* Used to retrieve a short description of what the plugin tool does.
* @return String containing the plugin's description.
*/
@Override
public String getToolDescription() {
return "Creates a feature space plot for two multispectral bands.";
}
/**
* Used to identify which toolboxes this plugin tool should be listed in.
* @return Array of Strings.
*/
@Override
public String[] getToolbox() {
String[] ret = { "ImageClass", "StatisticalTools" };
return ret;
}
/**
* Sets the WhiteboxPluginHost to which the plugin tool is tied. This is the class
* that the plugin will send all feedback messages, progress updates, and return objects.
* @param host The WhiteboxPluginHost that called the plugin tool.
*/
@Override
public void setPluginHost(WhiteboxPluginHost host) {
myHost = host;
}
/**
* Used to communicate feedback pop-up messages between a plugin tool and the main Whitebox user-interface.
* @param feedback String containing the text to display.
*/
private void showFeedback(String message) {
if (myHost != null) {
myHost.showFeedback(message);
} else {
System.out.println(message);
}
}
/**
* Used to communicate a return object from a plugin tool to the main Whitebox user-interface.
* @return Object, such as an output WhiteboxRaster.
*/
private void returnData(Object ret) {
if (myHost != null) {
myHost.returnData(ret);
}
}
private int previousProgress = 0;
private String previousProgressLabel = "";
/**
* Used to communicate a progress update between a plugin tool and the main Whitebox user interface.
* @param progressLabel A String to use for the progress label.
* @param progress Float containing the progress value (between 0 and 100).
*/
private void updateProgress(String progressLabel, int progress) {
if (myHost != null && ((progress != previousProgress) ||
(!progressLabel.equals(previousProgressLabel)))) {
myHost.updateProgress(progressLabel, progress);
}
previousProgress = progress;
previousProgressLabel = progressLabel;
}
/**
* Used to communicate a progress update between a plugin tool and the main Whitebox user interface.
* @param progress Float containing the progress value (between 0 and 100).
*/
private void updateProgress(int progress) {
if (myHost != null && progress != previousProgress) {
myHost.updateProgress(progress);
}
previousProgress = progress;
}
/**
* Sets the arguments (parameters) used by the plugin.
* @param args An array of string arguments.
*/
@Override
public void setArgs(String[] args) {
this.args = args.clone();
}
private boolean cancelOp = false;
/**
* Used to communicate a cancel operation from the Whitebox GUI.
* @param cancel Set to true if the plugin should be canceled.
*/
@Override
public void setCancelOp(boolean cancel) {
cancelOp = cancel;
}
private void cancelOperation() {
showFeedback("Operation cancelled.");
updateProgress("Progress: ", 0);
}
private boolean amIActive = false;
/**
* Used by the Whitebox GUI to tell if this plugin is still running.
* @return a boolean describing whether or not the plugin is actively being used.
*/
@Override
public boolean isActive() {
return amIActive;
}
/**
* Used to execute this plugin tool.
*/
@Override
public void run() {
amIActive = true;
String inputHeader1 = null;
String inputHeader2 = null;
int row, col;
double z;
float progress = 0;
int m, n;
if (args.length <= 0) {
showFeedback("Plugin parameters have not been set.");
return;
}
inputHeader1 = args[0];
inputHeader2 = args[1];
// check to see that the inputHeader and outputHeader are not null.
if ((inputHeader1 == null) || (inputHeader2 == null)) {
showFeedback("One or more of the input parameters have not been set properly.");
return;
}
try {
WhiteboxRasterInfo image1 = new WhiteboxRasterInfo(inputHeader1);
int rows = image1.getNumberRows();
int cols = image1.getNumberColumns();
double noData1 = image1.getNoDataValue();
WhiteboxRasterInfo image2 = new WhiteboxRasterInfo(inputHeader2);
if (image2.getNumberRows() != rows || image2.getNumberColumns() != cols) {
showFeedback("The input images must have the same number of rows and columns");
return;
}
double noData2 = image2.getNoDataValue();
int image1Min = (int)image1.getMinimumValue();
int image2Min = (int)image2.getMinimumValue();
int image1Max = (int)image1.getMaximumValue();
int image2Max = (int)image2.getMaximumValue();
int image1Range = image1Max - image1Min + 1;
int image2Range = image2Max - image2Min + 1;
int[][] featureSpace = new int[image1Range][image2Range];
double data1[] = null;
double data2[] = null;
for (row = 0; row < rows; row++) {
data1 = image1.getRowValues(row);
data2 = image2.getRowValues(row);
for (col = 0; col < cols; col++) {
if (data1[col] != noData1 && data2[col] != noData2) {
m = (int)(data1[col] - image1Min);
n = (int)(data2[col] - image2Min);
featureSpace[m][n]++;
}
}
if (cancelOp) {
cancelOperation();
return;
}
progress = (float) (100f * row / (rows - 1));
updateProgress((int) progress);
}
Plot plot = new Plot(featureSpace, image1.getShortHeaderFile(),
image2.getShortHeaderFile(), image1Min, image1Max, image2Min,
image2Max, myHost.getDefaultFont());
image1.close();
image2.close();
// returning a header file string displays the image.
returnData(plot);
} catch (OutOfMemoryError oe) {
myHost.showFeedback("An out-of-memory error has occurred during operation.");
} catch (Exception e) {
myHost.showFeedback("An error has occurred during operation. See log file for details.");
myHost.logException("Error in " + getDescriptiveName(), e);
} finally {
updateProgress("Progress: ", 0);
// tells the main application that this process is completed.
amIActive = false;
myHost.pluginComplete();
}
}
// // this is only used for debugging the tool
// public static void main(String[] args) {
// FeatureSpacePlot fsp = new FeatureSpacePlot();
// args = new String[2];
// args[0] = "/Users/johnlindsay/Documents/Teaching/GEOG3420/Winter 2012/Labs/Lab1/Data/LE70180302002142EDC00/band1 clipped.dep";
// args[1] = "/Users/johnlindsay/Documents/Teaching/GEOG3420/Winter 2012/Labs/Lab1/Data/LE70180302002142EDC00/band2 clipped.dep";
//
// fsp.setArgs(args);
// fsp.run();
//
// }
}
class Plot extends JPanel implements ActionListener, Printable, MouseMotionListener, MouseListener {
private boolean plotCreated = false;
private String shortName1, shortName2;
private int bottomMargin = 75;
private int topMargin = 45;
private int leftMargin = 90;
private int rightMargin = 20;
private int[][] featureSpace;
private int[] imageData;
private int width;
private int height;
private int numPix;
private int image1Min, image2Min, image1Max, image2Max;
private boolean isHillshaded = false;
private JPopupMenu myPopup = null;
private JCheckBoxMenuItem cmi = null;
private JCheckBoxMenuItem plotPeaksMi = null;
private JMenuItem backgroundMi = null;
private JMenuItem gridMi = null;
private JMenuItem paletteMi = null;
private int backgroundColor = 255;
private boolean gridOn = true;
private boolean spectrumPaletteMode = true;
private Font myFont;
// Constructors
public Plot(int[][] featureSpace, String shortName1, String shortName2, int image1Min,
int image1Max, int image2Min, int image2Max, Font myFont) {
this.featureSpace = featureSpace.clone();
this.shortName1 = shortName1;
this.shortName2 = shortName2;
this.image1Min = image1Min;
this.image2Min = image2Min;
this.image1Max = image1Max;
this.image2Max = image2Max;
this.myFont = myFont;
setMouseMotionListener();
setMouseListener();
setUp();
}
// Methods
private void setUp() {
try {
width = featureSpace[0].length;
height = featureSpace.length;
numPix = width * height;
createPopupMenus();
} catch (Exception e) {
}
}
//java.util.ArrayList<Dimension> peaks = new java.util.ArrayList<Dimension>();
boolean plotPeaks = false;
boolean peaksFound = false;
int[][] peaks = null;
private void findLocalPeaks() {
// local peaks are higher than all their neighbours, and are relatively high in the landscape
double z;
boolean flag = false;
int rN, cN;
double peakSize = 0.00001;
int[] Dy = {-1, 0, 1, 1, 1, 0, -1, -1};
int[] Dx = {1, 1, 1, 0, -1, -1, -1, 0};
double[] N = new double[8];
int numPeaks = 0;
// apply a 3x3 mean filter to remove insignificant local peaks
double[][] featureSpace2 = new double[height][width];
double cellTotal = 0;
int numNeighbours = 0;
for (int r = 0; r < height; r++) {
for (int c = 0; c < width; c++) {
if (featureSpace[r][c] > 0) {
cellTotal = featureSpace2[r][c];
numNeighbours = 1;
for (int j = 0; j < 8; j++) {
rN = r + Dy[j];
cN = c + Dx[j];
if (rN >= 0 && rN < height && cN >= 0 && cN < width) {
if (featureSpace[rN][cN] != 0) {
cellTotal += featureSpace[rN][cN];
numNeighbours++;
}
}
}
featureSpace2[r][c] = cellTotal / numNeighbours;
}
}
}
long total = 0;
for (int r = 0; r < height; r++) {
for (int c = 0; c < width; c++) {
total += featureSpace[r][c];
}
}
// first scan to find how many peaks there are
for (int r = 0; r < height; r++) {
for (int c = 0; c < width; c++) {
z = featureSpace2[r][c];
if (z / total > peakSize) {
flag = true;
for (int j = 0; j < 8; j++) {
rN = r + Dy[j];
cN = c + Dx[j];
if (rN >= 0 && rN < height && cN >= 0 && cN < width) {
if (featureSpace2[rN][cN] > z) {
flag = false;
break;
}
}
}
if (flag) {
// it's a peak
numPeaks++;
}
}
}
}
peaks = new int[numPeaks][3];
int k = 0;
for (int r = 0; r < height; r++) {
for (int c = 0; c < width; c++) {
z = featureSpace2[r][c];
if (z / total > peakSize) {
flag = true;
for (int j = 0; j < 8; j++) {
rN = r + Dy[j];
cN =c + Dx[j];
if (rN >= 0 && rN < height && cN >= 0 && cN < width) {
if (featureSpace2[rN][cN] > z) {
flag = false;
break;
}
}
}
if (flag) {
// it's a peak
peaks[k][0] = r + image1Min;
peaks[k][1] = c + image2Min;
peaks[k][2] = featureSpace[r][c];
k++;
//peaks.add(new Dimension(r + image1Min, c + image2Min));
}
}
}
}
}
private void createPlotImageData() {
// find the maximum point density in featureSpace
int maxDensityVal = -1;
long total = 0;
for (int r = 0; r < height; r++) {
for (int c = 0; c < width; c++) {
if (featureSpace[r][c] > maxDensityVal) {
maxDensityVal = featureSpace[r][c];
}
total += featureSpace[r][c];
}
}
double maxDensityValLog = Math.log(maxDensityVal);
imageData = new int[numPix];
int index = 0;
int red, green, blue;
double loc;
double paletteZoneSize = 1d / 6;
if (isHillshaded) {
double[][] hillshade = new double[height][width];
int[] Dy = {-1, 0, 1, 1, 1, 0, -1, -1};
int[] Dx = {1, 1, 1, 0, -1, -1, -1, 0};
double[] N = new double[8];
double term1, term2, term3;
final double radToDeg = 180 / Math.PI;
final double degToRad = Math.PI / 180;
double azimuth = 135 * degToRad;
double altitude = 45 * degToRad;
double z;
double fx, fy, aspect;
double Rad180 = 180 * degToRad;
double Rad90 = 90 * degToRad;
int rN, cN;
double sinTheta;
double cosTheta;
double tanSlope;
sinTheta = Math.sin(altitude);
cosTheta = Math.cos(altitude);
double minHill = 99999;
double maxHill = -99999;
for (int r = height - 1; r >= 0; r--) {
for (int c = 0; c < width; c++) {
z = (double) (featureSpace[r][c] / (double)total * 1000);
for (int j = 0; j < 8; j++) {
rN = r + Dy[j];
cN = c + Dx[j];
if (rN >= 0 && cN >= 0 && rN < height && cN < width) {
N[j] = (featureSpace[rN][cN] / (double)total) * 1000;
} else {
N[j] = z;
}
}
// calculate slope and aspect
fy = (N[6] - N[4] + 2 * (N[7] - N[3]) + N[0] - N[2]) / 8;
fx = (N[2] - N[4] + 2 * (N[1] - N[5]) + N[0] - N[6]) / 8;
if (fx != 0) {
tanSlope = Math.sqrt(fx * fx + fy * fy);
aspect = (180 - Math.atan(fy / fx) * radToDeg + 90 * (fx / Math.abs(fx))) * degToRad;
term1 = tanSlope / Math.sqrt(1 + tanSlope * tanSlope);
term2 = sinTheta / tanSlope;
term3 = cosTheta * Math.sin(azimuth - aspect);
z = term1 * (term2 - term3);
} else {
z = 0.5;
}
//if (z > 1) { z = 1; }
//if (z < 0.3) { z = 0.3; }
if (z < minHill) { minHill = z; }
if (z > maxHill) { maxHill = z; }
hillshade[r][c] = z;
}
}
double range = maxHill - minHill;
for (int r = height - 1; r >= 0; r--) {
for (int c = 0; c < width; c++) {
z = (hillshade[r][c] - minHill) / (range);
if (z < 0.3) { z = 0.3; }
hillshade[r][c] = z;
}
}
for (int r = height - 1; r >= 0; r--) {
for (int c = 0; c < width; c++) {
//loc = (double) featureSpace[r][c] / (double) maxDensityVal;
loc = Math.log(featureSpace[r][c]) / maxDensityValLog;
if (spectrumPaletteMode) {
if (featureSpace[r][c] == 0) {
red = backgroundColor;
green = backgroundColor;
blue = backgroundColor;
} else if (loc < paletteZoneSize) {
red = (int) (128 - (loc / paletteZoneSize * 128));
green = 0;
blue = (int) (128 + loc / paletteZoneSize * 128);
} else if (loc < 2 * paletteZoneSize) {
red = 0;
green = (int) ((loc - paletteZoneSize) / paletteZoneSize * 255);
blue = 255;
} else if (loc < 3 * paletteZoneSize) {
red = 0;
green = 255;
blue = (int) (255 - ((loc - 2 * paletteZoneSize) / paletteZoneSize * 255));
} else if (loc < 4 * paletteZoneSize) {
red = (int) ((loc - 3 * paletteZoneSize) / paletteZoneSize * 255);
green = 255;
blue = 0;
} else if (loc < 5 * paletteZoneSize) {
red = 255;
green = (int) (255 - ((loc - 4 * paletteZoneSize) / paletteZoneSize * 255));
blue = 0;
} else {
red = (int) (255 - ((loc - 5 * paletteZoneSize) / paletteZoneSize * 180));
green = 0;
blue = 0;
}
if (featureSpace[r][c] != 0) {
red = (int) (red * hillshade[r][c]);
green = (int) (green * hillshade[r][c]);
blue = (int) (blue * hillshade[r][c]);
}
} else {
if (featureSpace[r][c] == 0) {
red = 0;
green = 0;
blue = 0;
} else {
red = (int)(255 * loc);
green = (int)(255 * loc);
blue = (int)(255 * loc);
}
}
imageData[index] = ((255 << 24) | (red << 16) | (green << 8) | blue);
index++;
}
}
} else {
for (int r = height - 1; r >= 0; r--) {
for (int c = 0; c < width; c++) {
loc = Math.log(featureSpace[r][c]) / maxDensityValLog;
if (spectrumPaletteMode) {
if (featureSpace[r][c] == 0) {
red = backgroundColor;
green = backgroundColor;
blue = backgroundColor;
} else if (loc < paletteZoneSize) {
red = (int) (128 - (loc / paletteZoneSize * 128));
green = 0;
blue = (int) (128 + loc / paletteZoneSize * 128);
} else if (loc < 2 * paletteZoneSize) {
red = 0;
green = (int) ((loc - paletteZoneSize) / paletteZoneSize * 255);
blue = 255;
} else if (loc < 3 * paletteZoneSize) {
red = 0;
green = 255;
blue = (int) (255 - ((loc - 2 * paletteZoneSize) / paletteZoneSize * 255));
} else if (loc < 4 * paletteZoneSize) {
red = (int) ((loc - 3 * paletteZoneSize) / paletteZoneSize * 255);
green = 255;
blue = 0;
} else if (loc < 5 * paletteZoneSize) {
red = 255;
green = (int) (255 - ((loc - 4 * paletteZoneSize) / paletteZoneSize * 255));
blue = 0;
} else {
red = (int) (255 - ((loc - 5 * paletteZoneSize) / paletteZoneSize * 128));
green = 0;
blue = 0;
}
} else {
if (featureSpace[r][c] == 0) {
red = 0;
green = 0;
blue = 0;
} else {
red = (int)(255 * loc);
green = (int)(255 * loc);
blue = (int)(255 * loc);
}
}
imageData[index] = ((255 << 24) | (red << 16) | (green << 8) | blue);
index++;
}
}
}
plotCreated = true;
}
private void setMouseMotionListener() {
this.addMouseMotionListener(this);
}
private void setMouseListener() {
this.addMouseListener(this);
}
public void refresh() {
plotCreated = false;
repaint();
}
@Override
public void paint (Graphics g) {
drawPlot(g);
}
private void drawPlot(Graphics g) {
if (!plotCreated) { createPlotImageData(); }
leftMargin = 60;
Graphics2D g2d = (Graphics2D) g;
g2d.setFont(myFont);
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d.setColor(Color.white);
g2d.fillRect(0, 0, getWidth(), getHeight());
double activeWidth = getWidth() - leftMargin - rightMargin;
double activeHeight = getHeight() - topMargin - bottomMargin;
int bottomY = getHeight() - bottomMargin;
int rightX = getWidth() - rightMargin;
int rectLeft, rectWidth, rectTop, rectHeight;
Image image = createImage(new MemoryImageSource(width, height, imageData, 0, width));
g2d.drawImage(image, leftMargin, topMargin, (int)activeWidth, (int)activeHeight, this);
// draw axes
g2d.setColor(Color.black);
g2d.drawLine(leftMargin, bottomY, rightX, bottomY);
g2d.drawLine(leftMargin, bottomY, leftMargin, topMargin);
g2d.drawLine(leftMargin, topMargin, rightX, topMargin);
g2d.drawLine(rightX, bottomY, rightX, topMargin);
// draw ticks
int tickSize = 4;
g2d.drawLine(leftMargin, bottomY, leftMargin, bottomY + tickSize);
g2d.drawLine(rightX, bottomY, rightX, bottomY + tickSize);
g2d.drawLine(leftMargin, bottomY, leftMargin - tickSize, bottomY);
g2d.drawLine(leftMargin, topMargin, leftMargin - tickSize, topMargin);
// histo labels
DecimalFormat df = new DecimalFormat("#,###,###.###");
Font font = new Font("SanSerif", Font.PLAIN, 11);
FontMetrics metrics = g.getFontMetrics(font);
int hgt, adv;
hgt = metrics.getHeight();
// x-axis labels
String label;
label = df.format(image2Min);
g2d.drawString(label, leftMargin, bottomY + hgt + 4);
label = df.format(image2Max);
adv = metrics.stringWidth(label);
g2d.drawString(label, rightX - adv, bottomY + hgt + 4);
label = shortName2;
adv = metrics.stringWidth(label);
int xAxisMidPoint = (int)(leftMargin + activeWidth / 2);
g2d.drawString(label, xAxisMidPoint - adv / 2, bottomY + 2 * hgt + 4);
// y-axis labels
// rotate the font
Font oldFont = g.getFont();
Font f = oldFont.deriveFont(AffineTransform.getRotateInstance(-Math.PI / 2.0));
g2d.setFont(f);
int yAxisMidPoint = (int)(topMargin + activeHeight / 2);
int offset;
label = shortName1;
offset = metrics.stringWidth("0") + 12 + hgt;
adv = metrics.stringWidth(label);
g2d.drawString(label, leftMargin - offset, yAxisMidPoint + adv / 2);
// replace the rotated font.
g2d.setFont(oldFont);
df = new DecimalFormat("0");
label = df.format(image1Min);
adv = metrics.stringWidth(label);
g2d.drawString(label, leftMargin - adv - 12, bottomY + hgt / 2);
label = df.format(image1Max);
adv = metrics.stringWidth(label);
g2d.drawString(label, leftMargin - adv - 12, topMargin + hgt / 2);
// title
// bold font
oldFont = g.getFont();
font = font = new Font("SanSerif", Font.BOLD, 12);
g2d.setFont(font);
label = "Feature Space Plot: " + shortName1 + " vs. " + shortName2;
adv = metrics.stringWidth(label);
g2d.drawString(label, getWidth() / 2 - adv / 2, topMargin - hgt - 5);
g2d.setFont(oldFont);
if (gridOn) {
// vertical grid lines
int gridSpacing = 50;
if (image2Max > 255) {
gridSpacing = 100;
}
int loc = 0;
if (backgroundColor == 0 || !spectrumPaletteMode) {
g2d.setColor(Color.white);
} else {
g2d.setColor(Color.black);
}
for (int i = image2Min; i <= image2Max; i++) {
if (i % gridSpacing == 0) {
loc = (int)(leftMargin + (i - image2Min) / ((double) image2Max - image2Min) * activeWidth);
g2d.drawLine(loc, bottomY, loc, topMargin - 1);
}
}
// horizontal grid lines
if (image1Max > 255) {
gridSpacing = 100;
}
for (int i = image1Min; i <= image1Max; i++) {
if (i % gridSpacing == 0) {
loc = (int)(bottomY - (i - image1Min) / ((double) image1Max - image1Min) * activeHeight);
g2d.drawLine(leftMargin + 1, loc, rightX, loc);
}
}
}
if (plotPeaks) {
if (!peaksFound) {
findLocalPeaks();
}
g2d.setColor(Color.black);
int crossHairSize = 4;
int row, col;
for (int j = 0; j < peaks.length; j++) {
row = (int)(bottomY - (peaks[j][0] - image1Min) / ((double) image1Max - image1Min) * activeHeight);
col = (int)(leftMargin + (peaks[j][1] - image2Min) / ((double) image2Max - image2Min) * activeWidth);
g2d.drawLine(col - crossHairSize, row, col + crossHairSize, row);
g2d.drawLine(col, row - crossHairSize, col, row + crossHairSize);
}
}
if (drawPositionLine) {
// draw a red vertical line at this position.
g2d.setColor(Color.red);
g2d.drawLine(posX, bottomY, posX, topMargin);
g2d.setColor(Color.white);
g2d.drawLine(posX + 1, bottomY, posX + 1, topMargin);
// draw a red horizontal line at this position.
g2d.setColor(Color.red);
g2d.drawLine(leftMargin, posY, rightX, posY);
g2d.setColor(Color.white);
g2d.drawLine(leftMargin, posY + 1, rightX, posY + 1);
// which bin is it and how many are in the bin?
int x = (int)(((double)posX - leftMargin) / activeWidth * (width - 1));
int y = (int)(((1 - ((double)posY - topMargin) / activeHeight) * (height - 1)));
df = new DecimalFormat("0");
int val1 = featureSpace[y][x];
String val = df.format(val1);
label = "x: " + (x + image2Min) + " y: " + (y + image1Min) + " Value: " + val;
adv = metrics.stringWidth(label);
g2d.setColor(Color.black);
g2d.drawString(label, 10, getHeight() - hgt - 10);
}
}
public boolean saveToImage(String fileName) {
try {
int width = (int)this.getWidth();
int height =(int)this.getHeight();
// TYPE_INT_ARGB specifies the image format: 8-bit RGBA packed
// into integer pixels
BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics ig = bi.createGraphics();
drawPlot(ig);
int i = fileName.lastIndexOf(".");
String extension = fileName.substring(fileName.lastIndexOf(".") + 1).toUpperCase();
if (!ImageIO.write(bi, extension, new File(fileName))) {
return false;
}
return true;
} catch (Exception ex) {
return false;
}
}
@Override
public int print(Graphics g, PageFormat pf, int page)
throws PrinterException {
if (page > 0) {
return NO_SUCH_PAGE;
}
int i = pf.getOrientation();
// get the size of the page
double pageWidth = pf.getImageableWidth();
double pageHeight = pf.getImageableHeight();
double myWidth = this.getWidth();// - borderWidth * 2;
double myHeight = this.getHeight();// - borderWidth * 2;
double scaleX = pageWidth / myWidth;
double scaleY = pageHeight / myHeight;
double minScale = Math.min(scaleX, scaleY);
Graphics2D g2d = (Graphics2D) g;
g2d.translate(pf.getImageableX(), pf.getImageableY());
g2d.scale(minScale, minScale);
drawPlot(g);
return PAGE_EXISTS;
}
private boolean drawPositionLine = false;
private int posX = 0;
private int posY = 0;
@Override
public void mouseMoved(MouseEvent e) {
// is the mouse over the chart area?
int x = e.getX();
int y = e.getY();
int width = getWidth();
int height = getHeight();
if (x >= leftMargin && x <= (width - rightMargin) &&
y >= topMargin && y <= (height - bottomMargin) ) {
drawPositionLine = true;
posX = x;
posY = y;
} else {
drawPositionLine = false;
}
repaint();
}
private void createPopupMenus() {
// menu
myPopup = new JPopupMenu();
JMenuItem mi = new JMenuItem("Save");
mi.addActionListener(this);
mi.setActionCommand("save");
myPopup.add(mi);
mi = new JMenuItem("Print");
mi.addActionListener(this);
mi.setActionCommand("print");
myPopup.add(mi);
myPopup.addSeparator();
cmi = new JCheckBoxMenuItem("Hillshade Plot");
cmi.addActionListener(this);
cmi.setState(isHillshaded);
cmi.setActionCommand("hillshade plot");
myPopup.add(cmi);
backgroundMi = new JMenuItem("Black Background");
backgroundMi.addActionListener(this);
backgroundMi.setActionCommand("changeBackground");
myPopup.add(backgroundMi);
paletteMi = new JMenuItem("Grey-scale Palette");
paletteMi.addActionListener(this);
paletteMi.setActionCommand("changePalette");
myPopup.add(paletteMi);
gridMi = new JMenuItem("Turn Off Grid");
gridMi.addActionListener(this);
gridMi.setActionCommand("grid");
myPopup.add(gridMi);
plotPeaksMi = new JCheckBoxMenuItem("Plot Peaks");
plotPeaksMi.addActionListener(this);
plotPeaksMi.setState(plotPeaks);
plotPeaksMi.setActionCommand("plot peaks");
myPopup.add(plotPeaksMi);
myPopup.setOpaque(true);
myPopup.setLightWeightPopupEnabled(true);
}
private void printPlot() {
PrinterJob job = PrinterJob.getPrinterJob();
PrintRequestAttributeSet aset = new HashPrintRequestAttributeSet();
//PageFormat pf = job.pageDialog(aset);
job.setPrintable(this);
boolean ok = job.printDialog(aset);
if (ok) {
try {
job.print(aset);
} catch (PrinterException ex) {
//showFeedback("An error was encountered while printing." + ex);
/* The job did not successfully complete */
}
}
}
private void savePlotAsImage() {
// get the possible image name.
String imageName = shortName1 + "_vs_" + shortName2;
// Ask the user to specify a file name for saving the histo.
String pathSep = File.separator;
JFileChooser fc = new JFileChooser();
fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
//fc.setCurrentDirectory(new File(workingDirectory + pathSep + imageName + ".png"));
fc.setAcceptAllFileFilterUsed(false);
//File f = new File(workingDirectory + pathSep + imageName + ".png");
//fc.setSelectedFile(f);
// set the filter.
ArrayList<ExtensionFileFilter> filters = new ArrayList<ExtensionFileFilter>();
String[] extensions = ImageIO.getReaderFormatNames(); //{"PNG", "JPEG", "JPG"};
String filterDescription = "Image Files (" + extensions[0];
for (int i = 1; i < extensions.length; i++) {
filterDescription += ", " + extensions[i];
}
filterDescription += ")";
ExtensionFileFilter eff = new ExtensionFileFilter(filterDescription, extensions);
fc.setFileFilter(eff);
int result = fc.showSaveDialog(this);
File file = null;
if (result == JFileChooser.APPROVE_OPTION) {
file = fc.getSelectedFile();
// see if file has an extension.
if (file.toString().lastIndexOf(".") <= 0) {
String fileName = file.toString() + ".png";
file = new File(fileName);
}
String fileDirectory = file.getParentFile() + pathSep;
// if (!fileDirectory.equals(workingDirectory)) {
// workingDirectory = fileDirectory;
// }
// see if the file exists already, and if so, should it be overwritten?
if (file.exists()) {
Object[] options = {"Yes", "No"};
int n = JOptionPane.showOptionDialog(this,
"The file already exists.\n"
+ "Would you like to overwrite it?",
"Whitebox GAT Message",
JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE,
null, //do not use a custom Icon
options, //the titles of buttons
options[0]); //default button title
if (n == JOptionPane.YES_OPTION) {
file.delete();
} else if (n == JOptionPane.NO_OPTION) {
return;
}
}
if (!saveToImage(file.toString())) {
// showFeedback("An error occurred while saving the map to the image file.");
}
}
}
@Override
public void mouseDragged(MouseEvent me) {
//throw new UnsupportedOperationException("Not supported yet.");
}
// ActionListener for events
@Override
public void actionPerformed(ActionEvent ae) {
String actionCommand = ae.getActionCommand().toLowerCase();
if (actionCommand.equals("save")) {
savePlotAsImage();
} else if (actionCommand.equals("print")) {
printPlot();
} else if (actionCommand.equals("hillshade plot")) {
isHillshaded = !isHillshaded;
refresh();
} else if (actionCommand.equals("plot peaks")) {
plotPeaks = !plotPeaks;
refresh();
} else if (actionCommand.equals("changebackground")) {
if (backgroundMi.getText().equals("White Background")) {
backgroundMi.setText("Black Background");
backgroundColor = 255;
} else {
backgroundMi.setText("White Background");
backgroundColor = 0;
}
refresh();
} else if (actionCommand.equals("grid")) {
if (gridOn) {
gridOn = false;
gridMi.setText("Turn On Grid");
} else if (!gridOn) {
gridOn = true;
gridMi.setText("Turn Off Grid");
}
refresh();
} else if (actionCommand.equals("changepalette")) {
spectrumPaletteMode = !spectrumPaletteMode;
if (spectrumPaletteMode) {
paletteMi.setText("Grey-scale Palette");
backgroundMi.setText("Black Background");
backgroundColor = 255;
backgroundMi.setEnabled(true);
cmi.setEnabled(true);
} else {
paletteMi.setText("Spectrum Palette");
isHillshaded = false;
cmi.setState(false);
cmi.setEnabled(false);
backgroundMi.setText("White Background");
backgroundColor = 0;
backgroundMi.setEnabled(false);
}
refresh();
}
}
@Override
public void mouseClicked(MouseEvent me) {
if (me.getButton() == 3 || me.isPopupTrigger()) {
myPopup.show((Component)me.getSource(), me.getX(), me.getY());
}
}
@Override
public void mousePressed(MouseEvent me) {
}
@Override
public void mouseReleased(MouseEvent me) {
//throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void mouseEntered(MouseEvent me) {
//throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void mouseExited(MouseEvent me) {
//throw new UnsupportedOperationException("Not supported yet.");
}
}