David PHAM-VAN

Added surface fill and Bar Charts

@@ -33,6 +33,7 @@ part 'widgets/basic.dart'; @@ -33,6 +33,7 @@ part 'widgets/basic.dart';
33 part 'widgets/chart/chart.dart'; 33 part 'widgets/chart/chart.dart';
34 part 'widgets/chart/linear_grid.dart'; 34 part 'widgets/chart/linear_grid.dart';
35 part 'widgets/chart/line_chart.dart'; 35 part 'widgets/chart/line_chart.dart';
  36 +part 'widgets/chart/bar_chart.dart';
36 part 'widgets/clip.dart'; 37 part 'widgets/clip.dart';
37 part 'widgets/container.dart'; 38 part 'widgets/container.dart';
38 part 'widgets/content.dart'; 39 part 'widgets/content.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 BarDataSet extends DataSet {
  22 + BarDataSet({
  23 + @required this.data,
  24 + this.borderColor,
  25 + this.borderWidth = 1.5,
  26 + this.color = PdfColors.blue,
  27 + this.drawBorder = true,
  28 + this.drawSurface = true,
  29 + this.surfaceOpacity = 1,
  30 + this.width = 20,
  31 + this.offset = 0,
  32 + this.margin = 5,
  33 + }) : assert(drawBorder || drawSurface);
  34 +
  35 + final List<LineChartValue> data;
  36 + final double width;
  37 + final double offset;
  38 + final double margin;
  39 +
  40 + final bool drawBorder;
  41 + final PdfColor borderColor;
  42 + final double borderWidth;
  43 +
  44 + final bool drawSurface;
  45 + final PdfColor color;
  46 + final double surfaceOpacity;
  47 +
  48 + void _drawSurface(Context context, ChartGrid grid, LineChartValue value) {
  49 + final double y = (grid is LinearGrid) ? grid.xAxisOffset : 0;
  50 + final PdfPoint p = grid.tochart(value.point);
  51 +
  52 + context.canvas.drawRect(p.x + offset - width / 2, y, width, p.y);
  53 + }
  54 +
  55 + @override
  56 + void paintBackground(Context context, ChartGrid grid) {}
  57 +
  58 + @override
  59 + void paintForeground(Context context, ChartGrid grid) {
  60 + if (data.isEmpty) {
  61 + return;
  62 + }
  63 +
  64 + if (data.isEmpty) {
  65 + return;
  66 + }
  67 +
  68 + if (drawSurface) {
  69 + for (final LineChartValue value in data) {
  70 + _drawSurface(context, grid, value);
  71 + }
  72 +
  73 + if (surfaceOpacity != 1) {
  74 + context.canvas
  75 + ..saveContext()
  76 + ..setGraphicState(
  77 + PdfGraphicState(opacity: surfaceOpacity),
  78 + );
  79 + }
  80 +
  81 + context.canvas
  82 + ..setFillColor(color)
  83 + ..fillPath();
  84 +
  85 + if (surfaceOpacity != 1) {
  86 + context.canvas.restoreContext();
  87 + }
  88 + }
  89 +
  90 + if (drawBorder) {
  91 + for (final LineChartValue value in data) {
  92 + _drawSurface(context, grid, value);
  93 + }
  94 +
  95 + context.canvas
  96 + ..setStrokeColor(borderColor ?? color)
  97 + ..setLineWidth(borderWidth)
  98 + ..strokePath();
  99 + }
  100 + }
  101 +}
@@ -18,6 +18,7 @@ @@ -18,6 +18,7 @@
18 18
19 part of widget; 19 part of widget;
20 20
  21 +/// This widget is in preview and the API is subject to change
