// tools/binomial_distribution.dart import 'package:flutter/material.dart'; import 'package:rational/rational.dart'; import 'package:fl_chart/fl_chart.dart'; import '../utils/math_utils.dart'; class BinomialDistributionPage extends StatefulWidget { @override _BinomialDistributionPageState createState() => _BinomialDistributionPageState(); } class _BinomialDistributionPageState extends State { final TextEditingController nController = TextEditingController(); final TextEditingController pController = TextEditingController(); String expectation = ''; String variance = ''; List probabilities = []; List barGroups = []; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('二项分布')), body: Padding( padding: EdgeInsets.all(16), child: SingleChildScrollView( // 添加滚动容器 child: Column( children: [ _buildInputField('n (试验次数)', nController), _buildInputField('p (成功概率)', pController), SizedBox(height: 20), ElevatedButton( onPressed: _calculate, child: Text('计算'), ), SizedBox(height: 20), if (expectation.isNotEmpty) ...[ _buildResultCard('期望', expectation), _buildResultCard('方差', variance), SizedBox(height: 10), Text('概率分布:', style: TextStyle(fontWeight: FontWeight.bold)), if (barGroups.isNotEmpty) ...[ SizedBox(height: 10), Container( height: 300, child: BarChart( BarChartData( barTouchData: BarTouchData( enabled: false, // 禁用触摸交互 ), titlesData: FlTitlesData( show: true, bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, getTitlesWidget: (value, meta) { return Padding( padding: const EdgeInsets.only(top: 8.0), child: Text( value.toInt().toString(), style: TextStyle(fontSize: 10), ), ); }, reservedSize: 30, ), ), leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, getTitlesWidget: (value, meta) { if (value % 0.1 == 0) { return Text( value.toStringAsFixed(1), style: TextStyle(fontSize: 10), ); } return Text(''); }, reservedSize: 40, ), ), topTitles: AxisTitles( sideTitles: SideTitles(showTitles: false), ), rightTitles: AxisTitles( sideTitles: SideTitles(showTitles: false), ), ), borderData: FlBorderData(show: true), barGroups: barGroups, gridData: FlGridData(show: true), alignment: BarChartAlignment.spaceAround, maxY: _getMaxY(), ), ), ), SizedBox(height: 10), ], // 移除Expanded并替换为固定高度的ListView Container( height: 200, // 设置固定高度 child: ListView.builder( itemCount: probabilities.length, itemBuilder: (context, index) { return ListTile( title: Text(probabilities[index]), ); }, ), ), ], ], ), ), ), ); } double _getMaxY() { if (barGroups.isEmpty) return 1.0; double maxY = 0.0; for (var group in barGroups) { for (var rod in group.barRods) { if (rod.toY > maxY) maxY = rod.toY; } } return maxY * 1.1; } 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(), ), ), ); } Widget _buildResultCard(String title, String value) { return Card( child: ListTile( title: Text(title), subtitle: Text(value), ), ); } Rational _combination(int n, int k) { if (k < 0 || k > n) return Rational.zero; if (k == 0 || k == n) return Rational.one; BigInt result = BigInt.one; for (int i = 1; i <= k; i++) { result = result * BigInt.from(n - i + 1) ~/ BigInt.from(i); } return Rational(result); } void _calculate() { try { final n = int.parse(nController.text); final p = MathUtils.parseRational(pController.text); final EX = MathUtils.fromInt(n) * p; final DX = MathUtils.fromInt(n) * p * (Rational.one - p); final probs = []; final barGroupsData = []; // 首先计算所有概率并找到最大值 List probValues = []; double maxProb = 0.0; for (int i = 0; i <= n; i++) { final comb = _combination(n, i); final prob = comb * p.pow(i) * (Rational.one - p).pow(n - i); final probDouble = prob.toDouble(); probValues.add(probDouble); if (probDouble > maxProb) { maxProb = probDouble; } probs.add('P(X = $i) = ${MathUtils.rationalToString(prob)}'); } // 找到所有具有最大概率的i值 List maxProbIndices = []; for (int i = 0; i < probValues.length; i++) { if (probValues[i] == maxProb) { maxProbIndices.add(i); } } // 确定最高组的范围 int minMaxIndex = maxProbIndices.reduce((a, b) => a < b ? a : b); int maxMaxIndex = maxProbIndices.reduce((a, b) => a > b ? a : b); // 构建柱状图数据 for (int i = 0; i <= n; i++) { double probDouble = probValues[i]; barGroupsData.add( BarChartGroupData( x: i, barRods: [ BarChartRodData( toY: probDouble, color: _getBarColor(i, minMaxIndex, maxMaxIndex), width: 16, borderRadius: BorderRadius.circular(4), ), ], ), ); } setState(() { expectation = 'EX = ${MathUtils.rationalToString(EX)}'; variance = 'DX = ${MathUtils.rationalToString(DX)}'; probabilities = probs; barGroups = barGroupsData; }); } catch (e) { setState(() { expectation = '错误: $e'; variance = ''; probabilities = []; barGroups = []; }); } } Color _getBarColor(int x, int minMaxIndex, int maxMaxIndex) { if (x >= minMaxIndex && x <= maxMaxIndex) { // 最高组(可能有多组) return Colors.red; } else if (x < minMaxIndex) { // 最高组左边的组 return Colors.blue.shade300; } else { // 最高组右边的组 return Colors.green.shade300; } } @override void dispose() { nController.dispose(); pController.dispose(); super.dispose(); } }