LCOV - code coverage report
Current view: top level - chart - animated_line_chart.dart (source / functions) Hit Total Coverage
Test: lcov_filtered.info Lines: 120 129 93.0 %
Date: 2019-08-23 11:26:09 Functions: 0 0 -

          Line data    Source code
       1             : import 'dart:math';
       2             : 
       3             : import 'package:fl_animated_linechart/chart/datetime_chart_point.dart';
       4             : import 'package:fl_animated_linechart/chart/highlight_point.dart';
       5             : import 'package:fl_animated_linechart/chart/line_chart.dart';
       6             : import 'package:fl_animated_linechart/common/text_direction_helper.dart';
       7             : import 'package:flutter/material.dart';
       8             : import 'package:flutter/widgets.dart';
       9             : import 'package:intl/intl.dart';
      10             : 
      11             : class AnimatedLineChart extends StatefulWidget {
      12             : 
      13             :   final LineChart chart;
      14             : 
      15           2 :   const AnimatedLineChart(this.chart, {Key key, }) : super(key: key);
      16             : 
      17           1 :   @override
      18           1 :   _AnimatedLineChartState createState() => _AnimatedLineChartState();
      19             : }
      20             : 
      21             : class _AnimatedLineChartState extends State<AnimatedLineChart> with SingleTickerProviderStateMixin {
      22             :   AnimationController _controller;
      23             :   Animation _animation;
      24             : 
      25           1 :   @override
      26             :   void initState() {
      27           3 :     _controller = AnimationController(vsync: this, duration: Duration(milliseconds: 600));
      28             : 
      29           2 :     Animation curve = CurvedAnimation(parent: _controller, curve: Curves.easeInOutExpo);
      30             : 
      31           3 :     _animation = Tween(begin: 0.0, end: 1.0).animate(curve);
      32             : 
      33           2 :     _controller.forward();
      34             : 
      35           1 :     super.initState();
      36             :   }
      37             : 
      38           1 :   @override
      39             :   void dispose() {
      40           2 :     _controller.dispose();
      41           1 :     super.dispose();
      42             :   }
      43             : 
      44           1 :   @override
      45             :   Widget build(BuildContext context) {
      46           1 :     return Padding(
      47           1 :       padding: EdgeInsets.only(right: ChartPainter.axisOffsetPX),
      48           1 :       child: LayoutBuilder(
      49           1 :           builder: (BuildContext context, BoxConstraints constraints) {
      50           5 :             widget.chart.initialize(constraints.maxWidth, constraints.maxHeight);
      51             : 
      52           6 :             return _GestureWrapper(constraints.maxHeight, constraints.maxWidth, widget.chart, _animation);
      53             :           }
      54             :       ),
      55             :     );
      56             :   }
      57             : }
      58             : 
      59             : //Wrap gestures, to avoid reinitializing the chart model when doing gestures
      60             : class _GestureWrapper extends StatefulWidget {
      61             :   final double _height;
      62             :   final double width;
      63             :   final LineChart chart;
      64             :   final Animation animation;
      65             : 
      66           2 :   const _GestureWrapper(this._height, this.width, this.chart, this.animation, {Key key,}) : super(key: key);
      67             : 
      68           1 :   @override
      69           1 :   _GestureWrapperState createState() => _GestureWrapperState();
      70             : }
      71             : 
      72             : class _GestureWrapperState extends State<_GestureWrapper> {
      73             :   bool horizontalDragActive = false;
      74             :   double horizontalDragPosition = 0.0;
      75             : 
      76           1 :   @override
      77             :   Widget build(BuildContext context) {
      78           1 :     return GestureDetector(
      79          11 :       child: _AnimatedChart(widget.chart, widget.width, widget._height, horizontalDragActive, horizontalDragPosition, animation: widget.animation,),
      80           1 :       onHorizontalDragStart: (dragStartDetails) {
      81           1 :         horizontalDragActive = true;
      82           3 :         horizontalDragPosition = dragStartDetails.globalPosition.dx;
      83           2 :         setState(() {
      84             :         });
      85             :       },
      86           0 :       onHorizontalDragUpdate: (dragUpdateDetails) {
      87           0 :         horizontalDragPosition += dragUpdateDetails.primaryDelta;
      88           0 :         setState(() {
      89             :         });
      90             :       },
      91           0 :       onHorizontalDragEnd: (dragEndDetails) {
      92           0 :         horizontalDragActive = false;
      93           0 :         horizontalDragPosition = 0.0;
      94           0 :         setState(() {
      95             :         });
      96             :       },
      97             :     );
      98             :   }
      99             : }
     100             : 
     101             : class _AnimatedChart extends AnimatedWidget {
     102             :   final double height;
     103             :   final double width;
     104             :   final LineChart chart;
     105             :   final bool horizontalDragActive;
     106             :   final double horizontalDragPosition;
     107             : 
     108           2 :   _AnimatedChart(this.chart, this.width, this.height, this.horizontalDragActive, this.horizontalDragPosition, {Key key, Animation animation}) : super(key: key, listenable: animation);
     109             : 
     110           1 :   @override
     111             :   Widget build(BuildContext context) {
     112           1 :     Animation animation = listenable as Animation;
     113             : 
     114           1 :     return CustomPaint(
     115           5 :       painter: ChartPainter(animation?.value, chart, horizontalDragActive, horizontalDragPosition),
     116             :     );
     117             :   }
     118             : }
     119             : 
     120             : class ChartPainter extends CustomPainter {
     121             : 
     122             :   static final double axisOffsetPX = 50.0;
     123             :   static final double stepCount = 5;
     124             : 
     125             :   final DateFormat _formatMonthDayHoursMinutes = DateFormat('dd/MM kk:mm');
     126             : 
     127             :   final Paint _gridPainter = Paint()
     128             :                           ..style = PaintingStyle.stroke
     129             :                           ..strokeWidth = 1
     130             :                           ..color = Colors.black26;
     131             : 
     132             :   Paint linePainter = Paint()
     133             :     ..style = PaintingStyle.stroke
     134             :     ..strokeWidth = 2
     135             :     ..color = Colors.black26;
     136             : 
     137             :   Paint tooltipPainter = Paint()
     138             :     ..style = PaintingStyle.fill
     139             :     ..color = Colors.white.withAlpha(230);
     140             : 
     141             :   final double progress;
     142             :   final LineChart chart;
     143             :   final bool horizontalDragActive;
     144             :   final double horizontalDragPosition;
     145             : 
     146           1 :   ChartPainter(this.progress, this.chart, this.horizontalDragActive, this.horizontalDragPosition);
     147             : 
     148           1 :   @override
     149             :   void paint(Canvas canvas, Size size) {
     150           1 :     _drawGrid(canvas, size);
     151           1 :     _drawUnit(canvas, size);
     152           1 :     _drawLines(size, canvas);
     153           1 :     _drawAxisValues(canvas, size);
     154             : 
     155           1 :     if (horizontalDragActive) {
     156           1 :       _drawHighlights(size, canvas);
     157             :     }
     158             :   }
     159             : 
     160           1 :   void _drawHighlights(Size size, Canvas canvas) {
     161           2 :     linePainter.color = Colors.black45;
     162             :     
     163           5 :     if (horizontalDragPosition > axisOffsetPX && horizontalDragPosition < size.width) {
     164           8 :       canvas.drawLine(Offset(horizontalDragPosition, 0), Offset(horizontalDragPosition, size.height - axisOffsetPX), linePainter);
     165             :     }
     166             :     
     167           3 :     List<HighlightPoint> highlights = chart.getClosetHighlightPoints(horizontalDragPosition);
     168           1 :     List<TextPainter> textPainters = List();
     169             :     int index = 0;
     170           3 :     double minHighlightX = highlights[0].chartPoint.x;
     171           3 :     double minHighlightY = highlights[0].chartPoint.y;
     172             :     double maxWidth = 0;
     173             : 
     174           2 :     highlights.forEach((highlight) {
     175           3 :       if (highlight.chartPoint.x < minHighlightX) {
     176           0 :         minHighlightX = highlight.chartPoint.x;
     177             :       }
     178           3 :       if (highlight.chartPoint.y < minHighlightY) {
     179           0 :         minHighlightY = highlight.chartPoint.y;
     180             :       }
     181             :     });
     182             : 
     183           2 :     highlights.forEach((highlight) {
     184           7 :       canvas.drawCircle(Offset(highlight.chartPoint.x, highlight.chartPoint.y), 5, linePainter);
     185             : 
     186             :       String prefix = "";
     187             :     
     188           2 :       if (highlight.chartPoint is DateTimeChartPoint) {
     189           1 :         DateTimeChartPoint dateTimeChartPoint = highlight.chartPoint;
     190           3 :         prefix = _formatMonthDayHoursMinutes.format(dateTimeChartPoint.dateTime);
     191             :       }
     192             : 
     193          11 :       TextSpan span = new TextSpan(style: new TextStyle(color: chart.lines[index].color, fontWeight: FontWeight.w200, fontSize: 12), text: '$prefix: ${highlight.yValue.toStringAsFixed(1)} ${chart.yAxisUnit}');
     194           2 :       TextPainter tp = new TextPainter(text: span, textAlign: TextAlign.right, textDirection: TextDirectionHelper.getDirection());
     195             :     
     196           1 :       tp.layout();
     197             : 
     198           2 :       if (tp.width > maxWidth) {
     199           1 :         maxWidth = tp.width;
     200             :       }
     201             : 
     202           1 :       textPainters.add(tp);
     203           1 :       index++;
     204             :     });
     205             : 
     206           1 :     minHighlightX += 12; //make room for the chart points
     207           5 :     double tooltipHeight = textPainters[0].height * textPainters.length + 16;
     208             : 
     209           4 :     if ((minHighlightX + maxWidth + 16) > size.width) {
     210           1 :       minHighlightX -= maxWidth;
     211           1 :       minHighlightX -= 34;
     212             :     }
     213             : 
     214           6 :     if (minHighlightY + tooltipHeight > size.height - chart.axisOffSetWithPadding) {
     215           5 :       minHighlightY = size.height - chart.axisOffSetWithPadding - tooltipHeight;
     216             :     }
     217             : 
     218             :     //Draw highlight bordered box:
     219           4 :     Rect tooltipRect = Rect.fromLTWH(minHighlightX-5, minHighlightY - 5, maxWidth+20, tooltipHeight);
     220           2 :     canvas.drawRect(tooltipRect, tooltipPainter);
     221           2 :     canvas.drawRect(tooltipRect, _gridPainter);
     222             : 
     223             :     //Draw the actual highlights:
     224           2 :     textPainters.forEach((tp) {
     225           3 :       tp.paint(canvas, Offset(minHighlightX+5, minHighlightY));
     226           1 :       minHighlightY += 17;
     227             :     });
     228             :   }
     229             : 
     230           1 :   void _drawAxisValues(Canvas canvas, Size size) {
     231             :     //TODO: calculate and cache
     232           3 :     for (int c = 0; c <= (stepCount + 1); c++) {
     233           3 :       TextPainter tp = chart.yAxisTexts[c];
     234          13 :       tp.paint(canvas, new Offset(chart.axisOffSetWithPadding - tp.width, (size.height - 6)- (c * chart.heightStepSize) - axisOffsetPX));
     235             :     }
     236             :     
     237             :     //TODO: calculate and cache
     238           3 :     for (int c = 0; c <= (stepCount + 1); c++) {
     239          15 :       _drawRotatedText(canvas, chart.xAxisTexts[c], chart.axisOffSetWithPadding + (c * chart.widthStepSize), size.height - chart.axisOffSetWithPadding, pi * 1.5);
     240             :     }
     241             :   }
     242             : 
     243           1 :   void _drawLines(Size size, Canvas canvas) {
     244             :     int index = 0;
     245             : 
     246           4 :     chart.lines.forEach((chartLine) {
     247           3 :       linePainter.color = chartLine.color;
     248             :       Path path;
     249             : 
     250           3 :       List<HighlightPoint> points = chart.seriesMap[index];
     251             : 
     252           2 :       bool drawCircles = points.length < 100;
     253             : 
     254           2 :       if (progress < 1.0) {
     255           1 :         path = Path(); // create new path, to make animation work
     256             :         bool init = true;
     257             : 
     258           3 :         chartLine.points.forEach((p) {
     259           7 :           double x = (p.x * chart.xScale) - chart.xOffset;
     260          10 :           double adjustedY = (p.y * chart.yScale) - (chart.minY * chart.yScale);
     261           5 :           double y = (size.height - LineChart.axisOffsetPX) - (adjustedY * progress);
     262             : 
     263             :           //adjust to make room for axis values:
     264           1 :           x += LineChart.axisOffsetPX;
     265             : 
     266             :           if (init) {
     267             :             init = false;
     268           1 :             path.moveTo(x, y);
     269             :           }
     270             : 
     271           1 :           path.lineTo(x, y);
     272             :           if (drawCircles) {
     273           3 :             canvas.drawCircle(Offset(x, y), 2, linePainter);
     274             :           }
     275             :         });
     276             :       } else {
     277           2 :         path = chart.getPathCache(index);
     278             : 
     279             :         if (drawCircles) {
     280           9 :           points.forEach((p) => canvas.drawCircle(Offset(p.chartPoint.x, p.chartPoint.y), 2, linePainter));
     281             :         }
     282             :       }
     283             : 
     284           2 :       canvas.drawPath(path, linePainter);
     285           1 :       index++;
     286             :     });
     287             :   }
     288             : 
     289           1 :   void _drawUnit(Canvas canvas, Size size) {
     290           4 :     TextSpan span = new TextSpan(style: new TextStyle(color: Colors.black54, fontWeight: FontWeight.w200, fontSize: 12), text: chart.yAxisUnit);
     291           2 :     TextPainter tp = new TextPainter(text: span, textAlign: TextAlign.right, textDirection: TextDirectionHelper.getDirection());
     292           1 :     tp.layout();
     293             : 
     294           4 :     tp.paint(canvas, new Offset(LineChart.axisOffsetPX - tp.width, -18));
     295             :   }
     296             : 
     297           1 :   void _drawGrid(Canvas canvas, Size size) {
     298           7 :     canvas.drawRect(Rect.fromLTWH(axisOffsetPX, 0, size.width - axisOffsetPX, size.height - axisOffsetPX), _gridPainter);
     299             :     
     300           2 :     for(double c = 1; c <= stepCount; c ++) {
     301          11 :       canvas.drawLine(Offset(axisOffsetPX, c*chart.heightStepSize), Offset(size.width, c*chart.heightStepSize), _gridPainter);
     302          14 :       canvas.drawLine(Offset(c*chart.widthStepSize + axisOffsetPX, 0), Offset(c*chart.widthStepSize + axisOffsetPX, size.height-axisOffsetPX), _gridPainter);
     303             :     }
     304             :   }
     305             : 
     306           1 :   void _drawRotatedText(Canvas canvas,TextPainter tp, double x, double y, double angleRotationInRadians) {
     307           1 :     canvas.save();
     308           3 :     canvas.translate(x, y + tp.width);
     309           1 :     canvas.rotate(angleRotationInRadians);
     310           2 :     tp.paint(canvas, new Offset(0.0,0.0));
     311           1 :     canvas.restore();
     312             :   }
     313             : 
     314           1 :   @override
     315             :   bool shouldRepaint(CustomPainter oldDelegate) {
     316             :     return true;
     317             :   }
     318             : }

Generated by: LCOV version 1.14