binomial_distribution.dart 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. // tools/binomial_distribution.dart
  2. import 'package:flutter/material.dart';
  3. import 'package:rational/rational.dart';
  4. import 'package:fl_chart/fl_chart.dart';
  5. import '../utils/math_utils.dart';
  6. class BinomialDistributionPage extends StatefulWidget {
  7. @override
  8. _BinomialDistributionPageState createState() => _BinomialDistributionPageState();
  9. }
  10. class _BinomialDistributionPageState extends State<BinomialDistributionPage> {
  11. final TextEditingController nController = TextEditingController();
  12. final TextEditingController pController = TextEditingController();
  13. String expectation = '';
  14. String variance = '';
  15. List<String> probabilities = [];
  16. List<BarChartGroupData> barGroups = [];
  17. @override
  18. Widget build(BuildContext context) {
  19. return Scaffold(
  20. appBar: AppBar(title: Text('二项分布')),
  21. body: Padding(
  22. padding: EdgeInsets.all(16),
  23. child: SingleChildScrollView( // 添加滚动容器
  24. child: Column(
  25. children: [
  26. _buildInputField('n (试验次数)', nController),
  27. _buildInputField('p (成功概率)', pController),
  28. SizedBox(height: 20),
  29. ElevatedButton(
  30. onPressed: _calculate,
  31. child: Text('计算'),
  32. ),
  33. SizedBox(height: 20),
  34. if (expectation.isNotEmpty) ...[
  35. _buildResultCard('期望', expectation),
  36. _buildResultCard('方差', variance),
  37. SizedBox(height: 10),
  38. Text('概率分布:', style: TextStyle(fontWeight: FontWeight.bold)),
  39. if (barGroups.isNotEmpty) ...[
  40. SizedBox(height: 10),
  41. Container(
  42. height: 300,
  43. child: BarChart(
  44. BarChartData(
  45. barTouchData: BarTouchData(
  46. enabled: false, // 禁用触摸交互
  47. ),
  48. titlesData: FlTitlesData(
  49. show: true,
  50. bottomTitles: AxisTitles(
  51. sideTitles: SideTitles(
  52. showTitles: true,
  53. getTitlesWidget: (value, meta) {
  54. return Padding(
  55. padding: const EdgeInsets.only(top: 8.0),
  56. child: Text(
  57. value.toInt().toString(),
  58. style: TextStyle(fontSize: 10),
  59. ),
  60. );
  61. },
  62. reservedSize: 30,
  63. ),
  64. ),
  65. leftTitles: AxisTitles(
  66. sideTitles: SideTitles(
  67. showTitles: true,
  68. getTitlesWidget: (value, meta) {
  69. if (value % 0.1 == 0) {
  70. return Text(
  71. value.toStringAsFixed(1),
  72. style: TextStyle(fontSize: 10),
  73. );
  74. }
  75. return Text('');
  76. },
  77. reservedSize: 40,
  78. ),
  79. ),
  80. topTitles: AxisTitles(
  81. sideTitles: SideTitles(showTitles: false),
  82. ),
  83. rightTitles: AxisTitles(
  84. sideTitles: SideTitles(showTitles: false),
  85. ),
  86. ),
  87. borderData: FlBorderData(show: true),
  88. barGroups: barGroups,
  89. gridData: FlGridData(show: true),
  90. alignment: BarChartAlignment.spaceAround,
  91. maxY: _getMaxY(),
  92. ),
  93. ),
  94. ),
  95. SizedBox(height: 10),
  96. ],
  97. // 移除Expanded并替换为固定高度的ListView
  98. Container(
  99. height: 200, // 设置固定高度
  100. child: ListView.builder(
  101. itemCount: probabilities.length,
  102. itemBuilder: (context, index) {
  103. return ListTile(
  104. title: Text(probabilities[index]),
  105. );
  106. },
  107. ),
  108. ),
  109. ],
  110. ],
  111. ),
  112. ),
  113. ),
  114. );
  115. }
  116. double _getMaxY() {
  117. if (barGroups.isEmpty) return 1.0;
  118. double maxY = 0.0;
  119. for (var group in barGroups) {
  120. for (var rod in group.barRods) {
  121. if (rod.toY > maxY) maxY = rod.toY;
  122. }
  123. }
  124. return maxY * 1.1;
  125. }
  126. Widget _buildInputField(String label, TextEditingController controller) {
  127. return Padding(
  128. padding: EdgeInsets.symmetric(vertical: 8),
  129. child: TextField(
  130. controller: controller,
  131. keyboardType: TextInputType.numberWithOptions(signed: true, decimal: true),
  132. decoration: InputDecoration(
  133. labelText: label,
  134. border: OutlineInputBorder(),
  135. ),
  136. ),
  137. );
  138. }
  139. Widget _buildResultCard(String title, String value) {
  140. return Card(
  141. child: ListTile(
  142. title: Text(title),
  143. subtitle: Text(value),
  144. ),
  145. );
  146. }
  147. Rational _combination(int n, int k) {
  148. if (k < 0 || k > n) return Rational.zero;
  149. if (k == 0 || k == n) return Rational.one;
  150. BigInt result = BigInt.one;
  151. for (int i = 1; i <= k; i++) {
  152. result = result * BigInt.from(n - i + 1) ~/ BigInt.from(i);
  153. }
  154. return Rational(result);
  155. }
  156. void _calculate() {
  157. try {
  158. final n = int.parse(nController.text);
  159. final p = MathUtils.parseRational(pController.text);
  160. final EX = MathUtils.fromInt(n) * p;
  161. final DX = MathUtils.fromInt(n) * p * (Rational.one - p);
  162. final probs = <String>[];
  163. final barGroupsData = <BarChartGroupData>[];
  164. // 首先计算所有概率并找到最大值
  165. List<double> probValues = [];
  166. double maxProb = 0.0;
  167. for (int i = 0; i <= n; i++) {
  168. final comb = _combination(n, i);
  169. final prob = comb * p.pow(i) * (Rational.one - p).pow(n - i);
  170. final probDouble = prob.toDouble();
  171. probValues.add(probDouble);
  172. if (probDouble > maxProb) {
  173. maxProb = probDouble;
  174. }
  175. probs.add('P(X = $i) = ${MathUtils.rationalToString(prob)}');
  176. }
  177. // 找到所有具有最大概率的i值
  178. List<int> maxProbIndices = [];
  179. for (int i = 0; i < probValues.length; i++) {
  180. if (probValues[i] == maxProb) {
  181. maxProbIndices.add(i);
  182. }
  183. }
  184. // 确定最高组的范围
  185. int minMaxIndex = maxProbIndices.reduce((a, b) => a < b ? a : b);
  186. int maxMaxIndex = maxProbIndices.reduce((a, b) => a > b ? a : b);
  187. // 构建柱状图数据
  188. for (int i = 0; i <= n; i++) {
  189. double probDouble = probValues[i];
  190. barGroupsData.add(
  191. BarChartGroupData(
  192. x: i,
  193. barRods: [
  194. BarChartRodData(
  195. toY: probDouble,
  196. color: _getBarColor(i, minMaxIndex, maxMaxIndex),
  197. width: 16,
  198. borderRadius: BorderRadius.circular(4),
  199. ),
  200. ],
  201. ),
  202. );
  203. }
  204. setState(() {
  205. expectation = 'EX = ${MathUtils.rationalToString(EX)}';
  206. variance = 'DX = ${MathUtils.rationalToString(DX)}';
  207. probabilities = probs;
  208. barGroups = barGroupsData;
  209. });
  210. } catch (e) {
  211. setState(() {
  212. expectation = '错误: $e';
  213. variance = '';
  214. probabilities = [];
  215. barGroups = [];
  216. });
  217. }
  218. }
  219. Color _getBarColor(int x, int minMaxIndex, int maxMaxIndex) {
  220. if (x >= minMaxIndex && x <= maxMaxIndex) {
  221. // 最高组(可能有多组)
  222. return Colors.red;
  223. } else if (x < minMaxIndex) {
  224. // 最高组左边的组
  225. return Colors.blue.shade300;
  226. } else {
  227. // 最高组右边的组
  228. return Colors.green.shade300;
  229. }
  230. }
  231. @override
  232. void dispose() {
  233. nController.dispose();
  234. pController.dispose();
  235. super.dispose();
  236. }
  237. }