diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f7e04b81..f0f2c2416 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## newVersion * **BUGFIX** Fix a memory leak issue in the axis-based charts, there was a logic to calculate and cache the minX, maxX, minY and maxY properties to reduce the computation cost. But it caused some memory issues, as we don't have a quick solution for this, we disabled the caching logic for now, later we can move the calculation logic to the render objects to keep and update them only when the data is changed, #1106, #1693 * **BUGFIX** Fix showing grid lines even when there is no line to show in the LineChart, #1691 +* **Improvement** (by @sczesla) Allow users to control minIncluded and maxIncluded using SideTitles, #906 ## 0.68.0 * **Improvement** (by @imaNNeo) Update LineChartSample6 to implement a way to show a tooltip on a single spot, #1620 diff --git a/lib/src/chart/base/axis_chart/axis_chart_data.dart b/lib/src/chart/base/axis_chart/axis_chart_data.dart index 78ca3007d..ed5c41574 100644 --- a/lib/src/chart/base/axis_chart/axis_chart_data.dart +++ b/lib/src/chart/base/axis_chart/axis_chart_data.dart @@ -161,6 +161,8 @@ class SideTitles with EquatableMixin { this.getTitlesWidget = defaultGetTitle, this.reservedSize = 22, this.interval, + this.minIncluded = true, + this.maxIncluded = true, }) : assert(interval != 0, "SideTitles.interval couldn't be zero"); /// Determines showing or hiding this side titles @@ -178,6 +180,14 @@ class SideTitles with EquatableMixin { /// we try to find a suitable value to set as [interval] under the hood. final double? interval; + /// If true (default), a title for the minimum data value is included + /// independent of the sampling interval + final bool minIncluded; + + /// If true (default), a title for the maximum data value is included + /// independent of the sampling interval + final bool maxIncluded; + /// Lerps a [SideTitles] based on [t] value, check [Tween.lerp]. static SideTitles lerp(SideTitles a, SideTitles b, double t) { return SideTitles( @@ -185,6 +195,8 @@ class SideTitles with EquatableMixin { getTitlesWidget: b.getTitlesWidget, reservedSize: lerpDouble(a.reservedSize, b.reservedSize, t)!, interval: lerpDouble(a.interval, b.interval, t), + minIncluded: b.minIncluded, + maxIncluded: b.maxIncluded, ); } @@ -195,12 +207,16 @@ class SideTitles with EquatableMixin { GetTitleWidgetFunction? getTitlesWidget, double? reservedSize, double? interval, + bool? minIncluded, + bool? maxIncluded, }) { return SideTitles( showTitles: showTitles ?? this.showTitles, getTitlesWidget: getTitlesWidget ?? this.getTitlesWidget, reservedSize: reservedSize ?? this.reservedSize, interval: interval ?? this.interval, + minIncluded: minIncluded ?? this.minIncluded, + maxIncluded: maxIncluded ?? this.maxIncluded, ); } @@ -211,6 +227,8 @@ class SideTitles with EquatableMixin { getTitlesWidget, reservedSize, interval, + minIncluded, + maxIncluded, ]; } diff --git a/lib/src/chart/base/axis_chart/axis_chart_helper.dart b/lib/src/chart/base/axis_chart/axis_chart_helper.dart index 4d16a411b..b287b6730 100644 --- a/lib/src/chart/base/axis_chart/axis_chart_helper.dart +++ b/lib/src/chart/base/axis_chart/axis_chart_helper.dart @@ -32,6 +32,8 @@ class AxisChartHelper { var axisSeek = initialValue; final firstPositionOverlapsWithMin = axisSeek == min; if (!minIncluded && firstPositionOverlapsWithMin) { + // If inital value is equal to data minimum, + // move first label one interval further axisSeek += interval; } final diff = max - min; @@ -43,6 +45,7 @@ class AxisChartHelper { final epsilon = interval / 100000; if (minIncluded && !firstPositionOverlapsWithMin) { + // Data minimum shall be included and is not yet covered yield min; } while (axisSeek <= end + epsilon) { diff --git a/lib/src/chart/base/axis_chart/side_titles/side_titles_widget.dart b/lib/src/chart/base/axis_chart/side_titles/side_titles_widget.dart index aa3f58ea5..c1363b39a 100644 --- a/lib/src/chart/base/axis_chart/side_titles/side_titles_widget.dart +++ b/lib/src/chart/base/axis_chart/side_titles/side_titles_widget.dart @@ -133,6 +133,8 @@ class SideTitlesWidget extends StatelessWidget { final axisValues = AxisChartHelper().iterateThroughAxis( min: axisMin, max: axisMax, + minIncluded: sideTitles.minIncluded, + maxIncluded: sideTitles.maxIncluded, baseLine: axisBaseLine, interval: interval, ); diff --git a/repo_files/documentations/base_chart.md b/repo_files/documentations/base_chart.md index 7f40ca0d0..1017f9327 100644 --- a/repo_files/documentations/base_chart.md +++ b/repo_files/documentations/base_chart.md @@ -31,6 +31,9 @@ |getTitlesWidget| A function to retrieve the title widget with given value on the related axis.|defaultGetTitle| |reservedSize| It determines the maximum space that your titles need, |22| |interval| Texts are showing with provided `interval`. If you don't provide anything, we try to find a suitable value to set as `interval` under the hood. | null | +|minIncluded| Determines whether to include title for minimum data value | true | +|maxIncluded| Determines whether to include title for maximum data value | true | + ### SideTitleFitInsideData |PropName |Description |default value| diff --git a/test/chart/base/axis_chart/side_titles/side_titles_test.dart b/test/chart/base/axis_chart/side_titles/side_titles_test.dart new file mode 100644 index 000000000..72a283f17 --- /dev/null +++ b/test/chart/base/axis_chart/side_titles/side_titles_test.dart @@ -0,0 +1,81 @@ +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + final data = [ + const FlSpot(0, 0.5), + const FlSpot(1, 1.3), + const FlSpot(2, 1.9), + ]; + + const viewSize = Size(400, 400); + + testWidgets( + 'Test the effect of minIncluded and maxIncluded in sideTitles', + (WidgetTester tester) async { + // Minimum/maximum included + final mima = [ + [true, true], + [true, false], + [false, true], + [false, false], + ]; + + for (final e in mima) { + final titlesData = FlTitlesData( + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + minIncluded: e[0], + maxIncluded: e[1], + reservedSize: 50, + interval: 1, + ), + ), + rightTitles: const AxisTitles(), + topTitles: const AxisTitles(), + bottomTitles: const AxisTitles(), + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Center( + child: SizedBox( + width: viewSize.width, + height: viewSize.height, + child: LineChart( + LineChartData( + titlesData: titlesData, + lineBarsData: [ + LineChartBarData( + spots: data, + ), + ], + ), + ), + ), + ), + ), + ), + ); + // Number of expected text widgets (titles) on the y-axis + expect( + find.byType(Text), + findsNWidgets((e[0] ? 1 : 0) + (e[1] ? 1 : 0) + 1), + ); + // Always there + expect(find.text('1'), findsOneWidget); + if (e[0]) { + // Minimum included + expect(find.text('0.5'), findsOneWidget); + } + if (e[1]) { + // Maximum included + expect(find.text('1.9'), findsOneWidget); + } + } + }, + ); +}