Line data Source code
1 : import 'package:fl_animated_linechart/chart/chart_line.dart'; 2 : import 'package:fl_animated_linechart/chart/chart_point.dart'; 3 : import 'package:fl_animated_linechart/chart/datetime_chart_point.dart'; 4 : import 'package:fl_animated_linechart/chart/datetime_series_converter.dart'; 5 : import 'package:fl_animated_linechart/chart/highlight_point.dart'; 6 : import 'package:fl_animated_linechart/common/dates.dart'; 7 : import 'package:fl_animated_linechart/common/pair.dart'; 8 : import 'package:fl_animated_linechart/common/text_direction_helper.dart'; 9 : import 'package:flutter/material.dart'; 10 : import 'package:flutter/rendering.dart'; 11 : import 'package:intl/intl.dart'; 12 : 13 : class LineChart { 14 : 15 : final DateFormat _formatHoursMinutes = DateFormat('kk:mm'); 16 : final DateFormat _formatDayMonth = DateFormat('dd/MM'); 17 : 18 : static final double axisOffsetPX = 50.0; 19 : static final double stepCount = 5; 20 : 21 : final List<ChartLine> lines; 22 : final Dates fromTo; 23 : final String yAxisUnit; 24 : double _minX = 0; 25 : double _minY = 0; 26 : double _maxX = 0; 27 : double _maxY = 0; 28 : 29 : double _widthStepSize; 30 : double _heightStepSize; 31 : double _xScale; 32 : double _xOffset; 33 : double _yScale; 34 : Map<int, List<HighlightPoint>> _seriesMap; 35 : Map<int, Path> _pathMap; 36 : double _yTick; 37 : double _axisOffSetWithPadding; 38 : List<TextPainter> _axisTexts; 39 : List<TextPainter> _yAxisTexts; 40 : 41 2 : LineChart(this.lines, this.fromTo, this.yAxisUnit) { 42 6 : if (lines.length > 0) { 43 8 : _minX = lines[0].minX; 44 8 : _maxX = lines[0].maxX; 45 8 : _minY = lines[0].minY; 46 8 : _maxY = lines[0].maxY; 47 : } 48 : 49 6 : lines.forEach((p) { 50 6 : if (p.minX < _minX) { 51 2 : _minX = p.minX; 52 : } 53 6 : if (p.maxX > _maxX) { 54 2 : _maxX = p.maxX; 55 : } 56 6 : if (p.minY < _minY) { 57 2 : _minY = p.minY; 58 : } 59 6 : if (p.maxY > _maxY) { 60 2 : _maxY = p.maxY; 61 : } 62 : }); 63 : } 64 : 65 2 : factory LineChart.fromDateTimeMaps(List<Map<DateTime, double>> series, List<Color> colors, String yAxisUnit) { 66 2 : Pair<List<ChartLine>, Dates> convertFromDateMaps = DateTimeSeriesConverter.convertFromDateMaps(series, colors); 67 6 : return LineChart(convertFromDateMaps.left, convertFromDateMaps.right, yAxisUnit); 68 : } 69 : 70 8 : double get width => _maxX - _minX; 71 8 : double get height => _maxY - _minY; 72 : 73 4 : double get minX => _minX; 74 4 : double get minY => _minY; 75 2 : double get maxX => _maxX; 76 2 : double get maxY => _maxY; 77 : 78 : //Calculate ui pixels values 79 2 : void initialize(double widthPX, double heightPX) { 80 8 : _widthStepSize = (widthPX-axisOffsetPX) / (stepCount+1); 81 8 : _heightStepSize = (heightPX-axisOffsetPX) / (stepCount+1); 82 : 83 8 : _xScale = (widthPX - axisOffsetPX)/width; 84 8 : _xOffset = minX * _xScale; 85 10 : _yScale = (heightPX - axisOffsetPX - 20)/height; 86 : 87 4 : _seriesMap = Map(); 88 4 : _pathMap = Map(); 89 : 90 : int index = 0; 91 6 : lines.forEach((chartLine) { 92 6 : chartLine.points.forEach((p) { 93 10 : double x = (p.x * xScale) - xOffset; 94 : 95 14 : double adjustedY = (p.y * yScale) - (minY * yScale); 96 4 : double y = (heightPX - axisOffsetPX) - adjustedY; 97 : 98 : //adjust to make room for axis values: 99 2 : x += axisOffsetPX; 100 4 : if (_seriesMap[index] == null) { 101 6 : _seriesMap[index] = List(); 102 : } 103 : 104 2 : if (p is DateTimeChartPoint) { 105 14 : _seriesMap[index].add(HighlightPoint(DateTimeChartPoint(x, y, p.dateTime), p.y)); 106 : } else { 107 6 : _seriesMap[index].add(HighlightPoint(ChartPoint(x, y), p.y)); 108 : } 109 : }); 110 : 111 2 : index++; 112 : }); 113 : 114 6 : _yTick = height / 5; 115 : 116 4 : _axisOffSetWithPadding = axisOffsetPX - 5.0; 117 : 118 4 : _axisTexts = []; 119 : 120 6 : for (int c = 0; c <= (stepCount + 1); c++) { 121 18 : TextSpan span = new TextSpan(style: new TextStyle(color: Colors.grey[800], fontWeight: FontWeight.w200, fontSize: 10), text: '${(minY + yTick * c).round()}'); 122 4 : TextPainter tp = new TextPainter(text: span, textAlign: TextAlign.right, textDirection: TextDirectionHelper.getDirection()); 123 2 : tp.layout(); 124 : 125 4 : _axisTexts.add(tp); 126 : } 127 : 128 4 : _yAxisTexts = []; 129 : 130 : //Todo: make the axis part generic, to support both string, dates, and numbers 131 10 : Duration duration = fromTo.max.difference(fromTo.min); 132 8 : double stepInSeconds = duration.inSeconds.toDouble() / (stepCount + 1); 133 : 134 6 : for (int c = 0; c <= (stepCount + 1); c++) { 135 12 : DateTime tick = fromTo.min.add(Duration(seconds: (stepInSeconds * c).round())); 136 : 137 2 : TextSpan span = new TextSpan( 138 6 : style: new TextStyle(color: Colors.grey[800], fontSize: 11.0, fontWeight: FontWeight.w200), text: _formatDateTime(tick, duration)); 139 2 : TextPainter tp = new TextPainter( 140 : text: span, textAlign: TextAlign.right, 141 2 : textDirection: TextDirectionHelper.getDirection()); 142 2 : tp.layout(); 143 : 144 4 : _yAxisTexts.add(tp); 145 : } 146 : } 147 : 148 2 : String _formatDateTime(DateTime dateTime, Duration duration) { 149 4 : if (duration.inHours < 30) { 150 6 : return _formatHoursMinutes.format(dateTime.toLocal()); 151 : } else { 152 3 : return _formatDayMonth.format(dateTime.toLocal()); 153 : } 154 : } 155 : 156 4 : double get heightStepSize => _heightStepSize; 157 4 : double get widthStepSize => _widthStepSize; 158 : 159 4 : double get yScale => _yScale; 160 4 : double get xOffset => _xOffset; 161 4 : double get xScale => _xScale; 162 : 163 4 : Map<int, List<HighlightPoint>> get seriesMap => _seriesMap; 164 : 165 4 : double get yTick => _yTick; 166 : 167 4 : double get axisOffSetWithPadding => _axisOffSetWithPadding; 168 : 169 4 : List<TextPainter> get yAxisTexts => _axisTexts; 170 : 171 4 : List<TextPainter> get xAxisTexts => _yAxisTexts; 172 : 173 2 : List<HighlightPoint> getClosetHighlightPoints(double horizontalDragPosition) { 174 2 : List<HighlightPoint> highlights = List(); 175 : 176 6 : seriesMap.forEach((key, list) { 177 2 : HighlightPoint closest = _findClosest(list, horizontalDragPosition); 178 2 : highlights.add(closest); 179 : }); 180 : 181 : return highlights; 182 : } 183 : 184 2 : HighlightPoint _findClosest(List<HighlightPoint> list, double horizontalDragPosition) { 185 2 : HighlightPoint candidate = list[0]; 186 : 187 8 : double candidateDist = ((candidate.chartPoint.x) - horizontalDragPosition).abs(); 188 4 : list.forEach((alternative) { 189 8 : double alternativeDist = ((alternative.chartPoint.x) - horizontalDragPosition).abs(); 190 : 191 2 : if (alternativeDist < candidateDist) { 192 : candidate = alternative; 193 8 : candidateDist = ((candidate.chartPoint.x) - horizontalDragPosition).abs(); 194 : } 195 2 : if (alternativeDist > candidateDist) { 196 : return candidate; 197 : } 198 : }); 199 : 200 : return candidate; 201 : } 202 : 203 2 : Path getPathCache(int index) { 204 4 : if (_pathMap.containsKey(index)) { 205 4 : return _pathMap[index]; 206 : } else { 207 2 : Path path = Path(); 208 : 209 : bool init = true; 210 : 211 8 : this.seriesMap[index].forEach((p) { 212 : if (init) { 213 : init = false; 214 10 : path.moveTo(p.chartPoint.x, p.chartPoint.y); 215 : } 216 : 217 10 : path.lineTo(p.chartPoint.x, p.chartPoint.y); 218 : }); 219 : 220 4 : _pathMap[index] = path; 221 : 222 : return path; 223 : } 224 : } 225 : }