Commit b2f667c

derdilla <82763757+NobodyForNothing@users.noreply.github.com>
2024-01-19 16:50:05
Add distribution statistic
Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com>
1 parent b9b64e7
Changed files (1)
lib
components
lib/components/statistics/value_distribution.dart
@@ -0,0 +1,141 @@
+import 'package:collection/collection.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+
+class LinearBarDistribution extends StatelessWidget {
+  const LinearBarDistribution({
+    super.key,
+    required this.values,
+  });
+
+  /// Raw list of all values to extract the distribution from.
+  final Iterable<int> values;
+
+  @override
+  Widget build(BuildContext context) {
+    final distribution = <int, double>{};
+    for (final v in values) {
+      if(distribution.containsKey(v)) {
+        distribution[v] = distribution[v]! + 1.0;
+      } else {
+        distribution[v] = 1.0;
+      }
+    }
+    return CustomPaint(
+      painter: _LinearBarDistributionPainter(
+        distribution,
+        AppLocalizations.of(context)!,
+      ),
+    );
+  }
+  
+}
+
+/// Painter of a horizontal array vertical bars.
+class _LinearBarDistributionPainter extends CustomPainter {
+  _LinearBarDistributionPainter(this.distribution, this.localizations);
+
+  /// Positions and height of bars that make up the distribution.
+  ///
+  /// The key is the x order on the bar and it's value indicates it's relative
+  /// height.
+  final Map<int, double> distribution;
+
+  final AppLocalizations localizations;
+
+  static const double _kDefaultBarGapWidth = 5.0;
+  static const Color _kDecorationColor = Colors.white70;
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    // Draw the values:
+    final barWidth = ((size.width + _kDefaultBarGapWidth) // no trailing space
+        / distribution.length) - _kDefaultBarGapWidth;
+
+    // Set height so that the largest element takes up the full height.
+    final double heightUnit = (size.height - barWidth * 2)
+        / distribution.values.max;
+
+    final barPainter = Paint()
+      ..color = const Color.fromARGB(255, 0xb0, 0x18, 0x22)
+      ..style = PaintingStyle.stroke
+      ..strokeWidth = barWidth
+      ..strokeCap = StrokeCap.round
+      ..strokeJoin = StrokeJoin.round;
+
+    double xOffset = barWidth / 2;
+    for (final xPos in distribution.keys) {
+      final length = heightUnit * distribution[xPos]!;
+      final startPos = (size.height - length) / 2; // centered
+      canvas.drawLine(
+        Offset(xOffset, startPos),
+        Offset(xOffset, startPos + length),
+        barPainter,
+      );
+
+      xOffset += barWidth + _kDefaultBarGapWidth;
+    }
+
+    // Draw decorations on top:
+    final decorationsPainter = Paint()
+      ..strokeWidth = 2
+      ..style = PaintingStyle.stroke;
+    double centerLineLength = 0.0;
+    while (centerLineLength < size.width) {
+      canvas.drawLine(
+        Offset(centerLineLength, size.height / 2),
+        Offset(centerLineLength + 8.0, size.height / 2),
+        decorationsPainter..color = _kDecorationColor,
+      );
+      centerLineLength += 8.0 + 7.0;
+    }
+
+    /// Draws a decorative label above the center.
+    ///
+    /// [alignment] may only be [Alignment.centerLeft], [Alignment.center] or
+    /// [Alignment.centerRight].
+    void drawLabel(String text, Alignment alignment) {
+      final style = TextStyle(
+        color: _kDecorationColor,
+        backgroundColor: Colors.black.withOpacity(0.3),
+        fontSize: 12,
+      );
+      final textSpan = TextSpan(
+        text: text,
+        style: style,
+      );
+      final textPainter = TextPainter(
+        text: textSpan,
+        textDirection: TextDirection.ltr,
+      );
+      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 '),
+      };
+      final position = Offset(
+        posX,
+        size.height / 2 - textPainter.height, // above center
+      );
+      textPainter.paint(canvas, position);
+    }
+
+    drawLabel(localizations.minOf(distribution.keys.min.toString()),
+        Alignment.centerLeft,);
+    drawLabel(localizations.avgOf(distribution.keys.average.round().toString()),
+        Alignment.center,);
+    drawLabel(localizations.maxOf(distribution.keys.max.toString()),
+        Alignment.centerRight,);
+
+
+    
+    // TODO: implement decorations
+  }
+
+  @override
+  bool shouldRepaint(covariant _LinearBarDistributionPainter oldDelegate) =>
+      distribution == oldDelegate.distribution;
+  
+}
\ No newline at end of file