mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2025-01-07 00:24:18 +00:00
refactor(ui): Refactor charts to remove code duplication
This commit is contained in:
parent
d8d0ea0c3c
commit
149e6d5a47
|
@ -22,3 +22,20 @@ List<Color> harmonizedBasicColors(final BuildContext context) => [
|
||||||
Colors.blueGrey.harmonizeWith(Theme.of(context).colorScheme.primary),
|
Colors.blueGrey.harmonizeWith(Theme.of(context).colorScheme.primary),
|
||||||
Colors.grey.harmonizeWith(Theme.of(context).colorScheme.primary),
|
Colors.grey.harmonizeWith(Theme.of(context).colorScheme.primary),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
List<Color> getGraphColors(final BuildContext context, final int length) {
|
||||||
|
final colors = [
|
||||||
|
Theme.of(context).colorScheme.primary,
|
||||||
|
Theme.of(context).colorScheme.tertiary,
|
||||||
|
Theme.of(context).colorScheme.secondary,
|
||||||
|
...harmonizedBasicColors(context),
|
||||||
|
];
|
||||||
|
if (length <= colors.length) {
|
||||||
|
return colors.sublist(0, length);
|
||||||
|
} else {
|
||||||
|
return List.generate(
|
||||||
|
length,
|
||||||
|
(final index) => colors[index % colors.length],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/metrics.dart';
|
|
||||||
|
|
||||||
String bottomTitle(
|
|
||||||
final int value,
|
|
||||||
final List<TimeSeriesData> data,
|
|
||||||
final Period period,
|
|
||||||
) {
|
|
||||||
final hhmm = DateFormat('HH:mm');
|
|
||||||
final day = DateFormat('MMMd');
|
|
||||||
String res;
|
|
||||||
|
|
||||||
if (value <= 0 || value >= data.length) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
final time = data[value].time;
|
|
||||||
switch (period) {
|
|
||||||
case Period.hour:
|
|
||||||
case Period.day:
|
|
||||||
res = hhmm.format(time);
|
|
||||||
break;
|
|
||||||
case Period.month:
|
|
||||||
res = day.format(time);
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
|
@ -8,23 +8,6 @@ class _Chart extends StatelessWidget {
|
||||||
final MetricsState state = cubit.state;
|
final MetricsState state = cubit.state;
|
||||||
List<Widget> charts;
|
List<Widget> charts;
|
||||||
|
|
||||||
List<Color> getGraphColors(final BuildContext context, final int length) {
|
|
||||||
final colors = [
|
|
||||||
Theme.of(context).colorScheme.primary,
|
|
||||||
Theme.of(context).colorScheme.tertiary,
|
|
||||||
Theme.of(context).colorScheme.secondary,
|
|
||||||
...harmonizedBasicColors(context),
|
|
||||||
];
|
|
||||||
if (length <= colors.length) {
|
|
||||||
return colors.sublist(0, length);
|
|
||||||
} else {
|
|
||||||
return List.generate(
|
|
||||||
length,
|
|
||||||
(final index) => colors[index % colors.length],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state is MetricsLoaded || state is MetricsLoading) {
|
if (state is MetricsLoaded || state is MetricsLoading) {
|
||||||
charts = [
|
charts = [
|
||||||
FilledCard(
|
FilledCard(
|
||||||
|
@ -332,7 +315,7 @@ class _Chart extends StatelessWidget {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: 200,
|
height: 200,
|
||||||
child: CpuChart(
|
child: CpuChart(
|
||||||
data: data,
|
data: [data],
|
||||||
period: state.period,
|
period: state.period,
|
||||||
start: state.metrics.start,
|
start: state.metrics.start,
|
||||||
),
|
),
|
||||||
|
@ -349,7 +332,7 @@ class _Chart extends StatelessWidget {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: 200,
|
height: 200,
|
||||||
child: MemoryChart(
|
child: MemoryChart(
|
||||||
data: data.overallMetrics,
|
data: [data.overallMetrics],
|
||||||
period: state.period,
|
period: state.period,
|
||||||
start: state.metrics.start,
|
start: state.metrics.start,
|
||||||
),
|
),
|
||||||
|
@ -363,7 +346,7 @@ class _Chart extends StatelessWidget {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: 200,
|
height: 200,
|
||||||
child: NetworkChart(
|
child: NetworkChart(
|
||||||
listData: [ppsIn, ppsOut],
|
data: [ppsIn, ppsOut],
|
||||||
period: state.period,
|
period: state.period,
|
||||||
start: state.metrics.start,
|
start: state.metrics.start,
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,45 +1,29 @@
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:fl_chart/fl_chart.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
import 'package:selfprivacy/ui/pages/server_details/charts/generic_chart.dart';
|
||||||
import 'package:selfprivacy/logic/models/metrics.dart';
|
|
||||||
import 'package:selfprivacy/ui/pages/server_details/charts/bottom_title.dart';
|
|
||||||
|
|
||||||
class CpuChart extends StatelessWidget {
|
class CpuChart extends GenericLineChart {
|
||||||
const CpuChart({
|
const CpuChart({
|
||||||
required this.data,
|
required super.data,
|
||||||
required this.period,
|
required super.period,
|
||||||
required this.start,
|
required super.start,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
final List<TimeSeriesData> data;
|
@override
|
||||||
final Period period;
|
|
||||||
final DateTime start;
|
|
||||||
|
|
||||||
List<FlSpot> getSpots() {
|
|
||||||
var i = 0;
|
|
||||||
final List<FlSpot> res = [];
|
|
||||||
|
|
||||||
for (final d in data) {
|
|
||||||
res.add(FlSpot(i.toDouble(), d.value));
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
String screenReaderDescription(final BuildContext context) {
|
String screenReaderDescription(final BuildContext context) {
|
||||||
final lastData = data.last;
|
final lastData = data.first.last;
|
||||||
final lastValue = lastData.value;
|
final lastValue = lastData.value;
|
||||||
|
|
||||||
final averageUsage =
|
final averageUsage = data.first
|
||||||
data.map((final e) => e.value).reduce((final a, final b) => a + b) /
|
.map((final e) => e.value)
|
||||||
data.length;
|
.reduce((final a, final b) => a + b) /
|
||||||
final maxUsage = data
|
data.length;
|
||||||
|
final maxUsage = data.first
|
||||||
.map((final e) => e.value)
|
.map((final e) => e.value)
|
||||||
.reduce((final a, final b) => a > b ? a : b);
|
.reduce((final a, final b) => a > b ? a : b);
|
||||||
final maxUsageTime = data.firstWhere((final e) => e.value == maxUsage).time;
|
final maxUsageTime =
|
||||||
|
data.first.firstWhere((final e) => e.value == maxUsage).time;
|
||||||
|
|
||||||
final label = 'resource_chart.cpu_chart_screen_reader_explanation'.tr(
|
final label = 'resource_chart.cpu_chart_screen_reader_explanation'.tr(
|
||||||
namedArgs: {
|
namedArgs: {
|
||||||
|
@ -54,159 +38,4 @@ class CpuChart extends StatelessWidget {
|
||||||
|
|
||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(final BuildContext context) => Semantics(
|
|
||||||
label: screenReaderDescription(context),
|
|
||||||
child: LineChart(
|
|
||||||
LineChartData(
|
|
||||||
lineTouchData: LineTouchData(
|
|
||||||
enabled: true,
|
|
||||||
touchTooltipData: LineTouchTooltipData(
|
|
||||||
getTooltipColor: (final LineBarSpot _) =>
|
|
||||||
Theme.of(context).colorScheme.surface,
|
|
||||||
tooltipPadding: const EdgeInsets.all(8),
|
|
||||||
getTooltipItems: (final List<LineBarSpot> touchedBarSpots) {
|
|
||||||
final List<LineTooltipItem> res = [];
|
|
||||||
|
|
||||||
for (final spot in touchedBarSpots) {
|
|
||||||
final value = spot.y;
|
|
||||||
final date = data[spot.x.toInt()].time;
|
|
||||||
|
|
||||||
res.add(
|
|
||||||
LineTooltipItem(
|
|
||||||
'${value.toStringAsFixed(2)}% at ${DateFormat('HH:mm dd.MM.yyyy').format(date)}',
|
|
||||||
TextStyle(
|
|
||||||
color: Theme.of(context).colorScheme.onSurface,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
lineBarsData: [
|
|
||||||
LineChartBarData(
|
|
||||||
spots: getSpots(),
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
minY: 0,
|
|
||||||
// Maximal value of data by 100 step
|
|
||||||
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: ExcludeSemantics(
|
|
||||||
child: Text(
|
|
||||||
bottomTitle(
|
|
||||||
value.toInt(),
|
|
||||||
data,
|
|
||||||
period,
|
|
||||||
),
|
|
||||||
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
showTitles: true,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
leftTitles: const AxisTitles(
|
|
||||||
sideTitles: SideTitles(
|
|
||||||
showTitles: false,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
rightTitles: const AxisTitles(
|
|
||||||
sideTitles: SideTitles(
|
|
||||||
showTitles: false,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
gridData: FlGridData(
|
|
||||||
show: true,
|
|
||||||
drawVerticalLine: true,
|
|
||||||
horizontalInterval: 25,
|
|
||||||
verticalInterval: 40,
|
|
||||||
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,
|
|
||||||
final double maxValue,
|
|
||||||
final SideTitles sideTitles,
|
|
||||||
final double appliedInterval,
|
|
||||||
final double value,
|
|
||||||
) {
|
|
||||||
if (value < 0) {
|
|
||||||
return false;
|
|
||||||
} else if (value == 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
final localValue = value - minValue;
|
|
||||||
final v = localValue / 20;
|
|
||||||
return v - v.floor() == 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +1,22 @@
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:fl_chart/fl_chart.dart';
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
import 'package:selfprivacy/ui/pages/server_details/charts/generic_chart.dart';
|
||||||
import 'package:selfprivacy/logic/models/metrics.dart';
|
|
||||||
import 'package:selfprivacy/ui/pages/server_details/charts/bottom_title.dart';
|
|
||||||
import 'package:selfprivacy/ui/pages/server_details/server_details_screen.dart';
|
import 'package:selfprivacy/ui/pages/server_details/server_details_screen.dart';
|
||||||
|
|
||||||
class DiskChart extends StatelessWidget {
|
class DiskChart extends GenericLineChart {
|
||||||
const DiskChart({
|
DiskChart({
|
||||||
required this.diskData,
|
required this.diskData,
|
||||||
required this.period,
|
required super.period,
|
||||||
required this.start,
|
required super.start,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
}) : super(
|
||||||
|
data: diskData.map((final e) => e.diskData).toList(),
|
||||||
|
);
|
||||||
|
|
||||||
final List<DiskGraphData> diskData;
|
final List<DiskGraphData> diskData;
|
||||||
final Period period;
|
|
||||||
final DateTime start;
|
|
||||||
|
|
||||||
List<FlSpot> getSpots(final List<TimeSeriesData> data) {
|
|
||||||
var i = 0;
|
|
||||||
final List<FlSpot> res = [];
|
|
||||||
|
|
||||||
for (final d in data) {
|
|
||||||
res.add(FlSpot(i.toDouble(), d.value));
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@override
|
||||||
String screenReaderDescription(final BuildContext context) {
|
String screenReaderDescription(final BuildContext context) {
|
||||||
final buffer = StringBuffer();
|
final buffer = StringBuffer();
|
||||||
buffer.write(
|
buffer.write(
|
||||||
|
@ -61,159 +48,18 @@ class DiskChart extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(final BuildContext context) => Semantics(
|
LineTooltipItem generateTooltipItem({
|
||||||
label: screenReaderDescription(context),
|
required final bool timeShown,
|
||||||
child: LineChart(
|
required final DateTime date,
|
||||||
LineChartData(
|
required final double value,
|
||||||
lineTouchData: LineTouchData(
|
required final LineBarSpot spot,
|
||||||
enabled: true,
|
required final BuildContext context,
|
||||||
touchTooltipData: LineTouchTooltipData(
|
}) =>
|
||||||
getTooltipColor: (final LineBarSpot _) =>
|
LineTooltipItem(
|
||||||
Theme.of(context).colorScheme.surface,
|
'${timeShown ? '' : DateFormat('HH:mm dd.MM.yyyy').format(date)} ${diskData[spot.barIndex].volume.displayName} ${value.toInt()}%',
|
||||||
tooltipPadding: const EdgeInsets.all(8),
|
TextStyle(
|
||||||
getTooltipItems: (final List<LineBarSpot> touchedBarSpots) {
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
final List<LineTooltipItem> res = [];
|
fontWeight: FontWeight.bold,
|
||||||
|
|
||||||
bool timeShown = false;
|
|
||||||
for (final spot in touchedBarSpots) {
|
|
||||||
final value = spot.y;
|
|
||||||
final date = diskData.first.diskData[spot.x.toInt()].time;
|
|
||||||
|
|
||||||
res.add(
|
|
||||||
LineTooltipItem(
|
|
||||||
'${timeShown ? '' : DateFormat('HH:mm dd.MM.yyyy').format(date)} ${diskData[spot.barIndex].volume.displayName} ${value.toInt()}%',
|
|
||||||
TextStyle(
|
|
||||||
color: Theme.of(context).colorScheme.onSurface,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
timeShown = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
lineBarsData: diskData
|
|
||||||
.map<LineChartBarData>(
|
|
||||||
(final disk) => LineChartBarData(
|
|
||||||
spots: getSpots(disk.diskData),
|
|
||||||
isCurved: false,
|
|
||||||
barWidth: 2,
|
|
||||||
color: disk.color,
|
|
||||||
dotData: const FlDotData(
|
|
||||||
show: false,
|
|
||||||
),
|
|
||||||
belowBarData: BarAreaData(
|
|
||||||
show: true,
|
|
||||||
gradient: LinearGradient(
|
|
||||||
colors: [
|
|
||||||
disk.color.withOpacity(0.5),
|
|
||||||
disk.color.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: ExcludeSemantics(
|
|
||||||
child: Text(
|
|
||||||
bottomTitle(
|
|
||||||
value.toInt(),
|
|
||||||
diskData.first.diskData,
|
|
||||||
period,
|
|
||||||
),
|
|
||||||
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
showTitles: true,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
leftTitles: const AxisTitles(
|
|
||||||
sideTitles: SideTitles(showTitles: false),
|
|
||||||
),
|
|
||||||
rightTitles: const AxisTitles(
|
|
||||||
sideTitles: SideTitles(
|
|
||||||
showTitles: false,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
gridData: FlGridData(
|
|
||||||
show: true,
|
|
||||||
drawVerticalLine: true,
|
|
||||||
verticalInterval: 40,
|
|
||||||
horizontalInterval: 25,
|
|
||||||
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,
|
|
||||||
final double maxValue,
|
|
||||||
final SideTitles sideTitles,
|
|
||||||
final double appliedInterval,
|
|
||||||
final double value,
|
|
||||||
) {
|
|
||||||
if (value < 0) {
|
|
||||||
return false;
|
|
||||||
} else if (value == 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
final diff = value - minValue;
|
|
||||||
final finalValue = diff / 20;
|
|
||||||
return finalValue - finalValue.floor() == 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
261
lib/ui/pages/server_details/charts/generic_chart.dart
Normal file
261
lib/ui/pages/server_details/charts/generic_chart.dart
Normal file
|
@ -0,0 +1,261 @@
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/metrics.dart';
|
||||||
|
import 'package:selfprivacy/theming/harmonized_basic_colors.dart';
|
||||||
|
|
||||||
|
class GenericLineChart extends StatelessWidget {
|
||||||
|
const GenericLineChart({
|
||||||
|
required this.data,
|
||||||
|
required this.period,
|
||||||
|
required this.start,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<List<TimeSeriesData>> data;
|
||||||
|
final Period period;
|
||||||
|
final DateTime start;
|
||||||
|
|
||||||
|
bool get showRightTitle => false;
|
||||||
|
|
||||||
|
static List<FlSpot> getSpots(final List<TimeSeriesData> data) {
|
||||||
|
var i = 0;
|
||||||
|
final List<FlSpot> res = [];
|
||||||
|
|
||||||
|
for (final d in data) {
|
||||||
|
res.add(FlSpot(i.toDouble(), d.value));
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool checkToShowTitle(
|
||||||
|
final double minValue,
|
||||||
|
final double maxValue,
|
||||||
|
final SideTitles sideTitles,
|
||||||
|
final double appliedInterval,
|
||||||
|
final double value,
|
||||||
|
) {
|
||||||
|
if (value < 0) {
|
||||||
|
return false;
|
||||||
|
} else if (value == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
final diff = value - minValue;
|
||||||
|
final finalValue = diff / 20;
|
||||||
|
return finalValue - finalValue.floor() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
String screenReaderDescription(final BuildContext context) =>
|
||||||
|
'Overrite this function';
|
||||||
|
|
||||||
|
LineTooltipItem generateTooltipItem({
|
||||||
|
required final bool timeShown,
|
||||||
|
required final DateTime date,
|
||||||
|
required final double value,
|
||||||
|
required final LineBarSpot spot,
|
||||||
|
required final BuildContext context,
|
||||||
|
}) =>
|
||||||
|
LineTooltipItem(
|
||||||
|
'${value.toStringAsFixed(2)}% at ${DateFormat('HH:mm dd.MM.yyyy').format(date)}',
|
||||||
|
TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
double getMaxY() => 100;
|
||||||
|
|
||||||
|
String getRightTitle(final double value) => '';
|
||||||
|
|
||||||
|
String bottomTitle(
|
||||||
|
final int value,
|
||||||
|
final List<TimeSeriesData> data,
|
||||||
|
final Period period,
|
||||||
|
) {
|
||||||
|
final hhmm = DateFormat('HH:mm');
|
||||||
|
final day = DateFormat('MMMd');
|
||||||
|
String res;
|
||||||
|
|
||||||
|
if (value <= 0 || value >= data.length) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
final time = data[value].time;
|
||||||
|
switch (period) {
|
||||||
|
case Period.hour:
|
||||||
|
case Period.day:
|
||||||
|
res = hhmm.format(time);
|
||||||
|
break;
|
||||||
|
case Period.month:
|
||||||
|
res = day.format(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) {
|
||||||
|
final colors = getGraphColors(context, data.length);
|
||||||
|
return Semantics(
|
||||||
|
label: screenReaderDescription(context),
|
||||||
|
child: LineChart(
|
||||||
|
LineChartData(
|
||||||
|
lineTouchData: LineTouchData(
|
||||||
|
enabled: true,
|
||||||
|
touchTooltipData: LineTouchTooltipData(
|
||||||
|
getTooltipColor: (final LineBarSpot _) =>
|
||||||
|
Theme.of(context).colorScheme.surface,
|
||||||
|
tooltipPadding: const EdgeInsets.all(8),
|
||||||
|
getTooltipItems: (final List<LineBarSpot> touchedBarSpots) {
|
||||||
|
final List<LineTooltipItem> tooltipItems = [];
|
||||||
|
bool timeShown = false;
|
||||||
|
|
||||||
|
for (final spot in touchedBarSpots) {
|
||||||
|
final value = spot.y;
|
||||||
|
final date = data.first[spot.x.toInt()].time;
|
||||||
|
|
||||||
|
tooltipItems.add(
|
||||||
|
generateTooltipItem(
|
||||||
|
timeShown: timeShown,
|
||||||
|
date: date,
|
||||||
|
value: value,
|
||||||
|
spot: spot,
|
||||||
|
context: context,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
timeShown = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return tooltipItems;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
lineBarsData: data
|
||||||
|
.map<LineChartBarData>(
|
||||||
|
(final List<TimeSeriesData> dataSeries) => LineChartBarData(
|
||||||
|
spots: getSpots(dataSeries),
|
||||||
|
isCurved: false,
|
||||||
|
barWidth: 2,
|
||||||
|
color: colors[data.indexOf(dataSeries)],
|
||||||
|
dotData: const FlDotData(
|
||||||
|
show: false,
|
||||||
|
),
|
||||||
|
belowBarData: BarAreaData(
|
||||||
|
show: true,
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [
|
||||||
|
colors[data.indexOf(dataSeries)].withOpacity(0.5),
|
||||||
|
colors[data.indexOf(dataSeries)].withOpacity(0.0),
|
||||||
|
],
|
||||||
|
begin: Alignment.bottomCenter,
|
||||||
|
end: Alignment.topCenter,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
minY: 0,
|
||||||
|
maxY: getMaxY(),
|
||||||
|
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: ExcludeSemantics(
|
||||||
|
child: Text(
|
||||||
|
bottomTitle(value.toInt(), data.first, period),
|
||||||
|
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
||||||
|
color:
|
||||||
|
Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
showTitles: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
leftTitles: const AxisTitles(
|
||||||
|
sideTitles: SideTitles(showTitles: false),
|
||||||
|
),
|
||||||
|
rightTitles: showRightTitle
|
||||||
|
? AxisTitles(
|
||||||
|
sideTitles: SideTitles(
|
||||||
|
reservedSize: 50,
|
||||||
|
getTitlesWidget: (final value, final titleMeta) =>
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 5),
|
||||||
|
child: ExcludeSemantics(
|
||||||
|
child: Text(
|
||||||
|
getRightTitle(value),
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.labelSmall
|
||||||
|
?.copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
interval: getMaxY() * 2 / 6.5,
|
||||||
|
showTitles: true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const AxisTitles(
|
||||||
|
sideTitles: SideTitles(
|
||||||
|
showTitles: false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
gridData: FlGridData(
|
||||||
|
show: true,
|
||||||
|
drawVerticalLine: true,
|
||||||
|
verticalInterval: 40,
|
||||||
|
horizontalInterval: getMaxY() == 100 ? 25 : getMaxY() * 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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,45 +1,29 @@
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:fl_chart/fl_chart.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
import 'package:selfprivacy/ui/pages/server_details/charts/generic_chart.dart';
|
||||||
import 'package:selfprivacy/logic/models/metrics.dart';
|
|
||||||
import 'package:selfprivacy/ui/pages/server_details/charts/bottom_title.dart';
|
|
||||||
|
|
||||||
class MemoryChart extends StatelessWidget {
|
class MemoryChart extends GenericLineChart {
|
||||||
const MemoryChart({
|
const MemoryChart({
|
||||||
required this.data,
|
required super.data,
|
||||||
required this.period,
|
required super.period,
|
||||||
required this.start,
|
required super.start,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
final List<TimeSeriesData> data;
|
@override
|
||||||
final Period period;
|
|
||||||
final DateTime start;
|
|
||||||
|
|
||||||
List<FlSpot> getSpots() {
|
|
||||||
var i = 0;
|
|
||||||
final List<FlSpot> res = [];
|
|
||||||
|
|
||||||
for (final d in data) {
|
|
||||||
res.add(FlSpot(i.toDouble(), d.value));
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
String screenReaderDescription(final BuildContext context) {
|
String screenReaderDescription(final BuildContext context) {
|
||||||
final lastData = data.last;
|
final lastData = data.first.last;
|
||||||
final lastValue = lastData.value;
|
final lastValue = lastData.value;
|
||||||
|
|
||||||
final averageUsage =
|
final averageUsage = data.first
|
||||||
data.map((final e) => e.value).reduce((final a, final b) => a + b) /
|
.map((final e) => e.value)
|
||||||
data.length;
|
.reduce((final a, final b) => a + b) /
|
||||||
final maxUsage = data
|
data.length;
|
||||||
|
final maxUsage = data.first
|
||||||
.map((final e) => e.value)
|
.map((final e) => e.value)
|
||||||
.reduce((final a, final b) => a > b ? a : b);
|
.reduce((final a, final b) => a > b ? a : b);
|
||||||
final maxUsageTime = data.firstWhere((final e) => e.value == maxUsage).time;
|
final maxUsageTime =
|
||||||
|
data.first.firstWhere((final e) => e.value == maxUsage).time;
|
||||||
|
|
||||||
final label = 'resource_chart.memory_chart_screen_reader_explanation'.tr(
|
final label = 'resource_chart.memory_chart_screen_reader_explanation'.tr(
|
||||||
namedArgs: {
|
namedArgs: {
|
||||||
|
@ -54,159 +38,4 @@ class MemoryChart extends StatelessWidget {
|
||||||
|
|
||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(final BuildContext context) => Semantics(
|
|
||||||
label: screenReaderDescription(context),
|
|
||||||
child: LineChart(
|
|
||||||
LineChartData(
|
|
||||||
lineTouchData: LineTouchData(
|
|
||||||
enabled: true,
|
|
||||||
touchTooltipData: LineTouchTooltipData(
|
|
||||||
getTooltipColor: (final LineBarSpot _) =>
|
|
||||||
Theme.of(context).colorScheme.surface,
|
|
||||||
tooltipPadding: const EdgeInsets.all(8),
|
|
||||||
getTooltipItems: (final List<LineBarSpot> touchedBarSpots) {
|
|
||||||
final List<LineTooltipItem> res = [];
|
|
||||||
|
|
||||||
for (final spot in touchedBarSpots) {
|
|
||||||
final value = spot.y;
|
|
||||||
final date = data[spot.x.toInt()].time;
|
|
||||||
|
|
||||||
res.add(
|
|
||||||
LineTooltipItem(
|
|
||||||
'${value.toStringAsFixed(2)}% at ${DateFormat('HH:mm dd.MM.yyyy').format(date)}',
|
|
||||||
TextStyle(
|
|
||||||
color: Theme.of(context).colorScheme.onSurface,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
lineBarsData: [
|
|
||||||
LineChartBarData(
|
|
||||||
spots: getSpots(),
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
minY: 0,
|
|
||||||
// Maximal value of data by 100 step
|
|
||||||
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: ExcludeSemantics(
|
|
||||||
child: Text(
|
|
||||||
bottomTitle(
|
|
||||||
value.toInt(),
|
|
||||||
data,
|
|
||||||
period,
|
|
||||||
),
|
|
||||||
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
showTitles: true,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
leftTitles: const AxisTitles(
|
|
||||||
sideTitles: SideTitles(
|
|
||||||
showTitles: false,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
rightTitles: const AxisTitles(
|
|
||||||
sideTitles: SideTitles(
|
|
||||||
showTitles: false,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
gridData: FlGridData(
|
|
||||||
show: true,
|
|
||||||
drawVerticalLine: true,
|
|
||||||
horizontalInterval: 25,
|
|
||||||
verticalInterval: 40,
|
|
||||||
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,
|
|
||||||
final double maxValue,
|
|
||||||
final SideTitles sideTitles,
|
|
||||||
final double appliedInterval,
|
|
||||||
final double value,
|
|
||||||
) {
|
|
||||||
if (value < 0) {
|
|
||||||
return false;
|
|
||||||
} else if (value == 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
final localValue = value - minValue;
|
|
||||||
final v = localValue / 20;
|
|
||||||
return v - v.floor() == 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,61 +3,45 @@ import 'dart:math';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:fl_chart/fl_chart.dart';
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
|
||||||
import 'package:selfprivacy/logic/models/disk_size.dart';
|
import 'package:selfprivacy/logic/models/disk_size.dart';
|
||||||
import 'package:selfprivacy/logic/models/metrics.dart';
|
import 'package:selfprivacy/ui/pages/server_details/charts/generic_chart.dart';
|
||||||
import 'package:selfprivacy/ui/pages/server_details/charts/bottom_title.dart';
|
|
||||||
|
|
||||||
class NetworkChart extends StatelessWidget {
|
class NetworkChart extends GenericLineChart {
|
||||||
const NetworkChart({
|
const NetworkChart({
|
||||||
required this.listData,
|
required super.data,
|
||||||
required this.period,
|
required super.period,
|
||||||
required this.start,
|
required super.start,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
final List<List<TimeSeriesData>> listData;
|
@override
|
||||||
final Period period;
|
bool get showRightTitle => true;
|
||||||
final DateTime start;
|
|
||||||
|
|
||||||
List<FlSpot> getSpots(final data) {
|
|
||||||
var i = 0;
|
|
||||||
final List<FlSpot> res = [];
|
|
||||||
|
|
||||||
for (final d in data) {
|
|
||||||
res.add(FlSpot(i.toDouble(), d.value));
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@override
|
||||||
String screenReaderDescription(final BuildContext context) {
|
String screenReaderDescription(final BuildContext context) {
|
||||||
final lastDataIn = listData[0].last;
|
final lastDataIn = data[0].last;
|
||||||
final lastDataOut = listData[1].last;
|
final lastDataOut = data[1].last;
|
||||||
final lastValueIn = lastDataIn.value;
|
final lastValueIn = lastDataIn.value;
|
||||||
final lastValueOut = lastDataOut.value;
|
final lastValueOut = lastDataOut.value;
|
||||||
|
|
||||||
final averageUsageIn = listData[0]
|
final averageUsageIn =
|
||||||
.map((final e) => e.value)
|
data[0].map((final e) => e.value).reduce((final a, final b) => a + b) /
|
||||||
.reduce((final a, final b) => a + b) /
|
data[0].length;
|
||||||
listData[0].length;
|
final averageUsageOut =
|
||||||
final averageUsageOut = listData[1]
|
data[1].map((final e) => e.value).reduce((final a, final b) => a + b) /
|
||||||
.map((final e) => e.value)
|
data[1].length;
|
||||||
.reduce((final a, final b) => a + b) /
|
|
||||||
listData[1].length;
|
|
||||||
|
|
||||||
final maxUsageIn = listData[0]
|
final maxUsageIn = data[0]
|
||||||
.map((final e) => e.value)
|
.map((final e) => e.value)
|
||||||
.reduce((final a, final b) => a > b ? a : b);
|
.reduce((final a, final b) => a > b ? a : b);
|
||||||
final maxUsageOut = listData[1]
|
final maxUsageOut = data[1]
|
||||||
.map((final e) => e.value)
|
.map((final e) => e.value)
|
||||||
.reduce((final a, final b) => a > b ? a : b);
|
.reduce((final a, final b) => a > b ? a : b);
|
||||||
|
|
||||||
final maxUsageTimeIn =
|
final maxUsageTimeIn =
|
||||||
listData[0].firstWhere((final e) => e.value == maxUsageIn).time;
|
data[0].firstWhere((final e) => e.value == maxUsageIn).time;
|
||||||
final maxUsageTimeOut =
|
final maxUsageTimeOut =
|
||||||
listData[1].firstWhere((final e) => e.value == maxUsageOut).time;
|
data[1].firstWhere((final e) => e.value == maxUsageOut).time;
|
||||||
|
|
||||||
final label = 'resource_chart.network_chart_screen_reader_explanation'.tr(
|
final label = 'resource_chart.network_chart_screen_reader_explanation'.tr(
|
||||||
namedArgs: {
|
namedArgs: {
|
||||||
|
@ -81,199 +65,30 @@ class NetworkChart extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(final BuildContext context) {
|
double getMaxY() =>
|
||||||
final listDataMax = [
|
[
|
||||||
...listData[0].map((final e) => e.value),
|
...data[0].map((final e) => e.value),
|
||||||
...listData[1].map((final e) => e.value),
|
...data[1].map((final e) => e.value),
|
||||||
].reduce(max);
|
].reduce(max) *
|
||||||
return Semantics(
|
1.2;
|
||||||
label: screenReaderDescription(context),
|
|
||||||
child: LineChart(
|
|
||||||
LineChartData(
|
|
||||||
lineTouchData: LineTouchData(
|
|
||||||
enabled: true,
|
|
||||||
touchTooltipData: LineTouchTooltipData(
|
|
||||||
getTooltipColor: (final LineBarSpot _) =>
|
|
||||||
Theme.of(context).colorScheme.surface,
|
|
||||||
tooltipPadding: const EdgeInsets.all(8),
|
|
||||||
getTooltipItems: (final List<LineBarSpot> touchedBarSpots) {
|
|
||||||
final List<LineTooltipItem> res = [];
|
|
||||||
|
|
||||||
bool timeShown = false;
|
@override
|
||||||
|
LineTooltipItem generateTooltipItem({
|
||||||
for (final spot in touchedBarSpots) {
|
required final bool timeShown,
|
||||||
final value = spot.y;
|
required final DateTime date,
|
||||||
final date = listData[0][spot.x.toInt()].time;
|
required final double value,
|
||||||
|
required final LineBarSpot spot,
|
||||||
res.add(
|
required final BuildContext context,
|
||||||
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()}',
|
LineTooltipItem(
|
||||||
TextStyle(
|
'${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()}',
|
||||||
color: Theme.of(context).colorScheme.onSurface,
|
TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
),
|
fontWeight: FontWeight.bold,
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
timeShown = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
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: listDataMax * 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: ExcludeSemantics(
|
|
||||||
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: ExcludeSemantics(
|
|
||||||
child: Text(
|
|
||||||
DiskSize(byte: value.toInt()).toString(),
|
|
||||||
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
|
||||||
color:
|
|
||||||
Theme.of(context).colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
interval: listDataMax * 2 / 6.5,
|
|
||||||
showTitles: true,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
gridData: FlGridData(
|
|
||||||
show: true,
|
|
||||||
drawVerticalLine: true,
|
|
||||||
verticalInterval: 40,
|
|
||||||
horizontalInterval: listDataMax * 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(
|
@override
|
||||||
final double minValue,
|
String getRightTitle(final double value) =>
|
||||||
final double maxValue,
|
DiskSize(byte: value.toInt()).toString();
|
||||||
final SideTitles sideTitles,
|
|
||||||
final double appliedInterval,
|
|
||||||
final double value,
|
|
||||||
) {
|
|
||||||
if (value < 0) {
|
|
||||||
return false;
|
|
||||||
} else if (value == 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
final diff = value - minValue;
|
|
||||||
final finalValue = diff / 20;
|
|
||||||
return finalValue - finalValue.floor() == 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue