Extracted reusbale logic and ui componenets into a shared helper class for the total energy chart, and energy cosumption per devices, to avoid any code duplication. If another chart required some change, we dont need to edit the helper itself, we can just add out own implementation into the new chart.

This commit is contained in:
Faris Armoush
2025-05-04 10:46:12 +03:00
parent 56f9b1fc9a
commit bafd2b4d13
4 changed files with 208 additions and 217 deletions

View File

@ -0,0 +1,118 @@
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/analytics/helpers/format_number_to_kwh.dart';
import 'package:syncrow_web/pages/analytics/helpers/get_month_name_from_int.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
abstract final class EnergyManagementChartsHelper {
const EnergyManagementChartsHelper._();
static FlTitlesData titlesData(BuildContext context) {
const emptyTitle = AxisTitles(sideTitles: SideTitles(showTitles: false));
return FlTitlesData(
show: true,
bottomTitles: AxisTitles(
drawBelowEverything: true,
sideTitles: SideTitles(
interval: 1,
reservedSize: 32,
showTitles: true,
maxIncluded: true,
getTitlesWidget: (value, meta) => Padding(
padding: const EdgeInsetsDirectional.only(top: 20.0),
child: Text(
value.getMonthName,
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.greyColor,
fontSize: 12,
),
),
),
),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
maxIncluded: true,
reservedSize: 110,
getTitlesWidget: (value, meta) => Padding(
padding: const EdgeInsetsDirectional.only(end: 12),
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
value.formatNumberToKwh,
style: context.textTheme.bodySmall?.copyWith(
fontSize: 12,
color: ColorsManager.greyColor,
),
),
),
),
),
),
rightTitles: emptyTitle,
topTitles: emptyTitle,
);
}
static String getToolTipLabel(num month, double value) {
final monthLabel = month.getMonthName;
final valueLabel = value.formatNumberToKwh;
final labels = [monthLabel, valueLabel];
return labels.where((element) => element.isNotEmpty).join(', ');
}
static List<LineTooltipItem?> getTooltipItems(List<LineBarSpot> touchedSpots) {
return touchedSpots.map((spot) {
return LineTooltipItem(
getToolTipLabel(spot.x, spot.y),
const TextStyle(
color: ColorsManager.textPrimaryColor,
fontWeight: FontWeight.w600,
fontSize: 12,
),
);
}).toList();
}
static LineTouchTooltipData lineTouchTooltipData() {
return LineTouchTooltipData(
getTooltipColor: (touchTooltipItem) => ColorsManager.whiteColors,
tooltipBorder: const BorderSide(color: ColorsManager.semiTransparentBlack),
tooltipRoundedRadius: 16,
showOnTopOfTheChartBoxArea: false,
tooltipPadding: const EdgeInsets.all(8),
getTooltipItems: getTooltipItems,
);
}
static FlBorderData borderData() {
return FlBorderData(
show: true,
border: const Border.symmetric(
horizontal: BorderSide(
color: ColorsManager.greyColor,
style: BorderStyle.solid,
width: 1,
),
),
);
}
static FlGridData gridData() {
return FlGridData(
show: true,
drawVerticalLine: false,
drawHorizontalLine: true,
);
}
static LineTouchData lineTouchData() {
return LineTouchData(
handleBuiltInTouches: true,
touchSpotThreshold: 2,
touchTooltipData: EnergyManagementChartsHelper.lineTouchTooltipData(),
);
}
}

View File

