diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f7e04b81..ff6456304 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ -## 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 +## 0.69.0 +* **BUGFIX** (by @imaNNeo) 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** (by @imaNNeo) 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 +* **IMPROVEMENT** (by @elizabethzhenliu) Reverse the touch order in ScatterChart, so now the top spots are touched first, #1675 +* **IMPROVEMENT** (by @ksw2000) Remove redundant math import, #1683 +* **IMPROVEMENT** (by @Neer-Pathak) Fix linux example build issue, #1668 +* **IMPROVEMENT** (by @TobiasRump) Update the bar chart documentation, #1662 ## 0.68.0 * **Improvement** (by @imaNNeo) Update LineChartSample6 to implement a way to show a tooltip on a single spot, #1620 diff --git a/README.md b/README.md index fe219a7d2..ee939fa84 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,8 @@ Your financial support acts as fuel for fl_chart's development. [Support here](h - - + + Become a sponsor @@ -47,7 +47,7 @@ Your financial support acts as fuel for fl_chart's development. [Support here](h ### Overview FL Chart is a highly customizable Flutter chart library that supports **[Line Chart](https://app.flchart.dev/#/line)**, **[Bar Chart](https://app.flchart.dev/#/bar)**, **[Pie Chart](https://app.flchart.dev/#/pie)**, **[Scatter Chart](https://app.flchart.dev/#/scatter)**, and **[Radar Chart](https://app.flchart.dev/#/radar)**. -

+

