feat: add graphs #16
6 changed files with 92 additions and 39 deletions
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
@ -2,6 +2,7 @@
|
|||
"conventionalCommits.scopes": [
|
||||
"ocr",
|
||||
"ui",
|
||||
"translations"
|
||||
"translations",
|
||||
"graphs"
|
||||
]
|
||||
}
|
|
@ -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"
|
||||
|
||||
}
|
|
@ -95,7 +95,7 @@
|
|||
"setupStartingBalance":"Starting balance",
|
||||
"graphs":"Graphs",
|
||||
"createTestData":"Create test data",
|
||||
"spendingStats":"Spending statistics",
|
||||
"yearly":"Yearly",
|
||||
"monthly":"Monthly"
|
||||
"monthly":"Monthly",
|
||||
"expenses":"Expenses"
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
: [],
|
||||
),
|
||||
)
|
||||
],
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue