This commit is contained in:
gac-x11ma
2026-06-24 13:28:45 +02:00
parent b658ce0a15
commit 97ecbd2af2
113 changed files with 5193 additions and 456368 deletions
+656
View File
@@ -0,0 +1,656 @@
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;
}
}