diff --git a/lib/widgets/graph_card.dart b/lib/widgets/graph_card.dart index b535ba3..68ae73f 100644 --- a/lib/widgets/graph_card.dart +++ b/lib/widgets/graph_card.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:fl_chart/fl_chart.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:latext/latext.dart'; import 'package:simple_math_calc/parser.dart'; import 'package:simple_math_calc/calculator.dart'; import 'package:simple_math_calc/solver.dart'; @@ -25,6 +27,7 @@ class GraphCard extends StatefulWidget { class _GraphCardState extends State { final SolverService _solverService = SolverService(); + FlSpot? _currentTouchedPoint; /// 生成函数图表的点 List _generatePlotPoints(String expression, double zoomFactor) { @@ -145,6 +148,19 @@ class _GraphCardState extends State { ); } + String _formatAxisValue(double value) { + if (value.abs() < 1e-10) return "0"; + if ((value - value.roundToDouble()).abs() < 1e-10) { + return value.round().toString(); + } + double absVal = value.abs(); + if (absVal >= 100) return value.toStringAsFixed(0); + if (absVal >= 10) return value.toStringAsFixed(1); + if (absVal >= 1) return value.toStringAsFixed(2); + if (absVal >= 0.1) return value.toStringAsFixed(3); + return value.toStringAsFixed(4); + } + @override Widget build(BuildContext context) { return ListView( @@ -212,23 +228,53 @@ class _GraphCardState extends State { leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, - reservedSize: 80, + reservedSize: 60, + interval: (bounds.maxY - bounds.minY) / 8, getTitlesWidget: (value, meta) => SideTitleWidget( axisSide: meta.axisSide, - child: Text(value.toStringAsFixed(2)), + child: Text( + _formatAxisValue(value), + style: GoogleFonts.robotoFlex(), + ), ), ), ), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, - reservedSize: 24, - getTitlesWidget: (value, meta) => - SideTitleWidget( - axisSide: meta.axisSide, - child: Text(value.toStringAsFixed(2)), - ), + reservedSize: 80, + interval: (bounds.maxX - bounds.minX) / 10, + getTitlesWidget: (value, meta) => SideTitleWidget( + axisSide: meta.axisSide, + child: Column( + mainAxisSize: MainAxisSize.min, + children: _formatAxisValue(value) + .split('') + .map( + (char) => ['-', '.'].contains(char) + ? Transform.rotate( + angle: pi / 2, + child: Text( + char, + style: + GoogleFonts.robotoFlex( + height: char == '.' + ? 0.7 + : 0.9, + ), + ), + ) + : Text( + char, + style: GoogleFonts.robotoFlex( + height: 0.9, + ), + ), + ) + .toList(), + ), + ), ), ), topTitles: AxisTitles( @@ -246,6 +292,17 @@ class _GraphCardState extends State { ), lineTouchData: LineTouchData( enabled: true, + touchCallback: (event, response) { + if (response != null && + response.lineBarSpots != null && + response.lineBarSpots!.isNotEmpty) { + setState(() { + _currentTouchedPoint = + response.lineBarSpots!.first; + }); + } + // Keep the last touched point visible + }, touchTooltipData: LineTouchTooltipData( getTooltipItems: (touchedSpots) { return touchedSpots.map((spot) { @@ -276,6 +333,28 @@ class _GraphCardState extends State { }, ), ), + if (_currentTouchedPoint != null) + Container( + margin: const EdgeInsets.only(top: 16), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Theme.of( + context, + ).colorScheme.surfaceContainerHighest, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + LaTexT( + laTeXCode: Text( + '\$\$x = ${_currentTouchedPoint!.x.toStringAsFixed(4)},\\quad y = ${_currentTouchedPoint!.y.toStringAsFixed(4)}\$\$', + style: Theme.of(context).textTheme.bodyLarge, + ), + ), + ], + ), + ), ], ), ),