uaestats_fe/lib/presentation/components/charts/themed_distribution_chart.dart
2026-05-16 11:31:32 +05:30

253 lines
7.0 KiB
Dart
Executable File

import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:uae_stat/domain/use_cases/language.dart';
import 'package:uae_stat/infrastructure/services/packages/list.dart';
import 'package:uae_stat/infrastructure/services/packages/num_abbreviation.dart';
import 'package:uae_stat/presentation/components/space.dart';
class ThemedDistributionChart extends ConsumerWidget {
const ThemedDistributionChart(
this.data, {
required this.leftColor,
required this.middleText,
required this.rightColor,
this.doSort = true,
this.labelBuilder,
super.key,
});
final bool doSort;
final String middleText;
final Map<String, ({num left, num right})> data;
final Color leftColor;
final Color rightColor;
final String Function(bool isStart, num value)? labelBuilder;
Map<String, ({num left, num right})> calculateSortedData() {
final sortedEntries = data.entries.toList()
..sort(
(a, b) {
final aSum = a.value.left + a.value.right;
final bSum = b.value.left + b.value.right;
return bSum.compareTo(aSum);
},
);
return Map.fromEntries(sortedEntries);
}
@override
Widget build(BuildContext context, WidgetRef ref) {
final sortedDataIfAskedElseUnsorted = doSort ? calculateSortedData() : data;
final maxVal = data.values.fold<num>(
double.negativeInfinity,
(prev, vals) {
final localMax = max(vals.left, vals.right);
return localMax > prev ? localMax : prev;
},
);
const textSpace = 37.0;
final left = Column(
children: sortedDataIfAskedElseUnsorted.values.map<Widget>(
(e) {
final bar = FractionallySizedBox(
widthFactor: e.left / maxVal,
child: DecoratedBox(
decoration: BoxDecoration(
color: leftColor,
borderRadius: const BorderRadiusDirectional.horizontal(
start: Radius.circular(100),
),
),
child: double.infinity.verticalSpace,
),
);
final text = Text(
labelBuilder == null
? e.left.toStringWithCommas()
: labelBuilder!(true, e.left),
textAlign: TextAlign.end,
style: TextStyle(
fontFamily: context.translate(
'Roboto',
'NotoKufi',
),
fontSize: 11,
fontWeight: FontWeight.w500,
),
);
final segment = Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
SizedBox(
width: textSpace,
child: text,
),
4.horizontalSpace,
Flexible(
child: bar,
),
],
),
);
return segment;
},
).toList()
..intersperseDivider(
8.verticalSpace,
)
..insert(
0,
Padding(
padding: const EdgeInsets.only(bottom: 4),
child: Text(
'Male',
style: TextStyle(
fontFamily: context.translate(
'Roboto',
'NotoKufi',
),
fontSize: 11,
fontWeight: FontWeight.w800,
),
),
),
),
);
final right = Column(
children: sortedDataIfAskedElseUnsorted.values.map<Widget>(
(e) {
final bar = FractionallySizedBox(
widthFactor: e.right / maxVal,
child: DecoratedBox(
decoration: BoxDecoration(
color: rightColor,
borderRadius: const BorderRadiusDirectional.horizontal(
end: Radius.circular(100),
),
),
child: double.infinity.verticalSpace,
),
);
final text = Text(
labelBuilder == null
? e.right.toStringWithCommas()
: labelBuilder!(true, e.right),
textAlign: TextAlign.start,
style: TextStyle(
fontFamily: context.translate(
'Roboto',
'NotoKufi',
),
fontSize: 11,
fontWeight: FontWeight.w500,
),
);
final segment = Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Flexible(
child: bar,
),
4.horizontalSpace,
SizedBox(
width: textSpace,
child: text,
),
],
),
);
return segment;
},
).toList()
..intersperseDivider(
8.verticalSpace,
)
..insert(
0,
Padding(
padding: const EdgeInsets.only(bottom: 4),
child: Text(
'Female',
style: TextStyle(
fontFamily: context.translate(
'Roboto',
'NotoKufi',
),
fontSize: 11,
fontWeight: FontWeight.w800,
),
),
),
),
);
final legend = Column(
children: sortedDataIfAskedElseUnsorted.keys
.map<Widget>(
(e) => Expanded(
child: Center(
child: Text(
e,
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: context.translate(
'Roboto',
'NotoKufi',
),
fontSize: 11,
fontWeight: FontWeight.w900,
),
),
),
),
)
.toList()
..intersperseDivider(
8.verticalSpace,
)
..insert(
0,
Padding(
padding: const EdgeInsets.only(bottom: 4),
child: Text(
middleText,
textAlign: TextAlign.center,
maxLines: 1,
style: TextStyle(
fontFamily: context.translate(
'Roboto',
'NotoKufi',
),
fontSize: 11,
fontWeight: FontWeight.w800,
),
),
),
),
);
return Row(
children: [
Expanded(
child: left,
),
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 60),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 6,
),
child: legend,
),
),
Expanded(
child: right,
),
],
);
}
}