| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238 |
- // tools/regression.dart
- import 'package:flutter/material.dart';
- import 'dart:math';
- import 'package:fl_chart/fl_chart.dart';
- class RegressionPage extends StatefulWidget {
- @override
- _RegressionPageState createState() => _RegressionPageState();
- }
- class _RegressionPageState extends State<RegressionPage> {
- final TextEditingController xController = TextEditingController();
- final TextEditingController yController = TextEditingController();
- String result = '';
- List<FlSpot> scatterSpots = [];
- List<FlSpot> 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();
- }
- }
|