symmetry.dart 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. // tools/symmetry.dart
  2. import 'package:flutter/material.dart';
  3. import 'tool_kit.dart';
  4. /// 对称变换 —— 三种模式共用一个页面,靠下拉框切换。
  5. ///
  6. /// 输入框按模式分别建好、缓存在 [_fields] 里:切回来时原来填的数还在,
  7. /// 而外壳看到的是不同的 list 实例,会自动把上一次的结果清掉。
  8. class SymmetryPage extends StatefulWidget {
  9. const SymmetryPage({super.key});
  10. @override
  11. State<SymmetryPage> createState() => _SymmetryPageState();
  12. }
  13. class _SymmetryPageState extends State<SymmetryPage> {
  14. static const _modes = ['点关于直线对称', '直线关于点对称', '直线关于直线对称'];
  15. int _mode = 0;
  16. // 每种模式一套输入框,建一次、一直留着重用。
  17. final Map<int, List<ToolField>> _fields = {
  18. 0: [
  19. ToolField('点 x', hint: '分数如 1/2,或小数'),
  20. ToolField('点 y', hint: '分数如 1/2,或小数'),
  21. ToolField('直线 A', hint: 'Ax + By + C = 0 的 A'),
  22. ToolField('直线 B', hint: 'Ax + By + C = 0 的 B'),
  23. ToolField('直线 C', hint: 'Ax + By + C = 0 的 C'),
  24. ],
  25. 1: [
  26. ToolField('直线 A', hint: 'Ax + By + C = 0 的 A'),
  27. ToolField('直线 B', hint: 'Ax + By + C = 0 的 B'),
  28. ToolField('直线 C', hint: 'Ax + By + C = 0 的 C'),
  29. ToolField('点 x', hint: '分数如 1/2,或小数'),
  30. ToolField('点 y', hint: '分数如 1/2,或小数'),
  31. ],
  32. 2: [
  33. ToolField('原直线 A', hint: 'Px + Qy + R = 0 的 P'),
  34. ToolField('原直线 B', hint: 'Px + Qy + R = 0 的 Q'),
  35. ToolField('原直线 C', hint: 'Px + Qy + R = 0 的 R'),
  36. ToolField('对称轴 A', hint: 'Ax + By + C = 0 的 A'),
  37. ToolField('对称轴 B', hint: 'Ax + By + C = 0 的 B'),
  38. ToolField('对称轴 C', hint: 'Ax + By + C = 0 的 C'),
  39. ],
  40. };
  41. @override
  42. Widget build(BuildContext context) {
  43. final fields = _fields[_mode]!;
  44. return ToolScaffold(
  45. title: '对称变换',
  46. fields: fields,
  47. intro: Column(
  48. crossAxisAlignment: CrossAxisAlignment.start,
  49. children: [
  50. DropdownButton<int>(
  51. value: _mode,
  52. isExpanded: true,
  53. items: [
  54. for (var i = 0; i < _modes.length; i++)
  55. DropdownMenuItem(value: i, child: Text(_modes[i])),
  56. ],
  57. onChanged: (v) {
  58. if (v != null) setState(() => _mode = v);
  59. },
  60. ),
  61. ],
  62. ),
  63. compute: (session) async {
  64. final f = _fields[_mode]!;
  65. if (_mode == 0) {
  66. // 点 (x, y) 关于直线 Ax + By + C = 0 的对称点
  67. final x = _need(f[0]);
  68. final y = _need(f[1]);
  69. final a = _need(f[2]);
  70. final b = _need(f[3]);
  71. final c = _need(f[4]);
  72. final expr = 'With[{A=$a,B=$b,C=$c,x=$x,y=$y}, '
  73. 'Module[{t=(A*x+B*y+C)/(A^2+B^2)}, {x-2*A*t, y-2*B*t}]]';
  74. final r = parseSymjaList((await session.compute(expr)).text);
  75. return [ToolText('对称点', '(${r[0]}, ${r[1]})')];
  76. }
  77. if (_mode == 1) {
  78. // 直线 Ax + By + C = 0 关于点 (x, y):A、B 不变,C' = -2Ax - 2By - C
  79. final a = _need(f[0]);
  80. final b = _need(f[1]);
  81. final c = _need(f[2]);
  82. final x = _need(f[3]);
  83. final y = _need(f[4]);
  84. final expr = 'With[{A=$a,B=$b,C=$c,x=$x,y=$y}, '
  85. '{A, B, -2*A*x-2*B*y-C}]';
  86. final r = parseSymjaList((await session.compute(expr)).text);
  87. return [_line(r)];
  88. }
  89. // 原直线 Px + Qy + R = 0 关于对称轴 Ax + By + C = 0
  90. final p = _need(f[0]);
  91. final q = _need(f[1]);
  92. final rr = _need(f[2]);
  93. final a = _need(f[3]);
  94. final b = _need(f[4]);
  95. final c = _need(f[5]);
  96. final expr = 'With[{P=$p,Q=$q,R=$rr,A=$a,B=$b,C=$c}, '
  97. 'Module[{k=2*(P*A+Q*B)/(A^2+B^2)}, {P-A*k, Q-B*k, R-C*k}]]';
  98. final r = parseSymjaList((await session.compute(expr)).text);
  99. return [_line(r)];
  100. },
  101. );
  102. }
  103. /// 把 Symja 回来的一串 {A, B, C} 拼成直线方程文本。
  104. ToolOutput _line(List<Object> r) =>
  105. ToolText('对称直线方程', '${r[0]}·x + ${r[1]}·y + ${r[2]} = 0');
  106. /// 取一个标量输入框:空着或填了多个值都直接报中文错。
  107. String _need(ToolField f) {
  108. if (f.count == 0) throw Exception('请填写「${f.label}」');
  109. if (f.count > 1) throw Exception('「${f.label}」只能填一个数');
  110. return f.text;
  111. }
  112. }