// tools/circle_equation.dart import 'package:flutter/material.dart'; import 'tool_kit.dart'; /// 圆的方程 —— 过三点的圆。 /// /// 设圆为 x² + y² + Dx + Ey + F = 0,把三个点代进去解 D、E、F。 /// 未知量在 Symja 侧用 dd/ee/ff 命名(D、E 是 Symja 的内置名)。 /// 三点共线时 Symja 的 Solve 返回空集,我们的表达式直接回一句中文说明。 class CircleEquationPage extends StatefulWidget { const CircleEquationPage({super.key}); @override State createState() => _CircleEquationPageState(); } class _CircleEquationPageState extends State { final _x1 = ToolField('点1 x', hint: '分数如 1/2,或小数'); // 点的横纵坐标是一对,并排一行 final _y1 = ToolField('点1 y', hint: '分数如 1/2,或小数', rowWithPrevious: true); final _x2 = ToolField('点2 x', hint: '分数如 1/2,或小数'); // 点的横纵坐标是一对,并排一行 final _y2 = ToolField('点2 y', hint: '分数如 1/2,或小数', rowWithPrevious: true); final _x3 = ToolField('点3 x', hint: '分数如 1/2,或小数'); // 点的横纵坐标是一对,并排一行 final _y3 = ToolField('点3 y', hint: '分数如 1/2,或小数', rowWithPrevious: true); @override Widget build(BuildContext context) => ToolScaffold( title: '圆的方程', fields: [_x1, _y1, _x2, _y2, _x3, _y3], compute: (session) async { final bind = 'x1=${_need(_x1)},y1=${_need(_y1)},' 'x2=${_need(_x2)},y2=${_need(_y2)},' 'x3=${_need(_x3)},y3=${_need(_y3)}'; final eqs = '{x1^2+y1^2+dd*x1+ee*y1+ff==0, ' 'x2^2+y2^2+dd*x2+ee*y2+ff==0, ' 'x3^2+y3^2+dd*x3+ee*y3+ff==0}'; const noCircle = '"三点共线(没有外接圆)"'; // 三个系数 final defExpr = 'With[{$bind}, Module[{s}, s=Solve[$eqs,{dd,ee,ff}]; ' 'If[s==={}, $noCircle, {dd,ee,ff} /. First[s]]]]'; final defs = parseSymjaList((await session.compute(defExpr)).text); if (defs.length != 3) { // 共线:Symja 返回的就是那句中文,直接显示。 return [ToolText('结果', '${defs.isEmpty ? '' : defs.first}')]; } // 圆心 (-dd/2, -ee/2) 与半径 √(dd²+ee²-4ff)/2 final centerExpr = 'With[{$bind}, Module[{s}, s=Solve[$eqs,{dd,ee,ff}]; ' 'If[s==={}, $noCircle, ' '{-dd/2, -ee/2, Sqrt[dd^2+ee^2-4*ff]/2} /. First[s]]]]'; // 精确值 + 近似值都要:系数和圆心半径都是分数,小数形式有时更直观 final defsN = parseSymjaList( (await session.compute( approxExpr('Rationalize[$defExpr]', session.approximateDigits), )).text, ); final center = parseSymjaList((await session.compute(centerExpr)).text); final centerN = parseSymjaList( (await session.compute( approxExpr('Rationalize[$centerExpr]', session.approximateDigits), )).text, ); String at(List l, int i) => i < l.length ? '${l[i]}' : ''; final out = [ ToolText('D', withApprox('${defs[0]}', at(defsN, 0))), ToolText('E', withApprox('${defs[1]}', at(defsN, 1))), ToolText('F', withApprox('${defs[2]}', at(defsN, 2))), ]; if (center.length == 3) { // 圆心是坐标,两个分量各自配自己的近似值 out.add( ToolText( '圆心', '(${withApprox('${center[0]}', at(centerN, 0))}, ' '${withApprox('${center[1]}', at(centerN, 1))})', ), ); out.add(ToolText('半径', withApprox('${center[2]}', at(centerN, 2)))); } return out; }, ); /// 取一个标量输入框:空着或填了多个值都直接报中文错。 String _need(ToolField f) { if (f.count == 0) throw Exception('请填写「${f.label}」'); if (f.count > 1) throw Exception('「${f.label}」只能填一个数'); return f.text; } }