mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2024-11-19 07:09:14 +00:00
feat(accessibility): Add screen reader descriptions for graphs
This commit is contained in:
parent
100ad5c367
commit
d01df51296
|
@ -136,16 +136,24 @@
|
|||
"day": "Day",
|
||||
"hour": "Hour",
|
||||
"cpu_title": "CPU Usage",
|
||||
"cpu_chart_screen_reader_explanation": "This chart shows the CPU usage in last {period}. The last value is at {lastValue}%. The average usage is at {averageUsage}%. The maximal usage is at {maxUsage}% at {maxUsageTime}.",
|
||||
"disk_title": "Disk Usage",
|
||||
"disk_chart_screen_reader_explanation": {
|
||||
"beginning": "This chart shows the disk usage in last {period}. ",
|
||||
"disk": "For {disk} the consumption went from {beginningValue}% to {endValue}%. "
|
||||
},
|
||||
"network_title": "Network Usage",
|
||||
"network_chart_screen_reader_explanation": "This chart shows the network usage in last {period}. The last value is at {lastValueIn} inbound and {lastValueOut} outbound. The average usage is at {averageUsageIn} inbound and {averageUsageOut} outbound. The maximal inbound usage is at {maxUsageIn} at {maxUsageTimeIn} and maximal outbound {maxUsageOut} at {maxUsageTimeOut} out.",
|
||||
"in": "In",
|
||||
"out": "Out",
|
||||
"memory": "Memory usage",
|
||||
"memory_chart_screen_reader_explanation": "This chart shows the memory usage in last {period}. The last value is at {lastValue}%. The average usage is at {averageUsage}%. The maximal usage is at {maxUsage}% at {maxUsageTime}.",
|
||||
"view_usage_by_service": "View usage by service",
|
||||
"unsupported": "You can't view resource usage charts without the server provider token.",
|
||||
"system": "System",
|
||||
"ssh_users": "SSH users",
|
||||
"ram_usage": "Average usage: {average}. Maximum: {max}.",
|
||||
"loading": "The chart is loading…",
|
||||
"failed_to_load_memory_metrics": "Couldn't load memory usage data. This might be a connection issue, or there is not enough data yet."
|
||||
},
|
||||
"server": {
|
||||
|
|
|
@ -136,6 +136,7 @@
|
|||
"day": "День",
|
||||
"hour": "Час",
|
||||
"cpu_title": "Использование процессора",
|
||||
"cpu_chart_screen_reader_explanation": "Этот график показывает использование процессора за последний {period}. Последнее значение {lastValue}%. Среднее использование {averageUsage}%. Максимальное использование было {maxUsage}% в {maxUsageTime}.",
|
||||
"network_title": "Использование сети",
|
||||
"in": "Получено",
|
||||
"out": "Отправлено",
|
||||
|
|
|
@ -7,6 +7,7 @@ class FilledCard extends StatelessWidget {
|
|||
this.tertiary = false,
|
||||
this.error = false,
|
||||
this.clipped = true,
|
||||
this.mergeSemantics = true,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
@ -15,6 +16,7 @@ class FilledCard extends StatelessWidget {
|
|||
final bool error;
|
||||
final bool clipped;
|
||||
final bool secondary;
|
||||
final bool mergeSemantics;
|
||||
@override
|
||||
Widget build(final BuildContext context) => Card(
|
||||
elevation: 0.0,
|
||||
|
@ -29,6 +31,7 @@ class FilledCard extends StatelessWidget {
|
|||
: tertiary
|
||||
? Theme.of(context).colorScheme.tertiaryContainer
|
||||
: Theme.of(context).colorScheme.surfaceVariant,
|
||||
semanticContainer: mergeSemantics,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -75,6 +75,7 @@ class _BrandFabState extends State<BrandFab>
|
|||
);
|
||||
},
|
||||
isExtended: widget.extended,
|
||||
tooltip: 'jobs.title'.tr(),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
|
|
|
@ -60,6 +60,7 @@ class _Chart extends StatelessWidget {
|
|||
if (!(state is MetricsLoaded && state.memoryMetrics == null))
|
||||
FilledCard(
|
||||
clipped: false,
|
||||
mergeSemantics: false,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
|
@ -110,42 +111,44 @@ class _Chart extends StatelessWidget {
|
|||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
'resource_chart.network_title'.tr(),
|
||||
style:
|
||||
Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurfaceVariant,
|
||||
),
|
||||
ExcludeSemantics(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
'resource_chart.network_title'.tr(),
|
||||
style:
|
||||
Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
fit: FlexFit.loose,
|
||||
child: Wrap(
|
||||
spacing: 8.0,
|
||||
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(),
|
||||
),
|
||||
],
|
||||
Flexible(
|
||||
fit: FlexFit.loose,
|
||||
child: Wrap(
|
||||
spacing: 8.0,
|
||||
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(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Stack(
|
||||
|
@ -205,42 +208,44 @@ class _Chart extends StatelessWidget {
|
|||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
'resource_chart.disk_title'.tr(),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium
|
||||
?.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
fit: FlexFit.loose,
|
||||
child: Wrap(
|
||||
spacing: 8.0,
|
||||
runSpacing: 8.0,
|
||||
alignment: WrapAlignment.end,
|
||||
runAlignment: WrapAlignment.end,
|
||||
children: disksGraphData
|
||||
.map<Widget>(
|
||||
(final disk) => Legend(
|
||||
color: disk.color,
|
||||
text: disk.volume.displayName,
|
||||
ExcludeSemantics(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
'resource_chart.disk_title'.tr(),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium
|
||||
?.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurfaceVariant,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
Flexible(
|
||||
fit: FlexFit.loose,
|
||||
child: Wrap(
|
||||
spacing: 8.0,
|
||||
runSpacing: 8.0,
|
||||
alignment: WrapAlignment.end,
|
||||
runAlignment: WrapAlignment.end,
|
||||
children: disksGraphData
|
||||
.map<Widget>(
|
||||
(final disk) => Legend(
|
||||
color: disk.color,
|
||||
text: disk.volume.displayName,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Stack(
|
||||
|
@ -390,9 +395,12 @@ class _GraphLoadingCardContent extends StatelessWidget {
|
|||
const _GraphLoadingCardContent();
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => const SizedBox(
|
||||
Widget build(final BuildContext context) => SizedBox(
|
||||
height: 200,
|
||||
child: Center(child: CircularProgressIndicator.adaptive()),
|
||||
child: Semantics(
|
||||
label: 'resource_chart.loading'.tr(),
|
||||
child: const Center(child: CircularProgressIndicator.adaptive()),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||
import 'package:selfprivacy/logic/models/metrics.dart';
|
||||
import 'package:selfprivacy/ui/pages/server_details/charts/bottom_title.dart';
|
||||
|
@ -29,130 +29,163 @@ class CpuChart extends StatelessWidget {
|
|||
return res;
|
||||
}
|
||||
|
||||
String screenReaderDescription(final BuildContext context) {
|
||||
final lastData = data.last;
|
||||
final lastValue = lastData.value;
|
||||
|
||||
final averageUsage =
|
||||
data.map((final e) => e.value).reduce((final a, final b) => a + b) /
|
||||
data.length;
|
||||
final maxUsage = data
|
||||
.map((final e) => e.value)
|
||||
.reduce((final a, final b) => a > b ? a : b);
|
||||
final maxUsageTime = data.firstWhere((final e) => e.value == maxUsage).time;
|
||||
|
||||
final label = 'resource_chart.cpu_chart_screen_reader_explanation'.tr(
|
||||
namedArgs: {
|
||||
'period': 'resource_chart.${period.name}'.tr(),
|
||||
'lastValue': lastValue.toStringAsFixed(1),
|
||||
'averageUsage': averageUsage.toStringAsFixed(1),
|
||||
'maxUsage': maxUsage.toStringAsFixed(1),
|
||||
'maxUsageTime': DateFormat('HH:mm dd MMMM', context.locale.languageCode)
|
||||
.format(maxUsageTime),
|
||||
},
|
||||
);
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
@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<LineBarSpot> touchedBarSpots) {
|
||||
final List<LineTooltipItem> res = [];
|
||||
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;
|
||||
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: Text(
|
||||
bottomTitle(
|
||||
value.toInt(),
|
||||
data,
|
||||
period,
|
||||
),
|
||||
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
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,
|
||||
),
|
||||
),
|
||||
showTitles: true,
|
||||
),
|
||||
],
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
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(
|
||||
gridData: FlGridData(
|
||||
show: true,
|
||||
drawVerticalLine: true,
|
||||
horizontalInterval: 25,
|
||||
verticalInterval: 40,
|
||||
getDrawingHorizontalLine: (final value) => FlLine(
|
||||
color: Theme.of(context).colorScheme.outline.withOpacity(0.3),
|
||||
width: 1,
|
||||
strokeWidth: 1,
|
||||
),
|
||||
left: BorderSide(
|
||||
getDrawingVerticalLine: (final value) => FlLine(
|
||||
color: Theme.of(context).colorScheme.outline.withOpacity(0.3),
|
||||
width: 1,
|
||||
strokeWidth: 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,
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -32,6 +32,36 @@ class DiskChart extends StatelessWidget {
|
|||
return res;
|
||||
}
|
||||
|
||||
String screenReaderDescription(final BuildContext context) {
|
||||
final buffer = StringBuffer();
|
||||
buffer.write(
|
||||
'resource_chart.disk_chart_screen_reader_explanation.beginning'.tr(
|
||||
namedArgs: {
|
||||
'period': 'resource_chart.${period.name}'.tr(),
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
for (final disk in diskData) {
|
||||
final lastData = disk.diskData.last;
|
||||
final lastValue = lastData.value;
|
||||
|
||||
final firstData = disk.diskData.first;
|
||||
final firstValue = firstData.value;
|
||||
|
||||
buffer.write(
|
||||
'resource_chart.disk_chart_screen_reader_explanation.disk'.tr(
|
||||
namedArgs: {
|
||||
'disk': disk.volume.displayName,
|
||||
'beginningValue': firstValue.toStringAsFixed(1),
|
||||
'endValue': lastValue.toStringAsFixed(1),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
final diskDataMax = [
|
||||
|
@ -39,142 +69,137 @@ class DiskChart extends StatelessWidget {
|
|||
(final disk) => disk.diskData.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<LineBarSpot> touchedBarSpots) {
|
||||
final List<LineTooltipItem> res = [];
|
||||
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> res = [];
|
||||
|
||||
bool timeShown = false;
|
||||
for (final spot in touchedBarSpots) {
|
||||
final value = spot.y;
|
||||
final date = diskData.first.diskData[spot.x.toInt()].time;
|
||||
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,
|
||||
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,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
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,
|
||||
),
|
||||
)
|
||||
.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.first.diskData,
|
||||
period,
|
||||
),
|
||||
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
leftTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
rightTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: false,
|
||||
),
|
||||
showTitles: true,
|
||||
),
|
||||
),
|
||||
leftTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
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,
|
||||
),
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
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,
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||
import 'package:selfprivacy/logic/models/metrics.dart';
|
||||
import 'package:selfprivacy/ui/pages/server_details/charts/bottom_title.dart';
|
||||
|
@ -29,130 +29,163 @@ class MemoryChart extends StatelessWidget {
|
|||
return res;
|
||||
}
|
||||
|
||||
String screenReaderDescription(final BuildContext context) {
|
||||
final lastData = data.last;
|
||||
final lastValue = lastData.value;
|
||||
|
||||
final averageUsage =
|
||||
data.map((final e) => e.value).reduce((final a, final b) => a + b) /
|
||||
data.length;
|
||||
final maxUsage = data
|
||||
.map((final e) => e.value)
|
||||
.reduce((final a, final b) => a > b ? a : b);
|
||||
final maxUsageTime = data.firstWhere((final e) => e.value == maxUsage).time;
|
||||
|
||||
final label = 'resource_chart.memory_chart_screen_reader_explanation'.tr(
|
||||
namedArgs: {
|
||||
'period': 'resource_chart.${period.name}'.tr(),
|
||||
'lastValue': lastValue.toStringAsFixed(1),
|
||||
'averageUsage': averageUsage.toStringAsFixed(1),
|
||||
'maxUsage': maxUsage.toStringAsFixed(1),
|
||||
'maxUsageTime': DateFormat('HH:mm dd MMMM', context.locale.languageCode)
|
||||
.format(maxUsageTime),
|
||||
},
|
||||
);
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
@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<LineBarSpot> touchedBarSpots) {
|
||||
final List<LineTooltipItem> res = [];
|
||||
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;
|
||||
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: Text(
|
||||
bottomTitle(
|
||||
value.toInt(),
|
||||
data,
|
||||
period,
|
||||
),
|
||||
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
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,
|
||||
),
|
||||
),
|
||||
showTitles: true,
|
||||
),
|
||||
],
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
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(
|
||||
gridData: FlGridData(
|
||||
show: true,
|
||||
drawVerticalLine: true,
|
||||
horizontalInterval: 25,
|
||||
verticalInterval: 40,
|
||||
getDrawingHorizontalLine: (final value) => FlLine(
|
||||
color: Theme.of(context).colorScheme.outline.withOpacity(0.3),
|
||||
width: 1,
|
||||
strokeWidth: 1,
|
||||
),
|
||||
left: BorderSide(
|
||||
getDrawingVerticalLine: (final value) => FlLine(
|
||||
color: Theme.of(context).colorScheme.outline.withOpacity(0.3),
|
||||
width: 1,
|
||||
strokeWidth: 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,
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -32,169 +32,226 @@ class NetworkChart extends StatelessWidget {
|
|||
return res;
|
||||
}
|
||||
|
||||
String screenReaderDescription(final BuildContext context) {
|
||||
final lastDataIn = listData[0].last;
|
||||
final lastDataOut = listData[1].last;
|
||||
final lastValueIn = lastDataIn.value;
|
||||
final lastValueOut = lastDataOut.value;
|
||||
|
||||
final averageUsageIn = listData[0]
|
||||
.map((final e) => e.value)
|
||||
.reduce((final a, final b) => a + b) /
|
||||
listData[0].length;
|
||||
final averageUsageOut = listData[1]
|
||||
.map((final e) => e.value)
|
||||
.reduce((final a, final b) => a + b) /
|
||||
listData[1].length;
|
||||
|
||||
final maxUsageIn = listData[0]
|
||||
.map((final e) => e.value)
|
||||
.reduce((final a, final b) => a > b ? a : b);
|
||||
final maxUsageOut = listData[1]
|
||||
.map((final e) => e.value)
|
||||
.reduce((final a, final b) => a > b ? a : b);
|
||||
|
||||
final maxUsageTimeIn =
|
||||
listData[0].firstWhere((final e) => e.value == maxUsageIn).time;
|
||||
final maxUsageTimeOut =
|
||||
listData[1].firstWhere((final e) => e.value == maxUsageOut).time;
|
||||
|
||||
final label = 'resource_chart.network_chart_screen_reader_explanation'.tr(
|
||||
namedArgs: {
|
||||
'period': 'resource_chart.${period.name}'.tr(),
|
||||
'lastValueIn': DiskSize(byte: lastValueIn.toInt()).toString(),
|
||||
'lastValueOut': DiskSize(byte: lastValueOut.toInt()).toString(),
|
||||
'averageUsageIn': DiskSize(byte: averageUsageIn.toInt()).toString(),
|
||||
'averageUsageOut': DiskSize(byte: averageUsageOut.toInt()).toString(),
|
||||
'maxUsageIn': DiskSize(byte: maxUsageIn.toInt()).toString(),
|
||||
'maxUsageOut': DiskSize(byte: maxUsageOut.toInt()).toString(),
|
||||
'maxUsageTimeIn':
|
||||
DateFormat('HH:mm dd MMMM', context.locale.languageCode)
|
||||
.format(maxUsageTimeIn),
|
||||
'maxUsageTimeOut':
|
||||
DateFormat('HH:mm dd MMMM', context.locale.languageCode)
|
||||
.format(maxUsageTimeOut),
|
||||
},
|
||||
);
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
final listDataMax = [
|
||||
...listData[0].map((final e) => e.value),
|
||||
...listData[1].map((final e) => e.value),
|
||||
].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<LineBarSpot> touchedBarSpots) {
|
||||
final List<LineTooltipItem> res = [];
|
||||
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> res = [];
|
||||
|
||||
bool timeShown = false;
|
||||
bool timeShown = false;
|
||||
|
||||
for (final spot in touchedBarSpots) {
|
||||
final value = spot.y;
|
||||
final date = listData[0][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)} ${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,
|
||||
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: [
|
||||
// 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
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,
|
||||
),
|
||||
showTitles: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
// OUT
|
||||
LineChartBarData(
|
||||
spots: getSpots(listData[1]),
|
||||
isCurved: false,
|
||||
barWidth: 2,
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
dotData: const FlDotData(
|
||||
show: false,
|
||||
leftTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: 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: Text(
|
||||
bottomTitle(
|
||||
value.toInt(),
|
||||
listData[0],
|
||||
period,
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
interval: listDataMax * 2 / 6.5,
|
||||
showTitles: true,
|
||||
),
|
||||
showTitles: true,
|
||||
),
|
||||
),
|
||||
leftTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
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,
|
||||
),
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
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,
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
Loading…
Reference in a new issue