// tools/hypergeometric_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 HypergeometricDistributionPage extends StatefulWidget { @override _HypergeometricDistributionPageState createState() => _HypergeometricDistributionPageState(); } class _HypergeometricDistributionPageState extends State { final TextEditingController nController = TextEditingController(); // 抽取数 final TextEditingController NController = TextEditingController(); // 总数 final TextEditingController MController = TextEditingController(); // 满足条件数 String expectation = ''; String variance = ''; List probabilities = []; List barGroups = []; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('超几何分布')), body: SingleChildScrollView( // 添加滚动支持 child: Padding( padding: EdgeInsets.all(16), child: Column( children: [ _buildInputField('总数 N', NController), _buildInputField('满足条件数 M', MController), _buildInputField('抽取数 n', nController), 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.builder,改用简单的 Column Column( children: probabilities.map((prob) => Card( child: ListTile( title: Text(prob), ), ) ).toList(), ), ], ], ), ), ), ); } 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 M = int.parse(MController.text); final n = int.parse(nController.text); final m = n - (N - M) > 0 ? n - (N - M) : 0; final r = n < M ? n : M; final EX = MathUtils.fromInt(n * M) / MathUtils.fromInt(N); final DX = MathUtils.fromInt(n * M * (N - M) * (N - n)) / (MathUtils.fromInt(N) * MathUtils.fromInt(N) * MathUtils.fromInt(N - 1)); final probs = []; final barGroupsData = []; // 首先计算所有概率并找到最大值 List probValues = []; double maxProb = 0.0; for (int k = m; k <= r; k++) { final combMk = _combination(M, k); final combNM_nk = _combination(N - M, n - k); final combNn = _combination(N, n); final prob = combMk * combNM_nk / combNn; final probDouble = prob.toDouble(); probValues.add(probDouble); if (probDouble > maxProb) { maxProb = probDouble; } probs.add('P(X = $k) = ${MathUtils.rationalToString(prob)}'); } // 找到所有具有最大概率的k值 List maxProbIndices = []; for (int i = 0; i < probValues.length; i++) { if (probValues[i] == maxProb) { maxProbIndices.add(m + 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 <= r - m; i++) { int k = m + i; double probDouble = probValues[i]; barGroupsData.add( BarChartGroupData( x: k, barRods: [ BarChartRodData( toY: probDouble, color: _getBarColor(k, 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(); NController.dispose(); MController.dispose(); super.dispose(); } }