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 0 : GaugePainter({
21 : required this.value,
22 : required this.max,
23 : required this.min,
24 : this.color = Colors.grey,
25 : this.fontSize = 12,
26 : }) {
27 0 : formatter = intl.NumberFormat.compact(locale: AppLocale.code);
28 : }
29 :
30 0 : @override
31 : void paint(Canvas canvas, Size size) {
32 0 : final center = Offset(size.width / 2, size.height);
33 0 : final radius = size.width / 2;
34 :
35 0 : _drawArc(canvas, size, radius, center);
36 0 : if (size.width > threshold) {
37 0 : for (double i = 0.15; i < 1; i += 0.33) {
38 0 : _drawValue(canvas, size, radius, center, i);
39 : }
40 : }
41 0 : _drawLine(canvas, size, radius, center);
42 : }
43 :
44 0 : void _paintText(Canvas canvas, Size size, Offset position, String text) {
45 0 : callback(double dx, TextPainter textPainter) {
46 0 : double shift = cos(pi * position.dx / size.width);
47 0 : if (shift.toStringAsFixed(2) == '0.00') {
48 0 : shift = -textPainter.width / 2;
49 0 : } else if (position.dx >= size.width / 2) {
50 0 : shift = shift * textPainter.width * 1.5;
51 : } else {
52 0 : shift *= textPainter.width / 2;
53 : }
54 : return shift;
55 : }
56 :
57 0 : paintText(canvas, size, callback, position, text);
58 : }
59 :
60 0 : void paintText(Canvas canvas, Size size, FnShift shift, Offset position, String text) {
61 0 : final textPainter = TextPainter(
62 0 : text: TextSpan(
63 : text: text,
64 0 : style: TextStyle(
65 0 : color: color.withOpacity(0.8),
66 0 : fontSize: size.width > threshold * 2 ? fontSize : 9,
67 : ),
68 : ),
69 : textDirection: TextDirection.ltr,
70 : );
71 0 : textPainter.layout();
72 0 : textPainter.paint(canvas, Offset(position.dx + shift(position.dx, textPainter), position.dy));
73 : }
74 :
75 0 : void _drawValue(Canvas canvas, Size size, double radius, Offset center, double pos) {
76 0 : final line = Paint()
77 0 : ..color = color.withOpacity(0.5)
78 0 : ..strokeWidth = 1
79 0 : ..style = PaintingStyle.fill;
80 0 : final position = _getTip(radius, center, pos, 15);
81 0 : canvas.drawCircle(position, 1.5, line);
82 0 : _paintText(canvas, size, position, formatter.format(min + max * pos));
83 : }
84 :
85 0 : void _drawArc(Canvas canvas, Size size, double radius, Offset center) {
86 0 : final paint = Paint()
87 0 : ..color = Colors.green.shade500
88 0 : ..strokeWidth = 10
89 0 : ..style = PaintingStyle.stroke;
90 :
91 0 : double startPoint = (1 / 2.4 - 1) * pi;
92 0 : canvas.drawArc(Rect.fromCircle(center: center, radius: radius), startPoint, -startPoint, false, paint);
93 0 : canvas.drawArc(Rect.fromCircle(center: center, radius: radius), startPoint - pi / 3.8, pi / 4, false,
94 0 : paint..color = Colors.orange);
95 0 : canvas.drawArc(
96 0 : Rect.fromCircle(center: center, radius: radius), -pi, pi / 7.1, false, paint..color = Colors.red.shade900);
97 : }
98 :
99 0 : Offset _getTip(double radius, Offset center, double pos, [double needle = 20]) {
100 0 : final needleLength = radius - needle;
101 0 : final needleAngle = pi + pi * pos;
102 0 : return center.translate(needleLength * cos(needleAngle), needleLength * sin(needleAngle));
103 : }
104 :
105 0 : void _drawLine(Canvas canvas, Size size, double radius, Offset center) {
106 0 : final needleTip = _getTip(radius, center, value, size.width > threshold ? 20 : 0);
107 0 : final line = Paint()
108 0 : ..color = color
109 0 : ..strokeWidth = 2
110 0 : ..style = PaintingStyle.fill;
111 0 : final rnd = size.width > threshold ? 6.0 : 3.0;
112 0 : final shift = rnd / 2 * (needleTip.dx > radius ? 1 : -1);
113 0 : final path = Path()
114 0 : ..moveTo(radius - shift, size.height - rnd)
115 0 : ..lineTo(needleTip.dx, needleTip.dy)
116 0 : ..lineTo(radius + shift, size.height + rnd);
117 0 : canvas.drawPath(path, line);
118 0 : canvas.drawCircle(center, rnd, line);
119 : }
120 :
121 0 : @override
122 : bool shouldRepaint(CustomPainter oldDelegate) {
123 : return false;
124 : }
125 : }
|