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

散点图最佳拟合线梯度计算及JavaFX程序实现求助

问题解答

问题1:计算最佳拟合线的梯度(斜率)

最佳拟合线的梯度可通过**普通最小二乘法(OLS)**计算,JFreeChart的Regression.getOLSRegression()方法已封装该逻辑:

  • 调用Regression.getOLSRegression(dataset, seriesIndex),其中dataset为你的XY数据集,seriesIndex是散点系列的索引(此处为0)。
  • 方法返回double[]数组:
    • 索引0:截距(b)
    • 索引1:梯度(斜率m),即所需结果。

问题2:代码错误修复与功能实现

原代码核心错误

  1. 递归调用栈溢出:createChart()方法内注释代码反复调用自身,引发无限递归。
  2. 趋势线重复添加:每次散点更新时重复向数据集添加trend系列,导致渲染异常。
  3. 趋势线数据未清空:新计算趋势线时未清除旧数据,导致线条重叠。

修正后的完整代码

package Grava;

import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.fx.ChartViewer;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.statistics.Regression;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;

public class ScatterAdd extends Application {

    private final XYSeries series = new XYSeries("Voltage");
    private final XYSeries trend = new XYSeries("Trend");
    private final XYSeriesCollection dataset = new XYSeriesCollection();

    ChoiceBox<String> domainLabels = new ChoiceBox<>();
    ChoiceBox<String> rangeLabels = new ChoiceBox<>();

    private JFreeChart createChart() {
        // 初始化数据集,同时添加散点系列和趋势线系列
        dataset.addSeries(series);
        dataset.addSeries(trend);
        
        JFreeChart chart = ChartFactory.createScatterPlot(
                "VI Characteristics", "Current", "Voltage", dataset);
        
        // 设置渲染器:散点显示形状、隐藏线条;趋势线显示线条、隐藏形状
        XYPlot plot = chart.getXYPlot();
        XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer();
        // 散点系列(索引0)
        renderer.setSeriesLinesVisible(0, false);
        renderer.setSeriesShapesVisible(0, true);
        // 趋势线系列(索引1)
        renderer.setSeriesLinesVisible(1, true);
        renderer.setSeriesShapesVisible(1, false);
        plot.setRenderer(renderer);
        
        return chart;
    }

    @Override
    public void start(Stage stage) {
        try {
            Image image = new Image("Grava.logo.png");
            stage.getIcons().add(image);
        } catch (Exception e) {
            // 处理图片加载失败的情况
            System.err.println("Logo加载失败: " + e.getMessage());
        }

        TextField equation = new TextField();
        equation.setEditable(false); // 设置为只读,避免用户修改

        // 散点更新时重新计算趋势线
        series.addChangeListener(event -> {
            if (series.getItemCount() < 2) {
                trend.clear();
                equation.setText("至少需要2个点才能计算拟合线");
                return;
            }
            // 计算OLS回归系数
            double[] coefficients = Regression.getOLSRegression(dataset, 0);
            double intercept = coefficients[0];
            double slope = coefficients[1];

            // 清空旧的趋势线数据
            trend.clear();
            // 获取散点的X范围,绘制完整的趋势线
            double minX = series.getDataItem(0).getXValue();
            double maxX = series.getDataItem(series.getItemCount() - 1).getXValue();
            // 添加两个点即可绘制直线
            trend.add(minX, slope * minX + intercept);
            trend.add(maxX, slope * maxX + intercept);

            // 输出斜率和截距到文本框
            equation.setText(String.format("y = %.4f x + %.4f", slope, intercept));
        });

        // 坐标轴标签选择框设置
        domainLabels.getItems().addAll("Current", "Seconds");
        domainLabels.setValue("Current");
        rangeLabels.getItems().addAll("Voltage", "Metres");
        rangeLabels.setValue("Voltage");

        // Spinner设置
        Spinner<Double> xSpin = new Spinner<>(-10000000.0, 10000000.0, 0, 0.1);
        xSpin.setEditable(true);
        xSpin.setPromptText("Xvalue");

        Spinner<Double> ySpin = new Spinner<>(-10000000.0, 10000000.0, 0, 0.1);
        ySpin.setEditable(true);
        ySpin.setPromptText("Yvalue");

        // 添加按钮事件
        Button button = new Button("Add");
        button.setOnAction(ae -> series.add(xSpin.getValue(), ySpin.getValue()));

        // 布局设置
        HBox xBox = new HBox(domainLabels);
        HBox yBox = new HBox(rangeLabels);

        ToolBar enter = new ToolBar(xBox, xSpin, yBox, ySpin, button, equation);
        BorderPane.setAlignment(enter, Pos.CENTER);

        BorderPane root = new BorderPane();
        JFreeChart chart = createChart();
        root.setCenter(new ChartViewer(chart));
        root.setBottom(enter);

        // 坐标轴标签切换监听器
        domainLabels.getSelectionModel().selectedItemProperty().addListener((ov, old, newLabel) -> {
            chart.getXYPlot().getDomainAxis().setLabel(newLabel);
        });
        rangeLabels.getSelectionModel().selectedItemProperty().addListener((ov, old, newLabel) -> {
            chart.getXYPlot().getRangeAxis().setLabel(newLabel);
        });

        stage.setTitle("ScatterAdd");
        stage.setScene(new Scene(root, 720, 480));
        stage.show();
    }

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

关键改进点说明

  1. 修复递归问题:将坐标轴标签监听器移到start()方法中,在图表创建完成后再绑定,避免createChart()内部递归调用。
  2. 趋势线正确渲染:初始化数据集时同时添加散点和趋势线系列,使用XYLineAndShapeRenderer分别设置两个系列的显示样式。
  3. 避免重复数据:每次计算趋势线前调用trend.clear()清除旧数据,防止线条重叠。
  4. 边界处理:当散点数量不足2个时,提示用户无法计算拟合线,避免Regression方法报错。
  5. 文本框只读:将显示方程的文本框设置为只读,防止用户误修改。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 20:01:11