@ -1,6 +1,6 @@
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
class EnergyConsumptionPerDeviceChart extends StatelessWidget {
const EnergyConsumptionPerDeviceChart({super.key});
@ -9,118 +9,64 @@ class EnergyConsumptionPerDeviceChart extends StatelessWidget {
Widget build(BuildContext context) {
return LineChart(
LineChartData(
gridData: const FlGridData(
show: true,
drawVerticalLine: false,
drawHorizontalLine: false,
verticalInterval: 1,
),
titlesData: FlTitlesData(
show: true,
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 30,
interval: 4,
),
),
leftTitles: AxisTitles(
drawBelowEverything: true,
sideTitles: SideTitles(
interval: 2,
showTitles: true,
reservedSize: 40,
),
),
rightTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
topTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
),
lineTouchData: LineTouchData(
getTouchLineEnd: (data, index) => double.infinity,
getTouchedSpotIndicator:
(LineChartBarData barData, List<int> spotIndexes) {
return spotIndexes.map((spotIndex) {
return TouchedSpotIndicatorData(
const FlLine(color: Colors.orange, strokeWidth: 3),
FlDotData(
getDotPainter: (spot, percent, barData, index) =>
FlDotCirclePainter(
radius: 8,
color: Colors.orange,
),
),
);
}).toList();
},
touchTooltipData: LineTouchTooltipData(
getTooltipColor: (touchedSpot) => Colors.red,
getTooltipItems: (List<LineBarSpot> touchedSpots) => touchedSpots
.map(
(touchedSpot) => LineTooltipItem(
touchedSpot.y.toString(),
const TextStyle(
color: ColorsManager.whiteColors,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
)
.toList(),
),
),
borderData: FlBorderData(
show: true,
border: Border.all(
color: Colors.grey,
),
),
minX: 0,
maxX: 11,
minY: 0,
maxY: 6,
titlesData: EnergyManagementChartsHelper.titlesData(context),
gridData: EnergyManagementChartsHelper.gridData(),
borderData: EnergyManagementChartsHelper.borderData(),
lineTouchData: EnergyManagementChartsHelper.lineTouchData(),
lineBarsData: [
LineChartBarData(
_buildChartBar(
spots: const [
FlSpot(0, 0),
FlSpot(2, 1),
FlSpot(4.9, 5),
FlSpot(6.8, 5),
FlSpot(8, 1),
FlSpot(10, 2),
FlSpot(11, 2.5),
FlSpot(1, 100),
FlSpot(2, 200),
FlSpot(3, 300),
FlSpot(4, 400),
FlSpot(5, 500),
FlSpot(6, 600),
FlSpot(7, 700),
FlSpot(8, 800),
FlSpot(9, 900),
FlSpot(10, 1000),
FlSpot(11, 1100),
FlSpot(12, 1200),
],
dashArray: [12, 18],
isCurved: true,
color: Colors.red,
barWidth: 4,
isStrokeCapRound: true,
dotData: const FlDotData(
show: false,
),
),
LineChartBarData(
spots: const [
FlSpot(0, 0),
FlSpot(3.9, 4),
FlSpot(5.8, 4),
FlSpot(7, 0),
FlSpot(9, 1),
FlSpot(10, 1),
FlSpot(11, 1.5),
],
dashArray: [12, 18],
isCurved: true,
color: Colors.amber,
barWidth: 4,
isStrokeCapRound: true,
dotData: const FlDotData(show: false),
),
_buildChartBar(
color: Colors.red,
spots: const [
FlSpot(1, 100),
FlSpot(2, 300),
FlSpot(3, 400),
FlSpot(4, 500),
FlSpot(5, 600),
FlSpot(6, 700),
FlSpot(7, 800),
FlSpot(8, 900),
FlSpot(9, 1000),
FlSpot(10, 1100),
FlSpot(11, 1200),
FlSpot(12, 1300),
],
),
],
),
duration: Durations.extralong1,
curve: Curves.easeIn,
);
}
LineChartBarData _buildChartBar({
required Color color,
required List<FlSpot> spots,
}) {
return LineChartBarData(
spots: spots,
dashArray: [12, 18],
isCurved: true,
color: color,
barWidth: 4,
isStrokeCapRound: true,
dotData: const FlDotData(show: false),
);
}
}

View File

@ -12,16 +12,42 @@ class EnergyConsumptionPerDeviceChartBox extends StatelessWidget {
borderRadius: BorderRadius.circular(30),
),
padding: const EdgeInsets.all(30),
child: const Column(
child: Column(
spacing: 20,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Energy Consumption per Device',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w700,
),
Row(
spacing: 32,
children: [
Text(
'Energy Consumption per Device',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w700,
),
),
Spacer(),
Expanded(
flex: 2,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
spacing: 16,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: List.generate(
20,
(index) => Chip(
label: Text('Device ${index + 1}'),
backgroundColor: Colors.blueAccent,
labelStyle: TextStyle(color: Colors.white),
),
).toList(),
),
),
),
],
),
Divider(height: 0),
Expanded(child: EnergyConsumptionPerDeviceChart()),

