Commit 977bcdd
Changed files (2)
lib
components
statistics
test
ui
components
statistics
lib/components/statistics/value_distribution.dart
@@ -8,6 +8,8 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
///
/// Shows in form of a graph that has centered columns of different height and
/// labels that indicate min, max and average.
+///
+/// The widgets takes a width of at least 180 units and a height of at least 50.
class ValueDistribution extends StatelessWidget {
/// Create a statistic to show how often values occur.
const ValueDistribution({
@@ -34,6 +36,13 @@ class ValueDistribution extends StatelessWidget {
@override
Widget build(BuildContext context) {
+ final localizations = AppLocalizations.of(context)!;
+ if (values.isEmpty) {
+ return Center(
+ child: Text(localizations.errNoData),
+ );
+ }
+
final distribution = <int, double>{};
for (final v in values) {
if(distribution.containsKey(v)) {
@@ -48,11 +57,17 @@ class ValueDistribution extends StatelessWidget {
}
assert(distribution[distribution.keys.max]! > 0);
assert(distribution[distribution.keys.min]! > 0);
- return CustomPaint(
- painter: _ValueDistributionPainter(
- distribution,
- AppLocalizations.of(context)!,
- color,
+ return Container(
+ constraints: const BoxConstraints(
+ minWidth: 180,
+ minHeight: 50
+ ),
+ child: CustomPaint(
+ painter: _ValueDistributionPainter(
+ distribution,
+ localizations,
+ color,
+ ),
),
);
}
@@ -147,12 +162,12 @@ class _ValueDistributionPainter extends CustomPainter {
textPainter.layout();
final posX = switch(alignment) {
Alignment.centerLeft => 0.0,
- Alignment.centerRight => size.width - textPainter.width,
- Alignment.center => (size.width / 2) - (textPainter.width / 2),
- _ => throw ArgumentError('Invalid '),
+ Alignment.center => (size.width / 2) - (textPainter.width / 2).clamp(0, size.width),
+ Alignment.centerRight => (size.width - textPainter.width).clamp(0, size.width),
+ _ => throw ArgumentError('Unsupported alignment'),
};
final position = Offset(
- posX,
+ posX.toDouble(),
size.height / 2 - textPainter.height, // above center
);
textPainter.paint(canvas, position);
@@ -161,7 +176,7 @@ class _ValueDistributionPainter extends CustomPainter {
drawLabel(localizations.minOf(distribution.keys.min.toString()),
Alignment.centerLeft,);
drawLabel(localizations.avgOf(distribution.keys.average.round().toString()),
- Alignment.center,);
+ Alignment.center,); // FIXME: Average doesn't depend on count
drawLabel(localizations.maxOf(distribution.keys.max.toString()),
Alignment.centerRight,);
}
test/ui/components/statistics/value_distribution_test.dart
@@ -0,0 +1,66 @@
+
+import 'package:blood_pressure_app/components/statistics/value_distribution.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import '../util.dart';
+
+void main() {
+ testWidgets('should show centered info when values are empty', (widgetTester) async {
+ await widgetTester.pumpWidget(materialApp(const ValueDistribution(
+ color: Colors.red,
+ values: [],
+ ),),);
+ expect(find.byType(ValueDistribution), findsOneWidget);
+ final localizations = await AppLocalizations.delegate.load(const Locale('en'));
+ expect(find.byType(Text), findsOneWidget);
+ expect(find.text(localizations.errNoData), findsOneWidget);
+
+ final errorCenter = widgetTester.getCenter(find.byType(Text));
+ final canvasCenter = widgetTester.getCenter(find.byType(MaterialApp));
+ expect(errorCenter, equals(canvasCenter));;
+ },);
+
+ testWidgets('should draw labels at correct positions', (widgetTester) async {
+ await widgetTester.pumpWidget(materialApp(const SizedBox(
+ height: 50,
+ width: 180,
+ child: ValueDistribution(
+ color: Colors.red,
+ values: [5,6,3,8,8,10], // min 3, max 10, avg 6 + 2/3
+ ),
+ ),),);
+
+ expect(find.byType(ValueDistribution), findsOneWidget);
+ expect(find.byType(ValueDistribution),
+ paintsExactlyCountTimes(#drawParagraph, 3),);
+ const posBelowCenter = 9.0;
+ expect(find.byType(ValueDistribution), paints
+ ..paragraph(offset: const Offset(0.0, posBelowCenter))
+ ..paragraph(offset: const Offset(66.0, posBelowCenter)) // overflows at the end
+ ..paragraph(offset: const Offset(68.0, posBelowCenter)),
+ );
+ },);
+
+ testWidgets('should correct amount of value bars', (widgetTester) async {
+ await widgetTester.pumpWidget(materialApp(const SizedBox(
+ height: 50,
+ width: 180,
+ child: ValueDistribution(
+ color: Colors.red,
+ values: [1,2,3,3,4,5], // min 3, max 10, avg 6 + 2/3
+ ),
+ ),),);
+
+ expect(find.byType(ValueDistribution), findsOneWidget);
+ expect(find.byType(ValueDistribution), paints
+ ..line(color: Colors.red.shade500)
+ ..line(color: Colors.red.shade500)
+ ..line(color: Colors.red.shade500)
+ ..line(color: Colors.red.shade500)
+ ..line(color: Colors.red.shade500)
+ ..line(color: Colors.white70) // start drawing decoration
+ ,);
+ },);
+}