268 lines
8.1 KiB
Dart
Executable File
268 lines
8.1 KiB
Dart
Executable File
import 'package:fl_chart/fl_chart.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
|
|
import 'package:uae_stat/domain/use_cases/language.dart';
|
|
import 'package:uae_stat/infrastructure/services/packages/material_color.dart';
|
|
import 'package:uae_stat/infrastructure/services/packages/num_abbreviation.dart';
|
|
import 'package:uae_stat/presentation/components/space.dart';
|
|
|
|
/// individual pie charts per category
|
|
class ThemedPieChartPlural extends ConsumerWidget {
|
|
/// must provide either [materialColor] or [perSegmentColor]
|
|
const ThemedPieChartPlural({
|
|
super.key,
|
|
required this.data,
|
|
this.materialColor,
|
|
this.perSegmentColor,
|
|
this.badgePositionPercentageOffset = 2,
|
|
});
|
|
|
|
final Map<String, double> data;
|
|
|
|
/// key should match data
|
|
final Map<String, Color>? perSegmentColor;
|
|
final double badgePositionPercentageOffset;
|
|
|
|
/// generates segment colors from [MaterialColor]
|
|
/// do not provide if [perSegmentColor] is also provided
|
|
final MaterialColor? materialColor;
|
|
|
|
@override
|
|
Widget build(BuildContext context, ref) {
|
|
assert(!(perSegmentColor != null && materialColor != null));
|
|
final totalVal = data.values.fold(
|
|
0.0,
|
|
(prev, e) => prev + e,
|
|
);
|
|
final pieCharts = List.generate(
|
|
data.length,
|
|
(index) {
|
|
final e = data.entries.elementAt(index);
|
|
final fraction = e.value / totalVal;
|
|
final pieChartSectionData = [
|
|
PieChartSectionData(
|
|
radius: 11,
|
|
value: 1 - fraction,
|
|
color: Colors.grey.shade200,
|
|
showTitle: false,
|
|
),
|
|
PieChartSectionData(
|
|
radius: 11,
|
|
value: fraction,
|
|
color: _segmentColor(index),
|
|
showTitle: false,
|
|
),
|
|
];
|
|
return Column(
|
|
children: [
|
|
Expanded(
|
|
child: FittedBox(
|
|
fit: BoxFit.scaleDown,
|
|
child: Text(
|
|
e.key,
|
|
textAlign: TextAlign.center,
|
|
style: const TextStyle(
|
|
fontSize: 11,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
4.verticalSpace,
|
|
Expanded(
|
|
flex: 4,
|
|
child: Stack(
|
|
alignment: Alignment.center,
|
|
children: [
|
|
Text(
|
|
'${(fraction * 100).round()}%',
|
|
),
|
|
PieChart(
|
|
PieChartData(
|
|
sections: pieChartSectionData,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
4.verticalSpace,
|
|
Expanded(
|
|
child: Text(
|
|
e.value.toStringWithCommas(
|
|
abbreviateIfAbove10powN: 5,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
style: const TextStyle(
|
|
fontSize: 11,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
);
|
|
// final pieChart = PieChart(
|
|
// PieChartData(
|
|
// sections: List.generate(
|
|
// data.length,
|
|
// (index) {
|
|
// final e = data[index];
|
|
// return PieChartSectionData(
|
|
// radius: 22,
|
|
// badgeWidget: ConstrainedBox(
|
|
// constraints: const BoxConstraints(maxWidth: 100),
|
|
// child: Text(
|
|
// '${e.key}\n${e.value.toStringWithCommas()}',
|
|
// textAlign: TextAlign.center,
|
|
// style: TextStyle(
|
|
// fontFamily: context.translate(
|
|
// 'Roboto',
|
|
// 'NotoKufi',
|
|
// ),
|
|
// fontSize: 10,
|
|
// fontWeight: FontWeight.w600,
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// badgePositionPercentageOffset: badgePositionPercentageOffset,
|
|
// title: '${((e.value / totalVal) * 100).toStringAsFixed(1)}%',
|
|
// titlePositionPercentageOffset: -0.6,
|
|
// titleStyle: TextStyle(
|
|
// fontFamily: context.translate(
|
|
// 'Roboto',
|
|
// 'NotoKufi',
|
|
// ),
|
|
// fontSize: 10,
|
|
// fontWeight: FontWeight.w600,
|
|
// ),
|
|
// showTitle: true,
|
|
// color: _segmentColor(index),
|
|
// value: e.value,
|
|
// );
|
|
// },
|
|
// ),
|
|
// ),
|
|
// );
|
|
return Wrap(
|
|
spacing: 10,
|
|
runSpacing: 10,
|
|
alignment: WrapAlignment.center,
|
|
children: pieCharts
|
|
.map(
|
|
(e) => SizedBox.square(
|
|
dimension: 130,
|
|
child: e,
|
|
),
|
|
)
|
|
.toList(),
|
|
);
|
|
}
|
|
|
|
Color _segmentColor(int index) {
|
|
if (materialColor == null) {
|
|
final key = data.keys.elementAt(index);
|
|
return perSegmentColor![key]!;
|
|
}
|
|
final colorIndex = index % materialColor!.colorListButFewer.length;
|
|
return materialColor!.colorListButFewer[colorIndex];
|
|
}
|
|
}
|
|
|
|
class ThemedPieChartSingular extends ConsumerWidget {
|
|
/// must provide either [materialColor] or [perSegmentColor]
|
|
const ThemedPieChartSingular({
|
|
super.key,
|
|
required this.data,
|
|
this.materialColor,
|
|
this.perSegmentColor,
|
|
this.badgePositionPercentageOffset = 2,
|
|
});
|
|
|
|
final Map<String, double> data;
|
|
|
|
/// key should match data
|
|
final Map<String, Color>? perSegmentColor;
|
|
final double badgePositionPercentageOffset;
|
|
|
|
/// generates segment colors from [MaterialColor]
|
|
/// do not provide if [perSegmentColor] is also provided
|
|
final MaterialColor? materialColor;
|
|
|
|
@override
|
|
Widget build(BuildContext context, ref) {
|
|
assert(!(perSegmentColor != null && materialColor != null));
|
|
final totalVal = data.values.fold(
|
|
0.0,
|
|
(prev, e) => prev + e,
|
|
);
|
|
final dataWithoutLessThan1PctSectors = data.entries
|
|
.where(
|
|
(emirateConsumption) =>
|
|
emirateConsumption.value.asPctOf(totalVal) > 1,
|
|
)
|
|
.toList();
|
|
dataWithoutLessThan1PctSectors.insert(
|
|
1,
|
|
dataWithoutLessThan1PctSectors
|
|
.removeAt(dataWithoutLessThan1PctSectors.length - 1),
|
|
);
|
|
final pieChart = PieChart(
|
|
PieChartData(
|
|
sections: List.generate(
|
|
dataWithoutLessThan1PctSectors.length,
|
|
(index) {
|
|
final e = dataWithoutLessThan1PctSectors[index];
|
|
return PieChartSectionData(
|
|
radius: 22,
|
|
badgeWidget: ConstrainedBox(
|
|
constraints: const BoxConstraints(maxWidth: 100),
|
|
child: Text(
|
|
'${e.key}\n${e.value.toStringWithCommas(
|
|
abbreviateIfAbove10powN: 5,
|
|
)}',
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(
|
|
fontFamily: context.translate(
|
|
'Roboto',
|
|
'NotoKufi',
|
|
),
|
|
fontSize: 10,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
badgePositionPercentageOffset: badgePositionPercentageOffset,
|
|
title: '${((e.value / totalVal) * 100).toStringAsFixed(1)}%',
|
|
titlePositionPercentageOffset: -0.6,
|
|
titleStyle: TextStyle(
|
|
fontFamily: context.translate(
|
|
'Roboto',
|
|
'NotoKufi',
|
|
),
|
|
fontSize: 10,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
showTitle: true,
|
|
color: _segmentColor(index),
|
|
value: e.value,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
return Padding(
|
|
padding: const EdgeInsets.all(20),
|
|
child: pieChart,
|
|
);
|
|
}
|
|
|
|
Color _segmentColor(int index) {
|
|
if (materialColor == null) {
|
|
final key = data.keys.elementAt(index);
|
|
return perSegmentColor![key]!;
|
|
}
|
|
final colorIndex = index % materialColor!.colorListButFewer.length;
|
|
return materialColor!.colorListButFewer[colorIndex];
|
|
}
|
|
}
|