circle_equation.dart 3.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. // tools/circle_equation.dart
  2. import 'package:flutter/material.dart';
  3. import 'tool_kit.dart';
  4. /// 圆的方程 —— 过三点的圆。
  5. ///
  6. /// 设圆为 x² + y² + Dx + Ey + F = 0,把三个点代进去解 D、E、F。
  7. /// 未知量在 Symja 侧用 dd/ee/ff 命名(D、E 是 Symja 的内置名)。
  8. /// 三点共线时 Symja 的 Solve 返回空集,我们的表达式直接回一句中文说明。
  9. class CircleEquationPage extends StatefulWidget {
  10. const CircleEquationPage({super.key});
  11. @override
  12. State<CircleEquationPage> createState() => _CircleEquationPageState();
  13. }
  14. class _CircleEquationPageState extends State<CircleEquationPage> {
  15. final _x1 = ToolField('点1 x', hint: '分数如 1/2,或小数');
  16. // 点的横纵坐标是一对,并排一行
  17. final _y1 = ToolField('点1 y', hint: '分数如 1/2,或小数', rowWithPrevious: true);
  18. final _x2 = ToolField('点2 x', hint: '分数如 1/2,或小数');
  19. // 点的横纵坐标是一对,并排一行
  20. final _y2 = ToolField('点2 y', hint: '分数如 1/2,或小数', rowWithPrevious: true);
  21. final _x3 = ToolField('点3 x', hint: '分数如 1/2,或小数');
  22. // 点的横纵坐标是一对,并排一行
  23. final _y3 = ToolField('点3 y', hint: '分数如 1/2,或小数', rowWithPrevious: true);
  24. @override
  25. Widget build(BuildContext context) => ToolScaffold(
  26. title: '圆的方程',
  27. fields: [_x1, _y1, _x2, _y2, _x3, _y3],
  28. compute: (session) async {
  29. final bind =
  30. 'x1=${_need(_x1)},y1=${_need(_y1)},'
  31. 'x2=${_need(_x2)},y2=${_need(_y2)},'
  32. 'x3=${_need(_x3)},y3=${_need(_y3)}';
  33. final eqs =
  34. '{x1^2+y1^2+dd*x1+ee*y1+ff==0, '
  35. 'x2^2+y2^2+dd*x2+ee*y2+ff==0, '
  36. 'x3^2+y3^2+dd*x3+ee*y3+ff==0}';
  37. const noCircle = '"三点共线(没有外接圆)"';
  38. // 三个系数
  39. final defExpr =
  40. 'With[{$bind}, Module[{s}, s=Solve[$eqs,{dd,ee,ff}]; '
  41. 'If[s==={}, $noCircle, {dd,ee,ff} /. First[s]]]]';
  42. final defs = parseSymjaList((await session.compute(defExpr)).text);
  43. if (defs.length != 3) {
  44. // 共线:Symja 返回的就是那句中文,直接显示。
  45. return [ToolText('结果', '${defs.isEmpty ? '' : defs.first}')];
  46. }
  47. // 圆心 (-dd/2, -ee/2) 与半径平方 (dd²+ee²-4ff)/4
  48. final centerExpr =
  49. 'With[{$bind}, Module[{s}, s=Solve[$eqs,{dd,ee,ff}]; '
  50. 'If[s==={}, $noCircle, '
  51. '{-dd/2, -ee/2, (dd^2+ee^2-4*ff)/4} /. First[s]]]]';
  52. // 精确值 + 近似值都要:系数和圆心半径都是分数,小数形式有时更直观
  53. final defsN = parseSymjaList(
  54. (await session.compute('N[$defExpr, 10]')).text,
  55. );
  56. final center = parseSymjaList((await session.compute(centerExpr)).text);
  57. final centerN = parseSymjaList(
  58. (await session.compute('N[$centerExpr, 10]')).text,
  59. );
  60. String at(List<Object> l, int i) => i < l.length ? '${l[i]}' : '';
  61. final out = <ToolOutput>[
  62. ToolText('D', withApprox('${defs[0]}', at(defsN, 0))),
  63. ToolText('E', withApprox('${defs[1]}', at(defsN, 1))),
  64. ToolText('F', withApprox('${defs[2]}', at(defsN, 2))),
  65. ];
  66. if (center.length == 3) {
  67. // 圆心是坐标,两个分量各自配自己的近似值
  68. out.add(
  69. ToolText(
  70. '圆心',
  71. '(${withApprox('${center[0]}', at(centerN, 0))}, '
  72. '${withApprox('${center[1]}', at(centerN, 1))})',
  73. ),
  74. );
  75. out.add(ToolText('半径平方', withApprox('${center[2]}', at(centerN, 2))));
  76. }
  77. return out;
  78. },
  79. );
  80. /// 取一个标量输入框:空着或填了多个值都直接报中文错。
  81. String _need(ToolField f) {
  82. if (f.count == 0) throw Exception('请填写「${f.label}」');
  83. if (f.count > 1) throw Exception('「${f.label}」只能填一个数');
  84. return f.text;
  85. }
  86. }