Further work on pie chart for the nutritional plan

This commit is contained in:
Roland Geider
2023-05-19 20:59:37 +02:00
parent dc56e4295b
commit 237b4daaba
5 changed files with 218 additions and 91 deletions

42
lib/helpers/colors.dart Normal file
View File

@@ -0,0 +1,42 @@
import 'dart:ui';
const LIST_OF_COLORS8 = [
Color(0xFF2A4C7D),
Color(0xFF5B5291),
Color(0xFF8E5298),
Color(0xFFBF5092),
Color(0xFFE7537E),
Color(0xFFFF6461),
Color(0xFFFF813D),
Color(0xFFFFA600),
];
const LIST_OF_COLORS5 = [
Color(0xFF2A4C7D),
Color(0xFF825298),
Color(0xFFD45089),
Color(0xFFFF6A59),
Color(0xFFFFA600),
];
const LIST_OF_COLORS3 = [
Color(0xFF2A4C7D),
Color(0xFFD45089),
Color(0xFFFFA600),
];
Iterable<Color> generateChartColors(int nrOfItems) sync* {
final List<Color> colors;
if (nrOfItems <= 3) {
colors = LIST_OF_COLORS3;
} else if (nrOfItems <= 5) {
colors = LIST_OF_COLORS5;
} else {
colors = LIST_OF_COLORS8;
}
for (final color in colors) {
yield color;
}
}

View File

@@ -57,3 +57,45 @@ class MeasurementChartWidget extends StatelessWidget {
);
}
}
class Indicator extends StatelessWidget {
const Indicator({
super.key,
required this.color,
required this.text,
required this.isSquare,
this.size = 16,
this.textColor,
});
final Color color;
final String text;
final bool isSquare;
final double size;
final Color? textColor;
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Container(
width: size,
height: size,
decoration: BoxDecoration(
shape: isSquare ? BoxShape.rectangle : BoxShape.circle,
color: color,
),
),
const SizedBox(
width: 4,
),
Text(
text,
style: TextStyle(
color: textColor,
),
)
],
);
}
}

View File

@@ -190,7 +190,7 @@ class _DashboardNutritionWidgetState extends State<DashboardNutritionWidget> {
Container(
padding: const EdgeInsets.all(15),
height: 180,
child: FlNutritionalPlanPieChartWidget(_plan!.nutritionalValues),
child: NutritionalPlanPieChartWidget(_plan!.nutritionalValues),
)
],
),

View File

