From a64bdf349129464bf1ef86c755d51659ecfbfcbc Mon Sep 17 00:00:00 2001 From: Vincenzo Palazzo Date: Sun, 16 Jan 2022 21:03:28 +0100 Subject: [PATCH 01/45] ci: improve codecov configuration In other to improve the test coverage, we should remove the example directory because we don't want that also the example enter inside the scoring. Signed-off-by: Vincenzo Palazzo --- .codecov.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .codecov.yml diff --git a/ .codecov.yml b/ .codecov.yml new file mode 100644 index 000000000..aa328d3a8 --- /dev/null +++ b/ .codecov.yml @@ -0,0 +1,7 @@ +comment: + require_changes: true +# Ignore all the file inside the example and +# end eventually also the autogenerate file +ignore: + - '**/example/' + - '**/*.g.dart' \ No newline at end of file From 3bae7d1d0c1b5f21eb52fa2ec63facd731b83678 Mon Sep 17 00:00:00 2001 From: imaN Khoshabi Date: Sat, 22 Jan 2022 13:58:58 +0330 Subject: [PATCH 02/45] Add SOURCES.md --- SOURCES.md | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 SOURCES.md diff --git a/SOURCES.md b/SOURCES.md new file mode 100644 index 000000000..ee3c29d93 --- /dev/null +++ b/SOURCES.md @@ -0,0 +1,67 @@ +### Sources to learn more about the fl_chart: + +All sources are sorted by date. + +Did you find any new article or source? please contribute to have them all here. + +#### Blog post: + +* [Flutter4Fun UI Challenge 7](https://flutter4fun.com/ui-challenge-7/) + + +* [Stock charts](https://dev.to/kamilpowalowski/stock-charts-with-flchart-library-1gd2) + +#### Video: + +* [Responsive Admin Dashboard or Panel using Flutter - Flutter Web UI - Part 1](https://www.youtube.com/watch?v=MRiZpwdy1CM) + + +* [Admin Panel Dashboard - Flutter Responsive UI Design](https://www.youtube.com/watch?v=n7O3pXfENPU) + + +* [How to build Flutter UI - 3 Steps](https://www.youtube.com/watch?v=I0NBtFS_ibc) + + +* [How to create charts in Flutter](https://www.youtube.com/watch?v=JBJ6o4blgPA) + + +* [Flutter Charts 📊📈](https://www.youtube.com/watch?v=ibkcwCv9Lyw) + + +* [Flutter Library for Customizable](https://www.youtube.com/watch?v=1pjAItIDNz8) + + +* [Pie Chart - FLChart](https://www.youtube.com/watch?v=rZx_isqXrhg&t=77s) + + +* [Flutter Tutorial - Bar Chart](https://www.youtube.com/watch?v=7wUmzYOPQ8w) + + +* [wallet-app-ui-piechart](https://www.youtube.com/watch?v=M4w-dighmMU) + + +* [Flutter UI Tutorial - Fitness App](https://www.youtube.com/watch?v=hTg4DDl8Ixo) + + +* [Gradient Chart](https://www.youtube.com/watch?v=OR2DMRnEXkA) + + +* [Flutter charts tutorial for beginners](https://www.youtube.com/watch?v=nCmihMrWS38) + + +* [The easy way with fl-Chart](https://www.youtube.com/watch?v=R_vpnW5QZEw) + + +* [Get the data form COVID-19 API](https://www.youtube.com/watch?v=QXMWzbdGDkA) + + +* [Flutter COVID-19 Dashboard UI](https://www.youtube.com/watch?v=krU-ASLb8lM) + + +* [Flutter UI](https://www.youtube.com/watch?v=axWBN1aotQk) + + +* [Flutter](https://www.youtube.com/watch?v=rwHFslLo6ho) + + +* [Setup Pie Charts](https://www.youtube.com/watch?v=zRZiJdbp3_E) From d276a81caadacc6754fd1bbd18d354ce66e82dde Mon Sep 17 00:00:00 2001 From: Iman Khoshabi Date: Fri, 14 Jan 2022 00:58:11 +0330 Subject: [PATCH 03/45] Improve calculateGroupAndBarsPosition and drawBars deep test --- .../bar_chart/bar_chart_helper_test.dart | 7 + .../bar_chart/bar_chart_painter_test.dart | 605 +++++++++++++++++- test/helper_methods.dart | 41 +- 3 files changed, 631 insertions(+), 22 deletions(-) diff --git a/test/chart/bar_chart/bar_chart_helper_test.dart b/test/chart/bar_chart/bar_chart_helper_test.dart index 58f10ae4b..c3416c0fc 100644 --- a/test/chart/bar_chart/bar_chart_helper_test.dart +++ b/test/chart/bar_chart/bar_chart_helper_test.dart @@ -51,5 +51,12 @@ void main() { final result2 = BarChartHelper.calculateMaxAxisValues(barGroups); expect(result1, result2); }); + + test('Test BarChartMinMaxAxisValues class', () { + final result1 = BarChartMinMaxAxisValues(0, 10, readFromCache: false) + .copyWith(minY: 1, maxY: 11, readFromCache: true); + final result2 = BarChartMinMaxAxisValues(1, 11, readFromCache: true); + expect(result1, result2); + }); }); } diff --git a/test/chart/bar_chart/bar_chart_painter_test.dart b/test/chart/bar_chart/bar_chart_painter_test.dart index df97d79f8..a4431a44d 100644 --- a/test/chart/bar_chart/bar_chart_painter_test.dart +++ b/test/chart/bar_chart/bar_chart_painter_test.dart @@ -7,6 +7,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; +import '../../helper_methods.dart'; import '../data_pool.dart'; import 'bar_chart_painter_test.mocks.dart'; @@ -172,6 +173,49 @@ void main() { test('test 1', () { const viewSize = Size(200, 100); + final barGroups = [ + BarChartGroupData( + x: 0, + barRods: [ + BarChartRodData(y: 10, width: 10), + BarChartRodData(y: 8, width: 10), + BarChartRodData(y: 8, width: 10), + ], + barsSpace: 5), + BarChartGroupData( + x: 1, + barRods: [ + BarChartRodData(y: 10, width: 10), + BarChartRodData(y: 8, width: 10), + ], + barsSpace: 5), + ]; + + final BarChartData data = BarChartData( + titlesData: FlTitlesData(show: false), + axisTitleData: FlAxisTitleData(show: false), + groupsSpace: 10, + ); + + final BarChartPainter barChartPainter = BarChartPainter(); + final holder = PaintHolder(data, data, 1.0); + + final groupsX = barChartPainter.calculateGroupsX( + viewSize, barGroups, BarChartAlignment.center, holder); + late Exception exception; + try { + barChartPainter.calculateGroupAndBarsPosition( + viewSize, groupsX + [groupsX.last], barGroups); + } catch (e) { + exception = e as Exception; + } + + expect(true, exception.toString().contains('inconsistent')); + }); + + test('test 2', () { + const viewSize = Size(200, 100); + final barGroups = [ BarChartGroupData( x: 0, @@ -270,10 +314,36 @@ void main() { BarChartGroupData( x: 2, barRods: [ - BarChartRodData(y: 10, width: 10), - BarChartRodData(y: 8, width: 10), - BarChartRodData(y: 8, width: 10), - BarChartRodData(y: 8, width: 10), + BarChartRodData( + y: 10, + width: 10, + backDrawRodData: BackgroundBarChartRodData( + y: 8, + show: true, + ), + ), + BarChartRodData( + y: 8, + width: 10, + backDrawRodData: BackgroundBarChartRodData( + show: false, + ), + ), + BarChartRodData( + y: 8, + width: 10, + backDrawRodData: BackgroundBarChartRodData( + y: -3, + show: true, + ), + ), + BarChartRodData( + y: 8, + width: 10, + backDrawRodData: BackgroundBarChartRodData( + y: 0, + ), + ), ], barsSpace: 5), ]; @@ -318,43 +388,160 @@ void main() { }); barChartPainter.drawBars(_mockCanvasWrapper, barGroupsPosition, holder); - expect(results.length, 9); + expect(results.length, 11); - expect((results[0]['rrect'] as RRect), - RRect.fromLTRBR(28.5, 0.0, 38.5, 100.0, const Radius.circular(0.1))); + expect( + HelperMethods.equalsRRects( + (results[0]['rrect'] as RRect), + RRect.fromLTRBR( + 28.5, + 0.0, + 38.5, + 76.9, + const Radius.circular(0.1), + ), + ), + true); expect((results[0]['paint_color'] as Color), const Color(0x00000000)); - expect((results[1]['rrect'] as RRect), - RRect.fromLTRBR(43.5, 20.0, 54.5, 100.0, const Radius.circular(0.2))); + expect( + HelperMethods.equalsRRects( + (results[1]['rrect'] as RRect), + RRect.fromLTRBR( + 43.5, + 15.4, + 54.5, + 76.9, + const Radius.circular(0.2), + ), + ), + true, + ); expect((results[1]['paint_color'] as Color), const Color(0x11111111)); - expect((results[2]['rrect'] as RRect), - RRect.fromLTRBR(59.5, 20.0, 71.5, 100.0, const Radius.circular(0.3))); + expect( + HelperMethods.equalsRRects( + (results[2]['rrect'] as RRect), + RRect.fromLTRBR( + 59.5, + 15.4, + 71.5, + 76.9, + const Radius.circular(0.3), + ), + ), + true, + ); expect((results[2]['paint_color'] as Color), const Color(0x22222222)); - expect((results[3]['rrect'] as RRect), - RRect.fromLTRBR(81.5, 0.0, 91.5, 100.0, const Radius.circular(0.4))); expect( + HelperMethods.equalsRRects( + (results[3]['rrect'] as RRect), + RRect.fromLTRBR( + 81.5, + 0.0, + 91.5, + 76.9, + const Radius.circular(0.4), + ), + ), + true, + ); + expect( + HelperMethods.equalsRRects( (results[4]['rrect'] as RRect), RRect.fromLTRBR( - 96.5, 20.0, 106.5, 100.0, const Radius.circular(5.0))); + 96.5, + 15.4, + 106.5, + 76.9, + const Radius.circular(5.0), + ), + ), + true, + ); expect( + HelperMethods.equalsRRects( (results[5]['rrect'] as RRect), RRect.fromLTRBR( - 116.5, 0.0, 126.5, 100.0, const Radius.circular(5.0))); + 116.5, + 15.4, + 126.5, + 76.9, + const Radius.circular(5.0), + ), + ), + true, + ); + expect( + HelperMethods.equalsRRects( (results[6]['rrect'] as RRect), RRect.fromLTRBR( - 131.5, 20.0, 141.5, 100.0, const Radius.circular(5.0))); + 116.5, + 0.0, + 126.5, + 76.9, + const Radius.circular(5.0), + ), + ), + true, + ); expect( + HelperMethods.equalsRRects( (results[7]['rrect'] as RRect), RRect.fromLTRBR( - 146.5, 20.0, 156.5, 100.0, const Radius.circular(5.0))); + 131.5, + 15.4, + 141.5, + 76.9, + const Radius.circular(5.0), + ), + ), + true, + ); + expect( + HelperMethods.equalsRRects( (results[8]['rrect'] as RRect), RRect.fromLTRBR( - 161.5, 20.0, 171.5, 100.0, const Radius.circular(5.0))); + 146.5, + 76.9, + 156.5, + 100.0, + const Radius.circular(5.0), + ), + ), + true, + ); + + expect( + HelperMethods.equalsRRects( + (results[9]['rrect'] as RRect), + RRect.fromLTRBR( + 146.5, + 15.4, + 156.5, + 76.9, + const Radius.circular(5.0), + ), + ), + true, + ); + expect( + HelperMethods.equalsRRects( + (results[10]['rrect'] as RRect), + RRect.fromLTRBR( + 161.5, + 15.4, + 171.5, + 76.9, + const Radius.circular(5.0), + ), + ), + true, + ); }); }); @@ -881,8 +1068,8 @@ void main() { final BarChartData data = BarChartData( titlesData: FlTitlesData( show: true, - leftTitles: SideTitles(showTitles: true, interval: 1.0), - rightTitles: SideTitles(showTitles: true, interval: 1.0), + leftTitles: SideTitles(showTitles: true, interval: 3.0), + rightTitles: SideTitles(showTitles: true, interval: 3.0), topTitles: SideTitles(showTitles: true), bottomTitles: SideTitles(showTitles: true), ), @@ -920,7 +1107,7 @@ void main() { barChartPainter.drawTitles( _mockBuildContext, _mockCanvasWrapper, barGroupsPosition, holder); - expect(results.length, 28); + expect(results.length, 14); }); }); @@ -1088,6 +1275,306 @@ void main() { final drawOffset = result2.captured[1] as Offset; expect(drawOffset, const Offset(-6.5, -98.0)); }); + + test('test 2', () { + final MockUtils _mockUtils = MockUtils(); + when(_mockUtils.getThemeAwareTextStyle(any, any)).thenReturn(textStyle1); + when(_mockUtils.getEfficientInterval(any, any)).thenReturn(11); + when(_mockUtils.normalizeBorderRadius(any, any)) + .thenReturn(BorderRadius.zero); + when(_mockUtils.normalizeBorderSide(any, any)) + .thenReturn(BorderSide.none); + when(_mockUtils.calculateRotationOffset(any, any)) + .thenReturn(Offset.zero); + when(_mockUtils.getBestInitialIntervalValue(any, any, any)) + .thenReturn(0.0); + when(_mockUtils.formatNumber(captureAny)).thenAnswer((inv) { + final value = inv.positionalArguments[0] as double; + return '${value.toInt()}'; + }); + Utils.changeInstance(_mockUtils); + + const viewSize = Size(200, 100); + + final barGroups = [ + BarChartGroupData( + x: 0, + barRods: [ + BarChartRodData( + y: 10, + width: 10, + colors: [const Color(0x00000000)], + borderRadius: const BorderRadius.all(Radius.circular(0.1)), + ), + BarChartRodData( + y: 8, + width: 11, + colors: [const Color(0x11111111)], + borderRadius: const BorderRadius.all(Radius.circular(0.2)), + ), + BarChartRodData( + y: 8, + width: 12, + colors: [const Color(0x22222222)], + borderRadius: const BorderRadius.all(Radius.circular(0.3)), + ), + ], + barsSpace: 5), + BarChartGroupData( + x: 1, + barRods: [ + BarChartRodData( + y: 10, + width: 10, + borderRadius: const BorderRadius.all(Radius.circular(0.4))), + BarChartRodData(y: 8, width: 10), + ], + barsSpace: 5), + BarChartGroupData( + x: 2, + barRods: [ + BarChartRodData(y: 10, width: 10), + BarChartRodData(y: 8, width: 10), + BarChartRodData(y: 8, width: 10), + BarChartRodData(y: 8, width: 10), + ], + barsSpace: 5), + ]; + + BarTouchTooltipData tooltipData = BarTouchTooltipData( + tooltipRoundedRadius: 8, + tooltipBgColor: const Color(0xf33f33f3), + maxContentWidth: 80, + rotateAngle: 12, + direction: TooltipDirection.bottom, + getTooltipItem: ( + group, + groupIndex, + rod, + rodIndex, + ) { + return BarTooltipItem('helllo1', textStyle1, + textAlign: TextAlign.right, + textDirection: TextDirection.rtl, + children: [ + const TextSpan(text: 'helllo2'), + const TextSpan(text: 'helllo3'), + ]); + }); + + final BarChartData data = BarChartData( + groupsSpace: 10, + barGroups: barGroups, + barTouchData: BarTouchData( + touchTooltipData: tooltipData, + )); + + final BarChartPainter barChartPainter = BarChartPainter(); + final holder = PaintHolder(data, data, 1.0); + + final MockCanvasWrapper _mockCanvasWrapper = MockCanvasWrapper(); + when(_mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); + when(_mockCanvasWrapper.canvas).thenReturn(MockCanvas()); + + final MockBuildContext _mockBuildContext = MockBuildContext(); + + final groupsX = barChartPainter.calculateGroupsX( + viewSize, barGroups, BarChartAlignment.center, holder); + final barGroupsPosition = barChartPainter.calculateGroupAndBarsPosition( + viewSize, groupsX, barGroups); + + final angles = []; + when(_mockCanvasWrapper.drawRotated( + size: anyNamed('size'), + rotationOffset: anyNamed('rotationOffset'), + drawOffset: anyNamed('drawOffset'), + angle: anyNamed('angle'), + drawCallback: anyNamed('drawCallback'), + )).thenAnswer((inv) { + final callback = inv.namedArguments[const Symbol('drawCallback')]; + callback(); + angles.add(inv.namedArguments[const Symbol('angle')] as double); + }); + + barChartPainter.drawTouchTooltip( + _mockBuildContext, + _mockCanvasWrapper, + barGroupsPosition, + tooltipData, + barGroups[0], + 0, + barGroups[0].barRods[0], + 0, + holder, + ); + final result1 = + verify(_mockCanvasWrapper.drawRRect(captureAny, captureAny)); + result1.called(1); + final rrect = result1.captured[0] as RRect; + expect(rrect.blRadius, const Radius.circular(8.0)); + expect(rrect.width, 112); + expect(rrect.height, 90); + expect(rrect.left, -22.5); + expect(rrect.top, 104); + + final bgTooltipPaint = result1.captured[1] as Paint; + expect(bgTooltipPaint.color, const Color(0xf33f33f3)); + expect(bgTooltipPaint.style, PaintingStyle.fill); + + expect(angles.length, 1); + expect(angles[0], 12); + + final result2 = + verify(_mockCanvasWrapper.drawText(captureAny, captureAny)); + result2.called(1); + final textPainter = result2.captured[0] as TextPainter; + expect((textPainter.text as TextSpan).text, 'helllo1'); + expect((textPainter.text as TextSpan).style, textStyle1); + expect(textPainter.textAlign, TextAlign.right); + expect(textPainter.textDirection, TextDirection.rtl); + expect((textPainter.text as TextSpan).children![0], + const TextSpan(text: 'helllo2')); + expect((textPainter.text as TextSpan).children![1], + const TextSpan(text: 'helllo3')); + + final drawOffset = result2.captured[1] as Offset; + expect(drawOffset, const Offset(-6.5, 112.0)); + }); + + test('test 3', () { + final MockUtils _mockUtils = MockUtils(); + when(_mockUtils.getThemeAwareTextStyle(any, any)).thenReturn(textStyle1); + when(_mockUtils.getEfficientInterval(any, any)).thenReturn(11); + when(_mockUtils.normalizeBorderRadius(any, any)) + .thenReturn(BorderRadius.zero); + when(_mockUtils.normalizeBorderSide(any, any)) + .thenReturn(BorderSide.none); + when(_mockUtils.calculateRotationOffset(any, any)) + .thenReturn(Offset.zero); + when(_mockUtils.getBestInitialIntervalValue(any, any, any)) + .thenReturn(0.0); + when(_mockUtils.formatNumber(captureAny)).thenAnswer((inv) { + final value = inv.positionalArguments[0] as double; + return '${value.toInt()}'; + }); + Utils.changeInstance(_mockUtils); + + const viewSize = Size(200, 100); + + final barGroups = [ + BarChartGroupData(x: 0, barRods: [ + BarChartRodData( + y: 10, + width: 10, + colors: [const Color(0x00000000)], + borderRadius: const BorderRadius.all(Radius.circular(0.1)), + ), + BarChartRodData( + y: -10, + width: 10, + colors: [const Color(0x11111111)], + borderRadius: const BorderRadius.all(Radius.circular(0.2)), + ), + ]), + ]; + + BarTouchTooltipData tooltipData = BarTouchTooltipData( + tooltipRoundedRadius: 8, + tooltipBgColor: const Color(0xf33f33f3), + maxContentWidth: 8000, + rotateAngle: 12, + fitInsideHorizontally: true, + fitInsideVertically: true, + direction: TooltipDirection.top, + getTooltipItem: ( + group, + groupIndex, + rod, + rodIndex, + ) { + return BarTooltipItem( + 'helllo1asdfasdfasdfasdfasdfasdfhelllo1asdfasdfasdfasd' + 'fasdfasdfhelllo1asdfasdfasdfasdfasdfasdfhelllo1asdf' + 'asdfasdfasdfasdfasdfhelllo1asdfasdfasdfasdfasdfasdfh' + 'elllo1asdfasdfasdfasdfasdfasdf', + textStyle1, + textAlign: TextAlign.right, + textDirection: TextDirection.rtl, + children: List.generate( + 500, + (index) => const TextSpan(text: '\nhelllo3'), + )); + }); + final BarChartData data = BarChartData( + groupsSpace: 10, + barGroups: barGroups, + barTouchData: BarTouchData( + touchTooltipData: tooltipData, + ), + ); + + final BarChartPainter barChartPainter = BarChartPainter(); + final holder = PaintHolder(data, data, 1.0); + + final MockCanvasWrapper _mockCanvasWrapper = MockCanvasWrapper(); + when(_mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); + when(_mockCanvasWrapper.canvas).thenReturn(MockCanvas()); + + final MockBuildContext _mockBuildContext = MockBuildContext(); + + final groupsX = barChartPainter.calculateGroupsX( + viewSize, barGroups, BarChartAlignment.center, holder); + final barGroupsPosition = barChartPainter.calculateGroupAndBarsPosition( + viewSize, groupsX, barGroups); + + final angles = []; + when(_mockCanvasWrapper.drawRotated( + size: anyNamed('size'), + rotationOffset: anyNamed('rotationOffset'), + drawOffset: anyNamed('drawOffset'), + angle: anyNamed('angle'), + drawCallback: anyNamed('drawCallback'), + )).thenAnswer((inv) { + final callback = inv.namedArguments[const Symbol('drawCallback')]; + callback(); + angles.add(inv.namedArguments[const Symbol('angle')] as double); + }); + + barChartPainter.drawTouchTooltip( + _mockBuildContext, + _mockCanvasWrapper, + barGroupsPosition, + tooltipData, + barGroups[0], + 0, + barGroups[0].barRods[1], + 1, + holder, + ); + final result1 = + verify(_mockCanvasWrapper.drawRRect(captureAny, captureAny)); + result1.called(1); + final rrect = result1.captured[0] as RRect; + expect(rrect.blRadius, const Radius.circular(8.0)); + expect(rrect.width, 2636); + expect(rrect.height, 7034.0); + expect(rrect.left, -2436); + expect(rrect.top, -6934.0); + + final bgTooltipPaint = result1.captured[1] as Paint; + expect(bgTooltipPaint.color, const Color(0xf33f33f3)); + expect(bgTooltipPaint.style, PaintingStyle.fill); + + expect(angles.length, 1); + expect(angles[0], 12); + + final result2 = + verify(_mockCanvasWrapper.drawText(captureAny, captureAny)); + result2.called(1); + + final drawOffset = result2.captured[1] as Offset; + expect(drawOffset, const Offset(-2420, -6926)); + }); }); group('drawStackItemBorderStroke()', () { @@ -1519,5 +2006,81 @@ void main() { expect(result3!.touchedBarGroupIndex, 2); expect(result3.touchedRodDataIndex, 3); }); + + test('test 2', () { + const viewSize = Size(200, 100); + + final barGroups = [ + BarChartGroupData( + x: 0, + barRods: [ + BarChartRodData( + y: 10, + width: 10, + colors: [const Color(0x00000000)], + borderRadius: const BorderRadius.all(Radius.circular(0.1)), + rodStackItems: [ + BarChartRodStackItem(0, 5, const Color(0xFF0F0F0F)) + ]), + ], + barsSpace: 5), + BarChartGroupData( + x: 1, + barRods: [ + BarChartRodData( + y: -10, + width: 10, + borderRadius: const BorderRadius.all(Radius.circular(0.4))), + ], + barsSpace: 5), + ]; + + final BarChartData data = BarChartData( + barGroups: barGroups, + titlesData: FlTitlesData(show: false), + axisTitleData: FlAxisTitleData(show: false), + alignment: BarChartAlignment.center, + groupsSpace: 10, + barTouchData: BarTouchData( + handleBuiltInTouches: true, + touchExtraThreshold: const EdgeInsets.all(1), + ), + ); + + final BarChartPainter painter = BarChartPainter(); + final holder = PaintHolder(data, data, 1.0); + + expect(painter.handleTouch(const Offset(134.0, 48.6), viewSize, holder), + null); + expect(painter.handleTouch(const Offset(111.2, 31.1), viewSize, holder), + null); + + expect(painter.handleTouch(const Offset(103.2, 74.8), viewSize, holder), + null); + expect(painter.handleTouch(const Offset(91.3, 55.3), viewSize, holder), + null); + expect(painter.handleTouch(const Offset(100.4, 21.2), viewSize, holder), + null); + expect(painter.handleTouch(const Offset(80.1, 22.0), viewSize, holder), + null); + + final result1 = + painter.handleTouch(const Offset(110.1, 70.2), viewSize, holder); + expect(result1!.touchedBarGroupIndex, 1); + expect(result1.touchedRodDataIndex, 0); + expect(result1.touchedStackItemIndex, -1); + + final result2 = + painter.handleTouch(const Offset(89.0, 38.5), viewSize, holder); + expect(result2!.touchedBarGroupIndex, 0); + expect(result2.touchedRodDataIndex, 0); + expect(result2.touchedStackItemIndex, 0); + + final result3 = + painter.handleTouch(const Offset(88.8, 16.5), viewSize, holder); + expect(result3!.touchedBarGroupIndex, 0); + expect(result3.touchedRodDataIndex, 0); + expect(result3.touchedStackItemIndex, -1); + }); }); } diff --git a/test/helper_methods.dart b/test/helper_methods.dart index 85068a1a5..34569fdd5 100644 --- a/test/helper_methods.dart +++ b/test/helper_methods.dart @@ -1,6 +1,5 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter_test/flutter_test.dart'; - import 'chart/data_pool.dart'; void main() { @@ -43,4 +42,44 @@ class HelperMethods { } return true; } + + static bool equalsRRects( + RRect rrect1, + RRect rrect2, { + double tolerance = 0.05, + }) { + if ((rrect1.left - rrect2.left).abs() > tolerance) { + return false; + } + + if ((rrect1.top - rrect2.top).abs() > tolerance) { + return false; + } + + if ((rrect1.right - rrect2.right).abs() > tolerance) { + return false; + } + + if ((rrect1.bottom - rrect2.bottom).abs() > tolerance) { + return false; + } + + if (rrect1.blRadius != rrect2.blRadius) { + return false; + } + + if (rrect1.brRadius != rrect2.brRadius) { + return false; + } + + if (rrect1.trRadius != rrect2.trRadius) { + return false; + } + + if (rrect1.tlRadius != rrect2.tlRadius) { + return false; + } + + return true; + } } From 2b91ea5b537acc9de50cf6da13f3683977518e98 Mon Sep 17 00:00:00 2001 From: imaN Khoshabi Date: Thu, 27 Jan 2022 18:38:00 +0330 Subject: [PATCH 04/45] Add showTestCoverage function in Makefile --- Makefile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Makefile b/Makefile index aee356f1d..92691cbdf 100644 --- a/Makefile +++ b/Makefile @@ -28,3 +28,8 @@ sure: # To create generated files (for example mock files in unit_tests) codeGen: flutter pub run build_runner build --delete-conflicting-outputs + +showTestCoverage: + @flutter test --coverage + @genhtml coverage/lcov.info -o coverage/html + @google-chrome coverage/html/index.html From 2ecbae77da22d611b6ab4d12ca408b5406795248 Mon Sep 17 00:00:00 2001 From: imaN Khoshabi Date: Thu, 27 Jan 2022 18:54:00 +0330 Subject: [PATCH 05/45] Ignore base classes from test coverage --- lib/src/chart/base/base_chart/base_chart.dart | 1 + lib/src/chart/base/base_chart/base_chart_data.dart | 1 + lib/src/chart/base/base_chart/base_chart_painter.dart | 1 + lib/src/chart/base/base_chart/fl_touch_event.dart | 1 + lib/src/chart/base/base_chart/render_base_chart.dart | 1 + 5 files changed, 5 insertions(+) diff --git a/lib/src/chart/base/base_chart/base_chart.dart b/lib/src/chart/base/base_chart/base_chart.dart index e749a4a05..ecf994a02 100644 --- a/lib/src/chart/base/base_chart/base_chart.dart +++ b/lib/src/chart/base/base_chart/base_chart.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file import 'package:fl_chart/fl_chart.dart'; import 'base_chart_painter.dart'; diff --git a/lib/src/chart/base/base_chart/base_chart_data.dart b/lib/src/chart/base/base_chart/base_chart_data.dart index 83d7c3459..e6bd87eb9 100644 --- a/lib/src/chart/base/base_chart/base_chart_data.dart +++ b/lib/src/chart/base/base_chart/base_chart_data.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file import 'package:equatable/equatable.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/utils/utils.dart'; diff --git a/lib/src/chart/base/base_chart/base_chart_painter.dart b/lib/src/chart/base/base_chart/base_chart_painter.dart index 7d8bffdf0..3488f6469 100644 --- a/lib/src/chart/base/base_chart/base_chart_painter.dart +++ b/lib/src/chart/base/base_chart/base_chart_painter.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file import 'package:fl_chart/src/utils/canvas_wrapper.dart'; import 'package:flutter/material.dart'; import 'package:fl_chart/src/extensions/paint_extension.dart'; diff --git a/lib/src/chart/base/base_chart/fl_touch_event.dart b/lib/src/chart/base/base_chart/fl_touch_event.dart index 62739bb71..2a39827e1 100644 --- a/lib/src/chart/base/base_chart/fl_touch_event.dart +++ b/lib/src/chart/base/base_chart/fl_touch_event.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/services.dart'; diff --git a/lib/src/chart/base/base_chart/render_base_chart.dart b/lib/src/chart/base/base_chart/render_base_chart.dart index 933998acf..20d4215e6 100644 --- a/lib/src/chart/base/base_chart/render_base_chart.dart +++ b/lib/src/chart/base/base_chart/render_base_chart.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file import 'package:fl_chart/src/chart/base/base_chart/fl_touch_event.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/gestures.dart'; From 1f62d2d32232e58d33554b8b6f853edfece96971 Mon Sep 17 00:00:00 2001 From: imaN Khoshabi Date: Thu, 27 Jan 2022 19:45:46 +0330 Subject: [PATCH 06/45] Write unit test for drawGrid() of axisChartPainter in line_chart_painter_test.dart --- .../base/axis_chart/axis_chart_painter.dart | 5 +- .../bar_chart_painter_test.mocks.dart | 14 +- .../line_chart/line_chart_painter_test.dart | 217 ++++++++++++++++++ .../line_chart_painter_test.mocks.dart | 21 +- .../pie_chart_painter_test.mocks.dart | 14 +- .../radar_chart_painter_test.mocks.dart | 14 +- .../scatter_chart_painter_test.mocks.dart | 14 +- 7 files changed, 240 insertions(+), 59 deletions(-) diff --git a/lib/src/chart/base/axis_chart/axis_chart_painter.dart b/lib/src/chart/base/axis_chart/axis_chart_painter.dart index 9bcb1b1c1..3d774fcf0 100644 --- a/lib/src/chart/base/axis_chart/axis_chart_painter.dart +++ b/lib/src/chart/base/axis_chart/axis_chart_painter.dart @@ -37,7 +37,7 @@ abstract class AxisChartPainter super.paint(context, canvasWrapper, holder); _drawBackground(canvasWrapper, holder); _drawRangeAnnotation(canvasWrapper, holder); - _drawGrid(canvasWrapper, holder); + drawGrid(canvasWrapper, holder); } /// Draws an axis titles in each side (left, top, right, bottom). @@ -222,7 +222,8 @@ abstract class AxisChartPainter return sum; } - void _drawGrid(CanvasWrapper canvasWrapper, PaintHolder holder) { + @visibleForTesting + void drawGrid(CanvasWrapper canvasWrapper, PaintHolder holder) { final data = holder.data; if (!data.gridData.show) { return; diff --git a/test/chart/bar_chart/bar_chart_painter_test.mocks.dart b/test/chart/bar_chart/bar_chart_painter_test.mocks.dart index d8b2a2a5f..93a376c41 100644 --- a/test/chart/bar_chart/bar_chart_painter_test.mocks.dart +++ b/test/chart/bar_chart/bar_chart_painter_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.0.16 from annotations +// Mocks generated by Mockito 5.0.17 from annotations // in fl_chart/test/chart/bar_chart/bar_chart_painter_test.dart. // Do not manually edit this file. @@ -240,8 +240,6 @@ class MockCanvas extends _i1.Mock implements _i2.Canvas { Invocation.method( #drawShadow, [path, color, elevation, transparentOccluder]), returnValueForMissingStub: null); - @override - String toString() => super.toString(); } /// A class which mocks [CanvasWrapper]. @@ -339,8 +337,8 @@ class MockCanvasWrapper extends _i1.Mock implements _i6.CanvasWrapper { @override void drawRotated( {_i2.Size? size, - _i2.Offset? rotationOffset = const _i2.Offset(0.0, 0.0), - _i2.Offset? drawOffset = const _i2.Offset(0.0, 0.0), + _i2.Offset? rotationOffset = _i2.Offset.zero, + _i2.Offset? drawOffset = _i2.Offset.zero, double? angle, void Function()? drawCallback}) => super.noSuchMethod( @@ -358,8 +356,6 @@ class MockCanvasWrapper extends _i1.Mock implements _i6.CanvasWrapper { super.noSuchMethod( Invocation.method(#drawDashedLine, [from, to, painter, dashArray]), returnValueForMissingStub: null); - @override - String toString() => super.toString(); } /// A class which mocks [BuildContext]. @@ -417,8 +413,6 @@ class MockBuildContext extends _i1.Mock implements _i3.BuildContext { _i3.DiagnosticsNode describeOwnershipChain(String? name) => (super.noSuchMethod(Invocation.method(#describeOwnershipChain, [name]), returnValue: _FakeDiagnosticsNode_4()) as _i3.DiagnosticsNode); - @override - String toString() => super.toString(); } /// A class which mocks [Utils]. @@ -493,6 +487,4 @@ class MockUtils extends _i1.Mock implements _i8.Utils { double convertRadiusToSigma(double? radius) => (super.noSuchMethod(Invocation.method(#convertRadiusToSigma, [radius]), returnValue: 0.0) as double); - @override - String toString() => super.toString(); } diff --git a/test/chart/line_chart/line_chart_painter_test.dart b/test/chart/line_chart/line_chart_painter_test.dart index 06f75a68a..e71fad639 100644 --- a/test/chart/line_chart/line_chart_painter_test.dart +++ b/test/chart/line_chart/line_chart_painter_test.dart @@ -2805,4 +2805,221 @@ void main() { expect(result3.spotIndex, 0); }); }); + + group('drawGrid()', () { + test('test 1 - none', () { + const viewSize = Size(20, 100); + + final LineChartData data = LineChartData( + minY: 0, + maxY: 10, + minX: 0, + maxX: 10, + titlesData: FlTitlesData(show: false), + axisTitleData: FlAxisTitleData(show: false), + gridData: FlGridData( + show: false, + drawVerticalLine: true, + drawHorizontalLine: true, + horizontalInterval: 2, + )); + + final LineChartPainter lineChartPainter = LineChartPainter(); + final holder = PaintHolder(data, data, 1.0); + MockCanvasWrapper _mockCanvasWrapper = MockCanvasWrapper(); + when(_mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); + when(_mockCanvasWrapper.canvas).thenReturn(MockCanvas()); + + MockUtils _mockUtils = MockUtils(); + Utils.changeInstance(_mockUtils); + when(_mockUtils.getBestInitialIntervalValue(any, any, any)) + .thenAnswer((realInvocation) => 0); + + lineChartPainter.drawGrid(_mockCanvasWrapper, holder); + verifyNever(_mockCanvasWrapper.drawDashedLine(any, any, any, any)); + }); + + test('test 2 - horizontal', () { + const viewSize = Size(20, 100); + + final LineChartData data = LineChartData( + minY: 0, + maxY: 10, + minX: 0, + maxX: 10, + titlesData: FlTitlesData(show: false), + axisTitleData: FlAxisTitleData(show: false), + gridData: FlGridData( + show: true, + drawVerticalLine: false, + drawHorizontalLine: true, + horizontalInterval: 2, + checkToShowHorizontalLine: (value) => value != 2 && value != 8, + getDrawingHorizontalLine: (value) { + if (value == 4) { + return FlLine( + color: MockData.color1, + strokeWidth: 11, + dashArray: [1, 1], + ); + } else if (value == 6) { + return FlLine( + color: MockData.color2, + strokeWidth: 22, + dashArray: [2, 2], + ); + } else { + throw StateError("We shouldn't draw these lines"); + } + })); + + final LineChartPainter lineChartPainter = LineChartPainter(); + final holder = PaintHolder(data, data, 1.0); + MockCanvasWrapper _mockCanvasWrapper = MockCanvasWrapper(); + when(_mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); + when(_mockCanvasWrapper.canvas).thenReturn(MockCanvas()); + + MockUtils _mockUtils = MockUtils(); + Utils.changeInstance(_mockUtils); + when(_mockUtils.getBestInitialIntervalValue(any, any, any)) + .thenAnswer((realInvocation) => 0); + + List> results = []; + when(_mockCanvasWrapper.drawDashedLine( + captureAny, captureAny, captureAny, captureAny)) + .thenAnswer((inv) { + results.add({ + 'from': inv.positionalArguments[0] as Offset, + 'to': inv.positionalArguments[1] as Offset, + 'paint_color': (inv.positionalArguments[2] as Paint).color, + 'paint_stroke_width': + (inv.positionalArguments[2] as Paint).strokeWidth, + 'dash_array': inv.positionalArguments[3] as List, + }); + }); + + lineChartPainter.drawGrid(_mockCanvasWrapper, holder); + expect(results.length, 2); + + expect(results[0]['from'], const Offset(0, 60)); + expect(results[0]['to'], const Offset(20, 60)); + expect(results[0]['paint_color'], MockData.color1); + expect(results[0]['paint_stroke_width'], 11); + expect(results[0]['dash_array'], [1, 1]); + + expect(results[1]['from'], const Offset(0, 40)); + expect(results[1]['to'], const Offset(20, 40)); + expect(results[1]['paint_color'], MockData.color2); + expect(results[1]['paint_stroke_width'], 22); + expect(results[1]['dash_array'], [2, 2]); + }); + + test('test 3 - vertical', () { + const viewSize = Size(100, 20); + + final LineChartData data = LineChartData( + minY: 0, + maxY: 10, + minX: 0, + maxX: 10, + titlesData: FlTitlesData(show: false), + axisTitleData: FlAxisTitleData(show: false), + gridData: FlGridData( + show: true, + drawVerticalLine: true, + drawHorizontalLine: false, + verticalInterval: 2, + checkToShowVerticalLine: (value) => value != 2 && value != 8, + getDrawingVerticalLine: (value) { + if (value == 4) { + return FlLine( + color: MockData.color1, + strokeWidth: 11, + dashArray: [1, 1], + ); + } else if (value == 6) { + return FlLine( + color: MockData.color2, + strokeWidth: 22, + dashArray: [2, 2], + ); + } else { + throw StateError("We shouldn't draw these lines"); + } + }), + ); + + final LineChartPainter lineChartPainter = LineChartPainter(); + final holder = PaintHolder(data, data, 1.0); + MockCanvasWrapper _mockCanvasWrapper = MockCanvasWrapper(); + when(_mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); + when(_mockCanvasWrapper.canvas).thenReturn(MockCanvas()); + + MockUtils _mockUtils = MockUtils(); + Utils.changeInstance(_mockUtils); + when(_mockUtils.getBestInitialIntervalValue(any, any, any)) + .thenAnswer((realInvocation) => 0); + + List> results = []; + when(_mockCanvasWrapper.drawDashedLine( + captureAny, captureAny, captureAny, captureAny)) + .thenAnswer((inv) { + results.add({ + 'from': inv.positionalArguments[0] as Offset, + 'to': inv.positionalArguments[1] as Offset, + 'paint_color': (inv.positionalArguments[2] as Paint).color, + 'paint_stroke_width': + (inv.positionalArguments[2] as Paint).strokeWidth, + 'dash_array': inv.positionalArguments[3] as List, + }); + }); + + lineChartPainter.drawGrid(_mockCanvasWrapper, holder); + expect(results.length, 2); + + expect(results[0]['from'], const Offset(40, 0)); + expect(results[0]['to'], const Offset(40, 20)); + expect(results[0]['paint_color'], MockData.color1); + expect(results[0]['paint_stroke_width'], 11); + expect(results[0]['dash_array'], [1, 1]); + + expect(results[1]['from'], const Offset(60, 0)); + expect(results[1]['to'], const Offset(60, 20)); + expect(results[1]['paint_color'], MockData.color2); + expect(results[1]['paint_stroke_width'], 22); + expect(results[1]['dash_array'], [2, 2]); + }); + + test('test 4 - both', () { + const viewSize = Size(100, 20); + + final LineChartData data = LineChartData( + minY: 0, + maxY: 10, + minX: 0, + maxX: 10, + gridData: FlGridData( + show: true, + drawVerticalLine: true, + drawHorizontalLine: true, + ), + ); + + final LineChartPainter lineChartPainter = LineChartPainter(); + final holder = PaintHolder(data, data, 1.0); + MockCanvasWrapper _mockCanvasWrapper = MockCanvasWrapper(); + when(_mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); + when(_mockCanvasWrapper.canvas).thenReturn(MockCanvas()); + + MockUtils _mockUtils = MockUtils(); + Utils.changeInstance(_mockUtils); + when(_mockUtils.getEfficientInterval(any, any)) + .thenAnswer((realInvocation) => 3); + when(_mockUtils.getBestInitialIntervalValue(any, any, any)) + .thenAnswer((realInvocation) => 0); + + lineChartPainter.drawGrid(_mockCanvasWrapper, holder); + verify(_mockCanvasWrapper.drawDashedLine(any, any, any, any)).called(6); + }); + }); } diff --git a/test/chart/line_chart/line_chart_painter_test.mocks.dart b/test/chart/line_chart/line_chart_painter_test.mocks.dart index e963e6499..9d1812034 100644 --- a/test/chart/line_chart/line_chart_painter_test.mocks.dart +++ b/test/chart/line_chart/line_chart_painter_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.0.16 from annotations +// Mocks generated by Mockito 5.0.17 from annotations // in fl_chart/test/chart/line_chart/line_chart_painter_test.dart. // Do not manually edit this file. @@ -245,8 +245,6 @@ class MockCanvas extends _i1.Mock implements _i2.Canvas { Invocation.method( #drawShadow, [path, color, elevation, transparentOccluder]), returnValueForMissingStub: null); - @override - String toString() => super.toString(); } /// A class which mocks [CanvasWrapper]. @@ -344,8 +342,8 @@ class MockCanvasWrapper extends _i1.Mock implements _i6.CanvasWrapper { @override void drawRotated( {_i2.Size? size, - _i2.Offset? rotationOffset = const _i2.Offset(0.0, 0.0), - _i2.Offset? drawOffset = const _i2.Offset(0.0, 0.0), + _i2.Offset? rotationOffset = _i2.Offset.zero, + _i2.Offset? drawOffset = _i2.Offset.zero, double? angle, void Function()? drawCallback}) => super.noSuchMethod( @@ -363,8 +361,6 @@ class MockCanvasWrapper extends _i1.Mock implements _i6.CanvasWrapper { super.noSuchMethod( Invocation.method(#drawDashedLine, [from, to, painter, dashArray]), returnValueForMissingStub: null); - @override - String toString() => super.toString(); } /// A class which mocks [BuildContext]. @@ -422,8 +418,6 @@ class MockBuildContext extends _i1.Mock implements _i3.BuildContext { _i3.DiagnosticsNode describeOwnershipChain(String? name) => (super.noSuchMethod(Invocation.method(#describeOwnershipChain, [name]), returnValue: _FakeDiagnosticsNode_4()) as _i3.DiagnosticsNode); - @override - String toString() => super.toString(); } /// A class which mocks [Utils]. @@ -498,8 +492,6 @@ class MockUtils extends _i1.Mock implements _i8.Utils { double convertRadiusToSigma(double? radius) => (super.noSuchMethod(Invocation.method(#convertRadiusToSigma, [radius]), returnValue: 0.0) as double); - @override - String toString() => super.toString(); } /// A class which mocks [LineChartPainter]. @@ -770,6 +762,11 @@ class MockLineChartPainter extends _i1.Mock implements _i9.LineChartPainter { Invocation.method(#drawAxisTitles, [context, canvasWrapper, holder]), returnValueForMissingStub: null); @override + void drawGrid(_i6.CanvasWrapper? canvasWrapper, + _i10.PaintHolder<_i7.LineChartData>? holder) => + super.noSuchMethod(Invocation.method(#drawGrid, [canvasWrapper, holder]), + returnValueForMissingStub: null); + @override double getPixelX(double? spotX, _i2.Size? chartUsableSize, _i10.PaintHolder<_i7.LineChartData>? holder) => (super.noSuchMethod( @@ -787,6 +784,4 @@ class MockLineChartPainter extends _i1.Mock implements _i9.LineChartPainter { (super.noSuchMethod( Invocation.method(#getChartUsableDrawSize, [viewSize, holder]), returnValue: _FakeSize_1()) as _i2.Size); - @override - String toString() => super.toString(); } diff --git a/test/chart/pie_chart/pie_chart_painter_test.mocks.dart b/test/chart/pie_chart/pie_chart_painter_test.mocks.dart index a252d48a7..1cb65e7c0 100644 --- a/test/chart/pie_chart/pie_chart_painter_test.mocks.dart +++ b/test/chart/pie_chart/pie_chart_painter_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.0.16 from annotations +// Mocks generated by Mockito 5.0.17 from annotations // in fl_chart/test/chart/pie_chart/pie_chart_painter_test.dart. // Do not manually edit this file. @@ -240,8 +240,6 @@ class MockCanvas extends _i1.Mock implements _i2.Canvas { Invocation.method( #drawShadow, [path, color, elevation, transparentOccluder]), returnValueForMissingStub: null); - @override - String toString() => super.toString(); } /// A class which mocks [CanvasWrapper]. @@ -339,8 +337,8 @@ class MockCanvasWrapper extends _i1.Mock implements _i6.CanvasWrapper { @override void drawRotated( {_i2.Size? size, - _i2.Offset? rotationOffset = const _i2.Offset(0.0, 0.0), - _i2.Offset? drawOffset = const _i2.Offset(0.0, 0.0), + _i2.Offset? rotationOffset = _i2.Offset.zero, + _i2.Offset? drawOffset = _i2.Offset.zero, double? angle, void Function()? drawCallback}) => super.noSuchMethod( @@ -358,8 +356,6 @@ class MockCanvasWrapper extends _i1.Mock implements _i6.CanvasWrapper { super.noSuchMethod( Invocation.method(#drawDashedLine, [from, to, painter, dashArray]), returnValueForMissingStub: null); - @override - String toString() => super.toString(); } /// A class which mocks [BuildContext]. @@ -417,8 +413,6 @@ class MockBuildContext extends _i1.Mock implements _i3.BuildContext { _i3.DiagnosticsNode describeOwnershipChain(String? name) => (super.noSuchMethod(Invocation.method(#describeOwnershipChain, [name]), returnValue: _FakeDiagnosticsNode_4()) as _i3.DiagnosticsNode); - @override - String toString() => super.toString(); } /// A class which mocks [Utils]. @@ -493,6 +487,4 @@ class MockUtils extends _i1.Mock implements _i8.Utils { double convertRadiusToSigma(double? radius) => (super.noSuchMethod(Invocation.method(#convertRadiusToSigma, [radius]), returnValue: 0.0) as double); - @override - String toString() => super.toString(); } diff --git a/test/chart/radar_chart/radar_chart_painter_test.mocks.dart b/test/chart/radar_chart/radar_chart_painter_test.mocks.dart index 4bdb2a915..9a9ff50aa 100644 --- a/test/chart/radar_chart/radar_chart_painter_test.mocks.dart +++ b/test/chart/radar_chart/radar_chart_painter_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.0.16 from annotations +// Mocks generated by Mockito 5.0.17 from annotations // in fl_chart/test/chart/radar_chart/radar_chart_painter_test.dart. // Do not manually edit this file. @@ -240,8 +240,6 @@ class MockCanvas extends _i1.Mock implements _i2.Canvas { Invocation.method( #drawShadow, [path, color, elevation, transparentOccluder]), returnValueForMissingStub: null); - @override - String toString() => super.toString(); } /// A class which mocks [CanvasWrapper]. @@ -339,8 +337,8 @@ class MockCanvasWrapper extends _i1.Mock implements _i6.CanvasWrapper { @override void drawRotated( {_i2.Size? size, - _i2.Offset? rotationOffset = const _i2.Offset(0.0, 0.0), - _i2.Offset? drawOffset = const _i2.Offset(0.0, 0.0), + _i2.Offset? rotationOffset = _i2.Offset.zero, + _i2.Offset? drawOffset = _i2.Offset.zero, double? angle, void Function()? drawCallback}) => super.noSuchMethod( @@ -358,8 +356,6 @@ class MockCanvasWrapper extends _i1.Mock implements _i6.CanvasWrapper { super.noSuchMethod( Invocation.method(#drawDashedLine, [from, to, painter, dashArray]), returnValueForMissingStub: null); - @override - String toString() => super.toString(); } /// A class which mocks [BuildContext]. @@ -417,8 +413,6 @@ class MockBuildContext extends _i1.Mock implements _i3.BuildContext { _i3.DiagnosticsNode describeOwnershipChain(String? name) => (super.noSuchMethod(Invocation.method(#describeOwnershipChain, [name]), returnValue: _FakeDiagnosticsNode_4()) as _i3.DiagnosticsNode); - @override - String toString() => super.toString(); } /// A class which mocks [Utils]. @@ -493,6 +487,4 @@ class MockUtils extends _i1.Mock implements _i8.Utils { double convertRadiusToSigma(double? radius) => (super.noSuchMethod(Invocation.method(#convertRadiusToSigma, [radius]), returnValue: 0.0) as double); - @override - String toString() => super.toString(); } diff --git a/test/chart/scatter_chart/scatter_chart_painter_test.mocks.dart b/test/chart/scatter_chart/scatter_chart_painter_test.mocks.dart index 57b9eb086..767e9492e 100644 --- a/test/chart/scatter_chart/scatter_chart_painter_test.mocks.dart +++ b/test/chart/scatter_chart/scatter_chart_painter_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.0.16 from annotations +// Mocks generated by Mockito 5.0.17 from annotations // in fl_chart/test/chart/scatter_chart/scatter_chart_painter_test.dart. // Do not manually edit this file. @@ -240,8 +240,6 @@ class MockCanvas extends _i1.Mock implements _i2.Canvas { Invocation.method( #drawShadow, [path, color, elevation, transparentOccluder]), returnValueForMissingStub: null); - @override - String toString() => super.toString(); } /// A class which mocks [CanvasWrapper]. @@ -339,8 +337,8 @@ class MockCanvasWrapper extends _i1.Mock implements _i6.CanvasWrapper { @override void drawRotated( {_i2.Size? size, - _i2.Offset? rotationOffset = const _i2.Offset(0.0, 0.0), - _i2.Offset? drawOffset = const _i2.Offset(0.0, 0.0), + _i2.Offset? rotationOffset = _i2.Offset.zero, + _i2.Offset? drawOffset = _i2.Offset.zero, double? angle, void Function()? drawCallback}) => super.noSuchMethod( @@ -358,8 +356,6 @@ class MockCanvasWrapper extends _i1.Mock implements _i6.CanvasWrapper { super.noSuchMethod( Invocation.method(#drawDashedLine, [from, to, painter, dashArray]), returnValueForMissingStub: null); - @override - String toString() => super.toString(); } /// A class which mocks [BuildContext]. @@ -417,8 +413,6 @@ class MockBuildContext extends _i1.Mock implements _i3.BuildContext { _i3.DiagnosticsNode describeOwnershipChain(String? name) => (super.noSuchMethod(Invocation.method(#describeOwnershipChain, [name]), returnValue: _FakeDiagnosticsNode_4()) as _i3.DiagnosticsNode); - @override - String toString() => super.toString(); } /// A class which mocks [Utils]. @@ -493,6 +487,4 @@ class MockUtils extends _i1.Mock implements _i8.Utils { double convertRadiusToSigma(double? radius) => (super.noSuchMethod(Invocation.method(#convertRadiusToSigma, [radius]), returnValue: 0.0) as double); - @override - String toString() => super.toString(); } From e2163696ff9090325a612ae02e2027dd10d76ea2 Mon Sep 17 00:00:00 2001 From: imaN Khoshabi Date: Thu, 27 Jan 2022 19:51:37 +0330 Subject: [PATCH 07/45] Write unit test for drawBackground() of axisChartPainter in line_chart_painter_test.dart --- .../base/axis_chart/axis_chart_painter.dart | 5 +- .../line_chart/line_chart_painter_test.dart | 55 +++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/lib/src/chart/base/axis_chart/axis_chart_painter.dart b/lib/src/chart/base/axis_chart/axis_chart_painter.dart index 3d774fcf0..23d76bd74 100644 --- a/lib/src/chart/base/axis_chart/axis_chart_painter.dart +++ b/lib/src/chart/base/axis_chart/axis_chart_painter.dart @@ -35,7 +35,7 @@ abstract class AxisChartPainter void paint(BuildContext context, CanvasWrapper canvasWrapper, PaintHolder holder) { super.paint(context, canvasWrapper, holder); - _drawBackground(canvasWrapper, holder); + drawBackground(canvasWrapper, holder); _drawRangeAnnotation(canvasWrapper, holder); drawGrid(canvasWrapper, holder); } @@ -313,7 +313,8 @@ abstract class AxisChartPainter } /// This function draws a colored background behind the chart. - void _drawBackground(CanvasWrapper canvasWrapper, PaintHolder holder) { + @visibleForTesting + void drawBackground(CanvasWrapper canvasWrapper, PaintHolder holder) { final data = holder.data; if (data.backgroundColor.opacity == 0.0) { return; diff --git a/test/chart/line_chart/line_chart_painter_test.dart b/test/chart/line_chart/line_chart_painter_test.dart index e71fad639..033e59336 100644 --- a/test/chart/line_chart/line_chart_painter_test.dart +++ b/test/chart/line_chart/line_chart_painter_test.dart @@ -3022,4 +3022,59 @@ void main() { verify(_mockCanvasWrapper.drawDashedLine(any, any, any, any)).called(6); }); }); + + group('drawBackground()', () { + test('test 1', () { + const viewSize = Size(20, 100); + + final LineChartData data = LineChartData( + minY: 0, + maxY: 10, + minX: 0, + maxX: 10, + titlesData: FlTitlesData(show: false), + axisTitleData: FlAxisTitleData(show: false), + backgroundColor: MockData.color1.withOpacity(0), + ); + + final LineChartPainter lineChartPainter = LineChartPainter(); + final holder = PaintHolder(data, data, 1.0); + MockCanvasWrapper _mockCanvasWrapper = MockCanvasWrapper(); + when(_mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); + when(_mockCanvasWrapper.canvas).thenReturn(MockCanvas()); + + lineChartPainter.drawBackground(_mockCanvasWrapper, holder); + verifyNever(_mockCanvasWrapper.drawRect(any, any)); + }); + + test('test 2', () { + const viewSize = Size(20, 100); + + final LineChartData data = LineChartData( + minY: 0, + maxY: 10, + minX: 0, + maxX: 10, + titlesData: FlTitlesData(show: false), + axisTitleData: FlAxisTitleData(show: false), + backgroundColor: MockData.color1, + ); + + final LineChartPainter lineChartPainter = LineChartPainter(); + final holder = PaintHolder(data, data, 1.0); + MockCanvasWrapper _mockCanvasWrapper = MockCanvasWrapper(); + when(_mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); + when(_mockCanvasWrapper.canvas).thenReturn(MockCanvas()); + + lineChartPainter.drawBackground(_mockCanvasWrapper, holder); + final result = verify( + _mockCanvasWrapper.drawRect( + const Rect.fromLTRB(0, 0, 20, 100), + captureAny, + ), + ); + expect(result.callCount, 1); + expect((result.captured.single as Paint).color, MockData.color1); + }); + }); } From 696cc6cdbf5c645773b195f10270c63eb70e8a8c Mon Sep 17 00:00:00 2001 From: imaN Khoshabi Date: Thu, 27 Jan 2022 20:03:29 +0330 Subject: [PATCH 08/45] Write unit test for drawRangeAnnotation() of axisChartPainter in line_chart_painter_test.dart --- .../base/axis_chart/axis_chart_painter.dart | 6 +- .../line_chart/line_chart_painter_test.dart | 143 ++++++++++++++++++ .../line_chart_painter_test.mocks.dart | 12 ++ 3 files changed, 158 insertions(+), 3 deletions(-) diff --git a/lib/src/chart/base/axis_chart/axis_chart_painter.dart b/lib/src/chart/base/axis_chart/axis_chart_painter.dart index 23d76bd74..cfb0c22e0 100644 --- a/lib/src/chart/base/axis_chart/axis_chart_painter.dart +++ b/lib/src/chart/base/axis_chart/axis_chart_painter.dart @@ -36,7 +36,7 @@ abstract class AxisChartPainter PaintHolder holder) { super.paint(context, canvasWrapper, holder); drawBackground(canvasWrapper, holder); - _drawRangeAnnotation(canvasWrapper, holder); + drawRangeAnnotation(canvasWrapper, holder); drawGrid(canvasWrapper, holder); } @@ -334,8 +334,8 @@ abstract class AxisChartPainter ); } - void _drawRangeAnnotation( - CanvasWrapper canvasWrapper, PaintHolder holder) { + @visibleForTesting + void drawRangeAnnotation(CanvasWrapper canvasWrapper, PaintHolder holder) { final data = holder.data; final viewSize = canvasWrapper.size; final chartUsableSize = getChartUsableDrawSize(viewSize, holder); diff --git a/test/chart/line_chart/line_chart_painter_test.dart b/test/chart/line_chart/line_chart_painter_test.dart index 033e59336..62f8547e0 100644 --- a/test/chart/line_chart/line_chart_painter_test.dart +++ b/test/chart/line_chart/line_chart_painter_test.dart @@ -3077,4 +3077,147 @@ void main() { expect((result.captured.single as Paint).color, MockData.color1); }); }); + + group('drawRangeAnnotation()', () { + test('test 1 - none', () { + const viewSize = Size(20, 100); + + final LineChartData data = LineChartData( + minY: 0, + maxY: 10, + minX: 0, + maxX: 10, + titlesData: FlTitlesData(show: false), + axisTitleData: FlAxisTitleData(show: false), + rangeAnnotations: RangeAnnotations(), + ); + + final LineChartPainter lineChartPainter = LineChartPainter(); + final holder = PaintHolder(data, data, 1.0); + MockCanvasWrapper _mockCanvasWrapper = MockCanvasWrapper(); + when(_mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); + when(_mockCanvasWrapper.canvas).thenReturn(MockCanvas()); + + lineChartPainter.drawRangeAnnotation(_mockCanvasWrapper, holder); + verifyNever(_mockCanvasWrapper.drawRect(any, any)); + }); + + test('test 2 - horizontal', () { + const viewSize = Size(20, 100); + + final LineChartData data = LineChartData( + minY: 0, + maxY: 10, + minX: 0, + maxX: 10, + titlesData: FlTitlesData(show: false), + axisTitleData: FlAxisTitleData(show: false), + rangeAnnotations: RangeAnnotations( + horizontalRangeAnnotations: [ + HorizontalRangeAnnotation(y1: 4, y2: 10, color: MockData.color1), + HorizontalRangeAnnotation(y1: 12, y2: 14, color: MockData.color2), + ], + ), + ); + + final LineChartPainter lineChartPainter = LineChartPainter(); + final holder = PaintHolder(data, data, 1.0); + MockCanvasWrapper _mockCanvasWrapper = MockCanvasWrapper(); + when(_mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); + when(_mockCanvasWrapper.canvas).thenReturn(MockCanvas()); + + List> results = []; + when(_mockCanvasWrapper.drawRect(captureAny, captureAny)) + .thenAnswer((inv) { + results.add({ + 'rect': inv.positionalArguments[0] as Rect, + 'paint_color': (inv.positionalArguments[1] as Paint).color, + }); + }); + + lineChartPainter.drawRangeAnnotation(_mockCanvasWrapper, holder); + expect(results.length, 2); + + expect(results[0]['rect'], const Rect.fromLTRB(0.0, 0.0, 20.0, 60.0)); + expect(results[0]['paint_color'], MockData.color1); + + expect(results[1]['rect'], const Rect.fromLTRB(0.0, -40.0, 20.0, -20.0)); + expect(results[1]['paint_color'], MockData.color2); + }); + + test('test 3 - vertical', () { + const viewSize = Size(20, 100); + + final LineChartData data = LineChartData( + minY: 0, + maxY: 10, + minX: 0, + maxX: 10, + titlesData: FlTitlesData(show: false), + axisTitleData: FlAxisTitleData(show: false), + rangeAnnotations: RangeAnnotations( + verticalRangeAnnotations: [ + VerticalRangeAnnotation(x1: 1, x2: 2, color: MockData.color1), + VerticalRangeAnnotation(x1: 4, x2: 5, color: MockData.color2), + ], + ), + ); + + final LineChartPainter lineChartPainter = LineChartPainter(); + final holder = PaintHolder(data, data, 1.0); + MockCanvasWrapper _mockCanvasWrapper = MockCanvasWrapper(); + when(_mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); + when(_mockCanvasWrapper.canvas).thenReturn(MockCanvas()); + + List> results = []; + when(_mockCanvasWrapper.drawRect(captureAny, captureAny)) + .thenAnswer((inv) { + results.add({ + 'rect': inv.positionalArguments[0] as Rect, + 'paint_color': (inv.positionalArguments[1] as Paint).color, + }); + }); + + lineChartPainter.drawRangeAnnotation(_mockCanvasWrapper, holder); + expect(results.length, 2); + + expect(results[0]['rect'], const Rect.fromLTRB(2.0, 0.0, 4.0, 100.0)); + expect(results[0]['paint_color'], MockData.color1); + + expect(results[1]['rect'], const Rect.fromLTRB(8.0, 0.0, 10.0, 100.0)); + expect(results[1]['paint_color'], MockData.color2); + }); + + test('test 4 - both', () { + const viewSize = Size(20, 100); + + final LineChartData data = LineChartData( + minY: 0, + maxY: 10, + minX: 0, + maxX: 10, + titlesData: FlTitlesData(show: false), + axisTitleData: FlAxisTitleData(show: false), + rangeAnnotations: RangeAnnotations( + horizontalRangeAnnotations: [ + HorizontalRangeAnnotation(y1: 4, y2: 10, color: MockData.color1), + HorizontalRangeAnnotation(y1: 12, y2: 14, color: MockData.color2), + ], + verticalRangeAnnotations: [ + VerticalRangeAnnotation(x1: 1, x2: 2, color: MockData.color1), + VerticalRangeAnnotation(x1: 4, x2: 5, color: MockData.color2), + ], + ), + ); + + final LineChartPainter lineChartPainter = LineChartPainter(); + final holder = PaintHolder(data, data, 1.0); + MockCanvasWrapper _mockCanvasWrapper = MockCanvasWrapper(); + when(_mockCanvasWrapper.size).thenAnswer((realInvocation) => viewSize); + when(_mockCanvasWrapper.canvas).thenReturn(MockCanvas()); + lineChartPainter.drawRangeAnnotation(_mockCanvasWrapper, holder); + + verify(_mockCanvasWrapper.drawRect(captureAny, captureAny)).called(4); + }); + }); } diff --git a/test/chart/line_chart/line_chart_painter_test.mocks.dart b/test/chart/line_chart/line_chart_painter_test.mocks.dart index 9d1812034..b9b238f14 100644 --- a/test/chart/line_chart/line_chart_painter_test.mocks.dart +++ b/test/chart/line_chart/line_chart_painter_test.mocks.dart @@ -767,6 +767,18 @@ class MockLineChartPainter extends _i1.Mock implements _i9.LineChartPainter { super.noSuchMethod(Invocation.method(#drawGrid, [canvasWrapper, holder]), returnValueForMissingStub: null); @override + void drawBackground(_i6.CanvasWrapper? canvasWrapper, + _i10.PaintHolder<_i7.LineChartData>? holder) => + super.noSuchMethod( + Invocation.method(#drawBackground, [canvasWrapper, holder]), + returnValueForMissingStub: null); + @override + void drawRangeAnnotation(_i6.CanvasWrapper? canvasWrapper, + _i10.PaintHolder<_i7.LineChartData>? holder) => + super.noSuchMethod( + Invocation.method(#drawRangeAnnotation, [canvasWrapper, holder]), + returnValueForMissingStub: null); + @override double getPixelX(double? spotX, _i2.Size? chartUsableSize, _i10.PaintHolder<_i7.LineChartData>? holder) => (super.noSuchMethod( From 2c83454c535a7a15c5dce02f743d5b75e7122825 Mon Sep 17 00:00:00 2001 From: imaN Khoshabi Date: Thu, 27 Jan 2022 20:04:47 +0330 Subject: [PATCH 09/45] Ignore axis_chart_data and axis_chart from test coverage (because they are abstract and data classes) --- lib/src/chart/base/axis_chart/axis_chart.dart | 1 + lib/src/chart/base/axis_chart/axis_chart_data.dart | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/src/chart/base/axis_chart/axis_chart.dart b/lib/src/chart/base/axis_chart/axis_chart.dart index f505343d3..8e21d48ac 100644 --- a/lib/src/chart/base/axis_chart/axis_chart.dart +++ b/lib/src/chart/base/axis_chart/axis_chart.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file import 'package:fl_chart/src/chart/bar_chart/bar_chart.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart.dart'; import 'package:fl_chart/src/chart/line_chart/line_chart.dart'; 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 43f78299c..d0c29907b 100644 --- a/lib/src/chart/base/axis_chart/axis_chart_data.dart +++ b/lib/src/chart/base/axis_chart/axis_chart_data.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file import 'dart:ui'; import 'package:equatable/equatable.dart'; From 98dd64d769aedda1d8c54dddc67214e429c52c65 Mon Sep 17 00:00:00 2001 From: imaN Khoshabi Date: Thu, 27 Jan 2022 20:07:09 +0330 Subject: [PATCH 10/45] Ignore bar, line, pie, radar, scatter data classes from test coverage --- lib/src/chart/bar_chart/bar_chart_data.dart | 1 + lib/src/chart/line_chart/line_chart_data.dart | 1 + lib/src/chart/pie_chart/pie_chart_data.dart | 1 + lib/src/chart/radar_chart/radar_chart_data.dart | 1 + lib/src/chart/scatter_chart/scatter_chart_data.dart | 1 + 5 files changed, 5 insertions(+) diff --git a/lib/src/chart/bar_chart/bar_chart_data.dart b/lib/src/chart/bar_chart/bar_chart_data.dart index ef33ad45e..70419c886 100644 --- a/lib/src/chart/bar_chart/bar_chart_data.dart +++ b/lib/src/chart/bar_chart/bar_chart_data.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file import 'dart:ui'; import 'package:equatable/equatable.dart'; diff --git a/lib/src/chart/line_chart/line_chart_data.dart b/lib/src/chart/line_chart/line_chart_data.dart index 6ff3a74dd..91c370676 100644 --- a/lib/src/chart/line_chart/line_chart_data.dart +++ b/lib/src/chart/line_chart/line_chart_data.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file import 'dart:ui'; import 'package:equatable/equatable.dart'; diff --git a/lib/src/chart/pie_chart/pie_chart_data.dart b/lib/src/chart/pie_chart/pie_chart_data.dart index 9bdc0d69f..9a9303572 100644 --- a/lib/src/chart/pie_chart/pie_chart_data.dart +++ b/lib/src/chart/pie_chart/pie_chart_data.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file import 'dart:ui'; import 'package:equatable/equatable.dart'; diff --git a/lib/src/chart/radar_chart/radar_chart_data.dart b/lib/src/chart/radar_chart/radar_chart_data.dart index 828797a89..7fafdfa03 100644 --- a/lib/src/chart/radar_chart/radar_chart_data.dart +++ b/lib/src/chart/radar_chart/radar_chart_data.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file import 'dart:ui'; import 'package:equatable/equatable.dart'; diff --git a/lib/src/chart/scatter_chart/scatter_chart_data.dart b/lib/src/chart/scatter_chart/scatter_chart_data.dart index 0cc072c86..2d8fad52d 100644 --- a/lib/src/chart/scatter_chart/scatter_chart_data.dart +++ b/lib/src/chart/scatter_chart/scatter_chart_data.dart @@ -1,3 +1,4 @@ +// coverage:ignore-file import 'dart:ui'; import 'package:equatable/equatable.dart'; From b839ebb68f02c0575d4cbdcdf848e93d71663633 Mon Sep 17 00:00:00 2001 From: imaN Khoshabi Date: Thu, 27 Jan 2022 21:53:17 +0330 Subject: [PATCH 11/45] Implement unit test for chart renderers --- .../chart/bar_chart/bar_chart_renderer.dart | 21 +- .../chart/line_chart/line_chart_renderer.dart | 21 +- .../chart/pie_chart/pie_chart_renderer.dart | 29 +- .../radar_chart/radar_chart_renderer.dart | 24 +- .../scatter_chart/scatter_chart_data.dart | 2 +- .../scatter_chart/scatter_chart_renderer.dart | 23 +- .../bar_chart/bar_chart_renderer_test.dart | 102 +++ .../bar_chart_renderer_test.mocks.dart | 631 ++++++++++++++ test/chart/data_pool.dart | 111 +++ .../line_chart/line_chart_renderer_test.dart | 105 +++ .../line_chart_renderer_test.mocks.dart | 771 ++++++++++++++++++ .../pie_chart/pie_chart_renderer_test.dart | 90 ++ .../pie_chart_renderer_test.mocks.dart | 598 ++++++++++++++ .../radar_chart_renderer_test.dart | 96 +++ .../radar_chart_renderer_test.mocks.dart | 558 +++++++++++++ .../scatter_chart_renderer_test.dart | 92 +++ .../scatter_chart_renderer_test.mocks.dart | 584 +++++++++++++ 17 files changed, 3841 insertions(+), 17 deletions(-) create mode 100644 test/chart/bar_chart/bar_chart_renderer_test.dart create mode 100644 test/chart/bar_chart/bar_chart_renderer_test.mocks.dart create mode 100644 test/chart/line_chart/line_chart_renderer_test.dart create mode 100644 test/chart/line_chart/line_chart_renderer_test.mocks.dart create mode 100644 test/chart/pie_chart/pie_chart_renderer_test.dart create mode 100644 test/chart/pie_chart/pie_chart_renderer_test.mocks.dart create mode 100644 test/chart/radar_chart/radar_chart_renderer_test.dart create mode 100644 test/chart/radar_chart/radar_chart_renderer_test.mocks.dart create mode 100644 test/chart/scatter_chart/scatter_chart_renderer_test.dart create mode 100644 test/chart/scatter_chart/scatter_chart_renderer_test.mocks.dart diff --git a/lib/src/chart/bar_chart/bar_chart_renderer.dart b/lib/src/chart/bar_chart/bar_chart_renderer.dart index f54d1c47e..5b0958afd 100644 --- a/lib/src/chart/bar_chart/bar_chart_renderer.dart +++ b/lib/src/chart/bar_chart/bar_chart_renderer.dart @@ -6,6 +6,7 @@ import 'package:flutter/cupertino.dart'; import 'bar_chart_painter.dart'; +// coverage:ignore-start /// Low level BarChart Widget. class BarChartLeaf extends LeafRenderObjectWidget { const BarChartLeaf({Key? key, required this.data, required this.targetData}) @@ -26,6 +27,7 @@ class BarChartLeaf extends LeafRenderObjectWidget { ..buildContext = context; } } +// coverage:ignore-end /// Renders our BarChart, also handles hitTest. class RenderBarChart extends RenderBaseChart { @@ -67,7 +69,12 @@ class RenderBarChart extends RenderBaseChart { markNeedsPaint(); } - final _painter = BarChartPainter(); + // We couldn't mock [size] property of this class, that's why we have this + @visibleForTesting + Size? mockTestSize; + + @visibleForTesting + var painter = BarChartPainter(); PaintHolder get paintHolder { return PaintHolder(data, targetData, textScale); @@ -78,13 +85,21 @@ class RenderBarChart extends RenderBaseChart { final canvas = context.canvas; canvas.save(); canvas.translate(offset.dx, offset.dy); - _painter.paint(buildContext, CanvasWrapper(canvas, size), paintHolder); + painter.paint( + buildContext, + CanvasWrapper(canvas, mockTestSize ?? size), + paintHolder, + ); canvas.restore(); } @override BarTouchResponse getResponseAtLocation(Offset localPosition) { - var touchedSpot = _painter.handleTouch(localPosition, size, paintHolder); + var touchedSpot = painter.handleTouch( + localPosition, + mockTestSize ?? size, + paintHolder, + ); return BarTouchResponse(touchedSpot); } } diff --git a/lib/src/chart/line_chart/line_chart_renderer.dart b/lib/src/chart/line_chart/line_chart_renderer.dart index e2d14d20b..06371cd38 100644 --- a/lib/src/chart/line_chart/line_chart_renderer.dart +++ b/lib/src/chart/line_chart/line_chart_renderer.dart @@ -6,6 +6,7 @@ import 'package:fl_chart/src/utils/canvas_wrapper.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +// coverage:ignore-start /// Low level LineChart Widget. class LineChartLeaf extends LeafRenderObjectWidget { const LineChartLeaf({Key? key, required this.data, required this.targetData}) @@ -26,6 +27,7 @@ class LineChartLeaf extends LeafRenderObjectWidget { ..buildContext = context; } } +// coverage:ignore-end /// Renders our LineChart, also handles hitTest. class RenderLineChart extends RenderBaseChart { @@ -61,7 +63,12 @@ class RenderLineChart extends RenderBaseChart { markNeedsPaint(); } - final _painter = LineChartPainter(); + // We couldn't mock [size] property of this class, that's why we have this + @visibleForTesting + Size? mockTestSize; + + @visibleForTesting + var painter = LineChartPainter(); PaintHolder get paintHolder { return PaintHolder(data, targetData, textScale); @@ -72,13 +79,21 @@ class RenderLineChart extends RenderBaseChart { final canvas = context.canvas; canvas.save(); canvas.translate(offset.dx, offset.dy); - _painter.paint(buildContext, CanvasWrapper(canvas, size), paintHolder); + painter.paint( + buildContext, + CanvasWrapper(canvas, mockTestSize ?? size), + paintHolder, + ); canvas.restore(); } @override LineTouchResponse getResponseAtLocation(Offset localPosition) { - var touchedSpots = _painter.handleTouch(localPosition, size, paintHolder); + var touchedSpots = painter.handleTouch( + localPosition, + mockTestSize ?? size, + paintHolder, + ); return LineTouchResponse(touchedSpots); } } diff --git a/lib/src/chart/pie_chart/pie_chart_renderer.dart b/lib/src/chart/pie_chart/pie_chart_renderer.dart index 040f80edf..ddbcb5449 100644 --- a/lib/src/chart/pie_chart/pie_chart_renderer.dart +++ b/lib/src/chart/pie_chart/pie_chart_renderer.dart @@ -8,6 +8,7 @@ import 'package:flutter/services.dart'; import 'pie_chart_painter.dart'; +// coverage:ignore-start /// Low level PieChart Widget. class PieChartLeaf extends MultiChildRenderObjectWidget { PieChartLeaf({ @@ -38,6 +39,7 @@ class PieChartLeaf extends MultiChildRenderObjectWidget { ..buildContext = context; } } +// coverage:ignore-end /// Renders our PieChart, also handles hitTest. class RenderPieChart extends RenderBaseChart @@ -54,6 +56,7 @@ class RenderPieChart extends RenderBaseChart PieChartData get data => _data; PieChartData _data; + set data(PieChartData value) { if (_data == value) return; _data = value; @@ -63,6 +66,7 @@ class RenderPieChart extends RenderBaseChart PieChartData get targetData => _targetData; PieChartData _targetData; + set targetData(PieChartData value) { if (_targetData == value) return; _targetData = value; @@ -73,13 +77,19 @@ class RenderPieChart extends RenderBaseChart double get textScale => _textScale; double _textScale; + set textScale(double value) { if (_textScale == value) return; _textScale = value; markNeedsPaint(); } - final _painter = PieChartPainter(); + // We couldn't mock [size] property of this class, that's why we have this + @visibleForTesting + Size? mockTestSize; + + @visibleForTesting + var painter = PieChartPainter(); PaintHolder get paintHolder { return PaintHolder(data, targetData, textScale); @@ -100,7 +110,10 @@ class RenderPieChart extends RenderBaseChart final childConstraints = constraints.loosen(); var counter = 0; - var badgeOffsets = _painter.getBadgeOffsets(size, paintHolder); + var badgeOffsets = painter.getBadgeOffsets( + mockTestSize ?? size, + paintHolder, + ); while (child != null) { if (counter >= badgeOffsets.length) { break; @@ -124,14 +137,22 @@ class RenderPieChart extends RenderBaseChart final canvas = context.canvas; canvas.save(); canvas.translate(offset.dx, offset.dy); - _painter.paint(buildContext, CanvasWrapper(canvas, size), paintHolder); + painter.paint( + buildContext, + CanvasWrapper(canvas, mockTestSize ?? size), + paintHolder, + ); canvas.restore(); defaultPaint(context, offset); } @override PieTouchResponse getResponseAtLocation(Offset localPosition) { - final pieSection = _painter.handleTouch(localPosition, size, paintHolder); + final pieSection = painter.handleTouch( + localPosition, + mockTestSize ?? size, + paintHolder, + ); return PieTouchResponse(pieSection); } } diff --git a/lib/src/chart/radar_chart/radar_chart_renderer.dart b/lib/src/chart/radar_chart/radar_chart_renderer.dart index e4c8e4d74..0c528db47 100644 --- a/lib/src/chart/radar_chart/radar_chart_renderer.dart +++ b/lib/src/chart/radar_chart/radar_chart_renderer.dart @@ -6,6 +6,7 @@ import 'package:flutter/cupertino.dart'; import 'radar_chart_painter.dart'; +// coverage:ignore-start /// Low level RadarChart Widget. class RadarChartLeaf extends LeafRenderObjectWidget { const RadarChartLeaf({Key? key, required this.data, required this.targetData}) @@ -26,6 +27,7 @@ class RadarChartLeaf extends LeafRenderObjectWidget { ..buildContext = context; } } +// coverage:ignore-end /// Renders our RadarChart, also handles hitTest. class RenderRadarChart extends RenderBaseChart { @@ -38,6 +40,7 @@ class RenderRadarChart extends RenderBaseChart { RadarChartData get data => _data; RadarChartData _data; + set data(RadarChartData value) { if (_data == value) return; _data = value; @@ -46,6 +49,7 @@ class RenderRadarChart extends RenderBaseChart { RadarChartData get targetData => _targetData; RadarChartData _targetData; + set targetData(RadarChartData value) { if (_targetData == value) return; _targetData = value; @@ -55,13 +59,19 @@ class RenderRadarChart extends RenderBaseChart { double get textScale => _textScale; double _textScale; + set textScale(double value) { if (_textScale == value) return; _textScale = value; markNeedsPaint(); } - final _painter = RadarChartPainter(); + // We couldn't mock [size] property of this class, that's why we have this + @visibleForTesting + Size? mockTestSize; + + @visibleForTesting + var painter = RadarChartPainter(); PaintHolder get paintHolder { return PaintHolder(data, targetData, textScale); @@ -72,13 +82,21 @@ class RenderRadarChart extends RenderBaseChart { final canvas = context.canvas; canvas.save(); canvas.translate(offset.dx, offset.dy); - _painter.paint(buildContext, CanvasWrapper(canvas, size), paintHolder); + painter.paint( + buildContext, + CanvasWrapper(canvas, mockTestSize ?? size), + paintHolder, + ); canvas.restore(); } @override RadarTouchResponse getResponseAtLocation(Offset localPosition) { - var touchedSpot = _painter.handleTouch(localPosition, size, paintHolder); + var touchedSpot = painter.handleTouch( + localPosition, + mockTestSize ?? size, + paintHolder, + ); return RadarTouchResponse(touchedSpot); } } diff --git a/lib/src/chart/scatter_chart/scatter_chart_data.dart b/lib/src/chart/scatter_chart/scatter_chart_data.dart index 2d8fad52d..e579cc4a2 100644 --- a/lib/src/chart/scatter_chart/scatter_chart_data.dart +++ b/lib/src/chart/scatter_chart/scatter_chart_data.dart @@ -343,7 +343,7 @@ class ScatterTouchedSpot with EquatableMixin { /// [spot], and [spotIndex] tells you /// in which spot (of [ScatterChartData.scatterSpots]) touch happened. - ScatterTouchedSpot(this.spot, this.spotIndex); + const ScatterTouchedSpot(this.spot, this.spotIndex); /// Used for equality check, see [EquatableMixin]. @override diff --git a/lib/src/chart/scatter_chart/scatter_chart_renderer.dart b/lib/src/chart/scatter_chart/scatter_chart_renderer.dart index 03ada0d56..6244a93f5 100644 --- a/lib/src/chart/scatter_chart/scatter_chart_renderer.dart +++ b/lib/src/chart/scatter_chart/scatter_chart_renderer.dart @@ -6,6 +6,7 @@ import 'package:flutter/cupertino.dart'; import 'scatter_chart_painter.dart'; +// coverage:ignore-start /// Low level ScatterChart Widget. class ScatterChartLeaf extends LeafRenderObjectWidget { const ScatterChartLeaf( @@ -29,6 +30,7 @@ class ScatterChartLeaf extends LeafRenderObjectWidget { ..buildContext = context; } } +// coverage:ignore-end /// Renders our ScatterChart, also handles hitTest. class RenderScatterChart extends RenderBaseChart { @@ -41,6 +43,7 @@ class RenderScatterChart extends RenderBaseChart { ScatterChartData get data => _data; ScatterChartData _data; + set data(ScatterChartData value) { if (_data == value) return; _data = value; @@ -49,6 +52,7 @@ class RenderScatterChart extends RenderBaseChart { ScatterChartData get targetData => _targetData; ScatterChartData _targetData; + set targetData(ScatterChartData value) { if (_targetData == value) return; _targetData = value; @@ -65,7 +69,12 @@ class RenderScatterChart extends RenderBaseChart { markNeedsPaint(); } - final _painter = ScatterChartPainter(); + // We couldn't mock [size] property of this class, that's why we have this + @visibleForTesting + Size? mockTestSize; + + @visibleForTesting + var painter = ScatterChartPainter(); PaintHolder get paintHolder { return PaintHolder(data, targetData, textScale); @@ -76,13 +85,21 @@ class RenderScatterChart extends RenderBaseChart { final canvas = context.canvas; canvas.save(); canvas.translate(offset.dx, offset.dy); - _painter.paint(buildContext, CanvasWrapper(canvas, size), paintHolder); + painter.paint( + buildContext, + CanvasWrapper(canvas, mockTestSize ?? size), + paintHolder, + ); canvas.restore(); } @override ScatterTouchResponse getResponseAtLocation(Offset localPosition) { - var touchedSpot = _painter.handleTouch(localPosition, size, paintHolder); + var touchedSpot = painter.handleTouch( + localPosition, + mockTestSize ?? size, + paintHolder, + ); return ScatterTouchResponse(touchedSpot); } } diff --git a/test/chart/bar_chart/bar_chart_renderer_test.dart b/test/chart/bar_chart/bar_chart_renderer_test.dart new file mode 100644 index 000000000..e68072d3d --- /dev/null +++ b/test/chart/bar_chart/bar_chart_renderer_test.dart @@ -0,0 +1,102 @@ +import 'package:fl_chart/fl_chart.dart'; +import 'package:fl_chart/src/chart/bar_chart/bar_chart_painter.dart'; +import 'package:fl_chart/src/chart/bar_chart/bar_chart_renderer.dart'; +import 'package:fl_chart/src/utils/canvas_wrapper.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import '../data_pool.dart'; +import 'bar_chart_renderer_test.mocks.dart'; + +@GenerateMocks([Canvas, PaintingContext, BuildContext, BarChartPainter]) +void main() { + group('BarChartRenderer', () { + final BarChartData data = BarChartData( + titlesData: FlTitlesData( + leftTitles: SideTitles(reservedSize: 12, margin: 8, showTitles: true), + rightTitles: SideTitles(reservedSize: 44, margin: 20, showTitles: true), + topTitles: SideTitles(showTitles: false), + bottomTitles: SideTitles(showTitles: false), + )); + + final BarChartData targetData = BarChartData( + titlesData: FlTitlesData( + leftTitles: SideTitles(reservedSize: 0, margin: 8, showTitles: true), + rightTitles: SideTitles(reservedSize: 0, margin: 20, showTitles: true), + topTitles: SideTitles(showTitles: false), + bottomTitles: SideTitles(showTitles: false), + )); + + const textScale = 4.0; + + MockBuildContext _mockBuildContext = MockBuildContext(); + RenderBarChart renderBarChart = RenderBarChart( + _mockBuildContext, + data, + targetData, + textScale, + ); + + MockBarChartPainter _mockPainter = MockBarChartPainter(); + MockPaintingContext _mockPaintingContext = MockPaintingContext(); + MockCanvas _mockCanvas = MockCanvas(); + Size _mockSize = const Size(44, 44); + when(_mockPaintingContext.canvas) + .thenAnswer((realInvocation) => _mockCanvas); + renderBarChart.mockTestSize = _mockSize; + renderBarChart.painter = _mockPainter; + + test('test 1 correct data set', () { + expect(renderBarChart.data == data, true); + expect(renderBarChart.data == targetData, false); + expect(renderBarChart.targetData == targetData, true); + expect(renderBarChart.textScale == textScale, true); + expect(renderBarChart.paintHolder.data == data, true); + expect(renderBarChart.paintHolder.targetData == targetData, true); + expect(renderBarChart.paintHolder.textScale == textScale, true); + }); + + test('test 2 check paint function', () { + renderBarChart.paint(_mockPaintingContext, const Offset(10, 10)); + verify(_mockCanvas.save()).called(1); + verify(_mockCanvas.translate(10, 10)).called(1); + final result = verify(_mockPainter.paint(any, captureAny, captureAny)); + expect(result.callCount, 1); + + final canvasWrapper = result.captured[0] as CanvasWrapper; + expect(canvasWrapper.size, const Size(44, 44)); + expect(canvasWrapper.canvas, _mockCanvas); + + final paintHolder = result.captured[1] as PaintHolder; + expect(paintHolder.data, data); + expect(paintHolder.targetData, targetData); + expect(paintHolder.textScale, textScale); + + verify(_mockCanvas.restore()).called(1); + }); + + test('test 3 check getResponseAtLocation function', () { + List> results = []; + when(_mockPainter.handleTouch(captureAny, captureAny, captureAny)) + .thenAnswer((inv) { + results.add({ + 'local_position': inv.positionalArguments[0] as Offset, + 'size': inv.positionalArguments[1] as Size, + 'paint_holder': (inv.positionalArguments[2] as PaintHolder), + }); + return MockData.barTouchedSpot; + }); + final touchResponse = + renderBarChart.getResponseAtLocation(MockData.offset1); + expect(touchResponse.spot, MockData.barTouchedSpot); + expect(results[0]['local_position'] as Offset, MockData.offset1); + expect(results[0]['size'] as Size, _mockSize); + final paintHolder = results[0]['paint_holder'] as PaintHolder; + expect(paintHolder.data, data); + expect(paintHolder.targetData, targetData); + expect(paintHolder.textScale, textScale); + }); + }); +} diff --git a/test/chart/bar_chart/bar_chart_renderer_test.mocks.dart b/test/chart/bar_chart/bar_chart_renderer_test.mocks.dart new file mode 100644 index 000000000..ad7b3e84f --- /dev/null +++ b/test/chart/bar_chart/bar_chart_renderer_test.mocks.dart @@ -0,0 +1,631 @@ +// Mocks generated by Mockito 5.0.17 from annotations +// in fl_chart/test/chart/bar_chart/bar_chart_renderer_test.dart. +// Do not manually edit this file. + +import 'dart:typed_data' as _i7; +import 'dart:ui' as _i2; + +import 'package:fl_chart/fl_chart.dart' as _i12; +import 'package:fl_chart/src/chart/bar_chart/bar_chart_painter.dart' as _i9; +import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart' + as _i11; +import 'package:fl_chart/src/utils/canvas_wrapper.dart' as _i10; +import 'package:flutter/foundation.dart' as _i5; +import 'package:flutter/material.dart' as _i6; +import 'package:flutter/rendering.dart' as _i3; +import 'package:flutter/src/rendering/layer.dart' as _i4; +import 'package:mockito/mockito.dart' as _i1; +import 'package:vector_math/vector_math_64.dart' as _i8; + +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types + +class _FakeRect_0 extends _i1.Fake implements _i2.Rect {} + +class _FakeCanvas_1 extends _i1.Fake implements _i2.Canvas {} + +class _FakePaintingContext_2 extends _i1.Fake implements _i3.PaintingContext {} + +class _FakeColorFilterLayer_3 extends _i1.Fake implements _i4.ColorFilterLayer { + @override + String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakeOpacityLayer_4 extends _i1.Fake implements _i4.OpacityLayer { + @override + String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakeWidget_5 extends _i1.Fake implements _i6.Widget { + @override + String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakeInheritedWidget_6 extends _i1.Fake implements _i6.InheritedWidget { + @override + String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakeDiagnosticsNode_7 extends _i1.Fake implements _i5.DiagnosticsNode { + @override + String toString( + {_i5.TextTreeConfiguration? parentConfiguration, + _i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakeSize_8 extends _i1.Fake implements _i2.Size {} + +/// A class which mocks [Canvas]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCanvas extends _i1.Mock implements _i2.Canvas { + MockCanvas() { + _i1.throwOnMissingStub(this); + } + + @override + void save() => super.noSuchMethod(Invocation.method(#save, []), + returnValueForMissingStub: null); + @override + void saveLayer(_i2.Rect? bounds, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#saveLayer, [bounds, paint]), + returnValueForMissingStub: null); + @override + void restore() => super.noSuchMethod(Invocation.method(#restore, []), + returnValueForMissingStub: null); + @override + int getSaveCount() => + (super.noSuchMethod(Invocation.method(#getSaveCount, []), returnValue: 0) + as int); + @override + void translate(double? dx, double? dy) => + super.noSuchMethod(Invocation.method(#translate, [dx, dy]), + returnValueForMissingStub: null); + @override + void scale(double? sx, [double? sy]) => + super.noSuchMethod(Invocation.method(#scale, [sx, sy]), + returnValueForMissingStub: null); + @override + void rotate(double? radians) => + super.noSuchMethod(Invocation.method(#rotate, [radians]), + returnValueForMissingStub: null); + @override + void skew(double? sx, double? sy) => + super.noSuchMethod(Invocation.method(#skew, [sx, sy]), + returnValueForMissingStub: null); + @override + void transform(_i7.Float64List? matrix4) => + super.noSuchMethod(Invocation.method(#transform, [matrix4]), + returnValueForMissingStub: null); + @override + void clipRect(_i2.Rect? rect, + {_i2.ClipOp? clipOp = _i2.ClipOp.intersect, + bool? doAntiAlias = true}) => + super.noSuchMethod( + Invocation.method( + #clipRect, [rect], {#clipOp: clipOp, #doAntiAlias: doAntiAlias}), + returnValueForMissingStub: null); + @override + void clipRRect(_i2.RRect? rrect, {bool? doAntiAlias = true}) => + super.noSuchMethod( + Invocation.method(#clipRRect, [rrect], {#doAntiAlias: doAntiAlias}), + returnValueForMissingStub: null); + @override + void clipPath(_i2.Path? path, {bool? doAntiAlias = true}) => + super.noSuchMethod( + Invocation.method(#clipPath, [path], {#doAntiAlias: doAntiAlias}), + returnValueForMissingStub: null); + @override + void drawColor(_i2.Color? color, _i2.BlendMode? blendMode) => + super.noSuchMethod(Invocation.method(#drawColor, [color, blendMode]), + returnValueForMissingStub: null); + @override + void drawLine(_i2.Offset? p1, _i2.Offset? p2, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawLine, [p1, p2, paint]), + returnValueForMissingStub: null); + @override + void drawPaint(_i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawPaint, [paint]), + returnValueForMissingStub: null); + @override + void drawRect(_i2.Rect? rect, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawRect, [rect, paint]), + returnValueForMissingStub: null); + @override + void drawRRect(_i2.RRect? rrect, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawRRect, [rrect, paint]), + returnValueForMissingStub: null); + @override + void drawDRRect(_i2.RRect? outer, _i2.RRect? inner, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawDRRect, [outer, inner, paint]), + returnValueForMissingStub: null); + @override + void drawOval(_i2.Rect? rect, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawOval, [rect, paint]), + returnValueForMissingStub: null); + @override + void drawCircle(_i2.Offset? c, double? radius, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawCircle, [c, radius, paint]), + returnValueForMissingStub: null); + @override + void drawArc(_i2.Rect? rect, double? startAngle, double? sweepAngle, + bool? useCenter, _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method( + #drawArc, [rect, startAngle, sweepAngle, useCenter, paint]), + returnValueForMissingStub: null); + @override + void drawPath(_i2.Path? path, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawPath, [path, paint]), + returnValueForMissingStub: null); + @override + void drawImage(_i2.Image? image, _i2.Offset? offset, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawImage, [image, offset, paint]), + returnValueForMissingStub: null); + @override + void drawImageRect( + _i2.Image? image, _i2.Rect? src, _i2.Rect? dst, _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawImageRect, [image, src, dst, paint]), + returnValueForMissingStub: null); + @override + void drawImageNine(_i2.Image? image, _i2.Rect? center, _i2.Rect? dst, + _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawImageNine, [image, center, dst, paint]), + returnValueForMissingStub: null); + @override + void drawPicture(_i2.Picture? picture) => + super.noSuchMethod(Invocation.method(#drawPicture, [picture]), + returnValueForMissingStub: null); + @override + void drawParagraph(_i2.Paragraph? paragraph, _i2.Offset? offset) => + super.noSuchMethod(Invocation.method(#drawParagraph, [paragraph, offset]), + returnValueForMissingStub: null); + @override + void drawPoints(_i2.PointMode? pointMode, List<_i2.Offset>? points, + _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawPoints, [pointMode, points, paint]), + returnValueForMissingStub: null); + @override + void drawRawPoints(_i2.PointMode? pointMode, _i7.Float32List? points, + _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawRawPoints, [pointMode, points, paint]), + returnValueForMissingStub: null); + @override + void drawVertices( + _i2.Vertices? vertices, _i2.BlendMode? blendMode, _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawVertices, [vertices, blendMode, paint]), + returnValueForMissingStub: null); + @override + void drawAtlas( + _i2.Image? atlas, + List<_i2.RSTransform>? transforms, + List<_i2.Rect>? rects, + List<_i2.Color>? colors, + _i2.BlendMode? blendMode, + _i2.Rect? cullRect, + _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawAtlas, + [atlas, transforms, rects, colors, blendMode, cullRect, paint]), + returnValueForMissingStub: null); + @override + void drawRawAtlas( + _i2.Image? atlas, + _i7.Float32List? rstTransforms, + _i7.Float32List? rects, + _i7.Int32List? colors, + _i2.BlendMode? blendMode, + _i2.Rect? cullRect, + _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawRawAtlas, [ + atlas, + rstTransforms, + rects, + colors, + blendMode, + cullRect, + paint + ]), + returnValueForMissingStub: null); + @override + void drawShadow(_i2.Path? path, _i2.Color? color, double? elevation, + bool? transparentOccluder) => + super.noSuchMethod( + Invocation.method( + #drawShadow, [path, color, elevation, transparentOccluder]), + returnValueForMissingStub: null); +} + +/// A class which mocks [PaintingContext]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockPaintingContext extends _i1.Mock implements _i3.PaintingContext { + MockPaintingContext() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.Rect get estimatedBounds => + (super.noSuchMethod(Invocation.getter(#estimatedBounds), + returnValue: _FakeRect_0()) as _i2.Rect); + @override + _i2.Canvas get canvas => (super.noSuchMethod(Invocation.getter(#canvas), + returnValue: _FakeCanvas_1()) as _i2.Canvas); + @override + void paintChild(_i3.RenderObject? child, _i2.Offset? offset) => + super.noSuchMethod(Invocation.method(#paintChild, [child, offset]), + returnValueForMissingStub: null); + @override + void appendLayer(_i4.Layer? layer) => + super.noSuchMethod(Invocation.method(#appendLayer, [layer]), + returnValueForMissingStub: null); + @override + void stopRecordingIfNeeded() => + super.noSuchMethod(Invocation.method(#stopRecordingIfNeeded, []), + returnValueForMissingStub: null); + @override + void setIsComplexHint() => + super.noSuchMethod(Invocation.method(#setIsComplexHint, []), + returnValueForMissingStub: null); + @override + void setWillChangeHint() => + super.noSuchMethod(Invocation.method(#setWillChangeHint, []), + returnValueForMissingStub: null); + @override + void addLayer(_i4.Layer? layer) => + super.noSuchMethod(Invocation.method(#addLayer, [layer]), + returnValueForMissingStub: null); + @override + void pushLayer(_i4.ContainerLayer? childLayer, + _i3.PaintingContextCallback? painter, _i2.Offset? offset, + {_i2.Rect? childPaintBounds}) => + super.noSuchMethod( + Invocation.method(#pushLayer, [childLayer, painter, offset], + {#childPaintBounds: childPaintBounds}), + returnValueForMissingStub: null); + @override + _i3.PaintingContext createChildContext( + _i4.ContainerLayer? childLayer, _i2.Rect? bounds) => + (super.noSuchMethod( + Invocation.method(#createChildContext, [childLayer, bounds]), + returnValue: _FakePaintingContext_2()) as _i3.PaintingContext); + @override + _i4.ClipRectLayer? pushClipRect(bool? needsCompositing, _i2.Offset? offset, + _i2.Rect? clipRect, _i3.PaintingContextCallback? painter, + {_i2.Clip? clipBehavior = _i2.Clip.hardEdge, + _i4.ClipRectLayer? oldLayer}) => + (super.noSuchMethod(Invocation.method(#pushClipRect, [ + needsCompositing, + offset, + clipRect, + painter + ], { + #clipBehavior: clipBehavior, + #oldLayer: oldLayer + })) as _i4.ClipRectLayer?); + @override + _i4.ClipRRectLayer? pushClipRRect( + bool? needsCompositing, + _i2.Offset? offset, + _i2.Rect? bounds, + _i2.RRect? clipRRect, + _i3.PaintingContextCallback? painter, + {_i2.Clip? clipBehavior = _i2.Clip.antiAlias, + _i4.ClipRRectLayer? oldLayer}) => + (super.noSuchMethod(Invocation.method(#pushClipRRect, [ + needsCompositing, + offset, + bounds, + clipRRect, + painter + ], { + #clipBehavior: clipBehavior, + #oldLayer: oldLayer + })) as _i4.ClipRRectLayer?); + @override + _i4.ClipPathLayer? pushClipPath( + bool? needsCompositing, + _i2.Offset? offset, + _i2.Rect? bounds, + _i2.Path? clipPath, + _i3.PaintingContextCallback? painter, + {_i2.Clip? clipBehavior = _i2.Clip.antiAlias, + _i4.ClipPathLayer? oldLayer}) => + (super.noSuchMethod(Invocation.method(#pushClipPath, [ + needsCompositing, + offset, + bounds, + clipPath, + painter + ], { + #clipBehavior: clipBehavior, + #oldLayer: oldLayer + })) as _i4.ClipPathLayer?); + @override + _i4.ColorFilterLayer pushColorFilter(_i2.Offset? offset, + _i2.ColorFilter? colorFilter, _i3.PaintingContextCallback? painter, + {_i4.ColorFilterLayer? oldLayer}) => + (super.noSuchMethod( + Invocation.method(#pushColorFilter, [offset, colorFilter, painter], + {#oldLayer: oldLayer}), + returnValue: _FakeColorFilterLayer_3()) as _i4.ColorFilterLayer); + @override + _i4.TransformLayer? pushTransform(bool? needsCompositing, _i2.Offset? offset, + _i8.Matrix4? transform, _i3.PaintingContextCallback? painter, + {_i4.TransformLayer? oldLayer}) => + (super.noSuchMethod(Invocation.method( + #pushTransform, + [needsCompositing, offset, transform, painter], + {#oldLayer: oldLayer})) as _i4.TransformLayer?); + @override + _i4.OpacityLayer pushOpacity( + _i2.Offset? offset, int? alpha, _i3.PaintingContextCallback? painter, + {_i4.OpacityLayer? oldLayer}) => + (super.noSuchMethod( + Invocation.method( + #pushOpacity, [offset, alpha, painter], {#oldLayer: oldLayer}), + returnValue: _FakeOpacityLayer_4()) as _i4.OpacityLayer); + @override + void clipPathAndPaint(_i2.Path? path, _i2.Clip? clipBehavior, + _i2.Rect? bounds, _i2.VoidCallback? painter) => + super.noSuchMethod( + Invocation.method( + #clipPathAndPaint, [path, clipBehavior, bounds, painter]), + returnValueForMissingStub: null); + @override + void clipRRectAndPaint(_i2.RRect? rrect, _i2.Clip? clipBehavior, + _i2.Rect? bounds, _i2.VoidCallback? painter) => + super.noSuchMethod( + Invocation.method( + #clipRRectAndPaint, [rrect, clipBehavior, bounds, painter]), + returnValueForMissingStub: null); + @override + void clipRectAndPaint(_i2.Rect? rect, _i2.Clip? clipBehavior, + _i2.Rect? bounds, _i2.VoidCallback? painter) => + super.noSuchMethod( + Invocation.method( + #clipRectAndPaint, [rect, clipBehavior, bounds, painter]), + returnValueForMissingStub: null); +} + +/// A class which mocks [BuildContext]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockBuildContext extends _i1.Mock implements _i6.BuildContext { + MockBuildContext() { + _i1.throwOnMissingStub(this); + } + + @override + _i6.Widget get widget => (super.noSuchMethod(Invocation.getter(#widget), + returnValue: _FakeWidget_5()) as _i6.Widget); + @override + bool get debugDoingBuild => (super + .noSuchMethod(Invocation.getter(#debugDoingBuild), returnValue: false) + as bool); + @override + _i6.InheritedWidget dependOnInheritedElement(_i6.InheritedElement? ancestor, + {Object? aspect}) => + (super.noSuchMethod( + Invocation.method( + #dependOnInheritedElement, [ancestor], {#aspect: aspect}), + returnValue: _FakeInheritedWidget_6()) as _i6.InheritedWidget); + @override + void visitAncestorElements(bool Function(_i6.Element)? visitor) => + super.noSuchMethod(Invocation.method(#visitAncestorElements, [visitor]), + returnValueForMissingStub: null); + @override + void visitChildElements(_i6.ElementVisitor? visitor) => + super.noSuchMethod(Invocation.method(#visitChildElements, [visitor]), + returnValueForMissingStub: null); + @override + _i5.DiagnosticsNode describeElement(String? name, + {_i5.DiagnosticsTreeStyle? style = + _i5.DiagnosticsTreeStyle.errorProperty}) => + (super.noSuchMethod( + Invocation.method(#describeElement, [name], {#style: style}), + returnValue: _FakeDiagnosticsNode_7()) as _i5.DiagnosticsNode); + @override + _i5.DiagnosticsNode describeWidget(String? name, + {_i5.DiagnosticsTreeStyle? style = + _i5.DiagnosticsTreeStyle.errorProperty}) => + (super.noSuchMethod( + Invocation.method(#describeWidget, [name], {#style: style}), + returnValue: _FakeDiagnosticsNode_7()) as _i5.DiagnosticsNode); + @override + List<_i5.DiagnosticsNode> describeMissingAncestor( + {Type? expectedAncestorType}) => + (super.noSuchMethod( + Invocation.method(#describeMissingAncestor, [], + {#expectedAncestorType: expectedAncestorType}), + returnValue: <_i5.DiagnosticsNode>[]) as List<_i5.DiagnosticsNode>); + @override + _i5.DiagnosticsNode describeOwnershipChain(String? name) => + (super.noSuchMethod(Invocation.method(#describeOwnershipChain, [name]), + returnValue: _FakeDiagnosticsNode_7()) as _i5.DiagnosticsNode); +} + +/// A class which mocks [BarChartPainter]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockBarChartPainter extends _i1.Mock implements _i9.BarChartPainter { + MockBarChartPainter() { + _i1.throwOnMissingStub(this); + } + + @override + void paint(_i6.BuildContext? context, _i10.CanvasWrapper? canvasWrapper, + _i11.PaintHolder<_i12.BarChartData>? holder) => + super.noSuchMethod( + Invocation.method(#paint, [context, canvasWrapper, holder]), + returnValueForMissingStub: null); + @override + List calculateGroupsX( + _i2.Size? viewSize, + List<_i12.BarChartGroupData>? barGroups, + _i12.BarChartAlignment? alignment, + _i11.PaintHolder<_i12.BarChartData>? holder) => + (super.noSuchMethod( + Invocation.method( + #calculateGroupsX, [viewSize, barGroups, alignment, holder]), + returnValue: []) as List); + @override + List<_i9.GroupBarsPosition> calculateGroupAndBarsPosition(_i2.Size? viewSize, + List? groupsX, List<_i12.BarChartGroupData>? barGroups) => + (super.noSuchMethod( + Invocation.method(#calculateGroupAndBarsPosition, + [viewSize, groupsX, barGroups]), + returnValue: <_i9.GroupBarsPosition>[]) + as List<_i9.GroupBarsPosition>); + @override + void drawBars( + _i10.CanvasWrapper? canvasWrapper, + List<_i9.GroupBarsPosition>? groupBarsPosition, + _i11.PaintHolder<_i12.BarChartData>? holder) => + super.noSuchMethod( + Invocation.method( + #drawBars, [canvasWrapper, groupBarsPosition, holder]), + returnValueForMissingStub: null); + @override + void drawTitles( + _i6.BuildContext? context, + _i10.CanvasWrapper? canvasWrapper, + List<_i9.GroupBarsPosition>? groupBarsPosition, + _i11.PaintHolder<_i12.BarChartData>? holder) => + super.noSuchMethod( + Invocation.method( + #drawTitles, [context, canvasWrapper, groupBarsPosition, holder]), + returnValueForMissingStub: null); + @override + void drawTouchTooltip( + _i6.BuildContext? context, + _i10.CanvasWrapper? canvasWrapper, + List<_i9.GroupBarsPosition>? groupPositions, + _i12.BarTouchTooltipData? tooltipData, + _i12.BarChartGroupData? showOnBarGroup, + int? barGroupIndex, + _i12.BarChartRodData? showOnRodData, + int? barRodIndex, + _i11.PaintHolder<_i12.BarChartData>? holder) => + super.noSuchMethod( + Invocation.method(#drawTouchTooltip, [ + context, + canvasWrapper, + groupPositions, + tooltipData, + showOnBarGroup, + barGroupIndex, + showOnRodData, + barRodIndex, + holder + ]), + returnValueForMissingStub: null); + @override + void drawStackItemBorderStroke( + _i10.CanvasWrapper? canvasWrapper, + _i12.BarChartRodStackItem? stackItem, + int? index, + int? rodStacksSize, + double? barThickSize, + _i2.RRect? barRRect, + _i2.Size? drawSize, + _i11.PaintHolder<_i12.BarChartData>? holder) => + super.noSuchMethod( + Invocation.method(#drawStackItemBorderStroke, [ + canvasWrapper, + stackItem, + index, + rodStacksSize, + barThickSize, + barRRect, + drawSize, + holder + ]), + returnValueForMissingStub: null); + @override + double getExtraNeededHorizontalSpace( + _i11.PaintHolder<_i12.BarChartData>? holder) => + (super.noSuchMethod( + Invocation.method(#getExtraNeededHorizontalSpace, [holder]), + returnValue: 0.0) as double); + @override + double getExtraNeededVerticalSpace( + _i11.PaintHolder<_i12.BarChartData>? holder) => + (super.noSuchMethod( + Invocation.method(#getExtraNeededVerticalSpace, [holder]), + returnValue: 0.0) as double); + @override + double getLeftOffsetDrawSize(_i11.PaintHolder<_i12.BarChartData>? holder) => + (super.noSuchMethod(Invocation.method(#getLeftOffsetDrawSize, [holder]), + returnValue: 0.0) as double); + @override + double getTopOffsetDrawSize(_i11.PaintHolder<_i12.BarChartData>? holder) => + (super.noSuchMethod(Invocation.method(#getTopOffsetDrawSize, [holder]), + returnValue: 0.0) as double); + @override + _i12.BarTouchedSpot? handleTouch(_i2.Offset? localPosition, + _i2.Size? viewSize, _i11.PaintHolder<_i12.BarChartData>? holder) => + (super.noSuchMethod(Invocation.method( + #handleTouch, [localPosition, viewSize, holder])) + as _i12.BarTouchedSpot?); + @override + void drawAxisTitles( + _i6.BuildContext? context, + _i10.CanvasWrapper? canvasWrapper, + _i11.PaintHolder<_i12.BarChartData>? holder) => + super.noSuchMethod( + Invocation.method(#drawAxisTitles, [context, canvasWrapper, holder]), + returnValueForMissingStub: null); + @override + void drawGrid(_i10.CanvasWrapper? canvasWrapper, + _i11.PaintHolder<_i12.BarChartData>? holder) => + super.noSuchMethod(Invocation.method(#drawGrid, [canvasWrapper, holder]), + returnValueForMissingStub: null); + @override + void drawBackground(_i10.CanvasWrapper? canvasWrapper, + _i11.PaintHolder<_i12.BarChartData>? holder) => + super.noSuchMethod( + Invocation.method(#drawBackground, [canvasWrapper, holder]), + returnValueForMissingStub: null); + @override + void drawRangeAnnotation(_i10.CanvasWrapper? canvasWrapper, + _i11.PaintHolder<_i12.BarChartData>? holder) => + super.noSuchMethod( + Invocation.method(#drawRangeAnnotation, [canvasWrapper, holder]), + returnValueForMissingStub: null); + @override + double getPixelX(double? spotX, _i2.Size? chartUsableSize, + _i11.PaintHolder<_i12.BarChartData>? holder) => + (super.noSuchMethod( + Invocation.method(#getPixelX, [spotX, chartUsableSize, holder]), + returnValue: 0.0) as double); + @override + double getPixelY(double? spotY, _i2.Size? chartUsableSize, + _i11.PaintHolder<_i12.BarChartData>? holder) => + (super.noSuchMethod( + Invocation.method(#getPixelY, [spotY, chartUsableSize, holder]), + returnValue: 0.0) as double); + @override + _i2.Size getChartUsableDrawSize( + _i2.Size? viewSize, _i11.PaintHolder<_i12.BarChartData>? holder) => + (super.noSuchMethod( + Invocation.method(#getChartUsableDrawSize, [viewSize, holder]), + returnValue: _FakeSize_8()) as _i2.Size); +} diff --git a/test/chart/data_pool.dart b/test/chart/data_pool.dart index ce033d83a..f704eaed6 100644 --- a/test/chart/data_pool.dart +++ b/test/chart/data_pool.dart @@ -65,6 +65,115 @@ class MockData { static const Offset offset4 = Offset(4, 4); static const Offset offset5 = Offset(5, 5); static const Offset offset6 = Offset(6, 6); + + static final LineChartBarData lineChartBarData1 = LineChartBarData( + show: true, + dashArray: [0, 1], + colors: [Colors.red, Colors.green], + colorStops: [0, 1], + spots: [ + flSpot1, + flSpot2, + ], + shadow: shadow1, + isStepLineChart: false, + aboveBarData: barAreaData1, + belowBarData: barAreaData2, + gradientFrom: const Offset(0, 0), + gradientTo: const Offset(1, 1), + barWidth: 12, + curveSmoothness: 12.0, + dotData: flDotData1, + isCurved: false, + isStrokeCapRound: true, + preventCurveOverShooting: false, + preventCurveOvershootingThreshold: 1.2, + showingIndicators: [0, 1], + ); + + static final LineChartBarData lineChartBarData2 = LineChartBarData( + show: true, + dashArray: [0, 1], + colors: [Colors.red, Colors.green], + colorStops: [0, 1], + spots: [ + flSpot1, + flSpot2, + ], + shadow: shadow2, + isStepLineChart: true, + lineChartStepData: lineChartStepData1, + aboveBarData: barAreaData1, + belowBarData: barAreaData2, + gradientFrom: const Offset(0, 0), + gradientTo: const Offset(1, 1), + barWidth: 12, + curveSmoothness: 12.0, + dotData: flDotData1, + isCurved: false, + isStrokeCapRound: true, + preventCurveOverShooting: false, + preventCurveOvershootingThreshold: 1.2, + showingIndicators: [0, 4], + ); + + static const RadarEntry radarEntry1 = RadarEntry(value: 11); + static const RadarEntry radarEntry2 = RadarEntry(value: 22); + static const RadarEntry radarEntry3 = RadarEntry(value: 33); + static final RadarDataSet radarDataSet1 = RadarDataSet( + dataEntries: [radarEntry1, radarEntry2, radarEntry3], + ); + static final RadarDataSet radarDataSet2 = RadarDataSet( + dataEntries: [radarEntry3, radarEntry1, radarEntry2], + ); + static final RadarTouchedSpot radarTouchedSpot = RadarTouchedSpot( + radarDataSet1, + 0, + radarEntry1, + 0, + flSpot1, + offset1, + ); + static final scatterSpot1 = ScatterSpot(1, 1); + static final scatterSpot2 = ScatterSpot(2, 2); + static final scatterSpot3 = ScatterSpot(3, 3); + + static final scatterTouchedSpot = ScatterTouchedSpot(scatterSpot1, 0); + + static final pieChartSectionData1 = PieChartSectionData(value: 12); + static final pieTouchedSection1 = PieTouchedSection( + pieChartSectionData1, + 0, + 12, + 33, + ); + + static final lineBarSpot1 = LineBarSpot( + lineChartBarData1, + 0, + lineChartBarData1.spots.first, + ); + static final lineBarSpot2 = LineBarSpot( + MockData.lineChartBarData1, + 1, + MockData.lineChartBarData1.spots.last, + ); + + static final lineTouchResponse1 = + LineTouchResponse([lineBarSpot1, lineBarSpot2]); + + static final barChartRodData1 = BarChartRodData(y: 11); + static final barChartRodData2 = BarChartRodData(y: 22); + static final barTouchedSpot = BarTouchedSpot( + BarChartGroupData(x: 0, barRods: [barChartRodData1, barChartRodData2]), + 0, + barChartRodData1, + 0, + null, + -1, + flSpot1, + offset1, + ); } final VerticalRangeAnnotation verticalRangeAnnotation1 = @@ -107,6 +216,7 @@ final FlLine flLine1Clone = FlLine(color: Colors.green, strokeWidth: 1, dashArray: [1, 2, 3]); bool checkToShowLine(double value) => true; + FlLine getDrawingLine(double value) => FlLine(); const FlSpot flSpot1 = FlSpot(1, 1); @@ -2097,6 +2207,7 @@ final PieChartData pieChartData1 = PieChartData( final PieChartData pieChartData1Clone = pieChartData1.copyWith(); bool gridCheckToShowLine(double value) => true; + FlLine gridGetDrawingLine(double value) => FlLine(); ScatterTooltipItem? scatterChartGetTooltipItems(ScatterSpot spots) { diff --git a/test/chart/line_chart/line_chart_renderer_test.dart b/test/chart/line_chart/line_chart_renderer_test.dart new file mode 100644 index 000000000..9e1b23823 --- /dev/null +++ b/test/chart/line_chart/line_chart_renderer_test.dart @@ -0,0 +1,105 @@ +import 'package:fl_chart/fl_chart.dart'; +import 'package:fl_chart/src/chart/line_chart/line_chart_painter.dart'; +import 'package:fl_chart/src/chart/line_chart/line_chart_renderer.dart'; +import 'package:fl_chart/src/utils/canvas_wrapper.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import '../data_pool.dart'; +import 'line_chart_renderer_test.mocks.dart'; + +@GenerateMocks([Canvas, PaintingContext, BuildContext, LineChartPainter]) +void main() { + group('LineChartRenderer', () { + final LineChartData data = LineChartData( + titlesData: FlTitlesData( + leftTitles: SideTitles(reservedSize: 12, margin: 8, showTitles: true), + rightTitles: SideTitles(reservedSize: 44, margin: 20, showTitles: true), + topTitles: SideTitles(showTitles: false), + bottomTitles: SideTitles(showTitles: false), + )); + + final LineChartData targetData = LineChartData( + titlesData: FlTitlesData( + leftTitles: SideTitles(reservedSize: 0, margin: 8, showTitles: true), + rightTitles: SideTitles(reservedSize: 0, margin: 20, showTitles: true), + topTitles: SideTitles(showTitles: false), + bottomTitles: SideTitles(showTitles: false), + )); + + const textScale = 4.0; + + MockBuildContext _mockBuildContext = MockBuildContext(); + RenderLineChart renderLineChart = RenderLineChart( + _mockBuildContext, + data, + targetData, + textScale, + ); + + MockLineChartPainter _mockPainter = MockLineChartPainter(); + MockPaintingContext _mockPaintingContext = MockPaintingContext(); + MockCanvas _mockCanvas = MockCanvas(); + Size _mockSize = const Size(44, 44); + when(_mockPaintingContext.canvas) + .thenAnswer((realInvocation) => _mockCanvas); + renderLineChart.mockTestSize = _mockSize; + renderLineChart.painter = _mockPainter; + + test('test 1 correct data set', () { + expect(renderLineChart.data == data, true); + expect(renderLineChart.data == targetData, false); + expect(renderLineChart.targetData == targetData, true); + expect(renderLineChart.textScale == textScale, true); + expect(renderLineChart.paintHolder.data == data, true); + expect(renderLineChart.paintHolder.targetData == targetData, true); + expect(renderLineChart.paintHolder.textScale == textScale, true); + }); + + test('test 2 check paint function', () { + renderLineChart.paint(_mockPaintingContext, const Offset(10, 10)); + verify(_mockCanvas.save()).called(1); + verify(_mockCanvas.translate(10, 10)).called(1); + final result = verify(_mockPainter.paint(any, captureAny, captureAny)); + expect(result.callCount, 1); + + final canvasWrapper = result.captured[0] as CanvasWrapper; + expect(canvasWrapper.size, const Size(44, 44)); + expect(canvasWrapper.canvas, _mockCanvas); + + final paintHolder = result.captured[1] as PaintHolder; + expect(paintHolder.data, data); + expect(paintHolder.targetData, targetData); + expect(paintHolder.textScale, textScale); + + verify(_mockCanvas.restore()).called(1); + }); + + test('test 3 check getResponseAtLocation function', () { + List> results = []; + when(_mockPainter.handleTouch(captureAny, captureAny, captureAny)) + .thenAnswer((inv) { + results.add({ + 'local_position': inv.positionalArguments[0] as Offset, + 'size': inv.positionalArguments[1] as Size, + 'paint_holder': (inv.positionalArguments[2] as PaintHolder), + }); + return MockData.lineTouchResponse1.lineBarSpots; + }); + final touchResponse = + renderLineChart.getResponseAtLocation(MockData.offset1); + expect( + touchResponse.lineBarSpots, + MockData.lineTouchResponse1.lineBarSpots, + ); + expect(results[0]['local_position'] as Offset, MockData.offset1); + expect(results[0]['size'] as Size, _mockSize); + final paintHolder = results[0]['paint_holder'] as PaintHolder; + expect(paintHolder.data, data); + expect(paintHolder.targetData, targetData); + expect(paintHolder.textScale, textScale); + }); + }); +} diff --git a/test/chart/line_chart/line_chart_renderer_test.mocks.dart b/test/chart/line_chart/line_chart_renderer_test.mocks.dart new file mode 100644 index 000000000..d885c2cee --- /dev/null +++ b/test/chart/line_chart/line_chart_renderer_test.mocks.dart @@ -0,0 +1,771 @@ +// Mocks generated by Mockito 5.0.17 from annotations +// in fl_chart/test/chart/line_chart/line_chart_renderer_test.dart. +// Do not manually edit this file. + +import 'dart:typed_data' as _i7; +import 'dart:ui' as _i2; + +import 'package:fl_chart/fl_chart.dart' as _i12; +import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart' + as _i11; +import 'package:fl_chart/src/chart/line_chart/line_chart_painter.dart' as _i9; +import 'package:fl_chart/src/utils/canvas_wrapper.dart' as _i10; +import 'package:flutter/foundation.dart' as _i5; +import 'package:flutter/material.dart' as _i6; +import 'package:flutter/rendering.dart' as _i3; +import 'package:flutter/src/rendering/layer.dart' as _i4; +import 'package:mockito/mockito.dart' as _i1; +import 'package:vector_math/vector_math_64.dart' as _i8; + +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types + +class _FakeRect_0 extends _i1.Fake implements _i2.Rect {} + +class _FakeCanvas_1 extends _i1.Fake implements _i2.Canvas {} + +class _FakePaintingContext_2 extends _i1.Fake implements _i3.PaintingContext {} + +class _FakeColorFilterLayer_3 extends _i1.Fake implements _i4.ColorFilterLayer { + @override + String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakeOpacityLayer_4 extends _i1.Fake implements _i4.OpacityLayer { + @override + String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakeWidget_5 extends _i1.Fake implements _i6.Widget { + @override + String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakeInheritedWidget_6 extends _i1.Fake implements _i6.InheritedWidget { + @override + String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakeDiagnosticsNode_7 extends _i1.Fake implements _i5.DiagnosticsNode { + @override + String toString( + {_i5.TextTreeConfiguration? parentConfiguration, + _i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakePath_8 extends _i1.Fake implements _i2.Path {} + +class _FakeSize_9 extends _i1.Fake implements _i2.Size {} + +/// A class which mocks [Canvas]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCanvas extends _i1.Mock implements _i2.Canvas { + MockCanvas() { + _i1.throwOnMissingStub(this); + } + + @override + void save() => super.noSuchMethod(Invocation.method(#save, []), + returnValueForMissingStub: null); + @override + void saveLayer(_i2.Rect? bounds, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#saveLayer, [bounds, paint]), + returnValueForMissingStub: null); + @override + void restore() => super.noSuchMethod(Invocation.method(#restore, []), + returnValueForMissingStub: null); + @override + int getSaveCount() => + (super.noSuchMethod(Invocation.method(#getSaveCount, []), returnValue: 0) + as int); + @override + void translate(double? dx, double? dy) => + super.noSuchMethod(Invocation.method(#translate, [dx, dy]), + returnValueForMissingStub: null); + @override + void scale(double? sx, [double? sy]) => + super.noSuchMethod(Invocation.method(#scale, [sx, sy]), + returnValueForMissingStub: null); + @override + void rotate(double? radians) => + super.noSuchMethod(Invocation.method(#rotate, [radians]), + returnValueForMissingStub: null); + @override + void skew(double? sx, double? sy) => + super.noSuchMethod(Invocation.method(#skew, [sx, sy]), + returnValueForMissingStub: null); + @override + void transform(_i7.Float64List? matrix4) => + super.noSuchMethod(Invocation.method(#transform, [matrix4]), + returnValueForMissingStub: null); + @override + void clipRect(_i2.Rect? rect, + {_i2.ClipOp? clipOp = _i2.ClipOp.intersect, + bool? doAntiAlias = true}) => + super.noSuchMethod( + Invocation.method( + #clipRect, [rect], {#clipOp: clipOp, #doAntiAlias: doAntiAlias}), + returnValueForMissingStub: null); + @override + void clipRRect(_i2.RRect? rrect, {bool? doAntiAlias = true}) => + super.noSuchMethod( + Invocation.method(#clipRRect, [rrect], {#doAntiAlias: doAntiAlias}), + returnValueForMissingStub: null); + @override + void clipPath(_i2.Path? path, {bool? doAntiAlias = true}) => + super.noSuchMethod( + Invocation.method(#clipPath, [path], {#doAntiAlias: doAntiAlias}), + returnValueForMissingStub: null); + @override + void drawColor(_i2.Color? color, _i2.BlendMode? blendMode) => + super.noSuchMethod(Invocation.method(#drawColor, [color, blendMode]), + returnValueForMissingStub: null); + @override + void drawLine(_i2.Offset? p1, _i2.Offset? p2, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawLine, [p1, p2, paint]), + returnValueForMissingStub: null); + @override + void drawPaint(_i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawPaint, [paint]), + returnValueForMissingStub: null); + @override + void drawRect(_i2.Rect? rect, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawRect, [rect, paint]), + returnValueForMissingStub: null); + @override + void drawRRect(_i2.RRect? rrect, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawRRect, [rrect, paint]), + returnValueForMissingStub: null); + @override + void drawDRRect(_i2.RRect? outer, _i2.RRect? inner, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawDRRect, [outer, inner, paint]), + returnValueForMissingStub: null); + @override + void drawOval(_i2.Rect? rect, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawOval, [rect, paint]), + returnValueForMissingStub: null); + @override + void drawCircle(_i2.Offset? c, double? radius, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawCircle, [c, radius, paint]), + returnValueForMissingStub: null); + @override + void drawArc(_i2.Rect? rect, double? startAngle, double? sweepAngle, + bool? useCenter, _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method( + #drawArc, [rect, startAngle, sweepAngle, useCenter, paint]), + returnValueForMissingStub: null); + @override + void drawPath(_i2.Path? path, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawPath, [path, paint]), + returnValueForMissingStub: null); + @override + void drawImage(_i2.Image? image, _i2.Offset? offset, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawImage, [image, offset, paint]), + returnValueForMissingStub: null); + @override + void drawImageRect( + _i2.Image? image, _i2.Rect? src, _i2.Rect? dst, _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawImageRect, [image, src, dst, paint]), + returnValueForMissingStub: null); + @override + void drawImageNine(_i2.Image? image, _i2.Rect? center, _i2.Rect? dst, + _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawImageNine, [image, center, dst, paint]), + returnValueForMissingStub: null); + @override + void drawPicture(_i2.Picture? picture) => + super.noSuchMethod(Invocation.method(#drawPicture, [picture]), + returnValueForMissingStub: null); + @override + void drawParagraph(_i2.Paragraph? paragraph, _i2.Offset? offset) => + super.noSuchMethod(Invocation.method(#drawParagraph, [paragraph, offset]), + returnValueForMissingStub: null); + @override + void drawPoints(_i2.PointMode? pointMode, List<_i2.Offset>? points, + _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawPoints, [pointMode, points, paint]), + returnValueForMissingStub: null); + @override + void drawRawPoints(_i2.PointMode? pointMode, _i7.Float32List? points, + _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawRawPoints, [pointMode, points, paint]), + returnValueForMissingStub: null); + @override + void drawVertices( + _i2.Vertices? vertices, _i2.BlendMode? blendMode, _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawVertices, [vertices, blendMode, paint]), + returnValueForMissingStub: null); + @override + void drawAtlas( + _i2.Image? atlas, + List<_i2.RSTransform>? transforms, + List<_i2.Rect>? rects, + List<_i2.Color>? colors, + _i2.BlendMode? blendMode, + _i2.Rect? cullRect, + _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawAtlas, + [atlas, transforms, rects, colors, blendMode, cullRect, paint]), + returnValueForMissingStub: null); + @override + void drawRawAtlas( + _i2.Image? atlas, + _i7.Float32List? rstTransforms, + _i7.Float32List? rects, + _i7.Int32List? colors, + _i2.BlendMode? blendMode, + _i2.Rect? cullRect, + _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawRawAtlas, [ + atlas, + rstTransforms, + rects, + colors, + blendMode, + cullRect, + paint + ]), + returnValueForMissingStub: null); + @override + void drawShadow(_i2.Path? path, _i2.Color? color, double? elevation, + bool? transparentOccluder) => + super.noSuchMethod( + Invocation.method( + #drawShadow, [path, color, elevation, transparentOccluder]), + returnValueForMissingStub: null); +} + +/// A class which mocks [PaintingContext]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockPaintingContext extends _i1.Mock implements _i3.PaintingContext { + MockPaintingContext() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.Rect get estimatedBounds => + (super.noSuchMethod(Invocation.getter(#estimatedBounds), + returnValue: _FakeRect_0()) as _i2.Rect); + @override + _i2.Canvas get canvas => (super.noSuchMethod(Invocation.getter(#canvas), + returnValue: _FakeCanvas_1()) as _i2.Canvas); + @override + void paintChild(_i3.RenderObject? child, _i2.Offset? offset) => + super.noSuchMethod(Invocation.method(#paintChild, [child, offset]), + returnValueForMissingStub: null); + @override + void appendLayer(_i4.Layer? layer) => + super.noSuchMethod(Invocation.method(#appendLayer, [layer]), + returnValueForMissingStub: null); + @override + void stopRecordingIfNeeded() => + super.noSuchMethod(Invocation.method(#stopRecordingIfNeeded, []), + returnValueForMissingStub: null); + @override + void setIsComplexHint() => + super.noSuchMethod(Invocation.method(#setIsComplexHint, []), + returnValueForMissingStub: null); + @override + void setWillChangeHint() => + super.noSuchMethod(Invocation.method(#setWillChangeHint, []), + returnValueForMissingStub: null); + @override + void addLayer(_i4.Layer? layer) => + super.noSuchMethod(Invocation.method(#addLayer, [layer]), + returnValueForMissingStub: null); + @override + void pushLayer(_i4.ContainerLayer? childLayer, + _i3.PaintingContextCallback? painter, _i2.Offset? offset, + {_i2.Rect? childPaintBounds}) => + super.noSuchMethod( + Invocation.method(#pushLayer, [childLayer, painter, offset], + {#childPaintBounds: childPaintBounds}), + returnValueForMissingStub: null); + @override + _i3.PaintingContext createChildContext( + _i4.ContainerLayer? childLayer, _i2.Rect? bounds) => + (super.noSuchMethod( + Invocation.method(#createChildContext, [childLayer, bounds]), + returnValue: _FakePaintingContext_2()) as _i3.PaintingContext); + @override + _i4.ClipRectLayer? pushClipRect(bool? needsCompositing, _i2.Offset? offset, + _i2.Rect? clipRect, _i3.PaintingContextCallback? painter, + {_i2.Clip? clipBehavior = _i2.Clip.hardEdge, + _i4.ClipRectLayer? oldLayer}) => + (super.noSuchMethod(Invocation.method(#pushClipRect, [ + needsCompositing, + offset, + clipRect, + painter + ], { + #clipBehavior: clipBehavior, + #oldLayer: oldLayer + })) as _i4.ClipRectLayer?); + @override + _i4.ClipRRectLayer? pushClipRRect( + bool? needsCompositing, + _i2.Offset? offset, + _i2.Rect? bounds, + _i2.RRect? clipRRect, + _i3.PaintingContextCallback? painter, + {_i2.Clip? clipBehavior = _i2.Clip.antiAlias, + _i4.ClipRRectLayer? oldLayer}) => + (super.noSuchMethod(Invocation.method(#pushClipRRect, [ + needsCompositing, + offset, + bounds, + clipRRect, + painter + ], { + #clipBehavior: clipBehavior, + #oldLayer: oldLayer + })) as _i4.ClipRRectLayer?); + @override + _i4.ClipPathLayer? pushClipPath( + bool? needsCompositing, + _i2.Offset? offset, + _i2.Rect? bounds, + _i2.Path? clipPath, + _i3.PaintingContextCallback? painter, + {_i2.Clip? clipBehavior = _i2.Clip.antiAlias, + _i4.ClipPathLayer? oldLayer}) => + (super.noSuchMethod(Invocation.method(#pushClipPath, [ + needsCompositing, + offset, + bounds, + clipPath, + painter + ], { + #clipBehavior: clipBehavior, + #oldLayer: oldLayer + })) as _i4.ClipPathLayer?); + @override + _i4.ColorFilterLayer pushColorFilter(_i2.Offset? offset, + _i2.ColorFilter? colorFilter, _i3.PaintingContextCallback? painter, + {_i4.ColorFilterLayer? oldLayer}) => + (super.noSuchMethod( + Invocation.method(#pushColorFilter, [offset, colorFilter, painter], + {#oldLayer: oldLayer}), + returnValue: _FakeColorFilterLayer_3()) as _i4.ColorFilterLayer); + @override + _i4.TransformLayer? pushTransform(bool? needsCompositing, _i2.Offset? offset, + _i8.Matrix4? transform, _i3.PaintingContextCallback? painter, + {_i4.TransformLayer? oldLayer}) => + (super.noSuchMethod(Invocation.method( + #pushTransform, + [needsCompositing, offset, transform, painter], + {#oldLayer: oldLayer})) as _i4.TransformLayer?); + @override + _i4.OpacityLayer pushOpacity( + _i2.Offset? offset, int? alpha, _i3.PaintingContextCallback? painter, + {_i4.OpacityLayer? oldLayer}) => + (super.noSuchMethod( + Invocation.method( + #pushOpacity, [offset, alpha, painter], {#oldLayer: oldLayer}), + returnValue: _FakeOpacityLayer_4()) as _i4.OpacityLayer); + @override + void clipPathAndPaint(_i2.Path? path, _i2.Clip? clipBehavior, + _i2.Rect? bounds, _i2.VoidCallback? painter) => + super.noSuchMethod( + Invocation.method( + #clipPathAndPaint, [path, clipBehavior, bounds, painter]), + returnValueForMissingStub: null); + @override + void clipRRectAndPaint(_i2.RRect? rrect, _i2.Clip? clipBehavior, + _i2.Rect? bounds, _i2.VoidCallback? painter) => + super.noSuchMethod( + Invocation.method( + #clipRRectAndPaint, [rrect, clipBehavior, bounds, painter]), + returnValueForMissingStub: null); + @override + void clipRectAndPaint(_i2.Rect? rect, _i2.Clip? clipBehavior, + _i2.Rect? bounds, _i2.VoidCallback? painter) => + super.noSuchMethod( + Invocation.method( + #clipRectAndPaint, [rect, clipBehavior, bounds, painter]), + returnValueForMissingStub: null); +} + +/// A class which mocks [BuildContext]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockBuildContext extends _i1.Mock implements _i6.BuildContext { + MockBuildContext() { + _i1.throwOnMissingStub(this); + } + + @override + _i6.Widget get widget => (super.noSuchMethod(Invocation.getter(#widget), + returnValue: _FakeWidget_5()) as _i6.Widget); + @override + bool get debugDoingBuild => (super + .noSuchMethod(Invocation.getter(#debugDoingBuild), returnValue: false) + as bool); + @override + _i6.InheritedWidget dependOnInheritedElement(_i6.InheritedElement? ancestor, + {Object? aspect}) => + (super.noSuchMethod( + Invocation.method( + #dependOnInheritedElement, [ancestor], {#aspect: aspect}), + returnValue: _FakeInheritedWidget_6()) as _i6.InheritedWidget); + @override + void visitAncestorElements(bool Function(_i6.Element)? visitor) => + super.noSuchMethod(Invocation.method(#visitAncestorElements, [visitor]), + returnValueForMissingStub: null); + @override + void visitChildElements(_i6.ElementVisitor? visitor) => + super.noSuchMethod(Invocation.method(#visitChildElements, [visitor]), + returnValueForMissingStub: null); + @override + _i5.DiagnosticsNode describeElement(String? name, + {_i5.DiagnosticsTreeStyle? style = + _i5.DiagnosticsTreeStyle.errorProperty}) => + (super.noSuchMethod( + Invocation.method(#describeElement, [name], {#style: style}), + returnValue: _FakeDiagnosticsNode_7()) as _i5.DiagnosticsNode); + @override + _i5.DiagnosticsNode describeWidget(String? name, + {_i5.DiagnosticsTreeStyle? style = + _i5.DiagnosticsTreeStyle.errorProperty}) => + (super.noSuchMethod( + Invocation.method(#describeWidget, [name], {#style: style}), + returnValue: _FakeDiagnosticsNode_7()) as _i5.DiagnosticsNode); + @override + List<_i5.DiagnosticsNode> describeMissingAncestor( + {Type? expectedAncestorType}) => + (super.noSuchMethod( + Invocation.method(#describeMissingAncestor, [], + {#expectedAncestorType: expectedAncestorType}), + returnValue: <_i5.DiagnosticsNode>[]) as List<_i5.DiagnosticsNode>); + @override + _i5.DiagnosticsNode describeOwnershipChain(String? name) => + (super.noSuchMethod(Invocation.method(#describeOwnershipChain, [name]), + returnValue: _FakeDiagnosticsNode_7()) as _i5.DiagnosticsNode); +} + +/// A class which mocks [LineChartPainter]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockLineChartPainter extends _i1.Mock implements _i9.LineChartPainter { + MockLineChartPainter() { + _i1.throwOnMissingStub(this); + } + + @override + void paint(_i6.BuildContext? context, _i10.CanvasWrapper? canvasWrapper, + _i11.PaintHolder<_i12.LineChartData>? holder) => + super.noSuchMethod( + Invocation.method(#paint, [context, canvasWrapper, holder]), + returnValueForMissingStub: null); + @override + void clipToBorder(_i10.CanvasWrapper? canvasWrapper, + _i11.PaintHolder<_i12.LineChartData>? holder) => + super.noSuchMethod( + Invocation.method(#clipToBorder, [canvasWrapper, holder]), + returnValueForMissingStub: null); + @override + void drawBarLine( + _i10.CanvasWrapper? canvasWrapper, + _i12.LineChartBarData? barData, + _i11.PaintHolder<_i12.LineChartData>? holder) => + super.noSuchMethod( + Invocation.method(#drawBarLine, [canvasWrapper, barData, holder]), + returnValueForMissingStub: null); + @override + void drawBetweenBarsArea( + _i10.CanvasWrapper? canvasWrapper, + _i12.LineChartData? data, + _i12.BetweenBarsData? betweenBarsData, + _i11.PaintHolder<_i12.LineChartData>? holder) => + super.noSuchMethod( + Invocation.method(#drawBetweenBarsArea, + [canvasWrapper, data, betweenBarsData, holder]), + returnValueForMissingStub: null); + @override + void drawDots( + _i10.CanvasWrapper? canvasWrapper, + _i12.LineChartBarData? barData, + _i11.PaintHolder<_i12.LineChartData>? holder) => + super.noSuchMethod( + Invocation.method(#drawDots, [canvasWrapper, barData, holder]), + returnValueForMissingStub: null); + @override + void drawTouchedSpotsIndicator( + _i10.CanvasWrapper? canvasWrapper, + _i12.LineChartBarData? barData, + _i11.PaintHolder<_i12.LineChartData>? holder) => + super.noSuchMethod( + Invocation.method( + #drawTouchedSpotsIndicator, [canvasWrapper, barData, holder]), + returnValueForMissingStub: null); + @override + _i2.Path generateBarPath( + _i2.Size? viewSize, + _i12.LineChartBarData? barData, + List<_i12.FlSpot>? barSpots, + _i11.PaintHolder<_i12.LineChartData>? holder, + {_i2.Path? appendToPath}) => + (super.noSuchMethod( + Invocation.method( + #generateBarPath, + [viewSize, barData, barSpots, holder], + {#appendToPath: appendToPath}), + returnValue: _FakePath_8()) as _i2.Path); + @override + _i2.Path generateNormalBarPath( + _i2.Size? viewSize, + _i12.LineChartBarData? barData, + List<_i12.FlSpot>? barSpots, + _i11.PaintHolder<_i12.LineChartData>? holder, + {_i2.Path? appendToPath}) => + (super.noSuchMethod( + Invocation.method( + #generateNormalBarPath, + [viewSize, barData, barSpots, holder], + {#appendToPath: appendToPath}), + returnValue: _FakePath_8()) as _i2.Path); + @override + _i2.Path generateStepBarPath( + _i2.Size? viewSize, + _i12.LineChartBarData? barData, + List<_i12.FlSpot>? barSpots, + _i11.PaintHolder<_i12.LineChartData>? holder, + {_i2.Path? appendToPath}) => + (super.noSuchMethod( + Invocation.method( + #generateStepBarPath, + [viewSize, barData, barSpots, holder], + {#appendToPath: appendToPath}), + returnValue: _FakePath_8()) as _i2.Path); + @override + _i2.Path generateBelowBarPath( + _i2.Size? viewSize, + _i12.LineChartBarData? barData, + _i2.Path? barPath, + List<_i12.FlSpot>? barSpots, + _i11.PaintHolder<_i12.LineChartData>? holder, + {bool? fillCompletely = false}) => + (super.noSuchMethod( + Invocation.method( + #generateBelowBarPath, + [viewSize, barData, barPath, barSpots, holder], + {#fillCompletely: fillCompletely}), + returnValue: _FakePath_8()) as _i2.Path); + @override + _i2.Path generateAboveBarPath( + _i2.Size? viewSize, + _i12.LineChartBarData? barData, + _i2.Path? barPath, + List<_i12.FlSpot>? barSpots, + _i11.PaintHolder<_i12.LineChartData>? holder, + {bool? fillCompletely = false}) => + (super.noSuchMethod( + Invocation.method( + #generateAboveBarPath, + [viewSize, barData, barPath, barSpots, holder], + {#fillCompletely: fillCompletely}), + returnValue: _FakePath_8()) as _i2.Path); + @override + void drawBelowBar( + _i10.CanvasWrapper? canvasWrapper, + _i2.Path? belowBarPath, + _i2.Path? filledAboveBarPath, + _i11.PaintHolder<_i12.LineChartData>? holder, + _i12.LineChartBarData? barData) => + super.noSuchMethod( + Invocation.method(#drawBelowBar, [ + canvasWrapper, + belowBarPath, + filledAboveBarPath, + holder, + barData + ]), + returnValueForMissingStub: null); + @override + void drawAboveBar( + _i10.CanvasWrapper? canvasWrapper, + _i2.Path? aboveBarPath, + _i2.Path? filledBelowBarPath, + _i11.PaintHolder<_i12.LineChartData>? holder, + _i12.LineChartBarData? barData) => + super.noSuchMethod( + Invocation.method(#drawAboveBar, [ + canvasWrapper, + aboveBarPath, + filledBelowBarPath, + holder, + barData + ]), + returnValueForMissingStub: null); + @override + void drawBetweenBar( + _i10.CanvasWrapper? canvasWrapper, + _i2.Path? aboveBarPath, + _i12.BetweenBarsData? betweenBarsData, + _i11.PaintHolder<_i12.LineChartData>? holder) => + super.noSuchMethod( + Invocation.method(#drawBetweenBar, + [canvasWrapper, aboveBarPath, betweenBarsData, holder]), + returnValueForMissingStub: null); + @override + void drawBarShadow(_i10.CanvasWrapper? canvasWrapper, _i2.Path? barPath, + _i12.LineChartBarData? barData) => + super.noSuchMethod( + Invocation.method(#drawBarShadow, [canvasWrapper, barPath, barData]), + returnValueForMissingStub: null); + @override + void drawBar( + _i10.CanvasWrapper? canvasWrapper, + _i2.Path? barPath, + _i12.LineChartBarData? barData, + _i11.PaintHolder<_i12.LineChartData>? holder) => + super.noSuchMethod( + Invocation.method( + #drawBar, [canvasWrapper, barPath, barData, holder]), + returnValueForMissingStub: null); + @override + void drawTitles(_i6.BuildContext? context, _i10.CanvasWrapper? canvasWrapper, + _i11.PaintHolder<_i12.LineChartData>? holder) => + super.noSuchMethod( + Invocation.method(#drawTitles, [context, canvasWrapper, holder]), + returnValueForMissingStub: null); + @override + void drawExtraLines( + _i6.BuildContext? context, + _i10.CanvasWrapper? canvasWrapper, + _i11.PaintHolder<_i12.LineChartData>? holder) => + super.noSuchMethod( + Invocation.method(#drawExtraLines, [context, canvasWrapper, holder]), + returnValueForMissingStub: null); + @override + void drawTouchTooltip( + _i6.BuildContext? context, + _i10.CanvasWrapper? canvasWrapper, + _i12.LineTouchTooltipData? tooltipData, + _i12.FlSpot? showOnSpot, + _i12.ShowingTooltipIndicators? showingTooltipSpots, + _i11.PaintHolder<_i12.LineChartData>? holder) => + super.noSuchMethod( + Invocation.method(#drawTouchTooltip, [ + context, + canvasWrapper, + tooltipData, + showOnSpot, + showingTooltipSpots, + holder + ]), + returnValueForMissingStub: null); + @override + double getBarLineXLength( + _i12.LineChartBarData? barData, + _i2.Size? chartUsableSize, + _i11.PaintHolder<_i12.LineChartData>? holder) => + (super.noSuchMethod( + Invocation.method( + #getBarLineXLength, [barData, chartUsableSize, holder]), + returnValue: 0.0) as double); + @override + double getExtraNeededHorizontalSpace( + _i11.PaintHolder<_i12.LineChartData>? holder) => + (super.noSuchMethod( + Invocation.method(#getExtraNeededHorizontalSpace, [holder]), + returnValue: 0.0) as double); + @override + double getExtraNeededVerticalSpace( + _i11.PaintHolder<_i12.LineChartData>? holder) => + (super.noSuchMethod( + Invocation.method(#getExtraNeededVerticalSpace, [holder]), + returnValue: 0.0) as double); + @override + double getLeftOffsetDrawSize(_i11.PaintHolder<_i12.LineChartData>? holder) => + (super.noSuchMethod(Invocation.method(#getLeftOffsetDrawSize, [holder]), + returnValue: 0.0) as double); + @override + double getTopOffsetDrawSize(_i11.PaintHolder<_i12.LineChartData>? holder) => + (super.noSuchMethod(Invocation.method(#getTopOffsetDrawSize, [holder]), + returnValue: 0.0) as double); + @override + List<_i12.LineBarSpot>? handleTouch(_i2.Offset? localPosition, _i2.Size? size, + _i11.PaintHolder<_i12.LineChartData>? holder) => + (super.noSuchMethod( + Invocation.method(#handleTouch, [localPosition, size, holder])) + as List<_i12.LineBarSpot>?); + @override + _i12.LineBarSpot? getNearestTouchedSpot( + _i2.Size? viewSize, + _i2.Offset? touchedPoint, + _i12.LineChartBarData? barData, + int? barDataPosition, + _i11.PaintHolder<_i12.LineChartData>? holder) => + (super.noSuchMethod(Invocation.method(#getNearestTouchedSpot, [ + viewSize, + touchedPoint, + barData, + barDataPosition, + holder + ])) as _i12.LineBarSpot?); + @override + void drawAxisTitles( + _i6.BuildContext? context, + _i10.CanvasWrapper? canvasWrapper, + _i11.PaintHolder<_i12.LineChartData>? holder) => + super.noSuchMethod( + Invocation.method(#drawAxisTitles, [context, canvasWrapper, holder]), + returnValueForMissingStub: null); + @override + void drawGrid(_i10.CanvasWrapper? canvasWrapper, + _i11.PaintHolder<_i12.LineChartData>? holder) => + super.noSuchMethod(Invocation.method(#drawGrid, [canvasWrapper, holder]), + returnValueForMissingStub: null); + @override + void drawBackground(_i10.CanvasWrapper? canvasWrapper, + _i11.PaintHolder<_i12.LineChartData>? holder) => + super.noSuchMethod( + Invocation.method(#drawBackground, [canvasWrapper, holder]), + returnValueForMissingStub: null); + @override + void drawRangeAnnotation(_i10.CanvasWrapper? canvasWrapper, + _i11.PaintHolder<_i12.LineChartData>? holder) => + super.noSuchMethod( + Invocation.method(#drawRangeAnnotation, [canvasWrapper, holder]), + returnValueForMissingStub: null); + @override + double getPixelX(double? spotX, _i2.Size? chartUsableSize, + _i11.PaintHolder<_i12.LineChartData>? holder) => + (super.noSuchMethod( + Invocation.method(#getPixelX, [spotX, chartUsableSize, holder]), + returnValue: 0.0) as double); + @override + double getPixelY(double? spotY, _i2.Size? chartUsableSize, + _i11.PaintHolder<_i12.LineChartData>? holder) => + (super.noSuchMethod( + Invocation.method(#getPixelY, [spotY, chartUsableSize, holder]), + returnValue: 0.0) as double); + @override + _i2.Size getChartUsableDrawSize( + _i2.Size? viewSize, _i11.PaintHolder<_i12.LineChartData>? holder) => + (super.noSuchMethod( + Invocation.method(#getChartUsableDrawSize, [viewSize, holder]), + returnValue: _FakeSize_9()) as _i2.Size); +} diff --git a/test/chart/pie_chart/pie_chart_renderer_test.dart b/test/chart/pie_chart/pie_chart_renderer_test.dart new file mode 100644 index 000000000..57e4adc24 --- /dev/null +++ b/test/chart/pie_chart/pie_chart_renderer_test.dart @@ -0,0 +1,90 @@ +import 'package:fl_chart/fl_chart.dart'; +import 'package:fl_chart/src/chart/pie_chart/pie_chart_painter.dart'; +import 'package:fl_chart/src/chart/pie_chart/pie_chart_renderer.dart'; +import 'package:fl_chart/src/utils/canvas_wrapper.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import '../data_pool.dart'; +import 'pie_chart_renderer_test.mocks.dart'; + +@GenerateMocks([Canvas, PaintingContext, BuildContext, PieChartPainter]) +void main() { + group('PieChartRenderer', () { + final PieChartData data = PieChartData(); + + final PieChartData targetData = PieChartData(centerSpaceRadius: 12); + + const textScale = 4.0; + + MockBuildContext _mockBuildContext = MockBuildContext(); + RenderPieChart renderBarChart = RenderPieChart( + _mockBuildContext, + data, + targetData, + textScale, + ); + + MockPieChartPainter _mockPainter = MockPieChartPainter(); + MockPaintingContext _mockPaintingContext = MockPaintingContext(); + MockCanvas _mockCanvas = MockCanvas(); + Size _mockSize = const Size(44, 44); + when(_mockPaintingContext.canvas) + .thenAnswer((realInvocation) => _mockCanvas); + renderBarChart.mockTestSize = _mockSize; + renderBarChart.painter = _mockPainter; + + test('test 1 correct data set', () { + expect(renderBarChart.data == data, true); + expect(renderBarChart.data == targetData, false); + expect(renderBarChart.targetData == targetData, true); + expect(renderBarChart.textScale == textScale, true); + expect(renderBarChart.paintHolder.data == data, true); + expect(renderBarChart.paintHolder.targetData == targetData, true); + expect(renderBarChart.paintHolder.textScale == textScale, true); + }); + + test('test 2 check paint function', () { + renderBarChart.paint(_mockPaintingContext, const Offset(10, 10)); + verify(_mockCanvas.save()).called(1); + verify(_mockCanvas.translate(10, 10)).called(1); + final result = verify(_mockPainter.paint(any, captureAny, captureAny)); + expect(result.callCount, 1); + + final canvasWrapper = result.captured[0] as CanvasWrapper; + expect(canvasWrapper.size, const Size(44, 44)); + expect(canvasWrapper.canvas, _mockCanvas); + + final paintHolder = result.captured[1] as PaintHolder; + expect(paintHolder.data, data); + expect(paintHolder.targetData, targetData); + expect(paintHolder.textScale, textScale); + + verify(_mockCanvas.restore()).called(1); + }); + + test('test 3 check getResponseAtLocation function', () { + List> results = []; + when(_mockPainter.handleTouch(captureAny, captureAny, captureAny)) + .thenAnswer((inv) { + results.add({ + 'local_position': inv.positionalArguments[0] as Offset, + 'size': inv.positionalArguments[1] as Size, + 'paint_holder': (inv.positionalArguments[2] as PaintHolder), + }); + return MockData.pieTouchedSection1; + }); + final touchResponse = + renderBarChart.getResponseAtLocation(MockData.offset1); + expect(touchResponse.touchedSection, MockData.pieTouchedSection1); + expect(results[0]['local_position'] as Offset, MockData.offset1); + expect(results[0]['size'] as Size, _mockSize); + final paintHolder = results[0]['paint_holder'] as PaintHolder; + expect(paintHolder.data, data); + expect(paintHolder.targetData, targetData); + expect(paintHolder.textScale, textScale); + }); + }); +} diff --git a/test/chart/pie_chart/pie_chart_renderer_test.mocks.dart b/test/chart/pie_chart/pie_chart_renderer_test.mocks.dart new file mode 100644 index 000000000..1a74aeb06 --- /dev/null +++ b/test/chart/pie_chart/pie_chart_renderer_test.mocks.dart @@ -0,0 +1,598 @@ +// Mocks generated by Mockito 5.0.17 from annotations +// in fl_chart/test/chart/pie_chart/pie_chart_renderer_test.dart. +// Do not manually edit this file. + +import 'dart:typed_data' as _i8; +import 'dart:ui' as _i2; + +import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart' + as _i12; +import 'package:fl_chart/src/chart/base/line.dart' as _i13; +import 'package:fl_chart/src/chart/pie_chart/pie_chart_data.dart' as _i7; +import 'package:fl_chart/src/chart/pie_chart/pie_chart_painter.dart' as _i10; +import 'package:fl_chart/src/utils/canvas_wrapper.dart' as _i11; +import 'package:flutter/foundation.dart' as _i5; +import 'package:flutter/rendering.dart' as _i3; +import 'package:flutter/src/rendering/layer.dart' as _i4; +import 'package:flutter/widgets.dart' as _i6; +import 'package:mockito/mockito.dart' as _i1; +import 'package:vector_math/vector_math_64.dart' as _i9; + +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types + +class _FakeRect_0 extends _i1.Fake implements _i2.Rect {} + +class _FakeCanvas_1 extends _i1.Fake implements _i2.Canvas {} + +class _FakePaintingContext_2 extends _i1.Fake implements _i3.PaintingContext {} + +class _FakeColorFilterLayer_3 extends _i1.Fake implements _i4.ColorFilterLayer { + @override + String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakeOpacityLayer_4 extends _i1.Fake implements _i4.OpacityLayer { + @override + String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakeWidget_5 extends _i1.Fake implements _i6.Widget { + @override + String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakeInheritedWidget_6 extends _i1.Fake implements _i6.InheritedWidget { + @override + String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakeDiagnosticsNode_7 extends _i1.Fake implements _i5.DiagnosticsNode { + @override + String toString( + {_i5.TextTreeConfiguration? parentConfiguration, + _i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakePath_8 extends _i1.Fake implements _i2.Path {} + +class _FakePieTouchedSection_9 extends _i1.Fake + implements _i7.PieTouchedSection {} + +class _FakeSize_10 extends _i1.Fake implements _i2.Size {} + +/// A class which mocks [Canvas]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCanvas extends _i1.Mock implements _i2.Canvas { + MockCanvas() { + _i1.throwOnMissingStub(this); + } + + @override + void save() => super.noSuchMethod(Invocation.method(#save, []), + returnValueForMissingStub: null); + @override + void saveLayer(_i2.Rect? bounds, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#saveLayer, [bounds, paint]), + returnValueForMissingStub: null); + @override + void restore() => super.noSuchMethod(Invocation.method(#restore, []), + returnValueForMissingStub: null); + @override + int getSaveCount() => + (super.noSuchMethod(Invocation.method(#getSaveCount, []), returnValue: 0) + as int); + @override + void translate(double? dx, double? dy) => + super.noSuchMethod(Invocation.method(#translate, [dx, dy]), + returnValueForMissingStub: null); + @override + void scale(double? sx, [double? sy]) => + super.noSuchMethod(Invocation.method(#scale, [sx, sy]), + returnValueForMissingStub: null); + @override + void rotate(double? radians) => + super.noSuchMethod(Invocation.method(#rotate, [radians]), + returnValueForMissingStub: null); + @override + void skew(double? sx, double? sy) => + super.noSuchMethod(Invocation.method(#skew, [sx, sy]), + returnValueForMissingStub: null); + @override + void transform(_i8.Float64List? matrix4) => + super.noSuchMethod(Invocation.method(#transform, [matrix4]), + returnValueForMissingStub: null); + @override + void clipRect(_i2.Rect? rect, + {_i2.ClipOp? clipOp = _i2.ClipOp.intersect, + bool? doAntiAlias = true}) => + super.noSuchMethod( + Invocation.method( + #clipRect, [rect], {#clipOp: clipOp, #doAntiAlias: doAntiAlias}), + returnValueForMissingStub: null); + @override + void clipRRect(_i2.RRect? rrect, {bool? doAntiAlias = true}) => + super.noSuchMethod( + Invocation.method(#clipRRect, [rrect], {#doAntiAlias: doAntiAlias}), + returnValueForMissingStub: null); + @override + void clipPath(_i2.Path? path, {bool? doAntiAlias = true}) => + super.noSuchMethod( + Invocation.method(#clipPath, [path], {#doAntiAlias: doAntiAlias}), + returnValueForMissingStub: null); + @override + void drawColor(_i2.Color? color, _i2.BlendMode? blendMode) => + super.noSuchMethod(Invocation.method(#drawColor, [color, blendMode]), + returnValueForMissingStub: null); + @override + void drawLine(_i2.Offset? p1, _i2.Offset? p2, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawLine, [p1, p2, paint]), + returnValueForMissingStub: null); + @override + void drawPaint(_i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawPaint, [paint]), + returnValueForMissingStub: null); + @override + void drawRect(_i2.Rect? rect, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawRect, [rect, paint]), + returnValueForMissingStub: null); + @override + void drawRRect(_i2.RRect? rrect, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawRRect, [rrect, paint]), + returnValueForMissingStub: null); + @override + void drawDRRect(_i2.RRect? outer, _i2.RRect? inner, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawDRRect, [outer, inner, paint]), + returnValueForMissingStub: null); + @override + void drawOval(_i2.Rect? rect, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawOval, [rect, paint]), + returnValueForMissingStub: null); + @override + void drawCircle(_i2.Offset? c, double? radius, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawCircle, [c, radius, paint]), + returnValueForMissingStub: null); + @override + void drawArc(_i2.Rect? rect, double? startAngle, double? sweepAngle, + bool? useCenter, _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method( + #drawArc, [rect, startAngle, sweepAngle, useCenter, paint]), + returnValueForMissingStub: null); + @override + void drawPath(_i2.Path? path, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawPath, [path, paint]), + returnValueForMissingStub: null); + @override + void drawImage(_i2.Image? image, _i2.Offset? offset, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawImage, [image, offset, paint]), + returnValueForMissingStub: null); + @override + void drawImageRect( + _i2.Image? image, _i2.Rect? src, _i2.Rect? dst, _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawImageRect, [image, src, dst, paint]), + returnValueForMissingStub: null); + @override + void drawImageNine(_i2.Image? image, _i2.Rect? center, _i2.Rect? dst, + _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawImageNine, [image, center, dst, paint]), + returnValueForMissingStub: null); + @override + void drawPicture(_i2.Picture? picture) => + super.noSuchMethod(Invocation.method(#drawPicture, [picture]), + returnValueForMissingStub: null); + @override + void drawParagraph(_i2.Paragraph? paragraph, _i2.Offset? offset) => + super.noSuchMethod(Invocation.method(#drawParagraph, [paragraph, offset]), + returnValueForMissingStub: null); + @override + void drawPoints(_i2.PointMode? pointMode, List<_i2.Offset>? points, + _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawPoints, [pointMode, points, paint]), + returnValueForMissingStub: null); + @override + void drawRawPoints(_i2.PointMode? pointMode, _i8.Float32List? points, + _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawRawPoints, [pointMode, points, paint]), + returnValueForMissingStub: null); + @override + void drawVertices( + _i2.Vertices? vertices, _i2.BlendMode? blendMode, _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawVertices, [vertices, blendMode, paint]), + returnValueForMissingStub: null); + @override + void drawAtlas( + _i2.Image? atlas, + List<_i2.RSTransform>? transforms, + List<_i2.Rect>? rects, + List<_i2.Color>? colors, + _i2.BlendMode? blendMode, + _i2.Rect? cullRect, + _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawAtlas, + [atlas, transforms, rects, colors, blendMode, cullRect, paint]), + returnValueForMissingStub: null); + @override + void drawRawAtlas( + _i2.Image? atlas, + _i8.Float32List? rstTransforms, + _i8.Float32List? rects, + _i8.Int32List? colors, + _i2.BlendMode? blendMode, + _i2.Rect? cullRect, + _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawRawAtlas, [ + atlas, + rstTransforms, + rects, + colors, + blendMode, + cullRect, + paint + ]), + returnValueForMissingStub: null); + @override + void drawShadow(_i2.Path? path, _i2.Color? color, double? elevation, + bool? transparentOccluder) => + super.noSuchMethod( + Invocation.method( + #drawShadow, [path, color, elevation, transparentOccluder]), + returnValueForMissingStub: null); +} + +/// A class which mocks [PaintingContext]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockPaintingContext extends _i1.Mock implements _i3.PaintingContext { + MockPaintingContext() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.Rect get estimatedBounds => + (super.noSuchMethod(Invocation.getter(#estimatedBounds), + returnValue: _FakeRect_0()) as _i2.Rect); + @override + _i2.Canvas get canvas => (super.noSuchMethod(Invocation.getter(#canvas), + returnValue: _FakeCanvas_1()) as _i2.Canvas); + @override + void paintChild(_i3.RenderObject? child, _i2.Offset? offset) => + super.noSuchMethod(Invocation.method(#paintChild, [child, offset]), + returnValueForMissingStub: null); + @override + void appendLayer(_i4.Layer? layer) => + super.noSuchMethod(Invocation.method(#appendLayer, [layer]), + returnValueForMissingStub: null); + @override + void stopRecordingIfNeeded() => + super.noSuchMethod(Invocation.method(#stopRecordingIfNeeded, []), + returnValueForMissingStub: null); + @override + void setIsComplexHint() => + super.noSuchMethod(Invocation.method(#setIsComplexHint, []), + returnValueForMissingStub: null); + @override + void setWillChangeHint() => + super.noSuchMethod(Invocation.method(#setWillChangeHint, []), + returnValueForMissingStub: null); + @override + void addLayer(_i4.Layer? layer) => + super.noSuchMethod(Invocation.method(#addLayer, [layer]), + returnValueForMissingStub: null); + @override + void pushLayer(_i4.ContainerLayer? childLayer, + _i3.PaintingContextCallback? painter, _i2.Offset? offset, + {_i2.Rect? childPaintBounds}) => + super.noSuchMethod( + Invocation.method(#pushLayer, [childLayer, painter, offset], + {#childPaintBounds: childPaintBounds}), + returnValueForMissingStub: null); + @override + _i3.PaintingContext createChildContext( + _i4.ContainerLayer? childLayer, _i2.Rect? bounds) => + (super.noSuchMethod( + Invocation.method(#createChildContext, [childLayer, bounds]), + returnValue: _FakePaintingContext_2()) as _i3.PaintingContext); + @override + _i4.ClipRectLayer? pushClipRect(bool? needsCompositing, _i2.Offset? offset, + _i2.Rect? clipRect, _i3.PaintingContextCallback? painter, + {_i2.Clip? clipBehavior = _i2.Clip.hardEdge, + _i4.ClipRectLayer? oldLayer}) => + (super.noSuchMethod(Invocation.method(#pushClipRect, [ + needsCompositing, + offset, + clipRect, + painter + ], { + #clipBehavior: clipBehavior, + #oldLayer: oldLayer + })) as _i4.ClipRectLayer?); + @override + _i4.ClipRRectLayer? pushClipRRect( + bool? needsCompositing, + _i2.Offset? offset, + _i2.Rect? bounds, + _i2.RRect? clipRRect, + _i3.PaintingContextCallback? painter, + {_i2.Clip? clipBehavior = _i2.Clip.antiAlias, + _i4.ClipRRectLayer? oldLayer}) => + (super.noSuchMethod(Invocation.method(#pushClipRRect, [ + needsCompositing, + offset, + bounds, + clipRRect, + painter + ], { + #clipBehavior: clipBehavior, + #oldLayer: oldLayer + })) as _i4.ClipRRectLayer?); + @override + _i4.ClipPathLayer? pushClipPath( + bool? needsCompositing, + _i2.Offset? offset, + _i2.Rect? bounds, + _i2.Path? clipPath, + _i3.PaintingContextCallback? painter, + {_i2.Clip? clipBehavior = _i2.Clip.antiAlias, + _i4.ClipPathLayer? oldLayer}) => + (super.noSuchMethod(Invocation.method(#pushClipPath, [ + needsCompositing, + offset, + bounds, + clipPath, + painter + ], { + #clipBehavior: clipBehavior, + #oldLayer: oldLayer + })) as _i4.ClipPathLayer?); + @override + _i4.ColorFilterLayer pushColorFilter(_i2.Offset? offset, + _i2.ColorFilter? colorFilter, _i3.PaintingContextCallback? painter, + {_i4.ColorFilterLayer? oldLayer}) => + (super.noSuchMethod( + Invocation.method(#pushColorFilter, [offset, colorFilter, painter], + {#oldLayer: oldLayer}), + returnValue: _FakeColorFilterLayer_3()) as _i4.ColorFilterLayer); + @override + _i4.TransformLayer? pushTransform(bool? needsCompositing, _i2.Offset? offset, + _i9.Matrix4? transform, _i3.PaintingContextCallback? painter, + {_i4.TransformLayer? oldLayer}) => + (super.noSuchMethod(Invocation.method( + #pushTransform, + [needsCompositing, offset, transform, painter], + {#oldLayer: oldLayer})) as _i4.TransformLayer?); + @override + _i4.OpacityLayer pushOpacity( + _i2.Offset? offset, int? alpha, _i3.PaintingContextCallback? painter, + {_i4.OpacityLayer? oldLayer}) => + (super.noSuchMethod( + Invocation.method( + #pushOpacity, [offset, alpha, painter], {#oldLayer: oldLayer}), + returnValue: _FakeOpacityLayer_4()) as _i4.OpacityLayer); + @override + void clipPathAndPaint(_i2.Path? path, _i2.Clip? clipBehavior, + _i2.Rect? bounds, _i2.VoidCallback? painter) => + super.noSuchMethod( + Invocation.method( + #clipPathAndPaint, [path, clipBehavior, bounds, painter]), + returnValueForMissingStub: null); + @override + void clipRRectAndPaint(_i2.RRect? rrect, _i2.Clip? clipBehavior, + _i2.Rect? bounds, _i2.VoidCallback? painter) => + super.noSuchMethod( + Invocation.method( + #clipRRectAndPaint, [rrect, clipBehavior, bounds, painter]), + returnValueForMissingStub: null); + @override + void clipRectAndPaint(_i2.Rect? rect, _i2.Clip? clipBehavior, + _i2.Rect? bounds, _i2.VoidCallback? painter) => + super.noSuchMethod( + Invocation.method( + #clipRectAndPaint, [rect, clipBehavior, bounds, painter]), + returnValueForMissingStub: null); +} + +/// A class which mocks [BuildContext]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockBuildContext extends _i1.Mock implements _i6.BuildContext { + MockBuildContext() { + _i1.throwOnMissingStub(this); + } + + @override + _i6.Widget get widget => (super.noSuchMethod(Invocation.getter(#widget), + returnValue: _FakeWidget_5()) as _i6.Widget); + @override + bool get debugDoingBuild => (super + .noSuchMethod(Invocation.getter(#debugDoingBuild), returnValue: false) + as bool); + @override + _i6.InheritedWidget dependOnInheritedElement(_i6.InheritedElement? ancestor, + {Object? aspect}) => + (super.noSuchMethod( + Invocation.method( + #dependOnInheritedElement, [ancestor], {#aspect: aspect}), + returnValue: _FakeInheritedWidget_6()) as _i6.InheritedWidget); + @override + void visitAncestorElements(bool Function(_i6.Element)? visitor) => + super.noSuchMethod(Invocation.method(#visitAncestorElements, [visitor]), + returnValueForMissingStub: null); + @override + void visitChildElements(_i6.ElementVisitor? visitor) => + super.noSuchMethod(Invocation.method(#visitChildElements, [visitor]), + returnValueForMissingStub: null); + @override + _i5.DiagnosticsNode describeElement(String? name, + {_i5.DiagnosticsTreeStyle? style = + _i5.DiagnosticsTreeStyle.errorProperty}) => + (super.noSuchMethod( + Invocation.method(#describeElement, [name], {#style: style}), + returnValue: _FakeDiagnosticsNode_7()) as _i5.DiagnosticsNode); + @override + _i5.DiagnosticsNode describeWidget(String? name, + {_i5.DiagnosticsTreeStyle? style = + _i5.DiagnosticsTreeStyle.errorProperty}) => + (super.noSuchMethod( + Invocation.method(#describeWidget, [name], {#style: style}), + returnValue: _FakeDiagnosticsNode_7()) as _i5.DiagnosticsNode); + @override + List<_i5.DiagnosticsNode> describeMissingAncestor( + {Type? expectedAncestorType}) => + (super.noSuchMethod( + Invocation.method(#describeMissingAncestor, [], + {#expectedAncestorType: expectedAncestorType}), + returnValue: <_i5.DiagnosticsNode>[]) as List<_i5.DiagnosticsNode>); + @override + _i5.DiagnosticsNode describeOwnershipChain(String? name) => + (super.noSuchMethod(Invocation.method(#describeOwnershipChain, [name]), + returnValue: _FakeDiagnosticsNode_7()) as _i5.DiagnosticsNode); +} + +/// A class which mocks [PieChartPainter]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockPieChartPainter extends _i1.Mock implements _i10.PieChartPainter { + MockPieChartPainter() { + _i1.throwOnMissingStub(this); + } + + @override + void paint(_i6.BuildContext? context, _i11.CanvasWrapper? canvasWrapper, + _i12.PaintHolder<_i7.PieChartData>? holder) => + super.noSuchMethod( + Invocation.method(#paint, [context, canvasWrapper, holder]), + returnValueForMissingStub: null); + @override + List calculateSectionsAngle( + List<_i7.PieChartSectionData>? sections, double? sumValue) => + (super.noSuchMethod( + Invocation.method(#calculateSectionsAngle, [sections, sumValue]), + returnValue: []) as List); + @override + void drawCenterSpace(_i11.CanvasWrapper? canvasWrapper, double? centerRadius, + _i12.PaintHolder<_i7.PieChartData>? holder) => + super.noSuchMethod( + Invocation.method( + #drawCenterSpace, [canvasWrapper, centerRadius, holder]), + returnValueForMissingStub: null); + @override + void drawSections( + _i11.CanvasWrapper? canvasWrapper, + List? sectionsAngle, + double? centerRadius, + _i12.PaintHolder<_i7.PieChartData>? holder) => + super.noSuchMethod( + Invocation.method(#drawSections, + [canvasWrapper, sectionsAngle, centerRadius, holder]), + returnValueForMissingStub: null); + @override + _i2.Path generateSectionPath( + _i7.PieChartSectionData? section, + double? sectionSpace, + double? tempAngle, + double? sectionDegree, + _i2.Offset? center, + double? centerRadius) => + (super.noSuchMethod( + Invocation.method(#generateSectionPath, [ + section, + sectionSpace, + tempAngle, + sectionDegree, + center, + centerRadius + ]), + returnValue: _FakePath_8()) as _i2.Path); + @override + _i2.Path createRectPathAroundLine(_i13.Line? line, double? width) => (super + .noSuchMethod(Invocation.method(#createRectPathAroundLine, [line, width]), + returnValue: _FakePath_8()) as _i2.Path); + @override + void drawSection(_i7.PieChartSectionData? section, _i2.Path? sectionPath, + _i11.CanvasWrapper? canvasWrapper) => + super.noSuchMethod( + Invocation.method( + #drawSection, [section, sectionPath, canvasWrapper]), + returnValueForMissingStub: null); + @override + void drawSectionStroke( + _i7.PieChartSectionData? section, + _i2.Path? sectionPath, + _i11.CanvasWrapper? canvasWrapper, + _i2.Size? viewSize) => + super.noSuchMethod( + Invocation.method(#drawSectionStroke, + [section, sectionPath, canvasWrapper, viewSize]), + returnValueForMissingStub: null); + @override + void drawTexts(_i6.BuildContext? context, _i11.CanvasWrapper? canvasWrapper, + _i12.PaintHolder<_i7.PieChartData>? holder, double? centerRadius) => + super.noSuchMethod( + Invocation.method( + #drawTexts, [context, canvasWrapper, holder, centerRadius]), + returnValueForMissingStub: null); + @override + double calculateCenterRadius( + _i2.Size? viewSize, _i12.PaintHolder<_i7.PieChartData>? holder) => + (super.noSuchMethod( + Invocation.method(#calculateCenterRadius, [viewSize, holder]), + returnValue: 0.0) as double); + @override + _i7.PieTouchedSection handleTouch(_i2.Offset? localPosition, + _i2.Size? viewSize, _i12.PaintHolder<_i7.PieChartData>? holder) => + (super.noSuchMethod( + Invocation.method(#handleTouch, [localPosition, viewSize, holder]), + returnValue: _FakePieTouchedSection_9()) as _i7.PieTouchedSection); + @override + Map getBadgeOffsets( + _i2.Size? viewSize, _i12.PaintHolder<_i7.PieChartData>? holder) => + (super.noSuchMethod( + Invocation.method(#getBadgeOffsets, [viewSize, holder]), + returnValue: {}) as Map); + @override + _i2.Size getChartUsableDrawSize( + _i2.Size? viewSize, _i12.PaintHolder<_i7.PieChartData>? holder) => + (super.noSuchMethod( + Invocation.method(#getChartUsableDrawSize, [viewSize, holder]), + returnValue: _FakeSize_10()) as _i2.Size); + @override + double getExtraNeededHorizontalSpace( + _i12.PaintHolder<_i7.PieChartData>? holder) => + (super.noSuchMethod( + Invocation.method(#getExtraNeededHorizontalSpace, [holder]), + returnValue: 0.0) as double); + @override + double getExtraNeededVerticalSpace( + _i12.PaintHolder<_i7.PieChartData>? holder) => + (super.noSuchMethod( + Invocation.method(#getExtraNeededVerticalSpace, [holder]), + returnValue: 0.0) as double); + @override + double getLeftOffsetDrawSize(_i12.PaintHolder<_i7.PieChartData>? holder) => + (super.noSuchMethod(Invocation.method(#getLeftOffsetDrawSize, [holder]), + returnValue: 0.0) as double); + @override + double getTopOffsetDrawSize(_i12.PaintHolder<_i7.PieChartData>? holder) => + (super.noSuchMethod(Invocation.method(#getTopOffsetDrawSize, [holder]), + returnValue: 0.0) as double); +} diff --git a/test/chart/radar_chart/radar_chart_renderer_test.dart b/test/chart/radar_chart/radar_chart_renderer_test.dart new file mode 100644 index 000000000..a66163305 --- /dev/null +++ b/test/chart/radar_chart/radar_chart_renderer_test.dart @@ -0,0 +1,96 @@ +import 'package:fl_chart/fl_chart.dart'; +import 'package:fl_chart/src/chart/radar_chart/radar_chart_painter.dart'; +import 'package:fl_chart/src/chart/radar_chart/radar_chart_renderer.dart'; +import 'package:fl_chart/src/utils/canvas_wrapper.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import '../data_pool.dart'; +import 'radar_chart_renderer_test.mocks.dart'; + +@GenerateMocks([Canvas, PaintingContext, BuildContext, RadarChartPainter]) +void main() { + group('RadarChartRenderer', () { + final RadarChartData data = RadarChartData( + dataSets: [MockData.radarDataSet1], + tickCount: 1, + ); + + final RadarChartData targetData = RadarChartData( + dataSets: [MockData.radarDataSet2], + tickCount: 1, + ); + + const textScale = 4.0; + + MockBuildContext _mockBuildContext = MockBuildContext(); + RenderRadarChart renderBarChart = RenderRadarChart( + _mockBuildContext, + data, + targetData, + textScale, + ); + + MockRadarChartPainter _mockPainter = MockRadarChartPainter(); + MockPaintingContext _mockPaintingContext = MockPaintingContext(); + MockCanvas _mockCanvas = MockCanvas(); + Size _mockSize = const Size(44, 44); + when(_mockPaintingContext.canvas) + .thenAnswer((realInvocation) => _mockCanvas); + renderBarChart.mockTestSize = _mockSize; + renderBarChart.painter = _mockPainter; + + test('test 1 correct data set', () { + expect(renderBarChart.data == data, true); + expect(renderBarChart.data == targetData, false); + expect(renderBarChart.targetData == targetData, true); + expect(renderBarChart.textScale == textScale, true); + expect(renderBarChart.paintHolder.data == data, true); + expect(renderBarChart.paintHolder.targetData == targetData, true); + expect(renderBarChart.paintHolder.textScale == textScale, true); + }); + + test('test 2 check paint function', () { + renderBarChart.paint(_mockPaintingContext, const Offset(10, 10)); + verify(_mockCanvas.save()).called(1); + verify(_mockCanvas.translate(10, 10)).called(1); + final result = verify(_mockPainter.paint(any, captureAny, captureAny)); + expect(result.callCount, 1); + + final canvasWrapper = result.captured[0] as CanvasWrapper; + expect(canvasWrapper.size, const Size(44, 44)); + expect(canvasWrapper.canvas, _mockCanvas); + + final paintHolder = result.captured[1] as PaintHolder; + expect(paintHolder.data, data); + expect(paintHolder.targetData, targetData); + expect(paintHolder.textScale, textScale); + + verify(_mockCanvas.restore()).called(1); + }); + + test('test 3 check getResponseAtLocation function', () { + List> results = []; + when(_mockPainter.handleTouch(captureAny, captureAny, captureAny)) + .thenAnswer((inv) { + results.add({ + 'local_position': inv.positionalArguments[0] as Offset, + 'size': inv.positionalArguments[1] as Size, + 'paint_holder': (inv.positionalArguments[2] as PaintHolder), + }); + return MockData.radarTouchedSpot; + }); + final touchResponse = + renderBarChart.getResponseAtLocation(MockData.offset1); + expect(touchResponse.touchedSpot, MockData.radarTouchedSpot); + expect(results[0]['local_position'] as Offset, MockData.offset1); + expect(results[0]['size'] as Size, _mockSize); + final paintHolder = results[0]['paint_holder'] as PaintHolder; + expect(paintHolder.data, data); + expect(paintHolder.targetData, targetData); + expect(paintHolder.textScale, textScale); + }); + }); +} diff --git a/test/chart/radar_chart/radar_chart_renderer_test.mocks.dart b/test/chart/radar_chart/radar_chart_renderer_test.mocks.dart new file mode 100644 index 000000000..58cb44812 --- /dev/null +++ b/test/chart/radar_chart/radar_chart_renderer_test.mocks.dart @@ -0,0 +1,558 @@ +// Mocks generated by Mockito 5.0.17 from annotations +// in fl_chart/test/chart/radar_chart/radar_chart_renderer_test.dart. +// Do not manually edit this file. + +import 'dart:typed_data' as _i7; +import 'dart:ui' as _i2; + +import 'package:fl_chart/fl_chart.dart' as _i12; +import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart' + as _i11; +import 'package:fl_chart/src/chart/radar_chart/radar_chart_painter.dart' as _i9; +import 'package:fl_chart/src/utils/canvas_wrapper.dart' as _i10; +import 'package:flutter/foundation.dart' as _i5; +import 'package:flutter/material.dart' as _i6; +import 'package:flutter/rendering.dart' as _i3; +import 'package:flutter/src/rendering/layer.dart' as _i4; +import 'package:mockito/mockito.dart' as _i1; +import 'package:vector_math/vector_math_64.dart' as _i8; + +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types + +class _FakeRect_0 extends _i1.Fake implements _i2.Rect {} + +class _FakeCanvas_1 extends _i1.Fake implements _i2.Canvas {} + +class _FakePaintingContext_2 extends _i1.Fake implements _i3.PaintingContext {} + +class _FakeColorFilterLayer_3 extends _i1.Fake implements _i4.ColorFilterLayer { + @override + String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakeOpacityLayer_4 extends _i1.Fake implements _i4.OpacityLayer { + @override + String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakeWidget_5 extends _i1.Fake implements _i6.Widget { + @override + String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakeInheritedWidget_6 extends _i1.Fake implements _i6.InheritedWidget { + @override + String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakeDiagnosticsNode_7 extends _i1.Fake implements _i5.DiagnosticsNode { + @override + String toString( + {_i5.TextTreeConfiguration? parentConfiguration, + _i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakeSize_8 extends _i1.Fake implements _i2.Size {} + +/// A class which mocks [Canvas]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCanvas extends _i1.Mock implements _i2.Canvas { + MockCanvas() { + _i1.throwOnMissingStub(this); + } + + @override + void save() => super.noSuchMethod(Invocation.method(#save, []), + returnValueForMissingStub: null); + @override + void saveLayer(_i2.Rect? bounds, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#saveLayer, [bounds, paint]), + returnValueForMissingStub: null); + @override + void restore() => super.noSuchMethod(Invocation.method(#restore, []), + returnValueForMissingStub: null); + @override + int getSaveCount() => + (super.noSuchMethod(Invocation.method(#getSaveCount, []), returnValue: 0) + as int); + @override + void translate(double? dx, double? dy) => + super.noSuchMethod(Invocation.method(#translate, [dx, dy]), + returnValueForMissingStub: null); + @override + void scale(double? sx, [double? sy]) => + super.noSuchMethod(Invocation.method(#scale, [sx, sy]), + returnValueForMissingStub: null); + @override + void rotate(double? radians) => + super.noSuchMethod(Invocation.method(#rotate, [radians]), + returnValueForMissingStub: null); + @override + void skew(double? sx, double? sy) => + super.noSuchMethod(Invocation.method(#skew, [sx, sy]), + returnValueForMissingStub: null); + @override + void transform(_i7.Float64List? matrix4) => + super.noSuchMethod(Invocation.method(#transform, [matrix4]), + returnValueForMissingStub: null); + @override + void clipRect(_i2.Rect? rect, + {_i2.ClipOp? clipOp = _i2.ClipOp.intersect, + bool? doAntiAlias = true}) => + super.noSuchMethod( + Invocation.method( + #clipRect, [rect], {#clipOp: clipOp, #doAntiAlias: doAntiAlias}), + returnValueForMissingStub: null); + @override + void clipRRect(_i2.RRect? rrect, {bool? doAntiAlias = true}) => + super.noSuchMethod( + Invocation.method(#clipRRect, [rrect], {#doAntiAlias: doAntiAlias}), + returnValueForMissingStub: null); + @override + void clipPath(_i2.Path? path, {bool? doAntiAlias = true}) => + super.noSuchMethod( + Invocation.method(#clipPath, [path], {#doAntiAlias: doAntiAlias}), + returnValueForMissingStub: null); + @override + void drawColor(_i2.Color? color, _i2.BlendMode? blendMode) => + super.noSuchMethod(Invocation.method(#drawColor, [color, blendMode]), + returnValueForMissingStub: null); + @override + void drawLine(_i2.Offset? p1, _i2.Offset? p2, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawLine, [p1, p2, paint]), + returnValueForMissingStub: null); + @override + void drawPaint(_i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawPaint, [paint]), + returnValueForMissingStub: null); + @override + void drawRect(_i2.Rect? rect, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawRect, [rect, paint]), + returnValueForMissingStub: null); + @override + void drawRRect(_i2.RRect? rrect, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawRRect, [rrect, paint]), + returnValueForMissingStub: null); + @override + void drawDRRect(_i2.RRect? outer, _i2.RRect? inner, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawDRRect, [outer, inner, paint]), + returnValueForMissingStub: null); + @override + void drawOval(_i2.Rect? rect, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawOval, [rect, paint]), + returnValueForMissingStub: null); + @override + void drawCircle(_i2.Offset? c, double? radius, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawCircle, [c, radius, paint]), + returnValueForMissingStub: null); + @override + void drawArc(_i2.Rect? rect, double? startAngle, double? sweepAngle, + bool? useCenter, _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method( + #drawArc, [rect, startAngle, sweepAngle, useCenter, paint]), + returnValueForMissingStub: null); + @override + void drawPath(_i2.Path? path, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawPath, [path, paint]), + returnValueForMissingStub: null); + @override + void drawImage(_i2.Image? image, _i2.Offset? offset, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawImage, [image, offset, paint]), + returnValueForMissingStub: null); + @override + void drawImageRect( + _i2.Image? image, _i2.Rect? src, _i2.Rect? dst, _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawImageRect, [image, src, dst, paint]), + returnValueForMissingStub: null); + @override + void drawImageNine(_i2.Image? image, _i2.Rect? center, _i2.Rect? dst, + _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawImageNine, [image, center, dst, paint]), + returnValueForMissingStub: null); + @override + void drawPicture(_i2.Picture? picture) => + super.noSuchMethod(Invocation.method(#drawPicture, [picture]), + returnValueForMissingStub: null); + @override + void drawParagraph(_i2.Paragraph? paragraph, _i2.Offset? offset) => + super.noSuchMethod(Invocation.method(#drawParagraph, [paragraph, offset]), + returnValueForMissingStub: null); + @override + void drawPoints(_i2.PointMode? pointMode, List<_i2.Offset>? points, + _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawPoints, [pointMode, points, paint]), + returnValueForMissingStub: null); + @override + void drawRawPoints(_i2.PointMode? pointMode, _i7.Float32List? points, + _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawRawPoints, [pointMode, points, paint]), + returnValueForMissingStub: null); + @override + void drawVertices( + _i2.Vertices? vertices, _i2.BlendMode? blendMode, _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawVertices, [vertices, blendMode, paint]), + returnValueForMissingStub: null); + @override + void drawAtlas( + _i2.Image? atlas, + List<_i2.RSTransform>? transforms, + List<_i2.Rect>? rects, + List<_i2.Color>? colors, + _i2.BlendMode? blendMode, + _i2.Rect? cullRect, + _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawAtlas, + [atlas, transforms, rects, colors, blendMode, cullRect, paint]), + returnValueForMissingStub: null); + @override + void drawRawAtlas( + _i2.Image? atlas, + _i7.Float32List? rstTransforms, + _i7.Float32List? rects, + _i7.Int32List? colors, + _i2.BlendMode? blendMode, + _i2.Rect? cullRect, + _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawRawAtlas, [ + atlas, + rstTransforms, + rects, + colors, + blendMode, + cullRect, + paint + ]), + returnValueForMissingStub: null); + @override + void drawShadow(_i2.Path? path, _i2.Color? color, double? elevation, + bool? transparentOccluder) => + super.noSuchMethod( + Invocation.method( + #drawShadow, [path, color, elevation, transparentOccluder]), + returnValueForMissingStub: null); +} + +/// A class which mocks [PaintingContext]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockPaintingContext extends _i1.Mock implements _i3.PaintingContext { + MockPaintingContext() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.Rect get estimatedBounds => + (super.noSuchMethod(Invocation.getter(#estimatedBounds), + returnValue: _FakeRect_0()) as _i2.Rect); + @override + _i2.Canvas get canvas => (super.noSuchMethod(Invocation.getter(#canvas), + returnValue: _FakeCanvas_1()) as _i2.Canvas); + @override + void paintChild(_i3.RenderObject? child, _i2.Offset? offset) => + super.noSuchMethod(Invocation.method(#paintChild, [child, offset]), + returnValueForMissingStub: null); + @override + void appendLayer(_i4.Layer? layer) => + super.noSuchMethod(Invocation.method(#appendLayer, [layer]), + returnValueForMissingStub: null); + @override + void stopRecordingIfNeeded() => + super.noSuchMethod(Invocation.method(#stopRecordingIfNeeded, []), + returnValueForMissingStub: null); + @override + void setIsComplexHint() => + super.noSuchMethod(Invocation.method(#setIsComplexHint, []), + returnValueForMissingStub: null); + @override + void setWillChangeHint() => + super.noSuchMethod(Invocation.method(#setWillChangeHint, []), + returnValueForMissingStub: null); + @override + void addLayer(_i4.Layer? layer) => + super.noSuchMethod(Invocation.method(#addLayer, [layer]), + returnValueForMissingStub: null); + @override + void pushLayer(_i4.ContainerLayer? childLayer, + _i3.PaintingContextCallback? painter, _i2.Offset? offset, + {_i2.Rect? childPaintBounds}) => + super.noSuchMethod( + Invocation.method(#pushLayer, [childLayer, painter, offset], + {#childPaintBounds: childPaintBounds}), + returnValueForMissingStub: null); + @override + _i3.PaintingContext createChildContext( + _i4.ContainerLayer? childLayer, _i2.Rect? bounds) => + (super.noSuchMethod( + Invocation.method(#createChildContext, [childLayer, bounds]), + returnValue: _FakePaintingContext_2()) as _i3.PaintingContext); + @override + _i4.ClipRectLayer? pushClipRect(bool? needsCompositing, _i2.Offset? offset, + _i2.Rect? clipRect, _i3.PaintingContextCallback? painter, + {_i2.Clip? clipBehavior = _i2.Clip.hardEdge, + _i4.ClipRectLayer? oldLayer}) => + (super.noSuchMethod(Invocation.method(#pushClipRect, [ + needsCompositing, + offset, + clipRect, + painter + ], { + #clipBehavior: clipBehavior, + #oldLayer: oldLayer + })) as _i4.ClipRectLayer?); + @override + _i4.ClipRRectLayer? pushClipRRect( + bool? needsCompositing, + _i2.Offset? offset, + _i2.Rect? bounds, + _i2.RRect? clipRRect, + _i3.PaintingContextCallback? painter, + {_i2.Clip? clipBehavior = _i2.Clip.antiAlias, + _i4.ClipRRectLayer? oldLayer}) => + (super.noSuchMethod(Invocation.method(#pushClipRRect, [ + needsCompositing, + offset, + bounds, + clipRRect, + painter + ], { + #clipBehavior: clipBehavior, + #oldLayer: oldLayer + })) as _i4.ClipRRectLayer?); + @override + _i4.ClipPathLayer? pushClipPath( + bool? needsCompositing, + _i2.Offset? offset, + _i2.Rect? bounds, + _i2.Path? clipPath, + _i3.PaintingContextCallback? painter, + {_i2.Clip? clipBehavior = _i2.Clip.antiAlias, + _i4.ClipPathLayer? oldLayer}) => + (super.noSuchMethod(Invocation.method(#pushClipPath, [ + needsCompositing, + offset, + bounds, + clipPath, + painter + ], { + #clipBehavior: clipBehavior, + #oldLayer: oldLayer + })) as _i4.ClipPathLayer?); + @override + _i4.ColorFilterLayer pushColorFilter(_i2.Offset? offset, + _i2.ColorFilter? colorFilter, _i3.PaintingContextCallback? painter, + {_i4.ColorFilterLayer? oldLayer}) => + (super.noSuchMethod( + Invocation.method(#pushColorFilter, [offset, colorFilter, painter], + {#oldLayer: oldLayer}), + returnValue: _FakeColorFilterLayer_3()) as _i4.ColorFilterLayer); + @override + _i4.TransformLayer? pushTransform(bool? needsCompositing, _i2.Offset? offset, + _i8.Matrix4? transform, _i3.PaintingContextCallback? painter, + {_i4.TransformLayer? oldLayer}) => + (super.noSuchMethod(Invocation.method( + #pushTransform, + [needsCompositing, offset, transform, painter], + {#oldLayer: oldLayer})) as _i4.TransformLayer?); + @override + _i4.OpacityLayer pushOpacity( + _i2.Offset? offset, int? alpha, _i3.PaintingContextCallback? painter, + {_i4.OpacityLayer? oldLayer}) => + (super.noSuchMethod( + Invocation.method( + #pushOpacity, [offset, alpha, painter], {#oldLayer: oldLayer}), + returnValue: _FakeOpacityLayer_4()) as _i4.OpacityLayer); + @override + void clipPathAndPaint(_i2.Path? path, _i2.Clip? clipBehavior, + _i2.Rect? bounds, _i2.VoidCallback? painter) => + super.noSuchMethod( + Invocation.method( + #clipPathAndPaint, [path, clipBehavior, bounds, painter]), + returnValueForMissingStub: null); + @override + void clipRRectAndPaint(_i2.RRect? rrect, _i2.Clip? clipBehavior, + _i2.Rect? bounds, _i2.VoidCallback? painter) => + super.noSuchMethod( + Invocation.method( + #clipRRectAndPaint, [rrect, clipBehavior, bounds, painter]), + returnValueForMissingStub: null); + @override + void clipRectAndPaint(_i2.Rect? rect, _i2.Clip? clipBehavior, + _i2.Rect? bounds, _i2.VoidCallback? painter) => + super.noSuchMethod( + Invocation.method( + #clipRectAndPaint, [rect, clipBehavior, bounds, painter]), + returnValueForMissingStub: null); +} + +/// A class which mocks [BuildContext]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockBuildContext extends _i1.Mock implements _i6.BuildContext { + MockBuildContext() { + _i1.throwOnMissingStub(this); + } + + @override + _i6.Widget get widget => (super.noSuchMethod(Invocation.getter(#widget), + returnValue: _FakeWidget_5()) as _i6.Widget); + @override + bool get debugDoingBuild => (super + .noSuchMethod(Invocation.getter(#debugDoingBuild), returnValue: false) + as bool); + @override + _i6.InheritedWidget dependOnInheritedElement(_i6.InheritedElement? ancestor, + {Object? aspect}) => + (super.noSuchMethod( + Invocation.method( + #dependOnInheritedElement, [ancestor], {#aspect: aspect}), + returnValue: _FakeInheritedWidget_6()) as _i6.InheritedWidget); + @override + void visitAncestorElements(bool Function(_i6.Element)? visitor) => + super.noSuchMethod(Invocation.method(#visitAncestorElements, [visitor]), + returnValueForMissingStub: null); + @override + void visitChildElements(_i6.ElementVisitor? visitor) => + super.noSuchMethod(Invocation.method(#visitChildElements, [visitor]), + returnValueForMissingStub: null); + @override + _i5.DiagnosticsNode describeElement(String? name, + {_i5.DiagnosticsTreeStyle? style = + _i5.DiagnosticsTreeStyle.errorProperty}) => + (super.noSuchMethod( + Invocation.method(#describeElement, [name], {#style: style}), + returnValue: _FakeDiagnosticsNode_7()) as _i5.DiagnosticsNode); + @override + _i5.DiagnosticsNode describeWidget(String? name, + {_i5.DiagnosticsTreeStyle? style = + _i5.DiagnosticsTreeStyle.errorProperty}) => + (super.noSuchMethod( + Invocation.method(#describeWidget, [name], {#style: style}), + returnValue: _FakeDiagnosticsNode_7()) as _i5.DiagnosticsNode); + @override + List<_i5.DiagnosticsNode> describeMissingAncestor( + {Type? expectedAncestorType}) => + (super.noSuchMethod( + Invocation.method(#describeMissingAncestor, [], + {#expectedAncestorType: expectedAncestorType}), + returnValue: <_i5.DiagnosticsNode>[]) as List<_i5.DiagnosticsNode>); + @override + _i5.DiagnosticsNode describeOwnershipChain(String? name) => + (super.noSuchMethod(Invocation.method(#describeOwnershipChain, [name]), + returnValue: _FakeDiagnosticsNode_7()) as _i5.DiagnosticsNode); +} + +/// A class which mocks [RadarChartPainter]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockRadarChartPainter extends _i1.Mock implements _i9.RadarChartPainter { + MockRadarChartPainter() { + _i1.throwOnMissingStub(this); + } + + @override + set dataSetsPosition(List<_i9.RadarDataSetsPosition>? _dataSetsPosition) => + super.noSuchMethod( + Invocation.setter(#dataSetsPosition, _dataSetsPosition), + returnValueForMissingStub: null); + @override + void paint(_i6.BuildContext? context, _i10.CanvasWrapper? canvasWrapper, + _i11.PaintHolder<_i12.RadarChartData>? holder) => + super.noSuchMethod( + Invocation.method(#paint, [context, canvasWrapper, holder]), + returnValueForMissingStub: null); + @override + void drawTicks(_i6.BuildContext? context, _i10.CanvasWrapper? canvasWrapper, + _i11.PaintHolder<_i12.RadarChartData>? holder) => + super.noSuchMethod( + Invocation.method(#drawTicks, [context, canvasWrapper, holder]), + returnValueForMissingStub: null); + @override + void drawGrids(_i10.CanvasWrapper? canvasWrapper, + _i11.PaintHolder<_i12.RadarChartData>? holder) => + super.noSuchMethod(Invocation.method(#drawGrids, [canvasWrapper, holder]), + returnValueForMissingStub: null); + @override + void drawTitles(_i6.BuildContext? context, _i10.CanvasWrapper? canvasWrapper, + _i11.PaintHolder<_i12.RadarChartData>? holder) => + super.noSuchMethod( + Invocation.method(#drawTitles, [context, canvasWrapper, holder]), + returnValueForMissingStub: null); + @override + void drawDataSets(_i10.CanvasWrapper? canvasWrapper, + _i11.PaintHolder<_i12.RadarChartData>? holder) => + super.noSuchMethod( + Invocation.method(#drawDataSets, [canvasWrapper, holder]), + returnValueForMissingStub: null); + @override + _i12.RadarTouchedSpot? handleTouch(_i2.Offset? touchedPoint, + _i2.Size? viewSize, _i11.PaintHolder<_i12.RadarChartData>? holder) => + (super.noSuchMethod( + Invocation.method(#handleTouch, [touchedPoint, viewSize, holder])) + as _i12.RadarTouchedSpot?); + @override + double radarCenterY(_i2.Size? size) => + (super.noSuchMethod(Invocation.method(#radarCenterY, [size]), + returnValue: 0.0) as double); + @override + double radarCenterX(_i2.Size? size) => + (super.noSuchMethod(Invocation.method(#radarCenterX, [size]), + returnValue: 0.0) as double); + @override + double radarRadius(_i2.Size? size) => + (super.noSuchMethod(Invocation.method(#radarRadius, [size]), + returnValue: 0.0) as double); + @override + List<_i9.RadarDataSetsPosition> calculateDataSetsPosition( + _i2.Size? viewSize, _i11.PaintHolder<_i12.RadarChartData>? holder) => + (super.noSuchMethod( + Invocation.method(#calculateDataSetsPosition, [viewSize, holder]), + returnValue: <_i9.RadarDataSetsPosition>[]) + as List<_i9.RadarDataSetsPosition>); + @override + _i2.Size getChartUsableDrawSize( + _i2.Size? viewSize, _i11.PaintHolder<_i12.RadarChartData>? holder) => + (super.noSuchMethod( + Invocation.method(#getChartUsableDrawSize, [viewSize, holder]), + returnValue: _FakeSize_8()) as _i2.Size); + @override + double getExtraNeededHorizontalSpace( + _i11.PaintHolder<_i12.RadarChartData>? holder) => + (super.noSuchMethod( + Invocation.method(#getExtraNeededHorizontalSpace, [holder]), + returnValue: 0.0) as double); + @override + double getExtraNeededVerticalSpace( + _i11.PaintHolder<_i12.RadarChartData>? holder) => + (super.noSuchMethod( + Invocation.method(#getExtraNeededVerticalSpace, [holder]), + returnValue: 0.0) as double); + @override + double getLeftOffsetDrawSize(_i11.PaintHolder<_i12.RadarChartData>? holder) => + (super.noSuchMethod(Invocation.method(#getLeftOffsetDrawSize, [holder]), + returnValue: 0.0) as double); + @override + double getTopOffsetDrawSize(_i11.PaintHolder<_i12.RadarChartData>? holder) => + (super.noSuchMethod(Invocation.method(#getTopOffsetDrawSize, [holder]), + returnValue: 0.0) as double); +} diff --git a/test/chart/scatter_chart/scatter_chart_renderer_test.dart b/test/chart/scatter_chart/scatter_chart_renderer_test.dart new file mode 100644 index 000000000..a0b3555bd --- /dev/null +++ b/test/chart/scatter_chart/scatter_chart_renderer_test.dart @@ -0,0 +1,92 @@ +import 'package:fl_chart/fl_chart.dart'; +import 'package:fl_chart/src/chart/scatter_chart/scatter_chart_painter.dart'; +import 'package:fl_chart/src/chart/scatter_chart/scatter_chart_renderer.dart'; +import 'package:fl_chart/src/utils/canvas_wrapper.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import '../data_pool.dart'; +import 'scatter_chart_renderer_test.mocks.dart'; + +@GenerateMocks([Canvas, PaintingContext, BuildContext, ScatterChartPainter]) +void main() { + group('ScatterChartRenderer', () { + final ScatterChartData data = ScatterChartData( + scatterSpots: [MockData.scatterSpot1, MockData.scatterSpot2]); + + final ScatterChartData targetData = + ScatterChartData(scatterSpots: [MockData.scatterSpot3]); + + const textScale = 4.0; + + MockBuildContext _mockBuildContext = MockBuildContext(); + RenderScatterChart renderBarChart = RenderScatterChart( + _mockBuildContext, + data, + targetData, + textScale, + ); + + MockScatterChartPainter _mockPainter = MockScatterChartPainter(); + MockPaintingContext _mockPaintingContext = MockPaintingContext(); + MockCanvas _mockCanvas = MockCanvas(); + Size _mockSize = const Size(44, 44); + when(_mockPaintingContext.canvas) + .thenAnswer((realInvocation) => _mockCanvas); + renderBarChart.mockTestSize = _mockSize; + renderBarChart.painter = _mockPainter; + + test('test 1 correct data set', () { + expect(renderBarChart.data == data, true); + expect(renderBarChart.data == targetData, false); + expect(renderBarChart.targetData == targetData, true); + expect(renderBarChart.textScale == textScale, true); + expect(renderBarChart.paintHolder.data == data, true); + expect(renderBarChart.paintHolder.targetData == targetData, true); + expect(renderBarChart.paintHolder.textScale == textScale, true); + }); + + test('test 2 check paint function', () { + renderBarChart.paint(_mockPaintingContext, const Offset(10, 10)); + verify(_mockCanvas.save()).called(1); + verify(_mockCanvas.translate(10, 10)).called(1); + final result = verify(_mockPainter.paint(any, captureAny, captureAny)); + expect(result.callCount, 1); + + final canvasWrapper = result.captured[0] as CanvasWrapper; + expect(canvasWrapper.size, const Size(44, 44)); + expect(canvasWrapper.canvas, _mockCanvas); + + final paintHolder = result.captured[1] as PaintHolder; + expect(paintHolder.data, data); + expect(paintHolder.targetData, targetData); + expect(paintHolder.textScale, textScale); + + verify(_mockCanvas.restore()).called(1); + }); + + test('test 3 check getResponseAtLocation function', () { + List> results = []; + when(_mockPainter.handleTouch(captureAny, captureAny, captureAny)) + .thenAnswer((inv) { + results.add({ + 'local_position': inv.positionalArguments[0] as Offset, + 'size': inv.positionalArguments[1] as Size, + 'paint_holder': (inv.positionalArguments[2] as PaintHolder), + }); + return MockData.scatterTouchedSpot; + }); + final touchResponse = + renderBarChart.getResponseAtLocation(MockData.offset1); + expect(touchResponse.touchedSpot, MockData.scatterTouchedSpot); + expect(results[0]['local_position'] as Offset, MockData.offset1); + expect(results[0]['size'] as Size, _mockSize); + final paintHolder = results[0]['paint_holder'] as PaintHolder; + expect(paintHolder.data, data); + expect(paintHolder.targetData, targetData); + expect(paintHolder.textScale, textScale); + }); + }); +} diff --git a/test/chart/scatter_chart/scatter_chart_renderer_test.mocks.dart b/test/chart/scatter_chart/scatter_chart_renderer_test.mocks.dart new file mode 100644 index 000000000..bdc7a117a --- /dev/null +++ b/test/chart/scatter_chart/scatter_chart_renderer_test.mocks.dart @@ -0,0 +1,584 @@ +// Mocks generated by Mockito 5.0.17 from annotations +// in fl_chart/test/chart/scatter_chart/scatter_chart_renderer_test.dart. +// Do not manually edit this file. + +import 'dart:typed_data' as _i7; +import 'dart:ui' as _i2; + +import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart' + as _i11; +import 'package:fl_chart/src/chart/scatter_chart/scatter_chart_data.dart' + as _i12; +import 'package:fl_chart/src/chart/scatter_chart/scatter_chart_painter.dart' + as _i9; +import 'package:fl_chart/src/utils/canvas_wrapper.dart' as _i10; +import 'package:flutter/foundation.dart' as _i5; +import 'package:flutter/material.dart' as _i6; +import 'package:flutter/rendering.dart' as _i3; +import 'package:flutter/src/rendering/layer.dart' as _i4; +import 'package:mockito/mockito.dart' as _i1; +import 'package:vector_math/vector_math_64.dart' as _i8; + +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types + +class _FakeRect_0 extends _i1.Fake implements _i2.Rect {} + +class _FakeCanvas_1 extends _i1.Fake implements _i2.Canvas {} + +class _FakePaintingContext_2 extends _i1.Fake implements _i3.PaintingContext {} + +class _FakeColorFilterLayer_3 extends _i1.Fake implements _i4.ColorFilterLayer { + @override + String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakeOpacityLayer_4 extends _i1.Fake implements _i4.OpacityLayer { + @override + String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakeWidget_5 extends _i1.Fake implements _i6.Widget { + @override + String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakeInheritedWidget_6 extends _i1.Fake implements _i6.InheritedWidget { + @override + String toString({_i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakeDiagnosticsNode_7 extends _i1.Fake implements _i5.DiagnosticsNode { + @override + String toString( + {_i5.TextTreeConfiguration? parentConfiguration, + _i5.DiagnosticLevel? minLevel = _i5.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakeSize_8 extends _i1.Fake implements _i2.Size {} + +/// A class which mocks [Canvas]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCanvas extends _i1.Mock implements _i2.Canvas { + MockCanvas() { + _i1.throwOnMissingStub(this); + } + + @override + void save() => super.noSuchMethod(Invocation.method(#save, []), + returnValueForMissingStub: null); + @override + void saveLayer(_i2.Rect? bounds, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#saveLayer, [bounds, paint]), + returnValueForMissingStub: null); + @override + void restore() => super.noSuchMethod(Invocation.method(#restore, []), + returnValueForMissingStub: null); + @override + int getSaveCount() => + (super.noSuchMethod(Invocation.method(#getSaveCount, []), returnValue: 0) + as int); + @override + void translate(double? dx, double? dy) => + super.noSuchMethod(Invocation.method(#translate, [dx, dy]), + returnValueForMissingStub: null); + @override + void scale(double? sx, [double? sy]) => + super.noSuchMethod(Invocation.method(#scale, [sx, sy]), + returnValueForMissingStub: null); + @override + void rotate(double? radians) => + super.noSuchMethod(Invocation.method(#rotate, [radians]), + returnValueForMissingStub: null); + @override + void skew(double? sx, double? sy) => + super.noSuchMethod(Invocation.method(#skew, [sx, sy]), + returnValueForMissingStub: null); + @override + void transform(_i7.Float64List? matrix4) => + super.noSuchMethod(Invocation.method(#transform, [matrix4]), + returnValueForMissingStub: null); + @override + void clipRect(_i2.Rect? rect, + {_i2.ClipOp? clipOp = _i2.ClipOp.intersect, + bool? doAntiAlias = true}) => + super.noSuchMethod( + Invocation.method( + #clipRect, [rect], {#clipOp: clipOp, #doAntiAlias: doAntiAlias}), + returnValueForMissingStub: null); + @override + void clipRRect(_i2.RRect? rrect, {bool? doAntiAlias = true}) => + super.noSuchMethod( + Invocation.method(#clipRRect, [rrect], {#doAntiAlias: doAntiAlias}), + returnValueForMissingStub: null); + @override + void clipPath(_i2.Path? path, {bool? doAntiAlias = true}) => + super.noSuchMethod( + Invocation.method(#clipPath, [path], {#doAntiAlias: doAntiAlias}), + returnValueForMissingStub: null); + @override + void drawColor(_i2.Color? color, _i2.BlendMode? blendMode) => + super.noSuchMethod(Invocation.method(#drawColor, [color, blendMode]), + returnValueForMissingStub: null); + @override + void drawLine(_i2.Offset? p1, _i2.Offset? p2, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawLine, [p1, p2, paint]), + returnValueForMissingStub: null); + @override + void drawPaint(_i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawPaint, [paint]), + returnValueForMissingStub: null); + @override + void drawRect(_i2.Rect? rect, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawRect, [rect, paint]), + returnValueForMissingStub: null); + @override + void drawRRect(_i2.RRect? rrect, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawRRect, [rrect, paint]), + returnValueForMissingStub: null); + @override + void drawDRRect(_i2.RRect? outer, _i2.RRect? inner, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawDRRect, [outer, inner, paint]), + returnValueForMissingStub: null); + @override + void drawOval(_i2.Rect? rect, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawOval, [rect, paint]), + returnValueForMissingStub: null); + @override + void drawCircle(_i2.Offset? c, double? radius, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawCircle, [c, radius, paint]), + returnValueForMissingStub: null); + @override + void drawArc(_i2.Rect? rect, double? startAngle, double? sweepAngle, + bool? useCenter, _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method( + #drawArc, [rect, startAngle, sweepAngle, useCenter, paint]), + returnValueForMissingStub: null); + @override + void drawPath(_i2.Path? path, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawPath, [path, paint]), + returnValueForMissingStub: null); + @override + void drawImage(_i2.Image? image, _i2.Offset? offset, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawImage, [image, offset, paint]), + returnValueForMissingStub: null); + @override + void drawImageRect( + _i2.Image? image, _i2.Rect? src, _i2.Rect? dst, _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawImageRect, [image, src, dst, paint]), + returnValueForMissingStub: null); + @override + void drawImageNine(_i2.Image? image, _i2.Rect? center, _i2.Rect? dst, + _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawImageNine, [image, center, dst, paint]), + returnValueForMissingStub: null); + @override + void drawPicture(_i2.Picture? picture) => + super.noSuchMethod(Invocation.method(#drawPicture, [picture]), + returnValueForMissingStub: null); + @override + void drawParagraph(_i2.Paragraph? paragraph, _i2.Offset? offset) => + super.noSuchMethod(Invocation.method(#drawParagraph, [paragraph, offset]), + returnValueForMissingStub: null); + @override + void drawPoints(_i2.PointMode? pointMode, List<_i2.Offset>? points, + _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawPoints, [pointMode, points, paint]), + returnValueForMissingStub: null); + @override + void drawRawPoints(_i2.PointMode? pointMode, _i7.Float32List? points, + _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawRawPoints, [pointMode, points, paint]), + returnValueForMissingStub: null); + @override + void drawVertices( + _i2.Vertices? vertices, _i2.BlendMode? blendMode, _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawVertices, [vertices, blendMode, paint]), + returnValueForMissingStub: null); + @override + void drawAtlas( + _i2.Image? atlas, + List<_i2.RSTransform>? transforms, + List<_i2.Rect>? rects, + List<_i2.Color>? colors, + _i2.BlendMode? blendMode, + _i2.Rect? cullRect, + _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawAtlas, + [atlas, transforms, rects, colors, blendMode, cullRect, paint]), + returnValueForMissingStub: null); + @override + void drawRawAtlas( + _i2.Image? atlas, + _i7.Float32List? rstTransforms, + _i7.Float32List? rects, + _i7.Int32List? colors, + _i2.BlendMode? blendMode, + _i2.Rect? cullRect, + _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawRawAtlas, [ + atlas, + rstTransforms, + rects, + colors, + blendMode, + cullRect, + paint + ]), + returnValueForMissingStub: null); + @override + void drawShadow(_i2.Path? path, _i2.Color? color, double? elevation, + bool? transparentOccluder) => + super.noSuchMethod( + Invocation.method( + #drawShadow, [path, color, elevation, transparentOccluder]), + returnValueForMissingStub: null); +} + +/// A class which mocks [PaintingContext]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockPaintingContext extends _i1.Mock implements _i3.PaintingContext { + MockPaintingContext() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.Rect get estimatedBounds => + (super.noSuchMethod(Invocation.getter(#estimatedBounds), + returnValue: _FakeRect_0()) as _i2.Rect); + @override + _i2.Canvas get canvas => (super.noSuchMethod(Invocation.getter(#canvas), + returnValue: _FakeCanvas_1()) as _i2.Canvas); + @override + void paintChild(_i3.RenderObject? child, _i2.Offset? offset) => + super.noSuchMethod(Invocation.method(#paintChild, [child, offset]), + returnValueForMissingStub: null); + @override + void appendLayer(_i4.Layer? layer) => + super.noSuchMethod(Invocation.method(#appendLayer, [layer]), + returnValueForMissingStub: null); + @override + void stopRecordingIfNeeded() => + super.noSuchMethod(Invocation.method(#stopRecordingIfNeeded, []), + returnValueForMissingStub: null); + @override + void setIsComplexHint() => + super.noSuchMethod(Invocation.method(#setIsComplexHint, []), + returnValueForMissingStub: null); + @override + void setWillChangeHint() => + super.noSuchMethod(Invocation.method(#setWillChangeHint, []), + returnValueForMissingStub: null); + @override + void addLayer(_i4.Layer? layer) => + super.noSuchMethod(Invocation.method(#addLayer, [layer]), + returnValueForMissingStub: null); + @override + void pushLayer(_i4.ContainerLayer? childLayer, + _i3.PaintingContextCallback? painter, _i2.Offset? offset, + {_i2.Rect? childPaintBounds}) => + super.noSuchMethod( + Invocation.method(#pushLayer, [childLayer, painter, offset], + {#childPaintBounds: childPaintBounds}), + returnValueForMissingStub: null); + @override + _i3.PaintingContext createChildContext( + _i4.ContainerLayer? childLayer, _i2.Rect? bounds) => + (super.noSuchMethod( + Invocation.method(#createChildContext, [childLayer, bounds]), + returnValue: _FakePaintingContext_2()) as _i3.PaintingContext); + @override + _i4.ClipRectLayer? pushClipRect(bool? needsCompositing, _i2.Offset? offset, + _i2.Rect? clipRect, _i3.PaintingContextCallback? painter, + {_i2.Clip? clipBehavior = _i2.Clip.hardEdge, + _i4.ClipRectLayer? oldLayer}) => + (super.noSuchMethod(Invocation.method(#pushClipRect, [ + needsCompositing, + offset, + clipRect, + painter + ], { + #clipBehavior: clipBehavior, + #oldLayer: oldLayer + })) as _i4.ClipRectLayer?); + @override + _i4.ClipRRectLayer? pushClipRRect( + bool? needsCompositing, + _i2.Offset? offset, + _i2.Rect? bounds, + _i2.RRect? clipRRect, + _i3.PaintingContextCallback? painter, + {_i2.Clip? clipBehavior = _i2.Clip.antiAlias, + _i4.ClipRRectLayer? oldLayer}) => + (super.noSuchMethod(Invocation.method(#pushClipRRect, [ + needsCompositing, + offset, + bounds, + clipRRect, + painter + ], { + #clipBehavior: clipBehavior, + #oldLayer: oldLayer + })) as _i4.ClipRRectLayer?); + @override + _i4.ClipPathLayer? pushClipPath( + bool? needsCompositing, + _i2.Offset? offset, + _i2.Rect? bounds, + _i2.Path? clipPath, + _i3.PaintingContextCallback? painter, + {_i2.Clip? clipBehavior = _i2.Clip.antiAlias, + _i4.ClipPathLayer? oldLayer}) => + (super.noSuchMethod(Invocation.method(#pushClipPath, [ + needsCompositing, + offset, + bounds, + clipPath, + painter + ], { + #clipBehavior: clipBehavior, + #oldLayer: oldLayer + })) as _i4.ClipPathLayer?); + @override + _i4.ColorFilterLayer pushColorFilter(_i2.Offset? offset, + _i2.ColorFilter? colorFilter, _i3.PaintingContextCallback? painter, + {_i4.ColorFilterLayer? oldLayer}) => + (super.noSuchMethod( + Invocation.method(#pushColorFilter, [offset, colorFilter, painter], + {#oldLayer: oldLayer}), + returnValue: _FakeColorFilterLayer_3()) as _i4.ColorFilterLayer); + @override + _i4.TransformLayer? pushTransform(bool? needsCompositing, _i2.Offset? offset, + _i8.Matrix4? transform, _i3.PaintingContextCallback? painter, + {_i4.TransformLayer? oldLayer}) => + (super.noSuchMethod(Invocation.method( + #pushTransform, + [needsCompositing, offset, transform, painter], + {#oldLayer: oldLayer})) as _i4.TransformLayer?); + @override + _i4.OpacityLayer pushOpacity( + _i2.Offset? offset, int? alpha, _i3.PaintingContextCallback? painter, + {_i4.OpacityLayer? oldLayer}) => + (super.noSuchMethod( + Invocation.method( + #pushOpacity, [offset, alpha, painter], {#oldLayer: oldLayer}), + returnValue: _FakeOpacityLayer_4()) as _i4.OpacityLayer); + @override + void clipPathAndPaint(_i2.Path? path, _i2.Clip? clipBehavior, + _i2.Rect? bounds, _i2.VoidCallback? painter) => + super.noSuchMethod( + Invocation.method( + #clipPathAndPaint, [path, clipBehavior, bounds, painter]), + returnValueForMissingStub: null); + @override + void clipRRectAndPaint(_i2.RRect? rrect, _i2.Clip? clipBehavior, + _i2.Rect? bounds, _i2.VoidCallback? painter) => + super.noSuchMethod( + Invocation.method( + #clipRRectAndPaint, [rrect, clipBehavior, bounds, painter]), + returnValueForMissingStub: null); + @override + void clipRectAndPaint(_i2.Rect? rect, _i2.Clip? clipBehavior, + _i2.Rect? bounds, _i2.VoidCallback? painter) => + super.noSuchMethod( + Invocation.method( + #clipRectAndPaint, [rect, clipBehavior, bounds, painter]), + returnValueForMissingStub: null); +} + +/// A class which mocks [BuildContext]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockBuildContext extends _i1.Mock implements _i6.BuildContext { + MockBuildContext() { + _i1.throwOnMissingStub(this); + } + + @override + _i6.Widget get widget => (super.noSuchMethod(Invocation.getter(#widget), + returnValue: _FakeWidget_5()) as _i6.Widget); + @override + bool get debugDoingBuild => (super + .noSuchMethod(Invocation.getter(#debugDoingBuild), returnValue: false) + as bool); + @override + _i6.InheritedWidget dependOnInheritedElement(_i6.InheritedElement? ancestor, + {Object? aspect}) => + (super.noSuchMethod( + Invocation.method( + #dependOnInheritedElement, [ancestor], {#aspect: aspect}), + returnValue: _FakeInheritedWidget_6()) as _i6.InheritedWidget); + @override + void visitAncestorElements(bool Function(_i6.Element)? visitor) => + super.noSuchMethod(Invocation.method(#visitAncestorElements, [visitor]), + returnValueForMissingStub: null); + @override + void visitChildElements(_i6.ElementVisitor? visitor) => + super.noSuchMethod(Invocation.method(#visitChildElements, [visitor]), + returnValueForMissingStub: null); + @override + _i5.DiagnosticsNode describeElement(String? name, + {_i5.DiagnosticsTreeStyle? style = + _i5.DiagnosticsTreeStyle.errorProperty}) => + (super.noSuchMethod( + Invocation.method(#describeElement, [name], {#style: style}), + returnValue: _FakeDiagnosticsNode_7()) as _i5.DiagnosticsNode); + @override + _i5.DiagnosticsNode describeWidget(String? name, + {_i5.DiagnosticsTreeStyle? style = + _i5.DiagnosticsTreeStyle.errorProperty}) => + (super.noSuchMethod( + Invocation.method(#describeWidget, [name], {#style: style}), + returnValue: _FakeDiagnosticsNode_7()) as _i5.DiagnosticsNode); + @override + List<_i5.DiagnosticsNode> describeMissingAncestor( + {Type? expectedAncestorType}) => + (super.noSuchMethod( + Invocation.method(#describeMissingAncestor, [], + {#expectedAncestorType: expectedAncestorType}), + returnValue: <_i5.DiagnosticsNode>[]) as List<_i5.DiagnosticsNode>); + @override + _i5.DiagnosticsNode describeOwnershipChain(String? name) => + (super.noSuchMethod(Invocation.method(#describeOwnershipChain, [name]), + returnValue: _FakeDiagnosticsNode_7()) as _i5.DiagnosticsNode); +} + +/// A class which mocks [ScatterChartPainter]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockScatterChartPainter extends _i1.Mock + implements _i9.ScatterChartPainter { + MockScatterChartPainter() { + _i1.throwOnMissingStub(this); + } + + @override + void paint(_i6.BuildContext? context, _i10.CanvasWrapper? canvasWrapper, + _i11.PaintHolder<_i12.ScatterChartData>? holder) => + super.noSuchMethod( + Invocation.method(#paint, [context, canvasWrapper, holder]), + returnValueForMissingStub: null); + @override + void drawTitles(_i6.BuildContext? context, _i10.CanvasWrapper? canvasWrapper, + _i11.PaintHolder<_i12.ScatterChartData>? holder) => + super.noSuchMethod( + Invocation.method(#drawTitles, [context, canvasWrapper, holder]), + returnValueForMissingStub: null); + @override + void drawSpots(_i10.CanvasWrapper? canvasWrapper, + _i11.PaintHolder<_i12.ScatterChartData>? holder) => + super.noSuchMethod(Invocation.method(#drawSpots, [canvasWrapper, holder]), + returnValueForMissingStub: null); + @override + void drawTouchTooltips( + _i6.BuildContext? context, + _i10.CanvasWrapper? canvasWrapper, + _i11.PaintHolder<_i12.ScatterChartData>? holder) => + super.noSuchMethod( + Invocation.method( + #drawTouchTooltips, [context, canvasWrapper, holder]), + returnValueForMissingStub: null); + @override + void drawTouchTooltip( + _i6.BuildContext? context, + _i10.CanvasWrapper? canvasWrapper, + _i12.ScatterTouchTooltipData? tooltipData, + _i12.ScatterSpot? showOnSpot, + _i11.PaintHolder<_i12.ScatterChartData>? holder) => + super.noSuchMethod( + Invocation.method(#drawTouchTooltip, + [context, canvasWrapper, tooltipData, showOnSpot, holder]), + returnValueForMissingStub: null); + @override + double getExtraNeededHorizontalSpace( + _i11.PaintHolder<_i12.ScatterChartData>? holder) => + (super.noSuchMethod( + Invocation.method(#getExtraNeededHorizontalSpace, [holder]), + returnValue: 0.0) as double); + @override + double getExtraNeededVerticalSpace( + _i11.PaintHolder<_i12.ScatterChartData>? holder) => + (super.noSuchMethod( + Invocation.method(#getExtraNeededVerticalSpace, [holder]), + returnValue: 0.0) as double); + @override + double getLeftOffsetDrawSize( + _i11.PaintHolder<_i12.ScatterChartData>? holder) => + (super.noSuchMethod(Invocation.method(#getLeftOffsetDrawSize, [holder]), + returnValue: 0.0) as double); + @override + double getTopOffsetDrawSize( + _i11.PaintHolder<_i12.ScatterChartData>? holder) => + (super.noSuchMethod(Invocation.method(#getTopOffsetDrawSize, [holder]), + returnValue: 0.0) as double); + @override + _i12.ScatterTouchedSpot? handleTouch(_i2.Offset? localPosition, + _i2.Size? size, _i11.PaintHolder<_i12.ScatterChartData>? holder) => + (super.noSuchMethod( + Invocation.method(#handleTouch, [localPosition, size, holder])) + as _i12.ScatterTouchedSpot?); + @override + void drawAxisTitles( + _i6.BuildContext? context, + _i10.CanvasWrapper? canvasWrapper, + _i11.PaintHolder<_i12.ScatterChartData>? holder) => + super.noSuchMethod( + Invocation.method(#drawAxisTitles, [context, canvasWrapper, holder]), + returnValueForMissingStub: null); + @override + void drawGrid(_i10.CanvasWrapper? canvasWrapper, + _i11.PaintHolder<_i12.ScatterChartData>? holder) => + super.noSuchMethod(Invocation.method(#drawGrid, [canvasWrapper, holder]), + returnValueForMissingStub: null); + @override + void drawBackground(_i10.CanvasWrapper? canvasWrapper, + _i11.PaintHolder<_i12.ScatterChartData>? holder) => + super.noSuchMethod( + Invocation.method(#drawBackground, [canvasWrapper, holder]), + returnValueForMissingStub: null); + @override + void drawRangeAnnotation(_i10.CanvasWrapper? canvasWrapper, + _i11.PaintHolder<_i12.ScatterChartData>? holder) => + super.noSuchMethod( + Invocation.method(#drawRangeAnnotation, [canvasWrapper, holder]), + returnValueForMissingStub: null); + @override + double getPixelX(double? spotX, _i2.Size? chartUsableSize, + _i11.PaintHolder<_i12.ScatterChartData>? holder) => + (super.noSuchMethod( + Invocation.method(#getPixelX, [spotX, chartUsableSize, holder]), + returnValue: 0.0) as double); + @override + double getPixelY(double? spotY, _i2.Size? chartUsableSize, + _i11.PaintHolder<_i12.ScatterChartData>? holder) => + (super.noSuchMethod( + Invocation.method(#getPixelY, [spotY, chartUsableSize, holder]), + returnValue: 0.0) as double); + @override + _i2.Size getChartUsableDrawSize(_i2.Size? viewSize, + _i11.PaintHolder<_i12.ScatterChartData>? holder) => + (super.noSuchMethod( + Invocation.method(#getChartUsableDrawSize, [viewSize, holder]), + returnValue: _FakeSize_8()) as _i2.Size); +} From 7ff1f68123c23f23c0b45e5108f49ad6744e852d Mon Sep 17 00:00:00 2001 From: imaN Khoshabi Date: Thu, 27 Jan 2022 22:41:26 +0330 Subject: [PATCH 12/45] Write unit test for canvas_wrapper in canvas_wrapper_test.dart --- analysis_options.yaml | 5 +- lib/src/utils/canvas_wrapper.dart | 1 + test/chart/data_pool.dart | 39 +++ .../pie_chart_renderer_test.mocks.dart | 4 +- .../scatter_chart_renderer_test.mocks.dart | 3 +- test/utils/canvas_wrapper_test.dart | 124 +++++++ test/utils/canvas_wrapper_test.mocks.dart | 316 ++++++++++++++++++ 7 files changed, 487 insertions(+), 5 deletions(-) create mode 100644 test/utils/canvas_wrapper_test.dart create mode 100644 test/utils/canvas_wrapper_test.mocks.dart diff --git a/analysis_options.yaml b/analysis_options.yaml index e6b68b873..229c50f47 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -3,4 +3,7 @@ analyzer: exclude: - "**.mocks.dart" errors: - prefer_initializing_formals: ignore \ No newline at end of file + prefer_initializing_formals: ignore + # allow self-reference to deprecated members (we do this because otherwise we have + # to annotate every member in every test, assert, etc, when we deprecate something) + deprecated_member_use_from_same_package: ignore \ No newline at end of file diff --git a/lib/src/utils/canvas_wrapper.dart b/lib/src/utils/canvas_wrapper.dart index 36162ad4d..1ba9f4a07 100644 --- a/lib/src/utils/canvas_wrapper.dart +++ b/lib/src/utils/canvas_wrapper.dart @@ -9,6 +9,7 @@ import 'package:flutter/cupertino.dart' hide Image; /// /// We wrapped the canvas here, because we needed to write tests for our drawing system. /// Now in tests we can verify that these functions called with a specific value. +@Deprecated('We can use the [Canvas] directly to write unit tests') class CanvasWrapper { final Canvas canvas; final Size size; diff --git a/test/chart/data_pool.dart b/test/chart/data_pool.dart index f704eaed6..451113651 100644 --- a/test/chart/data_pool.dart +++ b/test/chart/data_pool.dart @@ -66,6 +66,45 @@ class MockData { static const Offset offset5 = Offset(5, 5); static const Offset offset6 = Offset(6, 6); + static const size1 = Size(11, 11); + static const size2 = Size(22, 22); + + static final textPainter1 = TextPainter(); + + static final rect1 = Rect.fromCenter(center: offset1, width: 11, height: 11); + static final rect2 = Rect.fromCenter(center: offset2, width: 22, height: 22); + + static final rRect1 = RRect.fromLTRBR(1, 1, 1, 1, const Radius.circular(11)); + static final rRect2 = RRect.fromLTRBR(2, 2, 2, 2, const Radius.circular(22)); + + static final paint1 = Paint() + ..color = color1 + ..strokeWidth = 11; + static final paint2 = Paint() + ..color = color2 + ..strokeWidth = 22; + + static Picture? _picture1; + static Picture picture1() { + if (_picture1 != null) { + return _picture1!; + } + final recorder1 = PictureRecorder(); + final canvas = Canvas(recorder1); + canvas.drawLine(offset1, offset2, paint1); + _picture1 = recorder1.endRecording(); + return _picture1!; + } + + static Image? _image1; + static Image image1() { + if (_image1 != null) { + return _image1!; + } + _image1 = Image.asset('asdf/asdf'); + return _image1!; + } + static final LineChartBarData lineChartBarData1 = LineChartBarData( show: true, dashArray: [0, 1], diff --git a/test/chart/pie_chart/pie_chart_renderer_test.mocks.dart b/test/chart/pie_chart/pie_chart_renderer_test.mocks.dart index 1a74aeb06..0272a8ff9 100644 --- a/test/chart/pie_chart/pie_chart_renderer_test.mocks.dart +++ b/test/chart/pie_chart/pie_chart_renderer_test.mocks.dart @@ -5,16 +5,16 @@ import 'dart:typed_data' as _i8; import 'dart:ui' as _i2; +import 'package:fl_chart/fl_chart.dart' as _i7; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart' as _i12; import 'package:fl_chart/src/chart/base/line.dart' as _i13; -import 'package:fl_chart/src/chart/pie_chart/pie_chart_data.dart' as _i7; import 'package:fl_chart/src/chart/pie_chart/pie_chart_painter.dart' as _i10; import 'package:fl_chart/src/utils/canvas_wrapper.dart' as _i11; import 'package:flutter/foundation.dart' as _i5; +import 'package:flutter/material.dart' as _i6; import 'package:flutter/rendering.dart' as _i3; import 'package:flutter/src/rendering/layer.dart' as _i4; -import 'package:flutter/widgets.dart' as _i6; import 'package:mockito/mockito.dart' as _i1; import 'package:vector_math/vector_math_64.dart' as _i9; diff --git a/test/chart/scatter_chart/scatter_chart_renderer_test.mocks.dart b/test/chart/scatter_chart/scatter_chart_renderer_test.mocks.dart index bdc7a117a..53171706f 100644 --- a/test/chart/scatter_chart/scatter_chart_renderer_test.mocks.dart +++ b/test/chart/scatter_chart/scatter_chart_renderer_test.mocks.dart @@ -5,10 +5,9 @@ import 'dart:typed_data' as _i7; import 'dart:ui' as _i2; +import 'package:fl_chart/fl_chart.dart' as _i12; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart' as _i11; -import 'package:fl_chart/src/chart/scatter_chart/scatter_chart_data.dart' - as _i12; import 'package:fl_chart/src/chart/scatter_chart/scatter_chart_painter.dart' as _i9; import 'package:fl_chart/src/utils/canvas_wrapper.dart' as _i10; diff --git a/test/utils/canvas_wrapper_test.dart b/test/utils/canvas_wrapper_test.dart new file mode 100644 index 000000000..8229ddeaf --- /dev/null +++ b/test/utils/canvas_wrapper_test.dart @@ -0,0 +1,124 @@ +import 'dart:ui'; + +import 'package:fl_chart/src/chart/line_chart/line_chart_data.dart'; +import 'package:fl_chart/src/utils/canvas_wrapper.dart'; +import 'package:fl_chart/src/utils/utils.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import '../chart/data_pool.dart'; +import 'canvas_wrapper_test.mocks.dart'; + +@GenerateMocks([Canvas, FlDotPainter, Utils]) +void main() { + MockCanvas _mockCanvas = MockCanvas(); + CanvasWrapper canvasWrapper = CanvasWrapper(_mockCanvas, MockData.size1); + + test('test drawRRect', () { + canvasWrapper.drawRRect(MockData.rRect1, MockData.paint1); + verify(_mockCanvas.drawRRect(MockData.rRect1, MockData.paint1)).called(1); + }); + + test('test save', () { + canvasWrapper.save(); + verify(_mockCanvas.save()).called(1); + }); + + test('test restore', () { + canvasWrapper.restore(); + verify(_mockCanvas.restore()).called(1); + }); + + test('test clipRect', () { + canvasWrapper.clipRect(MockData.rect1); + verify(_mockCanvas.clipRect(MockData.rect1)).called(1); + }); + + test('test translate', () { + canvasWrapper.translate(11, 232); + verify(_mockCanvas.translate(11, 232)).called(1); + }); + + test('test rotate', () { + canvasWrapper.rotate(12); + verify(_mockCanvas.rotate(12)).called(1); + }); + + test('test drawPath', () { + canvasWrapper.drawPath(MockData.path1, MockData.paint1); + verify(_mockCanvas.drawPath(MockData.path1, MockData.paint1)).called(1); + }); + + test('test saveLayer', () { + canvasWrapper.saveLayer(MockData.rect1, MockData.paint1); + verify(_mockCanvas.saveLayer(MockData.rect1, MockData.paint1)).called(1); + }); + + test('test drawPicture', () { + canvasWrapper.drawPicture(MockData.picture1()); + verify(_mockCanvas.drawPicture(MockData.picture1())).called(1); + }); + + test('test clipPath', () { + canvasWrapper.clipPath(MockData.path1); + verify(_mockCanvas.clipPath(MockData.path1)).called(1); + }); + + test('test drawRect', () { + canvasWrapper.drawRect(MockData.rect1, MockData.paint1); + verify(_mockCanvas.drawRect(MockData.rect1, MockData.paint1)).called(1); + }); + + test('test drawLine', () { + canvasWrapper.drawLine(MockData.offset1, MockData.offset2, MockData.paint1); + verify(_mockCanvas.drawLine( + MockData.offset1, + MockData.offset2, + MockData.paint1, + )).called(1); + }); + + test('test drawCircle', () { + canvasWrapper.drawCircle(MockData.offset1, 12, MockData.paint1); + verify(_mockCanvas.drawCircle(MockData.offset1, 12, MockData.paint1)) + .called(1); + }); + + test('test drawArc', () { + canvasWrapper.drawArc(MockData.rect1, 12, 22, false, MockData.paint1); + verify(_mockCanvas.drawArc(MockData.rect1, 12, 22, false, MockData.paint1)) + .called(1); + }); + + test('test drawDot', () { + MockFlDotPainter painter = MockFlDotPainter(); + canvasWrapper.drawDot(painter, MockData.lineBarSpot1, MockData.offset1); + verify(painter.draw(_mockCanvas, MockData.lineBarSpot1, MockData.offset1)) + .called(1); + }); + + test('test drawRotated', () { + final _mockUtils = MockUtils(); + when(_mockUtils.radians(any)).thenAnswer((realInvocation) => 12); + Utils.changeInstance(_mockUtils); + + var calledCallback = false; + void callback() { + calledCallback = true; + } + + canvasWrapper.drawRotated( + size: const Size(240, 240), + rotationOffset: MockData.offset1, + drawOffset: MockData.offset2, + angle: 12, + drawCallback: callback, + ); + verify(_mockCanvas.save()).called(1); + verify(_mockCanvas.translate(123, 123)).called(1); + verify(_mockCanvas.rotate(12)).called(1); + verify(_mockCanvas.translate(-122, -122)).called(1); + expect(calledCallback, true); + verify(_mockCanvas.restore()).called(1); + }); +} diff --git a/test/utils/canvas_wrapper_test.mocks.dart b/test/utils/canvas_wrapper_test.mocks.dart new file mode 100644 index 000000000..2ef2ba023 --- /dev/null +++ b/test/utils/canvas_wrapper_test.mocks.dart @@ -0,0 +1,316 @@ +// Mocks generated by Mockito 5.0.17 from annotations +// in fl_chart/test/utils/canvas_wrapper_test.dart. +// Do not manually edit this file. + +import 'dart:typed_data' as _i4; +import 'dart:ui' as _i2; + +import 'package:fl_chart/fl_chart.dart' as _i5; +import 'package:fl_chart/src/utils/utils.dart' as _i6; +import 'package:flutter/material.dart' as _i3; +import 'package:mockito/mockito.dart' as _i1; + +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types + +class _FakeSize_0 extends _i1.Fake implements _i2.Size {} + +class _FakeOffset_1 extends _i1.Fake implements _i2.Offset {} + +class _FakeBorderSide_2 extends _i1.Fake implements _i3.BorderSide {} + +class _FakeTextStyle_3 extends _i1.Fake implements _i3.TextStyle { + @override + String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => + super.toString(); +} + +/// A class which mocks [Canvas]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCanvas extends _i1.Mock implements _i2.Canvas { + MockCanvas() { + _i1.throwOnMissingStub(this); + } + + @override + void save() => super.noSuchMethod(Invocation.method(#save, []), + returnValueForMissingStub: null); + @override + void saveLayer(_i2.Rect? bounds, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#saveLayer, [bounds, paint]), + returnValueForMissingStub: null); + @override + void restore() => super.noSuchMethod(Invocation.method(#restore, []), + returnValueForMissingStub: null); + @override + int getSaveCount() => + (super.noSuchMethod(Invocation.method(#getSaveCount, []), returnValue: 0) + as int); + @override + void translate(double? dx, double? dy) => + super.noSuchMethod(Invocation.method(#translate, [dx, dy]), + returnValueForMissingStub: null); + @override + void scale(double? sx, [double? sy]) => + super.noSuchMethod(Invocation.method(#scale, [sx, sy]), + returnValueForMissingStub: null); + @override + void rotate(double? radians) => + super.noSuchMethod(Invocation.method(#rotate, [radians]), + returnValueForMissingStub: null); + @override + void skew(double? sx, double? sy) => + super.noSuchMethod(Invocation.method(#skew, [sx, sy]), + returnValueForMissingStub: null); + @override + void transform(_i4.Float64List? matrix4) => + super.noSuchMethod(Invocation.method(#transform, [matrix4]), + returnValueForMissingStub: null); + @override + void clipRect(_i2.Rect? rect, + {_i2.ClipOp? clipOp = _i2.ClipOp.intersect, + bool? doAntiAlias = true}) => + super.noSuchMethod( + Invocation.method( + #clipRect, [rect], {#clipOp: clipOp, #doAntiAlias: doAntiAlias}), + returnValueForMissingStub: null); + @override + void clipRRect(_i2.RRect? rrect, {bool? doAntiAlias = true}) => + super.noSuchMethod( + Invocation.method(#clipRRect, [rrect], {#doAntiAlias: doAntiAlias}), + returnValueForMissingStub: null); + @override + void clipPath(_i2.Path? path, {bool? doAntiAlias = true}) => + super.noSuchMethod( + Invocation.method(#clipPath, [path], {#doAntiAlias: doAntiAlias}), + returnValueForMissingStub: null); + @override + void drawColor(_i2.Color? color, _i2.BlendMode? blendMode) => + super.noSuchMethod(Invocation.method(#drawColor, [color, blendMode]), + returnValueForMissingStub: null); + @override + void drawLine(_i2.Offset? p1, _i2.Offset? p2, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawLine, [p1, p2, paint]), + returnValueForMissingStub: null); + @override + void drawPaint(_i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawPaint, [paint]), + returnValueForMissingStub: null); + @override + void drawRect(_i2.Rect? rect, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawRect, [rect, paint]), + returnValueForMissingStub: null); + @override + void drawRRect(_i2.RRect? rrect, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawRRect, [rrect, paint]), + returnValueForMissingStub: null); + @override + void drawDRRect(_i2.RRect? outer, _i2.RRect? inner, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawDRRect, [outer, inner, paint]), + returnValueForMissingStub: null); + @override + void drawOval(_i2.Rect? rect, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawOval, [rect, paint]), + returnValueForMissingStub: null); + @override + void drawCircle(_i2.Offset? c, double? radius, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawCircle, [c, radius, paint]), + returnValueForMissingStub: null); + @override + void drawArc(_i2.Rect? rect, double? startAngle, double? sweepAngle, + bool? useCenter, _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method( + #drawArc, [rect, startAngle, sweepAngle, useCenter, paint]), + returnValueForMissingStub: null); + @override + void drawPath(_i2.Path? path, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawPath, [path, paint]), + returnValueForMissingStub: null); + @override + void drawImage(_i2.Image? image, _i2.Offset? offset, _i2.Paint? paint) => + super.noSuchMethod(Invocation.method(#drawImage, [image, offset, paint]), + returnValueForMissingStub: null); + @override + void drawImageRect( + _i2.Image? image, _i2.Rect? src, _i2.Rect? dst, _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawImageRect, [image, src, dst, paint]), + returnValueForMissingStub: null); + @override + void drawImageNine(_i2.Image? image, _i2.Rect? center, _i2.Rect? dst, + _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawImageNine, [image, center, dst, paint]), + returnValueForMissingStub: null); + @override + void drawPicture(_i2.Picture? picture) => + super.noSuchMethod(Invocation.method(#drawPicture, [picture]), + returnValueForMissingStub: null); + @override + void drawParagraph(_i2.Paragraph? paragraph, _i2.Offset? offset) => + super.noSuchMethod(Invocation.method(#drawParagraph, [paragraph, offset]), + returnValueForMissingStub: null); + @override + void drawPoints(_i2.PointMode? pointMode, List<_i2.Offset>? points, + _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawPoints, [pointMode, points, paint]), + returnValueForMissingStub: null); + @override + void drawRawPoints(_i2.PointMode? pointMode, _i4.Float32List? points, + _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawRawPoints, [pointMode, points, paint]), + returnValueForMissingStub: null); + @override + void drawVertices( + _i2.Vertices? vertices, _i2.BlendMode? blendMode, _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawVertices, [vertices, blendMode, paint]), + returnValueForMissingStub: null); + @override + void drawAtlas( + _i2.Image? atlas, + List<_i2.RSTransform>? transforms, + List<_i2.Rect>? rects, + List<_i2.Color>? colors, + _i2.BlendMode? blendMode, + _i2.Rect? cullRect, + _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawAtlas, + [atlas, transforms, rects, colors, blendMode, cullRect, paint]), + returnValueForMissingStub: null); + @override + void drawRawAtlas( + _i2.Image? atlas, + _i4.Float32List? rstTransforms, + _i4.Float32List? rects, + _i4.Int32List? colors, + _i2.BlendMode? blendMode, + _i2.Rect? cullRect, + _i2.Paint? paint) => + super.noSuchMethod( + Invocation.method(#drawRawAtlas, [ + atlas, + rstTransforms, + rects, + colors, + blendMode, + cullRect, + paint + ]), + returnValueForMissingStub: null); + @override + void drawShadow(_i2.Path? path, _i2.Color? color, double? elevation, + bool? transparentOccluder) => + super.noSuchMethod( + Invocation.method( + #drawShadow, [path, color, elevation, transparentOccluder]), + returnValueForMissingStub: null); +} + +/// A class which mocks [FlDotPainter]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockFlDotPainter extends _i1.Mock implements _i5.FlDotPainter { + MockFlDotPainter() { + _i1.throwOnMissingStub(this); + } + + @override + List get props => + (super.noSuchMethod(Invocation.getter(#props), returnValue: []) + as List); + @override + void draw(_i2.Canvas? canvas, _i5.FlSpot? spot, _i2.Offset? offsetInCanvas) => + super.noSuchMethod( + Invocation.method(#draw, [canvas, spot, offsetInCanvas]), + returnValueForMissingStub: null); + @override + _i2.Size getSize(_i5.FlSpot? spot) => + (super.noSuchMethod(Invocation.method(#getSize, [spot]), + returnValue: _FakeSize_0()) as _i2.Size); +} + +/// A class which mocks [Utils]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockUtils extends _i1.Mock implements _i6.Utils { + MockUtils() { + _i1.throwOnMissingStub(this); + } + + @override + double radians(double? degrees) => + (super.noSuchMethod(Invocation.method(#radians, [degrees]), + returnValue: 0.0) as double); + @override + double degrees(double? radians) => + (super.noSuchMethod(Invocation.method(#degrees, [radians]), + returnValue: 0.0) as double); + @override + _i2.Size getDefaultSize(_i2.Size? screenSize) => + (super.noSuchMethod(Invocation.method(#getDefaultSize, [screenSize]), + returnValue: _FakeSize_0()) as _i2.Size); + @override + double translateRotatedPosition(double? size, double? degree) => + (super.noSuchMethod( + Invocation.method(#translateRotatedPosition, [size, degree]), + returnValue: 0.0) as double); + @override + _i2.Offset calculateRotationOffset(_i2.Size? size, double? degree) => (super + .noSuchMethod(Invocation.method(#calculateRotationOffset, [size, degree]), + returnValue: _FakeOffset_1()) as _i2.Offset); + @override + _i3.BorderRadius? normalizeBorderRadius( + _i3.BorderRadius? borderRadius, double? width) => + (super.noSuchMethod( + Invocation.method(#normalizeBorderRadius, [borderRadius, width])) + as _i3.BorderRadius?); + @override + _i3.BorderSide normalizeBorderSide( + _i3.BorderSide? borderSide, double? width) => + (super.noSuchMethod( + Invocation.method(#normalizeBorderSide, [borderSide, width]), + returnValue: _FakeBorderSide_2()) as _i3.BorderSide); + @override + double getEfficientInterval(double? axisViewSize, double? diffInYAxis, + {double? pixelPerInterval = 40.0}) => + (super.noSuchMethod( + Invocation.method(#getEfficientInterval, [axisViewSize, diffInYAxis], + {#pixelPerInterval: pixelPerInterval}), + returnValue: 0.0) as double); + @override + double roundInterval(double? input) => + (super.noSuchMethod(Invocation.method(#roundInterval, [input]), + returnValue: 0.0) as double); + @override + String formatNumber(double? number) => + (super.noSuchMethod(Invocation.method(#formatNumber, [number]), + returnValue: '') as String); + @override + _i3.TextStyle getThemeAwareTextStyle( + _i3.BuildContext? context, _i3.TextStyle? providedStyle) => + (super.noSuchMethod( + Invocation.method(#getThemeAwareTextStyle, [context, providedStyle]), + returnValue: _FakeTextStyle_3()) as _i3.TextStyle); + @override + double getBestInitialIntervalValue( + double? min, double? max, double? interval) => + (super.noSuchMethod( + Invocation.method(#getBestInitialIntervalValue, [min, max, interval]), + returnValue: 0.0) as double); + @override + double convertRadiusToSigma(double? radius) => + (super.noSuchMethod(Invocation.method(#convertRadiusToSigma, [radius]), + returnValue: 0.0) as double); +} From dcdf6e39bc53d80be67a05e78ecaacfa335fcfcd Mon Sep 17 00:00:00 2001 From: imaN Khoshabi Date: Fri, 28 Jan 2022 01:28:54 +0330 Subject: [PATCH 13/45] Write unit test for calculateRotationOffset --- .../pie_chart_renderer_test.mocks.dart | 4 +- .../scatter_chart_renderer_test.mocks.dart | 3 +- test/utils/utils_test.dart | 41 +++++++++++++++++++ 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/test/chart/pie_chart/pie_chart_renderer_test.mocks.dart b/test/chart/pie_chart/pie_chart_renderer_test.mocks.dart index 0272a8ff9..1a74aeb06 100644 --- a/test/chart/pie_chart/pie_chart_renderer_test.mocks.dart +++ b/test/chart/pie_chart/pie_chart_renderer_test.mocks.dart @@ -5,16 +5,16 @@ import 'dart:typed_data' as _i8; import 'dart:ui' as _i2; -import 'package:fl_chart/fl_chart.dart' as _i7; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart' as _i12; import 'package:fl_chart/src/chart/base/line.dart' as _i13; +import 'package:fl_chart/src/chart/pie_chart/pie_chart_data.dart' as _i7; import 'package:fl_chart/src/chart/pie_chart/pie_chart_painter.dart' as _i10; import 'package:fl_chart/src/utils/canvas_wrapper.dart' as _i11; import 'package:flutter/foundation.dart' as _i5; -import 'package:flutter/material.dart' as _i6; import 'package:flutter/rendering.dart' as _i3; import 'package:flutter/src/rendering/layer.dart' as _i4; +import 'package:flutter/widgets.dart' as _i6; import 'package:mockito/mockito.dart' as _i1; import 'package:vector_math/vector_math_64.dart' as _i9; diff --git a/test/chart/scatter_chart/scatter_chart_renderer_test.mocks.dart b/test/chart/scatter_chart/scatter_chart_renderer_test.mocks.dart index 53171706f..bdc7a117a 100644 --- a/test/chart/scatter_chart/scatter_chart_renderer_test.mocks.dart +++ b/test/chart/scatter_chart/scatter_chart_renderer_test.mocks.dart @@ -5,9 +5,10 @@ import 'dart:typed_data' as _i7; import 'dart:ui' as _i2; -import 'package:fl_chart/fl_chart.dart' as _i12; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart' as _i11; +import 'package:fl_chart/src/chart/scatter_chart/scatter_chart_data.dart' + as _i12; import 'package:fl_chart/src/chart/scatter_chart/scatter_chart_painter.dart' as _i9; import 'package:fl_chart/src/utils/canvas_wrapper.dart' as _i10; diff --git a/test/utils/utils_test.dart b/test/utils/utils_test.dart index 828dc18e5..b8ca80281 100644 --- a/test/utils/utils_test.dart +++ b/test/utils/utils_test.dart @@ -3,6 +3,8 @@ import 'package:fl_chart/src/utils/utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import '../chart/data_pool.dart'; + void main() { const tolerance = 0.001; @@ -44,6 +46,45 @@ void main() { expect(Utils().translateRotatedPosition(100, 0), 0); }); + test('calculateRotationOffset()', () { + expect(Utils().calculateRotationOffset(MockData.size1, 90), Offset.zero); + expect( + Utils().calculateRotationOffset(MockData.size1, 45).dx, + closeTo(-2.278, tolerance), + ); + expect( + Utils().calculateRotationOffset(MockData.size1, 45).dy, + closeTo(-2.278, tolerance), + ); + + expect( + Utils().calculateRotationOffset(MockData.size1, 180).dx, + closeTo(0.0, tolerance), + ); + expect( + Utils().calculateRotationOffset(MockData.size1, 180).dy, + closeTo(0.0, tolerance), + ); + + expect( + Utils().calculateRotationOffset(MockData.size1, 220).dx, + closeTo(-2.2485, tolerance), + ); + expect( + Utils().calculateRotationOffset(MockData.size1, 220).dy, + closeTo(-2.2485, tolerance), + ); + + expect( + Utils().calculateRotationOffset(MockData.size1, 350).dx, + closeTo(-0.87150, tolerance), + ); + expect( + Utils().calculateRotationOffset(MockData.size1, 350).dy, + closeTo(-0.87150, tolerance), + ); + }); + test('lerp gradient', () { expect( lerpGradient([ From a84c94e3e3cc82705dc56f94920152363dd586fe Mon Sep 17 00:00:00 2001 From: imaN Khoshabi Date: Fri, 28 Jan 2022 01:40:53 +0330 Subject: [PATCH 14/45] Improve renderers unit test --- .../chart/bar_chart/bar_chart_renderer.dart | 1 + .../chart/line_chart/line_chart_renderer.dart | 1 + .../chart/pie_chart/pie_chart_renderer.dart | 1 + .../radar_chart/radar_chart_renderer.dart | 1 + .../scatter_chart/scatter_chart_renderer.dart | 1 + .../bar_chart/bar_chart_renderer_test.dart | 10 ++++++ .../line_chart/line_chart_renderer_test.dart | 10 ++++++ .../pie_chart/pie_chart_renderer_test.dart | 10 ++++++ .../radar_chart_renderer_test.dart | 34 ++++++++++++------- .../scatter_chart_renderer_test.dart | 34 ++++++++++++------- 10 files changed, 79 insertions(+), 24 deletions(-) diff --git a/lib/src/chart/bar_chart/bar_chart_renderer.dart b/lib/src/chart/bar_chart/bar_chart_renderer.dart index 5b0958afd..4b5cfd381 100644 --- a/lib/src/chart/bar_chart/bar_chart_renderer.dart +++ b/lib/src/chart/bar_chart/bar_chart_renderer.dart @@ -7,6 +7,7 @@ import 'package:flutter/cupertino.dart'; import 'bar_chart_painter.dart'; // coverage:ignore-start + /// Low level BarChart Widget. class BarChartLeaf extends LeafRenderObjectWidget { const BarChartLeaf({Key? key, required this.data, required this.targetData}) diff --git a/lib/src/chart/line_chart/line_chart_renderer.dart b/lib/src/chart/line_chart/line_chart_renderer.dart index 06371cd38..a656bb9e7 100644 --- a/lib/src/chart/line_chart/line_chart_renderer.dart +++ b/lib/src/chart/line_chart/line_chart_renderer.dart @@ -7,6 +7,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; // coverage:ignore-start + /// Low level LineChart Widget. class LineChartLeaf extends LeafRenderObjectWidget { const LineChartLeaf({Key? key, required this.data, required this.targetData}) diff --git a/lib/src/chart/pie_chart/pie_chart_renderer.dart b/lib/src/chart/pie_chart/pie_chart_renderer.dart index ddbcb5449..52d12ea57 100644 --- a/lib/src/chart/pie_chart/pie_chart_renderer.dart +++ b/lib/src/chart/pie_chart/pie_chart_renderer.dart @@ -9,6 +9,7 @@ import 'package:flutter/services.dart'; import 'pie_chart_painter.dart'; // coverage:ignore-start + /// Low level PieChart Widget. class PieChartLeaf extends MultiChildRenderObjectWidget { PieChartLeaf({ diff --git a/lib/src/chart/radar_chart/radar_chart_renderer.dart b/lib/src/chart/radar_chart/radar_chart_renderer.dart index 0c528db47..a842ac7a8 100644 --- a/lib/src/chart/radar_chart/radar_chart_renderer.dart +++ b/lib/src/chart/radar_chart/radar_chart_renderer.dart @@ -7,6 +7,7 @@ import 'package:flutter/cupertino.dart'; import 'radar_chart_painter.dart'; // coverage:ignore-start + /// Low level RadarChart Widget. class RadarChartLeaf extends LeafRenderObjectWidget { const RadarChartLeaf({Key? key, required this.data, required this.targetData}) diff --git a/lib/src/chart/scatter_chart/scatter_chart_renderer.dart b/lib/src/chart/scatter_chart/scatter_chart_renderer.dart index 6244a93f5..722cb59d6 100644 --- a/lib/src/chart/scatter_chart/scatter_chart_renderer.dart +++ b/lib/src/chart/scatter_chart/scatter_chart_renderer.dart @@ -7,6 +7,7 @@ import 'package:flutter/cupertino.dart'; import 'scatter_chart_painter.dart'; // coverage:ignore-start + /// Low level ScatterChart Widget. class ScatterChartLeaf extends LeafRenderObjectWidget { const ScatterChartLeaf( diff --git a/test/chart/bar_chart/bar_chart_renderer_test.dart b/test/chart/bar_chart/bar_chart_renderer_test.dart index e68072d3d..1d0cf8230 100644 --- a/test/chart/bar_chart/bar_chart_renderer_test.dart +++ b/test/chart/bar_chart/bar_chart_renderer_test.dart @@ -98,5 +98,15 @@ void main() { expect(paintHolder.targetData, targetData); expect(paintHolder.textScale, textScale); }); + + test('test 4 check setters', () { + renderBarChart.data = targetData; + renderBarChart.targetData = data; + renderBarChart.textScale = 22; + + expect(renderBarChart.data, targetData); + expect(renderBarChart.targetData, data); + expect(renderBarChart.textScale, 22); + }); }); } diff --git a/test/chart/line_chart/line_chart_renderer_test.dart b/test/chart/line_chart/line_chart_renderer_test.dart index 9e1b23823..26ded046e 100644 --- a/test/chart/line_chart/line_chart_renderer_test.dart +++ b/test/chart/line_chart/line_chart_renderer_test.dart @@ -101,5 +101,15 @@ void main() { expect(paintHolder.targetData, targetData); expect(paintHolder.textScale, textScale); }); + + test('test 4 check setters', () { + renderLineChart.data = targetData; + renderLineChart.targetData = data; + renderLineChart.textScale = 22; + + expect(renderLineChart.data, targetData); + expect(renderLineChart.targetData, data); + expect(renderLineChart.textScale, 22); + }); }); } diff --git a/test/chart/pie_chart/pie_chart_renderer_test.dart b/test/chart/pie_chart/pie_chart_renderer_test.dart index 57e4adc24..3715a3bbe 100644 --- a/test/chart/pie_chart/pie_chart_renderer_test.dart +++ b/test/chart/pie_chart/pie_chart_renderer_test.dart @@ -86,5 +86,15 @@ void main() { expect(paintHolder.targetData, targetData); expect(paintHolder.textScale, textScale); }); + + test('test 4 check setters', () { + renderBarChart.data = targetData; + renderBarChart.targetData = data; + renderBarChart.textScale = 22; + + expect(renderBarChart.data, targetData); + expect(renderBarChart.targetData, data); + expect(renderBarChart.textScale, 22); + }); }); } diff --git a/test/chart/radar_chart/radar_chart_renderer_test.dart b/test/chart/radar_chart/radar_chart_renderer_test.dart index a66163305..f3ff5371e 100644 --- a/test/chart/radar_chart/radar_chart_renderer_test.dart +++ b/test/chart/radar_chart/radar_chart_renderer_test.dart @@ -26,7 +26,7 @@ void main() { const textScale = 4.0; MockBuildContext _mockBuildContext = MockBuildContext(); - RenderRadarChart renderBarChart = RenderRadarChart( + RenderRadarChart renderRadarChart = RenderRadarChart( _mockBuildContext, data, targetData, @@ -39,21 +39,21 @@ void main() { Size _mockSize = const Size(44, 44); when(_mockPaintingContext.canvas) .thenAnswer((realInvocation) => _mockCanvas); - renderBarChart.mockTestSize = _mockSize; - renderBarChart.painter = _mockPainter; + renderRadarChart.mockTestSize = _mockSize; + renderRadarChart.painter = _mockPainter; test('test 1 correct data set', () { - expect(renderBarChart.data == data, true); - expect(renderBarChart.data == targetData, false); - expect(renderBarChart.targetData == targetData, true); - expect(renderBarChart.textScale == textScale, true); - expect(renderBarChart.paintHolder.data == data, true); - expect(renderBarChart.paintHolder.targetData == targetData, true); - expect(renderBarChart.paintHolder.textScale == textScale, true); + expect(renderRadarChart.data == data, true); + expect(renderRadarChart.data == targetData, false); + expect(renderRadarChart.targetData == targetData, true); + expect(renderRadarChart.textScale == textScale, true); + expect(renderRadarChart.paintHolder.data == data, true); + expect(renderRadarChart.paintHolder.targetData == targetData, true); + expect(renderRadarChart.paintHolder.textScale == textScale, true); }); test('test 2 check paint function', () { - renderBarChart.paint(_mockPaintingContext, const Offset(10, 10)); + renderRadarChart.paint(_mockPaintingContext, const Offset(10, 10)); verify(_mockCanvas.save()).called(1); verify(_mockCanvas.translate(10, 10)).called(1); final result = verify(_mockPainter.paint(any, captureAny, captureAny)); @@ -83,7 +83,7 @@ void main() { return MockData.radarTouchedSpot; }); final touchResponse = - renderBarChart.getResponseAtLocation(MockData.offset1); + renderRadarChart.getResponseAtLocation(MockData.offset1); expect(touchResponse.touchedSpot, MockData.radarTouchedSpot); expect(results[0]['local_position'] as Offset, MockData.offset1); expect(results[0]['size'] as Size, _mockSize); @@ -92,5 +92,15 @@ void main() { expect(paintHolder.targetData, targetData); expect(paintHolder.textScale, textScale); }); + + test('test 4 check setters', () { + renderRadarChart.data = targetData; + renderRadarChart.targetData = data; + renderRadarChart.textScale = 22; + + expect(renderRadarChart.data, targetData); + expect(renderRadarChart.targetData, data); + expect(renderRadarChart.textScale, 22); + }); }); } diff --git a/test/chart/scatter_chart/scatter_chart_renderer_test.dart b/test/chart/scatter_chart/scatter_chart_renderer_test.dart index a0b3555bd..a34748d03 100644 --- a/test/chart/scatter_chart/scatter_chart_renderer_test.dart +++ b/test/chart/scatter_chart/scatter_chart_renderer_test.dart @@ -22,7 +22,7 @@ void main() { const textScale = 4.0; MockBuildContext _mockBuildContext = MockBuildContext(); - RenderScatterChart renderBarChart = RenderScatterChart( + RenderScatterChart renderScatterChart = RenderScatterChart( _mockBuildContext, data, targetData, @@ -35,21 +35,21 @@ void main() { Size _mockSize = const Size(44, 44); when(_mockPaintingContext.canvas) .thenAnswer((realInvocation) => _mockCanvas); - renderBarChart.mockTestSize = _mockSize; - renderBarChart.painter = _mockPainter; + renderScatterChart.mockTestSize = _mockSize; + renderScatterChart.painter = _mockPainter; test('test 1 correct data set', () { - expect(renderBarChart.data == data, true); - expect(renderBarChart.data == targetData, false); - expect(renderBarChart.targetData == targetData, true); - expect(renderBarChart.textScale == textScale, true); - expect(renderBarChart.paintHolder.data == data, true); - expect(renderBarChart.paintHolder.targetData == targetData, true); - expect(renderBarChart.paintHolder.textScale == textScale, true); + expect(renderScatterChart.data == data, true); + expect(renderScatterChart.data == targetData, false); + expect(renderScatterChart.targetData == targetData, true); + expect(renderScatterChart.textScale == textScale, true); + expect(renderScatterChart.paintHolder.data == data, true); + expect(renderScatterChart.paintHolder.targetData == targetData, true); + expect(renderScatterChart.paintHolder.textScale == textScale, true); }); test('test 2 check paint function', () { - renderBarChart.paint(_mockPaintingContext, const Offset(10, 10)); + renderScatterChart.paint(_mockPaintingContext, const Offset(10, 10)); verify(_mockCanvas.save()).called(1); verify(_mockCanvas.translate(10, 10)).called(1); final result = verify(_mockPainter.paint(any, captureAny, captureAny)); @@ -79,7 +79,7 @@ void main() { return MockData.scatterTouchedSpot; }); final touchResponse = - renderBarChart.getResponseAtLocation(MockData.offset1); + renderScatterChart.getResponseAtLocation(MockData.offset1); expect(touchResponse.touchedSpot, MockData.scatterTouchedSpot); expect(results[0]['local_position'] as Offset, MockData.offset1); expect(results[0]['size'] as Size, _mockSize); @@ -88,5 +88,15 @@ void main() { expect(paintHolder.targetData, targetData); expect(paintHolder.textScale, textScale); }); + + test('test 4 check setters', () { + renderScatterChart.data = targetData; + renderScatterChart.targetData = data; + renderScatterChart.textScale = 22; + + expect(renderScatterChart.data, targetData); + expect(renderScatterChart.targetData, data); + expect(renderScatterChart.textScale, 22); + }); }); } From 65b6d1b161d158f8a81c0a3130b96717c7fcba46 Mon Sep 17 00:00:00 2001 From: imaN Khoshabi Date: Fri, 28 Jan 2022 02:46:06 +0330 Subject: [PATCH 15/45] write some lerp unit tests --- .../base/axis_chart/axis_chart_data.dart | 3 + lib/src/utils/lerp.dart | 5 +- test/chart/data_pool.dart | 99 ++++- .../scatter_chart_painter_test.dart | 68 ++++ test/utils/lerp_test.dart | 364 ++++++++++++++++++ 5 files changed, 531 insertions(+), 8 deletions(-) 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 d0c29907b..a988e1a95 100644 --- a/lib/src/chart/base/axis_chart/axis_chart_data.dart +++ b/lib/src/chart/base/axis_chart/axis_chart_data.dart @@ -469,6 +469,9 @@ class FlSpot with EquatableMixin { /// Used for splitting lines, or maybe other concepts. static const FlSpot nullSpot = FlSpot(double.nan, double.nan); + /// Sets zero for x and y + static const FlSpot zero = FlSpot(0, 0); + /// Determines if [x] or [y] is null. bool isNull() => this == nullSpot; diff --git a/lib/src/utils/lerp.dart b/lib/src/utils/lerp.dart index a27048be6..f0c0f0093 100644 --- a/lib/src/utils/lerp.dart +++ b/lib/src/utils/lerp.dart @@ -43,7 +43,7 @@ double? lerpDoubleAllowInfinity(double? a, double? b, double t) { /// Lerps [double] list based on [t] value, check [Tween.lerp]. List? lerpDoubleList(List? a, List? b, double t) => - lerpList(a, b, t, lerp: _lerpNonNullDouble); + lerpList(a, b, t, lerp: lerpNonNullDouble); /// Lerps [int] list based on [t] value, check [Tween.lerp]. List? lerpIntList(List? a, List? b, double t) => @@ -52,7 +52,8 @@ List? lerpIntList(List? a, List? b, double t) => /// Lerps [int] list based on [t] value, check [Tween.lerp]. int lerpInt(int a, int b, double t) => (a + (b - a) * t).round(); -double _lerpNonNullDouble(double a, double b, double t) => lerpDouble(a, b, t)!; +@visibleForTesting +double lerpNonNullDouble(double a, double b, double t) => lerpDouble(a, b, t)!; /// Lerps [FlSpot] list based on [t] value, check [Tween.lerp]. List? lerpFlSpotList(List? a, List? b, double t) => diff --git a/test/chart/data_pool.dart b/test/chart/data_pool.dart index 451113651..b6f07289f 100644 --- a/test/chart/data_pool.dart +++ b/test/chart/data_pool.dart @@ -5,6 +5,7 @@ import 'package:fl_chart/src/chart/base/line.dart'; import 'package:flutter/material.dart'; class MockData { + static const color0 = Color(0x00000000); static const color1 = Color(0x11111111); static const color2 = Color(0x22222222); static const color3 = Color(0x33333333); @@ -156,9 +157,62 @@ class MockData { showingIndicators: [0, 4], ); - static const RadarEntry radarEntry1 = RadarEntry(value: 11); - static const RadarEntry radarEntry2 = RadarEntry(value: 22); - static const RadarEntry radarEntry3 = RadarEntry(value: 33); + static const flSpot0 = FlSpot.zero; + static const flSpot1 = FlSpot(1, 1); + static const flSpot2 = FlSpot(2, 2); + static const flSpot3 = FlSpot(3, 3); + static const flSpot4 = FlSpot(4, 4); + static const flSpot5 = FlSpot(5, 5); + + static final horizontalLine0 = HorizontalLine(y: 0, color: color0); + static final horizontalLine1 = HorizontalLine(y: 1, color: color1); + static final horizontalLine2 = HorizontalLine(y: 2, color: color2); + static final horizontalLine3 = HorizontalLine(y: 3, color: color3); + static final horizontalLine4 = HorizontalLine(y: 4, color: color4); + static final horizontalLine5 = HorizontalLine(y: 5, color: color5); + + static final verticalLine0 = VerticalLine(x: 0, color: color0); + static final verticalLine1 = VerticalLine(x: 1, color: color1); + static final verticalLine2 = VerticalLine(x: 2, color: color2); + static final verticalLine3 = VerticalLine(x: 3, color: color3); + static final verticalLine4 = VerticalLine(x: 4, color: color4); + static final verticalLine5 = VerticalLine(x: 5, color: color5); + + static final horizontalRangeAnnotation0 = + HorizontalRangeAnnotation(y1: 0, y2: 1, color: color0); + + static final horizontalRangeAnnotation1 = + HorizontalRangeAnnotation(y1: 1, y2: 2, color: color1); + + static final horizontalRangeAnnotation2 = + HorizontalRangeAnnotation(y1: 2, y2: 3, color: color2); + + static final horizontalRangeAnnotation3 = + HorizontalRangeAnnotation(y1: 3, y2: 4, color: color3); + + static final horizontalRangeAnnotation4 = + HorizontalRangeAnnotation(y1: 4, y2: 5, color: color4); + + static final verticalRangeAnnotation0 = + VerticalRangeAnnotation(x1: 0, x2: 1, color: color0); + + static final verticalRangeAnnotation1 = + VerticalRangeAnnotation(x1: 1, x2: 2, color: color1); + + static final verticalRangeAnnotation2 = + VerticalRangeAnnotation(x1: 2, x2: 3, color: color2); + + static final verticalRangeAnnotation3 = + VerticalRangeAnnotation(x1: 3, x2: 4, color: color3); + + static final verticalRangeAnnotation4 = + VerticalRangeAnnotation(x1: 4, x2: 5, color: color4); + + static const RadarEntry radarEntry0 = RadarEntry(value: 0); + static const RadarEntry radarEntry1 = RadarEntry(value: 1); + static const RadarEntry radarEntry2 = RadarEntry(value: 2); + static const RadarEntry radarEntry3 = RadarEntry(value: 3); + static const RadarEntry radarEntry4 = RadarEntry(value: 4); static final RadarDataSet radarDataSet1 = RadarDataSet( dataEntries: [radarEntry1, radarEntry2, radarEntry3], ); @@ -173,9 +227,42 @@ class MockData { flSpot1, offset1, ); - static final scatterSpot1 = ScatterSpot(1, 1); - static final scatterSpot2 = ScatterSpot(2, 2); - static final scatterSpot3 = ScatterSpot(3, 3); + + static final pieChartSection0 = PieChartSectionData( + value: 0, + color: color0, + radius: 0, + ); + + static final pieChartSection1 = PieChartSectionData( + value: 1, + color: color1, + radius: 1, + ); + + static final pieChartSection2 = PieChartSectionData( + value: 2, + color: color2, + radius: 2, + ); + + static final pieChartSection3 = PieChartSectionData( + value: 3, + color: color3, + radius: 3, + ); + + static final pieChartSection4 = PieChartSectionData( + value: 4, + color: color4, + radius: 4, + ); + + static final scatterSpot0 = ScatterSpot(0, 0, color: color0); + static final scatterSpot1 = ScatterSpot(1, 1, color: color1); + static final scatterSpot2 = ScatterSpot(2, 2, color: color2); + static final scatterSpot3 = ScatterSpot(3, 3, color: color3); + static final scatterSpot4 = ScatterSpot(4, 4, color: color4); static final scatterTouchedSpot = ScatterTouchedSpot(scatterSpot1, 0); diff --git a/test/chart/scatter_chart/scatter_chart_painter_test.dart b/test/chart/scatter_chart/scatter_chart_painter_test.dart index 056978fb3..3c1eabb3d 100644 --- a/test/chart/scatter_chart/scatter_chart_painter_test.dart +++ b/test/chart/scatter_chart/scatter_chart_painter_test.dart @@ -339,6 +339,72 @@ void main() { expect(bottomTitlesCalledValues.contains(0.0), true); expect(bottomTitlesCalledValues.contains(5.0), true); }); + + test('test 5', () { + const viewSize = Size(600, 400); + + List leftTitlesCalledValues = []; + String rightTitlesCallback(double value) { + leftTitlesCalledValues.add(value); + return value.toString(); + } + + List bottomTitlesCalledValues = []; + String topTitlesCallback(double value) { + bottomTitlesCalledValues.add(value); + return value.toString(); + } + + final ScatterChartData data = ScatterChartData( + minY: 0, + maxY: 20, + minX: 0, + maxX: 9, + titlesData: flTitlesData1.copyWith( + show: true, + leftTitles: SideTitles(showTitles: false), + bottomTitles: SideTitles(showTitles: false), + topTitles: SideTitles( + showTitles: true, + getTitles: topTitlesCallback, + ), + rightTitles: SideTitles( + showTitles: true, + getTitles: rightTitlesCallback, + ), + ), + ); + + final ScatterChartPainter scatterChartPainter = ScatterChartPainter(); + final holder = PaintHolder(data, data, 1.0); + MockCanvasWrapper _mockCanvasWrapper = MockCanvasWrapper(); + MockBuildContext _mockBuildContext = MockBuildContext(); + MockUtils _mockUtils = MockUtils(); + when(_mockCanvasWrapper.size).thenReturn(viewSize); + when(_mockCanvasWrapper.canvas).thenReturn(MockCanvas()); + Utils.changeInstance(_mockUtils); + when(_mockUtils.getEfficientInterval(any, any)).thenReturn(5); + + when(_mockUtils.formatNumber(any)).thenReturn("1"); + when(_mockUtils.calculateRotationOffset(any, any)) + .thenReturn(Offset.zero); + when(_mockUtils.getBestInitialIntervalValue(any, any, any)).thenReturn(0); + when(_mockUtils.getThemeAwareTextStyle(any, any)) + .thenReturn(const TextStyle(color: Color(0x00ffffff))); + scatterChartPainter.drawTitles( + _mockBuildContext, + _mockCanvasWrapper, + holder, + ); + verify(_mockCanvasWrapper.drawText(any, any, 0.0)).called(7); + expect(leftTitlesCalledValues.contains(0.0), true); + expect(leftTitlesCalledValues.contains(5.0), true); + expect(leftTitlesCalledValues.contains(10.0), true); + expect(leftTitlesCalledValues.contains(15.0), true); + expect(leftTitlesCalledValues.contains(20.0), true); + expect(bottomTitlesCalledValues.contains(0.0), true); + expect(bottomTitlesCalledValues.contains(5.0), true); + }); }); group('drawSpots()', () { @@ -616,6 +682,8 @@ void main() { rotateAngle: 18, tooltipBgColor: const Color(0xFFFFFF00), tooltipRoundedRadius: 22, + fitInsideHorizontally: true, + fitInsideVertically: true, tooltipPadding: const EdgeInsets.all(12), getTooltipItems: (_) { return ScatterTooltipItem( diff --git a/test/utils/lerp_test.dart b/test/utils/lerp_test.dart index d661327d5..b58b5ddfa 100644 --- a/test/utils/lerp_test.dart +++ b/test/utils/lerp_test.dart @@ -1,13 +1,377 @@ import 'dart:ui'; +import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/utils/lerp.dart'; import 'package:flutter_test/flutter_test.dart'; +import '../chart/data_pool.dart'; + void main() { + const tolerance = 0.001; + test('test lerpList', () { List list1 = [1.0, 1.0, 2.0]; List list2 = [1.0, 2.0, 3.0, 5.0]; expect(lerpList(list1, list2, 0.0, lerp: lerpDouble), [1.0, 1.0, 2.0, 5.0]); expect(lerpList(list1, list2, 0.5, lerp: lerpDouble), [1.0, 1.5, 2.5, 5.0]); + expect(lerpList(list1, list1, 0.5, lerp: lerpDouble), list1); + }); + + test('test lerpColorList', () { + List list1 = const [ + MockData.color1, + MockData.color1, + MockData.color2, + ]; + List list2 = const [ + MockData.color1, + MockData.color2, + MockData.color3, + MockData.color5, + ]; + expect(lerpColorList(list1, list2, 0.0), const [ + MockData.color1, + MockData.color1, + MockData.color2, + MockData.color5, + ]); + expect(lerpColorList(list1, list2, 1.0), list2); + expect(lerpColorList(list1, list2, 0.5), const [ + MockData.color1, + Color(0x19191919), + Color(0x2a2a2a2a), + MockData.color5, + ]); + }); + + test('test lerpColor', () { + expect(lerpColor(MockData.color1, MockData.color1, 0.5), MockData.color1); + expect(lerpColor(MockData.color1, MockData.color1, 0.0), MockData.color1); + expect(lerpColor(MockData.color1, MockData.color1, 1), MockData.color1); + + expect(lerpColor(MockData.color1, MockData.color2, 0), MockData.color1); + expect( + lerpColor(MockData.color1, MockData.color2, 0.3), + const Color(0x16161616), + ); + expect(lerpColor(MockData.color1, MockData.color2, 1), MockData.color2); + }); + + test('test lerpDoubleAllowInfinity', () { + expect(lerpDoubleAllowInfinity(12, 12, 0.0), 12); + expect(lerpDoubleAllowInfinity(12, 12, 0.2), 12); + expect(lerpDoubleAllowInfinity(12, 12, 0.5), 12); + expect(lerpDoubleAllowInfinity(12, 12, 1.0), 12); + + expect(lerpDoubleAllowInfinity(12, double.infinity, 1.0), double.infinity); + expect(lerpDoubleAllowInfinity(12, double.infinity, 0.0), double.infinity); + expect(lerpDoubleAllowInfinity(12, double.infinity, 0.4), double.infinity); + + expect(lerpDoubleAllowInfinity(double.infinity, 12, 1.0), 12); + expect(lerpDoubleAllowInfinity(double.infinity, 12, 0.0), 12); + expect(lerpDoubleAllowInfinity(double.infinity, 12, 0.4), 12); + + expect(lerpDoubleAllowInfinity(0, 10, 0.4), 4); + expect(lerpDoubleAllowInfinity(0, 10, 0.2), 2); + expect(lerpDoubleAllowInfinity(0, 10, 0.8), 8); + }); + + test('test lerpDoubleList', () { + List list1 = const [ + 0, + 0, + 0, + ]; + List list2 = const [ + 10, + 100, + 1000, + 10000, + ]; + expect(lerpDoubleList(list1, list2, 0.0), const [ + 0, + 0, + 0, + 10000, + ]); + expect(lerpDoubleList(list1, list2, 0.5), [ + 5, + 50, + 500, + 10000, + ]); + expect(lerpDoubleList(list1, list2, 1.0), list2); + }); + + test('test lerpIntList', () { + List list1 = const [ + 0, + 0, + 0, + ]; + List list2 = const [ + 10, + 100, + 1000, + 10000, + ]; + expect(lerpIntList(list1, list2, 0.0), const [ + 0, + 0, + 0, + 10000, + ]); + expect(lerpIntList(list1, list2, 0.5), [ + 5, + 50, + 500, + 10000, + ]); + expect(lerpIntList(list1, list2, 1.0), list2); + }); + + test('test lerpInt', () { + expect(lerpInt(0, 10, 1.0), 10); + expect(lerpInt(0, 10, 0.34), 3); + expect(lerpInt(0, 10, 0.38), 4); + }); + + test('test lerpNonNullDouble', () { + expect(lerpNonNullDouble(0, 10, 1.0), 10); + expect(lerpNonNullDouble(0, 10, 0.34), closeTo(3.4, tolerance)); + expect(lerpNonNullDouble(0, 10, 0.38), closeTo(3.8, tolerance)); + }); + + test('test lerpFlSpotList', () { + final list1 = [ + MockData.flSpot0, + MockData.flSpot0, + MockData.flSpot0, + ]; + final list2 = [ + MockData.flSpot1, + MockData.flSpot2, + MockData.flSpot3, + MockData.flSpot4, + ]; + expect(lerpFlSpotList(list1, list2, 0.0), [ + MockData.flSpot0, + MockData.flSpot0, + MockData.flSpot0, + MockData.flSpot4, + ]); + expect(lerpFlSpotList(list1, list2, 0.5), [ + const FlSpot(0.5, 0.5), + MockData.flSpot1, + const FlSpot(1.5, 1.5), + MockData.flSpot4, + ]); + expect(lerpFlSpotList(list1, list2, 1.0), list2); + }); + + test('test lerpHorizontalLineList', () { + final list1 = [ + MockData.horizontalLine0, + MockData.horizontalLine0, + MockData.horizontalLine0, + ]; + final list2 = [ + MockData.horizontalLine1, + MockData.horizontalLine2, + MockData.horizontalLine3, + MockData.horizontalLine4, + ]; + expect(lerpHorizontalLineList(list1, list2, 0.0), [ + MockData.horizontalLine0, + MockData.horizontalLine0, + MockData.horizontalLine0, + MockData.horizontalLine4, + ]); + expect(lerpHorizontalLineList(list1, list2, 0.5), [ + HorizontalLine(y: 0.5, color: const Color(0x08080808)), + MockData.horizontalLine1, + HorizontalLine(y: 1.5, color: const Color(0x19191919)), + MockData.horizontalLine4, + ]); + expect(lerpHorizontalLineList(list1, list2, 1.0), list2); + }); + + test('test lerpVerticalLineList', () { + final list1 = [ + MockData.verticalLine0, + MockData.verticalLine0, + MockData.verticalLine0, + ]; + final list2 = [ + MockData.verticalLine1, + MockData.verticalLine2, + MockData.verticalLine3, + MockData.verticalLine4, + ]; + expect(lerpVerticalLineList(list1, list2, 0.0), [ + MockData.verticalLine0, + MockData.verticalLine0, + MockData.verticalLine0, + MockData.verticalLine4, + ]); + expect(lerpVerticalLineList(list1, list2, 0.5), [ + VerticalLine(x: 0.5, color: const Color(0x08080808)), + MockData.verticalLine1, + VerticalLine(x: 1.5, color: const Color(0x19191919)), + MockData.verticalLine4, + ]); + expect(lerpVerticalLineList(list1, list2, 1.0), list2); + }); + + test('test lerpHorizontalRangeAnnotationList', () { + final list1 = [ + MockData.horizontalRangeAnnotation0, + MockData.horizontalRangeAnnotation0, + MockData.horizontalRangeAnnotation0, + ]; + final list2 = [ + MockData.horizontalRangeAnnotation1, + MockData.horizontalRangeAnnotation2, + MockData.horizontalRangeAnnotation3, + MockData.horizontalRangeAnnotation4, + ]; + expect(lerpHorizontalRangeAnnotationList(list1, list2, 0.0), [ + MockData.horizontalRangeAnnotation0, + MockData.horizontalRangeAnnotation0, + MockData.horizontalRangeAnnotation0, + MockData.horizontalRangeAnnotation4, + ]); + expect(lerpHorizontalRangeAnnotationList(list1, list2, 0.5), [ + HorizontalRangeAnnotation( + y1: 0.5, y2: 1.5, color: const Color(0x08080808)), + MockData.horizontalRangeAnnotation1, + HorizontalRangeAnnotation( + y1: 1.5, y2: 2.5, color: const Color(0x19191919)), + MockData.horizontalRangeAnnotation4, + ]); + expect(lerpHorizontalRangeAnnotationList(list1, list2, 1.0), list2); + }); + + test('test lerpVerticalRangeAnnotationList', () { + final list1 = [ + MockData.verticalRangeAnnotation0, + MockData.verticalRangeAnnotation0, + MockData.verticalRangeAnnotation0, + ]; + final list2 = [ + MockData.verticalRangeAnnotation1, + MockData.verticalRangeAnnotation2, + MockData.verticalRangeAnnotation3, + MockData.verticalRangeAnnotation4, + ]; + expect(lerpVerticalRangeAnnotationList(list1, list2, 0.0), [ + MockData.verticalRangeAnnotation0, + MockData.verticalRangeAnnotation0, + MockData.verticalRangeAnnotation0, + MockData.verticalRangeAnnotation4, + ]); + expect(lerpVerticalRangeAnnotationList(list1, list2, 0.5), [ + VerticalRangeAnnotation(x1: 0.5, x2: 1.5, color: const Color(0x08080808)), + MockData.verticalRangeAnnotation1, + VerticalRangeAnnotation(x1: 1.5, x2: 2.5, color: const Color(0x19191919)), + MockData.verticalRangeAnnotation4, + ]); + expect(lerpVerticalRangeAnnotationList(list1, list2, 1.0), list2); + }); + + test('test lerpBetweenBarsDataList', () { + final list1 = [ + MockData.verticalRangeAnnotation0, + MockData.verticalRangeAnnotation0, + MockData.verticalRangeAnnotation0, + ]; + final list2 = [ + MockData.verticalRangeAnnotation1, + MockData.verticalRangeAnnotation2, + MockData.verticalRangeAnnotation3, + MockData.verticalRangeAnnotation4, + ]; + expect(lerpVerticalRangeAnnotationList(list1, list2, 0.0), [ + MockData.verticalRangeAnnotation0, + MockData.verticalRangeAnnotation0, + MockData.verticalRangeAnnotation0, + MockData.verticalRangeAnnotation4, + ]); + expect(lerpVerticalRangeAnnotationList(list1, list2, 0.5), [ + VerticalRangeAnnotation(x1: 0.5, x2: 1.5, color: const Color(0x08080808)), + MockData.verticalRangeAnnotation1, + VerticalRangeAnnotation(x1: 1.5, x2: 2.5, color: const Color(0x19191919)), + MockData.verticalRangeAnnotation4, + ]); + expect(lerpVerticalRangeAnnotationList(list1, list2, 1.0), list2); + }); + + test('test lerpRadarEntryList', () { + final list1 = [ + MockData.radarEntry0, + MockData.radarEntry0, + MockData.radarEntry0, + ]; + final list2 = [ + MockData.radarEntry1, + MockData.radarEntry2, + MockData.radarEntry3, + MockData.radarEntry4, + ]; + expect(lerpRadarEntryList(list1, list2, 0.0), [ + MockData.radarEntry0, + MockData.radarEntry0, + MockData.radarEntry0, + MockData.radarEntry4, + ]); + expect(lerpRadarEntryList(list1, list2, 0.5), [ + const RadarEntry(value: 0.5), + MockData.radarEntry1, + const RadarEntry(value: 1.5), + MockData.radarEntry4, + ]); + expect(lerpRadarEntryList(list1, list2, 1.0), list2); + }); + + test('test lerpScatterSpotList', () { + final list1 = [ + MockData.scatterSpot0, + MockData.scatterSpot0, + MockData.scatterSpot0, + ]; + final list2 = [ + MockData.scatterSpot1, + MockData.scatterSpot2, + MockData.scatterSpot3, + MockData.scatterSpot4, + ]; + expect(lerpScatterSpotList(list1, list2, 0.0), [ + MockData.scatterSpot0, + MockData.scatterSpot0, + MockData.scatterSpot0, + MockData.scatterSpot4, + ]); + expect(lerpScatterSpotList(list1, list2, 0.5), [ + ScatterSpot(0.5, 0.5, color: const Color(0x08080808)), + MockData.scatterSpot1, + ScatterSpot(1.5, 1.5, color: const Color(0x19191919)), + MockData.scatterSpot4, + ]); + expect(lerpScatterSpotList(list1, list2, 1.0), list2); + }); + + test('test lerpGradient', () { + final colors = [ + MockData.color0, + MockData.color1, + MockData.color2, + MockData.color3, + ]; + expect(lerpGradient(colors, [], 0.0), const Color(0x00000000)); + expect(lerpGradient(colors, [], 0.2), const Color(0x00000000)); + expect(lerpGradient(colors, [], 0.4), const Color(0x0a0a0a0a)); + expect(lerpGradient(colors, [], 0.6), const Color(0x17171717)); + expect(lerpGradient(colors, [], 0.8), const Color(0x25252525)); + expect(lerpGradient(colors, [], 1), const Color(0x33333333)); }); } From ca2540af6670d1a67c8ccf084e15214297a9be7d Mon Sep 17 00:00:00 2001 From: imaN Khoshabi Date: Fri, 28 Jan 2022 02:58:59 +0330 Subject: [PATCH 16/45] Write bar_chart_extensions unit tests --- .../bar_chart/bar_chart_extensions_test.dart | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 test/chart/bar_chart/bar_chart_extensions_test.dart diff --git a/test/chart/bar_chart/bar_chart_extensions_test.dart b/test/chart/bar_chart/bar_chart_extensions_test.dart new file mode 100644 index 000000000..5bbbbd6bf --- /dev/null +++ b/test/chart/bar_chart/bar_chart_extensions_test.dart @@ -0,0 +1,98 @@ +import '../data_pool.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:fl_chart/src/chart/bar_chart/bar_chart_extensions.dart'; + +void main() { + group('test BackgroundBarChartRodData.getSafeColorStops()', () { + test('test 1', () { + final backgroundBarChartRodData = BackgroundBarChartRodData( + colors: [MockData.color1, MockData.color2], + colorStops: [0.2, 0.5], + ); + + expect(backgroundBarChartRodData.getSafeColorStops(), [0.2, 0.5]); + }); + + test('test 2', () { + final backgroundBarChartRodData = BackgroundBarChartRodData( + colors: [MockData.color1, MockData.color2], + colorStops: [0.2], + ); + + expect(backgroundBarChartRodData.getSafeColorStops(), [0.0, 1.0]); + }); + + test('test 3', () { + final backgroundBarChartRodData = BackgroundBarChartRodData( + colors: [MockData.color1, MockData.color2, MockData.color3], + colorStops: [0.2], + ); + + expect(backgroundBarChartRodData.getSafeColorStops(), [0.0, 0.5, 1.0]); + }); + + test('test 4', () { + final backgroundBarChartRodData = BackgroundBarChartRodData( + colors: [], + colorStops: [0.2], + ); + + var threwException = false; + try { + backgroundBarChartRodData.getSafeColorStops(); + } on ArgumentError { + threwException = true; + } + expect(threwException, true); + }); + }); + + group('test BarChartRodData.getSafeColorStops()', () { + test('test 1', () { + final barChartRodData = BarChartRodData( + y: 10, + colors: [MockData.color1, MockData.color2], + gradientColorStops: [0.2, 0.5], + ); + + expect(barChartRodData.getSafeColorStops(), [0.2, 0.5]); + }); + + test('test 2', () { + final barChartRodData = BarChartRodData( + y: 10, + colors: [MockData.color1, MockData.color2], + gradientColorStops: [0.2], + ); + + expect(barChartRodData.getSafeColorStops(), [0.0, 1.0]); + }); + + test('test 3', () { + final barChartRodData = BarChartRodData( + y: 10, + colors: [MockData.color1, MockData.color2, MockData.color3], + gradientColorStops: [0.2], + ); + + expect(barChartRodData.getSafeColorStops(), [0.0, 0.5, 1.0]); + }); + + test('test 4', () { + final barChartRodData = BarChartRodData( + y: 10, + colors: [], + gradientColorStops: [0.2], + ); + + var threwException = false; + try { + barChartRodData.getSafeColorStops(); + } on ArgumentError { + threwException = true; + } + expect(threwException, true); + }); + }); +} From 4d270fbab7fd3fce392325d45dbe2c23f08ed93c Mon Sep 17 00:00:00 2001 From: imaN Khoshabi Date: Fri, 28 Jan 2022 16:53:55 +0330 Subject: [PATCH 17/45] Fix PieChart changing sections issue (we have disabled semantics for pieChart badgeWidgets), #861 --- CHANGELOG.md | 3 ++ lib/src/chart/pie_chart/pie_chart_data.dart | 4 +-- lib/src/chart/pie_chart/pie_chart_helper.dart | 21 ++++++++++++ .../chart/pie_chart/pie_chart_renderer.dart | 17 +++++++--- .../pie_chart/pie_chart_helper_test.dart | 34 +++++++++++++++++++ 5 files changed, 73 insertions(+), 6 deletions(-) create mode 100644 lib/src/chart/pie_chart/pie_chart_helper.dart create mode 100644 test/chart/pie_chart/pie_chart_helper_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index ded0b173e..96dd9873d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## newVersion +* **BUGFIX** Fix PieChart changing sections issue (we have disabled semantics for pieChart badgeWidgets), #861. + ## 0.41.0 * **BUGFIX** Fix getNearestTouchedSpot. Previously it returned the first occurrence of a spot within the threshold, and not the nearest, #641, #645. * **FEATURE** Add `textAlign` property in the [SideTitles](https://github.com/imaNNeoFighT/fl_chart/blob/master/repo_files/documentations/base_chart.md#sidetitles), #784. diff --git a/lib/src/chart/pie_chart/pie_chart_data.dart b/lib/src/chart/pie_chart/pie_chart_data.dart index 9a9303572..2dd02ee89 100644 --- a/lib/src/chart/pie_chart/pie_chart_data.dart +++ b/lib/src/chart/pie_chart/pie_chart_data.dart @@ -152,7 +152,7 @@ class PieChartSectionData { /// /// This can be anything from a text, an image, an animation, and even a combination of widgets. /// Use AnimatedWidgets to animate this widget. - final Widget badgeWidget; + final Widget? badgeWidget; /// Defines position of showing title in the section. /// @@ -205,7 +205,7 @@ class PieChartSectionData { titleStyle = titleStyle, title = title ?? (value == null ? '' : value.toString()), borderSide = borderSide ?? const BorderSide(width: 0), - badgeWidget = badgeWidget ?? Container(), + badgeWidget = badgeWidget, titlePositionPercentageOffset = titlePositionPercentageOffset ?? 0.5, badgePositionPercentageOffset = badgePositionPercentageOffset ?? 0.5; diff --git a/lib/src/chart/pie_chart/pie_chart_helper.dart b/lib/src/chart/pie_chart/pie_chart_helper.dart new file mode 100644 index 000000000..1ae4456bd --- /dev/null +++ b/lib/src/chart/pie_chart/pie_chart_helper.dart @@ -0,0 +1,21 @@ +import 'package:fl_chart/src/chart/pie_chart/pie_chart_data.dart'; +import 'package:flutter/widgets.dart'; + +extension PieChartSectionDataListExtension on List { + List toWidgets() { + List widgets = List.filled(length, Container()); + var allWidgetsAreNull = true; + asMap().entries.forEach((e) { + final index = e.key; + final section = e.value; + if (section.badgeWidget != null) { + widgets[index] = section.badgeWidget!; + allWidgetsAreNull = false; + } + }); + if (allWidgetsAreNull) { + return List.empty(); + } + return widgets; + } +} diff --git a/lib/src/chart/pie_chart/pie_chart_renderer.dart b/lib/src/chart/pie_chart/pie_chart_renderer.dart index 52d12ea57..208ecfb45 100644 --- a/lib/src/chart/pie_chart/pie_chart_renderer.dart +++ b/lib/src/chart/pie_chart/pie_chart_renderer.dart @@ -1,6 +1,7 @@ import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; import 'package:fl_chart/src/chart/base/base_chart/render_base_chart.dart'; +import 'package:fl_chart/src/chart/pie_chart/pie_chart_helper.dart'; import 'package:fl_chart/src/utils/canvas_wrapper.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/rendering.dart'; @@ -16,10 +17,7 @@ class PieChartLeaf extends MultiChildRenderObjectWidget { Key? key, required this.data, required this.targetData, - }) : super( - key: key, - children: targetData.sections.map((e) => e.badgeWidget).toList(), - ); + }) : super(key: key, children: targetData.sections.toWidgets()); final PieChartData data, targetData; @@ -156,4 +154,15 @@ class RenderPieChart extends RenderBaseChart ); return PieTouchResponse(pieSection); } + + @override + void visitChildrenForSemantics(RenderObjectVisitor visitor) { + /// It produces an error when we change the sections list, Check this issue: + /// https://github.com/imaNNeoFighT/fl_chart/issues/861 + /// + /// Below is the error message: + /// Updated layout information required for RenderSemanticsAnnotations#f3b96 NEEDS-LAYOUT NEEDS-PAINT to calculate semantics. + /// + /// I don't know how to solve this error. That's why we disabled semantics for now. + } } diff --git a/test/chart/pie_chart/pie_chart_helper_test.dart b/test/chart/pie_chart/pie_chart_helper_test.dart new file mode 100644 index 000000000..374a572a1 --- /dev/null +++ b/test/chart/pie_chart/pie_chart_helper_test.dart @@ -0,0 +1,34 @@ +import 'package:flutter/cupertino.dart'; + +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:fl_chart/src/chart/pie_chart/pie_chart_helper.dart'; + +void main() { + test('Test List.toWidgets()', () { + final widgets1 = [ + PieChartSectionData(value: 1), + PieChartSectionData(value: 2), + PieChartSectionData(value: 3), + ].toWidgets(); + expect(widgets1, List.empty()); + + final widgets2 = [ + PieChartSectionData(value: 1), + PieChartSectionData(value: 2, badgeWidget: const Text('asdf')), + PieChartSectionData(value: 3), + ].toWidgets(); + expect(widgets2[0] is Container, true); + expect(widgets2[1] is Text, true); + expect(widgets2[2] is Container, true); + + final widgets3 = [ + PieChartSectionData(value: 1, badgeWidget: const Text('1')), + PieChartSectionData(value: 2, badgeWidget: const Text('2')), + PieChartSectionData(value: 3, badgeWidget: const Text('3')), + ].toWidgets(); + expect((widgets3[0] as Text).data, '1'); + expect((widgets3[1] as Text).data, '2'); + expect((widgets3[2] as Text).data, '3'); + }); +} From 4c9192029322772dfd2a9737a3295dd2c5998073 Mon Sep 17 00:00:00 2001 From: imaN Khoshabi Date: Fri, 28 Jan 2022 17:47:41 +0330 Subject: [PATCH 18/45] Fix LineChart width smaller width or height lower than 40, #869, #857. --- CHANGELOG.md | 1 + lib/src/utils/utils.dart | 2 +- test/utils/utils_test.dart | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96dd9873d..81f4a3b04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## newVersion * **BUGFIX** Fix PieChart changing sections issue (we have disabled semantics for pieChart badgeWidgets), #861. +* **BUGFIX** Fix LineChart width smaller width or height lower than 40, #869, #857. ## 0.41.0 * **BUGFIX** Fix getNearestTouchedSpot. Previously it returned the first occurrence of a spot within the threshold, and not the nearest, #641, #645. diff --git a/lib/src/utils/utils.dart b/lib/src/utils/utils.dart index 0ae0ab6f0..9b4c6fbeb 100644 --- a/lib/src/utils/utils.dart +++ b/lib/src/utils/utils.dart @@ -128,7 +128,7 @@ class Utils { /// 1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 5000, 10000,... double getEfficientInterval(double axisViewSize, double diffInYAxis, {double pixelPerInterval = 40}) { - final allowedCount = axisViewSize ~/ pixelPerInterval; + final allowedCount = math.max(axisViewSize ~/ pixelPerInterval, 1); final accurateInterval = diffInYAxis / allowedCount; return roundInterval(accurateInterval); } diff --git a/test/utils/utils_test.dart b/test/utils/utils_test.dart index b8ca80281..0a6eba640 100644 --- a/test/utils/utils_test.dart +++ b/test/utils/utils_test.dart @@ -133,6 +133,7 @@ void main() { expect(Utils().getEfficientInterval(200, 0.5, pixelPerInterval: 20), 0.05); expect(Utils().getEfficientInterval(200, 1.0, pixelPerInterval: 20), 0.1); expect(Utils().getEfficientInterval(100, 0.5, pixelPerInterval: 20), 0.1); + expect(Utils().getEfficientInterval(10, 10), 10); }); test('test formatNumber', () { From 332e1f12a64e262693fcd91485fdd5b0f34222bc Mon Sep 17 00:00:00 2001 From: imaN Khoshabi Date: Sat, 29 Jan 2022 21:50:40 +0330 Subject: [PATCH 19/45] Allow to show titles when axis diff is zero, #842, #879 --- CHANGELOG.md | 1 + example/lib/main.dart | 153 ++++---- .../chart/bar_chart/bar_chart_painter.dart | 168 +++++---- .../base/axis_chart/axis_chart_data.dart | 6 - .../chart/line_chart/line_chart_painter.dart | 320 ++++++++--------- .../scatter_chart/scatter_chart_painter.dart | 332 +++++++++--------- lib/src/utils/utils.dart | 15 +- .../bar_chart/bar_chart_painter_test.dart | 13 + .../line_chart/line_chart_painter_test.dart | 23 +- test/utils/utils_test.dart | 1 + test/utils/utils_test.mocks.dart | 93 +++++ 11 files changed, 613 insertions(+), 512 deletions(-) create mode 100644 test/utils/utils_test.mocks.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 81f4a3b04..c31473b8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## newVersion * **BUGFIX** Fix PieChart changing sections issue (we have disabled semantics for pieChart badgeWidgets), #861. * **BUGFIX** Fix LineChart width smaller width or height lower than 40, #869, #857. +* **BUGFIX** Allow to show title when axis diff is zero. ## 0.41.0 * **BUGFIX** Fix getNearestTouchedSpot. Previously it returned the first occurrence of a spot within the threshold, and not the nearest, #641, #645. diff --git a/example/lib/main.dart b/example/lib/main.dart index 4072a60c8..0e010e042 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,18 +1,6 @@ -import 'package:example/radar_chart/radar_chart_page.dart'; -import 'package:example/scatter_chart/scatter_chart_page.dart'; +import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; -import 'bar_chart/bar_chart_page.dart'; -import 'bar_chart/bar_chart_page2.dart'; -import 'bar_chart/bar_chart_page3.dart'; -import 'line_chart/line_chart_page.dart'; -import 'line_chart/line_chart_page2.dart'; -import 'line_chart/line_chart_page3.dart'; -import 'line_chart/line_chart_page4.dart'; -import 'pie_chart/pie_chart_page.dart'; -import 'utils/platform_info.dart'; -import 'scatter_chart/scatter_chart_page.dart'; - void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { @@ -20,98 +8,99 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return MaterialApp( + return const MaterialApp( title: 'FlChart Demo', showPerformanceOverlay: false, - theme: ThemeData( - primaryColor: const Color(0xff262545), - primaryColorDark: const Color(0xff201f39), - brightness: Brightness.dark, - ), - home: const MyHomePage(title: 'fl_chart'), + home: MyHomePage(), ); } } class MyHomePage extends StatefulWidget { - const MyHomePage({Key? key, required this.title}) : super(key: key); - final String title; + const MyHomePage({Key? key}) : super(key: key); @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State { - int _currentPage = 0; + final List _showingTouchedTooltips = []; - final _controller = PageController(initialPage: 0); - final _duration = const Duration(milliseconds: 300); - final _curve = Curves.easeInOutCubic; - final _pages = const [ - LineChartPage(), - BarChartPage(), - BarChartPage2(), - PieChartPage(), - LineChartPage2(), - LineChartPage3(), - LineChartPage4(), - BarChartPage3(), - ScatterChartPage(), - RadarChartPage(), - ]; - - bool get isDesktopOrWeb => PlatformInfo().isDesktopOrWeb(); - - @override - void initState() { - super.initState(); - _controller.addListener(() { - setState(() { - _currentPage = _controller.page!.round(); - }); - }); - } + final Map> _showingTouchedIndicators = {}; @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( - child: PageView( - physics: isDesktopOrWeb - ? const NeverScrollableScrollPhysics() - : const AlwaysScrollableScrollPhysics(), - controller: _controller, - children: _pages, - ), - ), - bottomNavigationBar: isDesktopOrWeb - ? Container( - padding: const EdgeInsets.all(16), - color: Colors.transparent, - child: Row( - mainAxisSize: MainAxisSize.max, - children: [ - Visibility( - visible: _currentPage != 0, - child: FloatingActionButton( - onPressed: () => _controller.previousPage( - duration: _duration, curve: _curve), - child: const Icon(Icons.chevron_left_rounded), - ), + child: Center( + child: SizedBox( + width: 200, + height: 100, + child: LineChart( + LineChartData( + lineBarsData: [ + LineChartBarData( + colors: [Colors.green], + spots: const [ + FlSpot(0, 0), + FlSpot(1, 1), + FlSpot(2, 1), + ], + showingIndicators: _showingTouchedIndicators[0] ?? [], ), - const Spacer(), - Visibility( - visible: _currentPage != _pages.length - 1, - child: FloatingActionButton( - onPressed: () => _controller.nextPage( - duration: _duration, curve: _curve), - child: const Icon(Icons.chevron_right_rounded), - ), + LineChartBarData( + colors: [Colors.red], + spots: const [ + FlSpot(0, 3), + FlSpot(1, 2), + FlSpot(2, 0), + ], + showingIndicators: _showingTouchedIndicators[1] ?? [], ), ], + showingTooltipIndicators: _showingTouchedTooltips, + lineTouchData: LineTouchData( + enabled: true, + handleBuiltInTouches: false, + touchCallback: (event, touchResponse) { + if (!event.isInterestedForInteractions || + touchResponse?.lineBarSpots == null || + touchResponse!.lineBarSpots!.isEmpty) { + setState(() { + _showingTouchedTooltips.clear(); + _showingTouchedIndicators.clear(); + }); + return; + } + + const showOnlyOnLineIndex = 0; + setState(() { + final sortedLineSpots = List.of( + touchResponse.lineBarSpots!.where((element) => + element.barIndex == showOnlyOnLineIndex)); + sortedLineSpots + .sort((spot1, spot2) => spot2.y.compareTo(spot1.y)); + + _showingTouchedIndicators.clear(); + + final touchedBarSpot = + touchResponse.lineBarSpots![showOnlyOnLineIndex]; + final barPos = touchedBarSpot.barIndex; + _showingTouchedIndicators[barPos] = [ + touchedBarSpot.spotIndex + ]; + + _showingTouchedTooltips.clear(); + _showingTouchedTooltips + .add(ShowingTooltipIndicators(sortedLineSpots)); + }); + }, + ), ), - ) - : null, + ), + ), + ), + ), ); } } diff --git a/lib/src/chart/bar_chart/bar_chart_painter.dart b/lib/src/chart/bar_chart/bar_chart_painter.dart index 6d4d5d860..a82fe33dd 100644 --- a/lib/src/chart/bar_chart/bar_chart_painter.dart +++ b/lib/src/chart/bar_chart/bar_chart_painter.dart @@ -386,49 +386,47 @@ class BarChartPainter extends AxisChartPainter { final drawSize = getChartUsableDrawSize(viewSize, holder); // Left Titles - if (!data.isVerticalMinMaxIsZero) { - final leftTitles = targetData.titlesData.leftTitles; - final leftInterval = leftTitles.interval ?? - Utils().getEfficientInterval(viewSize.height, data.verticalDiff); - if (leftTitles.showTitles) { - var verticalSeek = Utils() - .getBestInitialIntervalValue(data.minY, data.maxY, leftInterval); - while (verticalSeek <= data.maxY) { - if (leftTitles.checkToShowTitle( - data.minY, data.maxY, leftTitles, leftInterval, verticalSeek)) { - var x = 0 + getLeftOffsetDrawSize(holder); - var y = getPixelY(verticalSeek, drawSize, holder); - - final text = leftTitles.getTitles(verticalSeek); - - final span = TextSpan( - style: Utils().getThemeAwareTextStyle( - context, leftTitles.getTextStyles(context, verticalSeek)), - text: text, - ); - final tp = TextPainter( - text: span, - textAlign: leftTitles.textAlign, - textDirection: leftTitles.textDirection, - textScaleFactor: holder.textScale, - ); - tp.layout( - maxWidth: leftTitles.reservedSize, - minWidth: leftTitles.reservedSize, - ); - x -= tp.width + leftTitles.margin; - y -= tp.height / 2; - x += Utils() - .calculateRotationOffset(tp.size, leftTitles.rotateAngle) - .dx; - canvasWrapper.drawText(tp, Offset(x, y), leftTitles.rotateAngle); - } - if (data.maxY - verticalSeek < leftInterval && - data.maxY != verticalSeek) { - verticalSeek = data.maxY; - } else { - verticalSeek += leftInterval; - } + final leftTitles = targetData.titlesData.leftTitles; + final leftInterval = leftTitles.interval ?? + Utils().getEfficientInterval(viewSize.height, data.verticalDiff); + if (leftTitles.showTitles) { + var verticalSeek = Utils() + .getBestInitialIntervalValue(data.minY, data.maxY, leftInterval); + while (verticalSeek <= data.maxY) { + if (leftTitles.checkToShowTitle( + data.minY, data.maxY, leftTitles, leftInterval, verticalSeek)) { + var x = 0 + getLeftOffsetDrawSize(holder); + var y = getPixelY(verticalSeek, drawSize, holder); + + final text = leftTitles.getTitles(verticalSeek); + + final span = TextSpan( + style: Utils().getThemeAwareTextStyle( + context, leftTitles.getTextStyles(context, verticalSeek)), + text: text, + ); + final tp = TextPainter( + text: span, + textAlign: leftTitles.textAlign, + textDirection: leftTitles.textDirection, + textScaleFactor: holder.textScale, + ); + tp.layout( + maxWidth: leftTitles.reservedSize, + minWidth: leftTitles.reservedSize, + ); + x -= tp.width + leftTitles.margin; + y -= tp.height / 2; + x += Utils() + .calculateRotationOffset(tp.size, leftTitles.rotateAngle) + .dx; + canvasWrapper.drawText(tp, Offset(x, y), leftTitles.rotateAngle); + } + if (data.maxY - verticalSeek < leftInterval && + data.maxY != verticalSeek) { + verticalSeek = data.maxY; + } else { + verticalSeek += leftInterval; } } } @@ -463,49 +461,47 @@ class BarChartPainter extends AxisChartPainter { } // Right Titles - if (!data.isVerticalMinMaxIsZero) { - final rightTitles = targetData.titlesData.rightTitles; - final rightInterval = rightTitles.interval ?? - Utils().getEfficientInterval(viewSize.height, data.verticalDiff); - if (rightTitles.showTitles) { - var verticalSeek = Utils() - .getBestInitialIntervalValue(data.minY, data.maxY, rightInterval); - while (verticalSeek <= data.maxY) { - if (rightTitles.checkToShowTitle( - data.minY, data.maxY, rightTitles, rightInterval, verticalSeek)) { - var x = drawSize.width + getLeftOffsetDrawSize(holder); - var y = getPixelY(verticalSeek, drawSize, holder); - - final text = rightTitles.getTitles(verticalSeek); - - final span = TextSpan( - style: Utils().getThemeAwareTextStyle( - context, rightTitles.getTextStyles(context, verticalSeek)), - text: text, - ); - final tp = TextPainter( - text: span, - textAlign: rightTitles.textAlign, - textDirection: rightTitles.textDirection, - textScaleFactor: holder.textScale, - ); - tp.layout( - maxWidth: rightTitles.reservedSize, - minWidth: rightTitles.reservedSize, - ); - x += rightTitles.margin; - y -= tp.height / 2; - x -= Utils() - .calculateRotationOffset(tp.size, rightTitles.rotateAngle) - .dx; - canvasWrapper.drawText(tp, Offset(x, y), rightTitles.rotateAngle); - } - if (data.maxY - verticalSeek < rightInterval && - data.maxY != verticalSeek) { - verticalSeek = data.maxY; - } else { - verticalSeek += rightInterval; - } + final rightTitles = targetData.titlesData.rightTitles; + final rightInterval = rightTitles.interval ?? + Utils().getEfficientInterval(viewSize.height, data.verticalDiff); + if (rightTitles.showTitles) { + var verticalSeek = Utils() + .getBestInitialIntervalValue(data.minY, data.maxY, rightInterval); + while (verticalSeek <= data.maxY) { + if (rightTitles.checkToShowTitle( + data.minY, data.maxY, rightTitles, rightInterval, verticalSeek)) { + var x = drawSize.width + getLeftOffsetDrawSize(holder); + var y = getPixelY(verticalSeek, drawSize, holder); + + final text = rightTitles.getTitles(verticalSeek); + + final span = TextSpan( + style: Utils().getThemeAwareTextStyle( + context, rightTitles.getTextStyles(context, verticalSeek)), + text: text, + ); + final tp = TextPainter( + text: span, + textAlign: rightTitles.textAlign, + textDirection: rightTitles.textDirection, + textScaleFactor: holder.textScale, + ); + tp.layout( + maxWidth: rightTitles.reservedSize, + minWidth: rightTitles.reservedSize, + ); + x += rightTitles.margin; + y -= tp.height / 2; + x -= Utils() + .calculateRotationOffset(tp.size, rightTitles.rotateAngle) + .dx; + canvasWrapper.drawText(tp, Offset(x, y), rightTitles.rotateAngle); + } + if (data.maxY - verticalSeek < rightInterval && + data.maxY != verticalSeek) { + verticalSeek = data.maxY; + } else { + verticalSeek += rightInterval; } } } 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 a988e1a95..9a1c71673 100644 --- a/lib/src/chart/base/axis_chart/axis_chart_data.dart +++ b/lib/src/chart/base/axis_chart/axis_chart_data.dart @@ -34,12 +34,6 @@ abstract class AxisChartData extends BaseChartData with EquatableMixin { /// Difference of [maxX] and [minX] double get horizontalDiff => maxX - minX; - /// Returns true if [minX] and [maxX] both are zero - bool get isHorizontalMinMaxIsZero => minX == 0 && maxX == 0; - - /// Returns true if [minY] and [maxY] both are zero - bool get isVerticalMinMaxIsZero => minY == 0 && maxY == 0; - AxisChartData({ FlGridData? gridData, required FlAxisTitleData axisTitleData, diff --git a/lib/src/chart/line_chart/line_chart_painter.dart b/lib/src/chart/line_chart/line_chart_painter.dart index a0cdba057..6cc82e3e8 100644 --- a/lib/src/chart/line_chart/line_chart_painter.dart +++ b/lib/src/chart/line_chart/line_chart_painter.dart @@ -895,188 +895,180 @@ class LineChartPainter extends AxisChartPainter { final viewSize = getChartUsableDrawSize(canvasWrapper.size, holder); // Left Titles - if (!data.isVerticalMinMaxIsZero) { - final leftTitles = targetData.titlesData.leftTitles; - final leftInterval = leftTitles.interval ?? - Utils().getEfficientInterval(viewSize.height, data.verticalDiff); - if (leftTitles.showTitles) { - var verticalSeek = Utils() - .getBestInitialIntervalValue(data.minY, data.maxY, leftInterval); - while (verticalSeek <= data.maxY) { - if (leftTitles.checkToShowTitle( - data.minY, data.maxY, leftTitles, leftInterval, verticalSeek)) { - var x = 0 + getLeftOffsetDrawSize(holder); - var y = getPixelY(verticalSeek, viewSize, holder); - - final text = leftTitles.getTitles(verticalSeek); - - final span = TextSpan( - style: Utils().getThemeAwareTextStyle( - context, leftTitles.getTextStyles(context, verticalSeek)), - text: text, - ); - final tp = TextPainter( - text: span, - textAlign: leftTitles.textAlign, - textDirection: leftTitles.textDirection, - textScaleFactor: holder.textScale, - ); - tp.layout( - maxWidth: leftTitles.reservedSize, - minWidth: leftTitles.reservedSize, - ); - x -= tp.width + leftTitles.margin; - y -= tp.height / 2; - x += Utils() - .calculateRotationOffset(tp.size, leftTitles.rotateAngle) - .dx; - canvasWrapper.drawText(tp, Offset(x, y), leftTitles.rotateAngle); - } - if (data.maxY - verticalSeek < leftInterval && - data.maxY != verticalSeek) { - verticalSeek = data.maxY; - } else { - verticalSeek += leftInterval; - } + final leftTitles = targetData.titlesData.leftTitles; + final leftInterval = leftTitles.interval ?? + Utils().getEfficientInterval(viewSize.height, data.verticalDiff); + if (leftTitles.showTitles) { + var verticalSeek = Utils() + .getBestInitialIntervalValue(data.minY, data.maxY, leftInterval); + while (verticalSeek <= data.maxY) { + if (leftTitles.checkToShowTitle( + data.minY, data.maxY, leftTitles, leftInterval, verticalSeek)) { + var x = 0 + getLeftOffsetDrawSize(holder); + var y = getPixelY(verticalSeek, viewSize, holder); + + final text = leftTitles.getTitles(verticalSeek); + + final span = TextSpan( + style: Utils().getThemeAwareTextStyle( + context, leftTitles.getTextStyles(context, verticalSeek)), + text: text, + ); + final tp = TextPainter( + text: span, + textAlign: leftTitles.textAlign, + textDirection: leftTitles.textDirection, + textScaleFactor: holder.textScale, + ); + tp.layout( + maxWidth: leftTitles.reservedSize, + minWidth: leftTitles.reservedSize, + ); + x -= tp.width + leftTitles.margin; + y -= tp.height / 2; + x += Utils() + .calculateRotationOffset(tp.size, leftTitles.rotateAngle) + .dx; + canvasWrapper.drawText(tp, Offset(x, y), leftTitles.rotateAngle); + } + if (data.maxY - verticalSeek < leftInterval && + data.maxY != verticalSeek) { + verticalSeek = data.maxY; + } else { + verticalSeek += leftInterval; } } } // Top titles - if (!data.isHorizontalMinMaxIsZero) { - final topTitles = targetData.titlesData.topTitles; - final topInterval = topTitles.interval ?? - Utils().getEfficientInterval(viewSize.width, data.horizontalDiff); - if (topTitles.showTitles) { - var horizontalSeek = Utils() - .getBestInitialIntervalValue(data.minX, data.maxX, topInterval); - while (horizontalSeek <= data.maxX) { - if (topTitles.checkToShowTitle( - data.minX, data.maxX, topTitles, topInterval, horizontalSeek)) { - var x = getPixelX(horizontalSeek, viewSize, holder); - var y = getTopOffsetDrawSize(holder); - - final text = topTitles.getTitles(horizontalSeek); - - final span = TextSpan( - style: Utils().getThemeAwareTextStyle( - context, topTitles.getTextStyles(context, horizontalSeek)), - text: text, - ); - final tp = TextPainter( - text: span, - textAlign: topTitles.textAlign, - textDirection: topTitles.textDirection, - textScaleFactor: holder.textScale, - ); - tp.layout(); - - x -= tp.width / 2; - y -= topTitles.margin + tp.height; - y += Utils() - .calculateRotationOffset(tp.size, topTitles.rotateAngle) - .dy; - canvasWrapper.drawText(tp, Offset(x, y), topTitles.rotateAngle); - } - if (data.maxX - horizontalSeek < topInterval && - data.maxX != horizontalSeek) { - horizontalSeek = data.maxX; - } else { - horizontalSeek += topInterval; - } + final topTitles = targetData.titlesData.topTitles; + final topInterval = topTitles.interval ?? + Utils().getEfficientInterval(viewSize.width, data.horizontalDiff); + if (topTitles.showTitles) { + var horizontalSeek = Utils() + .getBestInitialIntervalValue(data.minX, data.maxX, topInterval); + while (horizontalSeek <= data.maxX) { + if (topTitles.checkToShowTitle( + data.minX, data.maxX, topTitles, topInterval, horizontalSeek)) { + var x = getPixelX(horizontalSeek, viewSize, holder); + var y = getTopOffsetDrawSize(holder); + + final text = topTitles.getTitles(horizontalSeek); + + final span = TextSpan( + style: Utils().getThemeAwareTextStyle( + context, topTitles.getTextStyles(context, horizontalSeek)), + text: text, + ); + final tp = TextPainter( + text: span, + textAlign: topTitles.textAlign, + textDirection: topTitles.textDirection, + textScaleFactor: holder.textScale, + ); + tp.layout(); + + x -= tp.width / 2; + y -= topTitles.margin + tp.height; + y += Utils() + .calculateRotationOffset(tp.size, topTitles.rotateAngle) + .dy; + canvasWrapper.drawText(tp, Offset(x, y), topTitles.rotateAngle); + } + if (data.maxX - horizontalSeek < topInterval && + data.maxX != horizontalSeek) { + horizontalSeek = data.maxX; + } else { + horizontalSeek += topInterval; } } } // Right Titles - if (!data.isVerticalMinMaxIsZero) { - final rightTitles = targetData.titlesData.rightTitles; - final rightInterval = rightTitles.interval ?? - Utils().getEfficientInterval(viewSize.height, data.verticalDiff); - if (rightTitles.showTitles) { - var verticalSeek = Utils() - .getBestInitialIntervalValue(data.minY, data.maxY, rightInterval); - while (verticalSeek <= data.maxY) { - if (rightTitles.checkToShowTitle( - data.minY, data.maxY, rightTitles, rightInterval, verticalSeek)) { - var x = viewSize.width + getLeftOffsetDrawSize(holder); - var y = getPixelY(verticalSeek, viewSize, holder); - - final text = rightTitles.getTitles(verticalSeek); - - final span = TextSpan( - style: Utils().getThemeAwareTextStyle( - context, rightTitles.getTextStyles(context, verticalSeek)), - text: text, - ); - final tp = TextPainter( - text: span, - textAlign: rightTitles.textAlign, - textDirection: rightTitles.textDirection, - textScaleFactor: holder.textScale, - ); - tp.layout( - maxWidth: rightTitles.reservedSize, - minWidth: rightTitles.reservedSize, - ); + final rightTitles = targetData.titlesData.rightTitles; + final rightInterval = rightTitles.interval ?? + Utils().getEfficientInterval(viewSize.height, data.verticalDiff); + if (rightTitles.showTitles) { + var verticalSeek = Utils() + .getBestInitialIntervalValue(data.minY, data.maxY, rightInterval); + while (verticalSeek <= data.maxY) { + if (rightTitles.checkToShowTitle( + data.minY, data.maxY, rightTitles, rightInterval, verticalSeek)) { + var x = viewSize.width + getLeftOffsetDrawSize(holder); + var y = getPixelY(verticalSeek, viewSize, holder); + + final text = rightTitles.getTitles(verticalSeek); - x += rightTitles.margin; - y -= tp.height / 2; - x -= Utils() - .calculateRotationOffset(tp.size, rightTitles.rotateAngle) - .dx; - canvasWrapper.drawText(tp, Offset(x, y), rightTitles.rotateAngle); - } + final span = TextSpan( + style: Utils().getThemeAwareTextStyle( + context, rightTitles.getTextStyles(context, verticalSeek)), + text: text, + ); + final tp = TextPainter( + text: span, + textAlign: rightTitles.textAlign, + textDirection: rightTitles.textDirection, + textScaleFactor: holder.textScale, + ); + tp.layout( + maxWidth: rightTitles.reservedSize, + minWidth: rightTitles.reservedSize, + ); - if (data.maxY - verticalSeek < rightInterval && - data.maxY != verticalSeek) { - verticalSeek = data.maxY; - } else { - verticalSeek += rightInterval; - } + x += rightTitles.margin; + y -= tp.height / 2; + x -= Utils() + .calculateRotationOffset(tp.size, rightTitles.rotateAngle) + .dx; + canvasWrapper.drawText(tp, Offset(x, y), rightTitles.rotateAngle); + } + + if (data.maxY - verticalSeek < rightInterval && + data.maxY != verticalSeek) { + verticalSeek = data.maxY; + } else { + verticalSeek += rightInterval; } } } // Bottom titles - if (!data.isHorizontalMinMaxIsZero) { - final bottomTitles = targetData.titlesData.bottomTitles; - final bottomInterval = bottomTitles.interval ?? - Utils().getEfficientInterval(viewSize.width, data.horizontalDiff); - if (bottomTitles.showTitles) { - var horizontalSeek = Utils() - .getBestInitialIntervalValue(data.minX, data.maxX, bottomInterval); - while (horizontalSeek <= data.maxX) { - if (bottomTitles.checkToShowTitle(data.minX, data.maxX, bottomTitles, - bottomInterval, horizontalSeek)) { - var x = getPixelX(horizontalSeek, viewSize, holder); - var y = viewSize.height + getTopOffsetDrawSize(holder); - final text = bottomTitles.getTitles(horizontalSeek); - final span = TextSpan( - style: Utils().getThemeAwareTextStyle(context, - bottomTitles.getTextStyles(context, horizontalSeek)), - text: text); - final tp = TextPainter( - text: span, - textAlign: bottomTitles.textAlign, - textDirection: bottomTitles.textDirection, - textScaleFactor: holder.textScale); - tp.layout(); - - x -= tp.width / 2; - y += bottomTitles.margin; - y -= Utils() - .calculateRotationOffset(tp.size, bottomTitles.rotateAngle) - .dy; - canvasWrapper.drawText(tp, Offset(x, y), bottomTitles.rotateAngle); - } + final bottomTitles = targetData.titlesData.bottomTitles; + final bottomInterval = bottomTitles.interval ?? + Utils().getEfficientInterval(viewSize.width, data.horizontalDiff); + if (bottomTitles.showTitles) { + var horizontalSeek = Utils() + .getBestInitialIntervalValue(data.minX, data.maxX, bottomInterval); + while (horizontalSeek <= data.maxX) { + if (bottomTitles.checkToShowTitle(data.minX, data.maxX, bottomTitles, + bottomInterval, horizontalSeek)) { + var x = getPixelX(horizontalSeek, viewSize, holder); + var y = viewSize.height + getTopOffsetDrawSize(holder); + final text = bottomTitles.getTitles(horizontalSeek); + final span = TextSpan( + style: Utils().getThemeAwareTextStyle( + context, bottomTitles.getTextStyles(context, horizontalSeek)), + text: text); + final tp = TextPainter( + text: span, + textAlign: bottomTitles.textAlign, + textDirection: bottomTitles.textDirection, + textScaleFactor: holder.textScale); + tp.layout(); - if (data.maxX - horizontalSeek < bottomInterval && - data.maxX != horizontalSeek) { - horizontalSeek = data.maxX; - } else { - horizontalSeek += bottomInterval; - } + x -= tp.width / 2; + y += bottomTitles.margin; + y -= Utils() + .calculateRotationOffset(tp.size, bottomTitles.rotateAngle) + .dy; + canvasWrapper.drawText(tp, Offset(x, y), bottomTitles.rotateAngle); + } + + if (data.maxX - horizontalSeek < bottomInterval && + data.maxX != horizontalSeek) { + horizontalSeek = data.maxX; + } else { + horizontalSeek += bottomInterval; } } } diff --git a/lib/src/chart/scatter_chart/scatter_chart_painter.dart b/lib/src/chart/scatter_chart/scatter_chart_painter.dart index bdf145ac3..887c49cc4 100644 --- a/lib/src/chart/scatter_chart/scatter_chart_painter.dart +++ b/lib/src/chart/scatter_chart/scatter_chart_painter.dart @@ -50,191 +50,183 @@ class ScatterChartPainter extends AxisChartPainter { final viewSize = getChartUsableDrawSize(canvasWrapper.size, holder); // Left Titles - if (!data.isVerticalMinMaxIsZero) { - final leftTitles = targetData.titlesData.leftTitles; - final leftInterval = leftTitles.interval ?? - Utils().getEfficientInterval(viewSize.height, data.verticalDiff); - if (leftTitles.showTitles) { - var verticalSeek = Utils() - .getBestInitialIntervalValue(data.minY, data.maxY, leftInterval); - while (verticalSeek <= data.maxY) { - if (leftTitles.checkToShowTitle( - data.minY, data.maxY, leftTitles, leftInterval, verticalSeek)) { - var x = 0 + getLeftOffsetDrawSize(holder); - var y = getPixelY(verticalSeek, viewSize, holder); - - final text = leftTitles.getTitles(verticalSeek); - - final span = TextSpan( - style: Utils().getThemeAwareTextStyle( - context, leftTitles.getTextStyles(context, verticalSeek)), - text: text, - ); - final tp = TextPainter( - text: span, - textAlign: leftTitles.textAlign, - textDirection: leftTitles.textDirection, - textScaleFactor: holder.textScale, - ); - tp.layout( - maxWidth: leftTitles.reservedSize, - minWidth: leftTitles.reservedSize, - ); - x -= tp.width + leftTitles.margin; - y -= tp.height / 2; - - x += Utils() - .calculateRotationOffset(tp.size, leftTitles.rotateAngle) - .dx; - canvasWrapper.drawText(tp, Offset(x, y), leftTitles.rotateAngle); - } - if (data.maxY - verticalSeek < leftInterval && - data.maxY != verticalSeek) { - verticalSeek = data.maxY; - } else { - verticalSeek += leftInterval; - } + final leftTitles = targetData.titlesData.leftTitles; + final leftInterval = leftTitles.interval ?? + Utils().getEfficientInterval(viewSize.height, data.verticalDiff); + if (leftTitles.showTitles) { + var verticalSeek = Utils() + .getBestInitialIntervalValue(data.minY, data.maxY, leftInterval); + while (verticalSeek <= data.maxY) { + if (leftTitles.checkToShowTitle( + data.minY, data.maxY, leftTitles, leftInterval, verticalSeek)) { + var x = 0 + getLeftOffsetDrawSize(holder); + var y = getPixelY(verticalSeek, viewSize, holder); + + final text = leftTitles.getTitles(verticalSeek); + + final span = TextSpan( + style: Utils().getThemeAwareTextStyle( + context, leftTitles.getTextStyles(context, verticalSeek)), + text: text, + ); + final tp = TextPainter( + text: span, + textAlign: leftTitles.textAlign, + textDirection: leftTitles.textDirection, + textScaleFactor: holder.textScale, + ); + tp.layout( + maxWidth: leftTitles.reservedSize, + minWidth: leftTitles.reservedSize, + ); + x -= tp.width + leftTitles.margin; + y -= tp.height / 2; + + x += Utils() + .calculateRotationOffset(tp.size, leftTitles.rotateAngle) + .dx; + canvasWrapper.drawText(tp, Offset(x, y), leftTitles.rotateAngle); + } + if (data.maxY - verticalSeek < leftInterval && + data.maxY != verticalSeek) { + verticalSeek = data.maxY; + } else { + verticalSeek += leftInterval; } } } // Top titles - if (!data.isHorizontalMinMaxIsZero) { - final topTitles = targetData.titlesData.topTitles; - final topInterval = topTitles.interval ?? - Utils().getEfficientInterval(viewSize.width, data.horizontalDiff); - if (topTitles.showTitles) { - var horizontalSeek = Utils() - .getBestInitialIntervalValue(data.minX, data.maxX, topInterval); - while (horizontalSeek <= data.maxX) { - if (topTitles.checkToShowTitle( - data.minX, data.maxX, topTitles, topInterval, horizontalSeek)) { - var x = getPixelX(horizontalSeek, viewSize, holder); - var y = getTopOffsetDrawSize(holder); - - final text = topTitles.getTitles(horizontalSeek); - - final span = TextSpan( - style: Utils().getThemeAwareTextStyle( - context, topTitles.getTextStyles(context, horizontalSeek)), - text: text, - ); - final tp = TextPainter( - text: span, - textAlign: topTitles.textAlign, - textDirection: topTitles.textDirection, - textScaleFactor: holder.textScale, - ); - tp.layout(); - - x -= tp.width / 2; - y -= topTitles.margin + tp.height; - y += Utils() - .calculateRotationOffset(tp.size, topTitles.rotateAngle) - .dy; - canvasWrapper.drawText(tp, Offset(x, y), topTitles.rotateAngle); - } - if (data.maxX - horizontalSeek < topInterval && - data.maxX != horizontalSeek) { - horizontalSeek = data.maxX; - } else { - horizontalSeek += topInterval; - } + final topTitles = targetData.titlesData.topTitles; + final topInterval = topTitles.interval ?? + Utils().getEfficientInterval(viewSize.width, data.horizontalDiff); + if (topTitles.showTitles) { + var horizontalSeek = Utils() + .getBestInitialIntervalValue(data.minX, data.maxX, topInterval); + while (horizontalSeek <= data.maxX) { + if (topTitles.checkToShowTitle( + data.minX, data.maxX, topTitles, topInterval, horizontalSeek)) { + var x = getPixelX(horizontalSeek, viewSize, holder); + var y = getTopOffsetDrawSize(holder); + + final text = topTitles.getTitles(horizontalSeek); + + final span = TextSpan( + style: Utils().getThemeAwareTextStyle( + context, topTitles.getTextStyles(context, horizontalSeek)), + text: text, + ); + final tp = TextPainter( + text: span, + textAlign: topTitles.textAlign, + textDirection: topTitles.textDirection, + textScaleFactor: holder.textScale, + ); + tp.layout(); + + x -= tp.width / 2; + y -= topTitles.margin + tp.height; + y += Utils() + .calculateRotationOffset(tp.size, topTitles.rotateAngle) + .dy; + canvasWrapper.drawText(tp, Offset(x, y), topTitles.rotateAngle); + } + if (data.maxX - horizontalSeek < topInterval && + data.maxX != horizontalSeek) { + horizontalSeek = data.maxX; + } else { + horizontalSeek += topInterval; } } } // Right Titles - if (!data.isVerticalMinMaxIsZero) { - final rightTitles = targetData.titlesData.rightTitles; - final rightInterval = rightTitles.interval ?? - Utils().getEfficientInterval(viewSize.height, data.verticalDiff); - if (rightTitles.showTitles) { - var verticalSeek = Utils() - .getBestInitialIntervalValue(data.minY, data.maxY, rightInterval); - while (verticalSeek <= data.maxY) { - if (rightTitles.checkToShowTitle( - data.minY, data.maxY, rightTitles, rightInterval, verticalSeek)) { - var x = viewSize.width + getLeftOffsetDrawSize(holder); - var y = getPixelY(verticalSeek, viewSize, holder); - - final text = rightTitles.getTitles(verticalSeek); - - final span = TextSpan( - style: Utils().getThemeAwareTextStyle( - context, rightTitles.getTextStyles(context, verticalSeek)), - text: text, - ); - final tp = TextPainter( - text: span, - textAlign: rightTitles.textAlign, - textDirection: rightTitles.textDirection, - textScaleFactor: holder.textScale, - ); - tp.layout( - maxWidth: rightTitles.reservedSize, - minWidth: rightTitles.reservedSize, - ); - - x += rightTitles.margin; - y -= tp.height / 2; - x -= Utils() - .calculateRotationOffset(tp.size, rightTitles.rotateAngle) - .dx; - canvasWrapper.drawText(tp, Offset(x, y), rightTitles.rotateAngle); - } - if (data.maxY - verticalSeek < rightInterval && - data.maxY != verticalSeek) { - verticalSeek = data.maxY; - } else { - verticalSeek += rightInterval; - } + final rightTitles = targetData.titlesData.rightTitles; + final rightInterval = rightTitles.interval ?? + Utils().getEfficientInterval(viewSize.height, data.verticalDiff); + if (rightTitles.showTitles) { + var verticalSeek = Utils() + .getBestInitialIntervalValue(data.minY, data.maxY, rightInterval); + while (verticalSeek <= data.maxY) { + if (rightTitles.checkToShowTitle( + data.minY, data.maxY, rightTitles, rightInterval, verticalSeek)) { + var x = viewSize.width + getLeftOffsetDrawSize(holder); + var y = getPixelY(verticalSeek, viewSize, holder); + + final text = rightTitles.getTitles(verticalSeek); + + final span = TextSpan( + style: Utils().getThemeAwareTextStyle( + context, rightTitles.getTextStyles(context, verticalSeek)), + text: text, + ); + final tp = TextPainter( + text: span, + textAlign: rightTitles.textAlign, + textDirection: rightTitles.textDirection, + textScaleFactor: holder.textScale, + ); + tp.layout( + maxWidth: rightTitles.reservedSize, + minWidth: rightTitles.reservedSize, + ); + + x += rightTitles.margin; + y -= tp.height / 2; + x -= Utils() + .calculateRotationOffset(tp.size, rightTitles.rotateAngle) + .dx; + canvasWrapper.drawText(tp, Offset(x, y), rightTitles.rotateAngle); + } + if (data.maxY - verticalSeek < rightInterval && + data.maxY != verticalSeek) { + verticalSeek = data.maxY; + } else { + verticalSeek += rightInterval; } } } // Bottom titles - if (!data.isHorizontalMinMaxIsZero) { - final bottomTitles = targetData.titlesData.bottomTitles; - final bottomInterval = bottomTitles.interval ?? - Utils().getEfficientInterval(viewSize.width, data.horizontalDiff); - if (bottomTitles.showTitles) { - var horizontalSeek = Utils() - .getBestInitialIntervalValue(data.minX, data.maxX, bottomInterval); - while (horizontalSeek <= data.maxX) { - if (bottomTitles.checkToShowTitle(data.minX, data.maxX, bottomTitles, - bottomInterval, horizontalSeek)) { - var x = getPixelX(horizontalSeek, viewSize, holder); - var y = viewSize.height + getTopOffsetDrawSize(holder); - - final text = bottomTitles.getTitles(horizontalSeek); - - final span = TextSpan( - style: Utils().getThemeAwareTextStyle( - context, bottomTitles.getTextStyles(context, horizontalSeek)), - text: text, - ); - final tp = TextPainter( - text: span, - textAlign: bottomTitles.textAlign, - textDirection: bottomTitles.textDirection, - textScaleFactor: holder.textScale, - ); - tp.layout(); - - x -= tp.width / 2; - y += bottomTitles.margin; - y -= Utils() - .calculateRotationOffset(tp.size, bottomTitles.rotateAngle) - .dy; - canvasWrapper.drawText(tp, Offset(x, y), bottomTitles.rotateAngle); - } - if (data.maxX - horizontalSeek < bottomInterval && - data.maxX != horizontalSeek) { - horizontalSeek = data.maxX; - } else { - horizontalSeek += bottomInterval; - } + final bottomTitles = targetData.titlesData.bottomTitles; + final bottomInterval = bottomTitles.interval ?? + Utils().getEfficientInterval(viewSize.width, data.horizontalDiff); + if (bottomTitles.showTitles) { + var horizontalSeek = Utils() + .getBestInitialIntervalValue(data.minX, data.maxX, bottomInterval); + while (horizontalSeek <= data.maxX) { + if (bottomTitles.checkToShowTitle(data.minX, data.maxX, bottomTitles, + bottomInterval, horizontalSeek)) { + var x = getPixelX(horizontalSeek, viewSize, holder); + var y = viewSize.height + getTopOffsetDrawSize(holder); + + final text = bottomTitles.getTitles(horizontalSeek); + + final span = TextSpan( + style: Utils().getThemeAwareTextStyle( + context, bottomTitles.getTextStyles(context, horizontalSeek)), + text: text, + ); + final tp = TextPainter( + text: span, + textAlign: bottomTitles.textAlign, + textDirection: bottomTitles.textDirection, + textScaleFactor: holder.textScale, + ); + tp.layout(); + + x -= tp.width / 2; + y += bottomTitles.margin; + y -= Utils() + .calculateRotationOffset(tp.size, bottomTitles.rotateAngle) + .dy; + canvasWrapper.drawText(tp, Offset(x, y), bottomTitles.rotateAngle); + } + if (data.maxX - horizontalSeek < bottomInterval && + data.maxX != horizontalSeek) { + horizontalSeek = data.maxX; + } else { + horizontalSeek += bottomInterval; } } } diff --git a/lib/src/utils/utils.dart b/lib/src/utils/utils.dart index 9b4c6fbeb..9813861dd 100644 --- a/lib/src/utils/utils.dart +++ b/lib/src/utils/utils.dart @@ -15,6 +15,11 @@ class Utils { @visibleForTesting static void changeInstance(Utils val) => _singleton = val; + @visibleForTesting + static void restoreDefaultInstance() { + _singleton = Utils._internal(); + } + static const double _degrees2Radians = math.pi / 180.0; /// Converts degrees to radians @@ -123,13 +128,17 @@ class Utils { /// /// If there isn't any provided interval, we use this function to calculate an interval to apply, /// using [axisViewSize] / [pixelPerInterval], we calculate the allowedCount lines in the axis, - /// then using [diffInYAxis] / allowedCount, we can find out how much interval we need, + /// then using [diffInAxis] / allowedCount, we can find out how much interval we need, /// then we round that number by finding nearest number in this pattern: /// 1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 5000, 10000,... - double getEfficientInterval(double axisViewSize, double diffInYAxis, + double getEfficientInterval(double axisViewSize, double diffInAxis, {double pixelPerInterval = 40}) { final allowedCount = math.max(axisViewSize ~/ pixelPerInterval, 1); - final accurateInterval = diffInYAxis / allowedCount; + if (diffInAxis == 0) { + return 1; + } + final accurateInterval = + diffInAxis == 0 ? axisViewSize : diffInAxis / allowedCount; return roundInterval(accurateInterval); } diff --git a/test/chart/bar_chart/bar_chart_painter_test.dart b/test/chart/bar_chart/bar_chart_painter_test.dart index a4431a44d..ca43d36fa 100644 --- a/test/chart/bar_chart/bar_chart_painter_test.dart +++ b/test/chart/bar_chart/bar_chart_painter_test.dart @@ -728,6 +728,18 @@ void main() { final MockBuildContext _mockBuildContext = MockBuildContext(); + final MockUtils _mockUtils = MockUtils(); + Utils.changeInstance(_mockUtils); + when(_mockUtils.getThemeAwareTextStyle(any, any)) + .thenAnswer((realInvocation) => MockData.textStyle1); + when(_mockUtils.getEfficientInterval(any, any)) + .thenAnswer((realInvocation) => 1); + when(_mockUtils.getBestInitialIntervalValue(any, any, any)) + .thenAnswer((realInvocation) => 0); + when(_mockUtils.formatNumber(any)).thenAnswer((realInvocation) => '1'); + when(_mockUtils.calculateRotationOffset(any, any)) + .thenAnswer((realInvocation) => Offset.zero); + final groupsX = barChartPainter.calculateGroupsX( viewSize, barGroups, BarChartAlignment.center, holder); final barGroupsPosition = barChartPainter.calculateGroupAndBarsPosition( @@ -736,6 +748,7 @@ void main() { barChartPainter.drawTitles( _mockBuildContext, _mockCanvasWrapper, barGroupsPosition, holder); verifyNever(_mockCanvasWrapper.drawRRect(any, any)); + Utils.restoreDefaultInstance(); }); test('test 4', () { diff --git a/test/chart/line_chart/line_chart_painter_test.dart b/test/chart/line_chart/line_chart_painter_test.dart index 62f8547e0..3091f7f91 100644 --- a/test/chart/line_chart/line_chart_painter_test.dart +++ b/test/chart/line_chart/line_chart_painter_test.dart @@ -105,6 +105,21 @@ void main() { expect(lineChartPainter.getChartUsableDrawSize(viewSize, holder), const Size(600, 320)); }); + + test('test 6', () { + const viewSize = Size(600, 400); + final LineChartData data = LineChartData( + titlesData: FlTitlesData(show: false), + axisTitleData: FlAxisTitleData(show: false), + minY: 0, + maxY: 0, + ); + + final LineChartPainter lineChartPainter = LineChartPainter(); + final holder = PaintHolder(data, data, 1.0); + expect(lineChartPainter.getPixelX(0, viewSize, holder), 0); + expect(lineChartPainter.getPixelY(0, viewSize, holder), 400); + }); }); group('clipToBorder()', () { @@ -1788,12 +1803,18 @@ void main() { when(mockUtils.getEfficientInterval(any, any)) .thenAnswer((realInvocation) => 1); + when(mockUtils.getThemeAwareTextStyle(any, any)) + .thenAnswer((realInvocation) => MockData.textStyle1); + when(mockUtils.formatNumber(any)).thenAnswer((realInvocation) => '1'); + when(mockUtils.calculateRotationOffset(any, any)) + .thenAnswer((realInvocation) => Offset.zero); + MockBuildContext _mockBuildContext = MockBuildContext(); lineChartPainter.drawTitles( _mockBuildContext, _mockCanvasWrapper, holder); - verifyNever(_mockCanvasWrapper.drawText(any, any, any)); + verify(_mockCanvasWrapper.drawText(any, any, any)).called(1); }); test('test 3', () { diff --git a/test/utils/utils_test.dart b/test/utils/utils_test.dart index 0a6eba640..c39404105 100644 --- a/test/utils/utils_test.dart +++ b/test/utils/utils_test.dart @@ -134,6 +134,7 @@ void main() { expect(Utils().getEfficientInterval(200, 1.0, pixelPerInterval: 20), 0.1); expect(Utils().getEfficientInterval(100, 0.5, pixelPerInterval: 20), 0.1); expect(Utils().getEfficientInterval(10, 10), 10); + expect(Utils().getEfficientInterval(10, 0), 1); }); test('test formatNumber', () { diff --git a/test/utils/utils_test.mocks.dart b/test/utils/utils_test.mocks.dart new file mode 100644 index 000000000..c30982f83 --- /dev/null +++ b/test/utils/utils_test.mocks.dart @@ -0,0 +1,93 @@ +// Mocks generated by Mockito 5.0.17 from annotations +// in fl_chart/test/utils/utils_test.dart. +// Do not manually edit this file. + +import 'package:flutter/foundation.dart' as _i3; +import 'package:flutter/src/widgets/framework.dart' as _i2; +import 'package:mockito/mockito.dart' as _i1; + +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types + +class _FakeWidget_0 extends _i1.Fake implements _i2.Widget { + @override + String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakeInheritedWidget_1 extends _i1.Fake implements _i2.InheritedWidget { + @override + String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => + super.toString(); +} + +class _FakeDiagnosticsNode_2 extends _i1.Fake implements _i3.DiagnosticsNode { + @override + String toString( + {_i3.TextTreeConfiguration? parentConfiguration, + _i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => + super.toString(); +} + +/// A class which mocks [BuildContext]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockBuildContext extends _i1.Mock implements _i2.BuildContext { + MockBuildContext() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.Widget get widget => (super.noSuchMethod(Invocation.getter(#widget), + returnValue: _FakeWidget_0()) as _i2.Widget); + @override + bool get debugDoingBuild => (super + .noSuchMethod(Invocation.getter(#debugDoingBuild), returnValue: false) + as bool); + @override + _i2.InheritedWidget dependOnInheritedElement(_i2.InheritedElement? ancestor, + {Object? aspect}) => + (super.noSuchMethod( + Invocation.method( + #dependOnInheritedElement, [ancestor], {#aspect: aspect}), + returnValue: _FakeInheritedWidget_1()) as _i2.InheritedWidget); + @override + void visitAncestorElements(bool Function(_i2.Element)? visitor) => + super.noSuchMethod(Invocation.method(#visitAncestorElements, [visitor]), + returnValueForMissingStub: null); + @override + void visitChildElements(_i2.ElementVisitor? visitor) => + super.noSuchMethod(Invocation.method(#visitChildElements, [visitor]), + returnValueForMissingStub: null); + @override + _i3.DiagnosticsNode describeElement(String? name, + {_i3.DiagnosticsTreeStyle? style = + _i3.DiagnosticsTreeStyle.errorProperty}) => + (super.noSuchMethod( + Invocation.method(#describeElement, [name], {#style: style}), + returnValue: _FakeDiagnosticsNode_2()) as _i3.DiagnosticsNode); + @override + _i3.DiagnosticsNode describeWidget(String? name, + {_i3.DiagnosticsTreeStyle? style = + _i3.DiagnosticsTreeStyle.errorProperty}) => + (super.noSuchMethod( + Invocation.method(#describeWidget, [name], {#style: style}), + returnValue: _FakeDiagnosticsNode_2()) as _i3.DiagnosticsNode); + @override + List<_i3.DiagnosticsNode> describeMissingAncestor( + {Type? expectedAncestorType}) => + (super.noSuchMethod( + Invocation.method(#describeMissingAncestor, [], + {#expectedAncestorType: expectedAncestorType}), + returnValue: <_i3.DiagnosticsNode>[]) as List<_i3.DiagnosticsNode>); + @override + _i3.DiagnosticsNode describeOwnershipChain(String? name) => + (super.noSuchMethod(Invocation.method(#describeOwnershipChain, [name]), + returnValue: _FakeDiagnosticsNode_2()) as _i3.DiagnosticsNode); +} From e4567d3b1ece05a62ac4d495faec443451eec2a2 Mon Sep 17 00:00:00 2001 From: imaN Khoshabi Date: Sat, 29 Jan 2022 23:32:24 +0330 Subject: [PATCH 20/45] Update SOURCES.md --- SOURCES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/SOURCES.md b/SOURCES.md index ee3c29d93..493c08ab9 100644 --- a/SOURCES.md +++ b/SOURCES.md @@ -12,6 +12,9 @@ Did you find any new article or source? please contribute to have them all here. * [Stock charts](https://dev.to/kamilpowalowski/stock-charts-with-flchart-library-1gd2) #### Video: + +* [Portfolio Dashboard Flutter UI Desktop & Web](https://www.youtube.com/watch?v=H9vXUine7Zo) + * [Responsive Admin Dashboard or Panel using Flutter - Flutter Web UI - Part 1](https://www.youtube.com/watch?v=MRiZpwdy1CM) From d98202d1dbc5f6c610742d9fbe01f9118bc50407 Mon Sep 17 00:00:00 2001 From: imaN Khoshabi Date: Sat, 29 Jan 2022 23:52:24 +0330 Subject: [PATCH 21/45] Correct main.dart function --- example/lib/main.dart | 153 ++++++++++++++++++++++-------------------- 1 file changed, 82 insertions(+), 71 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 0e010e042..4072a60c8 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,6 +1,18 @@ -import 'package:fl_chart/fl_chart.dart'; +import 'package:example/radar_chart/radar_chart_page.dart'; +import 'package:example/scatter_chart/scatter_chart_page.dart'; import 'package:flutter/material.dart'; +import 'bar_chart/bar_chart_page.dart'; +import 'bar_chart/bar_chart_page2.dart'; +import 'bar_chart/bar_chart_page3.dart'; +import 'line_chart/line_chart_page.dart'; +import 'line_chart/line_chart_page2.dart'; +import 'line_chart/line_chart_page3.dart'; +import 'line_chart/line_chart_page4.dart'; +import 'pie_chart/pie_chart_page.dart'; +import 'utils/platform_info.dart'; +import 'scatter_chart/scatter_chart_page.dart'; + void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { @@ -8,99 +20,98 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return const MaterialApp( + return MaterialApp( title: 'FlChart Demo', showPerformanceOverlay: false, - home: MyHomePage(), + theme: ThemeData( + primaryColor: const Color(0xff262545), + primaryColorDark: const Color(0xff201f39), + brightness: Brightness.dark, + ), + home: const MyHomePage(title: 'fl_chart'), ); } } class MyHomePage extends StatefulWidget { - const MyHomePage({Key? key}) : super(key: key); + const MyHomePage({Key? key, required this.title}) : super(key: key); + final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State { - final List _showingTouchedTooltips = []; + int _currentPage = 0; - final Map> _showingTouchedIndicators = {}; + final _controller = PageController(initialPage: 0); + final _duration = const Duration(milliseconds: 300); + final _curve = Curves.easeInOutCubic; + final _pages = const [ + LineChartPage(), + BarChartPage(), + BarChartPage2(), + PieChartPage(), + LineChartPage2(), + LineChartPage3(), + LineChartPage4(), + BarChartPage3(), + ScatterChartPage(), + RadarChartPage(), + ]; + + bool get isDesktopOrWeb => PlatformInfo().isDesktopOrWeb(); + + @override + void initState() { + super.initState(); + _controller.addListener(() { + setState(() { + _currentPage = _controller.page!.round(); + }); + }); + } @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( - child: Center( - child: SizedBox( - width: 200, - height: 100, - child: LineChart( - LineChartData( - lineBarsData: [ - LineChartBarData( - colors: [Colors.green], - spots: const [ - FlSpot(0, 0), - FlSpot(1, 1), - FlSpot(2, 1), - ], - showingIndicators: _showingTouchedIndicators[0] ?? [], + child: PageView( + physics: isDesktopOrWeb + ? const NeverScrollableScrollPhysics() + : const AlwaysScrollableScrollPhysics(), + controller: _controller, + children: _pages, + ), + ), + bottomNavigationBar: isDesktopOrWeb + ? Container( + padding: const EdgeInsets.all(16), + color: Colors.transparent, + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Visibility( + visible: _currentPage != 0, + child: FloatingActionButton( + onPressed: () => _controller.previousPage( + duration: _duration, curve: _curve), + child: const Icon(Icons.chevron_left_rounded), + ), ), - LineChartBarData( - colors: [Colors.red], - spots: const [ - FlSpot(0, 3), - FlSpot(1, 2), - FlSpot(2, 0), - ], - showingIndicators: _showingTouchedIndicators[1] ?? [], + const Spacer(), + Visibility( + visible: _currentPage != _pages.length - 1, + child: FloatingActionButton( + onPressed: () => _controller.nextPage( + duration: _duration, curve: _curve), + child: const Icon(Icons.chevron_right_rounded), + ), ), ], - showingTooltipIndicators: _showingTouchedTooltips, - lineTouchData: LineTouchData( - enabled: true, - handleBuiltInTouches: false, - touchCallback: (event, touchResponse) { - if (!event.isInterestedForInteractions || - touchResponse?.lineBarSpots == null || - touchResponse!.lineBarSpots!.isEmpty) { - setState(() { - _showingTouchedTooltips.clear(); - _showingTouchedIndicators.clear(); - }); - return; - } - - const showOnlyOnLineIndex = 0; - setState(() { - final sortedLineSpots = List.of( - touchResponse.lineBarSpots!.where((element) => - element.barIndex == showOnlyOnLineIndex)); - sortedLineSpots - .sort((spot1, spot2) => spot2.y.compareTo(spot1.y)); - - _showingTouchedIndicators.clear(); - - final touchedBarSpot = - touchResponse.lineBarSpots![showOnlyOnLineIndex]; - final barPos = touchedBarSpot.barIndex; - _showingTouchedIndicators[barPos] = [ - touchedBarSpot.spotIndex - ]; - - _showingTouchedTooltips.clear(); - _showingTouchedTooltips - .add(ShowingTooltipIndicators(sortedLineSpots)); - }); - }, - ), ), - ), - ), - ), - ), + ) + : null, ); } } From a0825af8ce3ed826c74b2a0f8454b0c5686364fb Mon Sep 17 00:00:00 2001 From: imaN Khoshabi Date: Sun, 30 Jan 2022 01:23:12 +0330 Subject: [PATCH 22/45] Improve iteration over axis values logic (it solves some minor problems on showing titles when min, max values are below than 1.0) --- CHANGELOG.md | 1 + .../chart/bar_chart/bar_chart_painter.dart | 151 +++++---- .../base/axis_chart/axis_chart_data.dart | 7 +- .../base/axis_chart/axis_chart_helper.dart | 48 +++ .../base/axis_chart/axis_chart_painter.dart | 119 +++---- .../chart/line_chart/line_chart_painter.dart | 287 ++++++++--------- .../scatter_chart/scatter_chart_painter.dart | 297 +++++++++--------- test/chart/base/axis_chart_helper_test.dart | 91 ++++++ 8 files changed, 546 insertions(+), 455 deletions(-) create mode 100644 lib/src/chart/base/axis_chart/axis_chart_helper.dart create mode 100644 test/chart/base/axis_chart_helper_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index c31473b8a..b91907ba4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ * **BUGFIX** Fix PieChart changing sections issue (we have disabled semantics for pieChart badgeWidgets), #861. * **BUGFIX** Fix LineChart width smaller width or height lower than 40, #869, #857. * **BUGFIX** Allow to show title when axis diff is zero. +* **IMPROVEMENT** Improve iteration over axis values logic (it solves some minor problems on showing titles when min, max values are below than 1.0). ## 0.41.0 * **BUGFIX** Fix getNearestTouchedSpot. Previously it returned the first occurrence of a spot within the threshold, and not the nearest, #641, #645. diff --git a/lib/src/chart/bar_chart/bar_chart_painter.dart b/lib/src/chart/bar_chart/bar_chart_painter.dart index a82fe33dd..03e09ec6a 100644 --- a/lib/src/chart/bar_chart/bar_chart_painter.dart +++ b/lib/src/chart/bar_chart/bar_chart_painter.dart @@ -3,6 +3,7 @@ import 'dart:math'; import 'dart:ui' as ui; import 'package:fl_chart/fl_chart.dart'; +import 'package:fl_chart/src/chart/base/axis_chart/axis_chart_helper.dart'; import 'package:fl_chart/src/chart/base/axis_chart/axis_chart_painter.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; import 'package:fl_chart/src/utils/canvas_wrapper.dart'; @@ -390,45 +391,42 @@ class BarChartPainter extends AxisChartPainter { final leftInterval = leftTitles.interval ?? Utils().getEfficientInterval(viewSize.height, data.verticalDiff); if (leftTitles.showTitles) { - var verticalSeek = Utils() - .getBestInitialIntervalValue(data.minY, data.maxY, leftInterval); - while (verticalSeek <= data.maxY) { - if (leftTitles.checkToShowTitle( - data.minY, data.maxY, leftTitles, leftInterval, verticalSeek)) { - var x = 0 + getLeftOffsetDrawSize(holder); - var y = getPixelY(verticalSeek, drawSize, holder); - - final text = leftTitles.getTitles(verticalSeek); - - final span = TextSpan( - style: Utils().getThemeAwareTextStyle( - context, leftTitles.getTextStyles(context, verticalSeek)), - text: text, - ); - final tp = TextPainter( - text: span, - textAlign: leftTitles.textAlign, - textDirection: leftTitles.textDirection, - textScaleFactor: holder.textScale, - ); - tp.layout( - maxWidth: leftTitles.reservedSize, - minWidth: leftTitles.reservedSize, - ); - x -= tp.width + leftTitles.margin; - y -= tp.height / 2; - x += Utils() - .calculateRotationOffset(tp.size, leftTitles.rotateAngle) - .dx; - canvasWrapper.drawText(tp, Offset(x, y), leftTitles.rotateAngle); - } - if (data.maxY - verticalSeek < leftInterval && - data.maxY != verticalSeek) { - verticalSeek = data.maxY; - } else { - verticalSeek += leftInterval; - } - } + AxisChartHelper().iterateThroughAxis( + min: data.minY, + max: data.maxY, + interval: leftInterval, + action: (axisValue) { + if (leftTitles.checkToShowTitle( + data.minY, data.maxY, leftTitles, leftInterval, axisValue)) { + var x = 0 + getLeftOffsetDrawSize(holder); + var y = getPixelY(axisValue, drawSize, holder); + + final text = leftTitles.getTitles(axisValue); + + final span = TextSpan( + style: Utils().getThemeAwareTextStyle( + context, leftTitles.getTextStyles(context, axisValue)), + text: text, + ); + final tp = TextPainter( + text: span, + textAlign: leftTitles.textAlign, + textDirection: leftTitles.textDirection, + textScaleFactor: holder.textScale, + ); + tp.layout( + maxWidth: leftTitles.reservedSize, + minWidth: leftTitles.reservedSize, + ); + x -= tp.width + leftTitles.margin; + y -= tp.height / 2; + x += Utils() + .calculateRotationOffset(tp.size, leftTitles.rotateAngle) + .dx; + canvasWrapper.drawText(tp, Offset(x, y), leftTitles.rotateAngle); + } + }, + ); } // Top Titles @@ -465,45 +463,42 @@ class BarChartPainter extends AxisChartPainter { final rightInterval = rightTitles.interval ?? Utils().getEfficientInterval(viewSize.height, data.verticalDiff); if (rightTitles.showTitles) { - var verticalSeek = Utils() - .getBestInitialIntervalValue(data.minY, data.maxY, rightInterval); - while (verticalSeek <= data.maxY) { - if (rightTitles.checkToShowTitle( - data.minY, data.maxY, rightTitles, rightInterval, verticalSeek)) { - var x = drawSize.width + getLeftOffsetDrawSize(holder); - var y = getPixelY(verticalSeek, drawSize, holder); - - final text = rightTitles.getTitles(verticalSeek); - - final span = TextSpan( - style: Utils().getThemeAwareTextStyle( - context, rightTitles.getTextStyles(context, verticalSeek)), - text: text, - ); - final tp = TextPainter( - text: span, - textAlign: rightTitles.textAlign, - textDirection: rightTitles.textDirection, - textScaleFactor: holder.textScale, - ); - tp.layout( - maxWidth: rightTitles.reservedSize, - minWidth: rightTitles.reservedSize, - ); - x += rightTitles.margin; - y -= tp.height / 2; - x -= Utils() - .calculateRotationOffset(tp.size, rightTitles.rotateAngle) - .dx; - canvasWrapper.drawText(tp, Offset(x, y), rightTitles.rotateAngle); - } - if (data.maxY - verticalSeek < rightInterval && - data.maxY != verticalSeek) { - verticalSeek = data.maxY; - } else { - verticalSeek += rightInterval; - } - } + AxisChartHelper().iterateThroughAxis( + min: data.minY, + max: data.maxY, + interval: rightInterval, + action: (axisValue) { + if (rightTitles.checkToShowTitle( + data.minY, data.maxY, rightTitles, rightInterval, axisValue)) { + var x = drawSize.width + getLeftOffsetDrawSize(holder); + var y = getPixelY(axisValue, drawSize, holder); + + final text = rightTitles.getTitles(axisValue); + + final span = TextSpan( + style: Utils().getThemeAwareTextStyle( + context, rightTitles.getTextStyles(context, axisValue)), + text: text, + ); + final tp = TextPainter( + text: span, + textAlign: rightTitles.textAlign, + textDirection: rightTitles.textDirection, + textScaleFactor: holder.textScale, + ); + tp.layout( + maxWidth: rightTitles.reservedSize, + minWidth: rightTitles.reservedSize, + ); + x += rightTitles.margin; + y -= tp.height / 2; + x -= Utils() + .calculateRotationOffset(tp.size, rightTitles.rotateAngle) + .dx; + canvasWrapper.drawText(tp, Offset(x, y), rightTitles.rotateAngle); + } + }, + ); } // Bottom titles 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 9a1c71673..d9d1dc544 100644 --- a/lib/src/chart/base/axis_chart/axis_chart_data.dart +++ b/lib/src/chart/base/axis_chart/axis_chart_data.dart @@ -298,15 +298,12 @@ class FlTitlesData with EquatableMixin { typedef CheckToShowTitle = bool Function(double minValue, double maxValue, SideTitles sideTitles, double appliedInterval, double value); -/// The default [SideTitles.checkToShowTitle] function. +/// The default [SideTitles.checkToShowTitle] function (shows all titles). /// /// It determines showing or not showing specific title. bool defaultCheckToShowTitle(double minValue, double maxValue, SideTitles sideTitles, double appliedInterval, double value) { - if ((maxValue - minValue) % appliedInterval == 0) { - return true; - } - return value != maxValue; + return true; } /// Holds data for showing each side titles (a title per each axis value). diff --git a/lib/src/chart/base/axis_chart/axis_chart_helper.dart b/lib/src/chart/base/axis_chart/axis_chart_helper.dart new file mode 100644 index 000000000..8ba0faf14 --- /dev/null +++ b/lib/src/chart/base/axis_chart/axis_chart_helper.dart @@ -0,0 +1,48 @@ +import 'package:fl_chart/src/utils/utils.dart'; + +class AxisChartHelper { + static final _singleton = AxisChartHelper._internal(); + + factory AxisChartHelper() { + return _singleton; + } + + AxisChartHelper._internal(); + + /// Iterates over an axis from [min] to [max]. + /// + /// [interval] determines each step + /// + /// If [minIncluded] is true, it starts from [min] value, + /// otherwise it starts from [min] + [interval] + /// + /// If [maxIncluded] is true, it ends at [max] value, + /// otherwise it ends at [max] - [interval] + void iterateThroughAxis({ + required double min, + bool minIncluded = true, + required double max, + bool maxIncluded = true, + required double interval, + required void Function(double axisValue) action, + }) { + final initialValue = + Utils().getBestInitialIntervalValue(min, max, interval); + var axisSeek = initialValue; + if (!minIncluded && axisSeek == min) { + axisSeek += interval; + } + final diff = max - min; + final count = diff ~/ interval; + final lastPosition = initialValue + (count * interval); + final lastPositionOverlapsWithMax = lastPosition == max; + final end = + !maxIncluded && lastPositionOverlapsWithMax ? max - interval : max; + + final epsilon = interval / 100000; + while (axisSeek <= end + epsilon) { + action(axisSeek); + axisSeek += interval; + } + } +} diff --git a/lib/src/chart/base/axis_chart/axis_chart_painter.dart b/lib/src/chart/base/axis_chart/axis_chart_painter.dart index cfb0c22e0..bbc04d6dd 100644 --- a/lib/src/chart/base/axis_chart/axis_chart_painter.dart +++ b/lib/src/chart/base/axis_chart/axis_chart_painter.dart @@ -1,6 +1,7 @@ import 'dart:math' as math; import 'package:fl_chart/src/chart/bar_chart/bar_chart_painter.dart'; +import 'package:fl_chart/src/chart/base/axis_chart/axis_chart_helper.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; import 'package:fl_chart/src/chart/line_chart/line_chart_painter.dart'; import 'package:fl_chart/src/extensions/paint_extension.dart'; @@ -233,40 +234,33 @@ abstract class AxisChartPainter // Show Vertical Grid if (data.gridData.drawVerticalLine) { final verticalInterval = data.gridData.verticalInterval ?? - Utils() - .getEfficientInterval(usableViewSize.width, data.horizontalDiff); - final initialVerticalValue = Utils() - .getBestInitialIntervalValue(data.minX, data.maxX, verticalInterval); - var verticalSeek = initialVerticalValue; - if (verticalSeek == data.minX) { - verticalSeek += verticalInterval; - } - final delta = data.horizontalDiff; - final count = delta ~/ verticalInterval; - final lastPosition = initialVerticalValue + (count * verticalInterval); - final lastPositionOverlapsWithBorder = lastPosition == data.maxX; - final end = lastPositionOverlapsWithBorder - ? data.maxX - verticalInterval - : data.maxX; - - while (verticalSeek <= end) { - if (data.gridData.checkToShowVerticalLine(verticalSeek)) { - final flLineStyle = - data.gridData.getDrawingVerticalLine(verticalSeek); - _gridPaint.color = flLineStyle.color; - _gridPaint.strokeWidth = flLineStyle.strokeWidth; - _gridPaint.transparentIfWidthIsZero(); - - final bothX = getPixelX(verticalSeek, usableViewSize, holder); - final x1 = bothX; - final y1 = 0 + getTopOffsetDrawSize(holder); - final x2 = bothX; - final y2 = usableViewSize.height + getTopOffsetDrawSize(holder); - canvasWrapper.drawDashedLine(Offset(x1, y1), Offset(x2, y2), - _gridPaint, flLineStyle.dashArray); - } - verticalSeek += verticalInterval; - } + Utils().getEfficientInterval( + usableViewSize.width, + data.horizontalDiff, + ); + AxisChartHelper().iterateThroughAxis( + min: data.minX, + minIncluded: false, + max: data.maxX, + maxIncluded: false, + interval: verticalInterval, + action: (axisValue) { + if (data.gridData.checkToShowVerticalLine(axisValue)) { + final flLineStyle = data.gridData.getDrawingVerticalLine(axisValue); + _gridPaint.color = flLineStyle.color; + _gridPaint.strokeWidth = flLineStyle.strokeWidth; + _gridPaint.transparentIfWidthIsZero(); + + final bothX = getPixelX(axisValue, usableViewSize, holder); + final x1 = bothX; + final y1 = 0 + getTopOffsetDrawSize(holder); + final x2 = bothX; + final y2 = usableViewSize.height + getTopOffsetDrawSize(holder); + canvasWrapper.drawDashedLine(Offset(x1, y1), Offset(x2, y2), + _gridPaint, flLineStyle.dashArray); + } + }, + ); } // Show Horizontal Grid @@ -274,41 +268,30 @@ abstract class AxisChartPainter final horizontalInterval = data.gridData.horizontalInterval ?? Utils() .getEfficientInterval(usableViewSize.height, data.verticalDiff); - final initialHorizontalValue = Utils().getBestInitialIntervalValue( - data.minY, data.maxY, horizontalInterval); - var horizontalSeek = initialHorizontalValue; - if (horizontalSeek == data.minY) { - horizontalSeek += horizontalInterval; - } - final delta = data.verticalDiff; - final count = delta ~/ horizontalInterval; - final lastPosition = - initialHorizontalValue + (count * horizontalInterval); - final lastPositionOverlapsWithBorder = lastPosition == data.maxY; - - final end = lastPositionOverlapsWithBorder - ? data.maxY - horizontalInterval - : data.maxY; - - while (horizontalSeek <= end) { - if (data.gridData.checkToShowHorizontalLine(horizontalSeek)) { - final flLine = data.gridData.getDrawingHorizontalLine(horizontalSeek); - _gridPaint.color = flLine.color; - _gridPaint.strokeWidth = flLine.strokeWidth; - _gridPaint.transparentIfWidthIsZero(); - - final bothY = getPixelY(horizontalSeek, usableViewSize, holder); - final x1 = 0 + getLeftOffsetDrawSize(holder); - final y1 = bothY; - final x2 = usableViewSize.width + getLeftOffsetDrawSize(holder); - final y2 = bothY; - canvasWrapper.drawDashedLine( - Offset(x1, y1), Offset(x2, y2), _gridPaint, flLine.dashArray); - } - - horizontalSeek += horizontalInterval; - } + AxisChartHelper().iterateThroughAxis( + min: data.minY, + minIncluded: false, + max: data.maxY, + maxIncluded: false, + interval: horizontalInterval, + action: (axisValue) { + if (data.gridData.checkToShowHorizontalLine(axisValue)) { + final flLine = data.gridData.getDrawingHorizontalLine(axisValue); + _gridPaint.color = flLine.color; + _gridPaint.strokeWidth = flLine.strokeWidth; + _gridPaint.transparentIfWidthIsZero(); + + final bothY = getPixelY(axisValue, usableViewSize, holder); + final x1 = 0 + getLeftOffsetDrawSize(holder); + final y1 = bothY; + final x2 = usableViewSize.width + getLeftOffsetDrawSize(holder); + final y2 = bothY; + canvasWrapper.drawDashedLine( + Offset(x1, y1), Offset(x2, y2), _gridPaint, flLine.dashArray); + } + }, + ); } } diff --git a/lib/src/chart/line_chart/line_chart_painter.dart b/lib/src/chart/line_chart/line_chart_painter.dart index 6cc82e3e8..b4de15075 100644 --- a/lib/src/chart/line_chart/line_chart_painter.dart +++ b/lib/src/chart/line_chart/line_chart_painter.dart @@ -2,6 +2,7 @@ import 'dart:math'; import 'dart:ui' as ui; import 'package:fl_chart/fl_chart.dart'; +import 'package:fl_chart/src/chart/base/axis_chart/axis_chart_helper.dart'; import 'package:fl_chart/src/chart/base/axis_chart/axis_chart_painter.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; import 'package:fl_chart/src/extensions/paint_extension.dart'; @@ -899,45 +900,42 @@ class LineChartPainter extends AxisChartPainter { final leftInterval = leftTitles.interval ?? Utils().getEfficientInterval(viewSize.height, data.verticalDiff); if (leftTitles.showTitles) { - var verticalSeek = Utils() - .getBestInitialIntervalValue(data.minY, data.maxY, leftInterval); - while (verticalSeek <= data.maxY) { - if (leftTitles.checkToShowTitle( - data.minY, data.maxY, leftTitles, leftInterval, verticalSeek)) { - var x = 0 + getLeftOffsetDrawSize(holder); - var y = getPixelY(verticalSeek, viewSize, holder); - - final text = leftTitles.getTitles(verticalSeek); - - final span = TextSpan( - style: Utils().getThemeAwareTextStyle( - context, leftTitles.getTextStyles(context, verticalSeek)), - text: text, - ); - final tp = TextPainter( - text: span, - textAlign: leftTitles.textAlign, - textDirection: leftTitles.textDirection, - textScaleFactor: holder.textScale, - ); - tp.layout( - maxWidth: leftTitles.reservedSize, - minWidth: leftTitles.reservedSize, - ); - x -= tp.width + leftTitles.margin; - y -= tp.height / 2; - x += Utils() - .calculateRotationOffset(tp.size, leftTitles.rotateAngle) - .dx; - canvasWrapper.drawText(tp, Offset(x, y), leftTitles.rotateAngle); - } - if (data.maxY - verticalSeek < leftInterval && - data.maxY != verticalSeek) { - verticalSeek = data.maxY; - } else { - verticalSeek += leftInterval; - } - } + AxisChartHelper().iterateThroughAxis( + min: data.minY, + max: data.maxY, + interval: leftInterval, + action: (axisValue) { + if (leftTitles.checkToShowTitle( + data.minY, data.maxY, leftTitles, leftInterval, axisValue)) { + var x = 0 + getLeftOffsetDrawSize(holder); + var y = getPixelY(axisValue, viewSize, holder); + + final text = leftTitles.getTitles(axisValue); + + final span = TextSpan( + style: Utils().getThemeAwareTextStyle( + context, leftTitles.getTextStyles(context, axisValue)), + text: text, + ); + final tp = TextPainter( + text: span, + textAlign: leftTitles.textAlign, + textDirection: leftTitles.textDirection, + textScaleFactor: holder.textScale, + ); + tp.layout( + maxWidth: leftTitles.reservedSize, + minWidth: leftTitles.reservedSize, + ); + x -= tp.width + leftTitles.margin; + y -= tp.height / 2; + x += Utils() + .calculateRotationOffset(tp.size, leftTitles.rotateAngle) + .dx; + canvasWrapper.drawText(tp, Offset(x, y), leftTitles.rotateAngle); + } + }, + ); } // Top titles @@ -945,43 +943,40 @@ class LineChartPainter extends AxisChartPainter { final topInterval = topTitles.interval ?? Utils().getEfficientInterval(viewSize.width, data.horizontalDiff); if (topTitles.showTitles) { - var horizontalSeek = Utils() - .getBestInitialIntervalValue(data.minX, data.maxX, topInterval); - while (horizontalSeek <= data.maxX) { - if (topTitles.checkToShowTitle( - data.minX, data.maxX, topTitles, topInterval, horizontalSeek)) { - var x = getPixelX(horizontalSeek, viewSize, holder); - var y = getTopOffsetDrawSize(holder); - - final text = topTitles.getTitles(horizontalSeek); - - final span = TextSpan( - style: Utils().getThemeAwareTextStyle( - context, topTitles.getTextStyles(context, horizontalSeek)), - text: text, - ); - final tp = TextPainter( - text: span, - textAlign: topTitles.textAlign, - textDirection: topTitles.textDirection, - textScaleFactor: holder.textScale, - ); - tp.layout(); - - x -= tp.width / 2; - y -= topTitles.margin + tp.height; - y += Utils() - .calculateRotationOffset(tp.size, topTitles.rotateAngle) - .dy; - canvasWrapper.drawText(tp, Offset(x, y), topTitles.rotateAngle); - } - if (data.maxX - horizontalSeek < topInterval && - data.maxX != horizontalSeek) { - horizontalSeek = data.maxX; - } else { - horizontalSeek += topInterval; - } - } + AxisChartHelper().iterateThroughAxis( + min: data.minX, + max: data.maxX, + interval: topInterval, + action: (axisValue) { + if (topTitles.checkToShowTitle( + data.minX, data.maxX, topTitles, topInterval, axisValue)) { + var x = getPixelX(axisValue, viewSize, holder); + var y = getTopOffsetDrawSize(holder); + + final text = topTitles.getTitles(axisValue); + + final span = TextSpan( + style: Utils().getThemeAwareTextStyle( + context, topTitles.getTextStyles(context, axisValue)), + text: text, + ); + final tp = TextPainter( + text: span, + textAlign: topTitles.textAlign, + textDirection: topTitles.textDirection, + textScaleFactor: holder.textScale, + ); + tp.layout(); + + x -= tp.width / 2; + y -= topTitles.margin + tp.height; + y += Utils() + .calculateRotationOffset(tp.size, topTitles.rotateAngle) + .dy; + canvasWrapper.drawText(tp, Offset(x, y), topTitles.rotateAngle); + } + }, + ); } // Right Titles @@ -989,47 +984,43 @@ class LineChartPainter extends AxisChartPainter { final rightInterval = rightTitles.interval ?? Utils().getEfficientInterval(viewSize.height, data.verticalDiff); if (rightTitles.showTitles) { - var verticalSeek = Utils() - .getBestInitialIntervalValue(data.minY, data.maxY, rightInterval); - while (verticalSeek <= data.maxY) { - if (rightTitles.checkToShowTitle( - data.minY, data.maxY, rightTitles, rightInterval, verticalSeek)) { - var x = viewSize.width + getLeftOffsetDrawSize(holder); - var y = getPixelY(verticalSeek, viewSize, holder); - - final text = rightTitles.getTitles(verticalSeek); - - final span = TextSpan( - style: Utils().getThemeAwareTextStyle( - context, rightTitles.getTextStyles(context, verticalSeek)), - text: text, - ); - final tp = TextPainter( - text: span, - textAlign: rightTitles.textAlign, - textDirection: rightTitles.textDirection, - textScaleFactor: holder.textScale, - ); - tp.layout( - maxWidth: rightTitles.reservedSize, - minWidth: rightTitles.reservedSize, - ); - - x += rightTitles.margin; - y -= tp.height / 2; - x -= Utils() - .calculateRotationOffset(tp.size, rightTitles.rotateAngle) - .dx; - canvasWrapper.drawText(tp, Offset(x, y), rightTitles.rotateAngle); - } + AxisChartHelper().iterateThroughAxis( + min: data.minY, + max: data.maxY, + interval: rightInterval, + action: (axisValue) { + if (rightTitles.checkToShowTitle( + data.minY, data.maxY, rightTitles, rightInterval, axisValue)) { + var x = viewSize.width + getLeftOffsetDrawSize(holder); + var y = getPixelY(axisValue, viewSize, holder); + + final text = rightTitles.getTitles(axisValue); + + final span = TextSpan( + style: Utils().getThemeAwareTextStyle( + context, rightTitles.getTextStyles(context, axisValue)), + text: text, + ); + final tp = TextPainter( + text: span, + textAlign: rightTitles.textAlign, + textDirection: rightTitles.textDirection, + textScaleFactor: holder.textScale, + ); + tp.layout( + maxWidth: rightTitles.reservedSize, + minWidth: rightTitles.reservedSize, + ); - if (data.maxY - verticalSeek < rightInterval && - data.maxY != verticalSeek) { - verticalSeek = data.maxY; - } else { - verticalSeek += rightInterval; - } - } + x += rightTitles.margin; + y -= tp.height / 2; + x -= Utils() + .calculateRotationOffset(tp.size, rightTitles.rotateAngle) + .dx; + canvasWrapper.drawText(tp, Offset(x, y), rightTitles.rotateAngle); + } + }, + ); } // Bottom titles @@ -1037,40 +1028,36 @@ class LineChartPainter extends AxisChartPainter { final bottomInterval = bottomTitles.interval ?? Utils().getEfficientInterval(viewSize.width, data.horizontalDiff); if (bottomTitles.showTitles) { - var horizontalSeek = Utils() - .getBestInitialIntervalValue(data.minX, data.maxX, bottomInterval); - while (horizontalSeek <= data.maxX) { - if (bottomTitles.checkToShowTitle(data.minX, data.maxX, bottomTitles, - bottomInterval, horizontalSeek)) { - var x = getPixelX(horizontalSeek, viewSize, holder); - var y = viewSize.height + getTopOffsetDrawSize(holder); - final text = bottomTitles.getTitles(horizontalSeek); - final span = TextSpan( - style: Utils().getThemeAwareTextStyle( - context, bottomTitles.getTextStyles(context, horizontalSeek)), - text: text); - final tp = TextPainter( - text: span, - textAlign: bottomTitles.textAlign, - textDirection: bottomTitles.textDirection, - textScaleFactor: holder.textScale); - tp.layout(); - - x -= tp.width / 2; - y += bottomTitles.margin; - y -= Utils() - .calculateRotationOffset(tp.size, bottomTitles.rotateAngle) - .dy; - canvasWrapper.drawText(tp, Offset(x, y), bottomTitles.rotateAngle); - } - - if (data.maxX - horizontalSeek < bottomInterval && - data.maxX != horizontalSeek) { - horizontalSeek = data.maxX; - } else { - horizontalSeek += bottomInterval; - } - } + AxisChartHelper().iterateThroughAxis( + min: data.minX, + max: data.maxX, + interval: bottomInterval, + action: (axisValue) { + if (bottomTitles.checkToShowTitle( + data.minX, data.maxX, bottomTitles, bottomInterval, axisValue)) { + var x = getPixelX(axisValue, viewSize, holder); + var y = viewSize.height + getTopOffsetDrawSize(holder); + final text = bottomTitles.getTitles(axisValue); + final span = TextSpan( + style: Utils().getThemeAwareTextStyle( + context, bottomTitles.getTextStyles(context, axisValue)), + text: text); + final tp = TextPainter( + text: span, + textAlign: bottomTitles.textAlign, + textDirection: bottomTitles.textDirection, + textScaleFactor: holder.textScale); + tp.layout(); + + x -= tp.width / 2; + y += bottomTitles.margin; + y -= Utils() + .calculateRotationOffset(tp.size, bottomTitles.rotateAngle) + .dy; + canvasWrapper.drawText(tp, Offset(x, y), bottomTitles.rotateAngle); + } + }, + ); } } diff --git a/lib/src/chart/scatter_chart/scatter_chart_painter.dart b/lib/src/chart/scatter_chart/scatter_chart_painter.dart index 887c49cc4..5e20eefca 100644 --- a/lib/src/chart/scatter_chart/scatter_chart_painter.dart +++ b/lib/src/chart/scatter_chart/scatter_chart_painter.dart @@ -1,4 +1,5 @@ import 'package:fl_chart/fl_chart.dart'; +import 'package:fl_chart/src/chart/base/axis_chart/axis_chart_helper.dart'; import 'package:fl_chart/src/chart/base/axis_chart/axis_chart_painter.dart'; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; import 'package:fl_chart/src/utils/canvas_wrapper.dart'; @@ -54,46 +55,43 @@ class ScatterChartPainter extends AxisChartPainter { final leftInterval = leftTitles.interval ?? Utils().getEfficientInterval(viewSize.height, data.verticalDiff); if (leftTitles.showTitles) { - var verticalSeek = Utils() - .getBestInitialIntervalValue(data.minY, data.maxY, leftInterval); - while (verticalSeek <= data.maxY) { - if (leftTitles.checkToShowTitle( - data.minY, data.maxY, leftTitles, leftInterval, verticalSeek)) { - var x = 0 + getLeftOffsetDrawSize(holder); - var y = getPixelY(verticalSeek, viewSize, holder); - - final text = leftTitles.getTitles(verticalSeek); - - final span = TextSpan( - style: Utils().getThemeAwareTextStyle( - context, leftTitles.getTextStyles(context, verticalSeek)), - text: text, - ); - final tp = TextPainter( - text: span, - textAlign: leftTitles.textAlign, - textDirection: leftTitles.textDirection, - textScaleFactor: holder.textScale, - ); - tp.layout( - maxWidth: leftTitles.reservedSize, - minWidth: leftTitles.reservedSize, - ); - x -= tp.width + leftTitles.margin; - y -= tp.height / 2; - - x += Utils() - .calculateRotationOffset(tp.size, leftTitles.rotateAngle) - .dx; - canvasWrapper.drawText(tp, Offset(x, y), leftTitles.rotateAngle); - } - if (data.maxY - verticalSeek < leftInterval && - data.maxY != verticalSeek) { - verticalSeek = data.maxY; - } else { - verticalSeek += leftInterval; - } - } + AxisChartHelper().iterateThroughAxis( + min: data.minY, + max: data.maxY, + interval: leftInterval, + action: (axisValue) { + if (leftTitles.checkToShowTitle( + data.minY, data.maxY, leftTitles, leftInterval, axisValue)) { + var x = 0 + getLeftOffsetDrawSize(holder); + var y = getPixelY(axisValue, viewSize, holder); + + final text = leftTitles.getTitles(axisValue); + + final span = TextSpan( + style: Utils().getThemeAwareTextStyle( + context, leftTitles.getTextStyles(context, axisValue)), + text: text, + ); + final tp = TextPainter( + text: span, + textAlign: leftTitles.textAlign, + textDirection: leftTitles.textDirection, + textScaleFactor: holder.textScale, + ); + tp.layout( + maxWidth: leftTitles.reservedSize, + minWidth: leftTitles.reservedSize, + ); + x -= tp.width + leftTitles.margin; + y -= tp.height / 2; + + x += Utils() + .calculateRotationOffset(tp.size, leftTitles.rotateAngle) + .dx; + canvasWrapper.drawText(tp, Offset(x, y), leftTitles.rotateAngle); + } + }, + ); } // Top titles @@ -101,43 +99,40 @@ class ScatterChartPainter extends AxisChartPainter { final topInterval = topTitles.interval ?? Utils().getEfficientInterval(viewSize.width, data.horizontalDiff); if (topTitles.showTitles) { - var horizontalSeek = Utils() - .getBestInitialIntervalValue(data.minX, data.maxX, topInterval); - while (horizontalSeek <= data.maxX) { - if (topTitles.checkToShowTitle( - data.minX, data.maxX, topTitles, topInterval, horizontalSeek)) { - var x = getPixelX(horizontalSeek, viewSize, holder); - var y = getTopOffsetDrawSize(holder); - - final text = topTitles.getTitles(horizontalSeek); - - final span = TextSpan( - style: Utils().getThemeAwareTextStyle( - context, topTitles.getTextStyles(context, horizontalSeek)), - text: text, - ); - final tp = TextPainter( - text: span, - textAlign: topTitles.textAlign, - textDirection: topTitles.textDirection, - textScaleFactor: holder.textScale, - ); - tp.layout(); - - x -= tp.width / 2; - y -= topTitles.margin + tp.height; - y += Utils() - .calculateRotationOffset(tp.size, topTitles.rotateAngle) - .dy; - canvasWrapper.drawText(tp, Offset(x, y), topTitles.rotateAngle); - } - if (data.maxX - horizontalSeek < topInterval && - data.maxX != horizontalSeek) { - horizontalSeek = data.maxX; - } else { - horizontalSeek += topInterval; - } - } + AxisChartHelper().iterateThroughAxis( + min: data.minX, + max: data.maxX, + interval: topInterval, + action: (axisValue) { + if (topTitles.checkToShowTitle( + data.minX, data.maxX, topTitles, topInterval, axisValue)) { + var x = getPixelX(axisValue, viewSize, holder); + var y = getTopOffsetDrawSize(holder); + + final text = topTitles.getTitles(axisValue); + + final span = TextSpan( + style: Utils().getThemeAwareTextStyle( + context, topTitles.getTextStyles(context, axisValue)), + text: text, + ); + final tp = TextPainter( + text: span, + textAlign: topTitles.textAlign, + textDirection: topTitles.textDirection, + textScaleFactor: holder.textScale, + ); + tp.layout(); + + x -= tp.width / 2; + y -= topTitles.margin + tp.height; + y += Utils() + .calculateRotationOffset(tp.size, topTitles.rotateAngle) + .dy; + canvasWrapper.drawText(tp, Offset(x, y), topTitles.rotateAngle); + } + }, + ); } // Right Titles @@ -145,46 +140,43 @@ class ScatterChartPainter extends AxisChartPainter { final rightInterval = rightTitles.interval ?? Utils().getEfficientInterval(viewSize.height, data.verticalDiff); if (rightTitles.showTitles) { - var verticalSeek = Utils() - .getBestInitialIntervalValue(data.minY, data.maxY, rightInterval); - while (verticalSeek <= data.maxY) { - if (rightTitles.checkToShowTitle( - data.minY, data.maxY, rightTitles, rightInterval, verticalSeek)) { - var x = viewSize.width + getLeftOffsetDrawSize(holder); - var y = getPixelY(verticalSeek, viewSize, holder); - - final text = rightTitles.getTitles(verticalSeek); - - final span = TextSpan( - style: Utils().getThemeAwareTextStyle( - context, rightTitles.getTextStyles(context, verticalSeek)), - text: text, - ); - final tp = TextPainter( - text: span, - textAlign: rightTitles.textAlign, - textDirection: rightTitles.textDirection, - textScaleFactor: holder.textScale, - ); - tp.layout( - maxWidth: rightTitles.reservedSize, - minWidth: rightTitles.reservedSize, - ); - - x += rightTitles.margin; - y -= tp.height / 2; - x -= Utils() - .calculateRotationOffset(tp.size, rightTitles.rotateAngle) - .dx; - canvasWrapper.drawText(tp, Offset(x, y), rightTitles.rotateAngle); - } - if (data.maxY - verticalSeek < rightInterval && - data.maxY != verticalSeek) { - verticalSeek = data.maxY; - } else { - verticalSeek += rightInterval; - } - } + AxisChartHelper().iterateThroughAxis( + min: data.minY, + max: data.maxY, + interval: rightInterval, + action: (axisValue) { + if (rightTitles.checkToShowTitle( + data.minY, data.maxY, rightTitles, rightInterval, axisValue)) { + var x = viewSize.width + getLeftOffsetDrawSize(holder); + var y = getPixelY(axisValue, viewSize, holder); + + final text = rightTitles.getTitles(axisValue); + + final span = TextSpan( + style: Utils().getThemeAwareTextStyle( + context, rightTitles.getTextStyles(context, axisValue)), + text: text, + ); + final tp = TextPainter( + text: span, + textAlign: rightTitles.textAlign, + textDirection: rightTitles.textDirection, + textScaleFactor: holder.textScale, + ); + tp.layout( + maxWidth: rightTitles.reservedSize, + minWidth: rightTitles.reservedSize, + ); + + x += rightTitles.margin; + y -= tp.height / 2; + x -= Utils() + .calculateRotationOffset(tp.size, rightTitles.rotateAngle) + .dx; + canvasWrapper.drawText(tp, Offset(x, y), rightTitles.rotateAngle); + } + }, + ); } // Bottom titles @@ -192,43 +184,40 @@ class ScatterChartPainter extends AxisChartPainter { final bottomInterval = bottomTitles.interval ?? Utils().getEfficientInterval(viewSize.width, data.horizontalDiff); if (bottomTitles.showTitles) { - var horizontalSeek = Utils() - .getBestInitialIntervalValue(data.minX, data.maxX, bottomInterval); - while (horizontalSeek <= data.maxX) { - if (bottomTitles.checkToShowTitle(data.minX, data.maxX, bottomTitles, - bottomInterval, horizontalSeek)) { - var x = getPixelX(horizontalSeek, viewSize, holder); - var y = viewSize.height + getTopOffsetDrawSize(holder); - - final text = bottomTitles.getTitles(horizontalSeek); - - final span = TextSpan( - style: Utils().getThemeAwareTextStyle( - context, bottomTitles.getTextStyles(context, horizontalSeek)), - text: text, - ); - final tp = TextPainter( - text: span, - textAlign: bottomTitles.textAlign, - textDirection: bottomTitles.textDirection, - textScaleFactor: holder.textScale, - ); - tp.layout(); - - x -= tp.width / 2; - y += bottomTitles.margin; - y -= Utils() - .calculateRotationOffset(tp.size, bottomTitles.rotateAngle) - .dy; - canvasWrapper.drawText(tp, Offset(x, y), bottomTitles.rotateAngle); - } - if (data.maxX - horizontalSeek < bottomInterval && - data.maxX != horizontalSeek) { - horizontalSeek = data.maxX; - } else { - horizontalSeek += bottomInterval; - } - } + AxisChartHelper().iterateThroughAxis( + min: data.minX, + max: data.maxX, + interval: bottomInterval, + action: (axisValue) { + if (bottomTitles.checkToShowTitle( + data.minX, data.maxX, bottomTitles, bottomInterval, axisValue)) { + var x = getPixelX(axisValue, viewSize, holder); + var y = viewSize.height + getTopOffsetDrawSize(holder); + + final text = bottomTitles.getTitles(axisValue); + + final span = TextSpan( + style: Utils().getThemeAwareTextStyle( + context, bottomTitles.getTextStyles(context, axisValue)), + text: text, + ); + final tp = TextPainter( + text: span, + textAlign: bottomTitles.textAlign, + textDirection: bottomTitles.textDirection, + textScaleFactor: holder.textScale, + ); + tp.layout(); + + x -= tp.width / 2; + y += bottomTitles.margin; + y -= Utils() + .calculateRotationOffset(tp.size, bottomTitles.rotateAngle) + .dy; + canvasWrapper.drawText(tp, Offset(x, y), bottomTitles.rotateAngle); + } + }, + ); } } diff --git a/test/chart/base/axis_chart_helper_test.dart b/test/chart/base/axis_chart_helper_test.dart new file mode 100644 index 000000000..c6b51b0d6 --- /dev/null +++ b/test/chart/base/axis_chart_helper_test.dart @@ -0,0 +1,91 @@ +import 'package:fl_chart/src/chart/base/axis_chart/axis_chart_helper.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + const tolerance = 0.0001; + group('iterateThroughAxis()', () { + test('test 1', () { + List results = []; + AxisChartHelper().iterateThroughAxis( + min: 0, + max: 0.1, + interval: 0.001, + action: (axisValue) { + results.add(axisValue); + }, + ); + expect(results.length, 101); + }); + + test('test 2', () { + List results = []; + AxisChartHelper().iterateThroughAxis( + min: 0, + minIncluded: false, + max: 0.1, + maxIncluded: false, + interval: 0.001, + action: (axisValue) { + results.add(axisValue); + }, + ); + expect(results.length, 99); + expect(results[0], closeTo(0.001, tolerance)); + expect(results[98], closeTo(0.099, tolerance)); + }); + + test('test 3', () { + List results = []; + AxisChartHelper().iterateThroughAxis( + min: 0, + max: 1000, + interval: 200, + action: (axisValue) { + results.add(axisValue); + }, + ); + expect(results.length, 6); + expect(results[0], 0); + expect(results[1], 200); + expect(results[2], 400); + expect(results[3], 600); + expect(results[4], 800); + expect(results[5], 1000); + }); + + test('test 4', () { + List results = []; + AxisChartHelper().iterateThroughAxis( + min: 0, + max: 10, + interval: 3, + action: (axisValue) { + results.add(axisValue); + }, + ); + expect(results.length, 4); + expect(results[0], 0); + expect(results[1], 3); + expect(results[2], 6); + expect(results[3], 9); + }); + + test('test 4', () { + List results = []; + AxisChartHelper().iterateThroughAxis( + min: 0, + minIncluded: false, + max: 10, + maxIncluded: false, + interval: 3, + action: (axisValue) { + results.add(axisValue); + }, + ); + expect(results.length, 3); + expect(results[0], 3); + expect(results[1], 6); + expect(results[2], 9); + }); + }); +} From db42e7640d6ce97586ae848c6d7c7ecb2a6ee8e3 Mon Sep 17 00:00:00 2001 From: Iman Khoshabi Date: Mon, 31 Jan 2022 13:32:24 +0330 Subject: [PATCH 23/45] Update SOURCES.md --- SOURCES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/SOURCES.md b/SOURCES.md index 493c08ab9..f75dd1ffb 100644 --- a/SOURCES.md +++ b/SOURCES.md @@ -12,6 +12,9 @@ Did you find any new article or source? please contribute to have them all here. * [Stock charts](https://dev.to/kamilpowalowski/stock-charts-with-flchart-library-1gd2) #### Video: + +* [Flutter Web - Dashboard Website Template (Responsive)](https://www.youtube.com/watch?v=3SMdJE_dSxU) + * [Portfolio Dashboard Flutter UI Desktop & Web](https://www.youtube.com/watch?v=H9vXUine7Zo) From 668b4d2d6b436b4a0f9f5d3a1d9306a0c6832964 Mon Sep 17 00:00:00 2001 From: Iman Khoshabi Date: Mon, 31 Jan 2022 13:54:44 +0330 Subject: [PATCH 24/45] Update SOURCES.md --- SOURCES.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/SOURCES.md b/SOURCES.md index f75dd1ffb..e155dc8cc 100644 --- a/SOURCES.md +++ b/SOURCES.md @@ -12,8 +12,6 @@ Did you find any new article or source? please contribute to have them all here. * [Stock charts](https://dev.to/kamilpowalowski/stock-charts-with-flchart-library-1gd2) #### Video: - -* [Flutter Web - Dashboard Website Template (Responsive)](https://www.youtube.com/watch?v=3SMdJE_dSxU) * [Portfolio Dashboard Flutter UI Desktop & Web](https://www.youtube.com/watch?v=H9vXUine7Zo) @@ -27,6 +25,9 @@ Did you find any new article or source? please contribute to have them all here. * [How to build Flutter UI - 3 Steps](https://www.youtube.com/watch?v=I0NBtFS_ibc) + +* [Flutter Web - Dashboard Website Template (Responsive)](https://www.youtube.com/watch?v=3SMdJE_dSxU) + * [How to create charts in Flutter](https://www.youtube.com/watch?v=JBJ6o4blgPA) From 4917dc476a5e44a6ffe20950e6258315df536d67 Mon Sep 17 00:00:00 2001 From: imaN Khoshabi Date: Fri, 4 Feb 2022 04:08:17 +0330 Subject: [PATCH 25/45] Make sample app compatible with Flutter 2.10 --- example/android/app/src/main/AndroidManifest.xml | 2 +- example/android/build.gradle | 4 ++-- example/android/gradle/wrapper/gradle-wrapper.properties | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 788d35b09..bb7d82863 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -6,7 +6,7 @@ additional functionality it is fine to subclass or reimplement FlutterApplication and put your custom class here. --> Date: Sat, 5 Feb 2022 23:01:13 +0330 Subject: [PATCH 26/45] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3aec3662c..a79ee3991 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -💥 A library to draw fantastic charts in Flutter 💥 +💥 FL Chart is a highly customizable Flutter chart library that supports Line Chart, Bar Chart, Pie Chart, Scatter Chart, and Radar Chart. 💥 [![pub package](https://img.shields.io/pub/v/fl_chart.svg)](https://pub.dartlang.org/packages/fl_chart) From 10867bf94e591dd32aa355208c8f0fce93173010 Mon Sep 17 00:00:00 2001 From: imaN Khoshabi Date: Wed, 9 Feb 2022 15:09:18 +0330 Subject: [PATCH 27/45] Remove branch before fetching in checkoutToPR makefile command --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 92691cbdf..e85390d2c 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,7 @@ runTests: flutter test checkoutToPR: + git branch -D pr-$(id) git fetch origin pull/$(id)/head:pr-$(id); \ git checkout pr-$(id) From 20b417fd12247e6a0d17b3266827dd109a8f658d Mon Sep 17 00:00:00 2001 From: Iman Khoshabi Date: Fri, 11 Feb 2022 00:02:46 +0330 Subject: [PATCH 28/45] Fix makefile `checkoutToPR` problem --- Makefile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Makefile b/Makefile index e85390d2c..78ff6834a 100644 --- a/Makefile +++ b/Makefile @@ -14,8 +14,7 @@ runTests: flutter test checkoutToPR: - git branch -D pr-$(id) - git fetch origin pull/$(id)/head:pr-$(id); \ + git fetch origin pull/$(id)/head:pr-$(id) --force; \ git checkout pr-$(id) # Tells you in which version this commit has landed From f395487bf90788ebc1fb329fa194f382d9a69c8d Mon Sep 17 00:00:00 2001 From: prajwal27 <31249460+prajwal27@users.noreply.github.com> Date: Sat, 12 Feb 2022 00:10:13 +0530 Subject: [PATCH 29/45] Implement `clipData` feature in scatter chart. (#898) * feat: Introduce 'clipBubble' attribute to scatter chart. * fix: remove `clipBubble` attribute and implement `clipData` function in scatter chart. * added issue fix #897 to changelog. * modify clip equation to resolve border issue. Co-authored-by: prajwal27wiijii --- CHANGELOG.md | 1 + .../scatter_chart/scatter_chart_data.dart | 2 +- .../scatter_chart/scatter_chart_painter.dart | 47 +++++++++++++++++++ .../scatter_chart_painter_test.dart | 8 +++- 4 files changed, 56 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b91907ba4..0b24b1905 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ ## newVersion +* **BUGFIX** Fix `clipData` implementation in ScatterChart, #897. * **BUGFIX** Fix PieChart changing sections issue (we have disabled semantics for pieChart badgeWidgets), #861. * **BUGFIX** Fix LineChart width smaller width or height lower than 40, #869, #857. * **BUGFIX** Allow to show title when axis diff is zero. diff --git a/lib/src/chart/scatter_chart/scatter_chart_data.dart b/lib/src/chart/scatter_chart/scatter_chart_data.dart index e579cc4a2..dc221f189 100644 --- a/lib/src/chart/scatter_chart/scatter_chart_data.dart +++ b/lib/src/chart/scatter_chart/scatter_chart_data.dart @@ -3,9 +3,9 @@ import 'dart:ui'; import 'package:equatable/equatable.dart'; import 'package:fl_chart/fl_chart.dart'; +import 'package:fl_chart/src/extensions/color_extension.dart'; import 'package:fl_chart/src/utils/lerp.dart'; import 'package:flutter/material.dart'; -import 'package:fl_chart/src/extensions/color_extension.dart'; import 'scatter_chart_helper.dart'; diff --git a/lib/src/chart/scatter_chart/scatter_chart_painter.dart b/lib/src/chart/scatter_chart/scatter_chart_painter.dart index 5e20eefca..c84504c5b 100644 --- a/lib/src/chart/scatter_chart/scatter_chart_painter.dart +++ b/lib/src/chart/scatter_chart/scatter_chart_painter.dart @@ -227,6 +227,49 @@ class ScatterChartPainter extends AxisChartPainter { final data = holder.data; final viewSize = canvasWrapper.size; final chartUsableSize = getChartUsableDrawSize(viewSize, holder); + final clip = data.clipData; + final border = data.borderData.show ? data.borderData.border : null; + + if (data.clipData.any) { + canvasWrapper.saveLayer( + Rect.fromLTRB( + 0, + 0, + canvasWrapper.size.width, + canvasWrapper.size.height, + ), + Paint(), + ); + + var left = 0.0; + var top = 0.0; + var right = viewSize.width; + var bottom = viewSize.height; + + if (clip.left) { + final borderWidth = border?.left.width ?? 0; + left = getLeftOffsetDrawSize(holder) + (borderWidth / 2); + } + if (clip.top) { + final borderWidth = border?.top.width ?? 0; + top = getTopOffsetDrawSize(holder) + (borderWidth / 2); + } + if (clip.right) { + final borderWidth = border?.right.width ?? 0; + right = getLeftOffsetDrawSize(holder) + + chartUsableSize.width - + (borderWidth / 2); + } + if (clip.bottom) { + final borderWidth = border?.bottom.width ?? 0; + bottom = getTopOffsetDrawSize(holder) + + chartUsableSize.height - + (borderWidth / 2); + } + + canvasWrapper.clipRect(Rect.fromLTRB(left, top, right, bottom)); + } + for (final scatterSpot in data.scatterSpots) { if (!scatterSpot.show) { continue; @@ -242,6 +285,10 @@ class ScatterChartPainter extends AxisChartPainter { _spotsPaint, ); } + + if (data.clipData.any) { + canvasWrapper.restore(); + } } @visibleForTesting diff --git a/test/chart/scatter_chart/scatter_chart_painter_test.dart b/test/chart/scatter_chart/scatter_chart_painter_test.dart index 3c1eabb3d..53bfc0aa3 100644 --- a/test/chart/scatter_chart/scatter_chart_painter_test.dart +++ b/test/chart/scatter_chart/scatter_chart_painter_test.dart @@ -1,14 +1,15 @@ import 'dart:math' as math; import 'package:fl_chart/fl_chart.dart'; +import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; import 'package:fl_chart/src/chart/scatter_chart/scatter_chart_painter.dart'; import 'package:fl_chart/src/utils/canvas_wrapper.dart'; import 'package:fl_chart/src/utils/utils.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; + import '../data_pool.dart'; import 'scatter_chart_painter_test.mocks.dart'; @@ -423,6 +424,7 @@ void main() { ScatterSpot(7, 5, radius: 6), ], titlesData: FlTitlesData(show: false), + clipData: FlClipData.all(), ); final ScatterChartPainter scatterChartPainter = ScatterChartPainter(); @@ -441,6 +443,8 @@ void main() { .called(1); verify(_mockCanvasWrapper.drawCircle(const Offset(70, 50), 6, any)) .called(1); + + verify(_mockCanvasWrapper.clipRect(any)).called(1); }); test('test 2', () { @@ -458,6 +462,7 @@ void main() { ScatterSpot(7, 5, show: false), ], titlesData: FlTitlesData(show: false), + clipData: FlClipData.none(), ); final ScatterChartPainter scatterChartPainter = ScatterChartPainter(); @@ -471,6 +476,7 @@ void main() { ); verifyNever(_mockCanvasWrapper.drawCircle(any, any, any)); + verifyNever(_mockCanvasWrapper.clipRect(any)); }); }); From 16100e9f6d5947b33c8f0fdb70b0d0390933f315 Mon Sep 17 00:00:00 2001 From: Iman khoshabi Date: Fri, 11 Feb 2022 22:21:09 +0330 Subject: [PATCH 30/45] Fix lineChart clipData issue with border, #898 (#901) * Fix lineChart clipData issue with border, #898 * Update CHANGELOG.md --- CHANGELOG.md | 2 +- lib/src/chart/line_chart/line_chart_painter.dart | 8 ++++---- test/chart/line_chart/line_chart_painter_test.dart | 12 ++++++------ 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b24b1905..e38b373df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ ## newVersion -* **BUGFIX** Fix `clipData` implementation in ScatterChart, #897. +* **BUGFIX** Fix `clipData` implementation in ScatterChart and LineChart, #897. * **BUGFIX** Fix PieChart changing sections issue (we have disabled semantics for pieChart badgeWidgets), #861. * **BUGFIX** Fix LineChart width smaller width or height lower than 40, #869, #857. * **BUGFIX** Allow to show title when axis diff is zero. diff --git a/lib/src/chart/line_chart/line_chart_painter.dart b/lib/src/chart/line_chart/line_chart_painter.dart index b4de15075..b4356a1ef 100644 --- a/lib/src/chart/line_chart/line_chart_painter.dart +++ b/lib/src/chart/line_chart/line_chart_painter.dart @@ -156,21 +156,21 @@ class LineChartPainter extends AxisChartPainter { if (clip.left) { final borderWidth = border?.left.width ?? 0; - left = getLeftOffsetDrawSize(holder) - (borderWidth / 2); + left = getLeftOffsetDrawSize(holder) + (borderWidth / 2); } if (clip.top) { final borderWidth = border?.top.width ?? 0; - top = getTopOffsetDrawSize(holder) - (borderWidth / 2); + top = getTopOffsetDrawSize(holder) + (borderWidth / 2); } if (clip.right) { final borderWidth = border?.right.width ?? 0; right = - getLeftOffsetDrawSize(holder) + usableSize.width + (borderWidth / 2); + getLeftOffsetDrawSize(holder) + usableSize.width - (borderWidth / 2); } if (clip.bottom) { final borderWidth = border?.bottom.width ?? 0; bottom = - getTopOffsetDrawSize(holder) + usableSize.height + (borderWidth / 2); + getTopOffsetDrawSize(holder) + usableSize.height - (borderWidth / 2); } canvasWrapper.clipRect(Rect.fromLTRB(left, top, right, bottom)); diff --git a/test/chart/line_chart/line_chart_painter_test.dart b/test/chart/line_chart/line_chart_painter_test.dart index 3091f7f91..796a298b1 100644 --- a/test/chart/line_chart/line_chart_painter_test.dart +++ b/test/chart/line_chart/line_chart_painter_test.dart @@ -190,9 +190,9 @@ void main() { final verifyResult = verify(_mockCanvasWrapper.clipRect(captureAny)); final Rect rect = verifyResult.captured.single; verifyResult.called(1); - expect(rect.left, 6); + expect(rect.left, 14); expect(rect.top, 0); - expect(rect.right, 374); + expect(rect.right, 366); expect(rect.bottom, 400); }); @@ -232,10 +232,10 @@ void main() { final verifyResult = verify(_mockCanvasWrapper.clipRect(captureAny)); final Rect rect = verifyResult.captured.single; verifyResult.called(1); - expect(rect.left, 6); - expect(rect.top, 36); - expect(rect.right, 374); - expect(rect.bottom, 364); + expect(rect.left, 14); + expect(rect.top, 44); + expect(rect.right, 366); + expect(rect.bottom, 356); }); }); From 52b0faafa826967b251529bd80992cf32e8f3186 Mon Sep 17 00:00:00 2001 From: Joel Domke Date: Thu, 10 Feb 2022 01:31:50 +0100 Subject: [PATCH 31/45] Add custom distance function and order spots in LineTouchResponse --- CHANGELOG.md | 1 + lib/src/chart/line_chart/line_chart_data.dart | 34 +++- .../chart/line_chart/line_chart_painter.dart | 32 ++-- test/chart/data_pool.dart | 22 +-- .../line_chart/line_chart_painter_test.dart | 153 ++++++++++++++++++ .../line_chart_painter_test.mocks.dart | 10 +- .../line_chart_renderer_test.mocks.dart | 10 +- 7 files changed, 228 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e38b373df..7b10047d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * **BUGFIX** Fix LineChart width smaller width or height lower than 40, #869, #857. * **BUGFIX** Allow to show title when axis diff is zero. * **IMPROVEMENT** Improve iteration over axis values logic (it solves some minor problems on showing titles when min, max values are below than 1.0). +* **BREAKING** `LineTouchResponse` response now contains a list of `TouchLineBarSpot` instead of `LineBarSpot`. They are ordered based on their distance to the touch event and also contain that distance. ## 0.41.0 * **BUGFIX** Fix getNearestTouchedSpot. Previously it returned the first occurrence of a spot within the threshold, and not the nearest, #641, #645. diff --git a/lib/src/chart/line_chart/line_chart_data.dart b/lib/src/chart/line_chart/line_chart_data.dart index 91c370676..3f67b3d98 100644 --- a/lib/src/chart/line_chart/line_chart_data.dart +++ b/lib/src/chart/line_chart/line_chart_data.dart @@ -1397,6 +1397,9 @@ class LineTouchData extends FlTouchData with EquatableMixin { /// Distance threshold to handle the touch event. final double touchSpotThreshold; + /// Distance function used when finding closest points to touch point + final CalculateTouchDistance distanceCalculator; + /// Determines to handle default built-in touch responses, /// [LineTouchResponse] shows a tooltip popup above the touched spot. final bool handleBuiltInTouches; @@ -1433,6 +1436,7 @@ class LineTouchData extends FlTouchData with EquatableMixin { LineTouchTooltipData? touchTooltipData, GetTouchedSpotIndicator? getTouchedSpotIndicator, double? touchSpotThreshold, + CalculateTouchDistance? distanceCalculator, bool? handleBuiltInTouches, GetTouchLineY? getTouchLineStart, GetTouchLineY? getTouchLineEnd, @@ -1440,6 +1444,7 @@ class LineTouchData extends FlTouchData with EquatableMixin { getTouchedSpotIndicator = getTouchedSpotIndicator ?? defaultTouchedIndicators, touchSpotThreshold = touchSpotThreshold ?? 10, + distanceCalculator = distanceCalculator ?? xDistance, handleBuiltInTouches = handleBuiltInTouches ?? true, getTouchLineStart = getTouchLineStart ?? defaultGetTouchLineStart, getTouchLineEnd = getTouchLineEnd ?? defaultGetTouchLineEnd, @@ -1454,6 +1459,7 @@ class LineTouchData extends FlTouchData with EquatableMixin { LineTouchTooltipData? touchTooltipData, GetTouchedSpotIndicator? getTouchedSpotIndicator, double? touchSpotThreshold, + CalculateTouchDistance? distanceCalculator, GetTouchLineY? getTouchLineStart, GetTouchLineY? getTouchLineEnd, bool? handleBuiltInTouches, @@ -1481,6 +1487,7 @@ class LineTouchData extends FlTouchData with EquatableMixin { touchTooltipData, getTouchedSpotIndicator, touchSpotThreshold, + distanceCalculator, handleBuiltInTouches, getTouchLineStart, getTouchLineEnd, @@ -1500,6 +1507,15 @@ typedef GetTouchedSpotIndicator = List Function( typedef GetTouchLineY = double Function( LineChartBarData barData, int spotIndex); +/// Used to calculate the distance between coordinates of a touch event and a spot +typedef CalculateTouchDistance = double Function( + Offset touchPoint, Offset spotPixelCoordinates); + +/// Default distanceCalculator only considers distance on x axis +double xDistance(Offset touchPoint, Offset spotPixelCoordinates) { + return ((touchPoint.dx - spotPixelCoordinates.dx)).abs(); +} + /// Default presentation of touched indicators. List defaultTouchedIndicators( LineChartBarData barData, List indicators) { @@ -1675,6 +1691,19 @@ class LineBarSpot extends FlSpot with EquatableMixin { ]; } +/// A [LineBarSpot] that holds information about the event that selected it +class TouchLineBarSpot extends LineBarSpot { + /// Distance in pixels from where the user taped + final double distance; + + TouchLineBarSpot( + LineChartBarData bar, + int barIndex, + FlSpot spot, + this.distance, + ) : super(bar, barIndex, spot); +} + /// Holds data of showing each row item in the tooltip popup. class LineTooltipItem with EquatableMixin { /// Showing text. @@ -1760,16 +1789,17 @@ class ShowingTooltipIndicators with EquatableMixin { class LineTouchResponse extends BaseTouchResponse { /// touch happened on these spots /// (if a single line provided on the chart, [lineBarSpots]'s length will be 1 always) - final List? lineBarSpots; + final List? lineBarSpots; /// If touch happens, [LineChart] processes it internally and /// passes out a list of [lineBarSpots] it gives you information about the touched spot. + /// They are sorted based on their distance to the touch event LineTouchResponse(this.lineBarSpots) : super(); /// Copies current [LineTouchResponse] to a new [LineTouchResponse], /// and replaces provided values. LineTouchResponse copyWith({ - List? lineBarSpots, + List? lineBarSpots, }) { return LineTouchResponse( lineBarSpots ?? this.lineBarSpots, diff --git a/lib/src/chart/line_chart/line_chart_painter.dart b/lib/src/chart/line_chart/line_chart_painter.dart index b4356a1ef..e12ba6d00 100644 --- a/lib/src/chart/line_chart/line_chart_painter.dart +++ b/lib/src/chart/line_chart/line_chart_painter.dart @@ -1498,7 +1498,7 @@ class LineChartPainter extends AxisChartPainter { /// Processes [localPosition] and checks /// the elements of the chart that are near the offset, /// then makes a [LineTouchResponse] from the elements that has been touched. - List? handleTouch( + List? handleTouch( Offset localPosition, Size size, PaintHolder holder, @@ -1507,7 +1507,7 @@ class LineChartPainter extends AxisChartPainter { /// it holds list of nearest touched spots of each line /// and we use it to draw touch stuff on them - final touchedSpots = []; + final touchedSpots = []; /// draw each line independently on the chart for (var i = 0; i < data.lineBarsData.length; i++) { @@ -1521,17 +1521,20 @@ class LineChartPainter extends AxisChartPainter { } } + touchedSpots.sort((a, b) => a.distance.compareTo(b.distance)); + return touchedSpots.isEmpty ? null : touchedSpots; } /// find the nearest spot base on the touched offset @visibleForTesting - LineBarSpot? getNearestTouchedSpot( - Size viewSize, - Offset touchedPoint, - LineChartBarData barData, - int barDataPosition, - PaintHolder holder) { + TouchLineBarSpot? getNearestTouchedSpot( + Size viewSize, + Offset touchedPoint, + LineChartBarData barData, + int barDataPosition, + PaintHolder holder, + ) { final data = holder.data; if (!barData.show) { return null; @@ -1539,13 +1542,17 @@ class LineChartPainter extends AxisChartPainter { final chartViewSize = getChartUsableDrawSize(viewSize, holder); - /// Find the nearest spot (on X axis) + /// Find the nearest spot (based on distanceCalculator) final sortedSpots = []; double? smallestDistance; for (var spot in barData.spots) { if (spot.isNull()) continue; - final distance = - (touchedPoint.dx - getPixelX(spot.x, chartViewSize, holder)).abs(); + final distance = data.lineTouchData.distanceCalculator( + touchedPoint, + Offset( + getPixelX(spot.x, chartViewSize, holder), + getPixelY(spot.y, chartViewSize, holder), + )); if (distance <= data.lineTouchData.touchSpotThreshold) { smallestDistance ??= distance; @@ -1560,7 +1567,8 @@ class LineChartPainter extends AxisChartPainter { } if (sortedSpots.isNotEmpty) { - return LineBarSpot(barData, barDataPosition, sortedSpots.first); + return TouchLineBarSpot( + barData, barDataPosition, sortedSpots.first, smallestDistance!); } else { return null; } diff --git a/test/chart/data_pool.dart b/test/chart/data_pool.dart index b6f07289f..a726c028c 100644 --- a/test/chart/data_pool.dart +++ b/test/chart/data_pool.dart @@ -274,15 +274,17 @@ class MockData { 33, ); - static final lineBarSpot1 = LineBarSpot( + static final lineBarSpot1 = TouchLineBarSpot( lineChartBarData1, 0, lineChartBarData1.spots.first, + 0, ); - static final lineBarSpot2 = LineBarSpot( + static final lineBarSpot2 = TouchLineBarSpot( MockData.lineChartBarData1, 1, MockData.lineChartBarData1.spots.last, + 2, ); static final lineTouchResponse1 = @@ -1082,27 +1084,27 @@ final LineChartBarData lineChartBarData9 = LineChartBarData( showingIndicators: [0, 1], ); -final LineBarSpot lineBarSpot1 = LineBarSpot( +final TouchLineBarSpot lineBarSpot1 = TouchLineBarSpot( lineChartBarData1, 0, flSpot1, + 0, ); -final LineBarSpot lineBarSpot1Clone = LineBarSpot( +final TouchLineBarSpot lineBarSpot1Clone = TouchLineBarSpot( lineChartBarData1Clone, 0, flSpot1Clone, + 0, ); -final LineBarSpot lineBarSpot2 = LineBarSpot( - lineChartBarData1, - 2, - flSpot1, -); +final TouchLineBarSpot lineBarSpot2 = + TouchLineBarSpot(lineChartBarData1, 2, flSpot1, 2); -final LineBarSpot lineBarSpot3 = LineBarSpot( +final TouchLineBarSpot lineBarSpot3 = TouchLineBarSpot( lineChartBarData1, 100, flSpot1, + 2, ); final LineTouchResponse lineTouchResponse1 = LineTouchResponse( diff --git a/test/chart/line_chart/line_chart_painter_test.dart b/test/chart/line_chart/line_chart_painter_test.dart index 796a298b1..790f4454d 100644 --- a/test/chart/line_chart/line_chart_painter_test.dart +++ b/test/chart/line_chart/line_chart_painter_test.dart @@ -2673,6 +2673,74 @@ void main() { .length, 2); }); + + test('test 3', () { + const viewSize = Size(100, 100); + + final LineChartBarData lineChartBarData1 = LineChartBarData( + show: true, + spots: const [ + FlSpot(1, 1), + FlSpot(2, 1), + FlSpot(3, 1), + FlSpot(8, 1), + ], + dotData: FlDotData(show: true), + barWidth: 80, + isStrokeCapRound: true, + isStepLineChart: true, + shadow: const Shadow( + color: Color(0x0100FF00), + offset: Offset(10, 15), + blurRadius: 10, + ), + ); + + final LineChartBarData lineChartBarData2 = LineChartBarData( + show: true, + spots: const [ + FlSpot(1.3, 1), + FlSpot(2, 1), + FlSpot(3, 1), + FlSpot(4, 1), + ], + dotData: FlDotData(show: true), + barWidth: 80, + isStrokeCapRound: true, + isStepLineChart: true, + shadow: const Shadow( + color: Color(0x0100FF00), + offset: Offset(10, 15), + blurRadius: 10, + ), + ); + + final LineChartData data = LineChartData( + minY: 0, + maxY: 10, + minX: 0, + maxX: 10, + lineBarsData: [lineChartBarData1, lineChartBarData2], + showingTooltipIndicators: [], + titlesData: FlTitlesData(show: false), + axisTitleData: FlAxisTitleData(show: false), + lineTouchData: LineTouchData( + touchSpotThreshold: 5, + )); + + final LineChartPainter lineChartPainter = LineChartPainter(); + final holder = PaintHolder(data, data, 1.0); + + final result1 = + lineChartPainter.handleTouch(const Offset(11, 0), viewSize, holder)!; + expect(result1[0].barIndex, 0); + expect(result1[1].barIndex, 1); + + final result2 = + lineChartPainter.handleTouch(const Offset(12, 0), viewSize, holder)!; + expect(result2[0].barIndex, 1); + expect(result2[1].barIndex, 0); + }); }); group('getNearestTouchedSpot()', () { @@ -2825,6 +2893,91 @@ void main() { expect(result3!.barIndex, 1); expect(result3.spotIndex, 0); }); + + test('test 3', () { + const viewSize = Size(100, 100); + + final LineChartBarData lineChartBarData1 = LineChartBarData( + show: true, + spots: const [ + FlSpot(1, 1), + FlSpot(4, 1), + FlSpot(6, 4), + FlSpot(8, 1), + ], + dotData: FlDotData(show: true), + barWidth: 80, + isStrokeCapRound: true, + isStepLineChart: true, + shadow: const Shadow( + color: Color(0x0100FF00), + offset: Offset(10, 15), + blurRadius: 10, + ), + ); + + final LineChartBarData lineChartBarData2 = LineChartBarData( + show: true, + spots: const [ + FlSpot(1.1, 4), + FlSpot(2, 4), + FlSpot(3.5, 1), + FlSpot(4.3, 4), + ], + dotData: FlDotData(show: true), + barWidth: 80, + isStrokeCapRound: true, + isStepLineChart: true, + shadow: const Shadow( + color: Color(0x0100FF00), + offset: Offset(10, 15), + blurRadius: 10, + ), + ); + + final LineChartData data = LineChartData( + minY: 0, + maxY: 10, + minX: 0, + maxX: 10, + lineBarsData: [lineChartBarData1, lineChartBarData2], + showingTooltipIndicators: [], + titlesData: FlTitlesData(show: false), + axisTitleData: FlAxisTitleData(show: false), + lineTouchData: LineTouchData( + distanceCalculator: (Offset a, Offset b) { + final dx = a.dx - b.dx; + final dy = a.dy - b.dy; + return math.sqrt(dx * dx + dy * dy); + }, + touchSpotThreshold: 5, + )); + + final LineChartPainter lineChartPainter = LineChartPainter(); + final holder = PaintHolder(data, data, 1.0); + expect( + lineChartPainter.getNearestTouchedSpot( + viewSize, const Offset(30, 0), data.lineBarsData[0], 0, holder), + null); + final result1 = lineChartPainter.getNearestTouchedSpot( + viewSize, const Offset(60, 65), data.lineBarsData[0], 0, holder); + expect(result1!.barIndex, 0); + expect(result1.spotIndex, 2); + + expect( + lineChartPainter.getNearestTouchedSpot(viewSize, + const Offset(60, 65.01), data.lineBarsData[0], 0, holder), + null); + expect( + lineChartPainter.getNearestTouchedSpot(viewSize, + const Offset(29.99, 0), data.lineBarsData[1], 1, holder), + null); + + final result2 = lineChartPainter.getNearestTouchedSpot( + viewSize, const Offset(63.5, 63.5), data.lineBarsData[0], 0, holder); + expect(result2!.barIndex, 0); + expect(result2.spotIndex, 2); + }); }); group('drawGrid()', () { diff --git a/test/chart/line_chart/line_chart_painter_test.mocks.dart b/test/chart/line_chart/line_chart_painter_test.mocks.dart index b9b238f14..4423f8e77 100644 --- a/test/chart/line_chart/line_chart_painter_test.mocks.dart +++ b/test/chart/line_chart/line_chart_painter_test.mocks.dart @@ -734,13 +734,13 @@ class MockLineChartPainter extends _i1.Mock implements _i9.LineChartPainter { (super.noSuchMethod(Invocation.method(#getTopOffsetDrawSize, [holder]), returnValue: 0.0) as double); @override - List<_i7.LineBarSpot>? handleTouch(_i2.Offset? localPosition, _i2.Size? size, - _i10.PaintHolder<_i7.LineChartData>? holder) => + List<_i7.TouchLineBarSpot>? handleTouch(_i2.Offset? localPosition, + _i2.Size? size, _i10.PaintHolder<_i7.LineChartData>? holder) => (super.noSuchMethod( Invocation.method(#handleTouch, [localPosition, size, holder])) - as List<_i7.LineBarSpot>?); + as List<_i7.TouchLineBarSpot>?); @override - _i7.LineBarSpot? getNearestTouchedSpot( + _i7.TouchLineBarSpot? getNearestTouchedSpot( _i2.Size? viewSize, _i2.Offset? touchedPoint, _i7.LineChartBarData? barData, @@ -752,7 +752,7 @@ class MockLineChartPainter extends _i1.Mock implements _i9.LineChartPainter { barData, barDataPosition, holder - ])) as _i7.LineBarSpot?); + ])) as _i7.TouchLineBarSpot?); @override void drawAxisTitles( _i3.BuildContext? context, diff --git a/test/chart/line_chart/line_chart_renderer_test.mocks.dart b/test/chart/line_chart/line_chart_renderer_test.mocks.dart index d885c2cee..022409349 100644 --- a/test/chart/line_chart/line_chart_renderer_test.mocks.dart +++ b/test/chart/line_chart/line_chart_renderer_test.mocks.dart @@ -706,13 +706,13 @@ class MockLineChartPainter extends _i1.Mock implements _i9.LineChartPainter { (super.noSuchMethod(Invocation.method(#getTopOffsetDrawSize, [holder]), returnValue: 0.0) as double); @override - List<_i12.LineBarSpot>? handleTouch(_i2.Offset? localPosition, _i2.Size? size, - _i11.PaintHolder<_i12.LineChartData>? holder) => + List<_i12.TouchLineBarSpot>? handleTouch(_i2.Offset? localPosition, + _i2.Size? size, _i11.PaintHolder<_i12.LineChartData>? holder) => (super.noSuchMethod( Invocation.method(#handleTouch, [localPosition, size, holder])) - as List<_i12.LineBarSpot>?); + as List<_i12.TouchLineBarSpot>?); @override - _i12.LineBarSpot? getNearestTouchedSpot( + _i12.TouchLineBarSpot? getNearestTouchedSpot( _i2.Size? viewSize, _i2.Offset? touchedPoint, _i12.LineChartBarData? barData, @@ -724,7 +724,7 @@ class MockLineChartPainter extends _i1.Mock implements _i9.LineChartPainter { barData, barDataPosition, holder - ])) as _i12.LineBarSpot?); + ])) as _i12.TouchLineBarSpot?); @override void drawAxisTitles( _i6.BuildContext? context, From 445c032eff2e07bea497eaccddfa9485dc34b8b8 Mon Sep 17 00:00:00 2001 From: Joel Domke Date: Fri, 11 Feb 2022 21:04:39 +0100 Subject: [PATCH 32/45] Add documentation and make small fix --- CHANGELOG.md | 1 + lib/src/chart/line_chart/line_chart_data.dart | 5 +++-- repo_files/documentations/line_chart.md | 12 +++++++++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b10047d1..6757521d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * **BUGFIX** Allow to show title when axis diff is zero. * **IMPROVEMENT** Improve iteration over axis values logic (it solves some minor problems on showing titles when min, max values are below than 1.0). * **BREAKING** `LineTouchResponse` response now contains a list of `TouchLineBarSpot` instead of `LineBarSpot`. They are ordered based on their distance to the touch event and also contain that distance. +* **IMPROVEMENT** Added `distanceCalculator` to `LineTouchData` which is used to calculate the distance between spots and touch events ## 0.41.0 * **BUGFIX** Fix getNearestTouchedSpot. Previously it returned the first occurrence of a spot within the threshold, and not the nearest, #641, #645. diff --git a/lib/src/chart/line_chart/line_chart_data.dart b/lib/src/chart/line_chart/line_chart_data.dart index 3f67b3d98..c116d9916 100644 --- a/lib/src/chart/line_chart/line_chart_data.dart +++ b/lib/src/chart/line_chart/line_chart_data.dart @@ -1444,7 +1444,7 @@ class LineTouchData extends FlTouchData with EquatableMixin { getTouchedSpotIndicator = getTouchedSpotIndicator ?? defaultTouchedIndicators, touchSpotThreshold = touchSpotThreshold ?? 10, - distanceCalculator = distanceCalculator ?? xDistance, + distanceCalculator = distanceCalculator ?? _xDistance, handleBuiltInTouches = handleBuiltInTouches ?? true, getTouchLineStart = getTouchLineStart ?? defaultGetTouchLineStart, getTouchLineEnd = getTouchLineEnd ?? defaultGetTouchLineEnd, @@ -1472,6 +1472,7 @@ class LineTouchData extends FlTouchData with EquatableMixin { getTouchedSpotIndicator: getTouchedSpotIndicator ?? this.getTouchedSpotIndicator, touchSpotThreshold: touchSpotThreshold ?? this.touchSpotThreshold, + distanceCalculator: distanceCalculator ?? this.distanceCalculator, getTouchLineStart: getTouchLineStart ?? this.getTouchLineStart, getTouchLineEnd: getTouchLineEnd ?? this.getTouchLineEnd, handleBuiltInTouches: handleBuiltInTouches ?? this.handleBuiltInTouches, @@ -1512,7 +1513,7 @@ typedef CalculateTouchDistance = double Function( Offset touchPoint, Offset spotPixelCoordinates); /// Default distanceCalculator only considers distance on x axis -double xDistance(Offset touchPoint, Offset spotPixelCoordinates) { +double _xDistance(Offset touchPoint, Offset spotPixelCoordinates) { return ((touchPoint.dx - spotPixelCoordinates.dx)).abs(); } diff --git a/repo_files/documentations/line_chart.md b/repo_files/documentations/line_chart.md index 9d9133d5d..c854c3e9e 100644 --- a/repo_files/documentations/line_chart.md +++ b/repo_files/documentations/line_chart.md @@ -169,6 +169,7 @@ When you change the chart's state, it animates to the new state internally (usin |touchTooltipData|a [LineTouchTooltipData](#LineTouchTooltipData), that determines how show the tooltip on top of touched spots (appearance of the showing tooltip bubble)|LineTouchTooltipData| |getTouchedSpotIndicator| a callback that retrieves list of [TouchedSpotIndicatorData](#TouchedSpotIndicatorData) by the given list of [LineBarSpot](#LineBarSpot) for showing the indicators on touched spots|defaultTouchedIndicators| |touchSpotThreshold|the threshold of the touch accuracy|10| +|distanceCalculator| a function to calculate the distance between a spot and a touch event| _xDistance| |handleBuiltInTouches| set this true if you want the built in touch handling (show a tooltip bubble and an indicator on touched spots) | true| |getTouchLineStart| controls where the line starts, default is bottom of the chart| defaultGetTouchLineStart| |getTouchLineEnd| controls where the line ends, default is the touch point| defaultGetTouchLineEnd| @@ -212,10 +213,19 @@ When you change the chart's state, it animates to the new state internally (usin |spotIndex|index of the target [FlSpot](#FlSpot) inside [LineChartBarData](#LineChartBarData)|null| +### TouchLineBarSpot +|PropName|Description|default value| +|:-------|:----------|:------------| +|bar|the [LineChartBarData](#LineChartBarData) that contains a spot|null| +|barIndex|index of the target [LineChartBarData](#LineChartBarData) inside [LineChartData](#LineChartData)|null| +|spotIndex|index of the target [FlSpot](#FlSpot) inside [LineChartBarData](#LineChartBarData)|null| +|distance|distance to the touch event|null| + + ### LineTouchResponse |PropName|Description|default value| |:-------|:----------|:------------| -|lineBarSpots|a list of [LineBarSpot](#LineBarSpot)|null| +|lineBarSpots|a list of [TouchLineBarSpot](#TouchLineBarSpot)|null| ### ShowingTooltipIndicators |PropName|Description|default value| From 19091b9d47815855eda232a0452c43d89dfb2192 Mon Sep 17 00:00:00 2001 From: Iman khoshabi Date: Sat, 12 Feb 2022 00:18:16 +0330 Subject: [PATCH 33/45] Fix getBestInitialIntervalValue() problem with values above or below zero (#894) * Fix getBestInitialIntervalValue() problem with values above or below zero, #893. * Add `baselineX` and `baselineY` property to all of our axis-based charts (Line, Bar, Scatter). default is zero, #893. * Remove all linux/flutter/ephemeral files from example directory * Update CHANGELOG.md, bar_chart.md, and line_chart.md * Update line_chart.md * Update CHANGELOG.md --- CHANGELOG.md | 1 + .../flutter_linux/fl_basic_message_channel.h | 205 ------ .../ephemeral/flutter_linux/fl_binary_codec.h | 45 -- .../flutter_linux/fl_binary_messenger.h | 158 ----- .../ephemeral/flutter_linux/fl_dart_project.h | 125 ---- .../ephemeral/flutter_linux/fl_engine.h | 49 -- .../flutter_linux/fl_event_channel.h | 187 ------ .../flutter_linux/fl_json_message_codec.h | 96 --- .../flutter_linux/fl_json_method_codec.h | 44 -- .../flutter_linux/fl_message_codec.h | 129 ---- .../ephemeral/flutter_linux/fl_method_call.h | 115 ---- .../flutter_linux/fl_method_channel.h | 190 ------ .../ephemeral/flutter_linux/fl_method_codec.h | 131 ---- .../flutter_linux/fl_method_response.h | 212 ------- .../flutter_linux/fl_plugin_registrar.h | 54 -- .../flutter_linux/fl_plugin_registry.h | 61 -- .../flutter_linux/fl_standard_message_codec.h | 47 -- .../flutter_linux/fl_standard_method_codec.h | 44 -- .../ephemeral/flutter_linux/fl_string_codec.h | 44 -- .../ephemeral/flutter_linux/fl_value.h | 586 ------------------ .../flutter/ephemeral/flutter_linux/fl_view.h | 61 -- .../ephemeral/flutter_linux/flutter_linux.h | 33 - .../flutter/ephemeral/generated_config.cmake | 15 - example/linux/flutter/ephemeral/icudtl.dat | Bin 799760 -> 0 bytes lib/src/chart/bar_chart/bar_chart_data.dart | 31 +- .../chart/bar_chart/bar_chart_painter.dart | 2 + .../base/axis_chart/axis_chart_data.dart | 12 +- .../base/axis_chart/axis_chart_helper.dart | 14 +- .../base/axis_chart/axis_chart_painter.dart | 2 + lib/src/chart/line_chart/line_chart_data.dart | 12 + .../chart/line_chart/line_chart_painter.dart | 4 + .../scatter_chart/scatter_chart_data.dart | 12 + .../scatter_chart/scatter_chart_painter.dart | 4 + lib/src/utils/utils.dart | 11 +- repo_files/documentations/bar_chart.md | 1 + repo_files/documentations/line_chart.md | 5 + .../bar_chart/bar_chart_painter_test.dart | 2 +- .../bar_chart_painter_test.mocks.dart | 11 +- test/chart/base/axis_chart_helper_test.dart | 28 +- .../line_chart_painter_test.mocks.dart | 11 +- .../pie_chart_painter_test.mocks.dart | 11 +- .../pie_chart_renderer_test.mocks.dart | 2 +- .../radar_chart_painter_test.mocks.dart | 11 +- .../scatter_chart_painter_test.dart | 6 +- .../scatter_chart_painter_test.mocks.dart | 11 +- .../scatter_chart_renderer_test.mocks.dart | 3 +- test/utils/canvas_wrapper_test.mocks.dart | 11 +- test/utils/utils_test.dart | 43 +- test/utils/utils_test.mocks.dart | 93 --- 49 files changed, 196 insertions(+), 2789 deletions(-) delete mode 100644 example/linux/flutter/ephemeral/flutter_linux/fl_basic_message_channel.h delete mode 100644 example/linux/flutter/ephemeral/flutter_linux/fl_binary_codec.h delete mode 100644 example/linux/flutter/ephemeral/flutter_linux/fl_binary_messenger.h delete mode 100644 example/linux/flutter/ephemeral/flutter_linux/fl_dart_project.h delete mode 100644 example/linux/flutter/ephemeral/flutter_linux/fl_engine.h delete mode 100644 example/linux/flutter/ephemeral/flutter_linux/fl_event_channel.h delete mode 100644 example/linux/flutter/ephemeral/flutter_linux/fl_json_message_codec.h delete mode 100644 example/linux/flutter/ephemeral/flutter_linux/fl_json_method_codec.h delete mode 100644 example/linux/flutter/ephemeral/flutter_linux/fl_message_codec.h delete mode 100644 example/linux/flutter/ephemeral/flutter_linux/fl_method_call.h delete mode 100644 example/linux/flutter/ephemeral/flutter_linux/fl_method_channel.h delete mode 100644 example/linux/flutter/ephemeral/flutter_linux/fl_method_codec.h delete mode 100644 example/linux/flutter/ephemeral/flutter_linux/fl_method_response.h delete mode 100644 example/linux/flutter/ephemeral/flutter_linux/fl_plugin_registrar.h delete mode 100644 example/linux/flutter/ephemeral/flutter_linux/fl_plugin_registry.h delete mode 100644 example/linux/flutter/ephemeral/flutter_linux/fl_standard_message_codec.h delete mode 100644 example/linux/flutter/ephemeral/flutter_linux/fl_standard_method_codec.h delete mode 100644 example/linux/flutter/ephemeral/flutter_linux/fl_string_codec.h delete mode 100644 example/linux/flutter/ephemeral/flutter_linux/fl_value.h delete mode 100644 example/linux/flutter/ephemeral/flutter_linux/fl_view.h delete mode 100644 example/linux/flutter/ephemeral/flutter_linux/flutter_linux.h delete mode 100644 example/linux/flutter/ephemeral/generated_config.cmake delete mode 100644 example/linux/flutter/ephemeral/icudtl.dat delete mode 100644 test/utils/utils_test.mocks.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 6757521d1..5baae5417 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * **IMPROVEMENT** Improve iteration over axis values logic (it solves some minor problems on showing titles when min, max values are below than 1.0). * **BREAKING** `LineTouchResponse` response now contains a list of `TouchLineBarSpot` instead of `LineBarSpot`. They are ordered based on their distance to the touch event and also contain that distance. * **IMPROVEMENT** Added `distanceCalculator` to `LineTouchData` which is used to calculate the distance between spots and touch events +* **IMPROVEMENT** Add `baselineX` and `baselineY` property in our axis-based charts, It fixes a problem about `interval` which mentioned in #893 (check [this sample](https://github.com/imaNNeoFighT/fl_chart/blob/hotfix/initial-interval-baseline/repo_files/documentations/line_chart.md#gist---baselinex-baseliney-sample-source-code). ## 0.41.0 * **BUGFIX** Fix getNearestTouchedSpot. Previously it returned the first occurrence of a spot within the threshold, and not the nearest, #641, #645. diff --git a/example/linux/flutter/ephemeral/flutter_linux/fl_basic_message_channel.h b/example/linux/flutter/ephemeral/flutter_linux/fl_basic_message_channel.h deleted file mode 100644 index b9fec0160..000000000 --- a/example/linux/flutter/ephemeral/flutter_linux/fl_basic_message_channel.h +++ /dev/null @@ -1,205 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_BASIC_MESSAGE_CHANNEL_H_ -#define FLUTTER_SHELL_PLATFORM_LINUX_FL_BASIC_MESSAGE_CHANNEL_H_ - -#if !defined(__FLUTTER_LINUX_INSIDE__) && !defined(FLUTTER_LINUX_COMPILATION) -#error "Only can be included directly." -#endif - -#include -#include - -#include "fl_binary_messenger.h" -#include "fl_message_codec.h" - -G_BEGIN_DECLS - -G_DECLARE_FINAL_TYPE(FlBasicMessageChannel, - fl_basic_message_channel, - FL, - BASIC_MESSAGE_CHANNEL, - GObject) - -G_DECLARE_FINAL_TYPE(FlBasicMessageChannelResponseHandle, - fl_basic_message_channel_response_handle, - FL, - BASIC_MESSAGE_CHANNEL_RESPONSE_HANDLE, - GObject) - -/** - * FlBasicMessageChannel: - * - * #FlBasicMessageChannel is an object that allows sending and receiving - * messages to/from Dart code over platform channels. - * - * The following example shows how to send messages on a channel: - * - * |[ - * static FlBasicMessageChannel *channel = NULL; - * - * static void message_cb (FlBasicMessageChannel* channel, - * FlValue* message, - * FlBasicMessageChannelResponseHandle* response_handle, - * gpointer user_data) { - * g_autoptr(FlValue) response = handle_message (message); - * g_autoptr(GError) error = NULL; - * if (!fl_basic_message_channel_respond (channel, response_handle, response, - * &error)) - * g_warning ("Failed to send channel response: %s", error->message); - * } - * - * static void message_response_cb (GObject *object, - * GAsyncResult *result, - * gpointer user_data) { - * g_autoptr(GError) error = NULL; - * g_autoptr(FlValue) response = - * fl_basic_message_channel_send_finish (FL_BASIC_MESSAGE_CHANNEL (object), - * result, &error); - * if (response == NULL) { - * g_warning ("Failed to send message: %s", error->message); - * return; - * } - * - * handle_response (response); - * } - * - * static void setup_channel () { - * g_autoptr(FlStandardMessageCodec) codec = fl_standard_message_codec_new (); - * channel = fl_basic_message_channel_new (messenger, "flutter/foo", - * FL_MESSAGE_CODEC (codec)); - * fl_basic_message_channel_set_message_handler (channel, message_cb, NULL, - * NULL); - * - * g_autoptr(FlValue) message = fl_value_new_string ("Hello World"); - * fl_basic_message_channel_send (channel, message, NULL, - * message_response_cb, NULL); - * } - * ]| - * - * #FlBasicMessageChannel matches the BasicMessageChannel class in the Flutter - * services library. - */ - -/** - * FlBasicMessageChannelResponseHandle: - * - * #FlBasicMessageChannelResponseHandle is an object used to send responses - * with. - */ - -/** - * FlBasicMessageChannelMessageHandler: - * @channel: an #FlBasicMessageChannel. - * @message: message received. - * @response_handle: a handle to respond to the message with. - * @user_data: (closure): data provided when registering this handler. - * - * Function called when a message is received. Call - * fl_basic_message_channel_respond() to respond to this message. If the - * response is not occurring in this callback take a reference to - * @response_handle and release that once it has been responded to. Failing to - * respond before the last reference to @response_handle is dropped is a - * programming error. - */ -typedef void (*FlBasicMessageChannelMessageHandler)( - FlBasicMessageChannel* channel, - FlValue* message, - FlBasicMessageChannelResponseHandle* response_handle, - gpointer user_data); - -/** - * fl_basic_message_channel_new: - * @messenger: an #FlBinaryMessenger. - * @name: a channel name. - * @codec: the message codec. - * - * Creates a basic message channel. @codec must match the codec used on the Dart - * end of the channel. - * - * Returns: a new #FlBasicMessageChannel. - */ -FlBasicMessageChannel* fl_basic_message_channel_new( - FlBinaryMessenger* messenger, - const gchar* name, - FlMessageCodec* codec); - -/** - * fl_basic_message_channel_set_message_handler: - * @channel: an #FlBasicMessageChannel. - * @handler: (allow-none): function to call when a message is received on this - * channel or %NULL to disable the handler. - * @user_data: (closure): user data to pass to @handler. - * @destroy_notify: (allow-none): a function which gets called to free - * @user_data, or %NULL. - * - * Sets the function called when a message is received from the Dart side of the - * channel. See #FlBasicMessageChannelMessageHandler for details on how to - * respond to messages. - * - * The handler is removed if the channel is closed or is replaced by another - * handler, set @destroy_notify if you want to detect this. - */ -void fl_basic_message_channel_set_message_handler( - FlBasicMessageChannel* channel, - FlBasicMessageChannelMessageHandler handler, - gpointer user_data, - GDestroyNotify destroy_notify); - -/** - * fl_basic_message_channel_respond: - * @channel: an #FlBasicMessageChannel. - * @response_handle: handle that was provided in a - * #FlBasicMessageChannelMessageHandler. - * @message: (allow-none): message response to send or %NULL for an empty - * response. - * @error: (allow-none): #GError location to store the error occurring, or %NULL - * to ignore. - * - * Responds to a message. - * - * Returns: %TRUE on success. - */ -gboolean fl_basic_message_channel_respond( - FlBasicMessageChannel* channel, - FlBasicMessageChannelResponseHandle* response_handle, - FlValue* message, - GError** error); - -/** - * fl_basic_message_channel_send: - * @channel: an #FlBasicMessageChannel. - * @message: message to send, must match what the #FlMessageCodec supports. - * @cancellable: (allow-none): a #GCancellable or %NULL. - * @callback: (scope async): (allow-none): a #GAsyncReadyCallback to call when - * the request is satisfied or %NULL to ignore the response. - * @user_data: (closure): user data to pass to @callback. - * - * Asynchronously sends a message. - */ -void fl_basic_message_channel_send(FlBasicMessageChannel* channel, - FlValue* message, - GCancellable* cancellable, - GAsyncReadyCallback callback, - gpointer user_data); - -/** - * fl_basic_message_channel_send_finish: - * @channel: an #FlBasicMessageChannel. - * @result: a #GAsyncResult. - * @error: (allow-none): #GError location to store the error occurring, or %NULL - * to ignore. - * - * Completes request started with fl_basic_message_channel_send(). - * - * Returns: message response on success or %NULL on error. - */ -FlValue* fl_basic_message_channel_send_finish(FlBasicMessageChannel* channel, - GAsyncResult* result, - GError** error); - -G_END_DECLS - -#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_BASIC_MESSAGE_CHANNEL_H_ diff --git a/example/linux/flutter/ephemeral/flutter_linux/fl_binary_codec.h b/example/linux/flutter/ephemeral/flutter_linux/fl_binary_codec.h deleted file mode 100644 index e7223d8df..000000000 --- a/example/linux/flutter/ephemeral/flutter_linux/fl_binary_codec.h +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_BINARY_CODEC_H_ -#define FLUTTER_SHELL_PLATFORM_LINUX_FL_BINARY_CODEC_H_ - -#if !defined(__FLUTTER_LINUX_INSIDE__) && !defined(FLUTTER_LINUX_COMPILATION) -#error "Only can be included directly." -#endif - -#include "fl_message_codec.h" - -G_BEGIN_DECLS - -G_DECLARE_FINAL_TYPE(FlBinaryCodec, - fl_binary_codec, - FL, - BINARY_CODEC, - FlMessageCodec) - -/** - * FlBinaryCodec: - * - * #FlBinaryCodec is an #FlMessageCodec that implements the Flutter binary - * message encoding. This only encodes and decodes #FlValue of type - * #FL_VALUE_TYPE_UINT8_LIST, other types #FlValues will generate an error - * during encoding. - * - * #FlBinaryCodec matches the BinaryCodec class in the Flutter services - * library. - */ - -/** - * fl_binary_codec_new: - * - * Creates an #FlBinaryCodec. - * - * Returns: a new #FlBinaryCodec. - */ -FlBinaryCodec* fl_binary_codec_new(); - -G_END_DECLS - -#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_BINARY_CODEC_H_ diff --git a/example/linux/flutter/ephemeral/flutter_linux/fl_binary_messenger.h b/example/linux/flutter/ephemeral/flutter_linux/fl_binary_messenger.h deleted file mode 100644 index 4f7398654..000000000 --- a/example/linux/flutter/ephemeral/flutter_linux/fl_binary_messenger.h +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_BINARY_MESSENGER_H_ -#define FLUTTER_SHELL_PLATFORM_LINUX_FL_BINARY_MESSENGER_H_ - -#if !defined(__FLUTTER_LINUX_INSIDE__) && !defined(FLUTTER_LINUX_COMPILATION) -#error "Only can be included directly." -#endif - -#include -#include - -G_BEGIN_DECLS - -/** - * FlBinaryMessengerError: - * @FL_BINARY_MESSENGER_ERROR_ALREADY_RESPONDED: unable to send response, this - * message has already been responded to. - * - * Errors for #FlBinaryMessenger objects to set on failures. - */ -#define FL_BINARY_MESSENGER_ERROR fl_binary_messenger_codec_error_quark() - -typedef enum { - FL_BINARY_MESSENGER_ERROR_ALREADY_RESPONDED, -} FlBinaryMessengerError; - -GQuark fl_binary_messenger_codec_error_quark(void) G_GNUC_CONST; - -G_DECLARE_FINAL_TYPE(FlBinaryMessenger, - fl_binary_messenger, - FL, - BINARY_MESSENGER, - GObject) - -G_DECLARE_FINAL_TYPE(FlBinaryMessengerResponseHandle, - fl_binary_messenger_response_handle, - FL, - BINARY_MESSENGER_RESPONSE_HANDLE, - GObject) - -/** - * FlBinaryMessenger: - * - * #FlBinaryMessenger is an object that allows sending and receiving of platform - * messages with an #FlEngine. - */ - -/** - * FlBinaryMessengerResponseHandle: - * - * #FlBinaryMessengerResponseHandle is an object used to send responses with. - */ - -/** - * FlBinaryMessengerMessageHandler: - * @messenger: an #FlBinaryMessenger. - * @channel: channel message received on. - * @message: message content received from Dart. - * @response_handle: a handle to respond to the message with. - * @user_data: (closure): data provided when registering this handler. - * - * Function called when platform messages are received. Call - * fl_binary_messenger_send_response() to respond to this message. If the - * response is not occurring in this callback take a reference to - * @response_handle and release that once it has been responded to. Failing to - * respond before the last reference to @response_handle is dropped is a - * programming error. - */ -typedef void (*FlBinaryMessengerMessageHandler)( - FlBinaryMessenger* messenger, - const gchar* channel, - GBytes* message, - FlBinaryMessengerResponseHandle* response_handle, - gpointer user_data); - -/** - * fl_binary_messenger_set_platform_message_handler: - * @binary_messenger: an #FlBinaryMessenger. - * @channel: channel to listen on. - * @handler: (allow-none): function to call when a message is received on this - * channel or %NULL to disable a handler - * @user_data: (closure): user data to pass to @handler. - * @destroy_notify: (allow-none): a function which gets called to free - * @user_data, or %NULL. - * - * Sets the function called when a platform message is received on the given - * channel. See #FlBinaryMessengerMessageHandler for details on how to respond - * to messages. - * - * The handler is removed if the channel is closed or is replaced by another - * handler, set @destroy_notify if you want to detect this. - */ -void fl_binary_messenger_set_message_handler_on_channel( - FlBinaryMessenger* messenger, - const gchar* channel, - FlBinaryMessengerMessageHandler handler, - gpointer user_data, - GDestroyNotify destroy_notify); - -/** - * fl_binary_messenger_send_response: - * @binary_messenger: an #FlBinaryMessenger. - * @response_handle: handle that was provided in a - * #FlBinaryMessengerMessageHandler. - * @response: (allow-none): response to send or %NULL for an empty response. - * @error: (allow-none): #GError location to store the error occurring, or %NULL - * to ignore. - * - * Responds to a platform message. - * - * Returns: %TRUE on success. - */ -gboolean fl_binary_messenger_send_response( - FlBinaryMessenger* messenger, - FlBinaryMessengerResponseHandle* response_handle, - GBytes* response, - GError** error); - -/** - * fl_binary_messenger_send_on_channel: - * @binary_messenger: an #FlBinaryMessenger. - * @channel: channel to send to. - * @message: (allow-none): message buffer to send or %NULL for an empty message. - * @cancellable: (allow-none): a #GCancellable or %NULL. - * @callback: (scope async): a #GAsyncReadyCallback to call when the request is - * satisfied. - * @user_data: (closure): user data to pass to @callback. - * - * Asynchronously sends a platform message. - */ -void fl_binary_messenger_send_on_channel(FlBinaryMessenger* messenger, - const gchar* channel, - GBytes* message, - GCancellable* cancellable, - GAsyncReadyCallback callback, - gpointer user_data); - -/** - * fl_binary_messenger_send_on_channel_finish: - * @binary_messenger: an #FlBinaryMessenger. - * @result: a #GAsyncResult. - * @error: (allow-none): #GError location to store the error occurring, or %NULL - * to ignore. - * - * Completes request started with fl_binary_messenger_send_on_channel(). - * - * Returns: (transfer full): message response on success or %NULL on error. - */ -GBytes* fl_binary_messenger_send_on_channel_finish(FlBinaryMessenger* messenger, - GAsyncResult* result, - GError** error); - -G_END_DECLS - -#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_BINARY_MESSENGER_H_ diff --git a/example/linux/flutter/ephemeral/flutter_linux/fl_dart_project.h b/example/linux/flutter/ephemeral/flutter_linux/fl_dart_project.h deleted file mode 100644 index 1b56e24c3..000000000 --- a/example/linux/flutter/ephemeral/flutter_linux/fl_dart_project.h +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_DART_PROJECT_H_ -#define FLUTTER_SHELL_PLATFORM_LINUX_FL_DART_PROJECT_H_ - -#include - -#if !defined(__FLUTTER_LINUX_INSIDE__) && !defined(FLUTTER_LINUX_COMPILATION) -#error "Only can be included directly." -#endif - -G_BEGIN_DECLS - -G_DECLARE_FINAL_TYPE(FlDartProject, fl_dart_project, FL, DART_PROJECT, GObject) - -/** - * FlDartProject: - * - * #FlDartProject represents a Dart project. It is used to provide information - * about the application when creating an #FlView. - */ - -/** - * fl_dart_project_new: - * - * Creates a Flutter project for the currently running executable. The following - * data files are required relative to the location of the executable: - * - data/flutter_assets/ (as built by the Flutter tool). - * - data/icudtl.dat (provided as a resource by the Flutter tool). - * - lib/libapp.so (as built by the Flutter tool when in AOT mode). - * - * Returns: a new #FlDartProject. - */ -FlDartProject* fl_dart_project_new(); - -/** - * fl_dart_project_set_enable_mirrors: - * @project: an #FlDartProject. - * @enable_mirrors: %TRUE if the dart:mirrors library should be used. - * - * Sets if this Flutter project can use the dart:mirrors library. - * - * Deprecated: This function is temporary and will be removed in a future - * release. - */ -void fl_dart_project_set_enable_mirrors(FlDartProject* project, - gboolean enable_mirrors) G_DEPRECATED; - -/** - * fl_dart_project_get_enable_mirrors: - * @project: an #FlDartProject. - * - * Gets if this Flutter project can use the dart:mirrors library. - * - * Returns: %TRUE if the dart:mirrors library can be used. - * - * Deprecated: This function is temporary and will be removed in a future - * release. - */ -gboolean fl_dart_project_get_enable_mirrors(FlDartProject* project) - G_DEPRECATED; - -/** - * fl_dart_project_get_aot_library_path: - * @project: an #FlDartProject. - * - * Gets the path to the AOT library in the Flutter application. - * - * Returns: (type filename): an absolute file path, e.g. - * "/projects/my_dart_project/lib/libapp.so". - */ -const gchar* fl_dart_project_get_aot_library_path(FlDartProject* project); - -/** - * fl_dart_project_get_assets_path: - * @project: an #FlDartProject. - * - * Gets the path to the directory containing the assets used in the Flutter - * application. - * - * Returns: (type filename): an absolute directory path, e.g. - * "/projects/my_dart_project/data/flutter_assets". - */ -const gchar* fl_dart_project_get_assets_path(FlDartProject* project); - -/** - * fl_dart_project_get_icu_data_path: - * @project: an #FlDartProject. - * - * Gets the path to the ICU data file in the Flutter application. - * - * Returns: (type filename): an absolute file path, e.g. - * "/projects/my_dart_project/data/icudtl.dat". - */ -const gchar* fl_dart_project_get_icu_data_path(FlDartProject* project); - -/** - * fl_dart_project_set_dart_entrypoint_arguments: - * @project: an #FlDartProject. - * @argv: a pointer to a NULL-terminated array of C strings containing the - * command line arguments. - * - * Sets the command line arguments to be passed through to the Dart - * entrypoint function. - */ -void fl_dart_project_set_dart_entrypoint_arguments(FlDartProject* project, - char** argv); - -/** - * fl_dart_project_get_dart_entrypoint_arguments: - * @project: an #FlDartProject. - * - * Gets the command line arguments to be passed through to the Dart entrypoint - * function. - * - * Returns: a NULL-terminated array of strings containing the command line - * arguments to be passed to the Dart entrypoint. - */ -gchar** fl_dart_project_get_dart_entrypoint_arguments(FlDartProject* project); - -G_END_DECLS - -#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_DART_PROJECT_H_ diff --git a/example/linux/flutter/ephemeral/flutter_linux/fl_engine.h b/example/linux/flutter/ephemeral/flutter_linux/fl_engine.h deleted file mode 100644 index 201c38f4a..000000000 --- a/example/linux/flutter/ephemeral/flutter_linux/fl_engine.h +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_ENGINE_H_ -#define FLUTTER_SHELL_PLATFORM_LINUX_FL_ENGINE_H_ - -#if !defined(__FLUTTER_LINUX_INSIDE__) && !defined(FLUTTER_LINUX_COMPILATION) -#error "Only can be included directly." -#endif - -#include - -#include "fl_binary_messenger.h" -#include "fl_dart_project.h" - -G_BEGIN_DECLS - -G_DECLARE_FINAL_TYPE(FlEngine, fl_engine, FL, ENGINE, GObject) - -/** - * FlEngine: - * - * #FlEngine is an object that contains a running Flutter engine. - */ - -/** - * fl_engine_new_headless: - * @project: an #FlDartProject. - * - * Creates new Flutter engine running in headless mode. - * - * Returns: a new #FlEngine. - */ -FlEngine* fl_engine_new_headless(FlDartProject* project); - -/** - * fl_engine_get_binary_messenger: - * @engine: an #FlEngine. - * - * Gets the messenger to communicate with this engine. - * - * Returns: an #FlBinaryMessenger. - */ -FlBinaryMessenger* fl_engine_get_binary_messenger(FlEngine* engine); - -G_END_DECLS - -#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_ENGINE_H_ diff --git a/example/linux/flutter/ephemeral/flutter_linux/fl_event_channel.h b/example/linux/flutter/ephemeral/flutter_linux/fl_event_channel.h deleted file mode 100644 index 7141defbe..000000000 --- a/example/linux/flutter/ephemeral/flutter_linux/fl_event_channel.h +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_EVENT_CHANNEL_H_ -#define FLUTTER_SHELL_PLATFORM_LINUX_FL_EVENT_CHANNEL_H_ - -#if !defined(__FLUTTER_LINUX_INSIDE__) && !defined(FLUTTER_LINUX_COMPILATION) -#error "Only can be included directly." -#endif - -#include -#include - -#include "fl_binary_messenger.h" -#include "fl_method_channel.h" -#include "fl_method_response.h" - -G_BEGIN_DECLS - -G_DECLARE_FINAL_TYPE(FlEventChannel, - fl_event_channel, - FL, - EVENT_CHANNEL, - GObject) - -/** - * FlEventChannel: - * - * #FlEventChannel is an object that allows sending - * an events stream to Dart code over platform channels. - * - * The following example shows how to send events on a channel: - * - * |[ - * static FlEventChannel *channel = NULL; - * static gboolean send_events = FALSE; - * - * static void event_occurs_cb (FooEvent *event) { - * if (send_events) { - * g_autoptr(FlValue) message = foo_event_to_value (event); - * g_autoptr(GError) error = NULL; - * if (!fl_event_channel_send (channel, message, NULL, &error)) { - * g_warning ("Failed to send event: %s", error->message); - * } - * } - * } - * - * static FlMethodErrorResponse* listen_cb (FlEventChannel* channel, - * FlValue *args, - * gpointer user_data) { - * send_events = TRUE; - * return NULL; - * } - * - * static FlMethodErrorResponse* cancel_cb (GObject *object, - * FlValue *args, - * gpointer user_data) { - * send_events = FALSE; - * return NULL; - * } - * - * static void setup_channel () { - * g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new (); - * channel = fl_event_channel_new (messenger, "flutter/foo", - * FL_METHOD_CODEC (codec)); - * fl_event_channel_set_stream_handlers (channel, listen_cb, cancel_cb, - * NULL, NULL); - * } - * ]| - * - * #FlEventChannel matches the EventChannel class in the Flutter - * services library. - */ - -/** - * FlEventChannelHandler: - * @channel: an #FlEventChannel. - * @args: arguments passed from the Dart end of the channel. - * @user_data: (closure): data provided when registering this handler. - * - * Function called when the stream is listened to or cancelled. - * - * Returns: (transfer full): an #FlMethodErrorResponse or %NULL if no error. - */ -typedef FlMethodErrorResponse* (*FlEventChannelHandler)(FlEventChannel* channel, - FlValue* args, - gpointer user_data); - -/** - * fl_event_channel_new: - * @messenger: an #FlBinaryMessenger. - * @name: a channel name. - * @codec: the message codec. - * - * Creates an event channel. @codec must match the codec used on the Dart - * end of the channel. - * - * Returns: a new #FlEventChannel. - */ -FlEventChannel* fl_event_channel_new(FlBinaryMessenger* messenger, - const gchar* name, - FlMethodCodec* codec); - -/** - * fl_event_channel_set_stream_handlers: - * @channel: an #FlEventChannel. - * @listen_handler: (allow-none): function to call when the Dart side of the - * channel starts listening to the stream. - * @cancel_handler: (allow-none): function to call when the Dart side of the - * channel cancels their subscription to the stream. - * @user_data: (closure): user data to pass to @listen_handler and - * @cancel_handler. - * @destroy_notify: (allow-none): a function which gets called to free - * @user_data, or %NULL. - * - * Sets the functions called when the Dart side requests the stream to start and - * finish. - * - * The handlers are removed if the channel is closed or is replaced by another - * handler, set @destroy_notify if you want to detect this. - */ -void fl_event_channel_set_stream_handlers(FlEventChannel* channel, - FlEventChannelHandler listen_handler, - FlEventChannelHandler cancel_handler, - gpointer user_data, - GDestroyNotify destroy_notify); - -/** - * fl_event_channel_send: - * @channel: an #FlEventChannel. - * @event: event to send, must match what the #FlMethodCodec supports. - * @cancellable: (allow-none): a #GCancellable or %NULL. - * @error: (allow-none): #GError location to store the error occurring, or %NULL - * to ignore. - * - * Sends an event on the channel. - * Events should only be sent once the channel is being listened to. - * - * Returns: %TRUE if successful. - */ -gboolean fl_event_channel_send(FlEventChannel* channel, - FlValue* event, - GCancellable* cancellable, - GError** error); - -/** - * fl_event_channel_send_error: - * @channel: an #FlEventChannel. - * @code: error code to send. - * @message: error message to send. - * @details: (allow-none): error details or %NULL. - * @cancellable: (allow-none): a #GCancellable or %NULL. - * @error: (allow-none): #GError location to store the error occurring, or %NULL - * to ignore. - * - * Sends an error on the channel. - * Errors should only be sent once the channel is being listened to. - * - * Returns: %TRUE if successful. - */ -gboolean fl_event_channel_send_error(FlEventChannel* channel, - const gchar* code, - const gchar* message, - FlValue* details, - GCancellable* cancellable, - GError** error); - -/** - * fl_event_channel_send_end_of_stream: - * @channel: an #FlEventChannel. - * @cancellable: (allow-none): a #GCancellable or %NULL. - * @error: (allow-none): #GError location to store the error occurring, or %NULL - * to ignore. - * - * Indicates the stream has completed. - * It is a programmer error to send any more events after calling this. - * - * Returns: %TRUE if successful. - */ -gboolean fl_event_channel_send_end_of_stream(FlEventChannel* channel, - GCancellable* cancellable, - GError** error); - -G_END_DECLS - -#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_EVENT_CHANNEL_H_ diff --git a/example/linux/flutter/ephemeral/flutter_linux/fl_json_message_codec.h b/example/linux/flutter/ephemeral/flutter_linux/fl_json_message_codec.h deleted file mode 100644 index ddf68189a..000000000 --- a/example/linux/flutter/ephemeral/flutter_linux/fl_json_message_codec.h +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_JSON_MESSAGE_CODEC_H_ -#define FLUTTER_SHELL_PLATFORM_LINUX_FL_JSON_MESSAGE_CODEC_H_ - -#if !defined(__FLUTTER_LINUX_INSIDE__) && !defined(FLUTTER_LINUX_COMPILATION) -#error "Only can be included directly." -#endif - -#include "fl_message_codec.h" - -G_BEGIN_DECLS - -/** - * FlJsonMessageCodecError: - * @FL_JSON_MESSAGE_CODEC_ERROR_INVALID_UTF8: Message is not valid UTF-8. - * @FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON: Message is not valid JSON. - * @FL_JSON_MESSAGE_CODEC_ERROR_INVALID_OBJECT_KEY_TYPE: Invalid object key - * type. - * - * Errors for #FlJsonMessageCodec objects to set on failures. - */ -#define FL_JSON_MESSAGE_CODEC_ERROR fl_json_message_codec_error_quark() - -typedef enum { - FL_JSON_MESSAGE_CODEC_ERROR_INVALID_UTF8, - FL_JSON_MESSAGE_CODEC_ERROR_INVALID_JSON, - FL_JSON_MESSAGE_CODEC_ERROR_INVALID_OBJECT_KEY_TYPE, -} FlJsonMessageCodecError; - -GQuark fl_json_message_codec_error_quark(void) G_GNUC_CONST; - -G_DECLARE_FINAL_TYPE(FlJsonMessageCodec, - fl_json_message_codec, - FL, - JSON_CODEC, - FlMessageCodec) - -/** - * FlJsonMessageCodec: - * - * #FlJsonMessageCodec is an #FlMessageCodec that implements the encodes - * #FlValue to/from JSON. This codec encodes and decodes #FlValue of type - * #FL_VALUE_TYPE_NULL, #FL_VALUE_TYPE_BOOL, #FL_VALUE_TYPE_INT, - * #FL_VALUE_TYPE_FLOAT, #FL_VALUE_TYPE_STRING, #FL_VALUE_TYPE_UINT8_LIST, - * #FL_VALUE_TYPE_INT32_LIST, #FL_VALUE_TYPE_INT64_LIST, - * #FL_VALUE_TYPE_FLOAT_LIST, #FL_VALUE_TYPE_LIST, and #FL_VALUE_TYPE_MAP. - * - * #FlJsonMessageCodec matches the JSONMessageCodec class in the Flutter - * services library. - */ - -/** - * fl_json_message_codec_new: - * - * Creates an #FlJsonMessageCodec. - * - * Returns: a new #FlJsonMessageCodec. - */ -FlJsonMessageCodec* fl_json_message_codec_new(); - -/** - * fl_json_message_codec_encode: - * @codec: an #FlJsonMessageCodec. - * @value: value to encode. - * @error: (allow-none): #GError location to store the error occurring, or - * %NULL. - * - * Encodes a value to a JSON string. - * - * Returns: a JSON representation of this value or %NULL on error. - */ -gchar* fl_json_message_codec_encode(FlJsonMessageCodec* codec, - FlValue* value, - GError** error); - -/** - * fl_json_message_codec_decode: - * @codec: an #FlJsonMessageCodec. - * @text: UTF-8 text in JSON format. - * @error: (allow-none): #GError location to store the error occurring, or - * %NULL. - * - * Decodes a value from a JSON string. - * - * Returns: an #FlValue or %NULL on error. - */ -FlValue* fl_json_message_codec_decode(FlJsonMessageCodec* codec, - const gchar* text, - GError** error); - -G_END_DECLS - -#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_JSON_MESSAGE_CODEC_H_ diff --git a/example/linux/flutter/ephemeral/flutter_linux/fl_json_method_codec.h b/example/linux/flutter/ephemeral/flutter_linux/fl_json_method_codec.h deleted file mode 100644 index 70ddf793b..000000000 --- a/example/linux/flutter/ephemeral/flutter_linux/fl_json_method_codec.h +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_JSON_METHOD_CODEC_H_ -#define FLUTTER_SHELL_PLATFORM_LINUX_FL_JSON_METHOD_CODEC_H_ - -#if !defined(__FLUTTER_LINUX_INSIDE__) && !defined(FLUTTER_LINUX_COMPILATION) -#error "Only can be included directly." -#endif - -#include "fl_method_codec.h" - -G_BEGIN_DECLS - -G_DECLARE_FINAL_TYPE(FlJsonMethodCodec, - fl_json_method_codec, - FL, - JSON_METHOD_CODEC, - FlMethodCodec) - -/** - * FlJsonMethodCodec: - * - * #FlJsonMessageCodec is an #FlMethodCodec that implements method calls using - * the Flutter JSON message encoding. It should be used with an - * #FlMethodChannel. - * - * #FlJsonMethodCodec matches the JSONMethodCodec class in the Flutter services - * library. - */ - -/** - * fl_json_method_codec_new: - * - * Creates an #FlJsonMethodCodec. - * - * Returns: a new #FlJsonMethodCodec. - */ -FlJsonMethodCodec* fl_json_method_codec_new(); - -G_END_DECLS - -#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_JSON_METHOD_CODEC_H_ diff --git a/example/linux/flutter/ephemeral/flutter_linux/fl_message_codec.h b/example/linux/flutter/ephemeral/flutter_linux/fl_message_codec.h deleted file mode 100644 index b74aad264..000000000 --- a/example/linux/flutter/ephemeral/flutter_linux/fl_message_codec.h +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_MESSAGE_CODEC_H_ -#define FLUTTER_SHELL_PLATFORM_LINUX_FL_MESSAGE_CODEC_H_ - -#if !defined(__FLUTTER_LINUX_INSIDE__) && !defined(FLUTTER_LINUX_COMPILATION) -#error "Only can be included directly." -#endif - -#include - -#include "fl_value.h" - -G_BEGIN_DECLS - -/** - * FlMessageCodecError: - * @FL_MESSAGE_CODEC_ERROR_FAILED: Codec failed due to an unspecified error. - * @FL_MESSAGE_CODEC_ERROR_OUT_OF_DATA: Codec ran out of data reading a value. - * @FL_MESSAGE_CODEC_ERROR_ADDITIONAL_DATA: Additional data encountered in - * message. - * @FL_MESSAGE_CODEC_ERROR_UNSUPPORTED_TYPE: Codec encountered an unsupported - * #FlValue. - * - * Errors for #FlMessageCodec objects to set on failures. - */ -#define FL_MESSAGE_CODEC_ERROR fl_message_codec_error_quark() - -typedef enum { - FL_MESSAGE_CODEC_ERROR_FAILED, - FL_MESSAGE_CODEC_ERROR_OUT_OF_DATA, - FL_MESSAGE_CODEC_ERROR_ADDITIONAL_DATA, - FL_MESSAGE_CODEC_ERROR_UNSUPPORTED_TYPE, -} FlMessageCodecError; - -GQuark fl_message_codec_error_quark(void) G_GNUC_CONST; - -G_DECLARE_DERIVABLE_TYPE(FlMessageCodec, - fl_message_codec, - FL, - MESSAGE_CODEC, - GObject) - -/** - * FlMessageCodec: - * - * #FlMessageCodec is a message encoding/decoding mechanism that operates on - * #FlValue objects. Both operations returns errors if the conversion fails. - * Such situations should be treated as programming errors. - * - * #FlMessageCodec matches the MethodCodec class in the Flutter services - * library. - */ - -struct _FlMessageCodecClass { - GObjectClass parent_class; - - /** - * FlMessageCodec::encode_message: - * @codec: A #FlMessageCodec. - * @message: message to encode or %NULL to encode the null value. - * @error: (allow-none): #GError location to store the error occurring, or - * %NULL. - * - * Virtual method to encode a message. A subclass must implement this method. - * If the subclass cannot handle the type of @message then it must generate a - * FL_MESSAGE_CODEC_ERROR_UNSUPPORTED_TYPE error. - * - * Returns: a binary message or %NULL on error. - */ - GBytes* (*encode_message)(FlMessageCodec* codec, - FlValue* message, - GError** error); - - /** - * FlMessageCodec::decode_message: - * @codec: an #FlMessageCodec. - * @message: binary message to decode. - * @error: (allow-none): #GError location to store the error occurring, or - * %NULL. - * - * Virtual method to decode a message. A subclass must implement this method. - * If @message is too small then a #FL_MESSAGE_CODEC_ERROR_OUT_OF_DATA error - * must be generated. If @message is too large then a - * #FL_MESSAGE_CODEC_ERROR_ADDITIONAL_DATA error must be generated. - * - * Returns: an #FlValue or %NULL on error. - */ - FlValue* (*decode_message)(FlMessageCodec* codec, - GBytes* message, - GError** error); -}; - -/** - * fl_message_codec_encode_message: - * @codec: an #FlMessageCodec. - * @buffer: buffer to write to. - * @message: message to encode or %NULL to encode the null value. - * @error: (allow-none): #GError location to store the error occurring, or - * %NULL. - * - * Encodes a message into a binary representation. - * - * Returns: a binary encoded message or %NULL on error. - */ -GBytes* fl_message_codec_encode_message(FlMessageCodec* codec, - FlValue* message, - GError** error); - -/** - * fl_message_codec_decode_message: - * @codec: an #FlMessageCodec. - * @message: binary message to decode. - * @error: (allow-none): #GError location to store the error occurring, or - * %NULL. - * - * Decodes a message from a binary encoding. - * - * Returns: an #FlValue or %NULL on error. - */ -FlValue* fl_message_codec_decode_message(FlMessageCodec* codec, - GBytes* message, - GError** error); - -G_END_DECLS - -#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_MESSAGE_CODEC_H_ diff --git a/example/linux/flutter/ephemeral/flutter_linux/fl_method_call.h b/example/linux/flutter/ephemeral/flutter_linux/fl_method_call.h deleted file mode 100644 index 5a5ebc741..000000000 --- a/example/linux/flutter/ephemeral/flutter_linux/fl_method_call.h +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_METHOD_CALL_H_ -#define FLUTTER_SHELL_PLATFORM_LINUX_FL_METHOD_CALL_H_ - -#if !defined(__FLUTTER_LINUX_INSIDE__) && !defined(FLUTTER_LINUX_COMPILATION) -#error "Only can be included directly." -#endif - -#include - -#include "fl_method_response.h" -#include "fl_value.h" - -G_BEGIN_DECLS - -G_DECLARE_FINAL_TYPE(FlMethodCall, fl_method_call, FL, METHOD_CALL, GObject) - -/** - * FlMethodCall: - * - * #FlMethodCall represents and incoming method call as returned by an - * #FlMethodChannel. - */ - -/** - * fl_method_call_get_name: - * @method_call: an #FlMethodCall. - * - * Gets the name of the method call. - * - * Returns: a method name. - */ -const gchar* fl_method_call_get_name(FlMethodCall* method_call); - -/** - * fl_method_call_get_args: - * @method_call: an #FlMethodCall. - * - * Gets the arguments passed to the method. - * - * Returns: an #FlValue. - */ -FlValue* fl_method_call_get_args(FlMethodCall* method_call); - -/** - * fl_method_call_respond: - * @method_call: an #FlMethodCall. - * @response: an #FlMethodResponse. - * @error: (allow-none): #GError location to store the error occurring, or %NULL - * to ignore. - * - * Responds to a method call. - * - * Returns: %TRUE on success. - */ -gboolean fl_method_call_respond(FlMethodCall* method_call, - FlMethodResponse* response, - GError** error); - -/** - * fl_method_call_respond_success: - * @method_call: an #FlMethodCall. - * @result: (allow-none): value to respond with, must match what the - * #FlMethodCodec supports. - * @error: (allow-none): #GError location to store the error occurring, or %NULL - * to ignore. - * - * Convenience method that responds to method call with - * #FlMethodSuccessResponse. - * - * Returns: %TRUE on success. - */ -gboolean fl_method_call_respond_success(FlMethodCall* method_call, - FlValue* result, - GError** error); - -/** - * fl_method_call_respond_error: - * @method_call: an #FlMethodCall. - * @code: error code. - * @message: (allow-none): error message. - * @details: (allow-none): details for the error. - * @error: (allow-none): #GError location to store the error occurring, or %NULL - * to ignore. - * - * Convenience method that responds to method call with #FlMethodErrorResponse. - * - * Returns: %TRUE on success. - */ -gboolean fl_method_call_respond_error(FlMethodCall* method_call, - const gchar* code, - const gchar* message, - FlValue* details, - GError** error); - -/** - * fl_method_call_respond_not_implemented: - * @method_call: an #FlMethodCall. - * @error: (allow-none): #GError location to store the error occurring, or %NULL - * to ignore. - * - * Convenience method that responds to method call with - * #FlMethodNotImplementedResponse. - * - * Returns: %TRUE on success. - */ -gboolean fl_method_call_respond_not_implemented(FlMethodCall* method_call, - GError** error); - -G_END_DECLS - -#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_METHOD_CALL_H_ diff --git a/example/linux/flutter/ephemeral/flutter_linux/fl_method_channel.h b/example/linux/flutter/ephemeral/flutter_linux/fl_method_channel.h deleted file mode 100644 index f4102ed29..000000000 --- a/example/linux/flutter/ephemeral/flutter_linux/fl_method_channel.h +++ /dev/null @@ -1,190 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_METHOD_CHANNEL_H_ -#define FLUTTER_SHELL_PLATFORM_LINUX_FL_METHOD_CHANNEL_H_ - -#if !defined(__FLUTTER_LINUX_INSIDE__) && !defined(FLUTTER_LINUX_COMPILATION) -#error "Only can be included directly." -#endif - -#include -#include - -#include "fl_binary_messenger.h" -#include "fl_method_call.h" -#include "fl_method_codec.h" -#include "fl_method_response.h" - -G_BEGIN_DECLS - -G_DECLARE_FINAL_TYPE(FlMethodChannel, - fl_method_channel, - FL, - METHOD_CHANNEL, - GObject) - -/** - * FlMethodChannel: - * - * #FlMethodChannel is an object that allows method calls to and from Dart code. - * - * The following example shows how to call and handle methods on a channel. - * See #FlMethodResponse for how to handle errors in more detail. - * - * |[ - * static FlMethodChannel *channel = NULL; - * - * static void method_call_cb (FlMethodChannel* channel, - * FlMethodCall* method_call, - * gpointer user_data) { - * g_autoptr(FlMethodResponse) response = NULL; - * if (strcmp (fl_method_call_get_name (method_call), "Foo.bar") == 0) { - * g_autoptr(GError) bar_error = NULL; - * g_autoptr(FlValue) result = - * do_bar (fl_method_call_get_args (method_call), &bar_error); - * if (result == NULL) - * response = - * FL_METHOD_RESPONSE (fl_method_error_response_new ("bar error", - * bar_error->message)); - * else - * response = FL_METHOD_RESPONSE (fl_method_success_response_new - * (result)); } else response = FL_METHOD_RESPONSE - * (fl_method_not_implemented_response_new ()); - * - * g_autoptr(GError) error = NULL; - * if (!fl_method_call_respond(method_call, response)) - * g_warning ("Failed to send response: %s", error->message); - * } - * - * static void method_response_cb(GObject *object, - * GAsyncResult *result, - * gpointer user_data) { - * g_autoptr(GError) error = NULL; - * g_autoptr(FlMethodResponse) response = - * fl_method_channel_invoke_method_finish (FL_METHOD_CODEC (object), result, - * &error); - * if (response == NULL) { - * g_warning ("Failed to call method: %s", error->message); - * return; - * } - * - * g_autoptr(FlValue) value = - * fl_method_response_get_result (response, &error); - * if (response == NULL) { - * g_warning ("Method returned error: %s", error->message); - * return; - * } - * - * use_result (value); - * } - * - * static void call_method () { - * g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new (); - * channel = - * fl_method_channel_new(messenger, "flutter/foo", FL_METHOD_CODEC (codec)); - * fl_method_channel_set_method_call_handler (channel, method_call_cb, NULL, - * NULL); - * - * g_autoptr(FlValue) args = fl_value_new_string ("Hello World"); - * fl_method_channel_invoke_method (channel, "Foo.foo", args, - * cancellable, method_response_cb, NULL); - * } - * ]| - * - * #FlMethodChannel matches the MethodChannel class in the Flutter services - * library. - */ - -/** - * FlMethodChannelMethodCallHandler: - * @channel: an #FlMethodChannel. - * @method_call: an #FlMethodCall. - * @user_data: (closure): data provided when registering this handler. - * - * Function called when a method call is received. Respond to the method call - * with fl_method_call_respond(). If the response is not occurring in this - * callback take a reference to @method_call and release that once it has been - * responded to. Failing to respond before the last reference to @method_call is - * dropped is a programming error. - */ -typedef void (*FlMethodChannelMethodCallHandler)(FlMethodChannel* channel, - FlMethodCall* method_call, - gpointer user_data); - -/** - * fl_method_channel_new: - * @messenger: an #FlBinaryMessenger. - * @name: a channel name. - * @codec: the method codec. - * - * Creates a new method channel. @codec must match the codec used on the Dart - * end of the channel. - * - * Returns: a new #FlMethodChannel. - */ -FlMethodChannel* fl_method_channel_new(FlBinaryMessenger* messenger, - const gchar* name, - FlMethodCodec* codec); - -/** - * fl_method_channel_set_method_call_handler: - * @channel: an #FlMethodChannel. - * @handler: function to call when a method call is received on this channel. - * @user_data: (closure): user data to pass to @handler. - * @destroy_notify: (allow-none): a function which gets called to free - * @user_data, or %NULL. - * - * Sets the function called when a method call is received from the Dart side of - * the channel. See #FlMethodChannelMethodCallHandler for details on how to - * respond to method calls. - * - * The handler is removed if the channel is closed or is replaced by another - * handler, set @destroy_notify if you want to detect this. - */ -void fl_method_channel_set_method_call_handler( - FlMethodChannel* channel, - FlMethodChannelMethodCallHandler handler, - gpointer user_data, - GDestroyNotify destroy_notify); - -/** - * fl_method_channel_invoke_method: - * @channel: an #FlMethodChannel. - * @method: the method to call. - * @args: (allow-none): arguments to the method, must match what the - * #FlMethodCodec supports. - * @cancellable: (allow-none): a #GCancellable or %NULL. - * @callback: (scope async): (allow-none): a #GAsyncReadyCallback to call when - * the request is satisfied or %NULL to ignore the response. - * @user_data: (closure): user data to pass to @callback. - * - * Calls a method on this channel. - */ -void fl_method_channel_invoke_method(FlMethodChannel* channel, - const gchar* method, - FlValue* args, - GCancellable* cancellable, - GAsyncReadyCallback callback, - gpointer user_data); - -/** - * fl_method_channel_invoke_method_finish: - * @channel: an #FlMethodChannel. - * @result: #GAsyncResult. - * @error: (allow-none): #GError location to store the error occurring, or %NULL - * to ignore. - * - * Completes request started with fl_method_channel_invoke_method(). - * - * Returns: (transfer full): an #FlMethodResponse or %NULL on error. - */ -FlMethodResponse* fl_method_channel_invoke_method_finish( - FlMethodChannel* channel, - GAsyncResult* result, - GError** error); - -G_END_DECLS - -#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_METHOD_CHANNEL_H_ diff --git a/example/linux/flutter/ephemeral/flutter_linux/fl_method_codec.h b/example/linux/flutter/ephemeral/flutter_linux/fl_method_codec.h deleted file mode 100644 index 97bf8e929..000000000 --- a/example/linux/flutter/ephemeral/flutter_linux/fl_method_codec.h +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_METHOD_CODEC_H_ -#define FLUTTER_SHELL_PLATFORM_LINUX_FL_METHOD_CODEC_H_ - -#if !defined(__FLUTTER_LINUX_INSIDE__) && !defined(FLUTTER_LINUX_COMPILATION) -#error "Only can be included directly." -#endif - -#include - -#include "fl_method_response.h" -#include "fl_value.h" - -G_BEGIN_DECLS - -G_DECLARE_DERIVABLE_TYPE(FlMethodCodec, - fl_method_codec, - FL, - METHOD_CODEC, - GObject) - -/** - * FlMethodCodec: - * - * #FlMethodCodec is an abstract class that encodes and decodes method calls on - * a platform channel. Override this class to implement an encoding. - * - * #FlMethodCodec matches the MethodCodec class in the Flutter services - * library. - */ - -struct _FlMethodCodecClass { - GObjectClass parent_class; - - /** - * FlMethodCodec::encode_method_call: - * @codec: an #FlMethodCodec. - * @name: method name. - * @args: (allow-none): method arguments, or %NULL. - * @error: (allow-none): #GError location to store the error occurring, or - * %NULL. - * - * Encodes a method call. - * - * Returns: (transfer full): a binary encoding of this method call or %NULL if - * not able to encode. - */ - GBytes* (*encode_method_call)(FlMethodCodec* codec, - const gchar* name, - FlValue* args, - GError** error); - - /** - * FlMethodCodec::decode_method_call: - * @codec: an #FlMethodCodec - * @message: message to decode. - * @name: (transfer full): location to write method name or %NULL if not - * required - * @args: (transfer full): location to write method arguments, or %NULL if not - * required - * @error: (allow-none): #GError location to store the error occurring, or - * %NULL - * - * Decodes a method call. - * - * Returns: %TRUE if successfully decoded. - */ - gboolean (*decode_method_call)(FlMethodCodec* codec, - GBytes* message, - gchar** name, - FlValue** args, - GError** error); - - /** - * FlMethodCodec::encode_success_envelope: - * @codec: an #FlMethodCodec. - * @result: (allow-none): method result, or %NULL. - * @error: (allow-none): #GError location to store the error occurring, or - * %NULL. - * - * Encodes a successful response to a method call. - * - * Returns: (transfer full): a binary encoding of this response or %NULL if - * not able to encode. - */ - GBytes* (*encode_success_envelope)(FlMethodCodec* codec, - FlValue* result, - GError** error); - - /** - * FlMethodCodec::encode_error_envelope: - * @codec: an #FlMethodCodec. - * @code: an error code. - * @message: (allow-none): an error message, or %NULL. - * @details: (allow-none): error details, or %NULL. - * @error: (allow-none): #GError location to store the error occurring, or - * %NULL. - * - * Encodes an error response to a method call. - * - * Returns: (transfer full): a binary encoding of this response or %NULL if - * not able to encode. - */ - GBytes* (*encode_error_envelope)(FlMethodCodec* codec, - const gchar* code, - const gchar* message, - FlValue* details, - GError** error); - - /** - * FlMethodCodec::decode_response: - * @codec: an #FlMethodCodec. - * @message: message to decode. - * @error: (allow-none): #GError location to store the error occurring, or - * %NULL. - * - * Decodes a response to a method call. - * - * Returns: a new #FlMethodResponse or %NULL on error. - */ - FlMethodResponse* (*decode_response)(FlMethodCodec* codec, - GBytes* message, - GError** error); -}; - -G_END_DECLS - -#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_METHOD_CODEC_H_ diff --git a/example/linux/flutter/ephemeral/flutter_linux/fl_method_response.h b/example/linux/flutter/ephemeral/flutter_linux/fl_method_response.h deleted file mode 100644 index 949cddc33..000000000 --- a/example/linux/flutter/ephemeral/flutter_linux/fl_method_response.h +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_METHOD_RESPONSE_H_ -#define FLUTTER_SHELL_PLATFORM_LINUX_FL_METHOD_RESPONSE_H_ - -#if !defined(__FLUTTER_LINUX_INSIDE__) && !defined(FLUTTER_LINUX_COMPILATION) -#error "Only can be included directly." -#endif - -#include - -#include "fl_value.h" - -G_BEGIN_DECLS - -/** - * FlMethodResponseError: - * @FL_METHOD_RESPONSE_ERROR_FAILED: Call failed due to an unspecified error. - * @FL_METHOD_RESPONSE_ERROR_REMOTE_ERROR: An error was returned by the other - * side of the channel. - * @FL_METHOD_RESPONSE_ERROR_NOT_IMPLEMENTED: The requested method is not - * implemented. - * - * Errors set by `fl_method_response_get_result` when the method call response - * is not #FlMethodSuccessResponse. - */ -#define FL_METHOD_RESPONSE_ERROR fl_method_response_error_quark() - -typedef enum { - FL_METHOD_RESPONSE_ERROR_FAILED, - FL_METHOD_RESPONSE_ERROR_REMOTE_ERROR, - FL_METHOD_RESPONSE_ERROR_NOT_IMPLEMENTED, -} FlMethodResponseError; - -GQuark fl_method_response_error_quark(void) G_GNUC_CONST; - -G_DECLARE_DERIVABLE_TYPE(FlMethodResponse, - fl_method_response, - FL, - METHOD_RESPONSE, - GObject) - -struct _FlMethodResponseClass { - GObjectClass parent_class; -}; - -G_DECLARE_FINAL_TYPE(FlMethodSuccessResponse, - fl_method_success_response, - FL, - METHOD_SUCCESS_RESPONSE, - FlMethodResponse) - -G_DECLARE_FINAL_TYPE(FlMethodErrorResponse, - fl_method_error_response, - FL, - METHOD_ERROR_RESPONSE, - FlMethodResponse) - -G_DECLARE_FINAL_TYPE(FlMethodNotImplementedResponse, - fl_method_not_implemented_response, - FL, - METHOD_NOT_IMPLEMENTED_RESPONSE, - FlMethodResponse) - -/** - * FlMethodResponse: - * - * #FlMethodResponse contains the information returned when an #FlMethodChannel - * method call returns. If you expect the method call to be successful use - * fl_method_response_get_result(). If you want to handle error cases then you - * should use code like: - * - * |[ - * if (FL_IS_METHOD_SUCCESS_RESPONSE (response)) { - * FlValue *result = - * fl_method_success_response_get_result( - * FL_METHOD_SUCCESS_RESPONSE (response)); - * handle_result (result); - * } else if (FL_IS_METHOD_ERROR_RESPONSE (response)) { - * FlMethodErrorResponse *error_response = - * FL_METHOD_ERROR_RESPONSE (response); - * handle_error (fl_method_error_response_get_code (error_response), - * fl_method_error_response_get_message (error_response), - * fl_method_error_response_get_details (error_response)); - * } - * else if (FL_IS_METHOD_ERROR_RESPONSE (response)) { - * handle_not_implemented (); - * } - * } - * ]| - */ - -/** - * FlMethodSuccessResponse: - * - * #FlMethodSuccessResponse is the #FlMethodResponse returned when a method call - * has successfully completed. The result of the method call is obtained using - * `fl_method_success_response_get_result`. - */ - -/** - * FlMethodErrorResponse: - * - * #FlMethodErrorResponse is the #FlMethodResponse returned when a method call - * results in an error. The error details are obtained using - * `fl_method_error_response_get_code`, `fl_method_error_response_get_message` - * and `fl_method_error_response_get_details`. - */ - -/** - * FlMethodNotImplementedResponse: - * - * #FlMethodNotImplementedResponse is the #FlMethodResponse returned when a - * method call is not implemented. - */ - -/** - * fl_method_response_get_result: - * @response: an #FlMethodResponse. - * @error: (allow-none): #GError location to store the error occurring, or %NULL - * to ignore. - * - * Gets the result of a method call, or an error if the response wasn't - * successful. - * - * Returns: an #FlValue or %NULL on error. - */ -FlValue* fl_method_response_get_result(FlMethodResponse* response, - GError** error); - -/** - * fl_method_success_response_new: - * @result: (allow-none): the #FlValue returned by the method call or %NULL. - * - * Creates a response to a method call when that method has successfully - * completed. - * - * Returns: a new #FlMethodResponse. - */ -FlMethodSuccessResponse* fl_method_success_response_new(FlValue* result); - -/** - * fl_method_success_response_get_result: - * @response: an #FlMethodSuccessResponse. - * - * Gets the result of the method call. - * - * Returns: an #FlValue. - */ -FlValue* fl_method_success_response_get_result( - FlMethodSuccessResponse* response); - -/** - * fl_method_error_response_new: - * @result: an #FlValue. - * @code: an error code. - * @message: (allow-none): an error message. - * @details: (allow-none): error details. - * - * Creates a response to a method call when that method has returned an error. - * - * Returns: a new #FlMethodErrorResponse. - */ -FlMethodErrorResponse* fl_method_error_response_new(const gchar* code, - const gchar* message, - FlValue* details); - -/** - * fl_method_error_response_get_code: - * @response: an #FlMethodErrorResponse. - * - * Gets the error code reported. - * - * Returns: an error code. - */ -const gchar* fl_method_error_response_get_code(FlMethodErrorResponse* response); - -/** - * fl_method_error_response_get_message: - * @response: an #FlMethodErrorResponse. - * - * Gets the error message reported. - * - * Returns: an error message or %NULL if no error message provided. - */ -const gchar* fl_method_error_response_get_message( - FlMethodErrorResponse* response); - -/** - * fl_method_error_response_get_details: - * @response: an #FlMethodErrorResponse. - * - * Gets the details provided with this error. - * - * Returns: an #FlValue or %NULL if no details provided. - */ -FlValue* fl_method_error_response_get_details(FlMethodErrorResponse* response); - -/** - * fl_method_not_implemented_response_new: - * - * Creates a response to a method call when that method does not exist. - * - * Returns: a new #FlMethodNotImplementedResponse. - */ -FlMethodNotImplementedResponse* fl_method_not_implemented_response_new(); - -G_END_DECLS - -#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_METHOD_RESPONSE_H_ diff --git a/example/linux/flutter/ephemeral/flutter_linux/fl_plugin_registrar.h b/example/linux/flutter/ephemeral/flutter_linux/fl_plugin_registrar.h deleted file mode 100644 index 5ac073c18..000000000 --- a/example/linux/flutter/ephemeral/flutter_linux/fl_plugin_registrar.h +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_PLUGIN_REGISTRAR_H_ -#define FLUTTER_SHELL_PLATFORM_LINUX_FL_PLUGIN_REGISTRAR_H_ - -#if !defined(__FLUTTER_LINUX_INSIDE__) && !defined(FLUTTER_LINUX_COMPILATION) -#error "Only can be included directly." -#endif - -#include - -#include "fl_binary_messenger.h" -#include "fl_view.h" - -G_BEGIN_DECLS - -G_DECLARE_FINAL_TYPE(FlPluginRegistrar, - fl_plugin_registrar, - FL, - PLUGIN_REGISTRAR, - GObject) - -/** - * FlPluginRegistrar: - * - * #FlPluginRegistrar is used when registering new plugins. - */ - -/** - * fl_plugin_registrar_get_messenger: - * @registrar: an #FlPluginRegistrar. - * - * Gets the messenger this plugin can communicate with. - * - * Returns: an #FlBinaryMessenger. - */ -FlBinaryMessenger* fl_plugin_registrar_get_messenger( - FlPluginRegistrar* registrar); - -/** - * fl_plugin_registrar_get_view: - * @registrar: an #FlPluginRegistrar. - * - * Get the view that Flutter is rendering with. - * - * Returns: (allow-none): an #FlView or %NULL if running in headless mode. - */ -FlView* fl_plugin_registrar_get_view(FlPluginRegistrar* registrar); - -G_END_DECLS - -#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_PLUGIN_REGISTRAR_H_ diff --git a/example/linux/flutter/ephemeral/flutter_linux/fl_plugin_registry.h b/example/linux/flutter/ephemeral/flutter_linux/fl_plugin_registry.h deleted file mode 100644 index bfc860e76..000000000 --- a/example/linux/flutter/ephemeral/flutter_linux/fl_plugin_registry.h +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_PLUGIN_REGISTRY_H_ -#define FLUTTER_SHELL_PLATFORM_LINUX_FL_PLUGIN_REGISTRY_H_ - -#if !defined(__FLUTTER_LINUX_INSIDE__) && !defined(FLUTTER_LINUX_COMPILATION) -#error "Only can be included directly." -#endif - -#include - -#include "fl_plugin_registrar.h" - -G_BEGIN_DECLS - -G_DECLARE_INTERFACE(FlPluginRegistry, - fl_plugin_registry, - FL, - PLUGIN_REGISTRY, - GObject) - -/** - * FlPluginRegistry: - * - * #FlPluginRegistry vends #FlPluginRegistrar objects for named plugins. - */ - -struct _FlPluginRegistryInterface { - GTypeInterface g_iface; - - /** - * FlPluginRegistry::get_registrar_for_plugin: - * @registry: an #FlPluginRegistry. - * @name: plugin name. - * - * Gets the plugin registrar for the the plugin with @name. - * - * Returns: (transfer full): an #FlPluginRegistrar. - */ - FlPluginRegistrar* (*get_registrar_for_plugin)(FlPluginRegistry* registry, - const gchar* name); -}; - -/** - * fl_plugin_registry_get_registrar_for_plugin: - * @registry: an #FlPluginRegistry. - * @name: plugin name. - * - * Gets the plugin registrar for the the plugin with @name. - * - * Returns: (transfer full): an #FlPluginRegistrar. - */ -FlPluginRegistrar* fl_plugin_registry_get_registrar_for_plugin( - FlPluginRegistry* registry, - const gchar* name); - -G_END_DECLS - -#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_PLUGIN_REGISTRY_H_ diff --git a/example/linux/flutter/ephemeral/flutter_linux/fl_standard_message_codec.h b/example/linux/flutter/ephemeral/flutter_linux/fl_standard_message_codec.h deleted file mode 100644 index 2af7b83b8..000000000 --- a/example/linux/flutter/ephemeral/flutter_linux/fl_standard_message_codec.h +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_STANDARD_MESSAGE_CODEC_H_ -#define FLUTTER_SHELL_PLATFORM_LINUX_FL_STANDARD_MESSAGE_CODEC_H_ - -#if !defined(__FLUTTER_LINUX_INSIDE__) && !defined(FLUTTER_LINUX_COMPILATION) -#error "Only can be included directly." -#endif - -#include "fl_message_codec.h" - -G_BEGIN_DECLS - -G_DECLARE_FINAL_TYPE(FlStandardMessageCodec, - fl_standard_message_codec, - FL, - STANDARD_CODEC, - FlMessageCodec) - -/** - * FlStandardMessageCodec: - * - * #FlStandardMessageCodec is an #FlMessageCodec that implements the Flutter - * standard message encoding. This codec encodes and decodes #FlValue of type - * #FL_VALUE_TYPE_NULL, #FL_VALUE_TYPE_BOOL, #FL_VALUE_TYPE_INT, - * #FL_VALUE_TYPE_FLOAT, #FL_VALUE_TYPE_STRING, #FL_VALUE_TYPE_UINT8_LIST, - * #FL_VALUE_TYPE_INT32_LIST, #FL_VALUE_TYPE_INT64_LIST, - * #FL_VALUE_TYPE_FLOAT_LIST, #FL_VALUE_TYPE_LIST, and #FL_VALUE_TYPE_MAP. - * - * #FlStandardMessageCodec matches the StandardCodec class in the Flutter - * services library. - */ - -/* - * fl_standard_message_codec_new: - * - * Creates an #FlStandardMessageCodec. - * - * Returns: a new #FlStandardMessageCodec. - */ -FlStandardMessageCodec* fl_standard_message_codec_new(); - -G_END_DECLS - -#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_STANDARD_MESSAGE_CODEC_H_ diff --git a/example/linux/flutter/ephemeral/flutter_linux/fl_standard_method_codec.h b/example/linux/flutter/ephemeral/flutter_linux/fl_standard_method_codec.h deleted file mode 100644 index 1685ebced..000000000 --- a/example/linux/flutter/ephemeral/flutter_linux/fl_standard_method_codec.h +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_STANDARD_METHOD_CODEC_H_ -#define FLUTTER_SHELL_PLATFORM_LINUX_FL_STANDARD_METHOD_CODEC_H_ - -#if !defined(__FLUTTER_LINUX_INSIDE__) && !defined(FLUTTER_LINUX_COMPILATION) -#error "Only can be included directly." -#endif - -#include "fl_method_codec.h" - -G_BEGIN_DECLS - -G_DECLARE_FINAL_TYPE(FlStandardMethodCodec, - fl_standard_method_codec, - FL, - STANDARD_METHOD_CODEC, - FlMethodCodec) - -/** - * FlStandardMethodCodec: - * - * #FlStandardMethodCodec is an #FlMethodCodec that implements method calls - * using the Flutter standard message encoding. It should be used with a - * #FlMethodChannel. - * - * #FlStandardMethodCodec matches the StandardMethodCodec class in the Flutter - * services library. - */ - -/** - * fl_standard_method_codec_new: - * - * Creates an #FlStandardMethodCodec. - * - * Returns: a new #FlStandardMethodCodec. - */ -FlStandardMethodCodec* fl_standard_method_codec_new(); - -G_END_DECLS - -#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_STANDARD_METHOD_CODEC_H_ diff --git a/example/linux/flutter/ephemeral/flutter_linux/fl_string_codec.h b/example/linux/flutter/ephemeral/flutter_linux/fl_string_codec.h deleted file mode 100644 index 8f49e9fe0..000000000 --- a/example/linux/flutter/ephemeral/flutter_linux/fl_string_codec.h +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_STRING_CODEC_H_ -#define FLUTTER_SHELL_PLATFORM_LINUX_FL_STRING_CODEC_H_ - -#if !defined(__FLUTTER_LINUX_INSIDE__) && !defined(FLUTTER_LINUX_COMPILATION) -#error "Only can be included directly." -#endif - -#include "fl_message_codec.h" - -G_BEGIN_DECLS - -G_DECLARE_FINAL_TYPE(FlStringCodec, - fl_string_codec, - FL, - STRING_CODEC, - FlMessageCodec) - -/** - * FlStringCodec: - * - * #FlStringCodec is an #FlMessageCodec that implements the Flutter string - * message encoding. This only encodes and decodes #FlValue of type - * #FL_VALUE_TYPE_STRING, other types #FlValues will generate an error during - * encoding. - * - * #FlStringCodec matches the StringCodec class in the Flutter services library. - */ - -/** - * fl_string_codec_new: - * - * Creates an #FlStringCodec. - * - * Returns: a new #FlStringCodec. - */ -FlStringCodec* fl_string_codec_new(); - -G_END_DECLS - -#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_STRING_CODEC_H_ diff --git a/example/linux/flutter/ephemeral/flutter_linux/fl_value.h b/example/linux/flutter/ephemeral/flutter_linux/fl_value.h deleted file mode 100644 index ad81c1913..000000000 --- a/example/linux/flutter/ephemeral/flutter_linux/fl_value.h +++ /dev/null @@ -1,586 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_VALUE_H_ -#define FLUTTER_SHELL_PLATFORM_LINUX_FL_VALUE_H_ - -#include -#include -#include - -#if !defined(__FLUTTER_LINUX_INSIDE__) && !defined(FLUTTER_LINUX_COMPILATION) -#error "Only can be included directly." -#endif - -G_BEGIN_DECLS - -/** - * FlValue: - * - * #FlValue is an object that contains the data types used in the platform - * channel used by Flutter. - * - * In Dart the values are represented as follows: - * - #FL_VALUE_TYPE_NULL: Null - * - #FL_VALUE_TYPE_BOOL: bool - * - #FL_VALUE_TYPE_INT: num - * - #FL_VALUE_TYPE_FLOAT: num - * - #FL_VALUE_TYPE_STRING: String - * - #FL_VALUE_TYPE_UINT8_LIST: Uint8List - * - #FL_VALUE_TYPE_INT32_LIST: Int32List - * - #FL_VALUE_TYPE_INT64_LIST: Int64List - * - #FL_VALUE_TYPE_FLOAT_LIST: Float64List - * - #FL_VALUE_TYPE_LIST: List - * - #FL_VALUE_TYPE_MAP: Map - * - * See #FlMessageCodec to encode and decode these values. - */ -typedef struct _FlValue FlValue; - -/** - * FlValueType: - * @FL_VALUE_TYPE_NULL: The null value. - * @FL_VALUE_TYPE_BOOL: A boolean. - * @FL_VALUE_TYPE_INT: A 64 bit signed integer. - * @FL_VALUE_TYPE_FLOAT: A 64 bit floating point number. - * @FL_VALUE_TYPE_STRING: UTF-8 text. - * @FL_VALUE_TYPE_UINT8_LIST: An ordered list of unsigned 8 bit integers. - * @FL_VALUE_TYPE_INT32_LIST: An ordered list of 32 bit integers. - * @FL_VALUE_TYPE_INT64_LIST: An ordered list of 64 bit integers. - * @FL_VALUE_TYPE_FLOAT_LIST: An ordered list of floating point numbers. - * @FL_VALUE_TYPE_LIST: An ordered list of #FlValue objects. - * @FL_VALUE_TYPE_MAP: A map of #FlValue objects keyed by #FlValue object. - * - * Types of #FlValue. - */ -typedef enum { - FL_VALUE_TYPE_NULL, - FL_VALUE_TYPE_BOOL, - FL_VALUE_TYPE_INT, - FL_VALUE_TYPE_FLOAT, - FL_VALUE_TYPE_STRING, - FL_VALUE_TYPE_UINT8_LIST, - FL_VALUE_TYPE_INT32_LIST, - FL_VALUE_TYPE_INT64_LIST, - FL_VALUE_TYPE_FLOAT_LIST, - FL_VALUE_TYPE_LIST, - FL_VALUE_TYPE_MAP, -} FlValueType; - -/** - * fl_value_new_null: - * - * Creates an #FlValue that contains a null value. The equivalent Dart type is - * null. - * - * Returns: a new #FlValue. - */ -FlValue* fl_value_new_null(); - -/** - * fl_value_new_bool: - * @value: the value. - * - * Creates an #FlValue that contains a boolean value. The equivalent Dart type - * is a bool. - * - * Returns: a new #FlValue. - */ -FlValue* fl_value_new_bool(bool value); - -/** - * fl_value_new_int: - * @value: the value. - * - * Creates an #FlValue that contains an integer number. The equivalent Dart type - * is a num. - * - * Returns: a new #FlValue. - */ -FlValue* fl_value_new_int(int64_t value); - -/** - * fl_value_new_float: - * @value: the value. - * - * Creates an #FlValue that contains a floating point number. The equivalent - * Dart type is a num. - * - * Returns: a new #FlValue. - */ -FlValue* fl_value_new_float(double value); - -/** - * fl_value_new_string: - * @value: a %NULL-terminated UTF-8 string. - * - * Creates an #FlValue that contains UTF-8 text. The equivalent Dart type is a - * String. - * - * Returns: a new #FlValue. - */ -FlValue* fl_value_new_string(const gchar* value); - -/** - * fl_value_new_string_sized: - * @value: a buffer containing UTF-8 text. It does not require a nul terminator. - * @value_length: the number of bytes to use from @value. - * - * Creates an #FlValue that contains UTF-8 text. The equivalent Dart type is a - * String. - * - * Returns: a new #FlValue. - */ -FlValue* fl_value_new_string_sized(const gchar* value, size_t value_length); - -/** - * fl_value_new_uint8_list: - * @value: an array of unsigned 8 bit integers. - * @value_length: number of elements in @value. - * - * Creates an ordered list containing 8 bit unsigned integers. The data is - * copied. The equivalent Dart type is a Uint8List. - * - * Returns: a new #FlValue. - */ -FlValue* fl_value_new_uint8_list(const uint8_t* value, size_t value_length); - -/** - * fl_value_new_uint8_list_from_bytes: - * @value: a #GBytes. - * - * Creates an ordered list containing 8 bit unsigned integers. The data is - * copied. The equivalent Dart type is a Uint8List. - * - * Returns: a new #FlValue. - */ -FlValue* fl_value_new_uint8_list_from_bytes(GBytes* value); - -/** - * fl_value_new_int32_list: - * @value: an array of signed 32 bit integers. - * @value_length: number of elements in @value. - * - * Creates an ordered list containing 32 bit integers. The equivalent Dart type - * is a Int32List. - * - * Returns: a new #FlValue. - */ -FlValue* fl_value_new_int32_list(const int32_t* value, size_t value_length); - -/** - * fl_value_new_int64_list: - * @value: an array of signed 64 bit integers. - * @value_length: number of elements in @value. - * - * Creates an ordered list containing 64 bit integers. The equivalent Dart type - * is a Int64List. - * - * Returns: a new #FlValue. - */ -FlValue* fl_value_new_int64_list(const int64_t* value, size_t value_length); - -/** - * fl_value_new_float_list: - * @value: an array of floating point numbers. - * @value_length: number of elements in @value. - * - * Creates an ordered list containing floating point numbers. The equivalent - * Dart type is a Float64List. - * - * Returns: a new #FlValue. - */ -FlValue* fl_value_new_float_list(const double* value, size_t value_length); - -/** - * fl_value_new_list: - * - * Creates an ordered list. Children can be added to the list using - * fl_value_append(). The children are accessed using fl_value_get_length() - * and fl_value_get_list_value(). The equivalent Dart type is a List. - * - * The following example shows a simple list of values: - * - * |[ - * g_autoptr(FlValue) value = fl_value_new_list (); - * fl_value_append_take (value, fl_value_new_string ("one"); - * fl_value_append_take (value, fl_value_new_int (2); - * fl_value_append_take (value, fl_value_new_double (3.0); - * ]| - * - * This value can be decoded using: - * - * |[ - * g_assert (fl_value_get_type (value) == FL_VALUE_TYPE_LIST); - * for (size_t i = 0; i < fl_value_get_length (value); i++) { - * FlValue *child = fl_value_get_list_value (value, i); - * process_value (child); - * } - * ]| - * - * Returns: a new #FlValue. - */ -FlValue* fl_value_new_list(); - -/** - * fl_value_new_list_from_strv: - * @value: a %NULL-terminated array of strings. - * - * Creates an ordered list containing #FlString values. - * - * Returns: a new #FlValue. - */ -FlValue* fl_value_new_list_from_strv(const gchar* const* value); - -/** - * fl_value_new_map: - * - * Creates an ordered associative array. Children can be added to the map - * using fl_value_set(), fl_value_set_take(), fl_value_set_string(), - * fl_value_set_string_take(). The children are accessed using - * fl_value_get_length(), fl_value_get_map_key(), fl_value_get_map_value(), - * fl_value_lookup() and fl_value_lookup_string(). The equivalent Dart type is a - * Map. - * - * The following example shows how to create a map of values keyed by strings: - * - * |[ - * g_autoptr(FlValue) value = fl_value_new_map (); - * fl_value_set_string_take (value, "name", fl_value_new_string ("Gandalf")); - * fl_value_set_string_take (value, "occupation", - * fl_value_new_string ("Wizard")); - * fl_value_set_string_take (value, "age", fl_value_new_int (2019)); - * ]| - * - * This value can be decoded using: - * |[ - * g_assert (fl_value_get_type (value) == FL_VALUE_TYPE_MAP); - * FlValue *name = fl_value_lookup_string (value, "name"); - * g_assert (fl_value_get_type (name) == FL_VALUE_TYPE_STRING); - * FlValue *age = fl_value_lookup_string (value, "age"); - * g_assert (fl_value_get_type (age) == FL_VALUE_TYPE_INT); - * g_message ("Next customer is %s (%d years old)", - * fl_value_get_string (name), - * fl_value_get_int (age)); - * ]| - * - * Returns: a new #FlValue. - */ -FlValue* fl_value_new_map(); - -/** - * fl_value_ref: - * @value: an #FlValue. - * - * Increases the reference count of an #FlValue. - * - * Returns: the value that was referenced. - */ -FlValue* fl_value_ref(FlValue* value); - -/** - * fl_value_unref: - * @value: an #FlValue. - * - * Decreases the reference count of an #FlValue. When the reference count hits - * zero @value is destroyed and no longer valid. - */ -void fl_value_unref(FlValue* value); - -/** - * fl_value_get_type: - * @value: an #FlValue. - * - * Gets the type of @value. - * - * Returns: an #FlValueType. - */ -FlValueType fl_value_get_type(FlValue* value); - -/** - * fl_value_equal: - * @a: an #FlValue. - * @b: an #FlValue. - * - * Compares two #FlValue to see if they are equivalent. Two values are - * considered equivalent if they are of the same type and their data is the same - * including any child values. For values of type #FL_VALUE_TYPE_MAP the order - * of the values does not matter. - * - * Returns: %TRUE if both values are equivalent. - */ -bool fl_value_equal(FlValue* a, FlValue* b); - -/** - * fl_value_append: - * @value: an #FlValue of type #FL_VALUE_TYPE_LIST. - * @child: an #FlValue. - * - * Adds @child to the end of @value. Calling this with an #FlValue that is not - * of type #FL_VALUE_TYPE_LIST is a programming error. - */ -void fl_value_append(FlValue* value, FlValue* child); - -/** - * fl_value_append_take: - * @value: an #FlValue of type #FL_VALUE_TYPE_LIST. - * @child: (transfer full): an #FlValue. - * - * Adds @child to the end of @value. Ownership of @child is taken by @value. - * Calling this with an #FlValue that is not of type #FL_VALUE_TYPE_LIST is a - * programming error. - */ -void fl_value_append_take(FlValue* value, FlValue* child); - -/** - * fl_value_set: - * @value: an #FlValue of type #FL_VALUE_TYPE_MAP. - * @key: an #FlValue. - * @child_value: an #FlValue. - * - * Sets @key in @value to @child_value. If an existing value was in the map with - * the same key it is replaced. Calling this with an #FlValue that is not of - * type #FL_VALUE_TYPE_MAP is a programming error. - */ -void fl_value_set(FlValue* value, FlValue* key, FlValue* child_value); - -/** - * fl_value_set_take: - * @value: an #FlValue of type #FL_VALUE_TYPE_MAP. - * @key: (transfer full): an #FlValue. - * @child_value: (transfer full): an #FlValue. - * - * Sets @key in @value to @child_value. Ownership of both @key and @child_value - * is taken by @value. If an existing value was in the map with the same key it - * is replaced. Calling this with an #FlValue that is not of type - * #FL_VALUE_TYPE_MAP is a programming error. - */ -void fl_value_set_take(FlValue* value, FlValue* key, FlValue* child_value); - -/** - * fl_value_set_string: - * @value: an #FlValue of type #FL_VALUE_TYPE_MAP. - * @key: a UTF-8 text key. - * @child_value: an #FlValue. - * - * Sets a value in the map with a text key. If an existing value was in the map - * with the same key it is replaced. Calling this with an #FlValue that is not - * of type #FL_VALUE_TYPE_MAP is a programming error. - */ -void fl_value_set_string(FlValue* value, - const gchar* key, - FlValue* child_value); - -/** - * fl_value_set_string_take: - * @value: an #FlValue of type #FL_VALUE_TYPE_MAP. - * @key: a UTF-8 text key. - * @child_value: (transfer full): an #FlValue. - * - * Sets a value in the map with a text key, taking ownership of the value. If an - * existing value was in the map with the same key it is replaced. Calling this - * with an #FlValue that is not of type #FL_VALUE_TYPE_MAP is a programming - * error. - */ -void fl_value_set_string_take(FlValue* value, - const gchar* key, - FlValue* child_value); - -/** - * fl_value_get_bool: - * @value: an #FlValue of type #FL_VALUE_TYPE_BOOL. - * - * Gets the boolean value of @value. Calling this with an #FlValue that is - * not of type #FL_VALUE_TYPE_BOOL is a programming error. - * - * Returns: a boolean value. - */ -bool fl_value_get_bool(FlValue* value); - -/** - * fl_value_get_int: - * @value: an #FlValue of type #FL_VALUE_TYPE_INT. - * - * Gets the integer number of @value. Calling this with an #FlValue that is - * not of type #FL_VALUE_TYPE_INT is a programming error. - * - * Returns: an integer number. - */ -int64_t fl_value_get_int(FlValue* value); - -/** - * fl_value_get_float: - * @value: an #FlValue of type #FL_VALUE_TYPE_FLOAT. - * - * Gets the floating point number of @value. Calling this with an #FlValue - * that is not of type #FL_VALUE_TYPE_FLOAT is a programming error. - * - * Returns: a floating point number. - */ -double fl_value_get_float(FlValue* value); - -/** - * fl_value_get_string: - * @value: an #FlValue of type #FL_VALUE_TYPE_STRING. - * - * Gets the UTF-8 text contained in @value. Calling this with an #FlValue - * that is not of type #FL_VALUE_TYPE_STRING is a programming error. - * - * Returns: a UTF-8 encoded string. - */ -const gchar* fl_value_get_string(FlValue* value); - -/** - * fl_value_get_length: - * @value: an #FlValue of type #FL_VALUE_TYPE_UINT8_LIST, - * #FL_VALUE_TYPE_INT32_LIST, #FL_VALUE_TYPE_INT64_LIST, - * #FL_VALUE_TYPE_FLOAT_LIST, #FL_VALUE_TYPE_LIST or #FL_VALUE_TYPE_MAP. - * - * Gets the number of elements @value contains. This is only valid for list - * and map types. Calling this with other types is a programming error. - * - * Returns: the number of elements inside @value. - */ -size_t fl_value_get_length(FlValue* value); - -/** - * fl_value_get_uint8_list: - * @value: an #FlValue of type #FL_VALUE_TYPE_UINT8_LIST. - * - * Gets the array of unisigned 8 bit integers @value contains. The data - * contains fl_get_length() elements. Calling this with an #FlValue that is - * not of type #FL_VALUE_TYPE_UINT8_LIST is a programming error. - * - * Returns: an array of unsigned 8 bit integers. - */ -const uint8_t* fl_value_get_uint8_list(FlValue* value); - -/** - * fl_value_get_int32_list: - * @value: an #FlValue of type #FL_VALUE_TYPE_INT32_LIST. - * - * Gets the array of 32 bit integers @value contains. The data contains - * fl_get_length() elements. Calling this with an #FlValue that is not of - * type #FL_VALUE_TYPE_INT32_LIST is a programming error. - * - * Returns: an array of 32 bit integers. - */ -const int32_t* fl_value_get_int32_list(FlValue* value); - -/** - * fl_value_get_int64_list: - * @value: an #FlValue of type #FL_VALUE_TYPE_INT64_LIST. - * - * Gets the array of 64 bit integers @value contains. The data contains - * fl_get_length() elements. Calling this with an #FlValue that is not of - * type #FL_VALUE_TYPE_INT64_LIST is a programming error. - * - * Returns: an array of 64 bit integers. - */ -const int64_t* fl_value_get_int64_list(FlValue* value); - -/** - * fl_value_get_float_list: - * @value: an #FlValue of type #FL_VALUE_TYPE_FLOAT_LIST. - * - * Gets the array of floating point numbers @value contains. The data - * contains fl_get_length() elements. Calling this with an #FlValue that is - * not of type #FL_VALUE_TYPE_FLOAT_LIST is a programming error. - * - * Returns: an array of floating point numbers. - */ -const double* fl_value_get_float_list(FlValue* value); - -/** - * fl_value_get_list_value: - * @value: an #FlValue of type #FL_VALUE_TYPE_LIST. - * @index: an index in the list. - * - * Gets a child element of the list. It is a programming error to request an - * index that is outside the size of the list as returned from - * fl_value_get_length(). Calling this with an #FlValue that is not of type - * #FL_VALUE_TYPE_LIST is a programming error. - * - * Returns: an #FlValue. - */ -FlValue* fl_value_get_list_value(FlValue* value, size_t index); - -/** - * fl_value_get_map_key: - * @value: an #FlValue of type #FL_VALUE_TYPE_MAP. - * @index: an index in the map. - * - * Gets a key from the map. It is a programming error to request an index that - * is outside the size of the list as returned from fl_value_get_length(). - * Calling this with an #FlValue that is not of type #FL_VALUE_TYPE_MAP is a - * programming error. - * - * Returns: an #FlValue. - */ -FlValue* fl_value_get_map_key(FlValue* value, size_t index); - -/** - * fl_value_get_map_value: - * @value: an #FlValue of type #FL_VALUE_TYPE_MAP. - * @index: an index in the map. - * - * Gets a value from the map. It is a programming error to request an index that - * is outside the size of the list as returned from fl_value_get_length(). - * Calling this with an #FlValue that is not of type #FL_VALUE_TYPE_MAP is a - * programming error. - * - * Returns: an #FlValue. - */ -FlValue* fl_value_get_map_value(FlValue* value, size_t index); - -/** - * fl_value_lookup: - * @value: an #FlValue of type #FL_VALUE_TYPE_MAP. - * @key: a key value. - * - * Gets the map entry that matches @key. Keys are checked using - * fl_value_equal(). Calling this with an #FlValue that is not of type - * #FL_VALUE_TYPE_MAP is a programming error. - * - * Map lookups are not optimized for performance - if you have a large map or - * need frequent access you should copy the data into another structure, e.g. - * #GHashTable. - * - * Returns: (allow-none): the value with this key or %NULL if not one present. - */ -FlValue* fl_value_lookup(FlValue* value, FlValue* key); - -/** - * fl_value_lookup_string: - * @value: an #FlValue of type #FL_VALUE_TYPE_MAP. - * @key: a key value. - * - * Gets the map entry that matches @key. Keys are checked using - * fl_value_equal(). Calling this with an #FlValue that is not of type - * #FL_VALUE_TYPE_MAP is a programming error. - * - * Map lookups are not optimized for performance - if you have a large map or - * need frequent access you should copy the data into another structure, e.g. - * #GHashTable. - * - * Returns: (allow-none): the value with this key or %NULL if not one present. - */ -FlValue* fl_value_lookup_string(FlValue* value, const gchar* key); - -/** - * fl_value_to_string: - * @value: an #FlValue. - * - * Converts an #FlValue to a text representation, suitable for logging purposes. - * The text is formatted to be the equivalent of Dart toString() methods. - * - * Returns: UTF-8 text. - */ -gchar* fl_value_to_string(FlValue* value); - -G_DEFINE_AUTOPTR_CLEANUP_FUNC(FlValue, fl_value_unref) - -G_END_DECLS - -#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_VALUE_H_ diff --git a/example/linux/flutter/ephemeral/flutter_linux/fl_view.h b/example/linux/flutter/ephemeral/flutter_linux/fl_view.h deleted file mode 100644 index f93f385a4..000000000 --- a/example/linux/flutter/ephemeral/flutter_linux/fl_view.h +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_VIEW_H_ -#define FLUTTER_SHELL_PLATFORM_LINUX_FL_VIEW_H_ - -#if !defined(__FLUTTER_LINUX_INSIDE__) && !defined(FLUTTER_LINUX_COMPILATION) -#error "Only can be included directly." -#endif - -#include - -#include "fl_dart_project.h" -#include "fl_engine.h" - -G_BEGIN_DECLS - -G_DECLARE_FINAL_TYPE(FlView, fl_view, FL, VIEW, GtkContainer) - -/** - * FlView: - * - * #FlView is a GTK widget that is capable of displaying a Flutter application. - * - * The following example shows how to set up a view in a GTK application: - * |[ - * FlDartProject *project = fl_dart_project_new (); - * FlView *view = fl_view_new (project); - * gtk_widget_show (GTK_WIDGET (view)); - * gtk_container_add (GTK_CONTAINER (parent), view); - * - * FlBinaryMessenger *messenger = - * fl_engine_get_binary_messenger (fl_view_get_engine (view)); - * setup_channels_or_plugins (messenger); - * ]| - */ - -/** - * fl_view_new: - * @project: The project to show. - * - * Creates a widget to show Flutter application. - * - * Returns: a new #FlView. - */ -FlView* fl_view_new(FlDartProject* project); - -/** - * fl_view_get_engine: - * @view: an #FlView. - * - * Gets the engine being rendered in the view. - * - * Returns: an #FlEngine. - */ -FlEngine* fl_view_get_engine(FlView* view); - -G_END_DECLS - -#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_VIEW_H_ diff --git a/example/linux/flutter/ephemeral/flutter_linux/flutter_linux.h b/example/linux/flutter/ephemeral/flutter_linux/flutter_linux.h deleted file mode 100644 index f80bef47d..000000000 --- a/example/linux/flutter/ephemeral/flutter_linux/flutter_linux.h +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FLUTTER_LINUX_H_ -#define FLUTTER_SHELL_PLATFORM_LINUX_FLUTTER_LINUX_H_ - -#define __FLUTTER_LINUX_INSIDE__ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#undef __FLUTTER_LINUX_INSIDE__ - -#endif // FLUTTER_SHELL_PLATFORM_LINUX_FLUTTER_LINUX_H_ diff --git a/example/linux/flutter/ephemeral/generated_config.cmake b/example/linux/flutter/ephemeral/generated_config.cmake deleted file mode 100644 index f43c11483..000000000 --- a/example/linux/flutter/ephemeral/generated_config.cmake +++ /dev/null @@ -1,15 +0,0 @@ -# Generated code do not commit. -file(TO_CMAKE_PATH "/home/neo/snap/flutter/common/flutter" FLUTTER_ROOT) -file(TO_CMAKE_PATH "/home/neo/IdeaProjects/fl_chart/example" PROJECT_DIR) - -# Environment variables to pass to tool_backend.sh -list(APPEND FLUTTER_TOOL_ENVIRONMENT - "FLUTTER_ROOT=/home/neo/snap/flutter/common/flutter" - "PROJECT_DIR=/home/neo/IdeaProjects/fl_chart/example" - "DART_DEFINES=Zmx1dHRlci5pbnNwZWN0b3Iuc3RydWN0dXJlZEVycm9ycz10cnVl,RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ==" - "DART_OBFUSCATION=false" - "TRACK_WIDGET_CREATION=true" - "TREE_SHAKE_ICONS=false" - "PACKAGE_CONFIG=/home/neo/IdeaProjects/fl_chart/example/.dart_tool/package_config.json" - "FLUTTER_TARGET=/home/neo/IdeaProjects/fl_chart/example/lib/main.dart" -) diff --git a/example/linux/flutter/ephemeral/icudtl.dat b/example/linux/flutter/ephemeral/icudtl.dat deleted file mode 100644 index 2ca30cd20761d626b329262705484f9ff9c76a0d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 799760 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 hD;5Ovsx=S5@@6}nA` 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

(+}dIJR~ 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

_^vEnzMSE6V`C$!6||7DcZE$YlruqGdp)*gwl!k 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�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�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

Wky!2zlM^=&~>Wng6bIBKYKl2HjQit6@5 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 {M=-$Y-yARU?L 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%0DX){`?&pU(1b){) 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 z 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 { AxisChartHelper().iterateThroughAxis( min: data.minY, max: data.maxY, + baseLine: data.baselineY, interval: leftInterval, action: (axisValue) { if (leftTitles.checkToShowTitle( @@ -466,6 +467,7 @@ class BarChartPainter extends AxisChartPainter { AxisChartHelper().iterateThroughAxis( min: data.minY, max: data.maxY, + baseLine: data.baselineY, interval: rightInterval, action: (axisValue) { if (rightTitles.checkToShowTitle( 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 d9d1dc544..4a3ce7fad 100644 --- a/lib/src/chart/base/axis_chart/axis_chart_data.dart +++ b/lib/src/chart/base/axis_chart/axis_chart_data.dart @@ -19,8 +19,8 @@ abstract class AxisChartData extends BaseChartData with EquatableMixin { final FlAxisTitleData axisTitleData; final RangeAnnotations rangeAnnotations; - double minX, maxX; - double minY, maxY; + double minX, maxX, baselineX; + double minY, maxY, baselineY; /// clip the chart to the border (prevent draw outside the border) FlClipData clipData; @@ -40,8 +40,10 @@ abstract class AxisChartData extends BaseChartData with EquatableMixin { RangeAnnotations? rangeAnnotations, required double minX, required double maxX, + double? baselineX, required double minY, required double maxY, + double? baselineY, FlClipData? clipData, Color? backgroundColor, FlBorderData? borderData, @@ -51,8 +53,10 @@ abstract class AxisChartData extends BaseChartData with EquatableMixin { rangeAnnotations = rangeAnnotations ?? RangeAnnotations(), minX = minX, maxX = maxX, + baselineX = baselineX ?? 0, minY = minY, maxY = maxY, + baselineY = baselineY ?? 0, clipData = clipData ?? FlClipData.none(), backgroundColor = backgroundColor ?? Colors.transparent, super(borderData: borderData, touchData: touchData); @@ -65,8 +69,10 @@ abstract class AxisChartData extends BaseChartData with EquatableMixin { rangeAnnotations, minX, maxX, + baselineX, minY, maxY, + baselineY, clipData, backgroundColor, borderData, @@ -642,7 +648,7 @@ bool showAllGrids(double value) { /// Determines the appearance of specified line. /// -/// It gives you an axis value (horizontal or vertical), +/// It gives you an axis [value] (horizontal or vertical), /// you should pass a [FlLine] that represents style of specified line. typedef GetDrawingGridLine = FlLine Function(double value); 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 8ba0faf14..ed1f38bee 100644 --- a/lib/src/chart/base/axis_chart/axis_chart_helper.dart +++ b/lib/src/chart/base/axis_chart/axis_chart_helper.dart @@ -23,13 +23,15 @@ class AxisChartHelper { bool minIncluded = true, required double max, bool maxIncluded = true, + required double baseLine, required double interval, required void Function(double axisValue) action, }) { - final initialValue = - Utils().getBestInitialIntervalValue(min, max, interval); + final initialValue = Utils() + .getBestInitialIntervalValue(min, max, interval, baseline: baseLine); var axisSeek = initialValue; - if (!minIncluded && axisSeek == min) { + final firstPositionOverlapsWithMin = axisSeek == min; + if (!minIncluded && firstPositionOverlapsWithMin) { axisSeek += interval; } final diff = max - min; @@ -40,9 +42,15 @@ class AxisChartHelper { !maxIncluded && lastPositionOverlapsWithMax ? max - interval : max; final epsilon = interval / 100000; + if (minIncluded && !firstPositionOverlapsWithMin) { + action(min); + } while (axisSeek <= end + epsilon) { action(axisSeek); axisSeek += interval; } + if (maxIncluded && !lastPositionOverlapsWithMax) { + action(max); + } } } diff --git a/lib/src/chart/base/axis_chart/axis_chart_painter.dart b/lib/src/chart/base/axis_chart/axis_chart_painter.dart index bbc04d6dd..2c9dfdd2b 100644 --- a/lib/src/chart/base/axis_chart/axis_chart_painter.dart +++ b/lib/src/chart/base/axis_chart/axis_chart_painter.dart @@ -243,6 +243,7 @@ abstract class AxisChartPainter minIncluded: false, max: data.maxX, maxIncluded: false, + baseLine: data.baselineX, interval: verticalInterval, action: (axisValue) { if (data.gridData.checkToShowVerticalLine(axisValue)) { @@ -274,6 +275,7 @@ abstract class AxisChartPainter minIncluded: false, max: data.maxY, maxIncluded: false, + baseLine: data.baselineY, interval: horizontalInterval, action: (axisValue) { if (data.gridData.checkToShowHorizontalLine(axisValue)) { diff --git a/lib/src/chart/line_chart/line_chart_data.dart b/lib/src/chart/line_chart/line_chart_data.dart index c116d9916..3e1bcdddc 100644 --- a/lib/src/chart/line_chart/line_chart_data.dart +++ b/lib/src/chart/line_chart/line_chart_data.dart @@ -73,8 +73,10 @@ class LineChartData extends AxisChartData with EquatableMixin { RangeAnnotations? rangeAnnotations, double? minX, double? maxX, + double? baselineX, double? minY, double? maxY, + double? baselineY, FlClipData? clipData, Color? backgroundColor, }) : lineBarsData = lineBarsData ?? const [], @@ -97,12 +99,14 @@ class LineChartData extends AxisChartData with EquatableMixin { maxX: maxX ?? LineChartHelper.calculateMaxAxisValues(lineBarsData ?? const []) .maxX, + baselineX: baselineX, minY: minY ?? LineChartHelper.calculateMaxAxisValues(lineBarsData ?? const []) .minY, maxY: maxY ?? LineChartHelper.calculateMaxAxisValues(lineBarsData ?? const []) .maxY, + baselineY: baselineY, ); /// Lerps a [BaseChartData] based on [t] value, check [Tween.lerp]. @@ -112,8 +116,10 @@ class LineChartData extends AxisChartData with EquatableMixin { return LineChartData( minX: lerpDouble(a.minX, b.minX, t), maxX: lerpDouble(a.maxX, b.maxX, t), + baselineX: lerpDouble(a.baselineX, b.baselineX, t), minY: lerpDouble(a.minY, b.minY, t), maxY: lerpDouble(a.maxY, b.maxY, t), + baselineY: lerpDouble(a.baselineY, b.baselineY, t), backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), borderData: FlBorderData.lerp(a.borderData, b.borderData, t), clipData: b.clipData, @@ -152,8 +158,10 @@ class LineChartData extends AxisChartData with EquatableMixin { FlBorderData? borderData, double? minX, double? maxX, + double? baselineX, double? minY, double? maxY, + double? baselineY, FlClipData? clipData, Color? backgroundColor, }) { @@ -171,8 +179,10 @@ class LineChartData extends AxisChartData with EquatableMixin { borderData: borderData ?? this.borderData, minX: minX ?? this.minX, maxX: maxX ?? this.maxX, + baselineX: baselineX ?? this.baselineX, minY: minY ?? this.minY, maxY: maxY ?? this.maxY, + baselineY: baselineY ?? this.baselineY, clipData: clipData ?? this.clipData, backgroundColor: backgroundColor ?? this.backgroundColor, ); @@ -193,8 +203,10 @@ class LineChartData extends AxisChartData with EquatableMixin { rangeAnnotations, minX, maxX, + baselineX, minY, maxY, + baselineY, clipData, backgroundColor, ]; diff --git a/lib/src/chart/line_chart/line_chart_painter.dart b/lib/src/chart/line_chart/line_chart_painter.dart index e12ba6d00..c43e0c565 100644 --- a/lib/src/chart/line_chart/line_chart_painter.dart +++ b/lib/src/chart/line_chart/line_chart_painter.dart @@ -903,6 +903,7 @@ class LineChartPainter extends AxisChartPainter { AxisChartHelper().iterateThroughAxis( min: data.minY, max: data.maxY, + baseLine: data.baselineY, interval: leftInterval, action: (axisValue) { if (leftTitles.checkToShowTitle( @@ -946,6 +947,7 @@ class LineChartPainter extends AxisChartPainter { AxisChartHelper().iterateThroughAxis( min: data.minX, max: data.maxX, + baseLine: data.baselineX, interval: topInterval, action: (axisValue) { if (topTitles.checkToShowTitle( @@ -987,6 +989,7 @@ class LineChartPainter extends AxisChartPainter { AxisChartHelper().iterateThroughAxis( min: data.minY, max: data.maxY, + baseLine: data.baselineY, interval: rightInterval, action: (axisValue) { if (rightTitles.checkToShowTitle( @@ -1031,6 +1034,7 @@ class LineChartPainter extends AxisChartPainter { AxisChartHelper().iterateThroughAxis( min: data.minX, max: data.maxX, + baseLine: data.baselineX, interval: bottomInterval, action: (axisValue) { if (bottomTitles.checkToShowTitle( diff --git a/lib/src/chart/scatter_chart/scatter_chart_data.dart b/lib/src/chart/scatter_chart/scatter_chart_data.dart index dc221f189..1a62b90d4 100644 --- a/lib/src/chart/scatter_chart/scatter_chart_data.dart +++ b/lib/src/chart/scatter_chart/scatter_chart_data.dart @@ -49,8 +49,10 @@ class ScatterChartData extends AxisChartData with EquatableMixin { FlAxisTitleData? axisTitleData, double? minX, double? maxX, + double? baselineX, double? minY, double? maxY, + double? baselineY, FlClipData? clipData, Color? backgroundColor, }) : scatterSpots = scatterSpots ?? const [], @@ -72,6 +74,7 @@ class ScatterChartData extends AxisChartData with EquatableMixin { ScatterChartHelper.calculateMaxAxisValues( scatterSpots ?? const []) .maxX, + baselineX: baselineX, minY: minY ?? ScatterChartHelper.calculateMaxAxisValues( scatterSpots ?? const []) @@ -80,6 +83,7 @@ class ScatterChartData extends AxisChartData with EquatableMixin { ScatterChartHelper.calculateMaxAxisValues( scatterSpots ?? const []) .maxY, + baselineY: baselineY, ); /// Lerps a [ScatterChartData] based on [t] value, check [Tween.lerp]. @@ -98,8 +102,10 @@ class ScatterChartData extends AxisChartData with EquatableMixin { FlAxisTitleData.lerp(a.axisTitleData, b.axisTitleData, t), minX: lerpDouble(a.minX, b.minX, t), maxX: lerpDouble(a.maxX, b.maxX, t), + baselineX: lerpDouble(a.baselineX, b.baselineX, t), minY: lerpDouble(a.minY, b.minY, t), maxY: lerpDouble(a.maxY, b.maxY, t), + baselineY: lerpDouble(a.baselineY, b.baselineY, t), clipData: b.clipData, backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), ); @@ -120,8 +126,10 @@ class ScatterChartData extends AxisChartData with EquatableMixin { FlAxisTitleData? axisTitleData, double? minX, double? maxX, + double? baselineX, double? minY, double? maxY, + double? baselineY, FlClipData? clipData, Color? backgroundColor, }) { @@ -136,8 +144,10 @@ class ScatterChartData extends AxisChartData with EquatableMixin { axisTitleData: axisTitleData ?? this.axisTitleData, minX: minX ?? this.minX, maxX: maxX ?? this.maxX, + baselineX: baselineX ?? this.baselineX, minY: minY ?? this.minY, maxY: maxY ?? this.maxY, + baselineY: baselineY ?? this.baselineY, clipData: clipData ?? this.clipData, backgroundColor: backgroundColor ?? this.backgroundColor, ); @@ -158,8 +168,10 @@ class ScatterChartData extends AxisChartData with EquatableMixin { backgroundColor, minX, maxX, + baselineX, minY, maxY, + baselineY, rangeAnnotations, ]; } diff --git a/lib/src/chart/scatter_chart/scatter_chart_painter.dart b/lib/src/chart/scatter_chart/scatter_chart_painter.dart index c84504c5b..89fd9a16f 100644 --- a/lib/src/chart/scatter_chart/scatter_chart_painter.dart +++ b/lib/src/chart/scatter_chart/scatter_chart_painter.dart @@ -58,6 +58,7 @@ class ScatterChartPainter extends AxisChartPainter { AxisChartHelper().iterateThroughAxis( min: data.minY, max: data.maxY, + baseLine: data.baselineY, interval: leftInterval, action: (axisValue) { if (leftTitles.checkToShowTitle( @@ -102,6 +103,7 @@ class ScatterChartPainter extends AxisChartPainter { AxisChartHelper().iterateThroughAxis( min: data.minX, max: data.maxX, + baseLine: data.baselineX, interval: topInterval, action: (axisValue) { if (topTitles.checkToShowTitle( @@ -143,6 +145,7 @@ class ScatterChartPainter extends AxisChartPainter { AxisChartHelper().iterateThroughAxis( min: data.minY, max: data.maxY, + baseLine: data.baselineY, interval: rightInterval, action: (axisValue) { if (rightTitles.checkToShowTitle( @@ -187,6 +190,7 @@ class ScatterChartPainter extends AxisChartPainter { AxisChartHelper().iterateThroughAxis( min: data.minX, max: data.maxX, + baseLine: data.baselineX, interval: bottomInterval, action: (axisValue) { if (bottomTitles.checkToShowTitle( diff --git a/lib/src/utils/utils.dart b/lib/src/utils/utils.dart index 9813861dd..4f52698f7 100644 --- a/lib/src/utils/utils.dart +++ b/lib/src/utils/utils.dart @@ -268,14 +268,17 @@ class Utils { /// If there is a zero point in the axis, we a value that passes through it. /// For example if we have -3 to +3, with interval 2. if we start from -3, we get something like this: -3, -1, +1, +3 /// But the most important point is zero in most cases. with this logic we get this: -2, 0, 2 - double getBestInitialIntervalValue(double min, double max, double interval) { - if (min > 0 || max < 0) { + double getBestInitialIntervalValue(double min, double max, double interval, + {double baseline = 0.0}) { + final diff = (baseline - min); + final mod = (diff % interval); + if ((max - min).abs() <= mod) { return min; } - if (max - min <= interval) { + if (mod == 0) { return min; } - return interval * (min ~/ interval).toDouble(); + return min + mod; } /// Converts radius number to sigma for drawing shadows diff --git a/repo_files/documentations/bar_chart.md b/repo_files/documentations/bar_chart.md index 8f4daa0d9..458481444 100644 --- a/repo_files/documentations/bar_chart.md +++ b/repo_files/documentations/bar_chart.md @@ -31,6 +31,7 @@ When you change the chart's state, it animates to the new state internally (usin |borderData| check the [FlBorderData](base_chart.md#FlBorderData)|FlBorderData()| |maxY| gets maximum y of y axis, if null, value will be read from the input barGroups | null| |minY| gets minimum y of y axis, if null, value will be read from the input barGroups | null| +|baselineY| defines the baseline of y-axis | 0| ### BarChartGroupData diff --git a/repo_files/documentations/line_chart.md b/repo_files/documentations/line_chart.md index c854c3e9e..422c99afd 100644 --- a/repo_files/documentations/line_chart.md +++ b/repo_files/documentations/line_chart.md @@ -31,8 +31,10 @@ When you change the chart's state, it animates to the new state internally (usin |borderData| check the [FlBorderData](base_chart.md#FlBorderData)|FlBorderData()| |minX| gets minimum x of x axis, if null, value will read from the input lineBars |null| |maxX| gets maximum x of x axis, if null, value will read from the input lineBars | null| +|baselineX| defines the baseline of x-axis | 0| |minY| gets minimum y of y axis, if null, value will read from the input lineBars | null| |maxY| gets maximum y of y axis, if null, value will read from the input lineBars | null| +|baselineY| defines the baseline of y-axis | 0| |clipData| clip the chart to the border (prevent drawing outside the border) | FlClipData.none()| |backgroundColor| a background color which is drawn behind th chart| null | @@ -275,3 +277,6 @@ When you change the chart's state, it animates to the new state internally (usin ##### Sample 10 ([Source Code](/example/lib/line_chart/samples/line_chart_sample10.dart)) + +##### Gist - baseLineX, baselineY sample ([Source Code](https://gist.github.com/imaNNeoFighT/5822eda603bdad18cf46f212d5a48822)) +https://user-images.githubusercontent.com/7009300/152555425-3b53ac8c-257f-49b0-8d75-1a878c03ccaa.mp4 diff --git a/test/chart/bar_chart/bar_chart_painter_test.dart b/test/chart/bar_chart/bar_chart_painter_test.dart index ca43d36fa..9ff4640fb 100644 --- a/test/chart/bar_chart/bar_chart_painter_test.dart +++ b/test/chart/bar_chart/bar_chart_painter_test.dart @@ -1120,7 +1120,7 @@ void main() { barChartPainter.drawTitles( _mockBuildContext, _mockCanvasWrapper, barGroupsPosition, holder); - expect(results.length, 14); + expect(results.length, 16); }); }); diff --git a/test/chart/bar_chart/bar_chart_painter_test.mocks.dart b/test/chart/bar_chart/bar_chart_painter_test.mocks.dart index 93a376c41..4a1439fce 100644 --- a/test/chart/bar_chart/bar_chart_painter_test.mocks.dart +++ b/test/chart/bar_chart/bar_chart_painter_test.mocks.dart @@ -457,10 +457,10 @@ class MockUtils extends _i1.Mock implements _i8.Utils { Invocation.method(#normalizeBorderSide, [borderSide, width]), returnValue: _FakeBorderSide_6()) as _i3.BorderSide); @override - double getEfficientInterval(double? axisViewSize, double? diffInYAxis, + double getEfficientInterval(double? axisViewSize, double? diffInAxis, {double? pixelPerInterval = 40.0}) => (super.noSuchMethod( - Invocation.method(#getEfficientInterval, [axisViewSize, diffInYAxis], + Invocation.method(#getEfficientInterval, [axisViewSize, diffInAxis], {#pixelPerInterval: pixelPerInterval}), returnValue: 0.0) as double); @override @@ -478,10 +478,11 @@ class MockUtils extends _i1.Mock implements _i8.Utils { Invocation.method(#getThemeAwareTextStyle, [context, providedStyle]), returnValue: _FakeTextStyle_7()) as _i3.TextStyle); @override - double getBestInitialIntervalValue( - double? min, double? max, double? interval) => + double getBestInitialIntervalValue(double? min, double? max, double? interval, + {double? baseline = 0.0}) => (super.noSuchMethod( - Invocation.method(#getBestInitialIntervalValue, [min, max, interval]), + Invocation.method(#getBestInitialIntervalValue, [min, max, interval], + {#baseline: baseline}), returnValue: 0.0) as double); @override double convertRadiusToSigma(double? radius) => diff --git a/test/chart/base/axis_chart_helper_test.dart b/test/chart/base/axis_chart_helper_test.dart index c6b51b0d6..c605f6b67 100644 --- a/test/chart/base/axis_chart_helper_test.dart +++ b/test/chart/base/axis_chart_helper_test.dart @@ -13,6 +13,7 @@ void main() { action: (axisValue) { results.add(axisValue); }, + baseLine: 0, ); expect(results.length, 101); }); @@ -28,6 +29,7 @@ void main() { action: (axisValue) { results.add(axisValue); }, + baseLine: 0, ); expect(results.length, 99); expect(results[0], closeTo(0.001, tolerance)); @@ -43,6 +45,7 @@ void main() { action: (axisValue) { results.add(axisValue); }, + baseLine: 0, ); expect(results.length, 6); expect(results[0], 0); @@ -62,12 +65,14 @@ void main() { action: (axisValue) { results.add(axisValue); }, + baseLine: 0, ); - expect(results.length, 4); + expect(results.length, 5); expect(results[0], 0); expect(results[1], 3); expect(results[2], 6); expect(results[3], 9); + expect(results[4], 10); }); test('test 4', () { @@ -81,11 +86,32 @@ void main() { action: (axisValue) { results.add(axisValue); }, + baseLine: 0, ); expect(results.length, 3); expect(results[0], 3); expect(results[1], 6); expect(results[2], 9); }); + + test('test 4', () { + List results = []; + AxisChartHelper().iterateThroughAxis( + min: 35, + minIncluded: true, + max: 130, + maxIncluded: true, + interval: 50, + action: (axisValue) { + results.add(axisValue); + }, + baseLine: 0, + ); + expect(results.length, 4); + expect(results[0], 35); + expect(results[1], 50); + expect(results[2], 100); + expect(results[3], 130); + }); }); } diff --git a/test/chart/line_chart/line_chart_painter_test.mocks.dart b/test/chart/line_chart/line_chart_painter_test.mocks.dart index 4423f8e77..0efc3bdd6 100644 --- a/test/chart/line_chart/line_chart_painter_test.mocks.dart +++ b/test/chart/line_chart/line_chart_painter_test.mocks.dart @@ -462,10 +462,10 @@ class MockUtils extends _i1.Mock implements _i8.Utils { Invocation.method(#normalizeBorderSide, [borderSide, width]), returnValue: _FakeBorderSide_6()) as _i3.BorderSide); @override - double getEfficientInterval(double? axisViewSize, double? diffInYAxis, + double getEfficientInterval(double? axisViewSize, double? diffInAxis, {double? pixelPerInterval = 40.0}) => (super.noSuchMethod( - Invocation.method(#getEfficientInterval, [axisViewSize, diffInYAxis], + Invocation.method(#getEfficientInterval, [axisViewSize, diffInAxis], {#pixelPerInterval: pixelPerInterval}), returnValue: 0.0) as double); @override @@ -483,10 +483,11 @@ class MockUtils extends _i1.Mock implements _i8.Utils { Invocation.method(#getThemeAwareTextStyle, [context, providedStyle]), returnValue: _FakeTextStyle_7()) as _i3.TextStyle); @override - double getBestInitialIntervalValue( - double? min, double? max, double? interval) => + double getBestInitialIntervalValue(double? min, double? max, double? interval, + {double? baseline = 0.0}) => (super.noSuchMethod( - Invocation.method(#getBestInitialIntervalValue, [min, max, interval]), + Invocation.method(#getBestInitialIntervalValue, [min, max, interval], + {#baseline: baseline}), returnValue: 0.0) as double); @override double convertRadiusToSigma(double? radius) => diff --git a/test/chart/pie_chart/pie_chart_painter_test.mocks.dart b/test/chart/pie_chart/pie_chart_painter_test.mocks.dart index 1cb65e7c0..bd8e378ea 100644 --- a/test/chart/pie_chart/pie_chart_painter_test.mocks.dart +++ b/test/chart/pie_chart/pie_chart_painter_test.mocks.dart @@ -457,10 +457,10 @@ class MockUtils extends _i1.Mock implements _i8.Utils { Invocation.method(#normalizeBorderSide, [borderSide, width]), returnValue: _FakeBorderSide_6()) as _i3.BorderSide); @override - double getEfficientInterval(double? axisViewSize, double? diffInYAxis, + double getEfficientInterval(double? axisViewSize, double? diffInAxis, {double? pixelPerInterval = 40.0}) => (super.noSuchMethod( - Invocation.method(#getEfficientInterval, [axisViewSize, diffInYAxis], + Invocation.method(#getEfficientInterval, [axisViewSize, diffInAxis], {#pixelPerInterval: pixelPerInterval}), returnValue: 0.0) as double); @override @@ -478,10 +478,11 @@ class MockUtils extends _i1.Mock implements _i8.Utils { Invocation.method(#getThemeAwareTextStyle, [context, providedStyle]), returnValue: _FakeTextStyle_7()) as _i3.TextStyle); @override - double getBestInitialIntervalValue( - double? min, double? max, double? interval) => + double getBestInitialIntervalValue(double? min, double? max, double? interval, + {double? baseline = 0.0}) => (super.noSuchMethod( - Invocation.method(#getBestInitialIntervalValue, [min, max, interval]), + Invocation.method(#getBestInitialIntervalValue, [min, max, interval], + {#baseline: baseline}), returnValue: 0.0) as double); @override double convertRadiusToSigma(double? radius) => diff --git a/test/chart/pie_chart/pie_chart_renderer_test.mocks.dart b/test/chart/pie_chart/pie_chart_renderer_test.mocks.dart index 1a74aeb06..0d8731ee2 100644 --- a/test/chart/pie_chart/pie_chart_renderer_test.mocks.dart +++ b/test/chart/pie_chart/pie_chart_renderer_test.mocks.dart @@ -5,10 +5,10 @@ import 'dart:typed_data' as _i8; import 'dart:ui' as _i2; +import 'package:fl_chart/fl_chart.dart' as _i7; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart' as _i12; import 'package:fl_chart/src/chart/base/line.dart' as _i13; -import 'package:fl_chart/src/chart/pie_chart/pie_chart_data.dart' as _i7; import 'package:fl_chart/src/chart/pie_chart/pie_chart_painter.dart' as _i10; import 'package:fl_chart/src/utils/canvas_wrapper.dart' as _i11; import 'package:flutter/foundation.dart' as _i5; diff --git a/test/chart/radar_chart/radar_chart_painter_test.mocks.dart b/test/chart/radar_chart/radar_chart_painter_test.mocks.dart index 9a9ff50aa..7f07335f5 100644 --- a/test/chart/radar_chart/radar_chart_painter_test.mocks.dart +++ b/test/chart/radar_chart/radar_chart_painter_test.mocks.dart @@ -457,10 +457,10 @@ class MockUtils extends _i1.Mock implements _i8.Utils { Invocation.method(#normalizeBorderSide, [borderSide, width]), returnValue: _FakeBorderSide_6()) as _i3.BorderSide); @override - double getEfficientInterval(double? axisViewSize, double? diffInYAxis, + double getEfficientInterval(double? axisViewSize, double? diffInAxis, {double? pixelPerInterval = 40.0}) => (super.noSuchMethod( - Invocation.method(#getEfficientInterval, [axisViewSize, diffInYAxis], + Invocation.method(#getEfficientInterval, [axisViewSize, diffInAxis], {#pixelPerInterval: pixelPerInterval}), returnValue: 0.0) as double); @override @@ -478,10 +478,11 @@ class MockUtils extends _i1.Mock implements _i8.Utils { Invocation.method(#getThemeAwareTextStyle, [context, providedStyle]), returnValue: _FakeTextStyle_7()) as _i3.TextStyle); @override - double getBestInitialIntervalValue( - double? min, double? max, double? interval) => + double getBestInitialIntervalValue(double? min, double? max, double? interval, + {double? baseline = 0.0}) => (super.noSuchMethod( - Invocation.method(#getBestInitialIntervalValue, [min, max, interval]), + Invocation.method(#getBestInitialIntervalValue, [min, max, interval], + {#baseline: baseline}), returnValue: 0.0) as double); @override double convertRadiusToSigma(double? radius) => diff --git a/test/chart/scatter_chart/scatter_chart_painter_test.dart b/test/chart/scatter_chart/scatter_chart_painter_test.dart index 53bfc0aa3..8172255a8 100644 --- a/test/chart/scatter_chart/scatter_chart_painter_test.dart +++ b/test/chart/scatter_chart/scatter_chart_painter_test.dart @@ -237,7 +237,7 @@ void main() { _mockCanvasWrapper, holder, ); - verify(_mockCanvasWrapper.drawText(any, any, 0.0)).called(4); + verify(_mockCanvasWrapper.drawText(any, any, 0.0)).called(5); }); test('test 3', () { @@ -331,7 +331,7 @@ void main() { _mockCanvasWrapper, holder, ); - verify(_mockCanvasWrapper.drawText(any, any, 0.0)).called(7); + verify(_mockCanvasWrapper.drawText(any, any, 0.0)).called(8); expect(leftTitlesCalledValues.contains(0.0), true); expect(leftTitlesCalledValues.contains(5.0), true); expect(leftTitlesCalledValues.contains(10.0), true); @@ -397,7 +397,7 @@ void main() { _mockCanvasWrapper, holder, ); - verify(_mockCanvasWrapper.drawText(any, any, 0.0)).called(7); + verify(_mockCanvasWrapper.drawText(any, any, 0.0)).called(8); expect(leftTitlesCalledValues.contains(0.0), true); expect(leftTitlesCalledValues.contains(5.0), true); expect(leftTitlesCalledValues.contains(10.0), true); diff --git a/test/chart/scatter_chart/scatter_chart_painter_test.mocks.dart b/test/chart/scatter_chart/scatter_chart_painter_test.mocks.dart index 767e9492e..618a1d581 100644 --- a/test/chart/scatter_chart/scatter_chart_painter_test.mocks.dart +++ b/test/chart/scatter_chart/scatter_chart_painter_test.mocks.dart @@ -457,10 +457,10 @@ class MockUtils extends _i1.Mock implements _i8.Utils { Invocation.method(#normalizeBorderSide, [borderSide, width]), returnValue: _FakeBorderSide_6()) as _i3.BorderSide); @override - double getEfficientInterval(double? axisViewSize, double? diffInYAxis, + double getEfficientInterval(double? axisViewSize, double? diffInAxis, {double? pixelPerInterval = 40.0}) => (super.noSuchMethod( - Invocation.method(#getEfficientInterval, [axisViewSize, diffInYAxis], + Invocation.method(#getEfficientInterval, [axisViewSize, diffInAxis], {#pixelPerInterval: pixelPerInterval}), returnValue: 0.0) as double); @override @@ -478,10 +478,11 @@ class MockUtils extends _i1.Mock implements _i8.Utils { Invocation.method(#getThemeAwareTextStyle, [context, providedStyle]), returnValue: _FakeTextStyle_7()) as _i3.TextStyle); @override - double getBestInitialIntervalValue( - double? min, double? max, double? interval) => + double getBestInitialIntervalValue(double? min, double? max, double? interval, + {double? baseline = 0.0}) => (super.noSuchMethod( - Invocation.method(#getBestInitialIntervalValue, [min, max, interval]), + Invocation.method(#getBestInitialIntervalValue, [min, max, interval], + {#baseline: baseline}), returnValue: 0.0) as double); @override double convertRadiusToSigma(double? radius) => diff --git a/test/chart/scatter_chart/scatter_chart_renderer_test.mocks.dart b/test/chart/scatter_chart/scatter_chart_renderer_test.mocks.dart index bdc7a117a..53171706f 100644 --- a/test/chart/scatter_chart/scatter_chart_renderer_test.mocks.dart +++ b/test/chart/scatter_chart/scatter_chart_renderer_test.mocks.dart @@ -5,10 +5,9 @@ import 'dart:typed_data' as _i7; import 'dart:ui' as _i2; +import 'package:fl_chart/fl_chart.dart' as _i12; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart' as _i11; -import 'package:fl_chart/src/chart/scatter_chart/scatter_chart_data.dart' - as _i12; import 'package:fl_chart/src/chart/scatter_chart/scatter_chart_painter.dart' as _i9; import 'package:fl_chart/src/utils/canvas_wrapper.dart' as _i10; diff --git a/test/utils/canvas_wrapper_test.mocks.dart b/test/utils/canvas_wrapper_test.mocks.dart index 2ef2ba023..f2b282213 100644 --- a/test/utils/canvas_wrapper_test.mocks.dart +++ b/test/utils/canvas_wrapper_test.mocks.dart @@ -283,10 +283,10 @@ class MockUtils extends _i1.Mock implements _i6.Utils { Invocation.method(#normalizeBorderSide, [borderSide, width]), returnValue: _FakeBorderSide_2()) as _i3.BorderSide); @override - double getEfficientInterval(double? axisViewSize, double? diffInYAxis, + double getEfficientInterval(double? axisViewSize, double? diffInAxis, {double? pixelPerInterval = 40.0}) => (super.noSuchMethod( - Invocation.method(#getEfficientInterval, [axisViewSize, diffInYAxis], + Invocation.method(#getEfficientInterval, [axisViewSize, diffInAxis], {#pixelPerInterval: pixelPerInterval}), returnValue: 0.0) as double); @override @@ -304,10 +304,11 @@ class MockUtils extends _i1.Mock implements _i6.Utils { Invocation.method(#getThemeAwareTextStyle, [context, providedStyle]), returnValue: _FakeTextStyle_3()) as _i3.TextStyle); @override - double getBestInitialIntervalValue( - double? min, double? max, double? interval) => + double getBestInitialIntervalValue(double? min, double? max, double? interval, + {double? baseline = 0.0}) => (super.noSuchMethod( - Invocation.method(#getBestInitialIntervalValue, [min, max, interval]), + Invocation.method(#getBestInitialIntervalValue, [min, max, interval], + {#baseline: baseline}), returnValue: 0.0) as double); @override double convertRadiusToSigma(double? radius) => diff --git a/test/utils/utils_test.dart b/test/utils/utils_test.dart index c39404105..482695278 100644 --- a/test/utils/utils_test.dart +++ b/test/utils/utils_test.dart @@ -166,10 +166,10 @@ void main() { test('test getInitialIntervalValue()', () { expect(Utils().getBestInitialIntervalValue(-3, 3, 2), -2); expect(Utils().getBestInitialIntervalValue(-3, 3, 1), -3); - expect(Utils().getBestInitialIntervalValue(-30, -20, 13), -30); + expect(Utils().getBestInitialIntervalValue(-30, -20, 13), -26); expect(Utils().getBestInitialIntervalValue(0, 13, 8), 0); - expect(Utils().getBestInitialIntervalValue(1, 13, 7), 1); - expect(Utils().getBestInitialIntervalValue(1, 13, 3), 1); + expect(Utils().getBestInitialIntervalValue(1, 13, 7), 7); + expect(Utils().getBestInitialIntervalValue(1, 13, 3), 3); expect(Utils().getBestInitialIntervalValue(-1, 13, 3), 0); expect(Utils().getBestInitialIntervalValue(-2, 13, 3), 0); expect(Utils().getBestInitialIntervalValue(-3, 13, 3), -3); @@ -177,12 +177,45 @@ void main() { expect(Utils().getBestInitialIntervalValue(-5, 13, 3), -3); expect(Utils().getBestInitialIntervalValue(-6, 13, 3), -6); expect(Utils().getBestInitialIntervalValue(-6.5, 13, 3), -6); - expect(Utils().getBestInitialIntervalValue(-1, 1, 2), -1); + expect(Utils().getBestInitialIntervalValue(-1, 1, 2), 0); expect(Utils().getBestInitialIntervalValue(-1, 2, 2), 0); expect(Utils().getBestInitialIntervalValue(-2, 0, 2), -2); expect(Utils().getBestInitialIntervalValue(-3, 0, 2), -2); expect(Utils().getBestInitialIntervalValue(-4, 0, 2), -4); - expect(Utils().getBestInitialIntervalValue(-0.5, 0.5, 2), -0.5); + expect(Utils().getBestInitialIntervalValue(-0.5, 0.5, 2), 0); + expect(Utils().getBestInitialIntervalValue(35, 130, 50), 50); + expect(Utils().getBestInitialIntervalValue(49, 130, 50), 50); + expect(Utils().getBestInitialIntervalValue(50, 130, 50), 50); + expect(Utils().getBestInitialIntervalValue(60, 130, 50), 100); + expect(Utils().getBestInitialIntervalValue(110, 130, 50), 110); + expect(Utils().getBestInitialIntervalValue(90, 180, 50), 100); + expect(Utils().getBestInitialIntervalValue(100, 180, 50), 100); + expect(Utils().getBestInitialIntervalValue(110, 180, 50), 150); + expect(Utils().getBestInitialIntervalValue(170, 180, 50), 170); + expect(Utils().getBestInitialIntervalValue(-120, -10, 50), -100); + expect(Utils().getBestInitialIntervalValue(-110, -10, 50), -100); + expect(Utils().getBestInitialIntervalValue(-100, -10, 50), -100); + expect(Utils().getBestInitialIntervalValue(-90, -10, 50), -50); + expect(Utils().getBestInitialIntervalValue(-80, -10, 50), -50); + expect(Utils().getBestInitialIntervalValue(-150, -10, 50), -150); + expect(Utils().getBestInitialIntervalValue(-10, 10, 2, baseline: -1), -9); + expect(Utils().getBestInitialIntervalValue(-10, 10, 2, baseline: -20), -10); + expect(Utils().getBestInitialIntervalValue(-10, 10, 15, baseline: -30), 0); + expect(Utils().getBestInitialIntervalValue(0, 20, 8, baseline: 28), 4); + expect(Utils().getBestInitialIntervalValue(130, 140, 50, baseline: 0), 130); + expect(Utils().getBestInitialIntervalValue(145, 155, 50, baseline: 0), 150); + expect( + Utils().getBestInitialIntervalValue(-200, -180, 30, baseline: 0), -200); + expect( + Utils().getBestInitialIntervalValue(-190, -170, 30, baseline: 0), -180); + expect( + Utils().getBestInitialIntervalValue(-2000, 2000, 100, baseline: -10000), + -2000); + expect(Utils().getBestInitialIntervalValue(-120, 120, 33, baseline: -200), + -101); + expect( + Utils().getBestInitialIntervalValue(120, 180, 60, baseline: 2000), 140); + expect(Utils().getBestInitialIntervalValue(-10, 10, 4, baseline: 3), -9); }); test('test convertRadiusToSigma()', () { diff --git a/test/utils/utils_test.mocks.dart b/test/utils/utils_test.mocks.dart deleted file mode 100644 index c30982f83..000000000 --- a/test/utils/utils_test.mocks.dart +++ /dev/null @@ -1,93 +0,0 @@ -// Mocks generated by Mockito 5.0.17 from annotations -// in fl_chart/test/utils/utils_test.dart. -// Do not manually edit this file. - -import 'package:flutter/foundation.dart' as _i3; -import 'package:flutter/src/widgets/framework.dart' as _i2; -import 'package:mockito/mockito.dart' as _i1; - -// ignore_for_file: avoid_redundant_argument_values -// ignore_for_file: avoid_setters_without_getters -// ignore_for_file: comment_references -// ignore_for_file: implementation_imports -// ignore_for_file: invalid_use_of_visible_for_testing_member -// ignore_for_file: prefer_const_constructors -// ignore_for_file: unnecessary_parenthesis -// ignore_for_file: camel_case_types - -class _FakeWidget_0 extends _i1.Fake implements _i2.Widget { - @override - String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => - super.toString(); -} - -class _FakeInheritedWidget_1 extends _i1.Fake implements _i2.InheritedWidget { - @override - String toString({_i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => - super.toString(); -} - -class _FakeDiagnosticsNode_2 extends _i1.Fake implements _i3.DiagnosticsNode { - @override - String toString( - {_i3.TextTreeConfiguration? parentConfiguration, - _i3.DiagnosticLevel? minLevel = _i3.DiagnosticLevel.info}) => - super.toString(); -} - -/// A class which mocks [BuildContext]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockBuildContext extends _i1.Mock implements _i2.BuildContext { - MockBuildContext() { - _i1.throwOnMissingStub(this); - } - - @override - _i2.Widget get widget => (super.noSuchMethod(Invocation.getter(#widget), - returnValue: _FakeWidget_0()) as _i2.Widget); - @override - bool get debugDoingBuild => (super - .noSuchMethod(Invocation.getter(#debugDoingBuild), returnValue: false) - as bool); - @override - _i2.InheritedWidget dependOnInheritedElement(_i2.InheritedElement? ancestor, - {Object? aspect}) => - (super.noSuchMethod( - Invocation.method( - #dependOnInheritedElement, [ancestor], {#aspect: aspect}), - returnValue: _FakeInheritedWidget_1()) as _i2.InheritedWidget); - @override - void visitAncestorElements(bool Function(_i2.Element)? visitor) => - super.noSuchMethod(Invocation.method(#visitAncestorElements, [visitor]), - returnValueForMissingStub: null); - @override - void visitChildElements(_i2.ElementVisitor? visitor) => - super.noSuchMethod(Invocation.method(#visitChildElements, [visitor]), - returnValueForMissingStub: null); - @override - _i3.DiagnosticsNode describeElement(String? name, - {_i3.DiagnosticsTreeStyle? style = - _i3.DiagnosticsTreeStyle.errorProperty}) => - (super.noSuchMethod( - Invocation.method(#describeElement, [name], {#style: style}), - returnValue: _FakeDiagnosticsNode_2()) as _i3.DiagnosticsNode); - @override - _i3.DiagnosticsNode describeWidget(String? name, - {_i3.DiagnosticsTreeStyle? style = - _i3.DiagnosticsTreeStyle.errorProperty}) => - (super.noSuchMethod( - Invocation.method(#describeWidget, [name], {#style: style}), - returnValue: _FakeDiagnosticsNode_2()) as _i3.DiagnosticsNode); - @override - List<_i3.DiagnosticsNode> describeMissingAncestor( - {Type? expectedAncestorType}) => - (super.noSuchMethod( - Invocation.method(#describeMissingAncestor, [], - {#expectedAncestorType: expectedAncestorType}), - returnValue: <_i3.DiagnosticsNode>[]) as List<_i3.DiagnosticsNode>); - @override - _i3.DiagnosticsNode describeOwnershipChain(String? name) => - (super.noSuchMethod(Invocation.method(#describeOwnershipChain, [name]), - returnValue: _FakeDiagnosticsNode_2()) as _i3.DiagnosticsNode); -} From 21df887b4db16e85bd825a306bdecd01a6a94dc5 Mon Sep 17 00:00:00 2001 From: imaN Khoshabi Date: Sat, 12 Feb 2022 00:22:35 +0330 Subject: [PATCH 34/45] Bump version to 0.45.0 --- CHANGELOG.md | 6 +++--- README.md | 2 +- pubspec.yaml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5baae5417..7f63943f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,12 @@ -## newVersion +## 0.45.0 * **BUGFIX** Fix `clipData` implementation in ScatterChart and LineChart, #897. * **BUGFIX** Fix PieChart changing sections issue (we have disabled semantics for pieChart badgeWidgets), #861. * **BUGFIX** Fix LineChart width smaller width or height lower than 40, #869, #857. * **BUGFIX** Allow to show title when axis diff is zero. * **IMPROVEMENT** Improve iteration over axis values logic (it solves some minor problems on showing titles when min, max values are below than 1.0). -* **BREAKING** `LineTouchResponse` response now contains a list of `TouchLineBarSpot` instead of `LineBarSpot`. They are ordered based on their distance to the touch event and also contain that distance. -* **IMPROVEMENT** Added `distanceCalculator` to `LineTouchData` which is used to calculate the distance between spots and touch events * **IMPROVEMENT** Add `baselineX` and `baselineY` property in our axis-based charts, It fixes a problem about `interval` which mentioned in #893 (check [this sample](https://github.com/imaNNeoFighT/fl_chart/blob/hotfix/initial-interval-baseline/repo_files/documentations/line_chart.md#gist---baselinex-baseliney-sample-source-code). +* **IMPROVEMENT** Added `distanceCalculator` to `LineTouchData` which is used to calculate the distance between spots and touch events +* **BREAKING** `LineTouchResponse` response now contains a list of `TouchLineBarSpot` instead of `LineBarSpot`. They are ordered based on their distance to the touch event and also contain that distance. ## 0.41.0 * **BUGFIX** Fix getNearestTouchedSpot. Previously it returned the first occurrence of a spot within the threshold, and not the nearest, #641, #645. diff --git a/README.md b/README.md index a79ee3991..114c1945d 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Thank you all! ```yml dependencies: - fl_chart: ^0.41.0 + fl_chart: ^0.45.0 ``` diff --git a/pubspec.yaml b/pubspec.yaml index e3df403a6..cabc914fa 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: fl_chart description: A powerful Flutter chart library, currently supporting Line Chart, Bar Chart and Pie Chart. -version: 0.41.0 +version: 0.45.0 homepage: https://github.com/imaNNeoFighT/fl_chart environment: From 7d1f83df06d3942f45f3492ad04446c503ed0883 Mon Sep 17 00:00:00 2001 From: imaN Khoshabi Date: Sat, 12 Feb 2022 00:57:24 +0330 Subject: [PATCH 35/45] Update mockito, and build_runner dependencies. --- .github/workflows/publish.yml | 2 +- pubspec.yaml | 4 ++-- test/chart/bar_chart/bar_chart_painter_test.mocks.dart | 3 ++- test/chart/bar_chart/bar_chart_renderer_test.mocks.dart | 3 ++- test/chart/line_chart/line_chart_painter_test.mocks.dart | 3 ++- test/chart/line_chart/line_chart_renderer_test.mocks.dart | 3 ++- test/chart/pie_chart/pie_chart_painter_test.mocks.dart | 3 ++- test/chart/pie_chart/pie_chart_renderer_test.mocks.dart | 5 +++-- test/chart/radar_chart/radar_chart_painter_test.mocks.dart | 3 ++- test/chart/radar_chart/radar_chart_renderer_test.mocks.dart | 3 ++- .../scatter_chart/scatter_chart_painter_test.mocks.dart | 3 ++- .../scatter_chart/scatter_chart_renderer_test.mocks.dart | 6 ++++-- test/utils/canvas_wrapper_test.mocks.dart | 3 ++- 13 files changed, 28 insertions(+), 16 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 330dd3859..018fec898 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,6 +13,6 @@ jobs: - name: Checkout uses: actions/checkout@v1 - name: Publish - uses: sakebook/actions-flutter-pub-publisher@v1.3.1 + uses: sakebook/actions-flutter-pub-publisher@v1.4.0 with: credential: ${{ secrets.CREDENTIAL_JSON }} \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index cabc914fa..583514bb2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,8 +12,8 @@ dependencies: equatable: ^2.0.3 dev_dependencies: - mockito: ^5.0.16 - build_runner: ^2.1.5 + mockito: ^5.1.0 + build_runner: ^2.1.7 flutter_lints: ^1.0.4 flutter_test: sdk: flutter diff --git a/test/chart/bar_chart/bar_chart_painter_test.mocks.dart b/test/chart/bar_chart/bar_chart_painter_test.mocks.dart index 4a1439fce..1e4fd6685 100644 --- a/test/chart/bar_chart/bar_chart_painter_test.mocks.dart +++ b/test/chart/bar_chart/bar_chart_painter_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.0.17 from annotations +// Mocks generated by Mockito 5.1.0 from annotations // in fl_chart/test/chart/bar_chart/bar_chart_painter_test.dart. // Do not manually edit this file. @@ -12,6 +12,7 @@ import 'package:flutter/cupertino.dart' as _i3; import 'package:flutter/foundation.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; +// ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references diff --git a/test/chart/bar_chart/bar_chart_renderer_test.mocks.dart b/test/chart/bar_chart/bar_chart_renderer_test.mocks.dart index ad7b3e84f..4213479c7 100644 --- a/test/chart/bar_chart/bar_chart_renderer_test.mocks.dart +++ b/test/chart/bar_chart/bar_chart_renderer_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.0.17 from annotations +// Mocks generated by Mockito 5.1.0 from annotations // in fl_chart/test/chart/bar_chart/bar_chart_renderer_test.dart. // Do not manually edit this file. @@ -17,6 +17,7 @@ import 'package:flutter/src/rendering/layer.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; import 'package:vector_math/vector_math_64.dart' as _i8; +// ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references diff --git a/test/chart/line_chart/line_chart_painter_test.mocks.dart b/test/chart/line_chart/line_chart_painter_test.mocks.dart index 0efc3bdd6..186d30c18 100644 --- a/test/chart/line_chart/line_chart_painter_test.mocks.dart +++ b/test/chart/line_chart/line_chart_painter_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.0.17 from annotations +// Mocks generated by Mockito 5.1.0 from annotations // in fl_chart/test/chart/line_chart/line_chart_painter_test.dart. // Do not manually edit this file. @@ -15,6 +15,7 @@ import 'package:flutter/cupertino.dart' as _i3; import 'package:flutter/foundation.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; +// ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references diff --git a/test/chart/line_chart/line_chart_renderer_test.mocks.dart b/test/chart/line_chart/line_chart_renderer_test.mocks.dart index 022409349..38f3a62b3 100644 --- a/test/chart/line_chart/line_chart_renderer_test.mocks.dart +++ b/test/chart/line_chart/line_chart_renderer_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.0.17 from annotations +// Mocks generated by Mockito 5.1.0 from annotations // in fl_chart/test/chart/line_chart/line_chart_renderer_test.dart. // Do not manually edit this file. @@ -17,6 +17,7 @@ import 'package:flutter/src/rendering/layer.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; import 'package:vector_math/vector_math_64.dart' as _i8; +// ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references diff --git a/test/chart/pie_chart/pie_chart_painter_test.mocks.dart b/test/chart/pie_chart/pie_chart_painter_test.mocks.dart index bd8e378ea..8f543b021 100644 --- a/test/chart/pie_chart/pie_chart_painter_test.mocks.dart +++ b/test/chart/pie_chart/pie_chart_painter_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.0.17 from annotations +// Mocks generated by Mockito 5.1.0 from annotations // in fl_chart/test/chart/pie_chart/pie_chart_painter_test.dart. // Do not manually edit this file. @@ -12,6 +12,7 @@ import 'package:flutter/cupertino.dart' as _i3; import 'package:flutter/foundation.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; +// ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references diff --git a/test/chart/pie_chart/pie_chart_renderer_test.mocks.dart b/test/chart/pie_chart/pie_chart_renderer_test.mocks.dart index 0d8731ee2..2312fd583 100644 --- a/test/chart/pie_chart/pie_chart_renderer_test.mocks.dart +++ b/test/chart/pie_chart/pie_chart_renderer_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.0.17 from annotations +// Mocks generated by Mockito 5.1.0 from annotations // in fl_chart/test/chart/pie_chart/pie_chart_renderer_test.dart. // Do not manually edit this file. @@ -12,12 +12,13 @@ import 'package:fl_chart/src/chart/base/line.dart' as _i13; import 'package:fl_chart/src/chart/pie_chart/pie_chart_painter.dart' as _i10; import 'package:fl_chart/src/utils/canvas_wrapper.dart' as _i11; import 'package:flutter/foundation.dart' as _i5; +import 'package:flutter/material.dart' as _i6; import 'package:flutter/rendering.dart' as _i3; import 'package:flutter/src/rendering/layer.dart' as _i4; -import 'package:flutter/widgets.dart' as _i6; import 'package:mockito/mockito.dart' as _i1; import 'package:vector_math/vector_math_64.dart' as _i9; +// ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references diff --git a/test/chart/radar_chart/radar_chart_painter_test.mocks.dart b/test/chart/radar_chart/radar_chart_painter_test.mocks.dart index 7f07335f5..71c24b48b 100644 --- a/test/chart/radar_chart/radar_chart_painter_test.mocks.dart +++ b/test/chart/radar_chart/radar_chart_painter_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.0.17 from annotations +// Mocks generated by Mockito 5.1.0 from annotations // in fl_chart/test/chart/radar_chart/radar_chart_painter_test.dart. // Do not manually edit this file. @@ -12,6 +12,7 @@ import 'package:flutter/cupertino.dart' as _i3; import 'package:flutter/foundation.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; +// ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references diff --git a/test/chart/radar_chart/radar_chart_renderer_test.mocks.dart b/test/chart/radar_chart/radar_chart_renderer_test.mocks.dart index 58cb44812..135bad448 100644 --- a/test/chart/radar_chart/radar_chart_renderer_test.mocks.dart +++ b/test/chart/radar_chart/radar_chart_renderer_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.0.17 from annotations +// Mocks generated by Mockito 5.1.0 from annotations // in fl_chart/test/chart/radar_chart/radar_chart_renderer_test.dart. // Do not manually edit this file. @@ -17,6 +17,7 @@ import 'package:flutter/src/rendering/layer.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; import 'package:vector_math/vector_math_64.dart' as _i8; +// ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references diff --git a/test/chart/scatter_chart/scatter_chart_painter_test.mocks.dart b/test/chart/scatter_chart/scatter_chart_painter_test.mocks.dart index 618a1d581..40450808c 100644 --- a/test/chart/scatter_chart/scatter_chart_painter_test.mocks.dart +++ b/test/chart/scatter_chart/scatter_chart_painter_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.0.17 from annotations +// Mocks generated by Mockito 5.1.0 from annotations // in fl_chart/test/chart/scatter_chart/scatter_chart_painter_test.dart. // Do not manually edit this file. @@ -12,6 +12,7 @@ import 'package:flutter/cupertino.dart' as _i3; import 'package:flutter/foundation.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; +// ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references diff --git a/test/chart/scatter_chart/scatter_chart_renderer_test.mocks.dart b/test/chart/scatter_chart/scatter_chart_renderer_test.mocks.dart index 53171706f..d28a5ee2c 100644 --- a/test/chart/scatter_chart/scatter_chart_renderer_test.mocks.dart +++ b/test/chart/scatter_chart/scatter_chart_renderer_test.mocks.dart @@ -1,13 +1,14 @@ -// Mocks generated by Mockito 5.0.17 from annotations +// Mocks generated by Mockito 5.1.0 from annotations // in fl_chart/test/chart/scatter_chart/scatter_chart_renderer_test.dart. // Do not manually edit this file. import 'dart:typed_data' as _i7; import 'dart:ui' as _i2; -import 'package:fl_chart/fl_chart.dart' as _i12; import 'package:fl_chart/src/chart/base/base_chart/base_chart_painter.dart' as _i11; +import 'package:fl_chart/src/chart/scatter_chart/scatter_chart_data.dart' + as _i12; import 'package:fl_chart/src/chart/scatter_chart/scatter_chart_painter.dart' as _i9; import 'package:fl_chart/src/utils/canvas_wrapper.dart' as _i10; @@ -18,6 +19,7 @@ import 'package:flutter/src/rendering/layer.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; import 'package:vector_math/vector_math_64.dart' as _i8; +// ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references diff --git a/test/utils/canvas_wrapper_test.mocks.dart b/test/utils/canvas_wrapper_test.mocks.dart index f2b282213..b87fc32c4 100644 --- a/test/utils/canvas_wrapper_test.mocks.dart +++ b/test/utils/canvas_wrapper_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.0.17 from annotations +// Mocks generated by Mockito 5.1.0 from annotations // in fl_chart/test/utils/canvas_wrapper_test.dart. // Do not manually edit this file. @@ -10,6 +10,7 @@ import 'package:fl_chart/src/utils/utils.dart' as _i6; import 'package:flutter/material.dart' as _i3; import 'package:mockito/mockito.dart' as _i1; +// ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_setters_without_getters // ignore_for_file: comment_references From f50800c68f12d994a996091725e87c6e4d1d9bca Mon Sep 17 00:00:00 2001 From: imaN Khoshabi Date: Sat, 12 Feb 2022 01:11:51 +0330 Subject: [PATCH 36/45] Add .pubignore file to ignore tests, repo_files, example and other things --- .pubignore | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .pubignore diff --git a/.pubignore b/.pubignore new file mode 100644 index 000000000..f24553fed --- /dev/null +++ b/.pubignore @@ -0,0 +1,12 @@ +coverage/ +doc/ +metrics/ +tool/ +website/ +repo_files/ +.codecov.yaml +.github/ +example/ +dart_dependency_validator.yaml +Makefile +test/ From 566277f3bc6a382ddb14bcd5007ab1a359a20b66 Mon Sep 17 00:00:00 2001 From: imaN Khoshabi Date: Sat, 12 Feb 2022 01:18:41 +0330 Subject: [PATCH 37/45] Remove .pubignore :/ --- .pubignore | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 .pubignore diff --git a/.pubignore b/.pubignore deleted file mode 100644 index f24553fed..000000000 --- a/.pubignore +++ /dev/null @@ -1,12 +0,0 @@ -coverage/ -doc/ -metrics/ -tool/ -website/ -repo_files/ -.codecov.yaml -.github/ -example/ -dart_dependency_validator.yaml -Makefile -test/ From f049571ec70e81a4bf749106fdee77e638fcc3a8 Mon Sep 17 00:00:00 2001 From: imaN Khoshabi Date: Sat, 12 Feb 2022 01:22:02 +0330 Subject: [PATCH 38/45] Add `vector_math` in dev_dependencies to solve the `dependency` error of publish pipeline here: https://github.com/imaNNeoFighT/fl_chart/runs/5162430955?check_suite_focus=true --- pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/pubspec.yaml b/pubspec.yaml index 583514bb2..f78c8b062 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,6 +15,7 @@ dev_dependencies: mockito: ^5.1.0 build_runner: ^2.1.7 flutter_lints: ^1.0.4 + vector_math: ^2.1.1 #Added to use in some generated codes of mockito flutter_test: sdk: flutter From e3ea89ff7b7dfd49f01d2b8ed9e963b0f355667d Mon Sep 17 00:00:00 2001 From: Iman Khoshabi Date: Sat, 12 Feb 2022 22:09:11 +0330 Subject: [PATCH 39/45] Revert publish action to 1.3.1 --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 018fec898..330dd3859 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,6 +13,6 @@ jobs: - name: Checkout uses: actions/checkout@v1 - name: Publish - uses: sakebook/actions-flutter-pub-publisher@v1.4.0 + uses: sakebook/actions-flutter-pub-publisher@v1.3.1 with: credential: ${{ secrets.CREDENTIAL_JSON }} \ No newline at end of file From eab937ba617ba27ece9598ad297fff2efb0e364e Mon Sep 17 00:00:00 2001 From: Iman Khoshabi Date: Sat, 12 Feb 2022 23:24:29 +0330 Subject: [PATCH 40/45] Update CHANGELOG.md --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f63943f9..33846b296 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,10 @@ * **BUGFIX** Fix `clipData` implementation in ScatterChart and LineChart, #897. * **BUGFIX** Fix PieChart changing sections issue (we have disabled semantics for pieChart badgeWidgets), #861. * **BUGFIX** Fix LineChart width smaller width or height lower than 40, #869, #857. -* **BUGFIX** Allow to show title when axis diff is zero. +* **BUGFIX** Allow to show title when axis diff is zero, #842, #879. * **IMPROVEMENT** Improve iteration over axis values logic (it solves some minor problems on showing titles when min, max values are below than 1.0). -* **IMPROVEMENT** Add `baselineX` and `baselineY` property in our axis-based charts, It fixes a problem about `interval` which mentioned in #893 (check [this sample](https://github.com/imaNNeoFighT/fl_chart/blob/hotfix/initial-interval-baseline/repo_files/documentations/line_chart.md#gist---baselinex-baseliney-sample-source-code). -* **IMPROVEMENT** Added `distanceCalculator` to `LineTouchData` which is used to calculate the distance between spots and touch events +* **IMPROVEMENT** Add `baselineX` and `baselineY` property in our axis-based charts, It fixes a problem about `interval` which mentioned in #893 (check [this sample](https://github.com/imaNNeoFighT/fl_chart/blob/master/repo_files/documentations/line_chart.md#gist---baselinex-baseliney-sample-source-code). +* **IMPROVEMENT** Added `distanceCalculator` to `LineTouchData` which is used to calculate the distance between spots and touch events, #716, #261, #892 * **BREAKING** `LineTouchResponse` response now contains a list of `TouchLineBarSpot` instead of `LineBarSpot`. They are ordered based on their distance to the touch event and also contain that distance. ## 0.41.0 From 42f89ea62282a5ab1fcfd0cb30f51104f3f0ad6f Mon Sep 17 00:00:00 2001 From: Iman Khoshabi Date: Wed, 16 Feb 2022 21:05:25 +0330 Subject: [PATCH 41/45] Update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 114c1945d..158d3bcc5 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,11 @@ 💥 FL Chart is a highly customizable Flutter chart library that supports Line Chart, Bar Chart, Pie Chart, Scatter Chart, and Radar Chart. 💥 - [![pub package](https://img.shields.io/pub/v/fl_chart.svg)](https://pub.dartlang.org/packages/fl_chart) [![codecov](https://codecov.io/gh/imaNNeoFighT/fl_chart/branch/master/graph/badge.svg?token=XBhsIZBbZG)](https://codecov.io/gh/imaNNeoFighT/fl_chart) Awesome Flutter -[![APK](https://img.shields.io/badge/APK-Demo-brightgreen.svg)](https://github.com/imaNNeoFighT/fl_chart/raw/master/repo_files/fl_chart_samples_0.3.0.apk) +GitHub Repo stars +GitHub contributors +GitHub closed issues Buy Me A Coffee donate button From a3e2e395d9252511b73d6d19b310a210004e10fc Mon Sep 17 00:00:00 2001 From: Iman Khoshabi Date: Fri, 18 Feb 2022 12:42:33 +0330 Subject: [PATCH 42/45] Set interval 1 for line_chart_sample2 grid lines --- example/lib/line_chart/samples/line_chart_sample2.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/example/lib/line_chart/samples/line_chart_sample2.dart b/example/lib/line_chart/samples/line_chart_sample2.dart index d4040b237..c3d206a0c 100644 --- a/example/lib/line_chart/samples/line_chart_sample2.dart +++ b/example/lib/line_chart/samples/line_chart_sample2.dart @@ -64,6 +64,8 @@ class _LineChartSample2State extends State { gridData: FlGridData( show: true, drawVerticalLine: true, + horizontalInterval: 1, + verticalInterval: 1, getDrawingHorizontalLine: (value) { return FlLine( color: const Color(0xff37434d), @@ -166,6 +168,8 @@ class _LineChartSample2State extends State { gridData: FlGridData( show: true, drawHorizontalLine: true, + verticalInterval: 1, + horizontalInterval: 1, getDrawingVerticalLine: (value) { return FlLine( color: const Color(0xff37434d), From cc3462a2614016917e205bc8891e0b53a0c27dd5 Mon Sep 17 00:00:00 2001 From: Iman Khoshabi Date: Fri, 18 Feb 2022 12:51:44 +0330 Subject: [PATCH 43/45] Set interval 1 for line_chart_sample5 bottom titles --- example/lib/line_chart/samples/line_chart_sample5.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/example/lib/line_chart/samples/line_chart_sample5.dart b/example/lib/line_chart/samples/line_chart_sample5.dart index e318a8e39..284cf52fe 100644 --- a/example/lib/line_chart/samples/line_chart_sample5.dart +++ b/example/lib/line_chart/samples/line_chart_sample5.dart @@ -105,6 +105,7 @@ class LineChartSample5 extends StatelessWidget { showTitles: false, ), bottomTitles: SideTitles( + interval: 1, showTitles: true, getTitles: (val) { switch (val.toInt()) { From 94e186c5a28d0ca031554eb332e3a359873507a8 Mon Sep 17 00:00:00 2001 From: Iman Khoshabi Date: Thu, 24 Feb 2022 00:08:09 +0330 Subject: [PATCH 44/45] Fix `FlSpot.nullSpot` at the first of list bug, #912. --- CHANGELOG.md | 3 +++ .../chart/line_chart/line_chart_helper.dart | 20 +++++++++----- .../line_chart/line_chart_helper_test.dart | 26 +++++++++++++++++++ 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33846b296..be00ee581 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## newVersion +* **BUGFIX** Fix `FlSpot.nullSpot` at the first of list bug, #912. + ## 0.45.0 * **BUGFIX** Fix `clipData` implementation in ScatterChart and LineChart, #897. * **BUGFIX** Fix PieChart changing sections issue (we have disabled semantics for pieChart badgeWidgets), #861. diff --git a/lib/src/chart/line_chart/line_chart_helper.dart b/lib/src/chart/line_chart/line_chart_helper.dart index cde9fba40..52501f5e2 100644 --- a/lib/src/chart/line_chart/line_chart_helper.dart +++ b/lib/src/chart/line_chart/line_chart_helper.dart @@ -1,8 +1,7 @@ import 'package:equatable/equatable.dart'; +import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/src/utils/list_wrapper.dart'; -import 'line_chart_data.dart'; - /// Contains anything that helps LineChart works class LineChartHelper { /// Contains List of cached results, base on [List] @@ -32,10 +31,19 @@ class LineChartHelper { return LineChartMinMaxAxisValues(0, 0, 0, 0); } - var minX = lineBarData.spots[0].x; - var maxX = lineBarData.spots[0].x; - var minY = lineBarData.spots[0].y; - var maxY = lineBarData.spots[0].y; + final FlSpot firstValidSpot; + try { + firstValidSpot = + lineBarData.spots.firstWhere((element) => element != FlSpot.nullSpot); + } catch (e) { + // There is no valid spot + return LineChartMinMaxAxisValues(0, 0, 0, 0); + } + + var minX = firstValidSpot.x; + var maxX = firstValidSpot.x; + var minY = firstValidSpot.y; + var maxY = firstValidSpot.y; for (var i = 0; i < lineBarsData.length; i++) { final barData = lineBarsData[i]; diff --git a/test/chart/line_chart/line_chart_helper_test.dart b/test/chart/line_chart/line_chart_helper_test.dart index bfcab8bad..eb7f2baed 100644 --- a/test/chart/line_chart/line_chart_helper_test.dart +++ b/test/chart/line_chart/line_chart_helper_test.dart @@ -56,5 +56,31 @@ void main() { final result2 = LineChartHelper.calculateMaxAxisValues(lineBarsClone); expect(result1, result2); }); + + test('Test null spot 1', () { + final lineBars = [ + LineChartBarData(spots: [ + FlSpot.nullSpot, + FlSpot.nullSpot, + FlSpot.nullSpot, + FlSpot.nullSpot, + ]) + ]; + final result1 = LineChartHelper.calculateMaxAxisValues(lineBars); + expect(result1, LineChartMinMaxAxisValues(0, 0, 0, 0)); + }); + + test('Test null spot 2', () { + final lineBars = [ + LineChartBarData(spots: [ + FlSpot.nullSpot, + const FlSpot(-1, 5), + FlSpot.nullSpot, + const FlSpot(4, -3), + ]) + ]; + final result1 = LineChartHelper.calculateMaxAxisValues(lineBars); + expect(result1, LineChartMinMaxAxisValues(-1, 4, -3, 5)); + }); }); } From f5665168739b66d09483667f79c9f0a3186ca574 Mon Sep 17 00:00:00 2001 From: Filip Pankau Date: Wed, 23 Feb 2022 22:14:23 +0100 Subject: [PATCH 45/45] Added Tooltip border color and width support --- CHANGELOG.md | 3 ++- lib/src/chart/bar_chart/bar_chart_data.dart | 12 ++++++++++++ lib/src/chart/bar_chart/bar_chart_painter.dart | 13 ++++++++++++- lib/src/chart/line_chart/line_chart_data.dart | 12 ++++++++++++ lib/src/chart/line_chart/line_chart_painter.dart | 12 ++++++++++++ .../chart/scatter_chart/scatter_chart_data.dart | 16 ++++++++++++++++ .../scatter_chart/scatter_chart_painter.dart | 13 ++++++++++++- 7 files changed, 78 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be00ee581..ca578c214 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## newVersion * **BUGFIX** Fix `FlSpot.nullSpot` at the first of list bug, #912. +* **FEATURE** Add `tooltipBorderColor` and `tooltipBorderWidth` property in the [LineTouchTooltipData], [BarTouchTooltipData], [ScatterTouchTooltipData], #692. ## 0.45.0 * **BUGFIX** Fix `clipData` implementation in ScatterChart and LineChart, #897. @@ -384,4 +385,4 @@ LineTouchData( * restricted to access private classes of the library -## 0.0.1 - Released on (2019 June 4) \ No newline at end of file +## 0.0.1 - Released on (2019 June 4) diff --git a/lib/src/chart/bar_chart/bar_chart_data.dart b/lib/src/chart/bar_chart/bar_chart_data.dart index 721e3f0bd..e36dca3c7 100644 --- a/lib/src/chart/bar_chart/bar_chart_data.dart +++ b/lib/src/chart/bar_chart/bar_chart_data.dart @@ -642,6 +642,12 @@ class BarTouchTooltipData with EquatableMixin { /// The tooltip background color. final Color tooltipBgColor; + /// The tooltip border color. + final Color tooltipBorderColor; + + /// The tooltip border stroke width. + final double tooltipBorderWidth; + /// Sets a rounded radius for the tooltip. final double tooltipRoundedRadius; @@ -684,6 +690,8 @@ class BarTouchTooltipData with EquatableMixin { /// also you can set [fitInsideVertically] true to force it to shift inside the chart vertically. BarTouchTooltipData({ Color? tooltipBgColor, + Color? tooltipBorderColor, + double? tooltipBorderWidth, double? tooltipRoundedRadius, EdgeInsets? tooltipPadding, double? tooltipMargin, @@ -694,6 +702,8 @@ class BarTouchTooltipData with EquatableMixin { TooltipDirection? direction, double? rotateAngle, }) : tooltipBgColor = tooltipBgColor ?? Colors.blueGrey.darken(15), + tooltipBorderColor = tooltipBorderColor ?? Colors.black, + tooltipBorderWidth = tooltipBorderWidth ?? 0.0, tooltipRoundedRadius = tooltipRoundedRadius ?? 4, tooltipPadding = tooltipPadding ?? const EdgeInsets.symmetric(horizontal: 16, vertical: 8), @@ -710,6 +720,8 @@ class BarTouchTooltipData with EquatableMixin { @override List get props => [ tooltipBgColor, + tooltipBorderColor, + tooltipBorderWidth, tooltipRoundedRadius, tooltipPadding, tooltipMargin, diff --git a/lib/src/chart/bar_chart/bar_chart_painter.dart b/lib/src/chart/bar_chart/bar_chart_painter.dart index 6d5c90c4a..bddc7a486 100644 --- a/lib/src/chart/bar_chart/bar_chart_painter.dart +++ b/lib/src/chart/bar_chart/bar_chart_painter.dart @@ -14,7 +14,7 @@ import 'bar_chart_extensions.dart'; /// Paints [BarChartData] in the canvas, it can be used in a [CustomPainter] class BarChartPainter extends AxisChartPainter { - late Paint _barPaint, _barStrokePaint, _bgTouchTooltipPaint; + late Paint _barPaint, _barStrokePaint, _bgTouchTooltipPaint, _bgTouchTooltipBorderPaint; List? _groupBarsPosition; @@ -33,6 +33,10 @@ class BarChartPainter extends AxisChartPainter { _bgTouchTooltipPaint = Paint() ..style = PaintingStyle.fill ..color = Colors.white; + + _bgTouchTooltipBorderPaint = Paint() + ..style = PaintingStyle.stroke + ..color = Colors.white; } /// Paints [BarChartData] into the provided canvas. @@ -668,6 +672,10 @@ class BarChartPainter extends AxisChartPainter { bottomLeft: radius, bottomRight: radius); _bgTouchTooltipPaint.color = tooltipData.tooltipBgColor; + if (tooltipData.tooltipBorderWidth > 0) { + _bgTouchTooltipBorderPaint.color = tooltipData.tooltipBorderColor; + _bgTouchTooltipBorderPaint.strokeWidth = tooltipData.tooltipBorderWidth; + } final rotateAngle = tooltipData.rotateAngle; final rectRotationOffset = @@ -690,6 +698,9 @@ class BarChartPainter extends AxisChartPainter { angle: rotateAngle, drawCallback: () { canvasWrapper.drawRRect(roundedRect, _bgTouchTooltipPaint); + if (tooltipData.tooltipBorderWidth > 0) { + canvasWrapper.drawRRect(roundedRect, _bgTouchTooltipBorderPaint); + } canvasWrapper.drawText(tp, drawOffset); }, ); diff --git a/lib/src/chart/line_chart/line_chart_data.dart b/lib/src/chart/line_chart/line_chart_data.dart index 3e1bcdddc..3afa32631 100644 --- a/lib/src/chart/line_chart/line_chart_data.dart +++ b/lib/src/chart/line_chart/line_chart_data.dart @@ -1569,6 +1569,12 @@ class LineTouchTooltipData with EquatableMixin { /// The tooltip background color. final Color tooltipBgColor; + /// The tooltip border color. + final Color tooltipBorderColor; + + /// The tooltip border stroke width. + final double tooltipBorderWidth; + /// Sets a rounded radius for the tooltip. final double tooltipRoundedRadius; @@ -1611,6 +1617,8 @@ class LineTouchTooltipData with EquatableMixin { /// also you can set [fitInsideVertically] true to force it to shift inside the chart vertically. LineTouchTooltipData({ Color? tooltipBgColor, + Color? tooltipBorderColor, + double? tooltipBorderWidth, double? tooltipRoundedRadius, EdgeInsets? tooltipPadding, double? tooltipMargin, @@ -1621,6 +1629,8 @@ class LineTouchTooltipData with EquatableMixin { bool? showOnTopOfTheChartBoxArea, double? rotateAngle, }) : tooltipBgColor = tooltipBgColor ?? Colors.blueGrey.darken(15), + tooltipBorderColor = tooltipBorderColor ?? Colors.black, + tooltipBorderWidth = tooltipBorderWidth ?? 0.0, tooltipRoundedRadius = tooltipRoundedRadius ?? 4, tooltipPadding = tooltipPadding ?? const EdgeInsets.symmetric(horizontal: 16, vertical: 8), @@ -1637,6 +1647,8 @@ class LineTouchTooltipData with EquatableMixin { @override List get props => [ tooltipBgColor, + tooltipBorderColor, + tooltipBorderWidth, tooltipRoundedRadius, tooltipPadding, tooltipMargin, diff --git a/lib/src/chart/line_chart/line_chart_painter.dart b/lib/src/chart/line_chart/line_chart_painter.dart index c43e0c565..72495019b 100644 --- a/lib/src/chart/line_chart/line_chart_painter.dart +++ b/lib/src/chart/line_chart/line_chart_painter.dart @@ -23,6 +23,7 @@ class LineChartPainter extends AxisChartPainter { _extraLinesPaint, _touchLinePaint, _bgTouchTooltipPaint, + _bgTouchTooltipBorderPaint, _imagePaint; /// Paints [data] into canvas, it is the animating [LineChartData], @@ -55,6 +56,10 @@ class LineChartPainter extends AxisChartPainter { ..style = PaintingStyle.fill ..color = Colors.white; + _bgTouchTooltipBorderPaint = Paint() + ..style = PaintingStyle.stroke + ..color = Colors.white; + _imagePaint = Paint(); } @@ -1357,6 +1362,10 @@ class LineChartPainter extends AxisChartPainter { bottomLeft: radius, bottomRight: radius); _bgTouchTooltipPaint.color = tooltipData.tooltipBgColor; + if (tooltipData.tooltipBorderWidth > 0) { + _bgTouchTooltipBorderPaint.color = tooltipData.tooltipBorderColor; + _bgTouchTooltipBorderPaint.strokeWidth = tooltipData.tooltipBorderWidth; + } final rotateAngle = tooltipData.rotateAngle; final rectRotationOffset = @@ -1373,6 +1382,9 @@ class LineChartPainter extends AxisChartPainter { angle: rotateAngle, drawCallback: () { canvasWrapper.drawRRect(roundedRect, _bgTouchTooltipPaint); + if (tooltipData.tooltipBorderWidth > 0) { + canvasWrapper.drawRRect(roundedRect, _bgTouchTooltipBorderPaint); + } }, ); diff --git a/lib/src/chart/scatter_chart/scatter_chart_data.dart b/lib/src/chart/scatter_chart/scatter_chart_data.dart index 1a62b90d4..096424593 100644 --- a/lib/src/chart/scatter_chart/scatter_chart_data.dart +++ b/lib/src/chart/scatter_chart/scatter_chart_data.dart @@ -379,6 +379,12 @@ class ScatterTouchTooltipData with EquatableMixin { /// The tooltip background color. final Color tooltipBgColor; + /// The tooltip border color. + final Color tooltipBorderColor; + + /// The tooltip border stroke width. + final double tooltipBorderWidth; + /// Sets a rounded radius for the tooltip. final double tooltipRoundedRadius; @@ -414,6 +420,8 @@ class ScatterTouchTooltipData with EquatableMixin { /// also you can set [fitInsideVertically] true to force it to shift inside the chart vertically. ScatterTouchTooltipData({ Color? tooltipBgColor, + Color? tooltipBorderColor, + double? tooltipBorderWidth, double? tooltipRoundedRadius, EdgeInsets? tooltipPadding, double? maxContentWidth, @@ -422,6 +430,8 @@ class ScatterTouchTooltipData with EquatableMixin { bool? fitInsideVertically, double? rotateAngle, }) : tooltipBgColor = tooltipBgColor ?? Colors.blueGrey.darken(15), + tooltipBorderColor = tooltipBorderColor ?? Colors.black, + tooltipBorderWidth = tooltipBorderWidth ?? 0.0, tooltipRoundedRadius = tooltipRoundedRadius ?? 4, tooltipPadding = tooltipPadding ?? const EdgeInsets.symmetric(horizontal: 16, vertical: 8), @@ -436,6 +446,8 @@ class ScatterTouchTooltipData with EquatableMixin { @override List get props => [ tooltipBgColor, + tooltipBorderColor, + tooltipBorderWidth, tooltipRoundedRadius, tooltipPadding, maxContentWidth, @@ -449,6 +461,8 @@ class ScatterTouchTooltipData with EquatableMixin { /// and replaces provided values. ScatterTouchTooltipData copyWith({ Color? tooltipBgColor, + Color? tooltipBorderColor, + double? tooltipBorderWidth, double? tooltipRoundedRadius, EdgeInsets? tooltipPadding, double? maxContentWidth, @@ -459,6 +473,8 @@ class ScatterTouchTooltipData with EquatableMixin { }) { return ScatterTouchTooltipData( tooltipBgColor: tooltipBgColor ?? this.tooltipBgColor, + tooltipBorderColor: tooltipBorderColor ?? this.tooltipBorderColor, + tooltipBorderWidth: tooltipBorderWidth ?? this.tooltipBorderWidth, tooltipRoundedRadius: tooltipRoundedRadius ?? this.tooltipRoundedRadius, tooltipPadding: tooltipPadding ?? this.tooltipPadding, maxContentWidth: maxContentWidth ?? this.maxContentWidth, diff --git a/lib/src/chart/scatter_chart/scatter_chart_painter.dart b/lib/src/chart/scatter_chart/scatter_chart_painter.dart index 89fd9a16f..22108c837 100644 --- a/lib/src/chart/scatter_chart/scatter_chart_painter.dart +++ b/lib/src/chart/scatter_chart/scatter_chart_painter.dart @@ -11,7 +11,7 @@ import 'scatter_chart_data.dart'; /// Paints [ScatterChartData] in the canvas, it can be used in a [CustomPainter] class ScatterChartPainter extends AxisChartPainter { /// [_spotsPaint] is responsible to draw scatter spots - late Paint _spotsPaint, _bgTouchTooltipPaint; + late Paint _spotsPaint, _bgTouchTooltipPaint, _bgTouchTooltipBorderPaint; /// Paints [data] into canvas, it is the animating [ScatterChartData], /// [targetData] is the animation's target and remains the same @@ -27,6 +27,10 @@ class ScatterChartPainter extends AxisChartPainter { _bgTouchTooltipPaint = Paint() ..style = PaintingStyle.fill ..color = Colors.white; + + _bgTouchTooltipBorderPaint = Paint() + ..style = PaintingStyle.stroke + ..color = Colors.white; } /// Paints [ScatterChartData] into the provided canvas. @@ -420,6 +424,10 @@ class ScatterChartPainter extends AxisChartPainter { bottomLeft: radius, bottomRight: radius); _bgTouchTooltipPaint.color = tooltipData.tooltipBgColor; + if (tooltipData.tooltipBorderWidth > 0) { + _bgTouchTooltipBorderPaint.color = tooltipData.tooltipBorderColor; + _bgTouchTooltipBorderPaint.strokeWidth = tooltipData.tooltipBorderWidth; + } final rotateAngle = tooltipData.rotateAngle; final rectRotationOffset = @@ -443,6 +451,9 @@ class ScatterChartPainter extends AxisChartPainter { angle: rotateAngle, drawCallback: () { canvasWrapper.drawRRect(roundedRect, _bgTouchTooltipPaint); + if (tooltipData.tooltipBorderWidth > 0) { + canvasWrapper.drawRRect(roundedRect, _bgTouchTooltipBorderPaint); + } canvasWrapper.drawText(drawingTextPainter, drawOffset); }, );