From 1719ff72f4c21927ce24d304907dd34a8f3ce532 Mon Sep 17 00:00:00 2001 From: PietroChitto Date: Thu, 18 Jan 2024 17:52:15 +0100 Subject: [PATCH 1/3] fix: * improved linechart tooltip * show message when there are 0 or 1 point * tooltips don't exit the screen anymore * added bar indicator * add possibility to set min y --- lib/custom_widgets/account_modal.dart | 4 +- lib/custom_widgets/line_chart.dart | 260 ++++++++++++++--------- lib/model/bank_account.dart | 2 +- lib/pages/account_page/account_page.dart | 3 +- lib/pages/statistics_page.dart | 2 +- 5 files changed, 168 insertions(+), 103 deletions(-) diff --git a/lib/custom_widgets/account_modal.dart b/lib/custom_widgets/account_modal.dart index b3381816..0fdac034 100644 --- a/lib/custom_widgets/account_modal.dart +++ b/lib/custom_widgets/account_modal.dart @@ -56,7 +56,7 @@ class AccountDialog extends StatelessWidget with Functions { const Padding( padding: EdgeInsets.all(8.0), ), - const LineChartWidget( + LineChartWidget( line1Data: [ FlSpot(0, 3), FlSpot(1, 1.3), @@ -80,7 +80,7 @@ class AccountDialog extends StatelessWidget with Functions { line2Data: [], colorLine2Data: Color(0xffffffff), colorBackground: Color(0xff356CA3), - maxDays: 30.0, + period: Period.month, ), const Padding( padding: EdgeInsets.only(bottom: 14.0), diff --git a/lib/custom_widgets/line_chart.dart b/lib/custom_widgets/line_chart.dart index 091918e6..f51e8582 100644 --- a/lib/custom_widgets/line_chart.dart +++ b/lib/custom_widgets/line_chart.dart @@ -1,5 +1,13 @@ +import 'dart:math'; + import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +enum Period { + month, + year +} //This class can be used when we need to draw a line chart with one or two lines class LineChartWidget extends StatefulWidget { @@ -10,21 +18,38 @@ class LineChartWidget extends StatefulWidget { final Color colorLine2Data; //Contains the number of days of the month - final double maxDays; + //final double maxDays; + // Used to decide the bottom labal + final Period period; + final int currentMonthDays = DateUtils.getDaysInMonth(DateTime.now().year, DateTime.now().month); + final int nXLabel = 10; + final double minY; + final Color colorBackground; - const LineChartWidget({ + LineChartWidget({ super.key, required this.line1Data, required this.colorLine1Data, required this.line2Data, required this.colorLine2Data, required this.colorBackground, - this.maxDays = 31, - }); + this.period = Period.month, + int nXLabel = 10, + double? minY, + }) : minY = minY ?? calculateMinY(line1Data, line2Data); + + static double calculateMinY(List line1Data, List line2Data){ + if (line1Data.isEmpty && line2Data.isEmpty) { + return 0; + } + + return [...line1Data, ...line2Data].map((e) => e.y).reduce(min); + } @override State createState() => _LineChartSample2State(); + } class _LineChartSample2State extends State { @@ -45,8 +70,28 @@ class _LineChartSample2State extends State { ), child: Padding( padding: const EdgeInsets.only(top: 24), - child: LineChart( - mainData(), + child: Builder( + builder: (context) { + if(widget.line1Data.length < 2 && widget.line2Data.length < 2){ + return Center( + child: RichText( + textAlign: TextAlign.center, + text: TextSpan( + style: DefaultTextStyle.of(context).style, + children: [ + TextSpan( + text: "We are sorry but there are not\nenough data to make the graph", + style: TextStyle(color: Theme.of(context).hintColor), + ) + ] + ) + ), + ); } + + return LineChart( + mainData(), + ); + }, ), ), ), @@ -62,24 +107,36 @@ class _LineChartSample2State extends State { fontSize: 8, ); Widget text; - switch (widget.maxDays) { - case 12: + switch (widget.period) { + case Period.year: switch (value.toInt()) { - case 0: - text = Text('Jan', style: style); + case 1: + text = Text('Feb', style: style); break; case 2: text = Text('Mar', style: style); break; + case 3: + text = Text('Apr', style: style); + break; case 4: text = Text('May', style: style); break; + case 5: + text = Text('Jun', style: style); + break; case 6: text = Text('Jul', style: style); break; + case 7: + text = Text('Aug', style: style); + break; case 8: text = Text('Sep', style: style); break; + case 9: + text = Text('Oct', style: style); + break; case 10: text = Text('Nov', style: style); break; @@ -88,94 +145,14 @@ class _LineChartSample2State extends State { break; } break; - case 28: - switch (value.toInt()) { - case 6: - text = Text('7', style: style); - break; - case 12: - text = Text('13', style: style); - break; - case 17: - text = Text('18', style: style); - break; - case 23: - text = Text('24', style: style); - break; - case 27: - text = Text('28', style: style); - break; - default: - text = Text('', style: style); - break; - } - break; - case 29: - switch (value.toInt()) { - case 6: - text = Text('7', style: style); - break; - case 12: - text = Text('13', style: style); - break; - case 17: - text = Text('18', style: style); - break; - case 23: - text = Text('24', style: style); - break; - case 27: - text = Text('28', style: style); - break; - default: - text = Text('', style: style); - break; + case Period.month: + int step = (widget.currentMonthDays / widget.nXLabel).round(); + if(value.toInt() % step == 1 && value.toInt() != widget.currentMonthDays){ + text = Text((value + 1).toStringAsFixed(0), style: style,); + }else{ + text = Text('', style: style); } - break; - case 30: - switch (value.toInt()) { - case 5: - text = Text('6', style: style); - break; - case 11: - text = Text('12', style: style); - break; - case 17: - text = Text('18', style: style); - break; - case 23: - text = Text('24', style: style); - break; - case 29: - text = Text('30', style: style); - break; - default: - text = Text('', style: style); - break; - } - break; - case 31: - switch (value.toInt()) { - case 3: - text = Text('4', style: style); - break; - case 10: - text = Text('11', style: style); - break; - case 17: - text = Text('18', style: style); - break; - case 24: - text = Text('25', style: style); - break; - case 30: - text = Text('31', style: style); - break; - default: - text = Text('', style: style); - break; - } - break; + default: text = Text('', style: style); break; @@ -208,13 +185,100 @@ class _LineChartSample2State extends State { ), ), gridData: const FlGridData(show: false), + lineTouchData: LineTouchData( + getTouchedSpotIndicator: + (LineChartBarData barData, List spotIndexes) { + bool allSameX = spotIndexes.toSet().length == 1; + + if(!allSameX){ + return []; + } + return spotIndexes.map((spotIndex) { + return TouchedSpotIndicatorData( + const FlLine( + color: Colors.blueGrey, + strokeWidth: 2, + ), + FlDotData( + getDotPainter: (spot, percent, barData, index) { + return FlDotCirclePainter( + radius: 2, + color: Colors.grey, + strokeWidth: 2, + strokeColor: Colors.blueGrey + ); + }, + ), + ); + }).toList(); + }, + touchTooltipData: LineTouchTooltipData( + fitInsideHorizontally: true, + getTooltipItems: (List touchedBarSpots) { + bool allSameX = touchedBarSpots.map((e) => e.x).toSet().length == 1; + + if(!allSameX || touchedBarSpots.isEmpty){ + return []; + } + + double x = touchedBarSpots[0].x; + DateTime date = widget.period == Period.month ? + DateTime(DateTime.now().year, DateTime.now().month, (x+1).toInt()) : + DateTime(DateTime.now().year, (x+1).toInt(), 1); + String dateFormat = widget.period == Period.month ? + DateFormat(DateFormat.ABBR_MONTH_DAY).format(date) : + DateFormat(DateFormat.ABBR_MONTH).format(date); + + LineTooltipItem first = LineTooltipItem( + '$dateFormat \n\n', + TextStyle( + color: Theme.of(context).colorScheme.onBackground, + fontWeight: FontWeight.bold, + ), + children: [ + TextSpan( + text: touchedBarSpots[0].y.toString(), + style: TextStyle( + color: widget.colorLine2Data, + fontWeight: FontWeight.w900, + ), + ), + ], + ); + + var others = touchedBarSpots.sublist(1).map((barSpot) { + final flSpot = barSpot; + + return LineTooltipItem( + '', + const TextStyle( + fontWeight: FontWeight.bold, + ), + children: [ + TextSpan( + text: flSpot.y.toString(), + style: TextStyle( + color: widget.colorLine1Data, + fontWeight: FontWeight.w900, + ), + ), + ], + ); + }).toList(); + + return [first, ...others]; + }, + ), + ), + borderData: FlBorderData( border: const Border( bottom: BorderSide(color: Colors.grey, width: 1.0, style: BorderStyle.solid), ), ), minX: 0, - maxX: widget.maxDays - 1, + maxX: widget.period == Period.year ? 11 : widget.currentMonthDays - 1, // if year display 12 month, if mont display the number of days in the month + minY: widget.minY, lineBarsData: [ LineChartBarData( spots: widget.line1Data, diff --git a/lib/model/bank_account.dart b/lib/model/bank_account.dart index ba375b96..69f5088d 100644 --- a/lib/model/bank_account.dart +++ b/lib/model/bank_account.dart @@ -147,7 +147,7 @@ class BankAccountMethods extends SossoldiDatabase { final db = await database; final orderByASC = '${BankAccountFields.createdAt} ASC'; - final where = '${BankAccountFields.active} = 1 AND ${TransactionFields.recurring} = 0'; + final where = '${BankAccountFields.active} = 1 AND (t.${TransactionFields.recurring} = 0 OR t.${TransactionFields.recurring} is NULL)'; final result = await db.rawQuery(''' SELECT b.*, (b.${BankAccountFields.startingValue} + diff --git a/lib/pages/account_page/account_page.dart b/lib/pages/account_page/account_page.dart index 041cdee9..9aa26106 100644 --- a/lib/pages/account_page/account_page.dart +++ b/lib/pages/account_page/account_page.dart @@ -52,7 +52,8 @@ class _AccountPage extends ConsumerState with Functions { line2Data: const [], colorLine2Data: const Color(0xffffffff), colorBackground: blue5, - maxDays: DateUtils.getDaysInMonth(DateTime.now().year, DateTime.now().month).toDouble(), + period: Period.month, + minY: 0, ), ), ], diff --git a/lib/pages/statistics_page.dart b/lib/pages/statistics_page.dart index cdd814a0..6aa291d0 100644 --- a/lib/pages/statistics_page.dart +++ b/lib/pages/statistics_page.dart @@ -106,7 +106,7 @@ class _StatsPageState extends ConsumerState with Functions { line2Data: [], colorLine2Data: Color(0xffB9BABC), colorBackground: Color(0xffF1F5F9), - maxDays: 12.0, + period: Period.year, ), ); }, From 3b83bf40918589cc11075e37dc12df9808f951b6 Mon Sep 17 00:00:00 2001 From: PietroChitto Date: Mon, 22 Jan 2024 09:06:37 +0100 Subject: [PATCH 2/3] fix typo and removed useless commented code --- lib/custom_widgets/line_chart.dart | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/custom_widgets/line_chart.dart b/lib/custom_widgets/line_chart.dart index f51e8582..970b55ce 100644 --- a/lib/custom_widgets/line_chart.dart +++ b/lib/custom_widgets/line_chart.dart @@ -17,10 +17,7 @@ class LineChartWidget extends StatefulWidget { final List line2Data; //this should be a list of Flspot(x,y), if you only need one just put an empty list final Color colorLine2Data; - //Contains the number of days of the month - //final double maxDays; - - // Used to decide the bottom labal + // Used to decide the bottom label final Period period; final int currentMonthDays = DateUtils.getDaysInMonth(DateTime.now().year, DateTime.now().month); final int nXLabel = 10; From 29cbff05d4757a9534cca083e81d1023ac2669a8 Mon Sep 17 00:00:00 2001 From: PietroChitto Date: Mon, 29 Jan 2024 15:00:15 +0100 Subject: [PATCH 3/3] fix line chart test --- test/widget/line_chart_test.dart | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/test/widget/line_chart_test.dart b/test/widget/line_chart_test.dart index 5d4baf9e..89dbed04 100644 --- a/test/widget/line_chart_test.dart +++ b/test/widget/line_chart_test.dart @@ -68,16 +68,22 @@ void main() { ], colorLine2Data: const Color(0xffffffff), colorBackground: const Color(0xff356CA3), - maxDays: 31.0, + period: Period.month, ), ), ) ); - expect(find.text('4'), findsOneWidget); + expect(find.text('2'), findsOneWidget); + expect(find.text('5'), findsOneWidget); + expect(find.text('8'), findsOneWidget); expect(find.text('11'), findsOneWidget); - expect(find.text('18'), findsOneWidget); - expect(find.text('25'), findsOneWidget); + expect(find.text('14'), findsOneWidget); + expect(find.text('17'), findsOneWidget); + expect(find.text('20'), findsOneWidget); + expect(find.text('23'), findsOneWidget); + expect(find.text('26'), findsOneWidget); + expect(find.text('29'), findsOneWidget); } );