View File

@ -1,10 +1,8 @@
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/analytics/helpers/format_number_to_kwh.dart';
import 'package:syncrow_web/pages/analytics/helpers/get_month_name_from_int.dart';
import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
// energy_consumption_chart will return id, name and consumption
const phasesJson = {
@ -25,28 +23,11 @@ class TotalEnergyConsumptionChart extends StatelessWidget {
return Expanded(
child: LineChart(
LineChartData(
titlesData: _titlesData(context),
titlesData: EnergyManagementChartsHelper.titlesData(context),
gridData: EnergyManagementChartsHelper.gridData(),
borderData: EnergyManagementChartsHelper.borderData(),
lineTouchData: EnergyManagementChartsHelper.lineTouchData(),
lineBarsData: _lineBarsData,
gridData: FlGridData(
show: true,
drawVerticalLine: false,
drawHorizontalLine: true,
),
borderData: FlBorderData(
show: true,
border: const Border.symmetric(
horizontal: BorderSide(
color: ColorsManager.greyColor,
width: 1,
style: BorderStyle.solid,
),
),
),
lineTouchData: LineTouchData(
handleBuiltInTouches: true,
touchSpotThreshold: 2,
touchTooltipData: _lineTouchTooltipData(),
),
),
duration: Durations.extralong1,
curve: Curves.easeIn,
@ -93,84 +74,4 @@ class TotalEnergyConsumptionChart extends StatelessWidget {
),
];
}
FlTitlesData _titlesData(BuildContext context) {
const emptyTitle = AxisTitles(sideTitles: SideTitles(showTitles: false));
return FlTitlesData(
show: true,
bottomTitles: AxisTitles(
drawBelowEverything: true,
sideTitles: SideTitles(
interval: 1,
reservedSize: 32,
showTitles: true,
maxIncluded: true,
getTitlesWidget: (value, meta) => Padding(
padding: const EdgeInsets.only(top: 20.0),
child: Text(
chartData.elementAtOrNull(value.toInt())?.date.month.getMonthName ??
'',
style: context.textTheme.bodySmall?.copyWith(
fontSize: 12,
color: ColorsManager.greyColor,
),
),
),
),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
maxIncluded: true,
reservedSize: 110,
getTitlesWidget: (value, meta) => Padding(
padding: const EdgeInsetsDirectional.only(end: 12),
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
value.formatNumberToKwh,
style: context.textTheme.bodySmall?.copyWith(
fontSize: 12,
color: ColorsManager.greyColor,
),
),
),
),
),
),
rightTitles: emptyTitle,
topTitles: emptyTitle,
);
}
LineTouchTooltipData _lineTouchTooltipData() {
return LineTouchTooltipData(
getTooltipColor: (touchTooltipItem) => ColorsManager.whiteColors,
tooltipBorder: const BorderSide(color: ColorsManager.semiTransparentBlack),
tooltipRoundedRadius: 16,
showOnTopOfTheChartBoxArea: false,
tooltipPadding: const EdgeInsets.all(8),
getTooltipItems: _getTooltipItems,
);
}
List<LineTooltipItem?> _getTooltipItems(List<LineBarSpot> touchedSpots) {
return touchedSpots.map((spot) {
return LineTooltipItem(
_getToolTipLabel(spot.x + 1, spot.y),
const TextStyle(
color: ColorsManager.textPrimaryColor,
fontWeight: FontWeight.w600,
fontSize: 12,
),
);
}).toList();
}
String _getToolTipLabel(num month, double value) {
final monthLabel = month.getMonthName;
final valueLabel = value.formatNumberToKwh;
final labels = [monthLabel, valueLabel];
return labels.where((element) => element.isNotEmpty).join(', ');
}
}