You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于X、Y坐标点列表绘制曲线峰值?

我明白你现在卡在Java Swing绘制曲线这一步了——空JFrame确实让人头疼,尤其是要用来分析鼠标移动的峰值数据。咱们一步步来解决这个问题,先从Swing绘制的核心逻辑说起,再针对性实现峰值标记:

第一步:用JPanel作为绘制容器(核心!)

Swing的绘制逻辑是重写JPanel的paintComponent(Graphics g)方法,而不是直接操作JFrame。因为JFrame的内容窗格是用来承载组件的,直接画会被覆盖或者根本不触发绘制。

下面是一个自定义的绘制面板,专门用来处理你的坐标数据和峰值标记:

import javax.swing.*;
import java.awt.*;
import java.util.List;

// 自定义绘制面板,负责曲线绘制和峰值标记
class PeakPlotPanel extends JPanel {
    private List<Integer> xCoords;
    private List<Integer> yCoords;

    // 传入鼠标移动的X/Y坐标数据
    public PeakPlotPanel(List<Integer> x, List<Integer> y) {
        this.xCoords = x;
        this.yCoords = y;
        // 设置面板的首选大小,避免窗口缩成一团
        setPreferredSize(new Dimension(800, 600));
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g); // 必须调用!清空面板并绘制背景
        Graphics2D g2d = (Graphics2D) g;
        // 开启抗锯齿,让曲线和标记更平滑
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        // 数据校验:没有数据就直接返回
        if (xCoords == null || yCoords == null || xCoords.size() != yCoords.size()) {
            return;
        }

        // 先绘制坐标轴
        g2d.setColor(Color.BLACK);
        int padding = 50; // 留边距,避免内容贴窗口边缘
        g2d.drawLine(padding, getHeight() - padding, getWidth() - padding, getHeight() - padding); // X轴
        g2d.drawLine(padding, padding, padding, getHeight() - padding); // Y轴

        // 计算坐标缩放:把原始数据映射到面板的绘制区域(避免数据超出窗口范围)
        int plotWidth = getWidth() - 2 * padding;
        int plotHeight = getHeight() - 2 * padding;

        // 找出X/Y的极值,用于缩放计算
        int minX = Integer.MAX_VALUE, maxX = Integer.MIN_VALUE;
        int minY = Integer.MAX_VALUE, maxY = Integer.MIN_VALUE;
        for (int x : xCoords) {
            minX = Math.min(minX, x);
            maxX = Math.max(maxX, x);
        }
        for (int y : yCoords) {
            minY = Math.min(minY, y);
            maxY = Math.max(maxY, y);
        }
        // 处理所有值相同的情况,防止除以0报错
        if (maxX == minX) maxX = minX + 1;
        if (maxY == minY) maxY = minY + 1;

        // 绘制曲线+标记峰值
        g2d.setColor(Color.BLUE);
        int prevPlotX = 0, prevPlotY = 0;
        for (int i = 0; i < xCoords.size(); i++) {
            // 把原始X坐标映射到面板的X位置(从左 padding 开始)
            int plotX = padding + (int) ((xCoords.get(i) - minX) * (double) plotWidth / (maxX - minX));
            // Swing的Y轴是从顶部开始的,所以反转Y坐标,让数值大的点在上方
            int plotY = getHeight() - padding - (int) ((yCoords.get(i) - minY) * (double) plotHeight / (maxY - minY));

            if (i == 0) {
                prevPlotX = plotX;
                prevPlotY = plotY;
                continue;
            }
            // 用线段连接相邻的坐标点
            g2d.drawLine(prevPlotX, prevPlotY, plotX, plotY);

            // 检测峰值:当前点比左右相邻点都高(边界点单独判断)
            boolean isPeak = false;
            if (i == 1) {
                isPeak = yCoords.get(i) > yCoords.get(i-1);
            } else if (i == xCoords.size()-1) {
                isPeak = yCoords.get(i) > yCoords.get(i-1);
            } else {
                isPeak = yCoords.get(i) > yCoords.get(i-1) && yCoords.get(i) > yCoords.get(i+1);
            }

            // 如果是峰值,用红色实心圆标记
            if (isPeak) {
                g2d.setColor(Color.RED);
                g2d.fillOval(plotX-5, plotY-5, 10, 10); // 半径5的圆,刚好覆盖峰值点
                g2d.setColor(Color.BLUE); // 切回曲线颜色,继续绘制
            }

            prevPlotX = plotX;
            prevPlotY = plotY;
        }
    }

    // 可选:实时更新数据的方法(如果你的鼠标数据是动态采集的)
    public void updateData(List<Integer> newX, List<Integer> newY) {
        this.xCoords = newX;
        this.yCoords = newY;
        repaint(); // 通知面板重新绘制
    }
}
第二步:把面板添加到JFrame中

接下来要正确初始化JFrame,把自定义面板加进去——这一步很多人会出错,直接在JFrame上画是行不通的:

import java.util.List;
import javax.swing.SwingUtilities;

public class MousePeakAnalyzer {
    public static void main(String[] args) {
        // 模拟你的鼠标移动X/Y数据(替换成你实际采集的坐标)
        List<Integer> mouseX = List.of(0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150);
        List<Integer> mouseY = List.of(8, 12, 20, 10, 28, 15, 35, 18, 12, 7, 3);

        // 必须在事件调度线程(EDT)中创建Swing组件!Swing不是线程安全的
        SwingUtilities.invokeLater(() -> {
            JFrame frame = new JFrame("鼠标移动峰值分析");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            // 添加自定义绘制面板
            frame.add(new PeakPlotPanel(mouseX, mouseY));
            frame.pack(); // 根据面板的首选大小自动调整窗口
            frame.setLocationRelativeTo(null); // 让窗口居中显示
            frame.setVisible(true);
        });
    }
}
为什么你的JFrame是空的?排查常见坑
  1. 没调用super.paintComponent(g):这会导致面板背景没有被正确绘制,要么显示空白,要么残留之前的绘制内容。
  2. 直接在JFrame上重写paint方法:JFrame的paint方法负责绘制标题栏、边框等UI元素,你的绘制内容会被覆盖,必须用JPanel的paintComponent。
  3. 没设置面板大小:如果JPanel没有首选大小,JFrame调用pack()后会缩成很小的窗口,看起来像空的。
  4. 不在EDT中创建组件:Swing组件必须在事件调度线程中初始化,否则可能出现绘制异常或者完全不显示。
  5. 数据问题:如果你的X/Y数据为空,或者缩放计算出错(比如除以0),会导致什么都画不出来,可以在paintComponent里加打印语句调试坐标值。

内容的提问来源于stack exchange,提问作者nano mano

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 10:10:56