Commit 3a39548
Changed files (2)
lib
components
lib/components/measurement_graph.dart
@@ -1,10 +1,10 @@
-import 'dart:collection';
import 'dart:math';
import 'package:blood_pressure_app/components/consistent_future_builder.dart';
import 'package:blood_pressure_app/components/display_interval_picker.dart';
import 'package:blood_pressure_app/model/blood_pressure.dart';
import 'package:blood_pressure_app/model/settings_store.dart';
+import 'package:collection/collection.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
@@ -30,149 +30,141 @@ class _LineChartState extends State<_LineChart> {
Align(
alignment: Alignment.topCenter,
child: SizedBox(
- height: widget.height,
- child: Consumer<Settings>(
- builder: (context, settings, child) {
- return Consumer<BloodPressureModel>(builder: (context, model, child) {
- var end = settings.displayDataEnd;
- return ConsistentFutureBuilder<UnmodifiableListView<BloodPressureRecord>>(
- future: (settings.graphStepSize == TimeStep.lifetime)
- ? model.all
- : model.getInTimeRange(settings.displayDataStart, end),
- onData: (context, fetchedData) {
- List<BloodPressureRecord> data = fetchedData.toList();
- data.sort((a, b) => a.creationTime.compareTo(b.creationTime));
-
- List<FlSpot> pulSpots = [];
- List<FlSpot> diaSpots = [];
- List<FlSpot> sysSpots = [];
- int pulMax = 0;
- int diaMax = 0;
- int sysMax = 0;
- for (var e in data) {
- final x = e.creationTime.millisecondsSinceEpoch.toDouble();
- if (e.diastolic != null) {
- diaSpots.add(FlSpot(x, e.diastolic!.toDouble()));
- diaMax = max(diaMax, e.diastolic!);
- }
- if (e.systolic != null) {
- sysSpots.add(FlSpot(x, e.systolic!.toDouble()));
- sysMax = max(sysMax, e.systolic!);
- }
- if (e.pulse != null) {
- pulSpots.add(FlSpot(x, e.pulse!.toDouble()));
- pulMax = max(pulMax, e.pulse!);
- }
- }
+ height: widget.height,
+ child: Consumer<Settings>(
+ builder: (context, settings, child) {
+ return Consumer<BloodPressureModel>(builder: (context, model, child) {
+ var end = settings.displayDataEnd;
+ return ConsistentFutureBuilder<UnmodifiableListView<BloodPressureRecord>>(
+ future: (settings.graphStepSize == TimeStep.lifetime)
+ ? model.all
+ : model.getInTimeRange(settings.displayDataStart, end),
+ onData: (context, fetchedData) {
+ List<BloodPressureRecord> data = fetchedData.toList();
+ data.sort((a, b) => a.creationTime.compareTo(b.creationTime));
- if (fetchedData.length < 2 || (diaSpots.length < 2 && sysSpots.length < 2 && pulSpots.length < 2)) {
- return Text(AppLocalizations.of(context)!.errNotEnoughDataToGraph);
+ List<FlSpot> pulSpots = [];
+ List<FlSpot> diaSpots = [];
+ List<FlSpot> sysSpots = [];
+ int pulMax = 0;
+ int diaMax = 0;
+ int sysMax = 0;
+ for (var e in data) {
+ final x = e.creationTime.millisecondsSinceEpoch.toDouble();
+ if (e.diastolic != null) {
+ diaSpots.add(FlSpot(x, e.diastolic!.toDouble()));
+ diaMax = max(diaMax, e.diastolic!);
+ }
+ if (e.systolic != null) {
+ sysSpots.add(FlSpot(x, e.systolic!.toDouble()));
+ sysMax = max(sysMax, e.systolic!);
}
+ if (e.pulse != null) {
+ pulSpots.add(FlSpot(x, e.pulse!.toDouble()));
+ pulMax = max(pulMax, e.pulse!);
+ }
+ }
- const noTitels = AxisTitles(sideTitles: SideTitles(reservedSize: 40, showTitles: false));
- return LineChart(
- LineChartData(
- minY: settings.validateInputs ? 30 : 0,
- maxY: max(pulMax.toDouble(), max(diaMax.toDouble(), sysMax.toDouble())) + 5,
- titlesData: FlTitlesData(
- topTitles: noTitels,
- rightTitles: noTitels,
- bottomTitles: AxisTitles(
- sideTitles: SideTitles(
- showTitles: true,
- interval: _lineChartTitleIntervall,
- getTitlesWidget: (double pos, TitleMeta meta) {
- // calculate new intervall
- // as graphWidth can technically be as low as one max is needed here to avoid freezes
- double graphWidth = meta.max - meta.min;
- if ((max(graphWidth - 2,1) / settings.graphTitlesCount) != _lineChartTitleIntervall) {
- // simple hack needed to change the state during build
- // https://stackoverflow.com/a/63607696/21489239
- Future.delayed(Duration.zero, () async {
- setState(() {
- _lineChartTitleIntervall = max(graphWidth - 2,1) / settings.graphTitlesCount;
- });
+ if (fetchedData.length < 2 || (diaSpots.length < 2 && sysSpots.length < 2 && pulSpots.length < 2)) {
+ return Text(AppLocalizations.of(context)!.errNotEnoughDataToGraph);
+ }
+
+ const noTitels = AxisTitles(sideTitles: SideTitles(reservedSize: 40, showTitles: false));
+ return LineChart(
+ LineChartData(
+ minY: settings.validateInputs ? 30 : 0,
+ maxY: max(pulMax.toDouble(), max(diaMax.toDouble(), sysMax.toDouble())) + 5,
+ titlesData: FlTitlesData(
+ topTitles: noTitels,
+ rightTitles: noTitels,
+ bottomTitles: AxisTitles(
+ sideTitles: SideTitles(
+ showTitles: true,
+ interval: _lineChartTitleIntervall,
+ getTitlesWidget: (double pos, TitleMeta meta) {
+ // calculate new intervall
+ // as graphWidth can technically be as low as one max is needed here to avoid freezes
+ double graphWidth = meta.max - meta.min;
+ if ((max(graphWidth - 2,1) / settings.graphTitlesCount) != _lineChartTitleIntervall) {
+ // simple hack needed to change the state during build
+ // https://stackoverflow.com/a/63607696/21489239
+ Future.delayed(Duration.zero, () async {
+ setState(() {
+ _lineChartTitleIntervall = max(graphWidth - 2,1) / settings.graphTitlesCount;
});
- }
-
- // don't show fixed titles, as they are replaced by long dates below
- if (meta.axisPosition <= 1 || pos >= meta.max) {
- return const SizedBox.shrink();
- }
-
- late final DateFormat formatter;
- switch (settings.graphStepSize) {
- case TimeStep.day:
- formatter = DateFormat('H:m');
- break;
- case TimeStep.month:
- case TimeStep.last7Days:
- formatter = DateFormat('d');
- break;
- case TimeStep.week:
- formatter = DateFormat('E');
- break;
- case TimeStep.year:
- formatter = DateFormat('MMM');
- break;
- case TimeStep.lifetime:
- formatter = DateFormat('yyyy');
- break;
- case TimeStep.last30Days:
- case TimeStep.custom:
- formatter = DateFormat.MMMd();
- }
- return Text(formatter
- .format(DateTime.fromMillisecondsSinceEpoch(pos.toInt())));
- }),
- ),
- ),
- lineTouchData: const LineTouchData(
- touchTooltipData:
- LineTouchTooltipData(tooltipMargin: -200, tooltipRoundedRadius: 20)),
- lineBarsData: [
- LineChartBarData(
- spots: pulSpots,
- dotData: const FlDotData(
- show: false,
- ),
- color: settings.pulColor,
- barWidth: settings.graphLineThickness,
+ });
+ }
+
+ // don't show fixed titles, as they are replaced by long dates below
+ if (meta.axisPosition <= 1 || pos >= meta.max) {
+ return const SizedBox.shrink();
+ }
+
+ late final DateFormat formatter;
+ switch (settings.graphStepSize) {
+ case TimeStep.day:
+ formatter = DateFormat('H:m');
+ break;
+ case TimeStep.month:
+ case TimeStep.last7Days:
+ formatter = DateFormat('d');
+ break;
+ case TimeStep.week:
+ formatter = DateFormat('E');
+ break;
+ case TimeStep.year:
+ formatter = DateFormat('MMM');
+ break;
+ case TimeStep.lifetime:
+ formatter = DateFormat('yyyy');
+ break;
+ case TimeStep.last30Days:
+ case TimeStep.custom:
+ formatter = DateFormat.MMMd();
+ }
+ return Text(formatter
+ .format(DateTime.fromMillisecondsSinceEpoch(pos.toInt())));
+ }
),
- LineChartBarData(
- spots: diaSpots,
- color: settings.diaColor,
- barWidth: settings.graphLineThickness,
- dotData: const FlDotData(
- show: false,
- ),
- belowBarData: BarAreaData(
- show: true,
- color: Colors.red.shade400.withAlpha(100),
- cutOffY: settings.diaWarn.toDouble(),
- applyCutOffY: true)),
- LineChartBarData(
- spots: sysSpots,
- color: settings.sysColor,
- barWidth: settings.graphLineThickness,
- dotData: const FlDotData(
- show: false,
- ),
- belowBarData: BarAreaData(
- show: true,
- color: Colors.red.shade400.withAlpha(100),
- cutOffY: settings.sysWarn.toDouble(),
- applyCutOffY: true))
- ]));
- }
- );
- });
- },
- )),
+ ),
+ ),
+ lineTouchData: const LineTouchData(
+ touchTooltipData:
+ LineTouchTooltipData(tooltipMargin: -200, tooltipRoundedRadius: 20)),
+ lineBarsData: [
+ _buildBarData(settings, sysSpots, settings.sysColor, true, settings.sysWarn.toDouble()),
+ _buildBarData(settings, diaSpots, settings.diaColor, true, settings.diaWarn.toDouble()),
+ _buildBarData(settings, pulSpots, settings.pulColor, false),
+ ]
+ )
+ );
+ }
+ );
+ });
+ },
+ )
+ ),
),
],
);
}
+
+ LineChartBarData _buildBarData(Settings settings, List<FlSpot> spots, Color color, bool hasAreaData, [double? areaDataCutOff]) {
+ return LineChartBarData(
+ spots: spots,
+ color: color,
+ barWidth: settings.graphLineThickness,
+ dotData: const FlDotData(
+ show: false,
+ ),
+ belowBarData: BarAreaData(
+ show: hasAreaData,
+ color: Colors.red.shade400.withAlpha(100),
+ cutOffY: areaDataCutOff ?? 0,
+ applyCutOffY: true)
+ );
+ }
+
}
class MeasurementGraph extends StatelessWidget {
pubspec.yaml
@@ -49,6 +49,7 @@ dependencies:
pdf: ^3.10.4
jsaver: ^1.2.0
package_info_plus: ^4.0.2
+ collection: any
dev_dependencies:
flutter_test: