| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408 |
- // tools/parametric_plotter.dart
- import 'package:flutter/material.dart';
- import 'package:math_expressions/math_expressions.dart';
- import 'package:fl_chart/fl_chart.dart';
- class ParametricPlotterPage extends StatefulWidget {
- @override
- _ParametricPlotterPageState createState() => _ParametricPlotterPageState();
- }
- class _ParametricPlotterPageState extends State<ParametricPlotterPage> {
- final TextEditingController xController = TextEditingController(text: 'cos(t)');
- final TextEditingController yController = TextEditingController(text: 'sin(t)');
- final TextEditingController tMinController = TextEditingController(text: '0');
- final TextEditingController tMaxController = TextEditingController(text: '6.3');
- final TextEditingController stepController = TextEditingController(text: '0.1');
- List<FlSpot> points = [];
- String errorMessage = '';
- bool isLoading = false;
- double minX = 0;
- double maxX = 0;
- double minY = 0;
- double maxY = 0;
- @override
- void initState() {
- super.initState();
- WidgetsBinding.instance.addPostFrameCallback((_) {
- _plotGraph();
- });
- }
- void _plotGraph() {
- setState(() {
- isLoading = true;
- errorMessage = '';
- });
- try {
- final tMin = double.parse(tMinController.text);
- final tMax = double.parse(tMaxController.text);
- final step = double.parse(stepController.text);
- if (tMin >= tMax) {
- throw Exception('t最小值必须小于t最大值');
- }
- if (step <= 0) {
- throw Exception('步长必须大于0');
- }
- final xExpr = _parseExpression(xController.text);
- final yExpr = _parseExpression(yController.text);
- List<FlSpot> newPoints = [];
- double currentMinX = double.infinity;
- double currentMaxX = -double.infinity;
- double currentMinY = double.infinity;
- double currentMaxY = -double.infinity;
- for (double t = tMin; t <= tMax; t += step) {
- try {
- final x = _evaluateExpression(xExpr, t);
- final y = _evaluateExpression(yExpr, t);
- if (x.isFinite && y.isFinite) {
- newPoints.add(FlSpot(x, y));
- currentMinX = x < currentMinX ? x : currentMinX;
- currentMaxX = x > currentMaxX ? x : currentMaxX;
- currentMinY = y < currentMinY ? y : currentMinY;
- currentMaxY = y > currentMaxY ? y : currentMaxY;
- }
- } catch (e) {
- // 跳过无效点
- }
- }
- if (newPoints.isEmpty) {
- throw Exception('未生成有效数据点,请检查参数方程');
- }
- setState(() {
- points = newPoints;
- minX = currentMinX;
- maxX = currentMaxX;
- minY = currentMinY;
- maxY = currentMaxY;
- });
- } catch (e) {
- setState(() {
- errorMessage = e.toString();
- points = [];
- });
- } finally {
- setState(() {
- isLoading = false;
- });
- }
- }
- Expression _parseExpression(String expression) {
- // 清理表达式中的空格
- String cleaned = expression.replaceAll(' ', '');
-
- // 将常见函数名转换为math_expressions识别的格式
- cleaned = cleaned.replaceAllMapped(RegExp(r'(\d)([a-zA-Z])'), (match) {
- return '${match.group(1)}*${match.group(2)}';
- });
-
- // 移除这行,因为math_expressions使用^作为幂运算符
- // cleaned = cleaned.replaceAll('^', '**');
-
- try {
- Parser p = Parser();
- return p.parse(cleaned);
- } catch (e) {
- throw Exception('表达式解析错误: $e');
- }
- }
- double _evaluateExpression(Expression expr, double t) {
- ContextModel cm = ContextModel()
- ..bindVariable(Variable('t'), Number(t))
- ..bindVariable(Variable('pi'), Number(3.141592653589793))
- ..bindVariable(Variable('e'), Number(2.718281828459045));
- try {
- return expr.evaluate(EvaluationType.REAL, cm);
- } catch (e) {
- throw Exception('在 t=$t 时计算错误: $e');
- }
- }
- Widget _buildInputPanel() {
- return Card(
- elevation: 4,
- margin: EdgeInsets.all(16),
- child: Padding(
- padding: EdgeInsets.all(16),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- '参数方程设置',
- style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
- ),
- SizedBox(height: 16),
- TextField(
- controller: xController,
- decoration: InputDecoration(
- labelText: 'x = f(t)',
- hintText: '例如: cos(t), t^2, 2*t+1',
- border: OutlineInputBorder(),
- ),
- ),
- SizedBox(height: 12),
- TextField(
- controller: yController,
- decoration: InputDecoration(
- labelText: 'y = g(t)',
- hintText: '例如: sin(t), t^3, 3*t-2',
- border: OutlineInputBorder(),
- ),
- ),
- SizedBox(height: 16),
- Row(
- children: [
- Expanded(
- child: TextField(
- controller: tMinController,
- decoration: InputDecoration(
- labelText: 't最小值',
- border: OutlineInputBorder(),
- ),
- keyboardType: TextInputType.numberWithOptions(decimal: true),
- ),
- ),
- SizedBox(width: 12),
- Expanded(
- child: TextField(
- controller: tMaxController,
- decoration: InputDecoration(
- labelText: 't最大值',
- border: OutlineInputBorder(),
- ),
- keyboardType: TextInputType.numberWithOptions(decimal: true),
- ),
- ),
- SizedBox(width: 12),
- Expanded(
- child: TextField(
- controller: stepController,
- decoration: InputDecoration(
- labelText: '步长',
- border: OutlineInputBorder(),
- ),
- keyboardType: TextInputType.numberWithOptions(decimal: true),
- ),
- ),
- ],
- ),
- SizedBox(height: 16),
- if (errorMessage.isNotEmpty)
- Container(
- width: double.infinity,
- padding: EdgeInsets.all(12),
- decoration: BoxDecoration(
- color: Colors.red[50],
- borderRadius: BorderRadius.circular(8),
- border: Border.all(color: Colors.red),
- ),
- child: Text(
- errorMessage,
- style: TextStyle(color: Colors.red),
- ),
- ),
- SizedBox(height: 16),
- ElevatedButton.icon(
- onPressed: _plotGraph,
- icon: Icon(Icons.show_chart),
- label: Text('绘制图像'),
- style: ElevatedButton.styleFrom(
- minimumSize: Size(double.infinity, 50),
- ),
- ),
- ],
- ),
- ),
- );
- }
- Widget _buildGraph() {
- if (points.isEmpty) {
- return Container(
- height: 300,
- child: Center(
- child: Text(
- '暂无数据',
- style: TextStyle(fontSize: 16, color: Colors.grey),
- ),
- ),
- );
- }
- // 计算坐标轴范围,确保x和y轴有相同的缩放比例
- double rangeX = maxX - minX;
- double rangeY = maxY - minY;
- double maxRange = rangeX > rangeY ? rangeX : rangeY;
-
- double centerX = (minX + maxX) / 2;
- double centerY = (minY + maxY) / 2;
-
- double adjustedMinX = centerX - maxRange / 2;
- double adjustedMaxX = centerX + maxRange / 2;
- double adjustedMinY = centerY - maxRange / 2;
- double adjustedMaxY = centerY + maxRange / 2;
- // 添加一些边距
- double margin = maxRange * 0.1;
- adjustedMinX -= margin;
- adjustedMaxX += margin;
- adjustedMinY -= margin;
- adjustedMaxY += margin;
- return Card(
- elevation: 4,
- margin: EdgeInsets.all(16),
- child: Padding(
- padding: EdgeInsets.all(16),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- '参数方程图像',
- style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
- ),
- SizedBox(height: 16),
- // 修复:使用 AspectRatio 确保图表始终保持正方形
- AspectRatio(
- aspectRatio: 1, // 强制宽高比为1:1
- child: Container(
- decoration: BoxDecoration(
- border: Border.all(color: Colors.grey[300]!),
- borderRadius: BorderRadius.circular(8),
- ),
- child: LineChart(
- LineChartData(
- gridData: FlGridData(show: true),
- titlesData: FlTitlesData(
- show: true,
- bottomTitles: AxisTitles(
- sideTitles: SideTitles(showTitles: true),
- ),
- leftTitles: AxisTitles(
- sideTitles: SideTitles(showTitles: true),
- ),
- rightTitles: AxisTitles(
- sideTitles: SideTitles(showTitles: false),
- ),
- topTitles: AxisTitles(
- sideTitles: SideTitles(showTitles: false),
- ),
- ),
- borderData: FlBorderData(show: true),
- minX: adjustedMinX,
- maxX: adjustedMaxX,
- minY: adjustedMinY,
- maxY: adjustedMaxY,
- lineTouchData: LineTouchData(enabled: false),
- lineBarsData: [
- LineChartBarData(
- spots: points,
- isCurved: true,
- color: Colors.blue,
- barWidth: 3,
- dotData: FlDotData(show: false),
- belowBarData: BarAreaData(show: false),
- ),
- ],
- ),
- ),
- ),
- ),
- SizedBox(height: 16),
- Text(
- '方程: x = ${xController.text}, y = ${yController.text}',
- style: TextStyle(fontSize: 14, color: Colors.grey[600]),
- ),
- Text(
- '参数范围: t ∈ [${tMinController.text}, ${tMaxController.text}], 步长: ${stepController.text}',
- style: TextStyle(fontSize: 14, color: Colors.grey[600]),
- ),
- ],
- ),
- ),
- );
- }
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- appBar: AppBar(
- title: Text('参数方程绘图', style: TextStyle(color: Colors.black)),
- backgroundColor: Colors.white,
- elevation: 1,
- ),
- body: isLoading
- ? Center(child: CircularProgressIndicator())
- : SingleChildScrollView(
- child: Column(
- children: [
- _buildInputPanel(),
- _buildGraph(),
- _buildExamples(),
- ],
- ),
- ),
- );
- }
- Widget _buildExamples() {
- return Card(
- elevation: 4,
- margin: EdgeInsets.all(16),
- child: Padding(
- padding: EdgeInsets.all(16),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- '示例方程',
- style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
- ),
- SizedBox(height: 12),
- Wrap(
- spacing: 8,
- runSpacing: 8,
- children: [
- // 删除了圆的示例
- _buildExampleChip('椭圆', '2*cos(t)', 'sin(t)', '0', '6.3'),
- _buildExampleChip('螺旋线', 't*cos(t)', 't*sin(t)', '0', '12.6'),
- _buildExampleChip('抛物线', 't', 't^2', '-2', '2'),
- _buildExampleChip('心形线', '2*cos(t)-cos(2*t)', '2*sin(t)-sin(2*t)', '0', '6.3'),
- ],
- ),
- ],
- ),
- ),
- );
- }
- Widget _buildExampleChip(String title, String xExpr, String yExpr, String tMin, String tMax) {
- return ActionChip(
- label: Text(title),
- onPressed: () {
- setState(() {
- xController.text = xExpr;
- yController.text = yExpr;
- tMinController.text = tMin;
- tMaxController.text = tMax;
- stepController.text = '0.1';
- });
- _plotGraph();
- },
- backgroundColor: Colors.blue[50],
- );
- }
- }
|