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

如何在Java中绘制从Socket读取的机器人传感器数据?

如何在Java中绘制从Socket读取的距离与舵机角度数据

首先,咱们得拆解这个问题:先正确读取并解析Socket传来的“距离(cm) 舵机角度”格式数据,再用Java的UI库把这些数据可视化出来。下面分步骤给你详细说明:

1. 解析Socket输入的数据

你已经通过BufferedReader拿到了输入流,接下来需要循环读取每一行数据,分割成距离和舵机角度的数值。这里要注意两个关键点:一是IO操作会阻塞,必须放在单独的线程里,避免卡住UI;二是要处理数据格式异常,防止程序崩溃。

示例代码片段:

// 把Socket读取逻辑放到独立线程,避免阻塞UI线程
new Thread(() -> {
    String line;
    try {
        while ((line = data.readLine()) != null) {
            // 按空格拆分数据,支持多个连续空格的情况
            String[] parts = line.trim().split("\\s+");
            if (parts.length != 2) {
                System.err.println("无效数据格式: " + line);
                continue;
            }
            // 转换为数值类型
            double distance = Double.parseDouble(parts[0]);
            double servoAngle = Double.parseDouble(parts[1]);
            
            // 通知UI线程更新绘图
            updatePlot(distance, servoAngle);
        }
    } catch (IOException e) {
        System.err.println("Socket连接异常: " + e.getMessage());
        e.printStackTrace();
    } catch (NumberFormatException e) {
        System.err.println("数值转换失败: " + e.getMessage());
    }
}).start();

2. 用Swing实现绘图(传统Java桌面程序首选)

Swing是Java自带的UI库,我们可以自定义一个JPanel,重写paintComponent方法来绘制数据。比如做一个实时折线图展示距离变化,再搭配一个仪表盘显示舵机角度。

示例:实时距离折线图 + 舵机角度仪表盘

import javax.swing.*;
import java.awt.*;
import java.util.LinkedList;
import java.util.Queue;

public class RobotDataPlotter extends JFrame {
    private final Queue<Double> distanceData = new LinkedList<>();
    private double currentServoAngle = 0;
    private static final int MAX_DATA_POINTS = 50; // 最多保留50个历史数据点

    public RobotDataPlotter() {
        setTitle("机器人数据可视化");
        setSize(800, 600);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        JPanel plotPanel = new JPanel() {
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                Graphics2D g2d = (Graphics2D) g;
                g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                
                // 绘制距离折线图
                int width = getWidth();
                int height = getHeight() / 2;
                g2d.drawString("距离(cm) 实时变化", 20, 20);
                
                synchronized (distanceData) {
                    if (distanceData.size() < 2) return;
                    double maxDistance = distanceData.stream().max(Double::compare).orElse(100.0);
                    double minDistance = distanceData.stream().min(Double::compare).orElse(0.0);
                    double yScale = (height - 40) / (maxDistance - minDistance + 10); // 留边距避免数据超出范围
                    double xScale = (width - 40) / (double) (MAX_DATA_POINTS - 1);
                    
                    int prevX = 20;
                    int prevY = height - (int) ((distanceData.peek() - minDistance) * yScale);
                    int index = 0;
                    for (double d : distanceData) {
                        int x = 20 + (int) (index * xScale);
                        int y = height - (int) ((d - minDistance) * yScale);
                        g2d.drawLine(prevX, prevY, x, y);
                        prevX = x;
                        prevY = y;
                        index++;
                    }
                }
                
                // 绘制舵机角度仪表盘
                int centerX = width / 2;
                int centerY = height + 100;
                int radius = 80;
                g2d.drawString("舵机角度", centerX - 30, height + 50);
                g2d.drawOval(centerX - radius, centerY - radius, radius * 2, radius * 2);
                
                // 绘制角度指针(Swing Y轴向下,所以角度计算要取反)
                double angleRad = Math.toRadians(currentServoAngle);
                int endX = centerX + (int) (Math.cos(angleRad) * radius);
                int endY = centerY - (int) (Math.sin(angleRad) * radius);
                g2d.setColor(Color.RED);
                g2d.drawLine(centerX, centerY, endX, endY);
                g2d.drawString(String.format("%.1f°", currentServoAngle), endX + 10, endY);
            }
        };
        add(plotPanel);
        setVisible(true);
    }

    // 必须在UI线程更新,用SwingUtilities.invokeLater保证线程安全
    public void updatePlot(double distance, double servoAngle) {
        SwingUtilities.invokeLater(() -> {
            synchronized (distanceData) {
                distanceData.add(distance);
                if (distanceData.size() > MAX_DATA_POINTS) {
                    distanceData.poll();
                }
            }
            currentServoAngle = servoAngle;
            repaint(); // 触发面板重绘
        });
    }

    public static void main(String[] args) {
        // 初始化Socket和BufferedReader的代码放在这里
        // Socket robotOutput = new Socket("192.168.1.1", 228);
        // BufferedReader data = new BufferedReader(new InputStreamReader(robotOutput.getInputStream()));
        
        RobotDataPlotter plotter = new RobotDataPlotter();
        // 启动Socket读取线程(即前面的线程代码)
    }
}

