David PHAM-VAN

Add curved LineDataSet Chart

@@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
3 ## 1.8.0 3 ## 1.8.0
4 4
5 - Improve Table.fromTextArray() 5 - Improve Table.fromTextArray()
  6 +- Add curved LineDataSet Chart
6 7
7 ## 1.7.1 8 ## 1.7.1
8 9
@@ -30,6 +30,9 @@ export 'package:barcode/barcode.dart'; @@ -30,6 +30,9 @@ export 'package:barcode/barcode.dart';
30 part 'widgets/annotations.dart'; 30 part 'widgets/annotations.dart';
31 part 'widgets/barcode.dart'; 31 part 'widgets/barcode.dart';
32 part 'widgets/basic.dart'; 32 part 'widgets/basic.dart';
  33 +part 'widgets/chart/chart.dart';
  34 +part 'widgets/chart/linear_grid.dart';
  35 +part 'widgets/chart/line_chart.dart';
33 part 'widgets/clip.dart'; 36 part 'widgets/clip.dart';
34 part 'widgets/container.dart'; 37 part 'widgets/container.dart';
35 part 'widgets/content.dart'; 38 part 'widgets/content.dart';
@@ -54,4 +57,3 @@ part 'widgets/text_style.dart'; @@ -54,4 +57,3 @@ part 'widgets/text_style.dart';
54 part 'widgets/theme.dart'; 57 part 'widgets/theme.dart';
55 part 'widgets/widget.dart'; 58 part 'widgets/widget.dart';
56 part 'widgets/wrap.dart'; 59 part 'widgets/wrap.dart';
57 -part 'widgets/chart.dart';  
  1 +/*
  2 + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +// ignore_for_file: omit_local_variable_types
  18 +
  19 +part of widget;
  20 +
  21 +class Chart extends Widget {
  22 + Chart({
  23 + @required this.grid,
  24 + @required this.data,
  25 + });
  26 +
  27 + final ChartGrid grid;
  28 +
  29 + final List<DataSet> data;
  30 +
  31 + PdfPoint _computeSize(BoxConstraints constraints) {
  32 + if (constraints.isTight) {
  33 + return constraints.smallest;
  34 + }
  35 +
  36 + double width = constraints.maxWidth;
  37 + double height = constraints.maxHeight;
  38 +
  39 + const double aspectRatio = 1;
  40 +
  41 + if (!width.isFinite) {
  42 + width = height * aspectRatio;
  43 + }
  44 +
  45 + if (!height.isFinite) {
  46 + height = width * aspectRatio;
  47 + }
  48 +
  49 + return constraints.constrain(PdfPoint(width, height));
  50 + }
  51 +
  52 + @override
  53 + void layout(Context context, BoxConstraints constraints,
  54 + {bool parentUsesSize = false}) {
  55 + box = PdfRect.fromPoints(PdfPoint.zero, _computeSize(constraints));
  56 +
  57 + grid.layout(context, box.size);
  58 + }
  59 +
  60 + @override
  61 + void paint(Context context) {
  62 + super.paint(context);
  63 +
  64 + final Matrix4 mat = Matrix4.identity();
  65 + mat.translate(box.x, box.y);
  66 + context.canvas
  67 + ..saveContext()
  68 + ..setTransform(mat);
  69 +
  70 + grid.paintBackground(context, box.size);
  71 + for (DataSet dataSet in data) {
  72 + dataSet.paintBackground(context, grid);
  73 + }
  74 + for (DataSet dataSet in data) {
  75 + dataSet.paintForeground(context, grid);
  76 + }
  77 + grid.paintForeground(context, box.size);
  78 + context.canvas.restoreContext();
  79 + }
  80 +}
  81 +
  82 +abstract class ChartGrid {
  83 + void layout(Context context, PdfPoint size);
  84 + void paintBackground(Context context, PdfPoint size);
  85 + void paintForeground(Context context, PdfPoint size);
  86 +
  87 + PdfPoint tochart(PdfPoint p);
  88 +}
  89 +
  90 +@immutable
  91 +abstract class ChartValue {
  92 + const ChartValue();
  93 +}
  94 +
  95 +abstract class DataSet {
  96 + void paintBackground(Context context, ChartGrid grid);
  97 + void paintForeground(Context context, ChartGrid grid);
  98 +}
  1 +/*
  2 + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +// ignore_for_file: omit_local_variable_types
  18 +
  19 +part of widget;
  20 +
  21 +class LineChartValue extends ChartValue {
  22 + const LineChartValue(this.x, this.y);
  23 + final double x;
  24 + final double y;
  25 +
  26 + PdfPoint get point => PdfPoint(x, y);
  27 +}
  28 +
  29 +class LineDataSet extends DataSet {
  30 + LineDataSet({
  31 + @required this.data,
  32 + this.pointColor,
  33 + this.pointSize = 3,
  34 + this.color = PdfColors.blue,
  35 + this.lineWidth = 2,
  36 + this.drawLine = true,
  37 + this.drawPoints = true,
  38 + this.isCurved = false,
  39 + this.smoothness = 0.35,
  40 + }) : assert(drawLine || drawPoints);
  41 +
  42 + final List<LineChartValue> data;
  43 + final PdfColor pointColor;
  44 + final double pointSize;
  45 + final PdfColor color;
  46 + final double lineWidth;
  47 + final bool drawLine;
  48 + final bool drawPoints;
  49 + final bool isCurved;
  50 + final double smoothness;
  51 +
  52 + void _drawLine(Context context, ChartGrid grid) {
  53 + if (data.length < 2) {
  54 + return;
  55 + }
  56 +
  57 + PdfPoint t = const PdfPoint(0, 0);
  58 +
  59 + final PdfPoint p = grid.tochart(data.first.point);
  60 + context.canvas.moveTo(p.x, p.y);
  61 +
  62 + for (int i = 1; i < data.length; i++) {
  63 + final PdfPoint p = grid.tochart(data[i].point);
  64 +
  65 + if (!isCurved) {
  66 + context.canvas.lineTo(p.x, p.y);
  67 + continue;
  68 + }
  69 +
  70 + final PdfPoint pp = grid.tochart(data[i - 1].point);
  71 + final PdfPoint pn =
  72 + grid.tochart(data[i + 1 < data.length ? i + 1 : i].point);
  73 +
  74 + final PdfPoint c1 = PdfPoint(pp.x + t.x, pp.y + t.y);
  75 +
  76 + t = PdfPoint(
  77 + (pn.x - pp.x) / 2 * smoothness, (pn.y - pp.y) / 2 * smoothness);
  78 +
  79 + final PdfPoint c2 = PdfPoint(p.x - t.x, p.y - t.y);
  80 +
  81 + context.canvas.curveTo(c1.x, c1.y, c2.x, c2.y, p.x, p.y);
  82 + }
  83 + }
  84 +
  85 + void _drawPoints(Context context, ChartGrid grid) {
  86 + for (final LineChartValue value in data) {
  87 + final PdfPoint p = grid.tochart(value.point);
  88 + context.canvas.drawEllipse(p.x, p.y, pointSize, pointSize);
  89 + }
  90 + }
  91 +
  92 + @override
  93 + void paintBackground(Context context, ChartGrid grid) {}
  94 +
  95 + @override
  96 + void paintForeground(Context context, ChartGrid grid) {
  97 + if (data.isEmpty) {
  98 + return;
  99 + }
  100 +
  101 + if (drawLine) {
  102 + _drawLine(context, grid);
  103 +
  104 + context.canvas
  105 + ..setStrokeColor(color)
  106 + ..setLineWidth(lineWidth)
  107 + ..setLineCap(PdfLineCap.joinRound)
  108 + ..setLineJoin(PdfLineCap.joinRound)
  109 + ..strokePath();
  110 + }
  111 +
  112 + if (drawPoints) {
  113 + _drawPoints(context, grid);
  114 +
  115 + context.canvas
  116 + ..setColor(pointColor ?? color)
  117 + ..fillPath();
  118 + }
  119 + }
  120 +}
@@ -18,86 +18,6 @@ @@ -18,86 +18,6 @@
18 18
19 part of widget; 19 part of widget;
20 20
21 -bool _isSortedAscending(List<double> list) {  
22 - double prev = list.first;  
23 - for (double elem in list) {  
24 - if (prev > elem) {  
25 - return false;  
26 - }  
27 - prev = elem;  
28 - }  
29 - return true;  
30 -}  
31 -  
32 -class Chart extends Widget {  
33 - Chart({  
34 - @required this.grid,  
35 - @required this.data,  
36 - });  
37 -  
38 - final ChartGrid grid;  
39 -  
40 - final List<DataSet> data;  
41 -  
42 - PdfPoint _computeSize(BoxConstraints constraints) {  
43 - if (constraints.isTight) {  
44 - return constraints.smallest;  
45 - }  
46 -  
47 - double width = constraints.maxWidth;  
48 - double height = constraints.maxHeight;  
49 -  
50 - const double aspectRatio = 1;  
51 -  
52 - if (!width.isFinite) {  
53 - width = height * aspectRatio;  
54 - }  
55 -  
56 - if (!height.isFinite) {  
57 - height = width * aspectRatio;  
58 - }  
59 -  
60 - return constraints.constrain(PdfPoint(width, height));  
61 - }  
62 -  
63 - @override  
64 - void layout(Context context, BoxConstraints constraints,  
65 - {bool parentUsesSize = false}) {  
66 - box = PdfRect.fromPoints(PdfPoint.zero, _computeSize(constraints));  
67 -  
68 - grid.layout(context, box.size);  
69 - }  
70 -  
71 - @override  
72 - void paint(Context context) {  
73 - super.paint(context);  
74 -  
75 - final Matrix4 mat = Matrix4.identity();  
76 - mat.translate(box.x, box.y);  
77 - context.canvas  
78 - ..saveContext()  
79 - ..setTransform(mat);  
80 -  
81 - grid.paintBackground(context, box.size);  
82 - for (DataSet dataSet in data) {  
83 - dataSet.paintBackground(context, grid);  
84 - }  
85 - for (DataSet dataSet in data) {  
86 - dataSet.paintForeground(context, grid);  
87 - }  
88 - grid.paintForeground(context, box.size);  
89 - context.canvas.restoreContext();  
90 - }  
91 -}  
92 -  
93 -abstract class ChartGrid {  
94 - void layout(Context context, PdfPoint size);  
95 - void paintBackground(Context context, PdfPoint size);  
96 - void paintForeground(Context context, PdfPoint size);  
97 -  
98 - PdfPoint tochart(PdfPoint p);  
99 -}  
100 -  
101 class LinearGrid extends ChartGrid { 21 class LinearGrid extends ChartGrid {
102 LinearGrid({ 22 LinearGrid({
103 @required this.xAxis, 23 @required this.xAxis,
@@ -107,7 +27,7 @@ class LinearGrid extends ChartGrid { @@ -107,7 +27,7 @@ class LinearGrid extends ChartGrid {
107 this.textStyle, 27 this.textStyle,
108 this.lineWidth = 1, 28 this.lineWidth = 1,
109 this.color = PdfColors.black, 29 this.color = PdfColors.black,
110 - this.separatorLineWidth = 1, 30 + this.separatorLineWidth = .5,
111 this.separatorColor = PdfColors.grey, 31 this.separatorColor = PdfColors.grey,
112 }) : assert(_isSortedAscending(xAxis)), 32 }) : assert(_isSortedAscending(xAxis)),
113 assert(_isSortedAscending(yAxis)); 33 assert(_isSortedAscending(yAxis));
@@ -132,6 +52,17 @@ class LinearGrid extends ChartGrid { @@ -132,6 +52,17 @@ class LinearGrid extends ChartGrid {
132 double yOffset; 52 double yOffset;
133 double yTotal; 53 double yTotal;
134 54
  55 + static bool _isSortedAscending(List<double> list) {
  56 + double prev = list.first;
  57 + for (double elem in list) {
  58 + if (prev > elem) {
  59 + return false;
  60 + }
  61 + prev = elem;
  62 + }
  63 + return true;
  64 + }
  65 +
135 @override 66 @override
136 PdfPoint tochart(PdfPoint p) { 67 PdfPoint tochart(PdfPoint p) {
137 return PdfPoint( 68 return PdfPoint(
@@ -216,78 +147,3 @@ class LinearGrid extends ChartGrid { @@ -216,78 +147,3 @@ class LinearGrid extends ChartGrid {
216 @override 147 @override
217 void paintForeground(Context context, PdfPoint size) {} 148 void paintForeground(Context context, PdfPoint size) {}
218 } 149 }
219 -  
220 -@immutable  
221 -abstract class ChartValue {  
222 - const ChartValue();  
223 -}  
224 -  
225 -class LineChartValue extends ChartValue {  
226 - const LineChartValue(this.x, this.y);  
227 - final double x;  
228 - final double y;  
229 -  
230 - PdfPoint get point => PdfPoint(x, y);  
231 -}  
232 -  
233 -abstract class DataSet {  
234 - void paintBackground(Context context, ChartGrid grid);  
235 - void paintForeground(Context context, ChartGrid grid);  
236 -}  
237 -  
238 -class LineDataSet extends DataSet {  
239 - LineDataSet({  
240 - @required this.data,  
241 - this.pointColor = PdfColors.blue,  
242 - this.pointSize = 3,  
243 - this.lineColor = PdfColors.blue,  
244 - this.lineWidth = 2,  
245 - this.drawLine = true,  
246 - this.drawPoints = true,  
247 - }) : assert(drawLine || drawPoints);  
248 -  
249 - final List<LineChartValue> data;  
250 - final PdfColor pointColor;  
251 - final double pointSize;  
252 - final PdfColor lineColor;  
253 - final double lineWidth;  
254 - final bool drawLine;  
255 - final bool drawPoints;  
256 -  
257 - double maxValue;  
258 -  
259 - @override  
260 - void paintBackground(Context context, ChartGrid grid) {  
261 - if (drawLine) {  
262 - LineChartValue lastValue;  
263 - for (LineChartValue value in data) {  
264 - if (lastValue != null) {  
265 - final PdfPoint p1 = grid.tochart(lastValue.point);  
266 - final PdfPoint p2 = grid.tochart(value.point);  
267 - context.canvas.drawLine(p1.x, p1.y, p2.x, p2.y);  
268 - }  
269 - lastValue = value;  
270 - }  
271 -  
272 - context.canvas  
273 - ..setStrokeColor(lineColor)  
274 - ..setLineWidth(lineWidth)  
275 - ..setLineCap(PdfLineCap.joinRound)  
276 - ..setLineJoin(PdfLineCap.joinRound)  
277 - ..strokePath();  
278 - }  
279 -  
280 - if (drawPoints) {  
281 - for (LineChartValue value in data) {  
282 - final PdfPoint p = grid.tochart(value.point);  
283 - context.canvas  
284 - ..setColor(pointColor)  
285 - ..drawEllipse(p.x, p.y, pointSize, pointSize)  
286 - ..fillPath();  
287 - }  
288 - }  
289 - }  
290 -  
291 - @override  
292 - void paintForeground(Context context, ChartGrid grid) {}  
293 -}  
@@ -33,6 +33,7 @@ void main() { @@ -33,6 +33,7 @@ void main() {
33 group('LineChart test', () { 33 group('LineChart test', () {
34 test('Default LineChart', () { 34 test('Default LineChart', () {
35 pdf.addPage(Page( 35 pdf.addPage(Page(
  36 + pageFormat: PdfPageFormat.standard.landscape,
36 build: (Context context) => Chart( 37 build: (Context context) => Chart(
37 grid: LinearGrid( 38 grid: LinearGrid(
38 xAxis: <double>[0, 1, 2, 3, 4, 5, 6], 39 xAxis: <double>[0, 1, 2, 3, 4, 5, 6],
@@ -53,6 +54,7 @@ void main() { @@ -53,6 +54,7 @@ void main() {
53 54
54 test('Default LineChart without lines connecting points', () { 55 test('Default LineChart without lines connecting points', () {
55 pdf.addPage(Page( 56 pdf.addPage(Page(
  57 + pageFormat: PdfPageFormat.standard.landscape,
56 build: (Context context) => Chart( 58 build: (Context context) => Chart(
57 grid: LinearGrid( 59 grid: LinearGrid(
58 xAxis: <double>[0, 1, 2, 3, 4, 5, 6], 60 xAxis: <double>[0, 1, 2, 3, 4, 5, 6],
@@ -95,6 +97,7 @@ void main() { @@ -95,6 +97,7 @@ void main() {
95 97
96 test('ScatterChart with custom points and lines', () { 98 test('ScatterChart with custom points and lines', () {
97 pdf.addPage(Page( 99 pdf.addPage(Page(
  100 + pageFormat: PdfPageFormat.standard.landscape,
98 build: (Context context) => Chart( 101 build: (Context context) => Chart(
99 grid: LinearGrid( 102 grid: LinearGrid(
100 xAxis: <double>[0, 1, 2, 3, 4, 5, 6], 103 xAxis: <double>[0, 1, 2, 3, 4, 5, 6],
@@ -110,7 +113,7 @@ void main() { @@ -110,7 +113,7 @@ void main() {
110 drawLine: false, 113 drawLine: false,
111 pointColor: PdfColors.red, 114 pointColor: PdfColors.red,
112 pointSize: 4, 115 pointSize: 4,
113 - lineColor: PdfColors.purple, 116 + color: PdfColors.purple,
114 lineWidth: 4, 117 lineWidth: 4,
115 ), 118 ),
116 ], 119 ],
@@ -120,6 +123,7 @@ void main() { @@ -120,6 +123,7 @@ void main() {
120 123
121 test('ScatterChart with custom size', () { 124 test('ScatterChart with custom size', () {
122 pdf.addPage(Page( 125 pdf.addPage(Page(
  126 + pageFormat: PdfPageFormat.standard.landscape,
123 build: (Context context) => SizedBox( 127 build: (Context context) => SizedBox(
124 width: 200, 128 width: 200,
125 height: 100, 129 height: 100,
@@ -141,6 +145,29 @@ void main() { @@ -141,6 +145,29 @@ void main() {
141 ), 145 ),
142 )); 146 ));
143 }); 147 });
  148 +
  149 + test('LineChart with curved lines', () {
  150 + pdf.addPage(Page(
  151 + pageFormat: PdfPageFormat.standard.landscape,
  152 + build: (Context context) => Chart(
  153 + grid: LinearGrid(
  154 + xAxis: <double>[0, 1, 2, 3, 4, 5, 6],
  155 + yAxis: <double>[0, 3, 6, 9],
  156 + ),
  157 + data: <DataSet>[
  158 + LineDataSet(
  159 + drawPoints: false,
  160 + isCurved: true,
  161 + data: const <LineChartValue>[
  162 + LineChartValue(1, 1),
  163 + LineChartValue(3, 7),
  164 + LineChartValue(5, 3),
  165 + ],
  166 + ),
  167 + ],
  168 + ),
  169 + ));
  170 + });
144 }); 171 });
145 172
146 tearDownAll(() { 173 tearDownAll(() {