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/charts/data/monte_carlo_simulation.dart';
5 : import 'package:app_finance/charts/interface/chart_data.dart';
6 : import 'package:app_finance/charts/painter/abstract_painter.dart';
7 : import 'package:flutter/material.dart';
8 : import 'dart:ui';
9 :
10 : class ForecastChartPainter extends AbstractPainter {
11 : final List<ChartData> data;
12 :
13 0 : ForecastChartPainter({
14 : required super.indent,
15 : required this.data,
16 : super.size,
17 : super.xMax = 1.0,
18 : super.xMin = 0.0,
19 : super.yMax = 1.0,
20 : });
21 :
22 0 : @override
23 : void paint(Canvas canvas, Size size) {
24 0 : if (data.isEmpty) {
25 : return;
26 : }
27 0 : size = this.size ?? size;
28 0 : for (final scope in data) {
29 0 : _paint(canvas, scope.data, size, scope.color);
30 0 : final dx = scope.data.last.dx;
31 0 : final total = _sumY(scope.data);
32 0 : if (scope.data.length > 2 && dx < xMax && total < yMax) {
33 0 : final cycles = (xMax - dx) ~/ msDay;
34 0 : final forecast = [Offset(scope.data.last.dx, total)];
35 0 : forecast.addAll(MonteCarloSimulation(cycles: cycles).generate(scope.data, msDay, xMax - 2 * msDay));
36 0 : _paint(canvas, forecast, size, scope.color.withBlue(200).withOpacity(0.4));
37 : }
38 : }
39 : }
40 :
41 0 : List<dynamic> _bind(Offset point, Size size, double total) {
42 0 : return [
43 0 : total + point.dy,
44 0 : getValue(point, size, total),
45 : ];
46 : }
47 :
48 0 : double _sumY(List<Offset> scope) {
49 0 : return scope.fold(0.0, (v, e) => v + e.dy);
50 : }
51 :
52 0 : Offset _getMedian(List<Offset> scope, double dx) {
53 0 : return Offset(dx, _sumY(scope));
54 : }
55 :
56 0 : void _paint(Canvas canvas, List<Offset> scope, Size size, Color color, [double total = 0.0]) {
57 0 : if (scope.isEmpty) {
58 : return;
59 : }
60 : Offset startPoint;
61 0 : [_, startPoint] = _bind(scope.first, size, total);
62 : int count = 0;
63 : Offset endPoint;
64 0 : [count, endPoint] = _paintDots(canvas, scope, size, color, total);
65 0 : int third = count ~/ 3;
66 0 : if (third > 0) {
67 0 : double dx = scope[count ~/ 2].dx;
68 0 : Offset startBezier = getValue(_getMedian(scope.sublist(0, third), dx), size, total);
69 0 : total += _sumY(scope.sublist(0, third));
70 0 : Offset endBezier = getValue(_getMedian(scope.sublist(third, third * 2), dx), size, total);
71 0 : _paintCurve(canvas, startPoint, startBezier, endBezier, endPoint, color);
72 : }
73 : }
74 :
75 0 : List<dynamic> _paintDots(Canvas canvas, List<Offset> scope, Size size, Color color, double total) {
76 : Offset point = const Offset(0, 0);
77 : int i = 0;
78 0 : for (i; i < scope.length; i++) {
79 0 : [total, point] = _bind(scope[i], size, total);
80 0 : if (point.dy < 0) {
81 0 : point = Offset(point.dx, 0);
82 0 : _paintDot(canvas, point, color);
83 : break;
84 : }
85 0 : _paintDot(canvas, point, color);
86 : }
87 0 : return [i, point];
88 : }
89 :
90 0 : void _paintDot(Canvas canvas, Offset point, Color color) {
91 0 : final dot = Paint()..color = color;
92 0 : canvas.drawCircle(point, 2.2, dot);
93 : }
94 :
95 0 : _paintCurve(Canvas canvas, Offset p0, Offset k1, Offset k2, Offset p1, Color color) {
96 0 : final line = Paint()
97 0 : ..color = color
98 0 : ..style = PaintingStyle.stroke
99 0 : ..strokeWidth = 2;
100 0 : final path = Path()..moveTo(p0.dx, p0.dy);
101 0 : path.cubicTo(k1.dx, k1.dy, k2.dx, k2.dy, p1.dx, p1.dy);
102 0 : canvas.drawPath(path, line);
103 : }
104 : }
|