21 class Chart extends Widget { 22 class Chart extends Widget {
22 Chart({ 23 Chart({
23 @required this.grid, 24 @required this.grid,
@@ -68,12 +69,17 @@ class Chart extends Widget { @@ -68,12 +69,17 @@ class Chart extends Widget {
68 ..setTransform(mat); 69 ..setTransform(mat);
69 70
70 grid.paintBackground(context, box.size); 71 grid.paintBackground(context, box.size);
  72 + grid.clip(context, box.size);
71 for (DataSet dataSet in data) { 73 for (DataSet dataSet in data) {
72 dataSet.paintBackground(context, grid); 74 dataSet.paintBackground(context, grid);
73 } 75 }
  76 + grid.unClip(context, box.size);
  77 + grid.paint(context, box.size);
  78 + grid.clip(context, box.size);
74 for (DataSet dataSet in data) { 79 for (DataSet dataSet in data) {
75 dataSet.paintForeground(context, grid); 80 dataSet.paintForeground(context, grid);
76 } 81 }
  82 + grid.unClip(context, box.size);
77 grid.paintForeground(context, box.size); 83 grid.paintForeground(context, box.size);
78 context.canvas.restoreContext(); 84 context.canvas.restoreContext();
79 } 85 }
@@ -82,8 +88,12 @@ class Chart extends Widget { @@ -82,8 +88,12 @@ class Chart extends Widget {
82 abstract class ChartGrid { 88 abstract class ChartGrid {
83 void layout(Context context, PdfPoint size); 89 void layout(Context context, PdfPoint size);
84 void paintBackground(Context context, PdfPoint size); 90 void paintBackground(Context context, PdfPoint size);
  91 + void paint(Context context, PdfPoint size);
85 void paintForeground(Context context, PdfPoint size); 92 void paintForeground(Context context, PdfPoint size);
86 93
  94 + void clip(Context context, PdfPoint size);
  95 + void unClip(Context context, PdfPoint size);
  96 +
87 PdfPoint tochart(PdfPoint p); 97 PdfPoint tochart(PdfPoint p);
88 } 98 }
89 99
@@ -35,21 +35,31 @@ class LineDataSet extends DataSet { @@ -35,21 +35,31 @@ class LineDataSet extends DataSet {
35 this.lineWidth = 2, 35 this.lineWidth = 2,
36 this.drawLine = true, 36 this.drawLine = true,
37 this.drawPoints = true, 37 this.drawPoints = true,
  38 + this.drawSurface = false,
  39 + this.surfaceOpacity = .2,
  40 + this.surfaceColor,
38 this.isCurved = false, 41 this.isCurved = false,
39 this.smoothness = 0.35, 42 this.smoothness = 0.35,
40 - }) : assert(drawLine || drawPoints); 43 + }) : assert(drawLine || drawPoints || drawSurface);
41 44
42 final List<LineChartValue> data; 45 final List<LineChartValue> data;
43 - final PdfColor pointColor;  
44 - final double pointSize; 46 +
  47 + final bool drawLine;
45 final PdfColor color; 48 final PdfColor color;
46 final double lineWidth; 49 final double lineWidth;
47 - final bool drawLine; 50 +
48 final bool drawPoints; 51 final bool drawPoints;
  52 + final PdfColor pointColor;
  53 + final double pointSize;
  54 +
  55 + final bool drawSurface;
  56 + final PdfColor surfaceColor;
  57 + final double surfaceOpacity;
  58 +
49 final bool isCurved; 59 final bool isCurved;
50 final double smoothness; 60 final double smoothness;
51 61
52 - void _drawLine(Context context, ChartGrid grid) { 62 + void _drawLine(Context context, ChartGrid grid, bool moveTo) {
53 if (data.length < 2) { 63 if (data.length < 2) {
54 return; 64 return;
55 } 65 }
@@ -57,7 +67,11 @@ class LineDataSet extends DataSet { @@ -57,7 +67,11 @@ class LineDataSet extends DataSet {
57 PdfPoint t = const PdfPoint(0, 0); 67 PdfPoint t = const PdfPoint(0, 0);
58 68
59 final PdfPoint p = grid.tochart(data.first.point); 69 final PdfPoint p = grid.tochart(data.first.point);
60 - context.canvas.moveTo(p.x, p.y); 70 + if (moveTo) {
  71 + context.canvas.moveTo(p.x, p.y);
  72 + } else {
  73 + context.canvas.lineTo(p.x, p.y);
  74 + }
61 75
62 for (int i = 1; i < data.length; i++) { 76 for (int i = 1; i < data.length; i++) {
63 final PdfPoint p = grid.tochart(data[i].point); 77 final PdfPoint p = grid.tochart(data[i].point);
@@ -82,6 +96,20 @@ class LineDataSet extends DataSet { @@ -82,6 +96,20 @@ class LineDataSet extends DataSet {
82 } 96 }
83 } 97 }
84 98
  99 + void _drawSurface(Context context, ChartGrid grid) {
  100 + if (data.length < 2) {
  101 + return;
  102 + }
  103 +
  104 + final double y = (grid is LinearGrid) ? grid.xAxisOffset : 0;
  105 + _drawLine(context, grid, true);
  106 +
  107 + final PdfPoint pe = grid.tochart(data.last.point);
  108 + context.canvas.lineTo(pe.x, y);
  109 + final PdfPoint pf = grid.tochart(data.first.point);
  110 + context.canvas.lineTo(pf.x, y);
  111 + }
  112 +
85 void _drawPoints(Context context, ChartGrid grid) { 113 void _drawPoints(Context context, ChartGrid grid) {
86 for (final LineChartValue value in data) { 114 for (final LineChartValue value in data) {
87 final PdfPoint p = grid.tochart(value.point); 115 final PdfPoint p = grid.tochart(value.point);
@@ -90,7 +118,31 @@ class LineDataSet extends DataSet { @@ -90,7 +118,31 @@ class LineDataSet extends DataSet {
90 } 118 }
91 119
92 @override 120 @override
93 - void paintBackground(Context context, ChartGrid grid) {} 121 + void paintBackground(Context context, ChartGrid grid) {
  122 + if (data.isEmpty) {
  123 + return;
  124 + }
  125 +
  126 + if (drawSurface) {
  127 + _drawSurface(context, grid);
  128 +
  129 + if (surfaceOpacity != 1) {
  130 + context.canvas
  131 + ..saveContext()
  132 + ..setGraphicState(
  133 + PdfGraphicState(opacity: surfaceOpacity),
  134 + );
  135 + }
  136 +
  137 + context.canvas
  138 + ..setFillColor(surfaceColor ?? color)
  139 + ..fillPath();
  140 +
  141 + if (surfaceOpacity != 1) {
  142 + context.canvas.restoreContext();
  143 + }
  144 + }
  145 + }
94 146
95 @override 147 @override
96 void paintForeground(Context context, ChartGrid grid) { 148 void paintForeground(Context context, ChartGrid grid) {
@@ -99,7 +151,7 @@ class LineDataSet extends DataSet { @@ -99,7 +151,7 @@ class LineDataSet extends DataSet {
99 } 151 }
100 152
101 if (drawLine) { 153 if (drawLine) {
102 - _drawLine(context, grid); 154 + _drawLine(context, grid, true);
103 155
104 context.canvas 156 context.canvas
105 ..setStrokeColor(color) 157 ..setStrokeColor(color)
@@ -18,6 +18,8 @@ @@ -18,6 +18,8 @@
18 18
19 part of widget; 19 part of widget;
20 20
  21 +typedef GridAxisFormat = String Function(double value);
  22 +
21 class LinearGrid extends ChartGrid { 23 class LinearGrid extends ChartGrid {
22 LinearGrid({ 24 LinearGrid({
23 @required this.xAxis, 25 @required this.xAxis,
@@ -28,7 +30,11 @@ class LinearGrid extends ChartGrid { @@ -28,7 +30,11 @@ class LinearGrid extends ChartGrid {
28 this.lineWidth = 1, 30 this.lineWidth = 1,
29 this.color = PdfColors.black, 31 this.color = PdfColors.black,
30 this.separatorLineWidth = .5, 32 this.separatorLineWidth = .5,
  33 + this.drawXDivisions = false,
  34 + this.drawYDivisions = true,
31 this.separatorColor = PdfColors.grey, 35 this.separatorColor = PdfColors.grey,
  36 + this.xAxisFormat = _defaultFormat,
  37 + this.yAxisFormat = _defaultFormat,
32 }) : assert(_isSortedAscending(xAxis)), 38 }) : assert(_isSortedAscending(xAxis)),
33 assert(_isSortedAscending(yAxis)); 39 assert(_isSortedAscending(yAxis));
34 40
@@ -41,20 +47,24 @@ class LinearGrid extends ChartGrid { @@ -41,20 +47,24 @@ class LinearGrid extends ChartGrid {
41 final PdfColor color; 47 final PdfColor color;
42 final double separatorLineWidth; 48 final double separatorLineWidth;
43 final PdfColor separatorColor; 49 final PdfColor separatorColor;
  50 + final bool drawXDivisions;
  51 + final bool drawYDivisions;
  52 + final GridAxisFormat xAxisFormat;
  53 + final GridAxisFormat yAxisFormat;
44 54
45 TextStyle style; 55 TextStyle style;
46 PdfFont font; 56 PdfFont font;
47 - PdfFontMetrics xAxisFontMetric;  
48 - PdfFontMetrics yAxisFontMetric;  
49 PdfRect gridBox; 57 PdfRect gridBox;
50 double xOffset; 58 double xOffset;
51 double xTotal; 59 double xTotal;
52 double yOffset; 60 double yOffset;
53 double yTotal; 61 double yTotal;
54 62
  63 + static String _defaultFormat(double v) => v.toString();
  64 +
55 static bool _isSortedAscending(List<double> list) { 65 static bool _isSortedAscending(List<double> list) {
56 double prev = list.first; 66 double prev = list.first;
57 - for (double elem in list) { 67 + for (final double elem in list) {
58 if (prev > elem) { 68 if (prev > elem) {
59 return false; 69 return false;
60 } 70 }
@@ -64,30 +74,34 @@ class LinearGrid extends ChartGrid { @@ -64,30 +74,34 @@ class LinearGrid extends ChartGrid {
64 } 74 }
65 75
66 @override 76 @override
67 - PdfPoint tochart(PdfPoint p) {  
68 - return PdfPoint(  
69 - gridBox.left + gridBox.width * (p.x - xOffset) / xTotal,  
70 - gridBox.bottom + gridBox.height * (p.y - yOffset) / yTotal,  
71 - );  
72 - }  
73 -  
74 - @override  
75 void layout(Context context, PdfPoint size) { 77 void layout(Context context, PdfPoint size) {
76 style = Theme.of(context).defaultTextStyle.merge(textStyle); 78 style = Theme.of(context).defaultTextStyle.merge(textStyle);
77 font = style.font.getFont(context); 79 font = style.font.getFont(context);
78 80
79 - xAxisFontMetric =  
80 - font.stringMetrics(xAxis.reduce(math.max).toStringAsFixed(1)) *  
81 - (style.fontSize);  
82 - yAxisFontMetric =  
83 - font.stringMetrics(yAxis.reduce(math.max).toStringAsFixed(1)) *  
84 - (style.fontSize); 81 + double xMaxWidth = 0;
  82 + double xMaxHeight = 0;
  83 + for (final double value in xAxis) {
  84 + final PdfFontMetrics metrics =
  85 + font.stringMetrics(xAxisFormat(value)) * style.fontSize;
  86 + xMaxWidth = math.max(xMaxWidth, metrics.width);
  87 + xMaxHeight = math.max(xMaxHeight, metrics.maxHeight);
  88 + }
  89 +
  90 + double yMaxWidth = 0;
  91 + double yMaxHeight = 0;
  92 + for (final double value in yAxis) {
  93 + final PdfFontMetrics metrics =
  94 + font.stringMetrics(yAxisFormat(value)) * style.fontSize;
  95 + yMaxWidth = math.max(yMaxWidth, metrics.width);
  96 + yMaxHeight = math.max(yMaxHeight, metrics.maxHeight);
  97 + }
85 98
86 gridBox = PdfRect.fromLTRB( 99 gridBox = PdfRect.fromLTRB(
87 - yAxisFontMetric.width + xMargin,  
88 - xAxisFontMetric.height + yMargin,  
89 - size.x - xAxisFontMetric.width / 2,  
90 - size.y - yAxisFontMetric.height / 2); 100 + yMaxWidth + xMargin,
  101 + xMaxHeight + yMargin,
  102 + size.x - xMaxWidth / 2,
  103 + size.y - yMaxHeight / 2,
  104 + );
91 105
92 xOffset = xAxis.reduce(math.min); 106 xOffset = xAxis.reduce(math.min);
93 yOffset = yAxis.reduce(math.min); 107 yOffset = yAxis.reduce(math.min);
@@ -96,54 +110,136 @@ class LinearGrid extends ChartGrid { @@ -96,54 +110,136 @@ class LinearGrid extends ChartGrid {
96 } 110 }
97 111
98 @override 112 @override
99 - void paintBackground(Context context, PdfPoint size) {  
100 - xAxis.asMap().forEach((int i, double x) { 113 + PdfPoint tochart(PdfPoint p) {
  114 + return PdfPoint(
  115 + gridBox.left + gridBox.width * (p.x - xOffset) / xTotal,
  116 + gridBox.bottom + gridBox.height * (p.y - yOffset) / yTotal,
  117 + );
  118 + }
  119 +
  120 + double get xAxisOffset => gridBox.bottom;
  121 +
  122 + double get yAxisOffset => gridBox.left;
  123 +
  124 + void _drawAxis(Context context, PdfPoint size) {
  125 + context.canvas
  126 + ..moveTo(size.x, gridBox.bottom)
  127 + ..lineTo(gridBox.left - xMargin / 2, gridBox.bottom)
  128 + ..moveTo(gridBox.left, gridBox.bottom)
  129 + ..lineTo(gridBox.left, size.y)
  130 + ..setStrokeColor(color)
  131 + ..setLineWidth(lineWidth)
  132 + ..setLineCap(PdfLineCap.joinMiter)
  133 + ..strokePath();
  134 + }
  135 +
  136 + void _drawYDivisions(Context context, PdfPoint size) {
  137 + for (final double y in yAxis.sublist(1)) {
  138 + final PdfPoint p = tochart(PdfPoint(0, y));
  139 + context.canvas.drawLine(
  140 + gridBox.left,
  141 + p.y,
  142 + size.x,
  143 + p.y,
  144 + );
  145 + }
  146 +
  147 + context.canvas
  148 + ..setStrokeColor(separatorColor)
  149 + ..setLineWidth(separatorLineWidth)
  150 + ..setLineCap(PdfLineCap.joinMiter)
  151 + ..strokePath();
  152 + }
  153 +
  154 + void _drawXDivisions(Context context, PdfPoint size) {
  155 + for (final double x in xAxis.sublist(1)) {
  156 + final PdfPoint p = tochart(PdfPoint(x, 0));
  157 + context.canvas.drawLine(
  158 + p.x,
  159 + size.y,
  160 + p.x,
  161 + gridBox.bottom,
  162 + );
  163 + }
  164 +
  165 + context.canvas
  166 + ..setStrokeColor(separatorColor)
  167 + ..setLineWidth(separatorLineWidth)
  168 + ..setLineCap(PdfLineCap.joinMiter)
  169 + ..strokePath();
  170 + }
  171 +
  172 + void _drawYValues(Context context, PdfPoint size) {
  173 + for (final double y in yAxis) {
  174 + final String v = yAxisFormat(y);
  175 + final PdfFontMetrics metrics = font.stringMetrics(v) * style.fontSize;
  176 + final PdfPoint p = tochart(PdfPoint(0, y));
  177 +
101 context.canvas 178 context.canvas
102 ..setColor(style.color) 179 ..setColor(style.color)
103 ..drawString( 180 ..drawString(
104 style.font.getFont(context), 181 style.font.getFont(context),
105 style.fontSize, 182 style.fontSize,
106 - x.toStringAsFixed(1),  
107 - gridBox.left +  
108 - gridBox.width * i / (xAxis.length - 1) -  
109 - xAxisFontMetric.width / 2,  
110 - 0, 183 + v,
  184 + gridBox.left - xMargin - metrics.width,
  185 + p.y - (metrics.ascent + metrics.descent) / 2,
111 ); 186 );
112 - }); 187 + }
  188 + }
  189 +
  190 + void _drawXValues(Context context, PdfPoint size) {
  191 + for (final double x in xAxis) {
  192 + final String v = xAxisFormat(x);
  193 + final PdfFontMetrics metrics = font.stringMetrics(v) * style.fontSize;
  194 + final PdfPoint p = tochart(PdfPoint(x, 0));
113 195
114 - for (double y in yAxis.where((double y) => y != yAxis.first)) {  
115 - final double textWidth =  
116 - (font.stringMetrics(y.toStringAsFixed(1)) * (style.fontSize)).width;  
117 - final double yPos = gridBox.bottom + gridBox.height * y / yAxis.last;  
118 context.canvas 196 context.canvas
119 ..setColor(style.color) 197 ..setColor(style.color)
120 ..drawString( 198 ..drawString(
121 style.font.getFont(context), 199 style.font.getFont(context),
122 style.fontSize, 200 style.fontSize,
123 - y.toStringAsFixed(1),  
124 - xAxisFontMetric.width / 2 - textWidth / 2,  
125 - yPos - font.ascent, 201 + v,
  202 + p.x - metrics.width / 2,
  203 + -metrics.descent,
126 ); 204 );
  205 + }
  206 + }
127 207
128 - context.canvas.drawLine(  
129 - gridBox.left,  
130 - yPos + font.descent + font.ascent - separatorLineWidth / 2,  
131 - gridBox.right,  
132 - yPos + font.descent + font.ascent - separatorLineWidth / 2); 208 + @override
  209 + void paintBackground(Context context, PdfPoint size) {}
  210 +
  211 + @override
  212 + void paint(Context context, PdfPoint size) {
  213 + if (drawXDivisions) {
  214 + _drawXDivisions(context, size);
133 } 215 }
134 - context.canvas  
135 - ..setStrokeColor(separatorColor)  
136 - ..setLineWidth(separatorLineWidth)  
137 - ..strokePath(); 216 + if (drawYDivisions) {
  217 + _drawYDivisions(context, size);
  218 + }
  219 + }
138 220
  221 + @override
  222 + void paintForeground(Context context, PdfPoint size) {
  223 + _drawAxis(context, size);
  224 + _drawXValues(context, size);
  225 + _drawYValues(context, size);
  226 + }
  227 +
  228 + @override
  229 + void clip(Context context, PdfPoint size) {
139 context.canvas 230 context.canvas
140 - ..setStrokeColor(color)  
141 - ..setLineWidth(lineWidth)  
142 - ..drawLine(gridBox.left, gridBox.bottom, gridBox.right, gridBox.bottom)  
143 - ..drawLine(gridBox.left, gridBox.bottom, gridBox.left, gridBox.top)  
144 - ..strokePath(); 231 + ..saveContext()
  232 + ..drawRect(
  233 + gridBox.left,
  234 + gridBox.bottom,
  235 + size.x - gridBox.left,
  236 + size.y - gridBox.bottom,
  237 + )
  238 + ..clipPath();
145 } 239 }
146 240
147 @override 241 @override
148 - void paintForeground(Context context, PdfPoint size) {} 242 + void unClip(Context context, PdfPoint size) {
  243 + context.canvas.restoreContext();
  244 + }
149 } 245 }