// tools/regression.dart import 'package:flutter/material.dart'; import 'package:rational/rational.dart'; import '../utils/math_utils.dart'; import 'dart:math'; import 'package:fl_chart/fl_chart.dart'; class RegressionPage extends StatefulWidget { @override _RegressionPageState createState() => _RegressionPageState(); } class _RegressionPageState extends State { final TextEditingController xController = TextEditingController(); final TextEditingController yController = TextEditingController(); String result = ''; List scatterSpots = []; List regressionLineSpots = []; bool showRegressionLine = false; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('线性回归')), body: SingleChildScrollView( // 添加可滚动容器 padding: EdgeInsets.all(16), // 将padding移到这里 child: Column( children: [ _buildInputField('x值(用空格分隔)', xController), _buildInputField('y值(用空格分隔)', yController), SizedBox(height: 20), ElevatedButton( onPressed: _calculate, child: Text('计算'), ), SizedBox(height: 20), // 回归直线显示选项 Row( children: [ Checkbox( value: showRegressionLine, onChanged: (value) { setState(() { showRegressionLine = value!; }); }, ), Text('显示回归直线'), ], ), SizedBox(height: 20), if (result.isNotEmpty) Card( child: Padding( padding: EdgeInsets.all(16), child: Text( result, style: TextStyle(fontSize: 14, fontFamily: 'Monospace'), ), ), ), SizedBox(height: 20), // 散点图 if (scatterSpots.isNotEmpty) _buildScatterChart(), ], ), ), ); } Widget _buildInputField(String label, TextEditingController controller) { return Padding( padding: EdgeInsets.symmetric(vertical: 8), child: TextField( controller: controller, keyboardType: TextInputType.numberWithOptions(signed: true, decimal: true), decoration: InputDecoration( labelText: label, border: OutlineInputBorder(), hintText: '用空格分隔多个值', ), ), ); } Widget _buildScatterChart() { return Container( height: 300, padding: EdgeInsets.all(16), child: Card( elevation: 4, child: Padding( padding: EdgeInsets.all(16), child: LineChart( LineChartData( gridData: FlGridData(show: true), titlesData: FlTitlesData( bottomTitles: AxisTitles( sideTitles: SideTitles(showTitles: true), ), leftTitles: AxisTitles( sideTitles: SideTitles(showTitles: true), ), topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)), rightTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)), ), borderData: FlBorderData(show: true), minX: scatterSpots.map((spot) => spot.x).reduce(min) - 1, maxX: scatterSpots.map((spot) => spot.x).reduce(max) + 1, minY: scatterSpots.map((spot) => spot.y).reduce(min) - 1, maxY: scatterSpots.map((spot) => spot.y).reduce(max) + 1, lineBarsData: [ // 散点图 LineChartBarData( spots: scatterSpots, isCurved: false, dotData: FlDotData( show: true, getDotPainter: (spot, percent, barData, index) { return FlDotCirclePainter( radius: 4, color: Colors.blue, strokeWidth: 2, strokeColor: Colors.blue, ); }, ), color: Colors.transparent, // 隐藏连接线 belowBarData: BarAreaData(show: false), ), // 回归直线(可选) if (showRegressionLine && regressionLineSpots.isNotEmpty) LineChartBarData( spots: regressionLineSpots, isCurved: false, dotData: FlDotData(show: false), color: Colors.red, barWidth: 2, belowBarData: BarAreaData(show: false), ), ], ), ), ), ), ); } void _calculate() { try { final xList = xController.text.split(' ').where((s) => s.isNotEmpty).map((s) => double.parse(s)).toList(); final yList = yController.text.split(' ').where((s) => s.isNotEmpty).map((s) => double.parse(s)).toList(); if (xList.isEmpty || yList.isEmpty) { setState(() { result = '错误: 请输入数据'; }); return; } if (xList.length != yList.length) { setState(() { result = '错误: x和y的数据长度不一致'; }); return; } final n = xList.length; double sx = 0, sy = 0, sx2 = 0, sy2 = 0, sxy = 0; for (int i = 0; i < n; i++) { sx += xList[i]; sy += yList[i]; sx2 += xList[i] * xList[i]; sy2 += yList[i] * yList[i]; sxy += xList[i] * yList[i]; } final xBar = sx / n; final yBar = sy / n; final sigmaX = sx2 - n * xBar * xBar; final sigmaY = sy2 - n * yBar * yBar; final sigmaXY = sxy - n * xBar * yBar; final r = sigmaXY / sqrt(sigmaX * sigmaY); final b = sigmaXY / sigmaX; final a = yBar - b * xBar; double se2 = 0; for (int i = 0; i < n; i++) { se2 += pow(yList[i] - b * xList[i] - a, 2); } final R2 = 1 - se2 / sigmaY; // 准备散点数据 scatterSpots = []; for (int i = 0; i < n; i++) { scatterSpots.add(FlSpot(xList[i], yList[i])); } // 准备回归直线数据(两个端点) regressionLineSpots = []; if (xList.isNotEmpty) { double minX = xList.reduce(min); double maxX = xList.reduce(max); regressionLineSpots.add(FlSpot(minX, a + b * minX)); regressionLineSpots.add(FlSpot(maxX, a + b * maxX)); } setState(() { result = ''' x_bar = ${xBar.toStringAsFixed(4)} y_bar = ${yBar.toStringAsFixed(4)} Σ(x-x̄)² = ${sigmaX.toStringAsFixed(4)} Σ(y-ȳ)² = ${sigmaY.toStringAsFixed(4)} Σ(x-x̄)(y-ȳ) = ${sigmaXY.toStringAsFixed(4)} 相关系数 r = ${r.toStringAsFixed(4)} 斜率 b = ${b.toStringAsFixed(4)} 截距 a = ${a.toStringAsFixed(4)} 残差平方和 = ${se2.toStringAsFixed(4)} 决定系数 R² = ${R2.toStringAsFixed(4)} '''; }); } catch (e) { setState(() { result = '错误: $e'; scatterSpots = []; regressionLineSpots = []; }); } } @override void dispose() { xController.dispose(); yController.dispose(); super.dispose(); } }