From bafd2b4d1319a25cca0f46b0b1aa715b14c9c03d Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 4 May 2025 10:46:12 +0300 Subject: [PATCH] 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. --- .../energy_management_charts_helper.dart | 118 +++++++++++++ .../energy_consumption_per_device_chart.dart | 158 ++++++------------ ...ergy_consumption_per_device_chart_box.dart | 40 ++++- .../total_energy_consumption_chart.dart | 109 +----------- 4 files changed, 208 insertions(+), 217 deletions(-) create mode 100644 lib/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart diff --git a/lib/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart b/lib/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart new file mode 100644 index 00000000..80e62ad3 --- /dev/null +++ b/lib/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart @@ -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 getTooltipItems(List 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(), + ); + } +} diff --git a/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart.dart b/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart.dart index 9d2f7066..b48ee453 100644 --- a/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart.dart +++ b/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart.dart @@ -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 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 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 spots, + }) { + return LineChartBarData( + spots: spots, + dashArray: [12, 18], + isCurved: true, + color: color, + barWidth: 4, + isStrokeCapRound: true, + dotData: const FlDotData(show: false), ); } } diff --git a/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart_box.dart b/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart_box.dart index 2aba8cea..4bc4306e 100644 --- a/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart_box.dart +++ b/lib/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart_box.dart @@ -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()), diff --git a/lib/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart.dart b/lib/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart.dart index ac4bc4e5..0ccc5d00 100644 --- a/lib/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart.dart +++ b/lib/pages/analytics/modules/energy_management/widgets/total_energy_consumption_chart.dart @@ -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 _getTooltipItems(List 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(', '); - } }