### Chart Types diff --git a/example/linux/CMakeLists.txt b/example/linux/CMakeLists.txt index 49b2e3101..228a43f98 100644 --- a/example/linux/CMakeLists.txt +++ b/example/linux/CMakeLists.txt @@ -4,7 +4,7 @@ project(runner LANGUAGES CXX) # The name of the executable created for the application. Change this to change # the on-disk name of your application. -set(BINARY_NAME "FL Chart App") +set(BINARY_NAME "fLChartApp") # The unique GTK application identifier for this application. See: # https://wiki.gnome.org/HowDoI/ChooseApplicationID set(APPLICATION_ID "dev.flchart.app") diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock index bf60fa4d3..8fc3fc4ef 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -26,9 +26,9 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 - package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce - path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 - url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95 + package_info_plus: fa739dd842b393193c5ca93c26798dff6e3d0e0c + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 + url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399 PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7 diff --git a/example/macos/Runner/AppDelegate.swift b/example/macos/Runner/AppDelegate.swift index d53ef6437..8e02df288 100644 --- a/example/macos/Runner/AppDelegate.swift +++ b/example/macos/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 29165d565..4c1d636ff 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -10,24 +10,24 @@ dependencies: sdk: flutter flutter_web_plugins: sdk: flutter - cupertino_icons: ^1.0.6 - google_fonts: ^6.1.0 - flutter_svg: ^2.0.7 - universal_platform: ^1.0.0+1 + cupertino_icons: ^1.0.8 + google_fonts: ^6.2.1 + flutter_svg: ^2.0.10+1 + universal_platform: ^1.1.0 flutter_staggered_grid_view: ^0.7.0 - url_launcher: ^6.1.14 - go_router: ^11.1.2 + url_launcher: ^6.3.0 + go_router: ^14.2.7 dartx: ^1.2.0 fl_chart: path: ../ - flutter_bloc: ^8.1.3 - package_info_plus: ^4.2.0 + flutter_bloc: ^8.1.6 + package_info_plus: ^8.0.2 equatable: ^2.0.5 dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^2.0.3 + flutter_lints: ^4.0.0 flutter: uses-material-design: true 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 a6064b737..156d98df1 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/lib/src/chart/scatter_chart/scatter_chart_painter.dart b/lib/src/chart/scatter_chart/scatter_chart_painter.dart index 109091ae2..ad7117037 100644 --- a/lib/src/chart/scatter_chart/scatter_chart_painter.dart +++ b/lib/src/chart/scatter_chart/scatter_chart_painter.dart @@ -364,7 +364,8 @@ class ScatterChartPainter extends AxisChartPainter { ) { final data = holder.data; - for (var i = 0; i < data.scatterSpots.length; i++) { + for (var i = data.scatterSpots.length - 1; i >= 0; i--) { + // Reverse the loop to check the topmost spot first final spot = data.scatterSpots[i]; final spotPixelX = getPixelX(spot.x, viewSize, holder); diff --git a/lib/src/utils/utils.dart b/lib/src/utils/utils.dart index dc5cdffc8..8f63bf5a0 100644 --- a/lib/src/utils/utils.dart +++ b/lib/src/utils/utils.dart @@ -1,5 +1,4 @@ import 'dart:math' as math; -import 'dart:math'; import 'package:flutter/material.dart'; @@ -45,9 +44,9 @@ class Utils { Offset calculateRotationOffset(Size size, double degree) { final rotatedHeight = (size.width * math.sin(radians(degree))).abs() + - (size.height * cos(radians(degree))).abs(); - final rotatedWidth = (size.width * cos(radians(degree))).abs() + - (size.height * sin(radians(degree))).abs(); + (size.height * math.cos(radians(degree))).abs(); + final rotatedWidth = (size.width * math.cos(radians(degree))).abs() + + (size.height * math.sin(radians(degree))).abs(); return Offset( (size.width - rotatedWidth) / 2, (size.height - rotatedHeight) / 2, @@ -178,7 +177,7 @@ class Utils { precisionCount -= numbersToRemove; } - final pow10onPrecision = pow(10, precisionCount); + final pow10onPrecision = math.pow(10, precisionCount); input *= pow10onPrecision; return _roundIntervalAboveOne(input) / pow10onPrecision; } @@ -186,18 +185,18 @@ class Utils { double _roundIntervalAboveOne(double input) { assert(input >= 1.0); final decimalCount = input.toInt().toString().length - 1; - input /= pow(10, decimalCount); + input /= math.pow(10, decimalCount); final scaled = input >= 10 ? input.round() / 10 : input; if (scaled >= 7.6) { - return 10 * pow(10, decimalCount).toInt().toDouble(); + return 10 * math.pow(10, decimalCount).toInt().toDouble(); } else if (scaled >= 2.6) { - return 5 * pow(10, decimalCount).toInt().toDouble(); + return 5 * math.pow(10, decimalCount).toInt().toDouble(); } else if (scaled >= 1.6) { - return 2 * pow(10, decimalCount).toInt().toDouble(); + return 2 * math.pow(10, decimalCount).toInt().toDouble(); } else { - return 1 * pow(10, decimalCount).toInt().toDouble(); + return 1 * math.pow(10, decimalCount).toInt().toDouble(); } } diff --git a/pubspec.yaml b/pubspec.yaml index 2c8058f95..fc4cd4a67 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: fl_chart description: A highly customizable Flutter chart library that supports Line Chart, Bar Chart, Pie Chart, Scatter Chart, and Radar Chart. -version: 0.68.0 +version: 0.69.0 homepage: https://flchart.dev/ repository: https://github.com/imaNNeo/fl_chart issue_tracker: https://github.com/imaNNeo/fl_chart/issues diff --git a/repo_files/documentations/bar_chart.md b/repo_files/documentations/bar_chart.md index 8d1f0f5b0..aa72cd1a0 100644 --- a/repo_files/documentations/bar_chart.md +++ b/repo_files/documentations/bar_chart.md @@ -1,4 +1,4 @@ - + ### How to use ```dart @@ -19,7 +19,7 @@ When you change the chart's state, it animates to the new state internally (usin |:---------------|:---------------|:-------| |barGroups| list of [BarChartGroupData ](#BarChartGroupData) to show the bar lines together, you can provide one item per group to show normal bar chart|[]| |groupsSpace| space between groups, it applies only when the [alignment](#BarChartAlignment) is `BarChartAlignment.start`, `BarChartAlignment.center` or `BarChartAlignment.end`|16| -|alignment| a [BarChartAlignment](#BarChartAlignment) that determines the alignment of the barGroups, inspired by [Flutter MainAxisAlignment](https://docs.flutter.io/flutter/rendering/MainAxisAlignment-class.html)| BarChartAlignment.spaceBetween| +|alignment| a [BarChartAlignment](#BarChartAlignment) that determines the alignment of the barGroups, inspired by [Flutter MainAxisAlignment](https://docs.flutter.io/flutter/rendering/MainAxisAlignment-class.html)| BarChartAlignment.spaceEvenly| |titlesData| check the [FlTitlesData](base_chart.md#FlTitlesData)|FlTitlesData()| |axisTitleData| check the [FlAxisTitleData](base_chart.md#FlAxisTitleData)| FlAxisTitleData()| |rangeAnnotations| show range annotations behind the chart, check [RangeAnnotations](base_chart.md#RangeAnnotations) | RangeAnnotations()| 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/repo_files/documentations/line_chart.md b/repo_files/documentations/line_chart.md index 65b4e21a6..b2f1f369c 100644 --- a/repo_files/documentations/line_chart.md +++ b/repo_files/documentations/line_chart.md @@ -1,4 +1,4 @@ - + ### How to use ```dart diff --git a/repo_files/documentations/pie_chart.md b/repo_files/documentations/pie_chart.md index 87a5e1395..b573b0f6c 100644 --- a/repo_files/documentations/pie_chart.md +++ b/repo_files/documentations/pie_chart.md @@ -1,4 +1,4 @@ - + ### How to use ```dart diff --git a/repo_files/sponsors/become_a_sponsor_dark.png b/repo_files/sponsors/become_a_sponsor_dark.png new file mode 100644 index 000000000..899c81625 Binary files /dev/null and b/repo_files/sponsors/become_a_sponsor_dark.png differ diff --git a/repo_files/sponsors/become_a_sponsor_light.png b/repo_files/sponsors/become_a_sponsor_light.png new file mode 100644 index 000000000..0059184e7 Binary files /dev/null and b/repo_files/sponsors/become_a_sponsor_light.png differ 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); + } + } + }, + ); +}