Line data Source code
1 : // Copyright 2023 The terCAD team. All rights reserved.
2 : // Use of this source code is governed by a CC BY-NC-ND 4.0 license that can be found in the LICENSE file.
3 :
4 : import 'package:app_finance/_classes/herald/app_locale.dart';
5 : import 'package:flutter/material.dart';
6 : import 'package:intl/intl.dart' as intl;
7 : import 'dart:math';
8 :
9 : typedef FnShift = double Function(double dx, TextPainter textPainter);
10 :
11 : class GaugePainter extends CustomPainter {
12 : final double value;
13 : final double max;
14 : final double min;
15 : final Color color;
16 : late final intl.NumberFormat formatter;
17 : final double threshold = 80;
18 : final double fontSize;
19 :
20 1 : GaugePainter({
21 : required this.value,
22 : required this.max,
23 : required this.min,
24 : this.color = Colors.grey,
25 : this.fontSize = 12,
26 : }) {
27 3 : formatter = intl.NumberFormat.compact(locale: AppLocale.code);
28 : }
29 :
30 1 : @override
31 : void paint(Canvas canvas, Size size) {
32 4 : final center = Offset(size.width / 2, size.height);
33 2 : final radius = size.width / 2;
34 :
35 1 : _drawArc(canvas, size, radius, center);
36 3 : if (size.width > threshold) {
37 2 : for (double i = 0.15; i < 1; i += 0.33) {
38 1 : _drawValue(canvas, size, radius, center, i);
39 : }
40 : }
41 1 : _drawLine(canvas, size, radius, center);
42 : }
43 :
44 1 : void _paintText(Canvas canvas, Size size, Offset position, String text) {
45 1 : callback(double dx, TextPainter textPainter) {
46 5 : double shift = cos(pi * position.dx / size.width);
47 2 : if (shift.toStringAsFixed(2) == '0.00') {
48 0 : shift = -textPainter.width / 2;
49 4 : } else if (position.dx >= size.width / 2) {
50 3 : shift = shift * textPainter.width * 1.5;
51 : } else {
52 3 : shift *= textPainter.width / 2;
53 : }
54 : return shift;
55 : }
56 :
57 1 : paintText(canvas, size, callback, position, text);
58 : }
59 :
60 1 : void paintText(Canvas canvas, Size size, FnShift shift, Offset position, String text) {
61 1 : final textPainter = TextPainter(
62 1 : text: TextSpan(
63 : text: text,
64 1 : style: TextStyle(
65 2 : color: color.withOpacity(0.8),
66 5 : fontSize: size.width > threshold * 2 ? fontSize : 9,
67 : ),
68 : ),
69 : textDirection: TextDirection.ltr,
70 : );
71 1 : textPainter.layout();
72 7 : textPainter.paint(canvas, Offset(position.dx + shift(position.dx, textPainter), position.dy));
73 : }
74 :
75 1 : void _drawValue(Canvas canvas, Size size, double radius, Offset center, double pos) {
76 1 : final line = Paint()
77 3 : ..color = color.withOpacity(0.5)
78 1 : ..strokeWidth = 1
79 1 : ..style = PaintingStyle.fill;
80 1 : final position = _getTip(radius, center, pos, 15);
81 1 : canvas.drawCircle(position, 1.5, line);
82 7 : _paintText(canvas, size, position, formatter.format(min + max * pos));
83 : }
84 :
85 1 : void _drawArc(Canvas canvas, Size size, double radius, Offset center) {
86 1 : final paint = Paint()
87 2 : ..color = Colors.green.shade500
88 1 : ..strokeWidth = 10
89 1 : ..style = PaintingStyle.stroke;
90 :
91 3 : double startPoint = (1 / 2.4 - 1) * pi;
92 3 : canvas.drawArc(Rect.fromCircle(center: center, radius: radius), startPoint, -startPoint, false, paint);
93 5 : canvas.drawArc(Rect.fromCircle(center: center, radius: radius), startPoint - pi / 3.8, pi / 4, false,
94 1 : paint..color = Colors.orange);
95 1 : canvas.drawArc(
96 5 : Rect.fromCircle(center: center, radius: radius), -pi, pi / 7.1, false, paint..color = Colors.red.shade900);
97 : }
98 :
99 1 : Offset _getTip(double radius, Offset center, double pos, [double needle = 20]) {
100 1 : final needleLength = radius - needle;
101 2 : final needleAngle = pi + pi * pos;
102 5 : return center.translate(needleLength * cos(needleAngle), needleLength * sin(needleAngle));
103 : }
104 :
105 1 : void _drawLine(Canvas canvas, Size size, double radius, Offset center) {
106 5 : final needleTip = _getTip(radius, center, value, size.width > threshold ? 20 : 0);
107 1 : final line = Paint()
108 2 : ..color = color
109 1 : ..strokeWidth = 2
110 1 : ..style = PaintingStyle.fill;
111 3 : final rnd = size.width > threshold ? 6.0 : 3.0;
112 4 : final shift = rnd / 2 * (needleTip.dx > radius ? 1 : -1);
113 1 : final path = Path()
114 4 : ..moveTo(radius - shift, size.height - rnd)
115 3 : ..lineTo(needleTip.dx, needleTip.dy)
116 4 : ..lineTo(radius + shift, size.height + rnd);
117 1 : canvas.drawPath(path, line);
118 1 : canvas.drawCircle(center, rnd, line);
119 : }
120 :
121 0 : @override
122 : bool shouldRepaint(CustomPainter oldDelegate) {
123 : return false;
124 : }
125 : }
|