@@ -21,9 +21,11 @@ import 'package:collection/collection.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:wger/helpers/colors.dart';
import 'package:wger/models/nutrition/nutritional_plan.dart';
import 'package:wger/models/nutrition/nutritional_values.dart';
import 'package:wger/theme/theme.dart';
import 'package:wger/widgets/core/charts.dart';
class NutritionData {
final String name;
@@ -32,74 +34,119 @@ class NutritionData {
NutritionData(this.name, this.value);
}
class FlNutritionalPlanPieChartWidget extends StatefulWidget {
class NutritionalPlanPieChartWidget extends StatefulWidget {
final NutritionalValues nutritionalValues;
const FlNutritionalPlanPieChartWidget(this.nutritionalValues);
const NutritionalPlanPieChartWidget(this.nutritionalValues);
@override
State<StatefulWidget> createState() => FlNutritionalPlanPieChartState();
}
class FlNutritionalPlanPieChartState extends State<FlNutritionalPlanPieChartWidget> {
class FlNutritionalPlanPieChartState extends State<NutritionalPlanPieChartWidget> {
int touchedIndex = -1;
@override
Widget build(BuildContext context) {
return PieChart(
PieChartData(
pieTouchData: PieTouchData(
touchCallback: (FlTouchEvent event, pieTouchResponse) {
setState(() {
if (!event.isInterestedForInteractions ||
pieTouchResponse == null ||
pieTouchResponse.touchedSection == null) {
touchedIndex = -1;
return;
}
touchedIndex = pieTouchResponse.touchedSection!.touchedSectionIndex;
});
},
return Row(
children: [
const SizedBox(
height: 18,
),
borderData: FlBorderData(
show: false,
Expanded(
child: AspectRatio(
aspectRatio: 1,
child: PieChart(
PieChartData(
pieTouchData: PieTouchData(
touchCallback: (FlTouchEvent event, pieTouchResponse) {
setState(() {
if (!event.isInterestedForInteractions ||
pieTouchResponse == null ||
pieTouchResponse.touchedSection == null) {
touchedIndex = -1;
return;
}
touchedIndex = pieTouchResponse.touchedSection!.touchedSectionIndex;
});
},
),
borderData: FlBorderData(
show: false,
),
sectionsSpace: 0,
centerSpaceRadius: 0,
sections: showingSections(),
),
),
),
),
sectionsSpace: 0,
centerSpaceRadius: 40,
sections: showingSections(),
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Indicator(
color: LIST_OF_COLORS3[0],
text: AppLocalizations.of(context).fat,
isSquare: true,
),
const SizedBox(
height: 4,
),
Indicator(
color: LIST_OF_COLORS3[1],
text: AppLocalizations.of(context).protein,
isSquare: true,
),
const SizedBox(
height: 4,
),
Indicator(
color: LIST_OF_COLORS3[2],
text: AppLocalizations.of(context).carbohydrates,
isSquare: true,
),
],
),
const SizedBox(
width: 28,
),
],
);
}
List<PieChartSectionData> showingSections() {
final colors = generateChartColors(3).iterator;
return List.generate(3, (i) {
final isTouched = i == touchedIndex;
final fontSize = isTouched ? 25.0 : 16.0;
final radius = isTouched ? 60.0 : 50.0;
final radius = isTouched ? 92.0 : 80.0;
colors.moveNext();
switch (i) {
case 0:
return PieChartSectionData(
color: Colors.blue,
value: widget.nutritionalValues.fat,
title: AppLocalizations.of(context).fat,
radius: radius,
titleStyle: TextStyle(fontSize: fontSize),
);
color: colors.current,
value: widget.nutritionalValues.fat,
title: '${widget.nutritionalValues.fat.toStringAsFixed(1)}g',
titlePositionPercentageOffset: 0.5,
radius: radius,
titleStyle: const TextStyle(color: Colors.white70));
case 1:
return PieChartSectionData(
color: Colors.amber,
color: colors.current,
value: widget.nutritionalValues.protein,
title: AppLocalizations.of(context).protein,
title: '${widget.nutritionalValues.protein.toStringAsFixed(1)}g',
titlePositionPercentageOffset: 0.5,
radius: radius,
titleStyle: TextStyle(fontSize: fontSize),
);
case 2:
return PieChartSectionData(
color: Colors.purple,
color: colors.current,
value: widget.nutritionalValues.carbohydrates,
title: AppLocalizations.of(context).carbohydrates,
title: '${widget.nutritionalValues.carbohydrates.toStringAsFixed(1)}g',
titlePositionPercentageOffset: 0.5,
radius: radius,
titleStyle: TextStyle(fontSize: fontSize),
);
default:
@@ -109,59 +156,6 @@ class FlNutritionalPlanPieChartState extends State<FlNutritionalPlanPieChartWidg
}
}
/// Nutritional plan pie chart widget
class NutritionalPlanPieChartWidget extends StatelessWidget {
final NutritionalValues _nutritionalValues;
/// [_nutritionalValues] are the calculated [NutritionalValues] for the wanted
/// plan.
const NutritionalPlanPieChartWidget(this._nutritionalValues);
@override
Widget build(BuildContext context) {
if (_nutritionalValues.energy == 0) {
return Container();
}
return charts.PieChart<String>([
charts.Series<NutritionData, String>(
id: 'NutritionalValues',
domainFn: (nutritionEntry, index) => nutritionEntry.name,
measureFn: (nutritionEntry, index) => nutritionEntry.value,
data: [
NutritionData(
AppLocalizations.of(context).protein,
_nutritionalValues.protein,
),
NutritionData(
AppLocalizations.of(context).fat,
_nutritionalValues.fat,
),
NutritionData(
AppLocalizations.of(context).carbohydrates,
_nutritionalValues.carbohydrates,
),
],
labelAccessorFn: (NutritionData row, _) =>
'${row.name}\n${row.value.toStringAsFixed(0)}${AppLocalizations.of(context).g}',
)
],
defaultRenderer: charts.ArcRendererConfig(arcWidth: 60, arcRendererDecorators: [
charts.ArcLabelDecorator(
labelPosition: charts.ArcLabelPosition.outside,
)
])
/*
defaultRenderer: new charts.ArcRendererConfig(
arcWidth: 60,
arcRendererDecorators: [new charts.ArcLabelDecorator()],
),
*/
);
}
}
class NutritionalDiaryChartWidget extends StatelessWidget {
const NutritionalDiaryChartWidget({
Key? key,

49
test/utils/colors.dart Normal file
View File

@@ -0,0 +1,49 @@
import 'dart:ui';
import 'package:flutter_test/flutter_test.dart';
import 'package:wger/helpers/colors.dart';
void main() {
group('test the color utility', () {
test('3 items or less', () {
final result = generateChartColors(2).iterator;
expect(result.current, equals(const Color(0xFF2A4C7D)));
result.moveNext();
expect(result.current, equals(const Color(0xFFD45089)));
});
test('5 items or less', () {
final result = generateChartColors(5).iterator;
expect(result.current, equals(const Color(0xFF2A4C7D)));
result.moveNext();
expect(result.current, equals(const Color(0xFF825298)));
result.moveNext();
expect(result.current, equals(const Color(0xFFD45089)));
result.moveNext();
expect(result.current, equals(const Color(0xFFFF6A59)));
result.moveNext();
expect(result.current, equals(const Color(0xFFFFA600)));
});
test('8 items or more - last ones undefined', () {
final result = generateChartColors(8).iterator;
expect(result.current, equals(const Color(0xFF2A4C7D)));
result.moveNext();
expect(result.current, equals(const Color(0xFF5B5291)));
result.moveNext();
expect(result.current, equals(const Color(0xFF8E5298)));
result.moveNext();
expect(result.current, equals(const Color(0xFFBF5092)));
result.moveNext();
expect(result.current, equals(const Color(0xFFE7537E)));
result.moveNext();
expect(result.current, equals(const Color(0xFFFF6461)));
result.moveNext();
expect(result.current, equals(const Color(0xFFFF813D)));
result.moveNext();
expect(result.current, equals(const Color(0xFFFFA600)));
result.moveNext();
expect(result.current, isNull);
});
});
}