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:app_finance/charts/painter/abstract_painter.dart';
6 : import 'package:flutter/material.dart';
7 : import 'package:intl/intl.dart' as intl;
8 :
9 : typedef Percentage = int;
10 :
11 : class ForegroundChartPainter extends AbstractPainter {
12 : final Color color;
13 : final Color lineColor;
14 : final Color areaColor;
15 : final Color background;
16 : final List<double> yArea;
17 : final Type? xType;
18 : final Type? yType;
19 : final List<dynamic> yMap;
20 : late final double textArea;
21 : late final double shift;
22 : late final int yDiv;
23 : final int yDivider;
24 : late final int xDiv;
25 : final int xDivider;
26 : final dynamic xTpl;
27 : final dynamic yTpl;
28 :
29 : static const double coercion = 1.2;
30 :
31 1 : ForegroundChartPainter({
32 : required super.size,
33 : super.indent = 0.0,
34 : this.areaColor = Colors.green,
35 : this.background = Colors.grey,
36 : this.color = Colors.black,
37 : this.lineColor = Colors.black,
38 : this.xDivider = 12,
39 : super.xMin = 0.0,
40 : super.xMax = 1.0,
41 : this.xTpl,
42 : this.xType = double,
43 : this.yArea = const [],
44 : this.yMap = const [],
45 : this.yDivider = 12,
46 : super.yMin = 0.0,
47 : super.yMax = 1.0,
48 : this.yTpl,
49 : this.yType = double,
50 : }) {
51 1 : _setTextArea();
52 3 : shift = textArea * coercion;
53 1 : final tmp = size ?? const Size(0, 0);
54 5 : yDiv = yDivider * tmp.height ~/ 400;
55 5 : xDiv = xDivider * tmp.width ~/ 640;
56 : }
57 :
58 1 : void _setTextArea() {
59 3 : double tmp = (size?.width ?? 0) / 20;
60 1 : if (tmp > 32) {
61 0 : textArea = 32.0;
62 1 : } else if (tmp < 20) {
63 1 : textArea = 20.0;
64 : } else {
65 0 : textArea = tmp;
66 : }
67 : }
68 :
69 1 : @override
70 : void paint(Canvas canvas, Size size) {
71 1 : size = this.size ?? size;
72 1 : _paintAxisY(canvas, size);
73 1 : _paintAxisX(canvas, size);
74 3 : if (yArea.length == 2) {
75 0 : _plotAssert(canvas, size);
76 0 : _paintAssertLine(canvas, size, (yArea[0] + yArea[1]) / 2);
77 3 : } else if (yArea.length == 1) {
78 0 : _paintAssertLine(canvas, size, yArea[0]);
79 : }
80 : }
81 :
82 0 : void _paintAssertLine(Canvas canvas, Size size, double yPos) {
83 0 : final line = Paint()
84 0 : ..color = areaColor
85 0 : ..strokeWidth = 1;
86 0 : if (yMax == 0 || yDiv == 0) {
87 : return;
88 : }
89 0 : double y = _shiftStep(size.height, textArea, yDiv, (yPos - yMin) / (yMax / yDiv));
90 0 : canvas.drawLine(Offset(shift, y), Offset(size.width, y), line);
91 : }
92 :
93 0 : void _plotAssert(Canvas canvas, Size size) {
94 0 : final background = Paint()
95 0 : ..color = areaColor.withOpacity(0.1)
96 0 : ..style = PaintingStyle.fill;
97 0 : if (yMax == 0 || yDiv == 0) {
98 : return;
99 : }
100 0 : canvas.drawRect(
101 0 : Rect.fromPoints(
102 0 : Offset(shift, _shiftStep(size.height, textArea, yDiv, ((yArea[0] - yMin) / (yMax / yDiv)).round())),
103 0 : Offset(size.width, _shiftStep(size.height, textArea, yDiv, ((yArea[1] - yMin) / (yMax / yDiv)).round())),
104 : ),
105 : background,
106 : );
107 : }
108 :
109 1 : void _paintText(Canvas canvas, double x, double y, String text) {
110 1 : final textPainter = TextPainter(
111 1 : text: TextSpan(
112 : text: text,
113 1 : style: TextStyle(
114 1 : color: color,
115 2 : fontSize: textArea / 2.2,
116 : fontFamily: 'Abel-Regular',
117 : ),
118 : ),
119 : textDirection: TextDirection.ltr,
120 : );
121 1 : textPainter.layout();
122 4 : textPainter.paint(canvas, Offset(x - textPainter.width, y));
123 : }
124 :
125 0 : void _paintIcon(Canvas canvas, double x, double y, IconData? icon) {
126 : if (icon == null) {
127 : return;
128 : }
129 0 : final textPainter = TextPainter(
130 0 : text: TextSpan(
131 0 : text: String.fromCharCode(icon.codePoint),
132 0 : style: TextStyle(
133 0 : color: color,
134 0 : fontSize: textArea,
135 0 : fontFamily: Icons.question_mark.fontFamily,
136 : ),
137 : ),
138 : textDirection: TextDirection.ltr,
139 : );
140 0 : textPainter.layout();
141 0 : textPainter.paint(canvas, Offset(x - textPainter.width, y));
142 : }
143 :
144 1 : void _paintAxisY(Canvas canvas, Size size) {
145 1 : final lineColor = Paint()
146 3 : ..color = this.lineColor.withOpacity(0.4)
147 1 : ..strokeWidth = 0.5;
148 5 : double step = (yMax - yMin) / yDiv;
149 3 : for (int i = 0; i < yDiv; i++) {
150 1 : double delta = step * i;
151 4 : double y = _shiftStep(size.height, textArea, yDiv, i);
152 1 : if (yType != null || i == 0) {
153 5 : canvas.drawLine(Offset(shift, y), Offset(size.width, y), lineColor);
154 : }
155 1 : switch (yType) {
156 1 : case double:
157 3 : final formatter = yTpl ?? intl.NumberFormat.decimalPatternDigits(decimalDigits: 2, locale: AppLocale.code);
158 8 : _paintText(canvas, textArea, y - textArea / 3, formatter.format(yMin + delta));
159 : break;
160 0 : case IconData:
161 0 : final code = i >= yMap.length ? null : yMap[i];
162 0 : _paintIcon(canvas, textArea, y - textArea, code);
163 : break;
164 0 : case Percentage:
165 0 : final formatter = yTpl ?? intl.NumberFormat.decimalPatternDigits(decimalDigits: 0, locale: AppLocale.code);
166 0 : _paintText(canvas, textArea, y - textArea / 3, formatter.format(yMin + delta) + ' %');
167 : break;
168 : }
169 : }
170 : }
171 :
172 1 : double _shiftStep(total, shift, div, i) {
173 4 : return (total - shift) * (1 - i / div);
174 : }
175 :
176 1 : void _paintAxisX(Canvas canvas, Size size) {
177 1 : final lineColor = Paint()
178 3 : ..color = this.lineColor.withOpacity(0.4)
179 1 : ..strokeWidth = 0.5;
180 1 : final background = Paint()
181 3 : ..color = this.background.withOpacity(0.05)
182 1 : ..style = PaintingStyle.fill;
183 1 : double step = switch (xType) {
184 7 : DateTime => (xMax - xMin - 1) / xDiv,
185 0 : _ => (xMax - xMin) / xDiv,
186 : };
187 3 : double height = size.height - textArea;
188 3 : for (int i = 0; i <= xDiv; i++) {
189 3 : double delta = step * (xDiv - i);
190 4 : double x = _shiftStep(size.width, shift, xDiv, i);
191 8 : canvas.drawLine(Offset(x + shift, height), Offset(x + shift, height + 4), lineColor);
192 2 : if (i < xDiv) {
193 2 : dynamic value = xMin + delta;
194 2 : if (xType == DateTime) {
195 1 : final formatter = xTpl ?? intl.DateFormat('d');
196 3 : value = formatter.format(DateTime.fromMillisecondsSinceEpoch(value.toInt()));
197 : } else {
198 0 : final formatter = xTpl ?? intl.NumberFormat.decimalPatternDigits(decimalDigits: 1, locale: AppLocale.code);
199 0 : value = formatter.format(value as double);
200 : }
201 1 : if (xType != null) {
202 6 : _paintText(canvas, x + shift - 2, height + 1, value.toString());
203 : }
204 : }
205 2 : if (i % 2 != 0) {
206 1 : canvas.drawRect(
207 1 : Rect.fromPoints(
208 3 : Offset(x + shift, 0),
209 8 : Offset(_shiftStep(size.width, shift, xDiv, i - 1) + shift, height),
210 : ),
211 : background,
212 : );
213 : }
214 : }
215 : }
216 : }
|