chi_square.dart 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. // tools/chi_square.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 ChiSquarePage extends StatefulWidget {
  7. @override
  8. _ChiSquarePageState createState() => _ChiSquarePageState();
  9. }
  10. class _ChiSquarePageState extends State<ChiSquarePage> {
  11. final TextEditingController aController = TextEditingController();
  12. final TextEditingController bController = TextEditingController();
  13. final TextEditingController cController = TextEditingController();
  14. final TextEditingController dController = TextEditingController();
  15. String result = '';
  16. List<BarChartGroupData> barGroups = [];
  17. bool showChart = false;
  18. @override
  19. Widget build(BuildContext context) {
  20. return Scaffold(
  21. appBar: AppBar(title: Text('卡方检验')),
  22. body: Padding(
  23. padding: EdgeInsets.all(16),
  24. child: Column(
  25. children: [
  26. Text('请输入2×2列联表数据:'),
  27. SizedBox(height: 20),
  28. Row(
  29. children: [
  30. Expanded(child: _buildInputField('a', aController)),
  31. SizedBox(width: 10),
  32. Expanded(child: _buildInputField('b', bController)),
  33. ],
  34. ),
  35. Row(
  36. children: [
  37. Expanded(child: _buildInputField('c', cController)),
  38. SizedBox(width: 10),
  39. Expanded(child: _buildInputField('d', dController)),
  40. ],
  41. ),
  42. SizedBox(height: 20),
  43. ElevatedButton(
  44. onPressed: _calculate,
  45. child: Text('计算卡方值'),
  46. ),
  47. SizedBox(height: 20),
  48. if (result.isNotEmpty)
  49. Card(
  50. child: Padding(
  51. padding: EdgeInsets.all(16),
  52. child: Text(
  53. result,
  54. style: TextStyle(fontSize: 16),
  55. ),
  56. ),
  57. ),
  58. SizedBox(height: 20),
  59. if (showChart && barGroups.isNotEmpty)
  60. Expanded(
  61. child: Card(
  62. elevation: 4,
  63. child: Padding(
  64. padding: EdgeInsets.all(16),
  65. child: Column(
  66. children: [
  67. Text(
  68. '等高堆积条形图',
  69. style: TextStyle(
  70. fontSize: 18,
  71. fontWeight: FontWeight.bold,
  72. ),
  73. ),
  74. SizedBox(height: 10),
  75. Expanded(
  76. child: BarChart(
  77. BarChartData(
  78. alignment: BarChartAlignment.spaceAround,
  79. maxY: 1.0,
  80. barTouchData: BarTouchData(
  81. enabled: false,
  82. ),
  83. titlesData: FlTitlesData(
  84. show: true,
  85. bottomTitles: AxisTitles(
  86. sideTitles: SideTitles(
  87. showTitles: true,
  88. getTitlesWidget: (value, meta) {
  89. switch (value.toInt()) {
  90. case 0:
  91. return Text('组1');
  92. case 1:
  93. return Text('组2');
  94. default:
  95. return Text('');
  96. }
  97. },
  98. ),
  99. ),
  100. leftTitles: AxisTitles(
  101. sideTitles: SideTitles(
  102. showTitles: true,
  103. getTitlesWidget: (value, meta) {
  104. return Text('${(value * 100).toInt()}%');
  105. },
  106. reservedSize: 40,
  107. ),
  108. ),
  109. topTitles: AxisTitles(
  110. sideTitles: SideTitles(showTitles: false),
  111. ),
  112. rightTitles: AxisTitles(
  113. sideTitles: SideTitles(showTitles: false),
  114. ),
  115. ),
  116. gridData: FlGridData(
  117. show: true,
  118. drawVerticalLine: false,
  119. getDrawingHorizontalLine: (value) {
  120. return FlLine(
  121. color: Colors.grey.withOpacity(0.3),
  122. strokeWidth: 1,
  123. );
  124. },
  125. ),
  126. borderData: FlBorderData(
  127. show: false,
  128. ),
  129. barGroups: barGroups,
  130. ),
  131. ),
  132. ),
  133. SizedBox(height: 10),
  134. Row(
  135. mainAxisAlignment: MainAxisAlignment.center,
  136. children: [
  137. _buildLegend(Colors.blue, '类别A'),
  138. SizedBox(width: 20),
  139. _buildLegend(Colors.red, '类别B'),
  140. ],
  141. ),
  142. ],
  143. ),
  144. ),
  145. ),
  146. ),
  147. ],
  148. ),
  149. ),
  150. );
  151. }
  152. Widget _buildInputField(String label, TextEditingController controller) {
  153. return TextField(
  154. controller: controller,
  155. keyboardType: TextInputType.numberWithOptions(signed: true, decimal: true),
  156. decoration: InputDecoration(
  157. labelText: label,
  158. border: OutlineInputBorder(),
  159. ),
  160. );
  161. }
  162. Widget _buildLegend(Color color, String text) {
  163. return Row(
  164. mainAxisSize: MainAxisSize.min,
  165. children: [
  166. Container(
  167. width: 16,
  168. height: 16,
  169. color: color,
  170. ),
  171. SizedBox(width: 8),
  172. Text(text),
  173. ],
  174. );
  175. }
  176. void _calculate() {
  177. try {
  178. final a = int.parse(aController.text);
  179. final b = int.parse(bController.text);
  180. final c = int.parse(cController.text);
  181. final d = int.parse(dController.text);
  182. final n = a + b + c + d;
  183. final numerator = Rational.fromInt(n) *
  184. Rational.fromInt((a * d - b * c) * (a * d - b * c));
  185. final denominator = Rational.fromInt((a + b) * (c + d) * (a + c) * (b + d));
  186. final K2 = numerator / denominator;
  187. // 计算比例用于图表
  188. final totalGroup1 = a + b;
  189. final totalGroup2 = c + d;
  190. final proportionA1 = totalGroup1 > 0 ? a / totalGroup1 : 0.0;
  191. final proportionB1 = totalGroup1 > 0 ? b / totalGroup1 : 0.0;
  192. final proportionA2 = totalGroup2 > 0 ? c / totalGroup2 : 0.0;
  193. final proportionB2 = totalGroup2 > 0 ? d / totalGroup2 : 0.0;
  194. // 创建条形图数据 - 修复堆积问题
  195. barGroups = [
  196. BarChartGroupData(
  197. x: 0,
  198. groupVertically: true, // 启用垂直堆积
  199. barsSpace: 4, // 条形之间的间距
  200. barRods: [
  201. BarChartRodData(
  202. fromY: 0,
  203. toY: proportionA1,
  204. color: Colors.blue,
  205. width: 30,
  206. ),
  207. BarChartRodData(
  208. fromY: proportionA1, // 从第一个条形的顶部开始
  209. toY: proportionA1 + proportionB1,
  210. color: Colors.red,
  211. width: 30,
  212. ),
  213. ],
  214. ),
  215. BarChartGroupData(
  216. x: 1,
  217. groupVertically: true, // 启用垂直堆积
  218. barsSpace: 4, // 条形之间的间距
  219. barRods: [
  220. BarChartRodData(
  221. fromY: 0,
  222. toY: proportionA2,
  223. color: Colors.blue,
  224. width: 30,
  225. ),
  226. BarChartRodData(
  227. fromY: proportionA2, // 从第一个条形的顶部开始
  228. toY: proportionA2 + proportionB2,
  229. color: Colors.red,
  230. width: 30,
  231. ),
  232. ],
  233. ),
  234. ];
  235. setState(() {
  236. result = 'K² = ${MathUtils.rationalToString(K2)}\n'
  237. '近似值: ${K2.toDouble().toStringAsFixed(4)}';
  238. showChart = true;
  239. });
  240. } catch (e) {
  241. setState(() {
  242. result = '错误: $e';
  243. showChart = false;
  244. });
  245. }
  246. }
  247. @override
  248. void dispose() {
  249. aController.dispose();
  250. bController.dispose();
  251. cController.dispose();
  252. dController.dispose();
  253. super.dispose();
  254. }
  255. }