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

191 lines
5.9 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/list.dart';
import 'package:uae_stat/infrastructure/services/packages/num_abbreviation.dart';
import 'package:uae_stat/presentation/components/space.dart';
import 'package:uae_stat/presentation/components/charts/fl_dot_circle_painter_with_text_above.dart';
import 'package:uae_stat/presentation/components/charts/line_chart_data_entity.dart';
class ThemedLineChart extends ConsumerWidget {
const ThemedLineChart({
super.key,
required this.lineDataList,
this.yAxisTitleBuilder,
this.yAxisInterval,
});
final double? yAxisInterval;
final List<LineChartDataEntity> lineDataList;
final String Function(double value)? yAxisTitleBuilder;
double get _minY => lineDataList
.map(
(e) => e.data.values.fold(
double.maxFinite,
(prev, curr) => curr < prev ? curr : prev,
),
)
.fold(
double.maxFinite,
(prev, curr) => curr < prev ? curr : prev,
);
double get _maxY => lineDataList
.map(
(e) => e.data.values.fold(
double.negativeInfinity,
(prev, curr) => curr > prev ? curr : prev,
),
)
.fold(
double.negativeInfinity,
(prev, curr) => curr > prev ? curr : prev,
);
@override
Widget build(BuildContext context, ref) {
final isMinValZero = _minY == 0;
final range = _maxY - _minY;
const paddingRate = 0.24;
final maxYPadded = _maxY + (range * paddingRate);
final minYPadded = _minY - (range * paddingRate);
final titlesData = FlTitlesData(
show: true,
leftTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
rightTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
topTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 30,
interval: yAxisInterval ?? 1,
getTitlesWidget: (value, meta) => Column(
children: [
const SizedBox(
height: 7,
child: VerticalDivider(
color: Colors.black,
),
),
Text(
yAxisTitleBuilder == null
? value.toString()
: yAxisTitleBuilder!(value),
style: TextStyle(
fontFamily: context.translate(
'Roboto',
'NotoKufi',
),
fontSize: 7,
color: Colors.black,
),
),
],
),
),
),
);
final lineChart = LineChart(
LineChartData(
maxY: maxYPadded,
minY: isMinValZero ? 0 : minYPadded,
lineTouchData: const LineTouchData(enabled: false),
borderData: FlBorderData(
border: const Border(
bottom: BorderSide(),
),
),
gridData: const FlGridData(show: false),
titlesData: titlesData,
lineBarsData: lineDataList
.map(
(e) => LineChartBarData(
belowBarData: BarAreaData(
color: e.color.withOpacity(0.3),
show: e.isShaded,
),
color: e.color,
dotData: FlDotData(
getDotPainter: (
flSpot,
yValue,
lineChartBarData,
i,
) =>
FlDotCirclePainterWithTextAbove(
text: e.textAboveMarkerBuilder == null
? flSpot.y.toAbbreviatedString()
: e.textAboveMarkerBuilder!(flSpot.y),
color: e.color,
textStyle: TextStyle(
fontFamily: context.translate(
'Roboto',
'NotoKufi',
),
fontWeight: FontWeight.w600,
fontSize: 10,
color: Colors.black,
),
),
),
spots: e.data.entries
.map(
(e) => FlSpot(
e.key,
e.value,
),
)
.toList(),
),
)
.toList(),
),
);
return Column(
children: [
if (lineDataList.any((e) => e.title != null))
...lineDataList
.map<Widget>(
(e) => Row(
children: [
Container(
height: 12,
width: 12,
decoration: BoxDecoration(
color: e.color,
borderRadius: BorderRadius.circular(2),
),
),
12.horizontalSpace,
Expanded(
child: Text(
e.title!,
style: TextStyle(
fontFamily: context.translate(
'Roboto',
'NotoKufi',
),
fontSize: 11,
color: Colors.black,
),
),
),
],
),
)
.toList()
..intersperseDivider(4.verticalSpace),
12.verticalSpace,
Expanded(child: lineChart),
],
);
}
}