3. 用JavaFX实现绘图(现代Java桌面程序首选)

如果是用JavaFX开发,推荐使用Canvas组件来绘制,JavaFX的线程模型更简洁,但更新UI同样需要切换到UI线程(用Platform.runLater)。

示例:JavaFX实时绘图

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

import java.io.BufferedReader;
import java.io.IOException;
import java.net.Socket;
import java.util.LinkedList;
import java.util.Queue;

public class RobotFxPlotter extends Application {
    private final Queue<Double> distanceData = new LinkedList<>();
    private double currentServoAngle = 0;
    private static final int MAX_DATA_POINTS = 50;

    @Override
    public void start(Stage stage) {
        Canvas canvas = new Canvas(800, 600);
        GraphicsContext gc = canvas.getGraphicsContext2D();
        
        VBox root = new VBox(canvas);
        Scene scene = new Scene(root, 800, 600);
        stage.setTitle("机器人数据可视化");
        stage.setScene(scene);
        stage.show();

        // 启动Socket读取线程
        new Thread(() -> {
            try (Socket robotOutput = new Socket("192.168.1.1", 228);
                 BufferedReader data = new BufferedReader(new java.io.InputStreamReader(robotOutput.getInputStream()))) {
                String line;
                while ((line = data.readLine()) != null) {
                    String[] parts = line.trim().split("\\s+");
                    if (parts.length != 2) continue;
                    double distance = Double.parseDouble(parts[0]);
                    double angle = Double.parseDouble(parts[1]);
                    
                    // 切换到UI线程更新绘图
                    Platform.runLater(() -> {
                        updateData(distance, angle);
                        drawPlot(gc, canvas.getWidth(), canvas.getHeight());
                    });
                }
            } catch (IOException | NumberFormatException e) {
                e.printStackTrace();
            }
        }).start();
    }

    private void updateData(double distance, double angle) {
        distanceData.add(distance);
        if (distanceData.size() > MAX_DATA_POINTS) {
            distanceData.poll();
        }
        currentServoAngle = angle;
    }

    private void drawPlot(GraphicsContext gc, double width, double height) {
        gc.clearRect(0, 0, width, height);
        
        // 绘制距离折线图
        gc.setFill(Color.BLACK);
        gc.fillText("距离(cm) 实时变化", 20, 20);
        gc.setStroke(Color.BLUE);
        
        if (distanceData.size() >= 2) {
            double maxDist = distanceData.stream().max(Double::compare).orElse(100.0);
            double minDist = distanceData.stream().min(Double::compare).orElse(0.0);
            double yScale = (height / 2 - 40) / (maxDist - minDist + 10);
            double xScale = (width - 40) / (double)(MAX_DATA_POINTS - 1);
            
            double prevX = 20;
            double prevY = (height / 2) - ((distanceData.peek() - minDist) * yScale);
            int index = 0;
            for (double d : distanceData) {
                double x = 20 + index * xScale;
                double y = (height / 2) - ((d - minDist) * yScale);
                gc.strokeLine(prevX, prevY, x, y);
                prevX = x;
                prevY = y;
                index++;
            }
        }
        
        // 绘制舵机角度仪表盘
        double centerX = width / 2;
        double centerY = height / 2 + 100;
        double radius = 80;
        gc.fillText("舵机角度", centerX - 30, height / 2 + 50);
        gc.strokeOval(centerX - radius, centerY - radius, radius * 2, radius * 2);
        
        gc.setStroke(Color.RED);
        double angleRad = Math.toRadians(currentServoAngle);
        double endX = centerX + Math.cos(angleRad) * radius;
        double endY = centerY - Math.sin(angleRad) * radius; // JavaFX Y轴向下,取反调整方向
        gc.strokeLine(centerX, centerY, endX, endY);
        gc.fillText(String.format("%.1f°", currentServoAngle), endX + 10, endY);
    }

    public static void main(String[] args) {
        launch(args);
    }
}

关键注意事项

  • 线程安全:Socket读取必须在非UI线程执行,更新UI时一定要通过SwingUtilities.invokeLater(Swing)或Platform.runLater(JavaFX)切换到UI线程,否则会出现界面卡顿或者异常。
  • 数据容错:一定要处理数据格式错误、IO异常的情况,避免程序崩溃。
  • 性能优化:如果数据传输频率很高,不要每次收到数据就立刻重绘,可以设置一个定时器(比如每50ms重绘一次),或者合并多个数据点再绘制,避免UI频繁刷新导致卡顿。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 06:30:40