From bc121aa7ed7f25ac0f510b4f3d978a9c2b1d666c Mon Sep 17 00:00:00 2001 From: NaiJi Date: Mon, 5 Aug 2024 12:43:52 +0400 Subject: [PATCH] feat(charts): Implement disk usage chart for server screen --- assets/translations/en.json | 1 + lib/ui/pages/server_details/charts/chart.dart | 24 +- .../server_details/charts/disk_charts.dart | 310 ++++++++---------- 3 files changed, 155 insertions(+), 180 deletions(-) diff --git a/assets/translations/en.json b/assets/translations/en.json index 06c982d2..0c4e4cd6 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -136,6 +136,7 @@ "day": "Day", "hour": "Hour", "cpu_title": "CPU Usage", + "disk_title": "Disk Usage", "network_title": "Network Usage", "in": "In", "out": "Out", diff --git a/lib/ui/pages/server_details/charts/chart.dart b/lib/ui/pages/server_details/charts/chart.dart index dc060bfb..666d3bd5 100644 --- a/lib/ui/pages/server_details/charts/chart.dart +++ b/lib/ui/pages/server_details/charts/chart.dart @@ -161,7 +161,7 @@ class _Chart extends StatelessWidget { children: [ Flexible( child: Text( - 'resource_chart.network_title'.tr(), + 'resource_chart.disk_title'.tr(), style: Theme.of(context).textTheme.titleMedium?.copyWith( color: Theme.of(context) @@ -177,16 +177,16 @@ class _Chart extends StatelessWidget { runSpacing: 8.0, alignment: WrapAlignment.end, runAlignment: WrapAlignment.end, - children: [ - Legend( - color: Theme.of(context).colorScheme.primary, - text: 'resource_chart.in'.tr(), - ), - Legend( - color: Theme.of(context).colorScheme.tertiary, - text: 'resource_chart.out'.tr(), - ), - ], + children: state.diskMetrics?.diskMetrics.keys + .map( + (final diskId) => Legend( + color: + Theme.of(context).colorScheme.primary, + text: diskId, + ), + ) + .toList() ?? + [], ), ), ], @@ -321,7 +321,7 @@ Widget getDiskChart(final MetricsLoaded state) { return SizedBox( height: 200, child: DiskChart( - listData: data.diskMetrics.values.toList(), + diskData: data.diskMetrics, period: state.period, start: state.metrics.start, ), diff --git a/lib/ui/pages/server_details/charts/disk_charts.dart b/lib/ui/pages/server_details/charts/disk_charts.dart index 9e170ea1..c0901d03 100644 --- a/lib/ui/pages/server_details/charts/disk_charts.dart +++ b/lib/ui/pages/server_details/charts/disk_charts.dart @@ -10,17 +10,17 @@ import 'package:selfprivacy/ui/pages/server_details/charts/bottom_title.dart'; class DiskChart extends StatelessWidget { const DiskChart({ - required this.listData, + required this.diskData, required this.period, required this.start, super.key, }); - final List> listData; + final Map> diskData; final Period period; final DateTime start; - List getSpots(final data) { + List getSpots(final List data) { var i = 0; final List res = []; @@ -33,182 +33,156 @@ class DiskChart extends StatelessWidget { } @override - Widget build(final BuildContext context) => LineChart( - LineChartData( - lineTouchData: LineTouchData( - enabled: true, - touchTooltipData: LineTouchTooltipData( - getTooltipColor: (final LineBarSpot _) => - Theme.of(context).colorScheme.surface, - tooltipPadding: const EdgeInsets.all(8), - getTooltipItems: (final List touchedBarSpots) { - final List res = []; + Widget build(final BuildContext context) { + final anyDiskId = diskData.keys.toList()[0]; + final diskDataMax = [ + ...diskData.values.map>( + (final timeData) => timeData.map((final e) => e.value).toList(), + ), + ].expand((final x) => x).reduce(max); + return LineChart( + LineChartData( + lineTouchData: LineTouchData( + enabled: true, + touchTooltipData: LineTouchTooltipData( + getTooltipColor: (final LineBarSpot _) => + Theme.of(context).colorScheme.surface, + tooltipPadding: const EdgeInsets.all(8), + getTooltipItems: (final List touchedBarSpots) { + final List res = []; - bool timeShown = false; + bool timeShown = false; + for (final spot in touchedBarSpots) { + final value = spot.y; + final date = diskData[anyDiskId]![spot.x.toInt()].time; - for (final spot in touchedBarSpots) { - final value = spot.y; - final date = listData[0][spot.x.toInt()].time; + res.add( + LineTooltipItem( + '${timeShown ? '' : DateFormat('HH:mm dd.MM.yyyy').format(date)} ${diskData.keys.toList()[spot.barIndex]} ${value.toInt()}%', + TextStyle( + color: Theme.of(context).colorScheme.onSurface, + fontWeight: FontWeight.bold, + ), + ), + ); - res.add( - LineTooltipItem( - '${timeShown ? '' : DateFormat('HH:mm dd.MM.yyyy').format(date)} ${spot.barIndex == 0 ? 'resource_chart.in'.tr() : 'resource_chart.out'.tr()} ${DiskSize(byte: value.toInt()).toString()}', - TextStyle( - color: Theme.of(context).colorScheme.onSurface, - fontWeight: FontWeight.bold, + timeShown = true; + } + + return res; + }, + ), + ), + lineBarsData: diskData.values + .toList() + .map( + (final timeData) => LineChartBarData( + spots: getSpots(timeData), + isCurved: false, + barWidth: 2, + color: Theme.of(context).colorScheme.primary, + dotData: const FlDotData( + show: false, + ), + belowBarData: BarAreaData( + show: true, + gradient: LinearGradient( + colors: [ + Theme.of(context).colorScheme.primary.withOpacity(0.5), + Theme.of(context).colorScheme.primary.withOpacity(0.0), + ], + begin: Alignment.bottomCenter, + end: Alignment.topCenter, + ), + ), + ), + ) + .toList(), + minY: 0, + maxY: 100, + minX: 0, + titlesData: FlTitlesData( + topTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + interval: 40, + reservedSize: 30, + getTitlesWidget: (final value, final titleMeta) => Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + bottomTitle( + value.toInt(), + diskData[anyDiskId]!, + period, + ), + style: Theme.of(context).textTheme.labelSmall?.copyWith( + color: Theme.of(context).colorScheme.onSurfaceVariant, ), - ), - ); - - timeShown = true; - } - - return res; - }, + ), + ), + showTitles: true, ), ), - lineBarsData: [ - // IN - LineChartBarData( - spots: getSpots(listData[0]), - isCurved: false, - barWidth: 2, - color: Theme.of(context).colorScheme.primary, - dotData: const FlDotData( - show: false, - ), - belowBarData: BarAreaData( - show: true, - gradient: LinearGradient( - colors: [ - Theme.of(context).colorScheme.primary.withOpacity(0.5), - Theme.of(context).colorScheme.primary.withOpacity(0.0), - ], - begin: Alignment.bottomCenter, - end: Alignment.topCenter, - ), - ), - ), - // OUT - LineChartBarData( - spots: getSpots(listData[1]), - isCurved: false, - barWidth: 2, - color: Theme.of(context).colorScheme.tertiary, - dotData: const FlDotData( - show: false, - ), - belowBarData: BarAreaData( - show: true, - gradient: LinearGradient( - colors: [ - Theme.of(context).colorScheme.tertiary.withOpacity(0.5), - Theme.of(context).colorScheme.tertiary.withOpacity(0.0), - ], - begin: Alignment.bottomCenter, - end: Alignment.topCenter, - ), - ), - ), - ], - minY: 0, - maxY: [ - ...listData[0].map((final e) => e.value), - ...listData[1].map((final e) => e.value), - ].reduce(max) * - 1.2, - minX: 0, - titlesData: FlTitlesData( - topTitles: const AxisTitles( - sideTitles: SideTitles(showTitles: false), - ), - bottomTitles: AxisTitles( - sideTitles: SideTitles( - interval: 40, - reservedSize: 30, - getTitlesWidget: (final value, final titleMeta) => Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - bottomTitle( - value.toInt(), - listData[0], - period, - ), - style: Theme.of(context).textTheme.labelSmall?.copyWith( - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - ), - showTitles: true, - ), - ), - leftTitles: const AxisTitles( - sideTitles: SideTitles(showTitles: false), - ), - rightTitles: AxisTitles( - sideTitles: SideTitles( - reservedSize: 50, - getTitlesWidget: (final value, final titleMeta) => Padding( - padding: const EdgeInsets.only(left: 5), - child: Text( - DiskSize(byte: value.toInt()).toString(), - style: Theme.of(context).textTheme.labelSmall?.copyWith( - color: Theme.of(context).colorScheme.onSurfaceVariant, - ), - ), - ), - interval: [ - ...listData[0].map((final e) => e.value), - ...listData[1].map((final e) => e.value), - ].reduce(max) * - 2 / - 6.5, - showTitles: true, - ), - ), + leftTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false), ), - gridData: FlGridData( - show: true, - drawVerticalLine: true, - verticalInterval: 40, - horizontalInterval: [ - ...listData[0].map((final e) => e.value), - ...listData[1].map((final e) => e.value), - ].reduce(max) * - 2 / - 6.5, - getDrawingHorizontalLine: (final value) => FlLine( - color: Theme.of(context).colorScheme.outline.withOpacity(0.3), - strokeWidth: 1, - ), - getDrawingVerticalLine: (final value) => FlLine( - color: Theme.of(context).colorScheme.outline.withOpacity(0.3), - strokeWidth: 1, - ), - ), - borderData: FlBorderData( - show: true, - border: Border( - bottom: BorderSide( - color: Theme.of(context).colorScheme.outline.withOpacity(0.3), - width: 1, - ), - left: BorderSide( - color: Theme.of(context).colorScheme.outline.withOpacity(0.3), - width: 1, - ), - right: BorderSide( - color: Theme.of(context).colorScheme.outline.withOpacity(0.3), - width: 1, - ), - top: BorderSide( - color: Theme.of(context).colorScheme.outline.withOpacity(0.3), - width: 1, + rightTitles: AxisTitles( + sideTitles: SideTitles( + reservedSize: 50, + getTitlesWidget: (final value, final titleMeta) => Padding( + padding: const EdgeInsets.only(left: 5), + child: Text( + '${value.toInt()}%', + style: Theme.of(context).textTheme.labelSmall?.copyWith( + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), ), + interval: diskDataMax * 2 / 6.5, + showTitles: true, ), ), ), - ); + gridData: FlGridData( + show: true, + drawVerticalLine: true, + verticalInterval: 40, + horizontalInterval: diskDataMax * 2 / 6.5, + getDrawingHorizontalLine: (final value) => FlLine( + color: Theme.of(context).colorScheme.outline.withOpacity(0.3), + strokeWidth: 1, + ), + getDrawingVerticalLine: (final value) => FlLine( + color: Theme.of(context).colorScheme.outline.withOpacity(0.3), + strokeWidth: 1, + ), + ), + borderData: FlBorderData( + show: true, + border: Border( + bottom: BorderSide( + color: Theme.of(context).colorScheme.outline.withOpacity(0.3), + width: 1, + ), + left: BorderSide( + color: Theme.of(context).colorScheme.outline.withOpacity(0.3), + width: 1, + ), + right: BorderSide( + color: Theme.of(context).colorScheme.outline.withOpacity(0.3), + width: 1, + ), + top: BorderSide( + color: Theme.of(context).colorScheme.outline.withOpacity(0.3), + width: 1, + ), + ), + ), + ), + ); + } bool checkToShowTitle( final double minValue,