Showing
10 changed files
with
792 additions
and
336 deletions
@@ -30,10 +30,12 @@ export 'package:barcode/barcode.dart'; | @@ -30,10 +30,12 @@ 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/bar_chart.dart'; | ||
33 | part 'widgets/chart/chart.dart'; | 34 | part 'widgets/chart/chart.dart'; |
34 | -part 'widgets/chart/linear_grid.dart'; | 35 | +part 'widgets/chart/grid_axis.dart'; |
36 | +part 'widgets/chart/grid_cartesian.dart'; | ||
37 | +part 'widgets/chart/legend.dart'; | ||
35 | part 'widgets/chart/line_chart.dart'; | 38 | part 'widgets/chart/line_chart.dart'; |
36 | -part 'widgets/chart/bar_chart.dart'; | ||
37 | part 'widgets/clip.dart'; | 39 | part 'widgets/clip.dart'; |
38 | part 'widgets/container.dart'; | 40 | part 'widgets/container.dart'; |
39 | part 'widgets/content.dart'; | 41 | part 'widgets/content.dart'; |
@@ -18,53 +18,60 @@ | @@ -18,53 +18,60 @@ | ||
18 | 18 | ||
19 | part of widget; | 19 | part of widget; |
20 | 20 | ||
21 | -class BarDataSet extends DataSet { | 21 | +class BarDataSet extends Dataset { |
22 | BarDataSet({ | 22 | BarDataSet({ |
23 | @required this.data, | 23 | @required this.data, |
24 | + String legend, | ||
24 | this.borderColor, | 25 | this.borderColor, |
25 | this.borderWidth = 1.5, | 26 | this.borderWidth = 1.5, |
26 | - this.color = PdfColors.blue, | 27 | + PdfColor color = PdfColors.blue, |
27 | this.drawBorder = true, | 28 | this.drawBorder = true, |
28 | this.drawSurface = true, | 29 | this.drawSurface = true, |
29 | this.surfaceOpacity = 1, | 30 | this.surfaceOpacity = 1, |
30 | - this.width = 20, | 31 | + this.width = 10, |
31 | this.offset = 0, | 32 | this.offset = 0, |
32 | - this.margin = 5, | ||
33 | - }) : assert(drawBorder || drawSurface); | 33 | + }) : assert(drawBorder || drawSurface), |
34 | + super( | ||
35 | + legend: legend, | ||
36 | + color: color, | ||
37 | + ); | ||
34 | 38 | ||
35 | final List<LineChartValue> data; | 39 | final List<LineChartValue> data; |
36 | - final double width; | ||
37 | - final double offset; | ||
38 | - final double margin; | ||
39 | 40 | ||
40 | final bool drawBorder; | 41 | final bool drawBorder; |
41 | final PdfColor borderColor; | 42 | final PdfColor borderColor; |
42 | final double borderWidth; | 43 | final double borderWidth; |
43 | 44 | ||
44 | final bool drawSurface; | 45 | final bool drawSurface; |
45 | - final PdfColor color; | 46 | + |
46 | final double surfaceOpacity; | 47 | final double surfaceOpacity; |
47 | 48 | ||
49 | + final double width; | ||
50 | + final double offset; | ||
51 | + | ||
48 | void _drawSurface(Context context, ChartGrid grid, LineChartValue value) { | 52 | 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); | 53 | + final double y = (grid is CartesianGrid) ? grid.xAxisOffset : 0; |
54 | + final PdfPoint p = grid.toChart(value.point); | ||
51 | 55 | ||
52 | context.canvas.drawRect(p.x + offset - width / 2, y, width, p.y); | 56 | context.canvas.drawRect(p.x + offset - width / 2, y, width, p.y); |
53 | } | 57 | } |
54 | 58 | ||
55 | @override | 59 | @override |
56 | - void paintBackground(Context context, ChartGrid grid) {} | 60 | + void layout(Context context, BoxConstraints constraints, |
61 | + {bool parentUsesSize = false}) { | ||
62 | + box = PdfRect.fromPoints(PdfPoint.zero, constraints.biggest); | ||
63 | + } | ||
57 | 64 | ||
58 | @override | 65 | @override |
59 | - void paintForeground(Context context, ChartGrid grid) { | ||
60 | - if (data.isEmpty) { | ||
61 | - return; | ||
62 | - } | 66 | + void paint(Context context) { |
67 | + super.paint(context); | ||
63 | 68 | ||
64 | if (data.isEmpty) { | 69 | if (data.isEmpty) { |
65 | return; | 70 | return; |
66 | } | 71 | } |
67 | 72 | ||
73 | + final ChartGrid grid = Chart.of(context).grid; | ||
74 | + | ||
68 | if (drawSurface) { | 75 | if (drawSurface) { |
69 | for (final LineChartValue value in data) { | 76 | for (final LineChartValue value in data) { |
70 | _drawSurface(context, grid, value); | 77 | _drawSurface(context, grid, value); |
@@ -19,15 +19,39 @@ | @@ -19,15 +19,39 @@ | ||
19 | part of widget; | 19 | part of widget; |
20 | 20 | ||
21 | /// This widget is in preview and the API is subject to change | 21 | /// This widget is in preview and the API is subject to change |
22 | -class Chart extends Widget { | 22 | +class Chart extends Widget implements Inherited { |
23 | Chart({ | 23 | Chart({ |
24 | @required this.grid, | 24 | @required this.grid, |
25 | - @required this.data, | 25 | + @required this.datasets, |
26 | + this.overlay, | ||
27 | + this.title, | ||
28 | + this.bottom, | ||
29 | + this.left, | ||
30 | + this.right, | ||
26 | }); | 31 | }); |
27 | 32 | ||
33 | + /// The Coordinate system that will layout the content | ||
28 | final ChartGrid grid; | 34 | final ChartGrid grid; |
29 | 35 | ||
30 | - final List<DataSet> data; | 36 | + /// The list of dataset to display |
37 | + final List<Dataset> datasets; | ||
38 | + | ||
39 | + /// Legend for this chart | ||
40 | + final Widget overlay; | ||
41 | + | ||
42 | + final Widget title; | ||
43 | + | ||
44 | + final Widget bottom; | ||
45 | + | ||
46 | + final Widget left; | ||
47 | + | ||
48 | + final Widget right; | ||
49 | + | ||
50 | + Context _context; | ||
51 | + | ||
52 | + Widget _child; | ||
53 | + | ||
54 | + static Chart of(Context context) => context.inherited[Chart]; | ||
31 | 55 | ||
32 | PdfPoint _computeSize(BoxConstraints constraints) { | 56 | PdfPoint _computeSize(BoxConstraints constraints) { |
33 | if (constraints.isTight) { | 57 | if (constraints.isTight) { |
@@ -50,51 +74,64 @@ class Chart extends Widget { | @@ -50,51 +74,64 @@ class Chart extends Widget { | ||
50 | return constraints.constrain(PdfPoint(width, height)); | 74 | return constraints.constrain(PdfPoint(width, height)); |
51 | } | 75 | } |
52 | 76 | ||
77 | + Widget _build(Context context) { | ||
78 | + return Column( | ||
79 | + children: <Widget>[ | ||
80 | + if (title != null) title, | ||
81 | + Expanded( | ||
82 | + child: Row( | ||
83 | + children: <Widget>[ | ||
84 | + if (left != null) left, | ||
85 | + Expanded( | ||
86 | + child: Stack( | ||
87 | + children: <Widget>[ | ||
88 | + grid, | ||
89 | + if (overlay != null) overlay, | ||
90 | + ], | ||
91 | + ), | ||
92 | + ), | ||
93 | + if (right != null) right, | ||
94 | + ], | ||
95 | + ), | ||
96 | + ), | ||
97 | + if (bottom != null) bottom, | ||
98 | + ], | ||
99 | + ); | ||
100 | + } | ||
101 | + | ||
53 | @override | 102 | @override |
54 | void layout(Context context, BoxConstraints constraints, | 103 | void layout(Context context, BoxConstraints constraints, |
55 | {bool parentUsesSize = false}) { | 104 | {bool parentUsesSize = false}) { |
56 | box = PdfRect.fromPoints(PdfPoint.zero, _computeSize(constraints)); | 105 | box = PdfRect.fromPoints(PdfPoint.zero, _computeSize(constraints)); |
57 | - | ||
58 | - grid.layout(context, box.size); | 106 | + _context = context.inheritFrom(this); |
107 | + _child = _build(_context); | ||
108 | + _child.layout(_context, BoxConstraints.tight(box.size)); | ||
59 | } | 109 | } |
60 | 110 | ||
61 | @override | 111 | @override |
62 | void paint(Context context) { | 112 | void paint(Context context) { |
63 | - super.paint(context); | 113 | + super.paint(_context); |
64 | 114 | ||
65 | final Matrix4 mat = Matrix4.identity(); | 115 | final Matrix4 mat = Matrix4.identity(); |
66 | mat.translate(box.x, box.y); | 116 | mat.translate(box.x, box.y); |
67 | - context.canvas | 117 | + _context.canvas |
68 | ..saveContext() | 118 | ..saveContext() |
69 | ..setTransform(mat); | 119 | ..setTransform(mat); |
70 | 120 | ||
71 | - grid.paintBackground(context, box.size); | ||
72 | - grid.clip(context, box.size); | ||
73 | - for (DataSet dataSet in data) { | ||
74 | - dataSet.paintBackground(context, grid); | ||
75 | - } | ||
76 | - grid.unClip(context, box.size); | ||
77 | - grid.paint(context, box.size); | ||
78 | - grid.clip(context, box.size); | ||
79 | - for (DataSet dataSet in data) { | ||
80 | - dataSet.paintForeground(context, grid); | ||
81 | - } | ||
82 | - grid.unClip(context, box.size); | ||
83 | - grid.paintForeground(context, box.size); | ||
84 | - context.canvas.restoreContext(); | 121 | + _child.paint(_context); |
122 | + | ||
123 | + _context.canvas.restoreContext(); | ||
85 | } | 124 | } |
86 | } | 125 | } |
87 | 126 | ||
88 | -abstract class ChartGrid { | ||
89 | - void layout(Context context, PdfPoint size); | ||
90 | - void paintBackground(Context context, PdfPoint size); | ||
91 | - void paint(Context context, PdfPoint size); | ||
92 | - void paintForeground(Context context, PdfPoint size); | ||
93 | - | ||
94 | - void clip(Context context, PdfPoint size); | ||
95 | - void unClip(Context context, PdfPoint size); | 127 | +abstract class ChartGrid extends Widget { |
128 | + @override | ||
129 | + void layout(Context context, BoxConstraints constraints, | ||
130 | + {bool parentUsesSize = false}) { | ||
131 | + box = PdfRect.fromPoints(PdfPoint.zero, constraints.biggest); | ||
132 | + } | ||
96 | 133 | ||
97 | - PdfPoint tochart(PdfPoint p); | 134 | + PdfPoint toChart(PdfPoint p); |
98 | } | 135 | } |
99 | 136 | ||
100 | @immutable | 137 | @immutable |
@@ -102,7 +139,31 @@ abstract class ChartValue { | @@ -102,7 +139,31 @@ abstract class ChartValue { | ||
102 | const ChartValue(); | 139 | const ChartValue(); |
103 | } | 140 | } |
104 | 141 | ||
105 | -abstract class DataSet { | ||
106 | - void paintBackground(Context context, ChartGrid grid); | ||
107 | - void paintForeground(Context context, ChartGrid grid); | 142 | +abstract class Dataset extends Widget { |
143 | + Dataset({ | ||
144 | + this.legend, | ||
145 | + this.color, | ||
146 | + }); | ||
147 | + | ||
148 | + final String legend; | ||
149 | + | ||
150 | + final PdfColor color; | ||
151 | + | ||
152 | + void paintBackground(Context context) {} | ||
153 | + | ||
154 | + Widget legendeShape() { | ||
155 | + return Container( | ||
156 | + decoration: BoxDecoration( | ||
157 | + color: color, | ||
158 | + border: const BoxBorder( | ||
159 | + left: true, | ||
160 | + top: true, | ||
161 | + bottom: true, | ||
162 | + right: true, | ||
163 | + color: PdfColors.black, | ||
164 | + width: .5, | ||
165 | + ), | ||
166 | + ), | ||
167 | + ); | ||
168 | + } | ||
108 | } | 169 | } |
pdf/lib/widgets/chart/grid_axis.dart
0 → 100644
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 | +typedef GridAxisFormat = String Function(num value); | ||
22 | + | ||
23 | +abstract class GridAxis extends Widget { | ||
24 | + GridAxis({ | ||
25 | + GridAxisFormat format, | ||
26 | + this.textStyle, | ||
27 | + this.margin, | ||
28 | + double marginStart, | ||
29 | + double marginEnd, | ||
30 | + PdfColor color, | ||
31 | + double width, | ||
32 | + bool divisions, | ||
33 | + double divisionsWidth, | ||
34 | + PdfColor divisionsColor, | ||
35 | + bool divisionsDashed, | ||
36 | + bool ticks, | ||
37 | + bool axisTick, | ||
38 | + }) : format = format ?? _defaultFormat, | ||
39 | + color = color ?? PdfColors.black, | ||
40 | + width = width ?? 1, | ||
41 | + divisions = divisions ?? false, | ||
42 | + divisionsWidth = divisionsWidth ?? .5, | ||
43 | + divisionsColor = divisionsColor ?? PdfColors.grey, | ||
44 | + _marginStart = marginStart ?? 0, | ||
45 | + _marginEnd = marginEnd ?? 0, | ||
46 | + ticks = ticks ?? false, | ||
47 | + _axisTick = axisTick, | ||
48 | + divisionsDashed = divisionsDashed ?? false; | ||
49 | + | ||
50 | + Axis direction; | ||
51 | + | ||
52 | + final GridAxisFormat format; | ||
53 | + | ||
54 | + final TextStyle textStyle; | ||
55 | + | ||
56 | + final double margin; | ||
57 | + | ||
58 | + double _crossAxisPosition = 0; | ||
59 | + | ||
60 | + double _textMargin; | ||
61 | + | ||
62 | + final double _marginStart; | ||
63 | + | ||
64 | + double _marginEnd; | ||
65 | + | ||
66 | + final PdfColor color; | ||
67 | + | ||
68 | + final double width; | ||
69 | + | ||
70 | + final bool divisions; | ||
71 | + | ||
72 | + final double divisionsWidth; | ||
73 | + | ||
74 | + final PdfColor divisionsColor; | ||
75 | + | ||
76 | + final bool divisionsDashed; | ||
77 | + | ||
78 | + final bool ticks; | ||
79 | + | ||
80 | + bool _axisTick; | ||
81 | + | ||
82 | + double axisPosition = 0; | ||
83 | + | ||
84 | + static String _defaultFormat(num v) => v.toString(); | ||
85 | + | ||
86 | + double transfer(num input) { | ||
87 | + return input.toDouble(); | ||
88 | + } | ||
89 | + | ||
90 | + double toChart(num input); | ||
91 | + | ||
92 | + void paintBackground(Context context); | ||
93 | +} | ||
94 | + | ||
95 | +class FixedAxis<T extends num> extends GridAxis { | ||
96 | + FixedAxis( | ||
97 | + this.values, { | ||
98 | + GridAxisFormat format, | ||
99 | + TextStyle textStyle, | ||
100 | + double margin, | ||
101 | + double marginStart, | ||
102 | + double marginEnd, | ||
103 | + PdfColor color, | ||
104 | + double width, | ||
105 | + bool divisions, | ||
106 | + double divisionsWidth, | ||
107 | + PdfColor divisionsColor, | ||
108 | + bool divisionsDashed, | ||
109 | + bool ticks, | ||
110 | + bool axisTick, | ||
111 | + }) : assert(_isSortedAscending(values)), | ||
112 | + super( | ||
113 | + format: format, | ||
114 | + textStyle: textStyle, | ||
115 | + margin: margin, | ||
116 | + marginStart: marginStart, | ||
117 | + marginEnd: marginEnd, | ||
118 | + color: color, | ||
119 | + width: width, | ||
120 | + divisions: divisions, | ||
121 | + divisionsWidth: divisionsWidth, | ||
122 | + divisionsColor: divisionsColor, | ||
123 | + divisionsDashed: divisionsDashed, | ||
124 | + ticks: ticks, | ||
125 | + axisTick: axisTick, | ||
126 | + ); | ||
127 | + | ||
128 | + static FixedAxis<int> fromStrings( | ||
129 | + List<String> values, { | ||
130 | + TextStyle textStyle, | ||
131 | + double margin, | ||
132 | + double marginStart, | ||
133 | + double marginEnd, | ||
134 | + PdfColor color, | ||
135 | + double width, | ||
136 | + bool divisions, | ||
137 | + double divisionsWidth, | ||
138 | + PdfColor divisionsColor, | ||
139 | + bool divisionsDashed, | ||
140 | + bool ticks, | ||
141 | + bool axisTick, | ||
142 | + }) { | ||
143 | + return FixedAxis<int>( | ||
144 | + List<int>.generate(values.length, (int index) => index), | ||
145 | + format: (num v) => values[v], | ||
146 | + textStyle: textStyle, | ||
147 | + margin: margin, | ||
148 | + marginStart: marginStart, | ||
149 | + marginEnd: marginEnd, | ||
150 | + color: color, | ||
151 | + width: width, | ||
152 | + divisions: divisions, | ||
153 | + divisionsWidth: divisionsWidth, | ||
154 | + divisionsColor: divisionsColor, | ||
155 | + divisionsDashed: divisionsDashed, | ||
156 | + ticks: ticks, | ||
157 | + axisTick: axisTick, | ||
158 | + ); | ||
159 | + } | ||
160 | + | ||
161 | + final List<T> values; | ||
162 | + | ||
163 | + static bool _isSortedAscending(List<num> list) { | ||
164 | + num prev = list.first; | ||
165 | + for (final num elem in list) { | ||
166 | + if (prev > elem) { | ||
167 | + return false; | ||
168 | + } | ||
169 | + prev = elem; | ||
170 | + } | ||
171 | + return true; | ||
172 | + } | ||
173 | + | ||
174 | + @override | ||
175 | + double toChart(num input) { | ||
176 | + final double offset = transfer(values.first); | ||
177 | + final double total = transfer(values.last) - offset; | ||
178 | + final double start = _crossAxisPosition + _marginStart; | ||
179 | + switch (direction) { | ||
180 | + case Axis.horizontal: | ||
181 | + return box.left + | ||
182 | + start + | ||
183 | + (box.width - start - _marginEnd) * | ||
184 | + (transfer(input) - offset) / | ||
185 | + total; | ||
186 | + case Axis.vertical: | ||
187 | + return box.bottom + | ||
188 | + start + | ||
189 | + (box.height - start - _marginEnd) * | ||
190 | + (transfer(input) - offset) / | ||
191 | + total; | ||
192 | + } | ||
193 | + | ||
194 | + return null; | ||
195 | + } | ||
196 | + | ||
197 | + @override | ||
198 | + void layout(Context context, BoxConstraints constraints, | ||
199 | + {bool parentUsesSize = false}) { | ||
200 | + assert(Chart.of(context) != null, | ||
201 | + '$runtimeType cannot be used without a Chart widget'); | ||
202 | + | ||
203 | + final PdfPoint size = constraints.biggest; | ||
204 | + final TextStyle style = Theme.of(context).defaultTextStyle.merge(textStyle); | ||
205 | + final PdfFont font = style.font.getFont(context); | ||
206 | + | ||
207 | + double maxWidth = 0; | ||
208 | + double maxHeight = 0; | ||
209 | + PdfFontMetrics metricsFirst; | ||
210 | + PdfFontMetrics metrics; | ||
211 | + for (final T value in values) { | ||
212 | + metrics = font.stringMetrics(format(value)) * style.fontSize; | ||
213 | + metricsFirst ??= metrics; | ||
214 | + maxWidth = math.max(maxWidth, metrics.maxWidth); | ||
215 | + maxHeight = math.max(maxHeight, metrics.maxHeight); | ||
216 | + } | ||
217 | + | ||
218 | + switch (direction) { | ||
219 | + case Axis.horizontal: | ||
220 | + _textMargin = margin ?? 2; | ||
221 | + _axisTick ??= false; | ||
222 | + final double minStart = metricsFirst.maxWidth / 2; | ||
223 | + _marginEnd = math.max(_marginEnd, metrics.maxWidth / 2); | ||
224 | + _crossAxisPosition = math.max(_crossAxisPosition, minStart); | ||
225 | + axisPosition = math.max(axisPosition, maxHeight + _textMargin); | ||
226 | + box = PdfRect(0, 0, size.x, axisPosition); | ||
227 | + break; | ||
228 | + case Axis.vertical: | ||
229 | + _textMargin = margin ?? 10; | ||
230 | + _axisTick ??= true; | ||
231 | + _marginEnd = math.max(_marginEnd, metrics.maxHeight / 2); | ||
232 | + final double minStart = metricsFirst.maxHeight / 2; | ||
233 | + _marginEnd = math.max(_marginEnd, metrics.maxWidth / 2); | ||
234 | + _crossAxisPosition = math.max(_crossAxisPosition, minStart); | ||
235 | + axisPosition = math.max(axisPosition, maxWidth + _textMargin); | ||
236 | + box = PdfRect(0, 0, axisPosition, size.y); | ||
237 | + break; | ||
238 | + } | ||
239 | + } | ||
240 | + | ||
241 | + void _drawYValues(Context context) { | ||
242 | + context.canvas | ||
243 | + ..moveTo(axisPosition, box.top) | ||
244 | + ..lineTo(axisPosition, box.bottom + _crossAxisPosition); | ||
245 | + | ||
246 | + if (_axisTick && _textMargin > 0) { | ||
247 | + context.canvas | ||
248 | + ..moveTo(axisPosition, box.bottom + _crossAxisPosition) | ||
249 | + ..lineTo( | ||
250 | + axisPosition - _textMargin / 2, box.bottom + _crossAxisPosition); | ||
251 | + } | ||
252 | + | ||
253 | + if (ticks && _textMargin > 0) { | ||
254 | + for (final num x in values) { | ||
255 | + final double p = toChart(x); | ||
256 | + context.canvas | ||
257 | + ..moveTo(axisPosition, p) | ||
258 | + ..lineTo(axisPosition - _textMargin / 2, p); | ||
259 | + } | ||
260 | + } | ||
261 | + | ||
262 | + context.canvas | ||
263 | + ..setStrokeColor(color) | ||
264 | + ..setLineWidth(width) | ||
265 | + ..setLineCap(PdfLineCap.joinBevel) | ||
266 | + ..strokePath(); | ||
267 | + | ||
268 | + for (final T y in values) { | ||
269 | + final String v = format(y); | ||
270 | + final TextStyle style = | ||
271 | + Theme.of(context).defaultTextStyle.merge(textStyle); | ||
272 | + final PdfFont font = style.font.getFont(context); | ||
273 | + final PdfFontMetrics metrics = font.stringMetrics(v) * style.fontSize; | ||
274 | + final double p = toChart(y); | ||
275 | + | ||
276 | + context.canvas | ||
277 | + ..setColor(style.color) | ||
278 | + ..drawString( | ||
279 | + style.font.getFont(context), | ||
280 | + style.fontSize, | ||
281 | + v, | ||
282 | + axisPosition - _textMargin - metrics.maxWidth, | ||
283 | + p - (metrics.ascent + metrics.descent) / 2, | ||
284 | + ); | ||
285 | + } | ||
286 | + } | ||
287 | + | ||
288 | + void _drawXValues(Context context) { | ||
289 | + context.canvas | ||
290 | + ..moveTo(box.left + _crossAxisPosition, axisPosition) | ||
291 | + ..lineTo(box.right, axisPosition); | ||
292 | + | ||
293 | + if (_axisTick && _textMargin > 0) { | ||
294 | + context.canvas | ||
295 | + ..moveTo(box.left + _crossAxisPosition, axisPosition) | ||
296 | + ..lineTo(box.left + _crossAxisPosition, axisPosition - _textMargin); | ||
297 | + } | ||
298 | + | ||
299 | + if (ticks && _textMargin > 0) { | ||
300 | + for (final num x in values) { | ||
301 | + final double p = toChart(x); | ||
302 | + context.canvas | ||
303 | + ..moveTo(p, axisPosition) | ||
304 | + ..lineTo(p, axisPosition - _textMargin); | ||
305 | + } | ||
306 | + } | ||
307 | + | ||
308 | + context.canvas | ||
309 | + ..setStrokeColor(color) | ||
310 | + ..setLineWidth(width) | ||
311 | + ..setLineCap(PdfLineCap.joinBevel) | ||
312 | + ..strokePath(); | ||
313 | + | ||
314 | + for (final num x in values) { | ||
315 | + final String v = format(x); | ||
316 | + final TextStyle style = | ||
317 | + Theme.of(context).defaultTextStyle.merge(textStyle); | ||
318 | + final PdfFont font = style.font.getFont(context); | ||
319 | + final PdfFontMetrics metrics = font.stringMetrics(v) * style.fontSize; | ||
320 | + final double p = toChart(x); | ||
321 | + | ||
322 | + context.canvas | ||
323 | + ..setColor(style.color) | ||
324 | + ..drawString( | ||
325 | + style.font.getFont(context), | ||
326 | + style.fontSize, | ||
327 | + v, | ||
328 | + p - metrics.maxWidth / 2, | ||
329 | + axisPosition - metrics.ascent - _textMargin, | ||
330 | + ); | ||
331 | + } | ||
332 | + } | ||
333 | + | ||
334 | + @override | ||
335 | + void paintBackground(Context context) { | ||
336 | + if (!divisions) { | ||
337 | + return; | ||
338 | + } | ||
339 | + | ||
340 | + final CartesianGrid grid = Chart.of(context).grid; | ||
341 | + | ||
342 | + switch (direction) { | ||
343 | + case Axis.horizontal: | ||
344 | + for (final num x in values.sublist(_marginStart > 0 ? 0 : 1)) { | ||
345 | + final double p = toChart(x); | ||
346 | + context.canvas.drawLine(p, grid.gridBox.top, p, grid.gridBox.bottom); | ||
347 | + } | ||
348 | + break; | ||
349 | + | ||
350 | + case Axis.vertical: | ||
351 | + for (final num y in values.sublist(_marginStart > 0 ? 0 : 1)) { | ||
352 | + final double p = toChart(y); | ||
353 | + context.canvas.drawLine(grid.gridBox.left, p, grid.gridBox.right, p); | ||
354 | + } | ||
355 | + | ||
356 | + break; | ||
357 | + } | ||
358 | + | ||
359 | + if (divisionsDashed) { | ||
360 | + context.canvas.setLineDashPattern(<int>[4, 2]); | ||
361 | + } | ||
362 | + | ||
363 | + context.canvas | ||
364 | + ..setStrokeColor(divisionsColor) | ||
365 | + ..setLineWidth(divisionsWidth) | ||
366 | + ..setLineCap(PdfLineCap.joinMiter) | ||
367 | + ..strokePath(); | ||
368 | + | ||
369 | + if (divisionsDashed) { | ||
370 | + context.canvas.setLineDashPattern(); | ||
371 | + } | ||
372 | + } | ||
373 | + | ||
374 | + @override | ||
375 | + void debugPaint(Context context) { | ||
376 | + switch (direction) { | ||
377 | + case Axis.horizontal: | ||
378 | + context.canvas | ||
379 | + ..setFillColor(PdfColors.grey300) | ||
380 | + ..drawRect(box.x, box.y, box.width, box.height) | ||
381 | + ..fillPath(); | ||
382 | + break; | ||
383 | + case Axis.vertical: | ||
384 | + context.canvas | ||
385 | + ..setFillColor(PdfColors.grey300) | ||
386 | + ..drawRect(box.x, box.y + _crossAxisPosition, box.width, | ||
387 | + box.height - _crossAxisPosition) | ||
388 | + ..fillPath(); | ||
389 | + break; | ||
390 | + } | ||
391 | + } | ||
392 | + | ||
393 | + @override | ||
394 | + void paint(Context context) { | ||
395 | + super.paint(context); | ||
396 | + | ||
397 | + switch (direction) { | ||
398 | + case Axis.horizontal: | ||
399 | + _drawXValues(context); | ||
400 | + break; | ||
401 | + case Axis.vertical: | ||
402 | + _drawYValues(context); | ||
403 | + break; | ||
404 | + } | ||
405 | + } | ||
406 | +} |
pdf/lib/widgets/chart/grid_cartesian.dart
0 → 100644
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 CartesianGrid extends ChartGrid { | ||
22 | + CartesianGrid({ | ||
23 | + @required GridAxis xAxis, | ||
24 | + @required GridAxis yAxis, | ||
25 | + }) : _xAxis = xAxis..direction = Axis.horizontal, | ||
26 | + _yAxis = yAxis..direction = Axis.vertical; | ||
27 | + | ||
28 | + final GridAxis _xAxis; | ||
29 | + final GridAxis _yAxis; | ||
30 | + | ||
31 | + PdfRect gridBox; | ||
32 | + | ||
33 | + @override | ||
34 | + void layout(Context context, BoxConstraints constraints, | ||
35 | + {bool parentUsesSize = false}) { | ||
36 | + assert(Chart.of(context) != null, | ||
37 | + '$runtimeType cannot be used without a Chart widget'); | ||
38 | + super.layout(context, constraints, parentUsesSize: parentUsesSize); | ||
39 | + | ||
40 | + final List<Dataset> datasets = Chart.of(context).datasets; | ||
41 | + final PdfPoint size = constraints.biggest; | ||
42 | + | ||
43 | + // In simple conditions, this loop will run only 2 times. | ||
44 | + int count = 5; | ||
45 | + while (count-- > 0) { | ||
46 | + _xAxis._crossAxisPosition = _yAxis.axisPosition; | ||
47 | + _xAxis.axisPosition = | ||
48 | + math.max(_xAxis.axisPosition, _yAxis._crossAxisPosition); | ||
49 | + _xAxis.layout(context, constraints); | ||
50 | + assert(_xAxis.box != null); | ||
51 | + _yAxis._crossAxisPosition = _xAxis.axisPosition; | ||
52 | + _yAxis.axisPosition = | ||
53 | + math.max(_yAxis.axisPosition, _xAxis._crossAxisPosition); | ||
54 | + _yAxis.layout(context, constraints); | ||
55 | + assert(_yAxis.box != null); | ||
56 | + if (_yAxis._crossAxisPosition == _xAxis.axisPosition && | ||
57 | + _xAxis._crossAxisPosition == _yAxis.axisPosition) { | ||
58 | + break; | ||
59 | + } | ||
60 | + } | ||
61 | + | ||
62 | + final double width = _yAxis.axisPosition; | ||
63 | + final double height = _xAxis.axisPosition; | ||
64 | + gridBox = PdfRect(width, height, size.x - width, size.y - height); | ||
65 | + | ||
66 | + for (final Dataset dataset in datasets) { | ||
67 | + dataset.layout(context, BoxConstraints.tight(gridBox.size)); | ||
68 | + dataset.box = | ||
69 | + PdfRect.fromPoints(PdfPoint(width, height), dataset.box.size); | ||
70 | + } | ||
71 | + } | ||
72 | + | ||
73 | + @override | ||
74 | + PdfPoint toChart(PdfPoint p) { | ||
75 | + return PdfPoint( | ||
76 | + _xAxis.toChart(p.x), | ||
77 | + _yAxis.toChart(p.y), | ||
78 | + ); | ||
79 | + } | ||
80 | + | ||
81 | + double get xAxisOffset => _xAxis.axisPosition; | ||
82 | + | ||
83 | + double get yAxisOffset => _yAxis.axisPosition; | ||
84 | + | ||
85 | + void paintBackground(Context context) { | ||
86 | + _xAxis.paintBackground(context); | ||
87 | + _yAxis.paintBackground(context); | ||
88 | + } | ||
89 | + | ||
90 | + void clip(Context context, PdfPoint size) { | ||
91 | + context.canvas | ||
92 | + ..saveContext() | ||
93 | + ..drawRect( | ||
94 | + gridBox.left, | ||
95 | + gridBox.bottom, | ||
96 | + gridBox.width, | ||
97 | + gridBox.height, | ||
98 | + ) | ||
99 | + ..clipPath(); | ||
100 | + } | ||
101 | + | ||
102 | + @override | ||
103 | + void paint(Context context) { | ||
104 | + super.paint(context); | ||
105 | + | ||
106 | + final List<Dataset> datasets = Chart.of(context).datasets; | ||
107 | + | ||
108 | + clip(context, box.size); | ||
109 | + for (Dataset dataSet in datasets) { | ||
110 | + dataSet.paintBackground(context); | ||
111 | + } | ||
112 | + context.canvas.restoreContext(); | ||
113 | + paintBackground(context); | ||
114 | + clip(context, box.size); | ||
115 | + for (Dataset dataSet in datasets) { | ||
116 | + dataSet.paint(context); | ||
117 | + } | ||
118 | + context.canvas.restoreContext(); | ||
119 | + _xAxis.paint(context); | ||
120 | + _yAxis.paint(context); | ||
121 | + } | ||
122 | +} |
pdf/lib/widgets/chart/legend.dart
0 → 100644
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 ChartLegend extends StatelessWidget { | ||
22 | + ChartLegend({ | ||
23 | + this.textStyle, | ||
24 | + this.position = Alignment.topRight, | ||
25 | + this.direction = Axis.vertical, | ||
26 | + this.decoration, | ||
27 | + this.padding = const EdgeInsets.all(5), | ||
28 | + }) : assert(position != null); | ||
29 | + | ||
30 | + final TextStyle textStyle; | ||
31 | + | ||
32 | + final Alignment position; | ||
33 | + | ||
34 | + final Axis direction; | ||
35 | + | ||
36 | + final BoxDecoration decoration; | ||
37 | + | ||
38 | + final EdgeInsets padding; | ||
39 | + | ||
40 | + Widget _buildLegend(Context context, Dataset dataset) { | ||
41 | + final TextStyle style = Theme.of(context).defaultTextStyle.merge(textStyle); | ||
42 | + | ||
43 | + return Row( | ||
44 | + mainAxisSize: MainAxisSize.min, | ||
45 | + children: <Widget>[ | ||
46 | + Container( | ||
47 | + width: style.fontSize, | ||
48 | + height: style.fontSize, | ||
49 | + margin: const EdgeInsets.only(right: 5), | ||
50 | + child: dataset.legendeShape(), | ||
51 | + ), | ||
52 | + Text( | ||
53 | + dataset.legend, | ||
54 | + style: textStyle, | ||
55 | + ) | ||
56 | + ], | ||
57 | + ); | ||
58 | + } | ||
59 | + | ||
60 | + @override | ||
61 | + Widget build(Context context) { | ||
62 | + assert(Chart.of(context) != null, | ||
63 | + '$runtimeType cannot be used without a Chart widget'); | ||
64 | + | ||
65 | + final List<Dataset> datasets = Chart.of(context).datasets; | ||
66 | + | ||
67 | + final Widget wrap = Wrap( | ||
68 | + direction: direction, | ||
69 | + spacing: 10, | ||
70 | + runSpacing: 10, | ||
71 | + children: <Widget>[ | ||
72 | + for (final Dataset dataset in datasets) | ||
73 | + if (dataset.legend != null) _buildLegend(context, dataset) | ||
74 | + ], | ||
75 | + ); | ||
76 | + | ||
77 | + return Align( | ||
78 | + alignment: position, | ||
79 | + child: Container( | ||
80 | + decoration: decoration ?? const BoxDecoration(color: PdfColors.white), | ||
81 | + padding: padding, | ||
82 | + child: wrap, | ||
83 | + ), | ||
84 | + ); | ||
85 | + } | ||
86 | +} |
@@ -26,12 +26,13 @@ class LineChartValue extends ChartValue { | @@ -26,12 +26,13 @@ class LineChartValue extends ChartValue { | ||
26 | PdfPoint get point => PdfPoint(x, y); | 26 | PdfPoint get point => PdfPoint(x, y); |
27 | } | 27 | } |
28 | 28 | ||
29 | -class LineDataSet extends DataSet { | 29 | +class LineDataSet extends Dataset { |
30 | LineDataSet({ | 30 | LineDataSet({ |
31 | @required this.data, | 31 | @required this.data, |
32 | + String legend, | ||
32 | this.pointColor, | 33 | this.pointColor, |
33 | this.pointSize = 3, | 34 | this.pointSize = 3, |
34 | - this.color = PdfColors.blue, | 35 | + PdfColor color = PdfColors.blue, |
35 | this.lineWidth = 2, | 36 | this.lineWidth = 2, |
36 | this.drawLine = true, | 37 | this.drawLine = true, |
37 | this.drawPoints = true, | 38 | this.drawPoints = true, |
@@ -40,12 +41,16 @@ class LineDataSet extends DataSet { | @@ -40,12 +41,16 @@ class LineDataSet extends DataSet { | ||
40 | this.surfaceColor, | 41 | this.surfaceColor, |
41 | this.isCurved = false, | 42 | this.isCurved = false, |
42 | this.smoothness = 0.35, | 43 | this.smoothness = 0.35, |
43 | - }) : assert(drawLine || drawPoints || drawSurface); | 44 | + }) : assert(drawLine || drawPoints || drawSurface), |
45 | + super( | ||
46 | + legend: legend, | ||
47 | + color: color, | ||
48 | + ); | ||
44 | 49 | ||
45 | final List<LineChartValue> data; | 50 | final List<LineChartValue> data; |
46 | 51 | ||
47 | final bool drawLine; | 52 | final bool drawLine; |
48 | - final PdfColor color; | 53 | + |
49 | final double lineWidth; | 54 | final double lineWidth; |
50 | 55 | ||
51 | final bool drawPoints; | 56 | final bool drawPoints; |
@@ -59,6 +64,12 @@ class LineDataSet extends DataSet { | @@ -59,6 +64,12 @@ class LineDataSet extends DataSet { | ||
59 | final bool isCurved; | 64 | final bool isCurved; |
60 | final double smoothness; | 65 | final double smoothness; |
61 | 66 | ||
67 | + @override | ||
68 | + void layout(Context context, BoxConstraints constraints, | ||
69 | + {bool parentUsesSize = false}) { | ||
70 | + box = PdfRect.fromPoints(PdfPoint.zero, constraints.biggest); | ||
71 | + } | ||
72 | + | ||
62 | void _drawLine(Context context, ChartGrid grid, bool moveTo) { | 73 | void _drawLine(Context context, ChartGrid grid, bool moveTo) { |
63 | if (data.length < 2) { | 74 | if (data.length < 2) { |
64 | return; | 75 | return; |
@@ -66,7 +77,7 @@ class LineDataSet extends DataSet { | @@ -66,7 +77,7 @@ class LineDataSet extends DataSet { | ||
66 | 77 | ||
67 | PdfPoint t = const PdfPoint(0, 0); | 78 | PdfPoint t = const PdfPoint(0, 0); |
68 | 79 | ||
69 | - final PdfPoint p = grid.tochart(data.first.point); | 80 | + final PdfPoint p = grid.toChart(data.first.point); |
70 | if (moveTo) { | 81 | if (moveTo) { |
71 | context.canvas.moveTo(p.x, p.y); | 82 | context.canvas.moveTo(p.x, p.y); |
72 | } else { | 83 | } else { |
@@ -74,16 +85,16 @@ class LineDataSet extends DataSet { | @@ -74,16 +85,16 @@ class LineDataSet extends DataSet { | ||
74 | } | 85 | } |
75 | 86 | ||
76 | for (int i = 1; i < data.length; i++) { | 87 | for (int i = 1; i < data.length; i++) { |
77 | - final PdfPoint p = grid.tochart(data[i].point); | 88 | + final PdfPoint p = grid.toChart(data[i].point); |
78 | 89 | ||
79 | if (!isCurved) { | 90 | if (!isCurved) { |
80 | context.canvas.lineTo(p.x, p.y); | 91 | context.canvas.lineTo(p.x, p.y); |
81 | continue; | 92 | continue; |
82 | } | 93 | } |
83 | 94 | ||
84 | - final PdfPoint pp = grid.tochart(data[i - 1].point); | 95 | + final PdfPoint pp = grid.toChart(data[i - 1].point); |
85 | final PdfPoint pn = | 96 | final PdfPoint pn = |
86 | - grid.tochart(data[i + 1 < data.length ? i + 1 : i].point); | 97 | + grid.toChart(data[i + 1 < data.length ? i + 1 : i].point); |
87 | 98 | ||
88 | final PdfPoint c1 = PdfPoint(pp.x + t.x, pp.y + t.y); | 99 | final PdfPoint c1 = PdfPoint(pp.x + t.x, pp.y + t.y); |
89 | 100 | ||
@@ -101,28 +112,30 @@ class LineDataSet extends DataSet { | @@ -101,28 +112,30 @@ class LineDataSet extends DataSet { | ||
101 | return; | 112 | return; |
102 | } | 113 | } |
103 | 114 | ||
104 | - final double y = (grid is LinearGrid) ? grid.xAxisOffset : 0; | 115 | + final double y = (grid is CartesianGrid) ? grid.xAxisOffset : 0; |
105 | _drawLine(context, grid, true); | 116 | _drawLine(context, grid, true); |
106 | 117 | ||
107 | - final PdfPoint pe = grid.tochart(data.last.point); | 118 | + final PdfPoint pe = grid.toChart(data.last.point); |
108 | context.canvas.lineTo(pe.x, y); | 119 | context.canvas.lineTo(pe.x, y); |
109 | - final PdfPoint pf = grid.tochart(data.first.point); | 120 | + final PdfPoint pf = grid.toChart(data.first.point); |
110 | context.canvas.lineTo(pf.x, y); | 121 | context.canvas.lineTo(pf.x, y); |
111 | } | 122 | } |
112 | 123 | ||
113 | void _drawPoints(Context context, ChartGrid grid) { | 124 | void _drawPoints(Context context, ChartGrid grid) { |
114 | for (final LineChartValue value in data) { | 125 | for (final LineChartValue value in data) { |
115 | - final PdfPoint p = grid.tochart(value.point); | 126 | + final PdfPoint p = grid.toChart(value.point); |
116 | context.canvas.drawEllipse(p.x, p.y, pointSize, pointSize); | 127 | context.canvas.drawEllipse(p.x, p.y, pointSize, pointSize); |
117 | } | 128 | } |
118 | } | 129 | } |
119 | 130 | ||
120 | @override | 131 | @override |
121 | - void paintBackground(Context context, ChartGrid grid) { | 132 | + void paintBackground(Context context) { |
122 | if (data.isEmpty) { | 133 | if (data.isEmpty) { |
123 | return; | 134 | return; |
124 | } | 135 | } |
125 | 136 | ||
137 | + final ChartGrid grid = Chart.of(context).grid; | ||
138 | + | ||
126 | if (drawSurface) { | 139 | if (drawSurface) { |
127 | _drawSurface(context, grid); | 140 | _drawSurface(context, grid); |
128 | 141 | ||
@@ -145,11 +158,15 @@ class LineDataSet extends DataSet { | @@ -145,11 +158,15 @@ class LineDataSet extends DataSet { | ||
145 | } | 158 | } |
146 | 159 | ||
147 | @override | 160 | @override |
148 | - void paintForeground(Context context, ChartGrid grid) { | 161 | + void paint(Context context) { |
162 | + super.paint(context); | ||
163 | + | ||
149 | if (data.isEmpty) { | 164 | if (data.isEmpty) { |
150 | return; | 165 | return; |
151 | } | 166 | } |
152 | 167 | ||
168 | + final ChartGrid grid = Chart.of(context).grid; | ||
169 | + | ||
153 | if (drawLine) { | 170 | if (drawLine) { |
154 | _drawLine(context, grid, true); | 171 | _drawLine(context, grid, true); |
155 | 172 |
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 | -typedef GridAxisFormat = String Function(double value); | ||
22 | - | ||
23 | -class LinearGrid extends ChartGrid { | ||
24 | - LinearGrid({ | ||
25 | - @required this.xAxis, | ||
26 | - @required this.yAxis, | ||
27 | - this.xMargin = 10, | ||
28 | - this.yMargin = 2, | ||
29 | - this.textStyle, | ||
30 | - this.lineWidth = 1, | ||
31 | - this.color = PdfColors.black, | ||
32 | - this.separatorLineWidth = .5, | ||
33 | - this.drawXDivisions = false, | ||
34 | - this.drawYDivisions = true, | ||
35 | - this.separatorColor = PdfColors.grey, | ||
36 | - this.xAxisFormat = _defaultFormat, | ||
37 | - this.yAxisFormat = _defaultFormat, | ||
38 | - }) : assert(_isSortedAscending(xAxis)), | ||
39 | - assert(_isSortedAscending(yAxis)); | ||
40 | - | ||
41 | - final List<double> xAxis; | ||
42 | - final List<double> yAxis; | ||
43 | - final double xMargin; | ||
44 | - final double yMargin; | ||
45 | - final TextStyle textStyle; | ||
46 | - final double lineWidth; | ||
47 | - final PdfColor color; | ||
48 | - final double separatorLineWidth; | ||
49 | - final PdfColor separatorColor; | ||
50 | - final bool drawXDivisions; | ||
51 | - final bool drawYDivisions; | ||
52 | - final GridAxisFormat xAxisFormat; | ||
53 | - final GridAxisFormat yAxisFormat; | ||
54 | - | ||
55 | - TextStyle style; | ||
56 | - PdfFont font; | ||
57 | - PdfRect gridBox; | ||
58 | - double xOffset; | ||
59 | - double xTotal; | ||
60 | - double yOffset; | ||
61 | - double yTotal; | ||
62 | - | ||
63 | - static String _defaultFormat(double v) => v.toString(); | ||
64 | - | ||
65 | - static bool _isSortedAscending(List<double> list) { | ||
66 | - double prev = list.first; | ||
67 | - for (final double elem in list) { | ||
68 | - if (prev > elem) { | ||
69 | - return false; | ||
70 | - } | ||
71 | - prev = elem; | ||
72 | - } | ||
73 | - return true; | ||
74 | - } | ||
75 | - | ||
76 | - @override | ||
77 | - void layout(Context context, PdfPoint size) { | ||
78 | - style = Theme.of(context).defaultTextStyle.merge(textStyle); | ||
79 | - font = style.font.getFont(context); | ||
80 | - | ||
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 | - } | ||
98 | - | ||
99 | - gridBox = PdfRect.fromLTRB( | ||
100 | - yMaxWidth + xMargin, | ||
101 | - xMaxHeight + yMargin, | ||
102 | - size.x - xMaxWidth / 2, | ||
103 | - size.y - yMaxHeight / 2, | ||
104 | - ); | ||
105 | - | ||
106 | - xOffset = xAxis.reduce(math.min); | ||
107 | - yOffset = yAxis.reduce(math.min); | ||
108 | - xTotal = xAxis.reduce(math.max) - xOffset; | ||
109 | - yTotal = yAxis.reduce(math.max) - yOffset; | ||
110 | - } | ||
111 | - | ||
112 | - @override | ||
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 | - | ||
178 | - context.canvas | ||
179 | - ..setColor(style.color) | ||
180 | - ..drawString( | ||
181 | - style.font.getFont(context), | ||
182 | - style.fontSize, | ||
183 | - v, | ||
184 | - gridBox.left - xMargin - metrics.width, | ||
185 | - p.y - (metrics.ascent + metrics.descent) / 2, | ||
186 | - ); | ||
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)); | ||
195 | - | ||
196 | - context.canvas | ||
197 | - ..setColor(style.color) | ||
198 | - ..drawString( | ||
199 | - style.font.getFont(context), | ||
200 | - style.fontSize, | ||
201 | - v, | ||
202 | - p.x - metrics.width / 2, | ||
203 | - -metrics.descent, | ||
204 | - ); | ||
205 | - } | ||
206 | - } | ||
207 | - | ||
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); | ||
215 | - } | ||
216 | - if (drawYDivisions) { | ||
217 | - _drawYDivisions(context, size); | ||
218 | - } | ||
219 | - } | ||
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) { | ||
230 | - context.canvas | ||
231 | - ..saveContext() | ||
232 | - ..drawRect( | ||
233 | - gridBox.left, | ||
234 | - gridBox.bottom, | ||
235 | - size.x - gridBox.left, | ||
236 | - size.y - gridBox.bottom, | ||
237 | - ) | ||
238 | - ..clipPath(); | ||
239 | - } | ||
240 | - | ||
241 | - @override | ||
242 | - void unClip(Context context, PdfPoint size) { | ||
243 | - context.canvas.restoreContext(); | ||
244 | - } | ||
245 | -} |
@@ -35,11 +35,11 @@ void main() { | @@ -35,11 +35,11 @@ void main() { | ||
35 | pdf.addPage(Page( | 35 | pdf.addPage(Page( |
36 | pageFormat: PdfPageFormat.standard.landscape, | 36 | pageFormat: PdfPageFormat.standard.landscape, |
37 | build: (Context context) => Chart( | 37 | build: (Context context) => Chart( |
38 | - grid: LinearGrid( | ||
39 | - xAxis: <double>[0, 1, 2, 3, 4, 5, 6], | ||
40 | - yAxis: <double>[0, 3, 6, 9], | 38 | + grid: CartesianGrid( |
39 | + xAxis: FixedAxis<int>(<int>[0, 1, 2, 3, 4, 5, 6]), | ||
40 | + yAxis: FixedAxis<int>(<int>[0, 3, 6, 9], divisions: true), | ||
41 | ), | 41 | ), |
42 | - data: <DataSet>[ | 42 | + datasets: <Dataset>[ |
43 | LineDataSet( | 43 | LineDataSet( |
44 | data: const <LineChartValue>[ | 44 | data: const <LineChartValue>[ |
45 | LineChartValue(1, 1), | 45 | LineChartValue(1, 1), |
@@ -56,11 +56,11 @@ void main() { | @@ -56,11 +56,11 @@ void main() { | ||
56 | pdf.addPage(Page( | 56 | pdf.addPage(Page( |
57 | pageFormat: PdfPageFormat.standard.landscape, | 57 | pageFormat: PdfPageFormat.standard.landscape, |
58 | build: (Context context) => Chart( | 58 | build: (Context context) => Chart( |
59 | - grid: LinearGrid( | ||
60 | - xAxis: <double>[0, 1, 2, 3, 4, 5, 6], | ||
61 | - yAxis: <double>[0, 3, 6, 9], | 59 | + grid: CartesianGrid( |
60 | + xAxis: FixedAxis<int>(<int>[0, 1, 2, 3, 4, 5, 6]), | ||
61 | + yAxis: FixedAxis<int>(<int>[0, 3, 6, 9], divisions: true), | ||
62 | ), | 62 | ), |
63 | - data: <DataSet>[ | 63 | + datasets: <Dataset>[ |
64 | LineDataSet( | 64 | LineDataSet( |
65 | data: const <LineChartValue>[ | 65 | data: const <LineChartValue>[ |
66 | LineChartValue(1, 1), | 66 | LineChartValue(1, 1), |
@@ -77,11 +77,11 @@ void main() { | @@ -77,11 +77,11 @@ void main() { | ||
77 | test('Default ScatterChart without dots', () { | 77 | test('Default ScatterChart without dots', () { |
78 | pdf.addPage(Page( | 78 | pdf.addPage(Page( |
79 | build: (Context context) => Chart( | 79 | build: (Context context) => Chart( |
80 | - grid: LinearGrid( | ||
81 | - xAxis: <double>[0, 1, 2, 3, 4, 5, 6], | ||
82 | - yAxis: <double>[0, 3, 6, 9], | 80 | + grid: CartesianGrid( |
81 | + xAxis: FixedAxis<int>(<int>[0, 1, 2, 3, 4, 5, 6]), | ||
82 | + yAxis: FixedAxis<int>(<int>[0, 3, 6, 9], divisions: true), | ||
83 | ), | 83 | ), |
84 | - data: <DataSet>[ | 84 | + datasets: <Dataset>[ |
85 | LineDataSet( | 85 | LineDataSet( |
86 | data: const <LineChartValue>[ | 86 | data: const <LineChartValue>[ |
87 | LineChartValue(1, 1), | 87 | LineChartValue(1, 1), |
@@ -99,11 +99,11 @@ void main() { | @@ -99,11 +99,11 @@ void main() { | ||
99 | pdf.addPage(Page( | 99 | pdf.addPage(Page( |
100 | pageFormat: PdfPageFormat.standard.landscape, | 100 | pageFormat: PdfPageFormat.standard.landscape, |
101 | build: (Context context) => Chart( | 101 | build: (Context context) => Chart( |
102 | - grid: LinearGrid( | ||
103 | - xAxis: <double>[0, 1, 2, 3, 4, 5, 6], | ||
104 | - yAxis: <double>[0, 3, 6, 9], | 102 | + grid: CartesianGrid( |
103 | + xAxis: FixedAxis<int>(<int>[0, 1, 2, 3, 4, 5, 6]), | ||
104 | + yAxis: FixedAxis<int>(<int>[0, 3, 6, 9], divisions: true), | ||
105 | ), | 105 | ), |
106 | - data: <DataSet>[ | 106 | + datasets: <Dataset>[ |
107 | LineDataSet( | 107 | LineDataSet( |
108 | data: const <LineChartValue>[ | 108 | data: const <LineChartValue>[ |
109 | LineChartValue(1, 1), | 109 | LineChartValue(1, 1), |
@@ -128,11 +128,11 @@ void main() { | @@ -128,11 +128,11 @@ void main() { | ||
128 | width: 200, | 128 | width: 200, |
129 | height: 100, | 129 | height: 100, |
130 | child: Chart( | 130 | child: Chart( |
131 | - grid: LinearGrid( | ||
132 | - xAxis: <double>[0, 1, 2, 3, 4, 5, 6], | ||
133 | - yAxis: <double>[0, 3, 6, 9], | 131 | + grid: CartesianGrid( |
132 | + xAxis: FixedAxis<int>(<int>[0, 1, 2, 3, 4, 5, 6]), | ||
133 | + yAxis: FixedAxis<int>(<int>[0, 3, 6, 9], divisions: true), | ||
134 | ), | 134 | ), |
135 | - data: <DataSet>[ | 135 | + datasets: <Dataset>[ |
136 | LineDataSet( | 136 | LineDataSet( |
137 | data: const <LineChartValue>[ | 137 | data: const <LineChartValue>[ |
138 | LineChartValue(1, 1), | 138 | LineChartValue(1, 1), |
@@ -150,11 +150,11 @@ void main() { | @@ -150,11 +150,11 @@ void main() { | ||
150 | pdf.addPage(Page( | 150 | pdf.addPage(Page( |
151 | pageFormat: PdfPageFormat.standard.landscape, | 151 | pageFormat: PdfPageFormat.standard.landscape, |
152 | build: (Context context) => Chart( | 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], | 153 | + grid: CartesianGrid( |
154 | + xAxis: FixedAxis<int>(<int>[0, 1, 2, 3, 4, 5, 6]), | ||
155 | + yAxis: FixedAxis<int>(<int>[0, 3, 6, 9], divisions: true), | ||
156 | ), | 156 | ), |
157 | - data: <DataSet>[ | 157 | + datasets: <Dataset>[ |
158 | LineDataSet( | 158 | LineDataSet( |
159 | drawPoints: false, | 159 | drawPoints: false, |
160 | isCurved: true, | 160 | isCurved: true, |
No preview for this file type
-
Please register or login to post a comment