/*
* Copyright (C) 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 whiteboxgis;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.io.File;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.nio.channels.FileChannel;
import java.text.DecimalFormat;
import javax.imageio.ImageIO;
import javax.swing.JPanel;
import whitebox.geospatialfiles.WhiteboxRasterBase.DataType;
import whitebox.geospatialfiles.WhiteboxRasterInfo;
import whitebox.utilities.Parallel;
/**
*
* @author Dr. John Lindsay email: jlindsay@uoguelph.ca
*/
public class Histogram extends JPanel implements ActionListener, Printable, MouseMotionListener {
private String headerFile = "";
private String paletteFile = "";
private WhiteboxRasterInfo wbInfo = null;
private int[] paletteData = new int[2048];
private int numPaletteEntries = 2048;
private int numHistoEntries = 1;
private double[] histo = new double[2048];
private double fullestBinVal = 0;
private int fullestBinVal2 = 0;
private boolean histoCreated = false;
private boolean cumulative = false;
private double noData;
private double minVal;
private double maxVal;
private double range;
private String shortName;
private long total;
private int bottomMargin = 50;
private int topMargin = 45;
private int leftMargin = 90;
private int rightMargin = 20;
private boolean isIntegerData = false;
// Constructors
public Histogram() {
setMouseMotionListener();
}
public Histogram(String headerFile) {
setHeaderFile(headerFile);
setMouseMotionListener();
}
// Setters and getters
public final void setHeaderFile(String headerFile) {
this.headerFile = headerFile;
wbInfo = new WhiteboxRasterInfo(headerFile);
shortName = wbInfo.getShortHeaderFile().replace(".dep", "");
getPaletteFileName();
readPalette();
}
public String getShortName() {
return shortName;
}
public void setCumulative(boolean cumulative) {
this.cumulative = cumulative;
this.histoCreated = false;
repaint();
}
// Methods
private void setMouseMotionListener() {
this.addMouseMotionListener(this);
}
public void refresh() {
histoCreated = false;
wbInfo = new WhiteboxRasterInfo(headerFile);
getPaletteFileName();
readPalette();
repaint();
}
private void getPaletteFileName() {
try {
if (wbInfo != null) {
String pathSep = File.separator;
String applicationDirectory = java.net.URLDecoder.decode(getClass().getProtectionDomain().getCodeSource().getLocation().getPath(), "UTF-8");
//getClass().getProtectionDomain().
if (applicationDirectory.endsWith(".exe") || applicationDirectory.endsWith(".jar")) {
applicationDirectory = new File(applicationDirectory).getParent();
} else {
// Add the path to the class files
applicationDirectory += getClass().getName().replace('.', File.separatorChar);
// Step one level up as we are only interested in the
// directory containing the class files
applicationDirectory = new File(applicationDirectory).getParent();
}
String paletteDirectory = applicationDirectory + pathSep + "resources" + pathSep + "palettes" + pathSep;
paletteFile = paletteDirectory + wbInfo.getPreferredPalette();
} else {
throw new Exception("File has not been set.");
}
} catch (Exception e) {
System.out.println(e);
}
}
private void readPalette() {
RandomAccessFile rIn = null;
ByteBuffer buf = null;
try {
// See if the data file exists.
File file = new File(paletteFile);
if (!file.exists()) {
return;
}
numPaletteEntries = (int) (file.length() / 4);
buf = ByteBuffer.allocate(numPaletteEntries * 4);
rIn = new RandomAccessFile(paletteFile, "r");
FileChannel inChannel = rIn.getChannel();
inChannel.position(0);
inChannel.read(buf);
// Check the byte order.
buf.order(ByteOrder.LITTLE_ENDIAN);
buf.rewind();
IntBuffer ib = buf.asIntBuffer();
paletteData = new int[numPaletteEntries];
ib.get(paletteData);
ib = null;
} catch (Exception e) {
System.err.println("Caught exception: " + e.toString());
System.err.println(e.getStackTrace());
} finally {
if (rIn != null) {
try {
rIn.close();
} catch (Exception e) {
}
}
}
}
int nCols;
int numHistoEntriesLessOne;
private void createHistogram() {
try {
if (wbInfo != null) {
whitebox.geospatialfiles.WhiteboxRasterBase.DataType dataType;
dataType = wbInfo.getDataType();
if (((dataType == DataType.DOUBLE) || (dataType == DataType.FLOAT))
&& wbInfo.doesDataContainFractionalParts()) {
numHistoEntries = numPaletteEntries;
} else {
numHistoEntries = (int) (wbInfo.getDisplayMaximum() - wbInfo.getDisplayMinimum());
isIntegerData = true;
}
histo = new double[numHistoEntries];
numHistoEntriesLessOne = numHistoEntries - 1;
int nRows = wbInfo.getNumberRows();
nCols = wbInfo.getNumberColumns();
double[] data = null;
noData = wbInfo.getNoDataValue();
minVal = wbInfo.getDisplayMinimum();
maxVal = wbInfo.getDisplayMaximum();
range = maxVal - minVal;
boolean tailed = false;
if (wbInfo.getDisplayMaximum() < wbInfo.getMaximumValue() || wbInfo.getDisplayMinimum() > wbInfo.getMinimumValue()) {
tailed = true;
}
//double binSize = range / numPaletteEntries;
double z = 0;
//int bin = 0;
Parallel.For(0, nRows, 1, new Parallel.LoopBody<Integer>() {
double[] data = null;
double z;
int bin = 0;
@Override
public void run(Integer row) {
//for (int row = 0; row < nRows; row++) {
data = wbInfo.getRowValues(row);
for (int col = 0; col < nCols; col++) {
if (data[col] != noData) {
z = data[col];
if (z < minVal) {
z = minVal;
}
if (z > maxVal) {
z = maxVal;
}
bin = (int) ((z - minVal) / range * numHistoEntriesLessOne);
histo[bin]++;
}
}
}
});
total = 0;
fullestBinVal2 = 0;
for (int a = 0; a < numHistoEntries; a++) {
total += histo[a];
if (histo[a] > fullestBinVal2) {
fullestBinVal2 = (int) histo[a];
}
}
if (cumulative) {
histo[0] = histo[0] / total;
fullestBinVal = 0;
for (int a = 1; a < numHistoEntries; a++) {
histo[a] = histo[a] / total + histo[a - 1];
if (histo[a] > fullestBinVal) {
fullestBinVal = histo[a];
}
}
} else {
fullestBinVal = 0;
if (!tailed) {
for (int a = 0; a < numHistoEntries; a++) {
histo[a] = histo[a] / total;
if (histo[a] > fullestBinVal) {
fullestBinVal = histo[a];
}
}
} else {
for (int a = 0; a < numHistoEntries; a++) {
histo[a] = histo[a] / total;
if (histo[a] > fullestBinVal && a > 0 && a < numHistoEntries - 1) {
fullestBinVal = histo[a];
}
}
}
}
//wbInfo.close();
histoCreated = true;
} else {
throw new Exception("File has not been set.");
}
} catch (Exception e) {
System.out.println(e);
}
}
@Override
public void paint(Graphics g) {
drawHistogram(g);
}
private void drawHistogram(Graphics g) {
if (!histoCreated) {
createHistogram();
}
if (!cumulative) {
leftMargin = 90;
} else {
leftMargin = 70;
}
Graphics2D g2d = (Graphics2D) g;
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;
double binWidth = activeWidth / (numHistoEntries + 1);
rectWidth = (int) (Math.round(binWidth));
if (rectWidth < 1) {
rectWidth = 1;
}
int paletteEntry = 0;
double paletteScale = numPaletteEntries / numHistoEntries;
for (int a = 0; a < numHistoEntries; a++) {
paletteEntry = (int) (a * paletteScale);
Color newColour = new Color(paletteData[paletteEntry]);
g2d.setColor(newColour);
rectLeft = (int) (leftMargin + a * binWidth);
if (histo[a] > fullestBinVal) { histo[a] = fullestBinVal; }
rectTop = (int) (topMargin + (double) (fullestBinVal - histo[a]) / fullestBinVal * activeHeight); //(int)(bottomMargin);
rectHeight = bottomY - rectTop;
g2d.fillRect(rectLeft, rectTop, rectWidth, rectHeight);
}
// 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(minVal);
g2d.drawString(label, leftMargin, bottomY + hgt + 4);
label = df.format(maxVal);
adv = metrics.stringWidth(label);
g2d.drawString(label, rightX - adv, bottomY + hgt + 4);
label = "Value";
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;
if (!cumulative) {
label = "Frequency Prob.";
offset = metrics.stringWidth("0.0000") + 12 + hgt;
} else {
label = "Cumulative Prob.";
offset = metrics.stringWidth("0.0") + 12 + hgt;
}
adv = metrics.stringWidth(label);
//g2d.drawString(label, leftMargin - offset, yAxisMidPoint + adv / 2);
double xr = leftMargin - offset;
double yr = yAxisMidPoint + adv / 2;
g2d.translate(xr, yr);
g2d.rotate(-Math.PI / 2.0, 0, 0);
g2d.drawString(label, 0, 0);
g2d.rotate(Math.PI / 2);
g2d.translate(-xr, -yr);
// replace the rotated font.
//g2d.setFont(oldFont);
if (!cumulative) {
df = new DecimalFormat("0.0000");
} else {
df = new DecimalFormat("0.0");
}
label = df.format(0.000);
adv = metrics.stringWidth(label);
g2d.drawString(label, leftMargin - adv - 12, bottomY + hgt / 2);
label = df.format(fullestBinVal);
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 = "Histogram: " + shortName;
adv = metrics.stringWidth(label);
g2d.drawString(label, xAxisMidPoint - adv / 2, topMargin - hgt - 5);
g2d.setFont(oldFont);
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);
// which histo bin is it and how many are in the bin?
int binNum = (int) (((double) posX - leftMargin) / activeWidth * (numHistoEntries - 1));
df = new DecimalFormat("0.000");
double val1 = minVal + ((double) posX - leftMargin) / activeWidth * (maxVal - minVal);
if (isIntegerData) {
df = new DecimalFormat("0");
val1 = (int) val1;
}
String val = df.format(val1);
//df = new DecimalFormat("#,###,###");
df = new DecimalFormat("0.0000");
// if (!cumulative) {
// label = "Value: " + val + " Freq: " + df.format((histo[binNum] * fullestBinVal2));
// } else {
label = "Value: " + val + " Freq: " + df.format((histo[binNum]));
// }
adv = metrics.stringWidth(label);
g2d.setColor(Color.black);
g2d.drawString(label, 5, hgt + 5);
}
}
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();
drawHistogram(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);
drawHistogram(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();
}
@Override
public void mouseDragged(MouseEvent me) {
throw new UnsupportedOperationException("Not supported yet.");
}
// ActionListener for events
@Override
public void actionPerformed(ActionEvent ae) {
throw new UnsupportedOperationException("Not supported yet.");
}
// // this is for debugging purposes.
// public static void main(String[] args) {
// JFrame frame = new JFrame();
// String file = "/Users/johnlindsay/Documents/Data/DEM filled.dep";
// //String file = "/Users/johnlindsay/Documents/Data/Picton Data/picton intensity_HistoEqual.dep";
// //String file = "/Users/johnlindsay/Documents/Data/Picton Data/picton intensity.dep";
// Histogram histo = new Histogram(file);
// Container contentPane = frame.getContentPane();
// contentPane.add(histo, BorderLayout.CENTER);
// frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// frame.setPreferredSize(new Dimension(600, 400));
// frame.pack();
// frame.setVisible(true);
//
// }
}