feat(graphs): render income graph next to expense graph

#8
This commit is contained in:
Matyáš Caras 2023-11-22 15:09:05 +01:00
parent d889611e19
commit 23480d80d2
Signed by untrusted user who does not match committer: hernik
GPG key ID: 2A3175F98820C5C6
6 changed files with 92 additions and 39 deletions

View file

@ -2,6 +2,7 @@
"conventionalCommits.scopes": [
"ocr",
"ui",
"translations"
"translations",
"graphs"
]
}

View file

@ -59,8 +59,8 @@
"setupStartingBalance":"Počáteční zůstatek",
"graphs":"Grafy",
"createTestData":"Vytvořit vzorková data",
"spendingStats":"Statistiky utrácení",
"yearly":"Roční",
"monthly":"Měsíční"
"monthly":"Měsíční",
"expenses":"Výdaje"
}

View file

@ -95,7 +95,7 @@
"setupStartingBalance":"Starting balance",
"graphs":"Graphs",
"createTestData":"Create test data",
"spendingStats":"Spending statistics",
"yearly":"Yearly",
"monthly":"Monthly"
"monthly":"Monthly",
"expenses":"Expenses"
}

View file

@ -1,35 +1,55 @@
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:prasule/main.dart';
/// Monthly/Yearly expenses [LineChart]
/// Monthly/Yearly expense/income [LineChart]
class ExpensesChart extends StatelessWidget {
const ExpensesChart(
{super.key,
required this.date,
required this.locale,
this.data = const [],
this.expenseData = const [],
this.incomeData = const [],
this.yearly = false});
final bool yearly;
final DateTime date;
final String locale;
final List<double> data;
List<double> get dataSorted {
var list = List<double>.from(data);
final List<double> expenseData;
List<double> get expenseDataSorted {
var list = List<double>.from(expenseData);
list.sort((a, b) => a.compareTo(b));
return list;
}
final List<double> incomeData;
List<double> get incomeDataSorted {
var list = List<double>.from(incomeData);
list.sort((a, b) => a.compareTo(b));
return list;
}
double get maxY {
if (incomeData.isEmpty) return expenseDataSorted.last;
if (expenseData.isEmpty) return incomeDataSorted.last;
if (expenseDataSorted.last > incomeDataSorted.last) {
return expenseDataSorted.last;
} else {
return incomeDataSorted.last;
}
}
@override
Widget build(BuildContext context) {
return LineChart(
LineChartData(
maxX: (yearly) ? 12 : DateTime(date.year, date.month, 0).day.toDouble(),
maxY: dataSorted.last,
maxY: maxY,
minX: 1,
minY: 0,
backgroundColor: Theme.of(context).colorScheme.background,
lineBarsData: [
if (incomeData.isNotEmpty)
LineChartBarData(
isCurved: true,
barWidth: 8,
@ -39,7 +59,20 @@ class ExpensesChart extends StatelessWidget {
color: Theme.of(context).colorScheme.primary,
spots: List.generate(
(yearly) ? 12 : DateTime(date.year, date.month, 0).day,
(index) => FlSpot(index.toDouble() + 1, data[index]),
(index) => FlSpot(index.toDouble() + 1, incomeData[index]),
),
),
if (expenseData.isNotEmpty)
LineChartBarData(
isCurved: true,
barWidth: 8,
isStrokeCapRound: true,
dotData: const FlDotData(show: false),
belowBarData: BarAreaData(show: false),
color: Theme.of(context).colorScheme.error,
spots: List.generate(
(yearly) ? 12 : DateTime(date.year, date.month, 0).day,
(index) => FlSpot(index.toDouble() + 1, expenseData[index]),
),
),
], // actual data

View file

@ -1,6 +1,7 @@
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:prasule/api/category.dart';
import 'package:prasule/api/wallet.dart';
import 'package:prasule/api/walletmanager.dart';
import 'package:prasule/main.dart';
@ -25,6 +26,7 @@ class _GraphViewState extends State<GraphView> {
List<Wallet> wallets = [];
String? locale;
var yearlyBtnSet = {"monthly"};
var graphTypeSet = {"expense", "income"};
bool get yearly => yearlyBtnSet.contains("yearly");
@override
@ -33,20 +35,22 @@ class _GraphViewState extends State<GraphView> {
locale ??= Localizations.localeOf(context).languageCode;
}
List<double> generateChartData() {
if (selectedWallet == null) return [0];
List<double> generateChartData(EntryType type) {
var data = List<double>.filled(
(yearly)
? 12
: DateTime(_selectedDate.year, _selectedDate.month, 0).day,
0.0);
if (selectedWallet == null) return [];
for (var i = 0; i < data.length; i++) {
var entriesForRange = selectedWallet!.entries.where((element) => (!yearly)
var entriesForRange = selectedWallet!.entries.where((element) =>
((!yearly)
? element.date.month == _selectedDate.month &&
element.date.year == _selectedDate.year &&
element.date.day == i + 1
: element.date.month == i + 1 &&
element.date.year == _selectedDate.year);
element.date.year == _selectedDate.year) &&
element.type == type);
var sum = 0.0;
for (var e in entriesForRange) {
sum += e.data.amount;
@ -148,12 +152,20 @@ class _GraphViewState extends State<GraphView> {
SegmentedButton<String>(
segments: [
ButtonSegment<String>(
value: "spending",
label: Text(AppLocalizations.of(context)!.spendingStats),
)
value: "expense",
label: Text(AppLocalizations.of(context)!.expenses),
),
ButtonSegment<String>(
value: "income",
label: Text(AppLocalizations.of(context)!.income),
),
],
selected: const {"spending"},
// TODO: onSelectionChanged
selected: graphTypeSet,
multiSelectionEnabled: true,
onSelectionChanged: (selection) {
graphTypeSet = selection;
setState(() {});
},
),
const SizedBox(
height: 5,
@ -189,6 +201,8 @@ class _GraphViewState extends State<GraphView> {
..sort((a, b) => b.date.compareTo(a.date)))
.first
.date;
logger.i(firstDate);
logger.i(lastDate);
var newDate = await showDatePicker(
context: context,
initialDate: _selectedDate,
@ -215,7 +229,12 @@ class _GraphViewState extends State<GraphView> {
date: _selectedDate,
locale: locale ?? "en",
yearly: yearly,
data: generateChartData(),
expenseData: (graphTypeSet.contains("expense"))
? generateChartData(EntryType.expense)
: [],
incomeData: (graphTypeSet.contains("income"))
? generateChartData(EntryType.income)
: [],
),
)
],

View file

@ -1,6 +1,4 @@
import 'dart:math';
import 'package:currency_picker/currency_picker.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
@ -87,7 +85,9 @@ class _HomeViewState extends State<HomeView> {
name: "Test Entry #${i + 1}",
amount: random.nextInt(20000).toDouble(),
),
type: EntryType.expense,
type: (random.nextInt(3) > 0)
? EntryType.expense
: EntryType.income,
date: DateTime(
2023,
random.nextInt(12) + 1,