Просмотр исходного кода

函数图像绘制去掉 x/y 范围输入框

横纵范围现在由图下面的滑动条控制,输入框就多余了。
顺带删掉那行已经过时的说明(写着"双指缩放、拖动" —— 早就改成滑动条了)。

采样区间改成固定值:x ∈ [-10, 10]、800 个点。点比原来的 240 密,
这样用滑动条放大到一小段时还剩足够多的点。

97 个测试通过。

Co-Authored-By: Claude Code <noreply@anthropic.com>
nonvioce 4 недель назад
Родитель
Сommit
dc0714647d
1 измененных файлов с 14 добавлено и 88 удалено
  1. 14 88
      lib/tools/function_plotter.dart

+ 14 - 88
lib/tools/function_plotter.dart

@@ -19,6 +19,14 @@ class FunctionPlotterPage extends StatefulWidget {
 }
 }
 
 
 class _FunctionPlotterPageState extends State<FunctionPlotterPage> {
 class _FunctionPlotterPageState extends State<FunctionPlotterPage> {
+  /// 固定的采样区间与点数。
+  ///
+  /// 以前这里是用户可以填的 x/y 范围;现在横纵范围由图下面的滑动条控制,
+  /// 输入框就用不上了。点取得密一些,放大到一小段时才有足够的点。
+  static const double _sampleFrom = -10;
+  static const double _sampleTo = 10;
+  static const int _sampleCount = 800;
+
   static const int _maxCurves = 5;
   static const int _maxCurves = 5;
 
 
   /// 每条曲线的固定颜色,下标与输入框一一对应(沿用旧版配色)。
   /// 每条曲线的固定颜色,下标与输入框一一对应(沿用旧版配色)。
@@ -33,22 +41,13 @@ class _FunctionPlotterPageState extends State<FunctionPlotterPage> {
   final List<TextEditingController> _exprs = <TextEditingController>[
   final List<TextEditingController> _exprs = <TextEditingController>[
     TextEditingController(text: 'x^2'),
     TextEditingController(text: 'x^2'),
   ];
   ];
-  final TextEditingController _xMin = TextEditingController(text: '-5');
-  final TextEditingController _xMax = TextEditingController(text: '5');
-  final TextEditingController _yMin = TextEditingController(text: '-10');
-  final TextEditingController _yMax = TextEditingController(text: '10');
 
 
   List<CurveSeries> _series = const <CurveSeries>[];
   List<CurveSeries> _series = const <CurveSeries>[];
 
 
-  /// 上一次绘制实际使用的 y 范围(没绘制过就是 null,交给 CurvePlot 自动定界)。
-  double? _plotYMin;
-  double? _plotYMax;
-
   /// 每个输入框的错误信息,与 [_exprs] 下标对齐;null 表示该项正常。
   /// 每个输入框的错误信息,与 [_exprs] 下标对齐;null 表示该项正常。
   List<String?> _errors = const <String?>[];
   List<String?> _errors = const <String?>[];
 
 
   /// 坐标范围本身的错误(与某条曲线无关),显示在绘图区。
   /// 坐标范围本身的错误(与某条曲线无关),显示在绘图区。
-  String? _rangeError;
 
 
   bool _busy = false;
   bool _busy = false;
 
 
@@ -64,10 +63,6 @@ class _FunctionPlotterPageState extends State<FunctionPlotterPage> {
     for (final c in _exprs) {
     for (final c in _exprs) {
       c.dispose();
       c.dispose();
     }
     }
-    _xMin.dispose();
-    _xMax.dispose();
-    _yMin.dispose();
-    _yMax.dispose();
     super.dispose();
     super.dispose();
   }
   }
 
 
@@ -84,26 +79,9 @@ class _FunctionPlotterPageState extends State<FunctionPlotterPage> {
   Future<void> _plot() async {
   Future<void> _plot() async {
     if (_busy) return;
     if (_busy) return;
 
 
-    final xMin = double.tryParse(_xMin.text.trim());
-    final xMax = double.tryParse(_xMax.text.trim());
-    final yMin = double.tryParse(_yMin.text.trim());
-    final yMax = double.tryParse(_yMax.text.trim());
-
-    if (xMin == null || xMax == null || yMin == null || yMax == null) {
-      setState(() => _rangeError = '请输入有效的坐标范围');
-      return;
-    }
-    if (xMin >= xMax || yMin >= yMax) {
-      setState(() => _rangeError = '最小值必须小于最大值');
-      return;
-    }
-
     final session = context.read<SymjaSession>();
     final session = context.read<SymjaSession>();
     setState(() {
     setState(() {
       _busy = true;
       _busy = true;
-      _rangeError = null;
-      _plotYMin = yMin;
-      _plotYMax = yMax;
       // 每次重绘都重建,长度与输入框对齐,旧错误自然被清掉。
       // 每次重绘都重建,长度与输入框对齐,旧错误自然被清掉。
       _errors = List<String?>.filled(_exprs.length, null);
       _errors = List<String?>.filled(_exprs.length, null);
     });
     });
@@ -117,12 +95,14 @@ class _FunctionPlotterPageState extends State<FunctionPlotterPage> {
         // 表达式**原样交给 Symja 解析** —— 不再自己转换,谁解析谁负责,
         // 表达式**原样交给 Symja 解析** —— 不再自己转换,谁解析谁负责,
         // 免得"工具里算得出来、控制台里又不对"。
         // 免得"工具里算得出来、控制台里又不对"。
         final expr = raw;
         final expr = raw;
+        // 采样区间固定,不再由输入框决定 —— 横纵范围交给图下面的滑动条。
+        // 点取得密一些(800 个),这样放大到一小段时也还有足够的点。
         final s = await session.sample(
         final s = await session.sample(
           expr,
           expr,
           variable: 'x',
           variable: 'x',
-          from: xMin,
-          to: xMax,
-          count: 240,
+          from: _sampleFrom,
+          to: _sampleTo,
+          count: _sampleCount,
         );
         );
         if (!mounted) return;
         if (!mounted) return;
         result.add(CurveSeries(
         result.add(CurveSeries(
@@ -198,40 +178,6 @@ class _FunctionPlotterPageState extends State<FunctionPlotterPage> {
               style: TextStyle(fontSize: 12, color: Colors.grey),
               style: TextStyle(fontSize: 12, color: Colors.grey),
             ),
             ),
             const SizedBox(height: 10),
             const SizedBox(height: 10),
-            LayoutBuilder(
-              builder: (context, constraints) {
-                final fields = <Widget>[
-                  _buildRangeField(_xMin, 'x最小值'),
-                  _buildRangeField(_xMax, 'x最大值'),
-                  _buildRangeField(_yMin, 'y最小值'),
-                  _buildRangeField(_yMax, 'y最大值'),
-                ];
-                // 窄屏竖向排,宽屏横向排。
-                if (constraints.maxWidth < 600) {
-                  return Column(
-                    children: [
-                      for (var i = 0; i < fields.length; i++) ...[
-                        if (i > 0) const SizedBox(height: 10),
-                        fields[i],
-                      ],
-                    ],
-                  );
-                }
-                return Row(
-                  children: [
-                    for (var i = 0; i < fields.length; i++) ...[
-                      if (i > 0) const SizedBox(width: 10),
-                      Expanded(child: fields[i]),
-                    ],
-                  ],
-                );
-              },
-            ),
-            const SizedBox(height: 4),
-            const Text(
-              '注: x 范围决定采样区间,y 范围决定显示窗口;图上还能双指缩放、拖动、双击复位。',
-              style: TextStyle(fontSize: 11, color: Colors.grey),
-            ),
             const SizedBox(height: 10),
             const SizedBox(height: 10),
             Container(
             Container(
               constraints: const BoxConstraints(maxHeight: 240),
               constraints: const BoxConstraints(maxHeight: 240),
@@ -317,21 +263,6 @@ class _FunctionPlotterPageState extends State<FunctionPlotterPage> {
     );
     );
   }
   }
 
 
-  Widget _buildRangeField(TextEditingController controller, String label) {
-    return TextField(
-      controller: controller,
-      decoration: InputDecoration(
-        labelText: label,
-        border: const OutlineInputBorder(),
-        contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
-      ),
-      keyboardType: TextInputType.numberWithOptions(
-        signed: true,
-        decimal: true,
-      ),
-    );
-  }
-
   Widget _buildPlotArea() {
   Widget _buildPlotArea() {
     return Card(
     return Card(
       elevation: 4,
       elevation: 4,
@@ -355,11 +286,6 @@ class _FunctionPlotterPageState extends State<FunctionPlotterPage> {
     if (_busy) {
     if (_busy) {
       return const Center(child: CircularProgressIndicator());
       return const Center(child: CircularProgressIndicator());
     }
     }
-    if (_rangeError != null) {
-      return Center(
-        child: Text(_rangeError!, style: const TextStyle(color: Colors.red)),
-      );
-    }
     if (_series.isEmpty) {
     if (_series.isEmpty) {
       return Center(
       return Center(
         child: Column(
         child: Column(
@@ -376,6 +302,6 @@ class _FunctionPlotterPageState extends State<FunctionPlotterPage> {
         ),
         ),
       );
       );
     }
     }
-    return CurvePlot(series: _series, yMin: _plotYMin, yMax: _plotYMax);
+    return CurvePlot(series: _series);
   }
   }
 }
 }