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