David PHAM-VAN

Implement donnut chart

... ... @@ -14,6 +14,7 @@
- Improve TextOverflow support
- Fix Table horizontalInside borders
- Improve PieChart default colors
- Implement donnut chart
## 3.2.0
... ...
... ... @@ -100,7 +100,7 @@ class _PdfGraphicsContext {
/// Pdf drawing operations
class PdfGraphics {
/// Create a new graphic canvas
PdfGraphics(this._page, this.buf) {
PdfGraphics(this._page, this._buf) {
_context = _PdfGraphicsContext(ctm: Matrix4.identity());
}
... ... @@ -114,7 +114,7 @@ class PdfGraphics {
final PdfGraphicStream _page;
/// Buffer where to write the graphic operations
final PdfStream buf;
final PdfStream _buf;
/// Default font if none selected
PdfFont? get defaultFont => _page.getDefaultFont();
... ... @@ -122,36 +122,36 @@ class PdfGraphics {
/// Draw a surface on the previously defined shape
/// set evenOdd to false to use the nonzero winding number rule to determine the region to fill and to true to use the even-odd rule to determine the region to fill
void fillPath({bool evenOdd = false}) {
buf.putString('f${evenOdd ? '*' : ''}\n');
_buf.putString('f${evenOdd ? '*' : ''}\n');
}
/// Draw the contour of the previously defined shape
void strokePath({bool close = false}) {
buf.putString('${close ? 's' : 'S'}\n');
_buf.putString('${close ? 's' : 'S'}\n');
}
/// Close the path with a line
void closePath() {
buf.putString('h\n');
_buf.putString('h\n');
}
/// Create a clipping surface from the previously defined shape,
/// to prevent any further drawing outside
void clipPath({bool evenOdd = false, bool end = true}) {
buf.putString('W${evenOdd ? '*' : ''}${end ? ' n' : ''}\n');
_buf.putString('W${evenOdd ? '*' : ''}${end ? ' n' : ''}\n');
}
/// Draw a surface on the previously defined shape and then draw the contour
/// set evenOdd to false to use the nonzero winding number rule to determine the region to fill and to true to use the even-odd rule to determine the region to fill
void fillAndStrokePath({bool evenOdd = false, bool close = false}) {
buf.putString('${close ? 'b' : 'B'}${evenOdd ? '*' : ''}\n');
_buf.putString('${close ? 'b' : 'B'}${evenOdd ? '*' : ''}\n');
}
/// Apply a shader
void applyShader(PdfShading shader) {
// The shader needs to be registered in the page resources
_page.addShader(shader);
buf.putString('${shader.name} sh\n');
_buf.putString('${shader.name} sh\n');
}
/// This releases any resources used by this Graphics object. You must use
... ... @@ -162,14 +162,14 @@ class PdfGraphics {
void restoreContext() {
if (_contextQueue.isNotEmpty) {
// restore graphics context
buf.putString('Q\n');
_buf.putString('Q\n');
_context = _contextQueue.removeLast();
}
}
/// Save the graphc context
void saveContext() {
buf.putString('q\n');
_buf.putString('q\n');
_contextQueue.addLast(_context.copy());
}
... ... @@ -182,35 +182,35 @@ class PdfGraphics {
_page.addXObject(img);
// q w 0 0 h x y cm % the coordinate matrix
buf.putString('q ');
_buf.putString('q ');
switch (img.orientation) {
case PdfImageOrientation.topLeft:
PdfNumList(<double>[w, 0, 0, h, x, y]).output(buf);
PdfNumList(<double>[w, 0, 0, h, x, y]).output(_buf);
break;
case PdfImageOrientation.topRight:
PdfNumList(<double>[-w, 0, 0, h, w + x, y]).output(buf);
PdfNumList(<double>[-w, 0, 0, h, w + x, y]).output(_buf);
break;
case PdfImageOrientation.bottomRight:
PdfNumList(<double>[-w, 0, 0, -h, w + x, h + y]).output(buf);
PdfNumList(<double>[-w, 0, 0, -h, w + x, h + y]).output(_buf);
break;
case PdfImageOrientation.bottomLeft:
PdfNumList(<double>[w, 0, 0, -h, x, h + y]).output(buf);
PdfNumList(<double>[w, 0, 0, -h, x, h + y]).output(_buf);
break;
case PdfImageOrientation.leftTop:
PdfNumList(<double>[0, -h, -w, 0, w + x, h + y]).output(buf);
PdfNumList(<double>[0, -h, -w, 0, w + x, h + y]).output(_buf);
break;
case PdfImageOrientation.rightTop:
PdfNumList(<double>[0, -h, w, 0, x, h + y]).output(buf);
PdfNumList(<double>[0, -h, w, 0, x, h + y]).output(_buf);
break;
case PdfImageOrientation.rightBottom:
PdfNumList(<double>[0, h, w, 0, x, y]).output(buf);
PdfNumList(<double>[0, h, w, 0, x, y]).output(_buf);
break;
case PdfImageOrientation.leftBottom:
PdfNumList(<double>[0, h, -w, 0, w + x, y]).output(buf);
PdfNumList(<double>[0, h, -w, 0, w + x, y]).output(_buf);
break;
}
buf.putString(' cm ${img.name} Do Q\n');
_buf.putString(' cm ${img.name} Do Q\n');
}
/// Draws a line between two coordinates.
... ... @@ -220,12 +220,22 @@ class PdfGraphics {
}
/// Draws an ellipse
void drawEllipse(double x, double y, double r1, double r2) {
///
/// Use clockwise=false to draw the inside of a donnnut
void drawEllipse(double x, double y, double r1, double r2,
{bool clockwise = true}) {
moveTo(x, y - r2);
curveTo(x + _m4 * r1, y - r2, x + r1, y - _m4 * r2, x + r1, y);
curveTo(x + r1, y + _m4 * r2, x + _m4 * r1, y + r2, x, y + r2);
curveTo(x - _m4 * r1, y + r2, x - r1, y + _m4 * r2, x - r1, y);
curveTo(x - r1, y - _m4 * r2, x - _m4 * r1, y - r2, x, y - r2);
if (clockwise) {
curveTo(x + _m4 * r1, y - r2, x + r1, y - _m4 * r2, x + r1, y);
curveTo(x + r1, y + _m4 * r2, x + _m4 * r1, y + r2, x, y + r2);
curveTo(x - _m4 * r1, y + r2, x - r1, y + _m4 * r2, x - r1, y);
curveTo(x - r1, y - _m4 * r2, x - _m4 * r1, y - r2, x, y - r2);
} else {
curveTo(x - _m4 * r1, y - r2, x - r1, y - _m4 * r2, x - r1, y);
curveTo(x - r1, y + _m4 * r2, x - _m4 * r1, y + r2, x, y + r2);
curveTo(x + _m4 * r1, y + r2, x + r1, y + _m4 * r2, x + r1, y);
curveTo(x + r1, y - _m4 * r2, x + _m4 * r1, y - r2, x, y - r2);
}
}
/// Draws a Rectangle
... ... @@ -235,8 +245,8 @@ class PdfGraphics {
double w,
double h,
) {
PdfNumList([x, y, w, h]).output(buf);
buf.putString(' re\n');
PdfNumList([x, y, w, h]).output(_buf);
_buf.putString(' re\n');
}
/// Draws a Rectangle
... ... @@ -268,27 +278,27 @@ class PdfGraphics {
PdfTextRenderingMode? mode = PdfTextRenderingMode.fill,
double? rise,
}) {
buf.putString('${font.name} ');
PdfNum(size).output(buf);
buf.putString(' Tf\n');
_buf.putString('${font.name} ');
PdfNum(size).output(_buf);
_buf.putString(' Tf\n');
if (charSpace != null) {
PdfNum(charSpace).output(buf);
buf.putString(' Tc\n');
PdfNum(charSpace).output(_buf);
_buf.putString(' Tc\n');
}
if (wordSpace != null) {
PdfNum(wordSpace).output(buf);
buf.putString(' Tw\n');
PdfNum(wordSpace).output(_buf);
_buf.putString(' Tw\n');
}
if (scale != null) {
PdfNum(scale * 100).output(buf);
buf.putString(' Tz\n');
PdfNum(scale * 100).output(_buf);
_buf.putString(' Tz\n');
}
if (rise != null) {
PdfNum(rise).output(buf);
buf.putString(' Ts\n');
PdfNum(rise).output(_buf);
_buf.putString(' Ts\n');
}
if (mode != PdfTextRenderingMode.fill) {
buf.putString('${mode!.index} Tr\n');
_buf.putString('${mode!.index} Tr\n');
}
}
... ... @@ -307,22 +317,22 @@ class PdfGraphics {
}) {
_page.addFont(font);
buf.putString('BT ');
PdfNumList([x, y]).output(buf);
buf.putString(' Td ');
_buf.putString('BT ');
PdfNumList([x, y]).output(_buf);
_buf.putString(' Td ');
setFont(font, size,
charSpace: charSpace,
mode: mode,
rise: rise,
scale: scale,
wordSpace: wordSpace);
buf.putString('[');
font.putText(buf, s);
buf.putString(']TJ ET\n');
_buf.putString('[');
font.putText(_buf, s);
_buf.putString(']TJ ET\n');
}
void reset() {
buf.putString('0 Tr\n');
_buf.putString('0 Tr\n');
}
/// Sets the color for drawing
... ... @@ -335,11 +345,11 @@ class PdfGraphics {
void setFillColor(PdfColor? color) {
if (color is PdfColorCmyk) {
PdfNumList(<double>[color.cyan, color.magenta, color.yellow, color.black])
.output(buf);
buf.putString(' k\n');
.output(_buf);
_buf.putString(' k\n');
} else {
PdfNumList(<double>[color!.red, color.green, color.blue]).output(buf);
buf.putString(' rg\n');
PdfNumList(<double>[color!.red, color.green, color.blue]).output(_buf);
_buf.putString(' rg\n');
}
}
... ... @@ -347,11 +357,11 @@ class PdfGraphics {
void setStrokeColor(PdfColor? color) {
if (color is PdfColorCmyk) {
PdfNumList(<double>[color.cyan, color.magenta, color.yellow, color.black])
.output(buf);
buf.putString(' K\n');
.output(_buf);
_buf.putString(' K\n');
} else {
PdfNumList(<double>[color!.red, color.green, color.blue]).output(buf);
buf.putString(' RG\n');
PdfNumList(<double>[color!.red, color.green, color.blue]).output(_buf);
_buf.putString(' RG\n');
}
}
... ... @@ -359,27 +369,27 @@ class PdfGraphics {
void setFillPattern(PdfPattern pattern) {
// The shader needs to be registered in the page resources
_page.addPattern(pattern);
buf.putString('/Pattern cs${pattern.name} scn\n');
_buf.putString('/Pattern cs${pattern.name} scn\n');
}
/// Sets the stroke pattern for drawing
void setStrokePattern(PdfPattern pattern) {
// The shader needs to be registered in the page resources
_page.addPattern(pattern);
buf.putString('/Pattern CS${pattern.name} SCN\n');
_buf.putString('/Pattern CS${pattern.name} SCN\n');
}
/// Set the graphic state for drawing
void setGraphicState(PdfGraphicState state) {
final name = _page.stateName(state);
buf.putString('$name gs\n');
_buf.putString('$name gs\n');
}
/// Set the transformation Matrix
void setTransform(Matrix4 t) {
final s = t.storage;
PdfNumList(<double>[s[0], s[1], s[4], s[5], s[12], s[13]]).output(buf);
buf.putString(' cm\n');
PdfNumList(<double>[s[0], s[1], s[4], s[5], s[12], s[13]]).output(_buf);
_buf.putString(' cm\n');
_context.ctm.multiply(t);
}
... ... @@ -390,14 +400,14 @@ class PdfGraphics {
/// This adds a line segment to the current path
void lineTo(double x, double y) {
PdfNumList([x, y]).output(buf);
buf.putString(' l\n');
PdfNumList([x, y]).output(_buf);
_buf.putString(' l\n');
}
/// This moves the current drawing point.
void moveTo(double x, double y) {
PdfNumList([x, y]).output(buf);
buf.putString(' m\n');
PdfNumList([x, y]).output(_buf);
_buf.putString(' m\n');
}
/// Draw a cubic bézier curve from the current point to (x3,y3)
... ... @@ -405,8 +415,8 @@ class PdfGraphics {
/// and (x2,y2) as the control point at the end of the curve.
void curveTo(
double x1, double y1, double x2, double y2, double x3, double y3) {
PdfNumList([x1, y1, x2, y2, x3, y3]).output(buf);
buf.putString(' c\n');
PdfNumList([x1, y1, x2, y2, x3, y3]).output(_buf);
_buf.putString(' c\n');
}
double _vectorAngle(double ux, double uy, double vx, double vy) {
... ... @@ -566,25 +576,25 @@ class PdfGraphics {
/// Set line starting and ending cap type
void setLineCap(PdfLineCap cap) {
buf.putString('${cap.index} J\n');
_buf.putString('${cap.index} J\n');
}
/// Set line join type
void setLineJoin(PdfLineJoin join) {
buf.putString('${join.index} j\n');
_buf.putString('${join.index} j\n');
}
/// Set line width
void setLineWidth(double width) {
PdfNum(width).output(buf);
buf.putString(' w\n');
PdfNum(width).output(_buf);
_buf.putString(' w\n');
}
/// Set line joint miter limit, applies if the
void setMiterLimit(double limit) {
assert(limit >= 1.0);
PdfNum(limit).output(buf);
buf.putString(' M\n');
PdfNum(limit).output(_buf);
_buf.putString(' M\n');
}
/// The dash array shall be cycled through, adding up the lengths of dashes and gaps.
... ... @@ -592,8 +602,17 @@ class PdfGraphics {
///
/// Example: [2 1] will create a dash pattern with 2 on, 1 off, 2 on, 1 off, ...
void setLineDashPattern([List<num> array = const <num>[], int phase = 0]) {
PdfArray.fromNum(array).output(buf);
buf.putString(' $phase d\n');
PdfArray.fromNum(array).output(_buf);
_buf.putString(' $phase d\n');
}
void markContentBegin(PdfName tag) {
tag.output(_buf);
_buf.putString(' BMC\n');
}
void markContentEnd() {
_buf.putString('EMC\n');
}
}
... ...
... ... @@ -2,20 +2,21 @@
import 'dart:math';
import 'package:meta/meta.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart';
import 'package:vector_math/vector_math_64.dart';
class PieGrid extends ChartGrid {
PieGrid();
PieGrid({this.startAngle = 0});
late PdfRect gridBox;
/// Start angle for the first [PieDataSet]
final double startAngle;
late double total;
late double _radius;
late double unit;
late double pieSize;
/// Nominal radius of a pie slice
double get radius => _radius;
@override
void layout(Context context, BoxConstraints constraints,
... ... @@ -25,19 +26,19 @@ class PieGrid extends ChartGrid {
final datasets = Chart.of(context).datasets;
final size = constraints.biggest;
gridBox = PdfRect(0, 0, size.x, size.y);
final _gridBox = PdfRect(0, 0, size.x, size.y);
total = 0.0;
var _total = 0.0;
for (final dataset in datasets) {
assert(dataset is PieDataSet, 'Use only PieDataSet with a PieGrid');
assert(dataset is PieDataSet, 'Use only PieDataset with a PieGrid');
if (dataset is PieDataSet) {
total += dataset.value;
_total += dataset.value;
}
}
unit = pi / total * 2;
var angle = 0.0;
final unit = pi / _total * 2;
var angle = startAngle;
for (final dataset in datasets) {
if (dataset is PieDataSet) {
... ... @@ -47,19 +48,19 @@ class PieGrid extends ChartGrid {
}
}
pieSize = min(gridBox.width / 2, gridBox.height / 2);
_radius = min(_gridBox.width / 2, _gridBox.height / 2);
var reduce = false;
do {
reduce = false;
for (final dataset in datasets) {
if (dataset is PieDataSet) {
dataset.layout(context, BoxConstraints.tight(gridBox.size));
dataset.layout(context, BoxConstraints.tight(_gridBox.size));
assert(dataset.box != null);
if (pieSize > 20 &&
(dataset.box!.width > gridBox.width ||
dataset.box!.height > gridBox.height)) {
pieSize -= 10;
if (_radius > 20 &&
(dataset.box!.width > _gridBox.width ||
dataset.box!.height > _gridBox.height)) {
_radius -= 10;
reduce = true;
break;
}
... ... @@ -129,7 +130,10 @@ class PieDataSet extends Dataset {
PdfColor? legendLineColor,
Widget? legendWidget,
this.legendOffset = 20,
}) : drawBorder = drawBorder ?? borderColor != null && color != borderColor,
this.innerRadius = 0,
}) : assert(innerRadius >= 0),
assert(offset >= 0),
drawBorder = drawBorder ?? borderColor != null && color != borderColor,
assert((drawBorder ?? borderColor != null && color != borderColor) ||
drawSurface),
_legendWidget = legendWidget,
... ... @@ -168,6 +172,8 @@ class PieDataSet extends Dataset {
final PdfColor legendLineColor;
final double innerRadius;
PdfPoint? _legendAnchor;
PdfPoint? _legendPivot;
PdfPoint? _legendStart;
... ... @@ -180,7 +186,7 @@ class PieDataSet extends Dataset {
final _offset = _isFullCircle ? 0 : offset;
final grid = Chart.of(context).grid as PieGrid;
final len = grid.pieSize + _offset;
final len = grid.radius + _offset;
var x = -len;
var y = -len;
var w = len * 2;
... ... @@ -217,7 +223,7 @@ class PieDataSet extends Dataset {
if (_legendWidget != null) {
_legendWidget!.layout(context,
BoxConstraints(maxWidth: grid.pieSize, maxHeight: grid.pieSize));
BoxConstraints(maxWidth: grid.radius, maxHeight: grid.radius));
assert(_legendWidget!.box != null);
final ls = _legendWidget!.box!.size;
... ... @@ -226,13 +232,13 @@ class PieDataSet extends Dataset {
switch (lp) {
case PieLegendPosition.outside:
final o = grid.pieSize + legendOffset;
final o = grid.radius + legendOffset;
final cx = sin(bisect) * (_offset + o);
final cy = cos(bisect) * (_offset + o);
_legendStart = PdfPoint(
sin(bisect) * (_offset + grid.pieSize + legendOffset * 0.1),
cos(bisect) * (_offset + grid.pieSize + legendOffset * 0.1),
sin(bisect) * (_offset + grid.radius + legendOffset * 0.1),
cos(bisect) * (_offset + grid.radius + legendOffset * 0.1),
);
_legendPivot = PdfPoint(cx, cy);
... ... @@ -269,9 +275,23 @@ class PieDataSet extends Dataset {
}
break;
case PieLegendPosition.inside:
final o = _isFullCircle ? 0 : grid.pieSize * 2 / 3;
final cx = sin(bisect) * (_offset + o);
final cy = cos(bisect) * (_offset + o);
final double o;
final double cx;
final double cy;
if (innerRadius == 0) {
o = _isFullCircle ? 0 : grid.radius * 2 / 3;
cx = sin(bisect) * (_offset + o);
cy = cos(bisect) * (_offset + o);
} else {
o = (grid.radius + innerRadius) / 2;
if (_isFullCircle) {
cx = 0;
cy = o;
} else {
cx = sin(bisect) * (_offset + o);
cy = cos(bisect) * (_offset + o);
}
}
_legendWidget!.box = PdfRect.fromPoints(
PdfPoint(
cx - ls.x / 2,
... ... @@ -287,7 +307,7 @@ class PieDataSet extends Dataset {
box = PdfRect(x, y, w, h);
}
void _shape(Context context) {
void _paintSliceShape(Context context) {
final grid = Chart.of(context).grid as PieGrid;
final bisect = (angleStart + angleEnd) / 2;
... ... @@ -295,28 +315,69 @@ class PieDataSet extends Dataset {
final cx = sin(bisect) * offset;
final cy = cos(bisect) * offset;
final sx = cx + sin(angleStart) * grid.pieSize;
final sy = cy + cos(angleStart) * grid.pieSize;
final ex = cx + sin(angleEnd) * grid.pieSize;
final ey = cy + cos(angleEnd) * grid.pieSize;
final sx = cx + sin(angleStart) * grid.radius;
final sy = cy + cos(angleStart) * grid.radius;
final ex = cx + sin(angleEnd) * grid.radius;
final ey = cy + cos(angleEnd) * grid.radius;
if (_isFullCircle) {
context.canvas.drawEllipse(0, 0, grid.pieSize, grid.pieSize);
context.canvas.drawEllipse(0, 0, grid.radius, grid.radius);
} else {
context.canvas
..moveTo(cx, cy)
..lineTo(sx, sy)
..bezierArc(sx, sy, grid.pieSize, grid.pieSize, ex, ey,
..bezierArc(sx, sy, grid.radius, grid.radius, ex, ey,
large: angleEnd - angleStart > pi);
}
}
void _paintDonnutShape(Context context) {
final grid = Chart.of(context).grid as PieGrid;
final bisect = (angleStart + angleEnd) / 2;
final cx = sin(bisect) * offset;
final cy = cos(bisect) * offset;
final stx = cx + sin(angleStart) * grid.radius;
final sty = cy + cos(angleStart) * grid.radius;
final etx = cx + sin(angleEnd) * grid.radius;
final ety = cy + cos(angleEnd) * grid.radius;
final sbx = cx + sin(angleStart) * innerRadius;
final sby = cy + cos(angleStart) * innerRadius;
final ebx = cx + sin(angleEnd) * innerRadius;
final eby = cy + cos(angleEnd) * innerRadius;
if (_isFullCircle) {
context.canvas.drawEllipse(0, 0, grid.radius, grid.radius);
context.canvas
.drawEllipse(0, 0, innerRadius, innerRadius, clockwise: false);
} else {
context.canvas
..moveTo(stx, sty)
..bezierArc(stx, sty, grid.radius, grid.radius, etx, ety,
large: angleEnd - angleStart > pi)
..lineTo(ebx, eby)
..bezierArc(ebx, eby, innerRadius, innerRadius, sbx, sby,
large: angleEnd - angleStart > pi, sweep: true)
..lineTo(stx, sty);
}
}
void _paintShape(Context context) {
if (innerRadius == 0) {
_paintSliceShape(context);
} else {
_paintDonnutShape(context);
}
}
@override
void paintBackground(Context context) {
super.paint(context);
if (drawSurface) {
_shape(context);
_paintShape(context);
if (surfaceOpacity != 1) {
context.canvas
..saveContext()
... ... @@ -340,7 +401,7 @@ class PieDataSet extends Dataset {
super.paint(context);
if (drawBorder) {
_shape(context);
_paintShape(context);
context.canvas
..setLineWidth(borderWidth)
..setLineJoin(PdfLineJoin.round)
... ... @@ -349,6 +410,7 @@ class PieDataSet extends Dataset {
}
}
@protected
void paintLegend(Context context) {
if (legendPosition != PieLegendPosition.none && _legendWidget != null) {
if (_legendAnchor != null &&
... ... @@ -379,8 +441,8 @@ class PieDataSet extends Dataset {
final bisect = (angleStart + angleEnd) / 2;
final cx = sin(bisect) * (offset + grid.pieSize + legendOffset);
final cy = cos(bisect) * (offset + grid.pieSize + legendOffset);
final cx = sin(bisect) * (offset + grid.radius + legendOffset);
final cy = cos(bisect) * (offset + grid.radius + legendOffset);
if (_legendWidget != null) {
context.canvas
... ...
... ... @@ -17,6 +17,7 @@
import 'dart:typed_data';
import 'package:pdf/pdf.dart';
import 'package:pdf/src/pdf/data_types.dart';
import 'package:vector_math/vector_math_64.dart';
import 'basic.dart';
... ... @@ -294,7 +295,7 @@ class TextField extends StatelessWidget {
if (value != null) {
final canvas = tf.appearance(context.document, PdfAnnotAppearance.normal,
matrix: mat, boundingBox: box);
canvas.buf.putString('/Tx BMC\n');
canvas.markContentBegin(const PdfName('/Tx'));
Widget.draw(
Text(value!, style: _textStyle),
offset: PdfPoint.zero,
... ... @@ -303,7 +304,7 @@ class TextField extends StatelessWidget {
constraints:
BoxConstraints.tightFor(width: box!.width, height: box!.height),
);
canvas.buf.putString('EMC\n');
canvas.markContentEnd();
}
PdfAnnot(context.page, tf);
... ...
... ... @@ -16,9 +16,9 @@
import 'dart:io';
import 'package:test/test.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart';
import 'package:test/test.dart';
late Document pdf;
... ... @@ -213,6 +213,83 @@ void main() {
});
});
test('Standard PieChart', () {
const data = <String, double>{
'Wind': 8.4,
'Hydro': 7.4,
'Solar': 2.4,
'Biomass': 1.4,
'Geothermal': 0.4,
'Nuclear': 20,
'Coal': 19,
'Petroleum': 1,
'Natural gas': 40,
};
var color = 0;
pdf.addPage(
Page(
pageFormat: PdfPageFormat.standard.landscape,
build: (Context context) => Chart(
title: Text('Sources of U.S. electricity generation, 2020'),
grid: PieGrid(),
datasets: [
for (final item in data.entries)
PieDataSet(
legend: item.key,
value: item.value,
color: PdfColors
.primaries[(color++) * 4 % PdfColors.primaries.length],
offset: color == 6 ? 30 : 0,
),
],
),
),
);
});
test('Donnuts PieChart', () {
const internalRadius = 150.0;
const data = <String, int>{
'Dogs': 5528,
'Birds': 2211,
'Rabbits': 3216,
'Ermine': 740,
'Cats': 8241,
};
var color = 0;
final total = data.values.fold<int>(0, (v, e) => v + e);
pdf.addPage(
Page(
pageFormat: PdfPageFormat.standard.landscape,
build: (Context context) => Stack(
alignment: Alignment.center,
children: [
Text(
'Pets',
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 30),
),
Chart(
grid: PieGrid(startAngle: 1),
datasets: [
for (final item in data.entries)
PieDataSet(
legend: '${item.key} ${item.value * 100 ~/ total}%',
value: item.value,
color: PdfColors
.primaries[(color++) * 2 % PdfColors.primaries.length],
innerRadius: internalRadius,
),
],
),
],
),
),
);
});
tearDownAll(() async {
final file = File('widgets-chart.pdf');
await file.writeAsBytes(await pdf.save());
... ...