From 468511b4e4dd9fce258e15950e09765b870712ba Mon Sep 17 00:00:00 2001 From: Ben Heidemann Date: Fri, 29 Aug 2025 15:05:49 +0100 Subject: [PATCH 1/2] fix: prevent curve overshooting --- lib/src/chart/line_chart/line_chart_painter.dart | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/src/chart/line_chart/line_chart_painter.dart b/lib/src/chart/line_chart/line_chart_painter.dart index 6f47aee2d..99a1e3df8 100644 --- a/lib/src/chart/line_chart/line_chart_painter.dart +++ b/lib/src/chart/line_chart/line_chart_painter.dart @@ -603,7 +603,12 @@ class LineChartPainter extends AxisChartPainter { getPixelY(barSpots[i + 1 < size ? i + 1 : i].y, viewSize, holder), ); - final controlPoint1 = previous + temp; + var controlPoint1 = previous + temp; + + /// Prevent controlPoint1 overshooting in the x-axis + if (barData.preventCurveOverShooting && controlPoint1.dx > current.dx) { + controlPoint1 = Offset(current.dx, controlPoint1.dy); + } /// if the isCurved is false, we set 0 for smoothness, /// it means we should not have any smoothness then we face with @@ -625,7 +630,12 @@ class LineChartPainter extends AxisChartPainter { } } - final controlPoint2 = current - temp; + var controlPoint2 = current - temp; + + /// Prevent controlPoint2 overshooting in the x-axis + if (barData.preventCurveOverShooting && controlPoint2.dx < previous.dx) { + controlPoint2 = Offset(previous.dx, controlPoint2.dy); + } path.cubicTo( controlPoint1.dx, From 3f19bac413952fa8e3f96adc92042e33fcaa7544 Mon Sep 17 00:00:00 2001 From: Ben Heidemann Date: Fri, 29 Aug 2025 16:10:51 +0100 Subject: [PATCH 2/2] feat: implement XY variants of preventCurveOverShooting --- lib/src/chart/line_chart/line_chart_data.dart | 30 ++++++++++++++++++- .../chart/line_chart/line_chart_painter.dart | 14 +++++++-- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/lib/src/chart/line_chart/line_chart_data.dart b/lib/src/chart/line_chart/line_chart_data.dart index 22ee479f5..ca0d933df 100644 --- a/lib/src/chart/line_chart/line_chart_data.dart +++ b/lib/src/chart/line_chart/line_chart_data.dart @@ -246,6 +246,8 @@ class LineChartBarData with EquatableMixin { this.isCurved = false, this.curveSmoothness = 0.35, this.preventCurveOverShooting = false, + this.preventCurveOverShootingX = false, + this.preventCurveOverShootingY = false, this.preventCurveOvershootingThreshold = 10.0, this.isStrokeCapRound = false, this.isStrokeJoinRound = false, @@ -262,7 +264,13 @@ class LineChartBarData with EquatableMixin { }) : color = color ?? ((color == null && gradient == null) ? Colors.cyan : null), belowBarData = belowBarData ?? BarAreaData(), - aboveBarData = aboveBarData ?? BarAreaData() { + aboveBarData = aboveBarData ?? BarAreaData(), + assert( + !(preventCurveOverShooting && + (preventCurveOverShootingX || preventCurveOverShootingY)), + 'preventCurveOverShooting cannot be used together with preventCurveOverShootingX or preventCurveOverShootingY. ' + 'Use either preventCurveOverShooting for both axes, or the specific axis variants.', + ) { FlSpot? mostLeft; FlSpot? mostTop; FlSpot? mostRight; @@ -353,6 +361,16 @@ class LineChartBarData with EquatableMixin { /// check this [issue](https://github.com/imaNNeo/fl_chart/issues/25) final bool preventCurveOverShooting; + /// Prevent overshooting when draw curve line on the X-axis only. + /// When true, prevents control points from extending beyond data points horizontally, + /// while allowing natural Y-axis curve smoothing. + final bool preventCurveOverShootingX; + + /// Prevent overshooting when draw curve line on the Y-axis only. + /// When true, prevents control points from extending beyond data points vertically, + /// while allowing natural X-axis curve smoothing. + final bool preventCurveOverShootingY; + /// Applies threshold for [preventCurveOverShooting] algorithm. final double preventCurveOvershootingThreshold; @@ -411,6 +429,8 @@ class LineChartBarData with EquatableMixin { b.preventCurveOvershootingThreshold, t, )!, + preventCurveOverShootingX: b.preventCurveOverShootingX, + preventCurveOverShootingY: b.preventCurveOverShootingY, dotData: FlDotData.lerp(a.dotData, b.dotData, t), errorIndicatorData: FlErrorIndicatorData.lerp( a.errorIndicatorData, @@ -442,6 +462,8 @@ class LineChartBarData with EquatableMixin { double? curveSmoothness, bool? preventCurveOverShooting, double? preventCurveOvershootingThreshold, + bool? preventCurveOverShootingX, + bool? preventCurveOverShootingY, bool? isStrokeCapRound, bool? isStrokeJoinRound, BarAreaData? belowBarData, @@ -468,6 +490,10 @@ class LineChartBarData with EquatableMixin { preventCurveOverShooting ?? this.preventCurveOverShooting, preventCurveOvershootingThreshold: preventCurveOvershootingThreshold ?? this.preventCurveOvershootingThreshold, + preventCurveOverShootingX: + preventCurveOverShootingX ?? this.preventCurveOverShootingX, + preventCurveOverShootingY: + preventCurveOverShootingY ?? this.preventCurveOverShootingY, isStrokeCapRound: isStrokeCapRound ?? this.isStrokeCapRound, isStrokeJoinRound: isStrokeJoinRound ?? this.isStrokeJoinRound, belowBarData: belowBarData ?? this.belowBarData, @@ -494,6 +520,8 @@ class LineChartBarData with EquatableMixin { curveSmoothness, preventCurveOverShooting, preventCurveOvershootingThreshold, + preventCurveOverShootingX, + preventCurveOverShootingY, isStrokeCapRound, isStrokeJoinRound, belowBarData, diff --git a/lib/src/chart/line_chart/line_chart_painter.dart b/lib/src/chart/line_chart/line_chart_painter.dart index 99a1e3df8..a5abda432 100644 --- a/lib/src/chart/line_chart/line_chart_painter.dart +++ b/lib/src/chart/line_chart/line_chart_painter.dart @@ -603,10 +603,16 @@ class LineChartPainter extends AxisChartPainter { getPixelY(barSpots[i + 1 < size ? i + 1 : i].y, viewSize, holder), ); + /// Determine which axes to prevent overshooting on + final preventCurveOverShootingY = + barData.preventCurveOverShooting || barData.preventCurveOverShootingY; + final preventCurveOverShootingX = + barData.preventCurveOverShooting || barData.preventCurveOverShootingX; + var controlPoint1 = previous + temp; /// Prevent controlPoint1 overshooting in the x-axis - if (barData.preventCurveOverShooting && controlPoint1.dx > current.dx) { + if (preventCurveOverShootingX && controlPoint1.dx > current.dx) { controlPoint1 = Offset(current.dx, controlPoint1.dy); } @@ -616,13 +622,15 @@ class LineChartPainter extends AxisChartPainter { final smoothness = barData.isCurved ? barData.curveSmoothness : 0.0; temp = ((next - previous) / 2) * smoothness; - if (barData.preventCurveOverShooting) { + if (preventCurveOverShootingY) { if ((next - current).dy <= barData.preventCurveOvershootingThreshold || (current - previous).dy <= barData.preventCurveOvershootingThreshold) { temp = Offset(temp.dx, 0); } + } + if (preventCurveOverShootingX) { if ((next - current).dx <= barData.preventCurveOvershootingThreshold || (current - previous).dx <= barData.preventCurveOvershootingThreshold) { @@ -633,7 +641,7 @@ class LineChartPainter extends AxisChartPainter { var controlPoint2 = current - temp; /// Prevent controlPoint2 overshooting in the x-axis - if (barData.preventCurveOverShooting && controlPoint2.dx < previous.dx) { + if (preventCurveOverShootingX && controlPoint2.dx < previous.dx) { controlPoint2 = Offset(previous.dx, controlPoint2.dy); }