Showing
7 changed files
with
345 additions
and
80 deletions
| 1 | include: package:pedantic/analysis_options.yaml | 1 | include: package:pedantic/analysis_options.yaml |
| 2 | - | ||
| 3 | -analyzer: | ||
| 4 | - strong-mode: | ||
| 5 | - implicit-dynamic: false | ||
| 6 | - errors: | ||
| 7 | - missing_required_param: warning | ||
| 8 | - missing_return: warning | ||
| 9 | - | ||
| 10 | -linter: | ||
| 11 | - rules: | ||
| 12 | - - always_put_control_body_on_new_line | ||
| 13 | - - avoid_as | ||
| 14 | - - avoid_bool_literals_in_conditional_expressions | ||
| 15 | - - avoid_classes_with_only_static_members | ||
| 16 | - - avoid_field_initializers_in_const_classes | ||
| 17 | - - avoid_function_literals_in_foreach_calls | ||
| 18 | - - avoid_renaming_method_parameters | ||
| 19 | - - avoid_returning_null_for_void | ||
| 20 | - - avoid_slow_async_io | ||
| 21 | - - avoid_unused_constructor_parameters | ||
| 22 | - - avoid_void_async | ||
| 23 | - - await_only_futures | ||
| 24 | - - camel_case_types | ||
| 25 | - - cancel_subscriptions | ||
| 26 | - - control_flow_in_finally | ||
| 27 | - - directives_ordering | ||
| 28 | - - empty_statements | ||
| 29 | - - flutter_style_todos | ||
| 30 | - - hash_and_equals | ||
| 31 | - - implementation_imports | ||
| 32 | - - iterable_contains_unrelated_type | ||
| 33 | - - list_remove_unrelated_type | ||
| 34 | - - no_adjacent_strings_in_list | ||
| 35 | - - non_constant_identifier_names | ||
| 36 | - - omit_local_variable_types | ||
| 37 | - - overridden_fields | ||
| 38 | - - package_api_docs | ||
| 39 | - - package_names | ||
| 40 | - - package_prefixed_library_names | ||
| 41 | - - prefer_asserts_in_initializer_lists | ||
| 42 | - - prefer_const_constructors | ||
| 43 | - - prefer_const_constructors_in_immutables | ||
| 44 | - - prefer_const_declarations | ||
| 45 | - - prefer_const_literals_to_create_immutables | ||
| 46 | - - prefer_final_locals | ||
| 47 | - - prefer_foreach | ||
| 48 | - - prefer_if_elements_to_conditional_expressions | ||
| 49 | - - prefer_initializing_formals | ||
| 50 | - - prefer_inlined_adds | ||
| 51 | - - prefer_typing_uninitialized_variables | ||
| 52 | - - prefer_void_to_null | ||
| 53 | - - sort_constructors_first | ||
| 54 | - - sort_pub_dependencies | ||
| 55 | - - sort_unnamed_constructors_first | ||
| 56 | - - test_types_in_equals | ||
| 57 | - - throw_in_finally | ||
| 58 | - - unnecessary_brace_in_string_interps | ||
| 59 | - - unnecessary_getters_setters | ||
| 60 | - - unnecessary_null_aware_assignments | ||
| 61 | - - unnecessary_overrides | ||
| 62 | - - unnecessary_parenthesis | ||
| 63 | - - unnecessary_statements | ||
| 64 | - - use_full_hex_values_for_flutter_colors |
| @@ -25,29 +25,40 @@ Future<Uint8List> generateReport(PdfPageFormat pageFormat) async { | @@ -25,29 +25,40 @@ Future<Uint8List> generateReport(PdfPageFormat pageFormat) async { | ||
| 25 | const tableHeaders = ['Category', 'Budget', 'Expense', 'Result']; | 25 | const tableHeaders = ['Category', 'Budget', 'Expense', 'Result']; |
| 26 | 26 | ||
| 27 | const dataTable = [ | 27 | const dataTable = [ |
| 28 | - ['Phone', 80, 95, -15], | ||
| 29 | - ['Internet', 250, 230, 20], | ||
| 30 | - ['Electricity', 300, 375, -75], | ||
| 31 | - ['Movies', 85, 80, 5], | ||
| 32 | - ['Food', 300, 350, -50], | ||
| 33 | - ['Fuel', 650, 550, 100], | ||
| 34 | - ['Insurance', 250, 310, -60], | 28 | + ['Phone', 80, 95], |
| 29 | + ['Internet', 250, 230], | ||
| 30 | + ['Electricity', 300, 375], | ||
| 31 | + ['Movies', 85, 80], | ||
| 32 | + ['Food', 300, 350], | ||
| 33 | + ['Fuel', 650, 550], | ||
| 34 | + ['Insurance', 250, 310], | ||
| 35 | ]; | 35 | ]; |
| 36 | 36 | ||
| 37 | + // Some summary maths | ||
| 38 | + final budget = dataTable | ||
| 39 | + .map((e) => e[1] as num) | ||
| 40 | + .reduce((value, element) => value + element); | ||
| 41 | + final expense = dataTable | ||
| 42 | + .map((e) => e[2] as num) | ||
| 43 | + .reduce((value, element) => value + element); | ||
| 44 | + | ||
| 37 | final baseColor = PdfColors.cyan; | 45 | final baseColor = PdfColors.cyan; |
| 38 | 46 | ||
| 39 | // Create a PDF document. | 47 | // Create a PDF document. |
| 40 | final document = pw.Document(); | 48 | final document = pw.Document(); |
| 41 | 49 | ||
| 50 | + final theme = pw.ThemeData.withFont( | ||
| 51 | + base: pw.Font.ttf(await rootBundle.load('assets/open-sans.ttf')), | ||
| 52 | + bold: pw.Font.ttf(await rootBundle.load('assets/open-sans-bold.ttf')), | ||
| 53 | + ); | ||
| 54 | + | ||
| 42 | // Add page to the PDF | 55 | // Add page to the PDF |
| 43 | document.addPage( | 56 | document.addPage( |
| 44 | pw.Page( | 57 | pw.Page( |
| 45 | pageFormat: pageFormat, | 58 | pageFormat: pageFormat, |
| 46 | - theme: pw.ThemeData.withFont( | ||
| 47 | - base: pw.Font.ttf(await rootBundle.load('assets/open-sans.ttf')), | ||
| 48 | - bold: pw.Font.ttf(await rootBundle.load('assets/open-sans-bold.ttf')), | ||
| 49 | - ), | 59 | + theme: theme, |
| 50 | build: (context) { | 60 | build: (context) { |
| 61 | + // Top bar chart | ||
| 51 | final chart1 = pw.Chart( | 62 | final chart1 = pw.Chart( |
| 52 | left: pw.Container( | 63 | left: pw.Container( |
| 53 | alignment: pw.Alignment.topCenter, | 64 | alignment: pw.Alignment.topCenter, |
| @@ -117,6 +128,7 @@ Future<Uint8List> generateReport(PdfPageFormat pageFormat) async { | @@ -117,6 +128,7 @@ Future<Uint8List> generateReport(PdfPageFormat pageFormat) async { | ||
| 117 | ], | 128 | ], |
| 118 | ); | 129 | ); |
| 119 | 130 | ||
| 131 | + // Left curved line chart | ||
| 120 | final chart2 = pw.Chart( | 132 | final chart2 = pw.Chart( |
| 121 | grid: pw.CartesianGrid( | 133 | grid: pw.CartesianGrid( |
| 122 | xAxis: pw.FixedAxis([0, 1, 2, 3, 4, 5, 6]), | 134 | xAxis: pw.FixedAxis([0, 1, 2, 3, 4, 5, 6]), |
| @@ -143,10 +155,19 @@ Future<Uint8List> generateReport(PdfPageFormat pageFormat) async { | @@ -143,10 +155,19 @@ Future<Uint8List> generateReport(PdfPageFormat pageFormat) async { | ||
| 143 | ], | 155 | ], |
| 144 | ); | 156 | ); |
| 145 | 157 | ||
| 158 | + // Data table | ||
| 146 | final table = pw.Table.fromTextArray( | 159 | final table = pw.Table.fromTextArray( |
| 147 | border: null, | 160 | border: null, |
| 148 | headers: tableHeaders, | 161 | headers: tableHeaders, |
| 149 | - data: dataTable, | 162 | + data: List<List<dynamic>>.generate( |
| 163 | + dataTable.length, | ||
| 164 | + (index) => <dynamic>[ | ||
| 165 | + dataTable[index][0], | ||
| 166 | + dataTable[index][1], | ||
| 167 | + dataTable[index][2], | ||
| 168 | + (dataTable[index][1] as num) - (dataTable[index][2] as num), | ||
| 169 | + ], | ||
| 170 | + ), | ||
| 150 | headerStyle: pw.TextStyle( | 171 | headerStyle: pw.TextStyle( |
| 151 | color: PdfColors.white, | 172 | color: PdfColors.white, |
| 152 | fontWeight: pw.FontWeight.bold, | 173 | fontWeight: pw.FontWeight.bold, |
| @@ -162,8 +183,11 @@ Future<Uint8List> generateReport(PdfPageFormat pageFormat) async { | @@ -162,8 +183,11 @@ Future<Uint8List> generateReport(PdfPageFormat pageFormat) async { | ||
| 162 | ), | 183 | ), |
| 163 | ), | 184 | ), |
| 164 | ), | 185 | ), |
| 186 | + cellAlignment: pw.Alignment.centerRight, | ||
| 187 | + cellAlignments: {0: pw.Alignment.centerLeft}, | ||
| 165 | ); | 188 | ); |
| 166 | 189 | ||
| 190 | + // Page layout | ||
| 167 | return pw.Column( | 191 | return pw.Column( |
| 168 | children: [ | 192 | children: [ |
| 169 | pw.Text('Budget Report', | 193 | pw.Text('Budget Report', |
| @@ -223,7 +247,7 @@ Future<Uint8List> generateReport(PdfPageFormat pageFormat) async { | @@ -223,7 +247,7 @@ Future<Uint8List> generateReport(PdfPageFormat pageFormat) async { | ||
| 223 | ), | 247 | ), |
| 224 | ), | 248 | ), |
| 225 | pw.Text( | 249 | pw.Text( |
| 226 | - 'Budget was originally \$1915. A total of \$1990 was spent on the month of January which exceeded the overall budget by \$75', | 250 | + 'Budget was originally \$$budget. A total of \$$expense was spent on the month of January which exceeded the overall budget by \$${expense - budget}', |
| 227 | textAlign: pw.TextAlign.justify, | 251 | textAlign: pw.TextAlign.justify, |
| 228 | ) | 252 | ) |
| 229 | ], | 253 | ], |
| @@ -237,6 +261,55 @@ Future<Uint8List> generateReport(PdfPageFormat pageFormat) async { | @@ -237,6 +261,55 @@ Future<Uint8List> generateReport(PdfPageFormat pageFormat) async { | ||
| 237 | ), | 261 | ), |
| 238 | ); | 262 | ); |
| 239 | 263 | ||
| 264 | + // Second page with a pie chart | ||
| 265 | + document.addPage( | ||
| 266 | + pw.Page( | ||
| 267 | + pageFormat: pageFormat, | ||
| 268 | + theme: theme, | ||
| 269 | + build: (context) { | ||
| 270 | + const chartColors = [ | ||
| 271 | + PdfColors.blue300, | ||
| 272 | + PdfColors.green300, | ||
| 273 | + PdfColors.amber300, | ||
| 274 | + PdfColors.pink300, | ||
| 275 | + PdfColors.cyan300, | ||
| 276 | + PdfColors.purple300, | ||
| 277 | + PdfColors.lime300, | ||
| 278 | + ]; | ||
| 279 | + | ||
| 280 | + return pw.SizedBox( | ||
| 281 | + height: 400, | ||
| 282 | + child: pw.Chart( | ||
| 283 | + title: pw.Text( | ||
| 284 | + 'Expense breakdown', | ||
| 285 | + style: pw.TextStyle( | ||
| 286 | + color: baseColor, | ||
| 287 | + fontSize: 20, | ||
| 288 | + ), | ||
| 289 | + ), | ||
| 290 | + grid: pw.PieGrid(), | ||
| 291 | + datasets: List<pw.Dataset>.generate(dataTable.length, (index) { | ||
| 292 | + final data = dataTable[index]; | ||
| 293 | + final color = chartColors[index % chartColors.length]; | ||
| 294 | + final textColor = | ||
| 295 | + color.luminance < 0.2 ? PdfColors.white : PdfColors.black; | ||
| 296 | + | ||
| 297 | + final value = (data[2] as num).toDouble(); | ||
| 298 | + final pct = (value / expense * 100).round(); | ||
| 299 | + | ||
| 300 | + return pw.PieDataSet( | ||
| 301 | + legend: '${data[0]}\n$pct%', | ||
| 302 | + value: value, | ||
| 303 | + color: color, | ||
| 304 | + legendStyle: pw.TextStyle(fontSize: 10, color: textColor), | ||
| 305 | + ); | ||
| 306 | + }), | ||
| 307 | + ), | ||
| 308 | + ); | ||
| 309 | + }, | ||
| 310 | + ), | ||
| 311 | + ); | ||
| 312 | + | ||
| 240 | // Return the PDF file content | 313 | // Return the PDF file content |
| 241 | return document.save(); | 314 | return document.save(); |
| 242 | } | 315 | } |
| 1 | # Changelog | 1 | # Changelog |
| 2 | 2 | ||
| 3 | -## 3.0.2 | 3 | +## 3.1.0 |
| 4 | 4 | ||
| 5 | - Fix some linting issues | 5 | - Fix some linting issues |
| 6 | - Add PdfPage.rotate attribute | 6 | - Add PdfPage.rotate attribute |
| 7 | - Add RadialGrid for charts with polar coordinates | 7 | - Add RadialGrid for charts with polar coordinates |
| 8 | +- Add PieChart | ||
| 8 | 9 | ||
| 9 | ## 3.0.1 | 10 | ## 3.0.1 |
| 10 | 11 |
| @@ -35,6 +35,7 @@ class ChartLegend extends StatelessWidget { | @@ -35,6 +35,7 @@ class ChartLegend extends StatelessWidget { | ||
| 35 | this.direction = Axis.vertical, | 35 | this.direction = Axis.vertical, |
| 36 | this.decoration, | 36 | this.decoration, |
| 37 | this.padding = const EdgeInsets.all(5), | 37 | this.padding = const EdgeInsets.all(5), |
| 38 | + this.maxWidth = 200, | ||
| 38 | }); | 39 | }); |
| 39 | 40 | ||
| 40 | final TextStyle? textStyle; | 41 | final TextStyle? textStyle; |
| @@ -47,6 +48,8 @@ class ChartLegend extends StatelessWidget { | @@ -47,6 +48,8 @@ class ChartLegend extends StatelessWidget { | ||
| 47 | 48 | ||
| 48 | final EdgeInsets padding; | 49 | final EdgeInsets padding; |
| 49 | 50 | ||
| 51 | + final double maxWidth; | ||
| 52 | + | ||
| 50 | Widget _buildLegend(Context context, Dataset dataset) { | 53 | Widget _buildLegend(Context context, Dataset dataset) { |
| 51 | final style = Theme.of(context).defaultTextStyle.merge(textStyle); | 54 | final style = Theme.of(context).defaultTextStyle.merge(textStyle); |
| 52 | 55 | ||
| @@ -59,10 +62,14 @@ class ChartLegend extends StatelessWidget { | @@ -59,10 +62,14 @@ class ChartLegend extends StatelessWidget { | ||
| 59 | margin: const EdgeInsets.only(right: 5), | 62 | margin: const EdgeInsets.only(right: 5), |
| 60 | child: dataset.legendShape(), | 63 | child: dataset.legendShape(), |
| 61 | ), | 64 | ), |
| 62 | - Text( | 65 | + ConstrainedBox( |
| 66 | + constraints: BoxConstraints(maxWidth: maxWidth), | ||
| 67 | + child: Text( | ||
| 63 | dataset.legend!, | 68 | dataset.legend!, |
| 64 | style: textStyle, | 69 | style: textStyle, |
| 65 | - ) | 70 | + softWrap: false, |
| 71 | + ), | ||
| 72 | + ), | ||
| 66 | ], | 73 | ], |
| 67 | ); | 74 | ); |
| 68 | } | 75 | } |
pdf/lib/src/widgets/chart/pie_chart.dart
0 → 100644
| 1 | +// ignore_for_file: public_member_api_docs | ||
| 2 | + | ||
| 3 | +import 'dart:math'; | ||
| 4 | + | ||
| 5 | +import 'package:pdf/pdf.dart'; | ||
| 6 | +import 'package:pdf/widgets.dart'; | ||
| 7 | +import 'package:vector_math/vector_math_64.dart'; | ||
| 8 | + | ||
| 9 | +class PieGrid extends ChartGrid { | ||
| 10 | + PieGrid(); | ||
| 11 | + | ||
| 12 | + late PdfRect gridBox; | ||
| 13 | + | ||
| 14 | + late double total; | ||
| 15 | + | ||
| 16 | + late double unit; | ||
| 17 | + | ||
| 18 | + late double pieSize; | ||
| 19 | + | ||
| 20 | + @override | ||
| 21 | + void layout(Context context, BoxConstraints constraints, | ||
| 22 | + {bool parentUsesSize = false}) { | ||
| 23 | + super.layout(context, constraints, parentUsesSize: parentUsesSize); | ||
| 24 | + | ||
| 25 | + final datasets = Chart.of(context).datasets; | ||
| 26 | + final size = constraints.biggest; | ||
| 27 | + | ||
| 28 | + gridBox = PdfRect(0, 0, size.x, size.y); | ||
| 29 | + | ||
| 30 | + total = 0.0; | ||
| 31 | + | ||
| 32 | + for (final dataset in datasets) { | ||
| 33 | + assert(dataset is PieDataSet, 'Use only PieDataSet with a PieGrid'); | ||
| 34 | + if (dataset is PieDataSet) { | ||
| 35 | + total += dataset.value; | ||
| 36 | + } | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | + unit = pi / total * 2; | ||
| 40 | + var angle = 0.0; | ||
| 41 | + | ||
| 42 | + for (final dataset in datasets) { | ||
| 43 | + if (dataset is PieDataSet) { | ||
| 44 | + dataset.angleStart = angle; | ||
| 45 | + angle += dataset.value * unit; | ||
| 46 | + dataset.angleEnd = angle; | ||
| 47 | + } | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | + pieSize = min(gridBox.width / 2, gridBox.height / 2); | ||
| 51 | + var reduce = false; | ||
| 52 | + | ||
| 53 | + do { | ||
| 54 | + reduce = false; | ||
| 55 | + for (final dataset in datasets) { | ||
| 56 | + if (dataset is PieDataSet) { | ||
| 57 | + dataset.layout(context, BoxConstraints.tight(gridBox.size)); | ||
| 58 | + assert(dataset.box != null); | ||
| 59 | + if (pieSize > 20 && | ||
| 60 | + (dataset.box!.width > gridBox.width || | ||
| 61 | + dataset.box!.height > gridBox.height)) { | ||
| 62 | + pieSize -= 10; | ||
| 63 | + reduce = true; | ||
| 64 | + break; | ||
| 65 | + } | ||
| 66 | + } | ||
| 67 | + } | ||
| 68 | + } while (reduce); | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + @override | ||
| 72 | + PdfPoint toChart(PdfPoint p) { | ||
| 73 | + return p; | ||
| 74 | + } | ||
| 75 | + | ||
| 76 | + void clip(Context context, PdfPoint size) {} | ||
| 77 | + | ||
| 78 | + @override | ||
| 79 | + void paint(Context context) { | ||
| 80 | + super.paint(context); | ||
| 81 | + | ||
| 82 | + final datasets = Chart.of(context).datasets; | ||
| 83 | + | ||
| 84 | + context.canvas | ||
| 85 | + ..saveContext() | ||
| 86 | + ..setTransform( | ||
| 87 | + Matrix4.translationValues(box!.width / 2, box!.height / 2, 0), | ||
| 88 | + ); | ||
| 89 | + | ||
| 90 | + for (var dataSet in datasets) { | ||
| 91 | + if (dataSet is PieDataSet) { | ||
| 92 | + dataSet.paintBackground(context); | ||
| 93 | + } | ||
| 94 | + } | ||
| 95 | + | ||
| 96 | + for (var dataSet in datasets) { | ||
| 97 | + if (dataSet is PieDataSet) { | ||
| 98 | + dataSet.paint(context); | ||
| 99 | + } | ||
| 100 | + } | ||
| 101 | + | ||
| 102 | + for (var dataSet in datasets) { | ||
| 103 | + if (dataSet is PieDataSet) { | ||
| 104 | + dataSet.paintLegend(context); | ||
| 105 | + } | ||
| 106 | + } | ||
| 107 | + | ||
| 108 | + context.canvas.restoreContext(); | ||
| 109 | + } | ||
| 110 | +} | ||
| 111 | + | ||
| 112 | +enum PieLegendPosition { none, auto, inside } | ||
| 113 | + | ||
| 114 | +class PieDataSet extends Dataset { | ||
| 115 | + PieDataSet({ | ||
| 116 | + required this.value, | ||
| 117 | + String? legend, | ||
| 118 | + required PdfColor color, | ||
| 119 | + this.borderColor = PdfColors.white, | ||
| 120 | + this.borderWidth = 1.5, | ||
| 121 | + bool? drawBorder, | ||
| 122 | + this.drawSurface = true, | ||
| 123 | + this.surfaceOpacity = 1, | ||
| 124 | + this.offset = 0, | ||
| 125 | + this.legendStyle, | ||
| 126 | + this.legendPosition = PieLegendPosition.auto, | ||
| 127 | + }) : drawBorder = drawBorder ?? borderColor != null && color != borderColor, | ||
| 128 | + assert((drawBorder ?? borderColor != null && color != borderColor) || | ||
| 129 | + drawSurface), | ||
| 130 | + super( | ||
| 131 | + legend: legend, | ||
| 132 | + color: color, | ||
| 133 | + ); | ||
| 134 | + | ||
| 135 | + final double value; | ||
| 136 | + | ||
| 137 | + late double angleStart; | ||
| 138 | + | ||
| 139 | + late double angleEnd; | ||
| 140 | + | ||
| 141 | + final bool drawBorder; | ||
| 142 | + final PdfColor? borderColor; | ||
| 143 | + final double borderWidth; | ||
| 144 | + | ||
| 145 | + final bool drawSurface; | ||
| 146 | + | ||
| 147 | + final double surfaceOpacity; | ||
| 148 | + | ||
| 149 | + final double offset; | ||
| 150 | + | ||
| 151 | + final TextStyle? legendStyle; | ||
| 152 | + | ||
| 153 | + final PieLegendPosition legendPosition; | ||
| 154 | + | ||
| 155 | + @override | ||
| 156 | + void layout(Context context, BoxConstraints constraints, | ||
| 157 | + {bool parentUsesSize = false}) { | ||
| 158 | + // final size = constraints.biggest; | ||
| 159 | + | ||
| 160 | + // ignore: avoid_as | ||
| 161 | + final grid = Chart.of(context).grid as PieGrid; | ||
| 162 | + final len = grid.pieSize + offset; | ||
| 163 | + | ||
| 164 | + box = PdfRect(-len, -len, len * 2, len * 2); | ||
| 165 | + } | ||
| 166 | + | ||
| 167 | + void _shape(Context context) { | ||
| 168 | + // ignore: avoid_as | ||
| 169 | + final grid = Chart.of(context).grid as PieGrid; | ||
| 170 | + | ||
| 171 | + final bisect = (angleStart + angleEnd) / 2; | ||
| 172 | + | ||
| 173 | + final cx = sin(bisect) * offset; | ||
| 174 | + final cy = cos(bisect) * offset; | ||
| 175 | + | ||
| 176 | + final sx = cx + sin(angleStart) * grid.pieSize; | ||
| 177 | + final sy = cy + cos(angleStart) * grid.pieSize; | ||
| 178 | + final ex = cx + sin(angleEnd) * grid.pieSize; | ||
| 179 | + final ey = cy + cos(angleEnd) * grid.pieSize; | ||
| 180 | + | ||
| 181 | + context.canvas | ||
| 182 | + ..moveTo(cx, cy) | ||
| 183 | + ..lineTo(sx, sy) | ||
| 184 | + ..bezierArc(sx, sy, grid.pieSize, grid.pieSize, ex, ey, | ||
| 185 | + large: angleEnd - angleStart > pi); | ||
| 186 | + } | ||
| 187 | + | ||
| 188 | + @override | ||
| 189 | + void paintBackground(Context context) { | ||
| 190 | + super.paint(context); | ||
| 191 | + | ||
| 192 | + if (drawSurface) { | ||
| 193 | + _shape(context); | ||
| 194 | + if (surfaceOpacity != 1) { | ||
| 195 | + context.canvas | ||
| 196 | + ..saveContext() | ||
| 197 | + ..setGraphicState( | ||
| 198 | + PdfGraphicState(opacity: surfaceOpacity), | ||
| 199 | + ); | ||
| 200 | + } | ||
| 201 | + | ||
| 202 | + context.canvas | ||
| 203 | + ..setFillColor(color) | ||
| 204 | + ..fillPath(); | ||
| 205 | + | ||
| 206 | + if (surfaceOpacity != 1) { | ||
| 207 | + context.canvas.restoreContext(); | ||
| 208 | + } | ||
| 209 | + } | ||
| 210 | + } | ||
| 211 | + | ||
| 212 | + @override | ||
| 213 | + void paint(Context context) { | ||
| 214 | + super.paint(context); | ||
| 215 | + | ||
| 216 | + if (drawBorder) { | ||
| 217 | + _shape(context); | ||
| 218 | + context.canvas | ||
| 219 | + ..setLineWidth(borderWidth) | ||
| 220 | + ..setLineJoin(PdfLineJoin.round) | ||
| 221 | + ..setStrokeColor(borderColor ?? color) | ||
| 222 | + ..strokePath(close: true); | ||
| 223 | + } | ||
| 224 | + } | ||
| 225 | + | ||
| 226 | + void paintLegend(Context context) { | ||
| 227 | + if (legendPosition != PieLegendPosition.none && legend != null) { | ||
| 228 | + // ignore: avoid_as | ||
| 229 | + final grid = Chart.of(context).grid as PieGrid; | ||
| 230 | + | ||
| 231 | + final bisect = (angleStart + angleEnd) / 2; | ||
| 232 | + | ||
| 233 | + final o = grid.pieSize * 2 / 3; | ||
| 234 | + final cx = sin(bisect) * (offset + o); | ||
| 235 | + final cy = cos(bisect) * (offset + o); | ||
| 236 | + | ||
| 237 | + Widget.draw( | ||
| 238 | + Text(legend!, style: legendStyle, textAlign: TextAlign.center), | ||
| 239 | + offset: PdfPoint(cx, cy), | ||
| 240 | + context: context, | ||
| 241 | + alignment: Alignment.center, | ||
| 242 | + constraints: const BoxConstraints(maxWidth: 200, maxHeight: 200), | ||
| 243 | + ); | ||
| 244 | + } | ||
| 245 | + } | ||
| 246 | +} |
| @@ -28,6 +28,7 @@ export 'src/widgets/chart/grid_cartesian.dart'; | @@ -28,6 +28,7 @@ export 'src/widgets/chart/grid_cartesian.dart'; | ||
| 28 | export 'src/widgets/chart/grid_radial.dart'; | 28 | export 'src/widgets/chart/grid_radial.dart'; |
| 29 | export 'src/widgets/chart/legend.dart'; | 29 | export 'src/widgets/chart/legend.dart'; |
| 30 | export 'src/widgets/chart/line_chart.dart'; | 30 | export 'src/widgets/chart/line_chart.dart'; |
| 31 | +export 'src/widgets/chart/pie_chart.dart'; | ||
| 31 | export 'src/widgets/clip.dart'; | 32 | export 'src/widgets/clip.dart'; |
| 32 | export 'src/widgets/container.dart'; | 33 | export 'src/widgets/container.dart'; |
| 33 | export 'src/widgets/content.dart'; | 34 | export 'src/widgets/content.dart'; |
| @@ -4,7 +4,7 @@ description: A pdf producer for Dart. It can create pdf files for both web or fl | @@ -4,7 +4,7 @@ description: A pdf producer for Dart. It can create pdf files for both web or fl | ||
| 4 | homepage: https://github.com/DavBfr/dart_pdf/tree/master/pdf | 4 | homepage: https://github.com/DavBfr/dart_pdf/tree/master/pdf |
| 5 | repository: https://github.com/DavBfr/dart_pdf | 5 | repository: https://github.com/DavBfr/dart_pdf |
| 6 | issue_tracker: https://github.com/DavBfr/dart_pdf/issues | 6 | issue_tracker: https://github.com/DavBfr/dart_pdf/issues |
| 7 | -version: 3.0.2 | 7 | +version: 3.1.0 |
| 8 | 8 | ||
| 9 | environment: | 9 | environment: |
| 10 | sdk: ">=2.12.0-0 <3.0.0" | 10 | sdk: ">=2.12.0-0 <3.0.0" |
-
Please register or login to post a comment