656 lines
17 KiB
Java
656 lines
17 KiB
Java
package sparseviewer.ui;
|
|
|
|
import sparseviewer.model.PlotMode;
|
|
import sparseviewer.interp.InterpolationResult;
|
|
import sparseviewer.interp.Grid2D;
|
|
|
|
|
|
import javax.swing.JPanel;
|
|
|
|
import java.awt.BasicStroke;
|
|
import java.awt.Color;
|
|
import java.awt.FontMetrics;
|
|
import java.awt.Graphics;
|
|
import java.awt.Graphics2D;
|
|
import java.awt.RenderingHints;
|
|
import java.awt.image.BufferedImage;
|
|
|
|
import java.awt.event.MouseAdapter;
|
|
import java.awt.event.MouseEvent;
|
|
import java.awt.event.MouseMotionAdapter;
|
|
|
|
public class PlotPanel extends JPanel {
|
|
|
|
private double[] x;
|
|
private double[] y;
|
|
private double[] z;
|
|
|
|
private InterpolationResult interpolationResult;
|
|
private BufferedImage imageBuffer;
|
|
private String xName = "X";
|
|
private String yName = "Y";
|
|
private String zName = "Z";
|
|
|
|
private PlotMode mode = PlotMode.PLOT_1D_LINE_POINTS;
|
|
|
|
private double xmin;
|
|
private double xmax;
|
|
private double ymin;
|
|
private double ymax;
|
|
private double zmin;
|
|
private double zmax;
|
|
|
|
private boolean hasData = false;
|
|
private boolean is2D = false;
|
|
|
|
private double crossX = Double.NaN;
|
|
private double crossY = Double.NaN;
|
|
|
|
private boolean dragVertical = false;
|
|
private boolean dragHorizontal = false;
|
|
|
|
private final int leftMargin = 80;
|
|
private final int rightMargin = 45;
|
|
private final int topMargin = 40;
|
|
private final int bottomMargin = 70;
|
|
|
|
public PlotPanel() {
|
|
setBackground(Color.WHITE);
|
|
|
|
addMouseListener(new MouseAdapter() {
|
|
@Override
|
|
public void mousePressed(MouseEvent e) {
|
|
if (!hasData) {
|
|
return;
|
|
}
|
|
|
|
int px = dataToScreenX(crossX);
|
|
int py = dataToScreenY(crossY);
|
|
|
|
if (Math.abs(e.getX() - px) < 8) {
|
|
dragVertical = true;
|
|
}
|
|
|
|
if (Math.abs(e.getY() - py) < 8) {
|
|
dragHorizontal = true;
|
|
}
|
|
|
|
if (!dragVertical && !dragHorizontal) {
|
|
crossX = screenToDataX(e.getX());
|
|
crossY = screenToDataY(e.getY());
|
|
dragVertical = true;
|
|
dragHorizontal = true;
|
|
}
|
|
|
|
repaint();
|
|
}
|
|
|
|
@Override
|
|
public void mouseReleased(MouseEvent e) {
|
|
dragVertical = false;
|
|
dragHorizontal = false;
|
|
}
|
|
});
|
|
|
|
addMouseMotionListener(new MouseMotionAdapter() {
|
|
@Override
|
|
public void mouseDragged(MouseEvent e) {
|
|
if (!hasData) {
|
|
return;
|
|
}
|
|
|
|
if (dragVertical) {
|
|
crossX = screenToDataX(e.getX());
|
|
}
|
|
|
|
if (dragHorizontal) {
|
|
crossY = screenToDataY(e.getY());
|
|
}
|
|
|
|
repaint();
|
|
}
|
|
});
|
|
}
|
|
|
|
public void clear() {
|
|
hasData = false;
|
|
x = null;
|
|
y = null;
|
|
z = null;
|
|
interpolationResult = null;
|
|
imageBuffer = null;
|
|
repaint();
|
|
}
|
|
|
|
public void set1DData(double[] x,
|
|
double[] y,
|
|
String xName,
|
|
String yName,
|
|
PlotMode mode) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.z = null;
|
|
this.xName = xName;
|
|
this.yName = yName;
|
|
this.zName = "";
|
|
this.mode = mode;
|
|
this.is2D = false;
|
|
this.hasData = x != null
|
|
&& y != null
|
|
&& x.length > 0
|
|
&& x.length == y.length;
|
|
|
|
computeBounds();
|
|
|
|
crossX = 0.5 * (xmin + xmax);
|
|
crossY = 0.5 * (ymin + ymax);
|
|
|
|
repaint();
|
|
}
|
|
|
|
public void set2DData(double[] x,
|
|
double[] y,
|
|
double[] z,
|
|
String xName,
|
|
String yName,
|
|
String zName) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.z = z;
|
|
this.xName = xName;
|
|
this.yName = yName;
|
|
this.zName = zName;
|
|
this.mode = PlotMode.PLOT_2D_SCATTER;
|
|
|
|
this.interpolationResult = null;
|
|
this.imageBuffer = null;
|
|
|
|
this.is2D = true;
|
|
this.hasData = x != null
|
|
&& y != null
|
|
&& z != null
|
|
&& x.length > 0
|
|
&& x.length == y.length
|
|
&& x.length == z.length;
|
|
|
|
computeBounds();
|
|
|
|
crossX = 0.5 * (xmin + xmax);
|
|
crossY = 0.5 * (ymin + ymax);
|
|
|
|
repaint();
|
|
}
|
|
|
|
private void computeBounds() {
|
|
xmin = Double.POSITIVE_INFINITY;
|
|
xmax = Double.NEGATIVE_INFINITY;
|
|
ymin = Double.POSITIVE_INFINITY;
|
|
ymax = Double.NEGATIVE_INFINITY;
|
|
zmin = Double.POSITIVE_INFINITY;
|
|
zmax = Double.NEGATIVE_INFINITY;
|
|
|
|
if (!hasData) {
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < x.length; i++) {
|
|
if (isFinite(x[i])) {
|
|
if (x[i] < xmin) {
|
|
xmin = x[i];
|
|
}
|
|
|
|
if (x[i] > xmax) {
|
|
xmax = x[i];
|
|
}
|
|
}
|
|
|
|
if (isFinite(y[i])) {
|
|
if (y[i] < ymin) {
|
|
ymin = y[i];
|
|
}
|
|
|
|
if (y[i] > ymax) {
|
|
ymax = y[i];
|
|
}
|
|
}
|
|
|
|
if (is2D && z != null && isFinite(z[i])) {
|
|
if (z[i] < zmin) {
|
|
zmin = z[i];
|
|
}
|
|
|
|
if (z[i] > zmax) {
|
|
zmax = z[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!isFinite(xmin) || !isFinite(xmax) || !isFinite(ymin) || !isFinite(ymax)) {
|
|
hasData = false;
|
|
return;
|
|
}
|
|
|
|
if (xmin == xmax) {
|
|
xmin -= 1.0;
|
|
xmax += 1.0;
|
|
}
|
|
|
|
if (ymin == ymax) {
|
|
ymin -= 1.0;
|
|
ymax += 1.0;
|
|
}
|
|
|
|
if (is2D) {
|
|
if (!isFinite(zmin) || !isFinite(zmax)) {
|
|
zmin = 0.0;
|
|
zmax = 1.0;
|
|
}
|
|
|
|
if (zmin == zmax) {
|
|
zmin -= 1.0;
|
|
zmax += 1.0;
|
|
}
|
|
}
|
|
|
|
double xpad = 0.03 * (xmax - xmin);
|
|
double ypad = 0.03 * (ymax - ymin);
|
|
|
|
xmin -= xpad;
|
|
xmax += xpad;
|
|
ymin -= ypad;
|
|
ymax += ypad;
|
|
}
|
|
|
|
@Override
|
|
protected void paintComponent(Graphics g) {
|
|
super.paintComponent(g);
|
|
|
|
Graphics2D g2 = (Graphics2D) g;
|
|
g2.setRenderingHint(
|
|
RenderingHints.KEY_ANTIALIASING,
|
|
RenderingHints.VALUE_ANTIALIAS_ON
|
|
);
|
|
|
|
drawAxes(g2);
|
|
|
|
if (!hasData) {
|
|
g2.setColor(Color.GRAY);
|
|
g2.drawString("Load TXT file(s) to start.", leftMargin + 20, topMargin + 30);
|
|
return;
|
|
}
|
|
|
|
if (is2D) {
|
|
if (mode == PlotMode.PLOT_2D_IMAGE) {
|
|
drawInterpolatedImage(g2);
|
|
} else {
|
|
drawScatter2D(g2);
|
|
}
|
|
} else {
|
|
drawPlot1D(g2);
|
|
}
|
|
|
|
drawCrosshair(g2);
|
|
drawReadout(g2);
|
|
}
|
|
|
|
private void drawAxes(Graphics2D g2) {
|
|
int w = getWidth();
|
|
int h = getHeight();
|
|
|
|
int x0 = leftMargin;
|
|
int y0 = h - bottomMargin;
|
|
int x1 = w - rightMargin;
|
|
int y1 = topMargin;
|
|
|
|
g2.setColor(Color.BLACK);
|
|
g2.drawLine(x0, y0, x1, y0);
|
|
g2.drawLine(x0, y0, x0, y1);
|
|
|
|
g2.drawString(shortName(xName), x0 + 10, h - 25);
|
|
g2.drawString(shortName(yName), 15, y1 + 15);
|
|
|
|
if (is2D) {
|
|
g2.drawString("Color: " + shortName(zName), x0 + 10, 22);
|
|
}
|
|
}
|
|
|
|
private void drawPlot1D(Graphics2D g2) {
|
|
boolean drawLine = mode == PlotMode.PLOT_1D_LINE
|
|
|| mode == PlotMode.PLOT_1D_LINE_POINTS;
|
|
|
|
boolean drawPoints = mode == PlotMode.PLOT_1D_POINTS
|
|
|| mode == PlotMode.PLOT_1D_LINE_POINTS;
|
|
|
|
g2.setColor(new Color(30, 90, 180));
|
|
g2.setStroke(new BasicStroke(1.3f));
|
|
|
|
int prevX = 0;
|
|
int prevY = 0;
|
|
boolean havePrev = false;
|
|
|
|
for (int i = 0; i < x.length; i++) {
|
|
if (!isFinite(x[i]) || !isFinite(y[i])) {
|
|
continue;
|
|
}
|
|
|
|
int sx = dataToScreenX(x[i]);
|
|
int sy = dataToScreenY(y[i]);
|
|
|
|
if (drawLine && havePrev) {
|
|
g2.drawLine(prevX, prevY, sx, sy);
|
|
}
|
|
|
|
if (drawPoints) {
|
|
g2.fillOval(sx - 3, sy - 3, 6, 6);
|
|
}
|
|
|
|
prevX = sx;
|
|
prevY = sy;
|
|
havePrev = true;
|
|
}
|
|
}
|
|
|
|
private void drawScatter2D(Graphics2D g2) {
|
|
for (int i = 0; i < x.length; i++) {
|
|
if (!isFinite(x[i]) || !isFinite(y[i]) || !isFinite(z[i])) {
|
|
continue;
|
|
}
|
|
|
|
int sx = dataToScreenX(x[i]);
|
|
int sy = dataToScreenY(y[i]);
|
|
|
|
g2.setColor(colorForValue(z[i]));
|
|
g2.fillOval(sx - 3, sy - 3, 6, 6);
|
|
}
|
|
|
|
drawColorScale(g2);
|
|
}
|
|
|
|
private void drawColorScale(Graphics2D g2) {
|
|
int w = getWidth();
|
|
int h = getHeight();
|
|
|
|
int x0 = w - 30;
|
|
int y0 = topMargin;
|
|
int barH = h - topMargin - bottomMargin;
|
|
|
|
for (int i = 0; i < barH; i++) {
|
|
double t = 1.0 - (double) i / Math.max(1, barH - 1);
|
|
double val = zmin + t * (zmax - zmin);
|
|
|
|
g2.setColor(colorForValue(val));
|
|
g2.drawLine(x0, y0 + i, x0 + 12, y0 + i);
|
|
}
|
|
|
|
g2.setColor(Color.BLACK);
|
|
g2.drawRect(x0, y0, 12, barH);
|
|
|
|
g2.drawString(format(zmax), x0 - 65, y0 + 10);
|
|
g2.drawString(format(zmin), x0 - 65, y0 + barH);
|
|
}
|
|
|
|
private void drawCrosshair(Graphics2D g2) {
|
|
if (!isFinite(crossX) || !isFinite(crossY)) {
|
|
return;
|
|
}
|
|
|
|
int sx = dataToScreenX(crossX);
|
|
int sy = dataToScreenY(crossY);
|
|
|
|
int w = getWidth();
|
|
int h = getHeight();
|
|
|
|
g2.setColor(new Color(0, 0, 0, 150));
|
|
g2.setStroke(new BasicStroke(1.0f));
|
|
|
|
g2.drawLine(sx, topMargin, sx, h - bottomMargin);
|
|
g2.drawLine(leftMargin, sy, w - rightMargin, sy);
|
|
}
|
|
|
|
private void drawReadout(Graphics2D g2) {
|
|
int h = getHeight();
|
|
|
|
String text = "X=" + format(crossX) + " Y=" + format(crossY);
|
|
|
|
if (is2D) {
|
|
if (mode == PlotMode.PLOT_2D_IMAGE && interpolationResult != null) {
|
|
float value = interpolationResult.getNearestValue(crossX, crossY);
|
|
|
|
text += " Z(interp)=" + format(value);
|
|
|
|
int idx = nearestPoint(crossX, crossY);
|
|
if (idx >= 0 && z != null) {
|
|
text += " nearest raw Z=" + format(z[idx])
|
|
+ " idx=" + idx;
|
|
}
|
|
} else {
|
|
int idx = nearestPoint(crossX, crossY);
|
|
|
|
if (idx >= 0) {
|
|
text += " nearest Z=" + format(z[idx])
|
|
+ " idx=" + idx;
|
|
}
|
|
}
|
|
} else {
|
|
int idx = nearestX(crossX);
|
|
|
|
if (idx >= 0) {
|
|
text += " nearest Y=" + format(y[idx])
|
|
+ " idx=" + idx;
|
|
}
|
|
}
|
|
|
|
g2.setColor(Color.BLACK);
|
|
g2.drawString(text, leftMargin + 10, h - 8);
|
|
}
|
|
private void drawInterpolatedImage(Graphics2D g2) {
|
|
if (imageBuffer == null) {
|
|
return;
|
|
}
|
|
|
|
int x0 = leftMargin;
|
|
int y0 = topMargin;
|
|
int w = getWidth() - leftMargin - rightMargin;
|
|
int h = getHeight() - topMargin - bottomMargin;
|
|
|
|
g2.drawImage(imageBuffer, x0, y0, w, h, null);
|
|
|
|
g2.setColor(Color.BLACK);
|
|
g2.drawRect(x0, y0, w, h);
|
|
|
|
drawColorScale(g2);
|
|
}
|
|
private int nearestX(double xv) {
|
|
int best = -1;
|
|
double bestD = Double.POSITIVE_INFINITY;
|
|
|
|
for (int i = 0; i < x.length; i++) {
|
|
if (!isFinite(x[i])) {
|
|
continue;
|
|
}
|
|
|
|
double d = Math.abs(x[i] - xv);
|
|
|
|
if (d < bestD) {
|
|
bestD = d;
|
|
best = i;
|
|
}
|
|
}
|
|
|
|
return best;
|
|
}
|
|
|
|
private int nearestPoint(double xv, double yv) {
|
|
int best = -1;
|
|
double bestD = Double.POSITIVE_INFINITY;
|
|
|
|
double xr = xmax - xmin;
|
|
double yr = ymax - ymin;
|
|
|
|
for (int i = 0; i < x.length; i++) {
|
|
if (!isFinite(x[i]) || !isFinite(y[i])) {
|
|
continue;
|
|
}
|
|
|
|
double dx = (x[i] - xv) / xr;
|
|
double dy = (y[i] - yv) / yr;
|
|
double d = dx * dx + dy * dy;
|
|
|
|
if (d < bestD) {
|
|
bestD = d;
|
|
best = i;
|
|
}
|
|
}
|
|
|
|
return best;
|
|
}
|
|
|
|
private int dataToScreenX(double xv) {
|
|
int w = getWidth();
|
|
double t = (xv - xmin) / (xmax - xmin);
|
|
|
|
return leftMargin + (int) Math.round(t * (w - leftMargin - rightMargin));
|
|
}
|
|
|
|
private int dataToScreenY(double yv) {
|
|
int h = getHeight();
|
|
double t = (yv - ymin) / (ymax - ymin);
|
|
|
|
return h - bottomMargin
|
|
- (int) Math.round(t * (h - topMargin - bottomMargin));
|
|
}
|
|
private void rebuildImageBuffer() {
|
|
if (interpolationResult == null) {
|
|
imageBuffer = null;
|
|
return;
|
|
}
|
|
|
|
Grid2D grid = interpolationResult.getGrid();
|
|
int width = grid.getWidth();
|
|
int height = grid.getHeight();
|
|
|
|
imageBuffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
|
float[] pixels = interpolationResult.getPixels();
|
|
|
|
for (int iy = 0; iy < height; iy++) {
|
|
for (int ix = 0; ix < width; ix++) {
|
|
float v = pixels[iy * width + ix];
|
|
|
|
Color c;
|
|
|
|
if (Float.isNaN(v) || Float.isInfinite(v)) {
|
|
c = Color.GRAY;
|
|
} else {
|
|
c = colorForValue(v);
|
|
}
|
|
|
|
imageBuffer.setRGB(ix, height - 1 - iy, c.getRGB());
|
|
}
|
|
}
|
|
}
|
|
|
|
public void set2DImageData(double[] x,
|
|
double[] y,
|
|
double[] z,
|
|
String xName,
|
|
String yName,
|
|
String zName,
|
|
InterpolationResult result) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.z = z;
|
|
this.xName = xName;
|
|
this.yName = yName;
|
|
this.zName = zName;
|
|
this.mode = PlotMode.PLOT_2D_IMAGE;
|
|
this.is2D = true;
|
|
this.hasData = result != null;
|
|
this.interpolationResult = result;
|
|
|
|
if (result != null) {
|
|
Grid2D grid = result.getGrid();
|
|
|
|
this.xmin = grid.getXMin();
|
|
this.xmax = grid.getXMax();
|
|
this.ymin = grid.getYMin();
|
|
this.ymax = grid.getYMax();
|
|
this.zmin = result.getZMin();
|
|
this.zmax = result.getZMax();
|
|
|
|
this.crossX = 0.5 * (xmin + xmax);
|
|
this.crossY = 0.5 * (ymin + ymax);
|
|
|
|
rebuildImageBuffer();
|
|
}
|
|
|
|
repaint();
|
|
}
|
|
|
|
private double screenToDataX(int sx) {
|
|
int w = getWidth();
|
|
|
|
double t = (double) (sx - leftMargin)
|
|
/ Math.max(1, w - leftMargin - rightMargin);
|
|
|
|
return xmin + t * (xmax - xmin);
|
|
}
|
|
|
|
private double screenToDataY(int sy) {
|
|
int h = getHeight();
|
|
|
|
double t = (double) (h - bottomMargin - sy)
|
|
/ Math.max(1, h - topMargin - bottomMargin);
|
|
|
|
return ymin + t * (ymax - ymin);
|
|
}
|
|
|
|
private Color colorForValue(double v) {
|
|
double t = (v - zmin) / (zmax - zmin);
|
|
|
|
if (t < 0) {
|
|
t = 0;
|
|
}
|
|
|
|
if (t > 1) {
|
|
t = 1;
|
|
}
|
|
|
|
float hue = (float) (0.66 - 0.66 * t);
|
|
|
|
return Color.getHSBColor(hue, 1.0f, 1.0f);
|
|
}
|
|
|
|
private boolean isFinite(double v) {
|
|
return !Double.isNaN(v) && !Double.isInfinite(v);
|
|
}
|
|
|
|
private String format(double v) {
|
|
if (!isFinite(v)) {
|
|
return "NaN";
|
|
}
|
|
|
|
return String.format("%.6g", v);
|
|
}
|
|
|
|
private String shortName(String name) {
|
|
if (name == null) {
|
|
return "";
|
|
}
|
|
|
|
String s = name;
|
|
|
|
int typeIdx = s.lastIndexOf(":float");
|
|
|
|
if (typeIdx > 0) {
|
|
s = s.substring(0, typeIdx);
|
|
}
|
|
|
|
int idx = s.lastIndexOf(':');
|
|
|
|
if (idx >= 0 && idx < s.length() - 1) {
|
|
return s.substring(idx + 1);
|
|
}
|
|
|
|
return s;
|
|
}
|
|
} |