🐛 Fix function graph
This commit is contained in:
@@ -32,10 +32,8 @@ class _GraphCardState extends State<GraphCard> {
|
|||||||
double? _manualY;
|
double? _manualY;
|
||||||
|
|
||||||
/// 生成函数图表的点
|
/// 生成函数图表的点
|
||||||
({List<FlSpot> leftPoints, List<FlSpot> rightPoints}) _generatePlotPoints(
|
({List<FlSpot> leftPoints, List<FlSpot> rightPoints, bool shouldSplit})
|
||||||
String expression,
|
_generatePlotPoints(String expression, double zoomFactor) {
|
||||||
double zoomFactor,
|
|
||||||
) {
|
|
||||||
try {
|
try {
|
||||||
// 使用solver准备函数表达式(展开因式形式)
|
// 使用solver准备函数表达式(展开因式形式)
|
||||||
String functionExpr = _solverService.prepareFunctionForGraphing(
|
String functionExpr = _solverService.prepareFunctionForGraphing(
|
||||||
@@ -44,7 +42,7 @@ class _GraphCardState extends State<GraphCard> {
|
|||||||
|
|
||||||
// 如果表达式不包含 x,返回空列表
|
// 如果表达式不包含 x,返回空列表
|
||||||
if (!functionExpr.contains('x') && !functionExpr.contains('X')) {
|
if (!functionExpr.contains('x') && !functionExpr.contains('X')) {
|
||||||
return (leftPoints: [], rightPoints: []);
|
return (leftPoints: [], rightPoints: [], shouldSplit: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 预处理表达式,确保格式正确
|
// 预处理表达式,确保格式正确
|
||||||
@@ -72,6 +70,18 @@ class _GraphCardState extends State<GraphCard> {
|
|||||||
final parser = Parser(functionExpr);
|
final parser = Parser(functionExpr);
|
||||||
final expr = parser.parse();
|
final expr = parser.parse();
|
||||||
|
|
||||||
|
// 检查函数在 x=0 处是否有垂直渐近线
|
||||||
|
bool hasVerticalAsymptoteAtZero = false;
|
||||||
|
try {
|
||||||
|
final substituted = expr.substitute('x', DoubleExpr(0.0));
|
||||||
|
final evaluated = substituted.evaluate();
|
||||||
|
if (evaluated is DoubleExpr) {
|
||||||
|
hasVerticalAsymptoteAtZero = !evaluated.value.isFinite;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
hasVerticalAsymptoteAtZero = true;
|
||||||
|
}
|
||||||
|
|
||||||
// 根据缩放因子动态调整范围和步长
|
// 根据缩放因子动态调整范围和步长
|
||||||
final range = 10.0 * zoomFactor;
|
final range = 10.0 * zoomFactor;
|
||||||
final step = max(0.01, 0.05 / zoomFactor); // 更小的步长以获得更好的分辨率
|
final step = max(0.01, 0.05 / zoomFactor); // 更小的步长以获得更好的分辨率
|
||||||
@@ -79,6 +89,7 @@ class _GraphCardState extends State<GraphCard> {
|
|||||||
// 生成点
|
// 生成点
|
||||||
List<FlSpot> leftPoints = [];
|
List<FlSpot> leftPoints = [];
|
||||||
List<FlSpot> rightPoints = [];
|
List<FlSpot> rightPoints = [];
|
||||||
|
List<FlSpot> allPoints = [];
|
||||||
for (double i = -range; i <= range; i += step) {
|
for (double i = -range; i <= range; i += step) {
|
||||||
// 跳过 x = 0 以避免在 y=1/x 等函数中的奇点
|
// 跳过 x = 0 以避免在 y=1/x 等函数中的奇点
|
||||||
if (i.abs() < 1e-10) continue;
|
if (i.abs() < 1e-10) continue;
|
||||||
@@ -91,10 +102,14 @@ class _GraphCardState extends State<GraphCard> {
|
|||||||
if (evaluated is DoubleExpr) {
|
if (evaluated is DoubleExpr) {
|
||||||
final y = evaluated.value;
|
final y = evaluated.value;
|
||||||
if (y.isFinite && y.abs() <= 100.0) {
|
if (y.isFinite && y.abs() <= 100.0) {
|
||||||
if (i < 0) {
|
final spot = FlSpot(i, y);
|
||||||
leftPoints.add(FlSpot(i, y));
|
allPoints.add(spot);
|
||||||
} else {
|
if (hasVerticalAsymptoteAtZero) {
|
||||||
rightPoints.add(FlSpot(i, y));
|
if (i < 0) {
|
||||||
|
leftPoints.add(spot);
|
||||||
|
} else {
|
||||||
|
rightPoints.add(spot);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,17 +119,30 @@ class _GraphCardState extends State<GraphCard> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 排序点按 x 值
|
if (hasVerticalAsymptoteAtZero) {
|
||||||
leftPoints.sort((a, b) => a.x.compareTo(b.x));
|
// 排序点按 x 值
|
||||||
rightPoints.sort((a, b) => a.x.compareTo(b.x));
|
leftPoints.sort((a, b) => a.x.compareTo(b.x));
|
||||||
|
rightPoints.sort((a, b) => a.x.compareTo(b.x));
|
||||||
|
|
||||||
debugPrint(
|
debugPrint(
|
||||||
'Generated ${leftPoints.length} left dots and ${rightPoints.length} right dots with zoom factor $zoomFactor',
|
'Generated ${leftPoints.length} left dots and ${rightPoints.length} right dots with zoom factor $zoomFactor (split due to asymptote)',
|
||||||
);
|
);
|
||||||
return (leftPoints: leftPoints, rightPoints: rightPoints);
|
return (
|
||||||
|
leftPoints: leftPoints,
|
||||||
|
rightPoints: rightPoints,
|
||||||
|
shouldSplit: true,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// 不需要分割,直接返回所有点
|
||||||
|
allPoints.sort((a, b) => a.x.compareTo(b.x));
|
||||||
|
debugPrint(
|
||||||
|
'Generated ${allPoints.length} dots with zoom factor $zoomFactor (no split)',
|
||||||
|
);
|
||||||
|
return (leftPoints: allPoints, rightPoints: [], shouldSplit: false);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('Error generating plot points: $e');
|
debugPrint('Error generating plot points: $e');
|
||||||
return (leftPoints: [], rightPoints: []);
|
return (leftPoints: [], rightPoints: [], shouldSplit: false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,7 +308,11 @@ class _GraphCardState extends State<GraphCard> {
|
|||||||
height: 340,
|
height: 340,
|
||||||
child: Builder(
|
child: Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
final (:leftPoints, :rightPoints) = _generatePlotPoints(
|
final (
|
||||||
|
:leftPoints,
|
||||||
|
:rightPoints,
|
||||||
|
:shouldSplit,
|
||||||
|
) = _generatePlotPoints(
|
||||||
widget.expression,
|
widget.expression,
|
||||||
widget.zoomFactor,
|
widget.zoomFactor,
|
||||||
);
|
);
|
||||||
@@ -383,26 +415,44 @@ class _GraphCardState extends State<GraphCard> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
lineBarsData: [
|
lineBarsData: shouldSplit
|
||||||
if (leftPoints.isNotEmpty)
|
? [
|
||||||
LineChartBarData(
|
if (leftPoints.isNotEmpty)
|
||||||
spots: leftPoints,
|
LineChartBarData(
|
||||||
isCurved: true,
|
spots: leftPoints,
|
||||||
color: Theme.of(context).colorScheme.primary,
|
isCurved: true,
|
||||||
barWidth: 3,
|
color: Theme.of(
|
||||||
belowBarData: BarAreaData(show: false),
|
context,
|
||||||
dotData: FlDotData(show: false),
|
).colorScheme.primary,
|
||||||
),
|
barWidth: 3,
|
||||||
if (rightPoints.isNotEmpty)
|
belowBarData: BarAreaData(show: false),
|
||||||
LineChartBarData(
|
dotData: FlDotData(show: false),
|
||||||
spots: rightPoints,
|
),
|
||||||
isCurved: true,
|
if (rightPoints.isNotEmpty)
|
||||||
color: Theme.of(context).colorScheme.primary,
|
LineChartBarData(
|
||||||
barWidth: 3,
|
spots: rightPoints,
|
||||||
belowBarData: BarAreaData(show: false),
|
isCurved: true,
|
||||||
dotData: FlDotData(show: false),
|
color: Theme.of(
|
||||||
),
|
context,
|
||||||
],
|
).colorScheme.primary,
|
||||||
|
barWidth: 3,
|
||||||
|
belowBarData: BarAreaData(show: false),
|
||||||
|
dotData: FlDotData(show: false),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
if (leftPoints.isNotEmpty)
|
||||||
|
LineChartBarData(
|
||||||
|
spots: leftPoints,
|
||||||
|
isCurved: true,
|
||||||
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.primary,
|
||||||
|
barWidth: 3,
|
||||||
|
belowBarData: BarAreaData(show: false),
|
||||||
|
dotData: FlDotData(show: false),
|
||||||
|
),
|
||||||
|
],
|
||||||
minX: bounds.minX,
|
minX: bounds.minX,
|
||||||
maxX: bounds.maxX,
|
maxX: bounds.maxX,
|
||||||
minY: bounds.minY,
|
minY: bounds.minY,
|
||||||
|
Reference in New Issue
Block a user