David PHAM-VAN

Implement donnut chart

@@ -14,6 +14,7 @@ @@ -14,6 +14,7 @@
14 - Improve TextOverflow support 14 - Improve TextOverflow support
15 - Fix Table horizontalInside borders 15 - Fix Table horizontalInside borders
16 - Improve PieChart default colors 16 - Improve PieChart default colors
  17 +- Implement donnut chart
17 18
18 ## 3.2.0 19 ## 3.2.0
19 20
@@ -100,7 +100,7 @@ class _PdfGraphicsContext { @@ -100,7 +100,7 @@ class _PdfGraphicsContext {
100 /// Pdf drawing operations 100 /// Pdf drawing operations
101 class PdfGraphics { 101 class PdfGraphics {
102 /// Create a new graphic canvas 102 /// Create a new graphic canvas
103 - PdfGraphics(this._page, this.buf) { 103 + PdfGraphics(this._page, this._buf) {
104 _context = _PdfGraphicsContext(ctm: Matrix4.identity()); 104 _context = _PdfGraphicsContext(ctm: Matrix4.identity());
105 } 105 }
106 106
@@ -114,7 +114,7 @@ class PdfGraphics { @@ -114,7 +114,7 @@ class PdfGraphics {
114 final PdfGraphicStream _page; 114 final PdfGraphicStream _page;
115 115
116 /// Buffer where to write the graphic operations 116 /// Buffer where to write the graphic operations
117 - final PdfStream buf; 117 + final PdfStream _buf;
118 118
119 /// Default font if none selected 119 /// Default font if none selected
120 PdfFont? get defaultFont => _page.getDefaultFont(); 120 PdfFont? get defaultFont => _page.getDefaultFont();
@@ -122,36 +122,36 @@ class PdfGraphics { @@ -122,36 +122,36 @@ class PdfGraphics {
122 /// Draw a surface on the previously defined shape 122 /// Draw a surface on the previously defined shape
123 /// 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 123 /// 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
124 void fillPath({bool evenOdd = false}) { 124 void fillPath({bool evenOdd = false}) {
125 - buf.putString('f${evenOdd ? '*' : ''}\n'); 125 + _buf.putString('f${evenOdd ? '*' : ''}\n');
126 } 126 }
127 127
128 /// Draw the contour of the previously defined shape 128 /// Draw the contour of the previously defined shape
129 void strokePath({bool close = false}) { 129 void strokePath({bool close = false}) {
130 - buf.putString('${close ? 's' : 'S'}\n'); 130 + _buf.putString('${close ? 's' : 'S'}\n');
131 } 131 }
132 132
133 /// Close the path with a line 133 /// Close the path with a line
134 void closePath() { 134 void closePath() {
135 - buf.putString('h\n'); 135 + _buf.putString('h\n');
136 } 136 }
137 137
138 /// Create a clipping surface from the previously defined shape, 138 /// Create a clipping surface from the previously defined shape,
139 /// to prevent any further drawing outside 139 /// to prevent any further drawing outside
140 void clipPath({bool evenOdd = false, bool end = true}) { 140 void clipPath({bool evenOdd = false, bool end = true}) {
141 - buf.putString('W${evenOdd ? '*' : ''}${end ? ' n' : ''}\n'); 141 + _buf.putString('W${evenOdd ? '*' : ''}${end ? ' n' : ''}\n');
142 } 142 }
143 143
144 /// Draw a surface on the previously defined shape and then draw the contour 144 /// Draw a surface on the previously defined shape and then draw the contour
145 /// 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 145 /// 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
146 void fillAndStrokePath({bool evenOdd = false, bool close = false}) { 146 void fillAndStrokePath({bool evenOdd = false, bool close = false}) {
147 - buf.putString('${close ? 'b' : 'B'}${evenOdd ? '*' : ''}\n'); 147 + _buf.putString('${close ? 'b' : 'B'}${evenOdd ? '*' : ''}\n');
148 } 148 }
149 149
150 /// Apply a shader 150 /// Apply a shader
151 void applyShader(PdfShading shader) { 151 void applyShader(PdfShading shader) {
152 // The shader needs to be registered in the page resources 152 // The shader needs to be registered in the page resources
153 _page.addShader(shader); 153 _page.addShader(shader);
154 - buf.putString('${shader.name} sh\n'); 154 + _buf.putString('${shader.name} sh\n');
155 } 155 }
156 156
157 /// This releases any resources used by this Graphics object. You must use 157 /// This releases any resources used by this Graphics object. You must use
@@ -162,14 +162,14 @@ class PdfGraphics { @@ -162,14 +162,14 @@ class PdfGraphics {
162 void restoreContext() { 162 void restoreContext() {
163 if (_contextQueue.isNotEmpty) { 163 if (_contextQueue.isNotEmpty) {
164 // restore graphics context 164 // restore graphics context
165 - buf.putString('Q\n'); 165 + _buf.putString('Q\n');
166 _context = _contextQueue.removeLast(); 166 _context = _contextQueue.removeLast();
167 } 167 }
168 } 168 }
169 169
170 /// Save the graphc context 170 /// Save the graphc context
171 void saveContext() { 171 void saveContext() {
172 - buf.putString('q\n'); 172 + _buf.putString('q\n');
173 _contextQueue.addLast(_context.copy()); 173 _contextQueue.addLast(_context.copy());
174 } 174 }
175 175
@@ -182,35 +182,35 @@ class PdfGraphics { @@ -182,35 +182,35 @@ class PdfGraphics {
182 _page.addXObject(img); 182 _page.addXObject(img);
183 183
184 // q w 0 0 h x y cm % the coordinate matrix 184 // q w 0 0 h x y cm % the coordinate matrix
185 - buf.putString('q '); 185 + _buf.putString('q ');
186 switch (img.orientation) { 186 switch (img.orientation) {
187 case PdfImageOrientation.topLeft: 187 case PdfImageOrientation.topLeft:
188 - PdfNumList(<double>[w, 0, 0, h, x, y]).output(buf); 188 + PdfNumList(<double>[w, 0, 0, h, x, y]).output(_buf);
189 break; 189 break;
190 case PdfImageOrientation.topRight: 190 case PdfImageOrientation.topRight:
191 - PdfNumList(<double>[-w, 0, 0, h, w + x, y]).output(buf); 191 + PdfNumList(<double>[-w, 0, 0, h, w + x, y]).output(_buf);
192 break; 192 break;
193 case PdfImageOrientation.bottomRight: 193 case PdfImageOrientation.bottomRight:
194 - PdfNumList(<double>[-w, 0, 0, -h, w + x, h + y]).output(buf); 194 + PdfNumList(<double>[-w, 0, 0, -h, w + x, h + y]).output(_buf);
195 break; 195 break;
196 case PdfImageOrientation.bottomLeft: 196 case PdfImageOrientation.bottomLeft:
197 - PdfNumList(<double>[w, 0, 0, -h, x, h + y]).output(buf); 197 + PdfNumList(<double>[w, 0, 0, -h, x, h + y]).output(_buf);
198 break; 198 break;
199 case PdfImageOrientation.leftTop: 199 case PdfImageOrientation.leftTop:
200 - PdfNumList(<double>[0, -h, -w, 0, w + x, h + y]).output(buf); 200 + PdfNumList(<double>[0, -h, -w, 0, w + x, h + y]).output(_buf);
201 break; 201 break;
202 case PdfImageOrientation.rightTop: 202 case PdfImageOrientation.rightTop:
203 - PdfNumList(<double>[0, -h, w, 0, x, h + y]).output(buf); 203 + PdfNumList(<double>[0, -h, w, 0, x, h + y]).output(_buf);
204 break; 204 break;
205 case PdfImageOrientation.rightBottom: 205 case PdfImageOrientation.rightBottom:
206 - PdfNumList(<double>[0, h, w, 0, x, y]).output(buf); 206 + PdfNumList(<double>[0, h, w, 0, x, y]).output(_buf);
207 break; 207 break;
208 case PdfImageOrientation.leftBottom: 208 case PdfImageOrientation.leftBottom:
209 - PdfNumList(<double>[0, h, -w, 0, w + x, y]).output(buf); 209 + PdfNumList(<double>[0, h, -w, 0, w + x, y]).output(_buf);
210 break; 210 break;
211 } 211 }
212 212
213 - buf.putString(' cm ${img.name} Do Q\n'); 213 + _buf.putString(' cm ${img.name} Do Q\n');
214 } 214 }
215 215
216 /// Draws a line between two coordinates. 216 /// Draws a line between two coordinates.
@@ -220,12 +220,22 @@ class PdfGraphics { @@ -220,12 +220,22 @@ class PdfGraphics {
220 } 220 }
221 221
222 /// Draws an ellipse 222 /// Draws an ellipse
223 - void drawEllipse(double x, double y, double r1, double r2) { 223 + ///
  224 + /// Use clockwise=false to draw the inside of a donnnut
  225 + void drawEllipse(double x, double y, double r1, double r2,
  226 + {bool clockwise = true}) {
224 moveTo(x, y - r2); 227 moveTo(x, y - r2);
  228 + if (clockwise) {
225 curveTo(x + _m4 * r1, y - r2, x + r1, y - _m4 * r2, x + r1, y); 229 curveTo(x + _m4 * r1, y - r2, x + r1, y - _m4 * r2, x + r1, y);
226 curveTo(x + r1, y + _m4 * r2, x + _m4 * r1, y + r2, x, y + r2); 230 curveTo(x + r1, y + _m4 * r2, x + _m4 * r1, y + r2, x, y + r2);
227 curveTo(x - _m4 * r1, y + r2, x - r1, y + _m4 * r2, x - r1, y); 231 curveTo(x - _m4 * r1, y + r2, x - r1, y + _m4 * r2, x - r1, y);
228 curveTo(x - r1, y - _m4 * r2, x - _m4 * r1, y - r2, x, y - r2); 232 curveTo(x - r1, y - _m4 * r2, x - _m4 * r1, y - r2, x, y - r2);
  233 + } else {
  234 + curveTo(x - _m4 * r1, y - r2, x - r1, y - _m4 * r2, x - r1, y);
  235 + curveTo(x - r1, y + _m4 * r2, x - _m4 * r1, y + r2, x, y + r2);
  236 + curveTo(x + _m4 * r1, y + r2, x + r1, y + _m4 * r2, x + r1, y);
  237 + curveTo(x + r1, y - _m4 * r2, x + _m4 * r1, y - r2, x, y - r2);
  238 + }
229 } 239 }
230 240
231 /// Draws a Rectangle 241 /// Draws a Rectangle
@@ -235,8 +245,8 @@ class PdfGraphics { @@ -235,8 +245,8 @@ class PdfGraphics {
235 double w, 245 double w,
236 double h, 246 double h,
237 ) { 247 ) {
238 - PdfNumList([x, y, w, h]).output(buf);  
239 - buf.putString(' re\n'); 248 + PdfNumList([x, y, w, h]).output(_buf);
  249 + _buf.putString(' re\n');
240 } 250 }
241 251
242 /// Draws a Rectangle 252 /// Draws a Rectangle
@@ -268,27 +278,27 @@ class PdfGraphics { @@ -268,27 +278,27 @@ class PdfGraphics {
268 PdfTextRenderingMode? mode = PdfTextRenderingMode.fill, 278 PdfTextRenderingMode? mode = PdfTextRenderingMode.fill,
269 double? rise, 279 double? rise,
270 }) { 280 }) {
271 - buf.putString('${font.name} ');  
272 - PdfNum(size).output(buf);  
273 - buf.putString(' Tf\n'); 281 + _buf.putString('${font.name} ');
  282 + PdfNum(size).output(_buf);
  283 + _buf.putString(' Tf\n');
274 if (charSpace != null) { 284 if (charSpace != null) {
275 - PdfNum(charSpace).output(buf);  
276 - buf.putString(' Tc\n'); 285 + PdfNum(charSpace).output(_buf);
  286 + _buf.putString(' Tc\n');
277 } 287 }
278 if (wordSpace != null) { 288 if (wordSpace != null) {
279 - PdfNum(wordSpace).output(buf);  
280 - buf.putString(' Tw\n'); 289 + PdfNum(wordSpace).output(_buf);
  290 + _buf.putString(' Tw\n');
281 } 291 }
282 if (scale != null) { 292 if (scale != null) {
283 - PdfNum(scale * 100).output(buf);  
284 - buf.putString(' Tz\n'); 293 + PdfNum(scale * 100).output(_buf);
  294 + _buf.putString(' Tz\n');
285 } 295 }
286 if (rise != null) { 296 if (rise != null) {
287 - PdfNum(rise).output(buf);  
288 - buf.putString(' Ts\n'); 297 + PdfNum(rise).output(_buf);
  298 + _buf.putString(' Ts\n');
289 } 299 }
290 if (mode != PdfTextRenderingMode.fill) { 300 if (mode != PdfTextRenderingMode.fill) {
291 - buf.putString('${mode!.index} Tr\n'); 301 + _buf.putString('${mode!.index} Tr\n');
292 } 302 }
293 } 303 }
294 304
@@ -307,22 +317,22 @@ class PdfGraphics { @@ -307,22 +317,22 @@ class PdfGraphics {
307 }) { 317 }) {
308 _page.addFont(font); 318 _page.addFont(font);
309 319
310 - buf.putString('BT ');  
311 - PdfNumList([x, y]).output(buf);  
312 - buf.putString(' Td '); 320 + _buf.putString('BT ');
  321 + PdfNumList([x, y]).output(_buf);
  322 + _buf.putString(' Td ');
313 setFont(font, size, 323 setFont(font, size,
314 charSpace: charSpace, 324 charSpace: charSpace,
315 mode: mode, 325 mode: mode,
316 rise: rise, 326 rise: rise,
317 scale: scale, 327 scale: scale,
318 wordSpace: wordSpace); 328 wordSpace: wordSpace);
319 - buf.putString('[');  
320 - font.putText(buf, s);  
321 - buf.putString(']TJ ET\n'); 329 + _buf.putString('[');
  330 + font.putText(_buf, s);
  331 + _buf.putString(']TJ ET\n');
322 } 332 }
323 333
324 void reset() { 334 void reset() {
325 - buf.putString('0 Tr\n'); 335 + _buf.putString('0 Tr\n');
326 } 336 }
327 337
328 /// Sets the color for drawing 338 /// Sets the color for drawing
@@ -335,11 +345,11 @@ class PdfGraphics { @@ -335,11 +345,11 @@ class PdfGraphics {
335 void setFillColor(PdfColor? color) { 345 void setFillColor(PdfColor? color) {
336 if (color is PdfColorCmyk) { 346 if (color is PdfColorCmyk) {
337 PdfNumList(<double>[color.cyan, color.magenta, color.yellow, color.black]) 347 PdfNumList(<double>[color.cyan, color.magenta, color.yellow, color.black])
338 - .output(buf);  
339 - buf.putString(' k\n'); 348 + .output(_buf);
  349 + _buf.putString(' k\n');
340 } else { 350 } else {
341 - PdfNumList(<double>[color!.red, color.green, color.blue]).output(buf);  
342 - buf.putString(' rg\n'); 351 + PdfNumList(<double>[color!.red, color.green, color.blue]).output(_buf);
  352 + _buf.putString(' rg\n');
343 } 353 }
344 } 354 }
345 355
@@ -347,11 +357,11 @@ class PdfGraphics { @@ -347,11 +357,11 @@ class PdfGraphics {
347 void setStrokeColor(PdfColor? color) { 357 void setStrokeColor(PdfColor? color) {
348 if (color is PdfColorCmyk) { 358 if (color is PdfColorCmyk) {
349 PdfNumList(<double>[color.cyan, color.magenta, color.yellow, color.black]) 359 PdfNumList(<double>[color.cyan, color.magenta, color.yellow, color.black])
350 - .output(buf);  
351 - buf.putString(' K\n'); 360 + .output(_buf);
  361 + _buf.putString(' K\n');
352 } else { 362 } else {
353 - PdfNumList(<double>[color!.red, color.green, color.blue]).output(buf);  
354 - buf.putString(' RG\n'); 363 + PdfNumList(<double>[color!.red, color.green, color.blue]).output(_buf);
  364 + _buf.putString(' RG\n');
355 } 365 }
356 } 366 }
357 367
@@ -359,27 +369,27 @@ class PdfGraphics { @@ -359,27 +369,27 @@ class PdfGraphics {
359 void setFillPattern(PdfPattern pattern) { 369 void setFillPattern(PdfPattern pattern) {
360 // The shader needs to be registered in the page resources 370 // The shader needs to be registered in the page resources
361 _page.addPattern(pattern); 371 _page.addPattern(pattern);
362 - buf.putString('/Pattern cs${pattern.name} scn\n'); 372 + _buf.putString('/Pattern cs${pattern.name} scn\n');
363 } 373 }
364 374
365 /// Sets the stroke pattern for drawing 375 /// Sets the stroke pattern for drawing
366 void setStrokePattern(PdfPattern pattern) { 376 void setStrokePattern(PdfPattern pattern) {
367 // The shader needs to be registered in the page resources 377 // The shader needs to be registered in the page resources
368 _page.addPattern(pattern); 378 _page.addPattern(pattern);
369 - buf.putString('/Pattern CS${pattern.name} SCN\n'); 379 + _buf.putString('/Pattern CS${pattern.name} SCN\n');
370 } 380 }
371 381
372 /// Set the graphic state for drawing 382 /// Set the graphic state for drawing
373 void setGraphicState(PdfGraphicState state) { 383 void setGraphicState(PdfGraphicState state) {
374 final name = _page.stateName(state); 384 final name = _page.stateName(state);
375 - buf.putString('$name gs\n'); 385 + _buf.putString('$name gs\n');
376 } 386 }
377 387
378 /// Set the transformation Matrix 388 /// Set the transformation Matrix
379 void setTransform(Matrix4 t) { 389 void setTransform(Matrix4 t) {
380 final s = t.storage; 390 final s = t.storage;
381 - PdfNumList(<double>[s[0], s[1], s[4], s[5], s[12], s[13]]).output(buf);  
382 - buf.putString(' cm\n'); 391 + PdfNumList(<double>[s[0], s[1], s[4], s[5], s[12], s[13]]).output(_buf);
  392 + _buf.putString(' cm\n');
383 _context.ctm.multiply(t); 393 _context.ctm.multiply(t);
384 } 394 }
385 395
@@ -390,14 +400,14 @@ class PdfGraphics { @@ -390,14 +400,14 @@ class PdfGraphics {
390 400
391 /// This adds a line segment to the current path 401 /// This adds a line segment to the current path
392 void lineTo(double x, double y) { 402 void lineTo(double x, double y) {
393 - PdfNumList([x, y]).output(buf);  
394 - buf.putString(' l\n'); 403 + PdfNumList([x, y]).output(_buf);
  404 + _buf.putString(' l\n');
395 } 405 }
396 406
397 /// This moves the current drawing point. 407 /// This moves the current drawing point.
398 void moveTo(double x, double y) { 408 void moveTo(double x, double y) {
399 - PdfNumList([x, y]).output(buf);  
400 - buf.putString(' m\n'); 409 + PdfNumList([x, y]).output(_buf);
  410 + _buf.putString(' m\n');
401 } 411 }
402 412
403 /// Draw a cubic bézier curve from the current point to (x3,y3) 413 /// Draw a cubic bézier curve from the current point to (x3,y3)
@@ -405,8 +415,8 @@ class PdfGraphics { @@ -405,8 +415,8 @@ class PdfGraphics {
405 /// and (x2,y2) as the control point at the end of the curve. 415 /// and (x2,y2) as the control point at the end of the curve.
406 void curveTo( 416 void curveTo(
407 double x1, double y1, double x2, double y2, double x3, double y3) { 417 double x1, double y1, double x2, double y2, double x3, double y3) {
408 - PdfNumList([x1, y1, x2, y2, x3, y3]).output(buf);  
409 - buf.putString(' c\n'); 418 + PdfNumList([x1, y1, x2, y2, x3, y3]).output(_buf);
  419 + _buf.putString(' c\n');
410 } 420 }
411 421
412 double _vectorAngle(double ux, double uy, double vx, double vy) { 422 double _vectorAngle(double ux, double uy, double vx, double vy) {
@@ -566,25 +576,25 @@ class PdfGraphics { @@ -566,25 +576,25 @@ class PdfGraphics {
566 576
567 /// Set line starting and ending cap type 577 /// Set line starting and ending cap type
568 void setLineCap(PdfLineCap cap) { 578 void setLineCap(PdfLineCap cap) {
569 - buf.putString('${cap.index} J\n'); 579 + _buf.putString('${cap.index} J\n');
570 } 580 }
571 581
572 /// Set line join type 582 /// Set line join type
573 void setLineJoin(PdfLineJoin join) { 583 void setLineJoin(PdfLineJoin join) {
574 - buf.putString('${join.index} j\n'); 584 + _buf.putString('${join.index} j\n');
575 } 585 }
576 586
577 /// Set line width 587 /// Set line width
578 void setLineWidth(double width) { 588 void setLineWidth(double width) {
579 - PdfNum(width).output(buf);  
580 - buf.putString(' w\n'); 589 + PdfNum(width).output(_buf);
  590 + _buf.putString(' w\n');
581 } 591 }
582 592
583 /// Set line joint miter limit, applies if the 593 /// Set line joint miter limit, applies if the
584 void setMiterLimit(double limit) { 594 void setMiterLimit(double limit) {
585 assert(limit >= 1.0); 595 assert(limit >= 1.0);
586 - PdfNum(limit).output(buf);  
587 - buf.putString(' M\n'); 596 + PdfNum(limit).output(_buf);
  597 + _buf.putString(' M\n');
588 } 598 }
589 599
590 /// The dash array shall be cycled through, adding up the lengths of dashes and gaps. 600 /// The dash array shall be cycled through, adding up the lengths of dashes and gaps.
@@ -592,8 +602,17 @@ class PdfGraphics { @@ -592,8 +602,17 @@ class PdfGraphics {
592 /// 602 ///
593 /// Example: [2 1] will create a dash pattern with 2 on, 1 off, 2 on, 1 off, ... 603 /// Example: [2 1] will create a dash pattern with 2 on, 1 off, 2 on, 1 off, ...
594 void setLineDashPattern([List<num> array = const <num>[], int phase = 0]) { 604 void setLineDashPattern([List<num> array = const <num>[], int phase = 0]) {
595 - PdfArray.fromNum(array).output(buf);  
596 - buf.putString(' $phase d\n'); 605 + PdfArray.fromNum(array).output(_buf);
  606 + _buf.putString(' $phase d\n');
  607 + }
  608 +
  609 + void markContentBegin(PdfName tag) {
  610 + tag.output(_buf);
  611 + _buf.putString(' BMC\n');
  612 + }
  613 +
  614 + void markContentEnd() {
  615 + _buf.putString('EMC\n');
597 } 616 }
598 } 617 }
599 618
@@ -2,20 +2,21 @@ @@ -2,20 +2,21 @@
2 2
3 import 'dart:math'; 3 import 'dart:math';
4 4
  5 +import 'package:meta/meta.dart';
5 import 'package:pdf/pdf.dart'; 6 import 'package:pdf/pdf.dart';
6 import 'package:pdf/widgets.dart'; 7 import 'package:pdf/widgets.dart';
7 import 'package:vector_math/vector_math_64.dart'; 8 import 'package:vector_math/vector_math_64.dart';
8 9
9 class PieGrid extends ChartGrid { 10 class PieGrid extends ChartGrid {
10 - PieGrid(); 11 + PieGrid({this.startAngle = 0});
11 12
12 - late PdfRect gridBox; 13 + /// Start angle for the first [PieDataSet]
  14 + final double startAngle;
13 15
14 - late double total; 16 + late double _radius;
15 17
16 - late double unit;  
17 -  
18 - late double pieSize; 18 + /// Nominal radius of a pie slice
  19 + double get radius => _radius;
19 20
20 @override 21 @override
21 void layout(Context context, BoxConstraints constraints, 22 void layout(Context context, BoxConstraints constraints,
@@ -25,19 +26,19 @@ class PieGrid extends ChartGrid { @@ -25,19 +26,19 @@ class PieGrid extends ChartGrid {
25 final datasets = Chart.of(context).datasets; 26 final datasets = Chart.of(context).datasets;
26 final size = constraints.biggest; 27 final size = constraints.biggest;
27 28
28 - gridBox = PdfRect(0, 0, size.x, size.y); 29 + final _gridBox = PdfRect(0, 0, size.x, size.y);
29 30
30 - total = 0.0; 31 + var _total = 0.0;
31 32
32 for (final dataset in datasets) { 33 for (final dataset in datasets) {
33 - assert(dataset is PieDataSet, 'Use only PieDataSet with a PieGrid'); 34 + assert(dataset is PieDataSet, 'Use only PieDataset with a PieGrid');
34 if (dataset is PieDataSet) { 35 if (dataset is PieDataSet) {
35 - total += dataset.value; 36 + _total += dataset.value;
36 } 37 }
37 } 38 }
38 39
39 - unit = pi / total * 2;  
40 - var angle = 0.0; 40 + final unit = pi / _total * 2;
  41 + var angle = startAngle;
41 42
42 for (final dataset in datasets) { 43 for (final dataset in datasets) {
43 if (dataset is PieDataSet) { 44 if (dataset is PieDataSet) {
@@ -47,19 +48,19 @@ class PieGrid extends ChartGrid { @@ -47,19 +48,19 @@ class PieGrid extends ChartGrid {
47 } 48 }
48 } 49 }
49 50
50 - pieSize = min(gridBox.width / 2, gridBox.height / 2); 51 + _radius = min(_gridBox.width / 2, _gridBox.height / 2);
51 var reduce = false; 52 var reduce = false;
52 53
53 do { 54 do {
54 reduce = false; 55 reduce = false;
55 for (final dataset in datasets) { 56 for (final dataset in datasets) {
56 if (dataset is PieDataSet) { 57 if (dataset is PieDataSet) {
57 - dataset.layout(context, BoxConstraints.tight(gridBox.size)); 58 + dataset.layout(context, BoxConstraints.tight(_gridBox.size));
58 assert(dataset.box != null); 59 assert(dataset.box != null);
59 - if (pieSize > 20 &&  
60 - (dataset.box!.width > gridBox.width ||  
61 - dataset.box!.height > gridBox.height)) {  
62 - pieSize -= 10; 60 + if (_radius > 20 &&
  61 + (dataset.box!.width > _gridBox.width ||
  62 + dataset.box!.height > _gridBox.height)) {
  63 + _radius -= 10;
63 reduce = true; 64 reduce = true;
64 break; 65 break;
65 } 66 }
@@ -129,7 +130,10 @@ class PieDataSet extends Dataset { @@ -129,7 +130,10 @@ class PieDataSet extends Dataset {
129 PdfColor? legendLineColor, 130 PdfColor? legendLineColor,
130 Widget? legendWidget, 131 Widget? legendWidget,
131 this.legendOffset = 20, 132 this.legendOffset = 20,
132 - }) : drawBorder = drawBorder ?? borderColor != null && color != borderColor, 133 + this.innerRadius = 0,
  134 + }) : assert(innerRadius >= 0),
  135 + assert(offset >= 0),
  136 + drawBorder = drawBorder ?? borderColor != null && color != borderColor,
133 assert((drawBorder ?? borderColor != null && color != borderColor) || 137 assert((drawBorder ?? borderColor != null && color != borderColor) ||
134 drawSurface), 138 drawSurface),
135 _legendWidget = legendWidget, 139 _legendWidget = legendWidget,
@@ -168,6 +172,8 @@ class PieDataSet extends Dataset { @@ -168,6 +172,8 @@ class PieDataSet extends Dataset {
168 172
169 final PdfColor legendLineColor; 173 final PdfColor legendLineColor;
170 174
  175 + final double innerRadius;
  176 +
171 PdfPoint? _legendAnchor; 177 PdfPoint? _legendAnchor;
172 PdfPoint? _legendPivot; 178 PdfPoint? _legendPivot;
173 PdfPoint? _legendStart; 179 PdfPoint? _legendStart;
@@ -180,7 +186,7 @@ class PieDataSet extends Dataset { @@ -180,7 +186,7 @@ class PieDataSet extends Dataset {
180 final _offset = _isFullCircle ? 0 : offset; 186 final _offset = _isFullCircle ? 0 : offset;
181 187
182 final grid = Chart.of(context).grid as PieGrid; 188 final grid = Chart.of(context).grid as PieGrid;
183 - final len = grid.pieSize + _offset; 189 + final len = grid.radius + _offset;
184 var x = -len; 190 var x = -len;
185 var y = -len; 191 var y = -len;
186 var w = len * 2; 192 var w = len * 2;
@@ -217,7 +223,7 @@ class PieDataSet extends Dataset { @@ -217,7 +223,7 @@ class PieDataSet extends Dataset {
217 223
218 if (_legendWidget != null) { 224 if (_legendWidget != null) {
219 _legendWidget!.layout(context, 225 _legendWidget!.layout(context,
220 - BoxConstraints(maxWidth: grid.pieSize, maxHeight: grid.pieSize)); 226 + BoxConstraints(maxWidth: grid.radius, maxHeight: grid.radius));
221 assert(_legendWidget!.box != null); 227 assert(_legendWidget!.box != null);
222 228
223 final ls = _legendWidget!.box!.size; 229 final ls = _legendWidget!.box!.size;
@@ -226,13 +232,13 @@ class PieDataSet extends Dataset { @@ -226,13 +232,13 @@ class PieDataSet extends Dataset {
226 232
227 switch (lp) { 233 switch (lp) {
228 case PieLegendPosition.outside: 234 case PieLegendPosition.outside:
229 - final o = grid.pieSize + legendOffset; 235 + final o = grid.radius + legendOffset;
230 final cx = sin(bisect) * (_offset + o); 236 final cx = sin(bisect) * (_offset + o);
231 final cy = cos(bisect) * (_offset + o); 237 final cy = cos(bisect) * (_offset + o);
232 238
233 _legendStart = PdfPoint( 239 _legendStart = PdfPoint(
234 - sin(bisect) * (_offset + grid.pieSize + legendOffset * 0.1),  
235 - cos(bisect) * (_offset + grid.pieSize + legendOffset * 0.1), 240 + sin(bisect) * (_offset + grid.radius + legendOffset * 0.1),
  241 + cos(bisect) * (_offset + grid.radius + legendOffset * 0.1),
236 ); 242 );
237 243
238 _legendPivot = PdfPoint(cx, cy); 244 _legendPivot = PdfPoint(cx, cy);
@@ -269,9 +275,23 @@ class PieDataSet extends Dataset { @@ -269,9 +275,23 @@ class PieDataSet extends Dataset {
269 } 275 }
270 break; 276 break;
271 case PieLegendPosition.inside: 277 case PieLegendPosition.inside:
272 - final o = _isFullCircle ? 0 : grid.pieSize * 2 / 3;  
273 - final cx = sin(bisect) * (_offset + o);  
274 - final cy = cos(bisect) * (_offset + o); 278 + final double o;
  279 + final double cx;
  280 + final double cy;
  281 + if (innerRadius == 0) {
  282 + o = _isFullCircle ? 0 : grid.radius * 2 / 3;
  283 + cx = sin(bisect) * (_offset + o);
  284 + cy = cos(bisect) * (_offset + o);
  285 + } else {
  286 + o = (grid.radius + innerRadius) / 2;
  287 + if (_isFullCircle) {
  288 + cx = 0;
  289 + cy = o;
  290 + } else {
  291 + cx = sin(bisect) * (_offset + o);
  292 + cy = cos(bisect) * (_offset + o);
  293 + }
  294 + }
275 _legendWidget!.box = PdfRect.fromPoints( 295 _legendWidget!.box = PdfRect.fromPoints(
276 PdfPoint( 296 PdfPoint(
277 cx - ls.x / 2, 297 cx - ls.x / 2,
@@ -287,7 +307,7 @@ class PieDataSet extends Dataset { @@ -287,7 +307,7 @@ class PieDataSet extends Dataset {
287 box = PdfRect(x, y, w, h); 307 box = PdfRect(x, y, w, h);
288 } 308 }
289 309
290 - void _shape(Context context) { 310 + void _paintSliceShape(Context context) {
291 final grid = Chart.of(context).grid as PieGrid; 311 final grid = Chart.of(context).grid as PieGrid;
292 312
293 final bisect = (angleStart + angleEnd) / 2; 313 final bisect = (angleStart + angleEnd) / 2;
@@ -295,28 +315,69 @@ class PieDataSet extends Dataset { @@ -295,28 +315,69 @@ class PieDataSet extends Dataset {
295 final cx = sin(bisect) * offset; 315 final cx = sin(bisect) * offset;
296 final cy = cos(bisect) * offset; 316 final cy = cos(bisect) * offset;
297 317
298 - final sx = cx + sin(angleStart) * grid.pieSize;  
299 - final sy = cy + cos(angleStart) * grid.pieSize;  
300 - final ex = cx + sin(angleEnd) * grid.pieSize;  
301 - final ey = cy + cos(angleEnd) * grid.pieSize; 318 + final sx = cx + sin(angleStart) * grid.radius;
  319 + final sy = cy + cos(angleStart) * grid.radius;
  320 + final ex = cx + sin(angleEnd) * grid.radius;
  321 + final ey = cy + cos(angleEnd) * grid.radius;
302 322
303 if (_isFullCircle) { 323 if (_isFullCircle) {
304 - context.canvas.drawEllipse(0, 0, grid.pieSize, grid.pieSize); 324 + context.canvas.drawEllipse(0, 0, grid.radius, grid.radius);
305 } else { 325 } else {
306 context.canvas 326 context.canvas
307 ..moveTo(cx, cy) 327 ..moveTo(cx, cy)
308 ..lineTo(sx, sy) 328 ..lineTo(sx, sy)
309 - ..bezierArc(sx, sy, grid.pieSize, grid.pieSize, ex, ey, 329 + ..bezierArc(sx, sy, grid.radius, grid.radius, ex, ey,
310 large: angleEnd - angleStart > pi); 330 large: angleEnd - angleStart > pi);
311 } 331 }
312 } 332 }
313 333
  334 + void _paintDonnutShape(Context context) {
  335 + final grid = Chart.of(context).grid as PieGrid;
  336 +
  337 + final bisect = (angleStart + angleEnd) / 2;
  338 +
  339 + final cx = sin(bisect) * offset;
  340 + final cy = cos(bisect) * offset;
  341 +
  342 + final stx = cx + sin(angleStart) * grid.radius;
  343 + final sty = cy + cos(angleStart) * grid.radius;
  344 + final etx = cx + sin(angleEnd) * grid.radius;
  345 + final ety = cy + cos(angleEnd) * grid.radius;
  346 + final sbx = cx + sin(angleStart) * innerRadius;
  347 + final sby = cy + cos(angleStart) * innerRadius;
  348 + final ebx = cx + sin(angleEnd) * innerRadius;
  349 + final eby = cy + cos(angleEnd) * innerRadius;
  350 +
  351 + if (_isFullCircle) {
  352 + context.canvas.drawEllipse(0, 0, grid.radius, grid.radius);
  353 + context.canvas
  354 + .drawEllipse(0, 0, innerRadius, innerRadius, clockwise: false);
  355 + } else {
  356 + context.canvas
  357 + ..moveTo(stx, sty)
  358 + ..bezierArc(stx, sty, grid.radius, grid.radius, etx, ety,
  359 + large: angleEnd - angleStart > pi)
  360 + ..lineTo(ebx, eby)
  361 + ..bezierArc(ebx, eby, innerRadius, innerRadius, sbx, sby,
  362 + large: angleEnd - angleStart > pi, sweep: true)
  363 + ..lineTo(stx, sty);
  364 + }
  365 + }
  366 +
  367 + void _paintShape(Context context) {
  368 + if (innerRadius == 0) {
  369 + _paintSliceShape(context);
  370 + } else {
  371 + _paintDonnutShape(context);
  372 + }
  373 + }
  374 +
314 @override 375 @override
315 void paintBackground(Context context) { 376 void paintBackground(Context context) {
316 super.paint(context); 377 super.paint(context);
317 378
318 if (drawSurface) { 379 if (drawSurface) {
319 - _shape(context); 380 + _paintShape(context);
320 if (surfaceOpacity != 1) { 381 if (surfaceOpacity != 1) {
321 context.canvas 382 context.canvas
322 ..saveContext() 383 ..saveContext()
@@ -340,7 +401,7 @@ class PieDataSet extends Dataset { @@ -340,7 +401,7 @@ class PieDataSet extends Dataset {
340 super.paint(context); 401 super.paint(context);
341 402
342 if (drawBorder) { 403 if (drawBorder) {
343 - _shape(context); 404 + _paintShape(context);
344 context.canvas 405 context.canvas
345 ..setLineWidth(borderWidth) 406 ..setLineWidth(borderWidth)
346 ..setLineJoin(PdfLineJoin.round) 407 ..setLineJoin(PdfLineJoin.round)
@@ -349,6 +410,7 @@ class PieDataSet extends Dataset { @@ -349,6 +410,7 @@ class PieDataSet extends Dataset {
349 } 410 }
350 } 411 }
351 412
  413 + @protected
352 void paintLegend(Context context) { 414 void paintLegend(Context context) {
353 if (legendPosition != PieLegendPosition.none && _legendWidget != null) { 415 if (legendPosition != PieLegendPosition.none && _legendWidget != null) {
354 if (_legendAnchor != null && 416 if (_legendAnchor != null &&
@@ -379,8 +441,8 @@ class PieDataSet extends Dataset { @@ -379,8 +441,8 @@ class PieDataSet extends Dataset {
379 441
380 final bisect = (angleStart + angleEnd) / 2; 442 final bisect = (angleStart + angleEnd) / 2;
381 443
382 - final cx = sin(bisect) * (offset + grid.pieSize + legendOffset);  
383 - final cy = cos(bisect) * (offset + grid.pieSize + legendOffset); 444 + final cx = sin(bisect) * (offset + grid.radius + legendOffset);
  445 + final cy = cos(bisect) * (offset + grid.radius + legendOffset);
384 446
385 if (_legendWidget != null) { 447 if (_legendWidget != null) {
386 context.canvas 448 context.canvas
@@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
17 import 'dart:typed_data'; 17 import 'dart:typed_data';
18 18
19 import 'package:pdf/pdf.dart'; 19 import 'package:pdf/pdf.dart';
  20 +import 'package:pdf/src/pdf/data_types.dart';
20 import 'package:vector_math/vector_math_64.dart'; 21 import 'package:vector_math/vector_math_64.dart';
21 22
22 import 'basic.dart'; 23 import 'basic.dart';
@@ -294,7 +295,7 @@ class TextField extends StatelessWidget { @@ -294,7 +295,7 @@ class TextField extends StatelessWidget {
294 if (value != null) { 295 if (value != null) {
295 final canvas = tf.appearance(context.document, PdfAnnotAppearance.normal, 296 final canvas = tf.appearance(context.document, PdfAnnotAppearance.normal,
296 matrix: mat, boundingBox: box); 297 matrix: mat, boundingBox: box);
297 - canvas.buf.putString('/Tx BMC\n'); 298 + canvas.markContentBegin(const PdfName('/Tx'));
298 Widget.draw( 299 Widget.draw(
299 Text(value!, style: _textStyle), 300 Text(value!, style: _textStyle),
300 offset: PdfPoint.zero, 301 offset: PdfPoint.zero,
@@ -303,7 +304,7 @@ class TextField extends StatelessWidget { @@ -303,7 +304,7 @@ class TextField extends StatelessWidget {
303 constraints: 304 constraints:
304 BoxConstraints.tightFor(width: box!.width, height: box!.height), 305 BoxConstraints.tightFor(width: box!.width, height: box!.height),
305 ); 306 );
306 - canvas.buf.putString('EMC\n'); 307 + canvas.markContentEnd();
307 } 308 }
308 309
309 PdfAnnot(context.page, tf); 310 PdfAnnot(context.page, tf);
@@ -16,9 +16,9 @@ @@ -16,9 +16,9 @@
16 16
17 import 'dart:io'; 17 import 'dart:io';
18 18
19 -import 'package:test/test.dart';  
20 import 'package:pdf/pdf.dart'; 19 import 'package:pdf/pdf.dart';
21 import 'package:pdf/widgets.dart'; 20 import 'package:pdf/widgets.dart';
  21 +import 'package:test/test.dart';
22 22
23 late Document pdf; 23 late Document pdf;
24 24
@@ -213,6 +213,83 @@ void main() { @@ -213,6 +213,83 @@ void main() {
213 }); 213 });
214 }); 214 });
215 215
  216 + test('Standard PieChart', () {
  217 + const data = <String, double>{
  218 + 'Wind': 8.4,
  219 + 'Hydro': 7.4,
  220 + 'Solar': 2.4,
  221 + 'Biomass': 1.4,
  222 + 'Geothermal': 0.4,
  223 + 'Nuclear': 20,
  224 + 'Coal': 19,
  225 + 'Petroleum': 1,
  226 + 'Natural gas': 40,
  227 + };
  228 + var color = 0;
  229 +
  230 + pdf.addPage(
  231 + Page(
  232 + pageFormat: PdfPageFormat.standard.landscape,
  233 + build: (Context context) => Chart(
  234 + title: Text('Sources of U.S. electricity generation, 2020'),
  235 + grid: PieGrid(),
  236 + datasets: [
  237 + for (final item in data.entries)
  238 + PieDataSet(
  239 + legend: item.key,
  240 + value: item.value,
  241 + color: PdfColors
  242 + .primaries[(color++) * 4 % PdfColors.primaries.length],
  243 + offset: color == 6 ? 30 : 0,
  244 + ),
  245 + ],
  246 + ),
  247 + ),
  248 + );
  249 + });
  250 +
  251 + test('Donnuts PieChart', () {
  252 + const internalRadius = 150.0;
  253 + const data = <String, int>{
  254 + 'Dogs': 5528,
  255 + 'Birds': 2211,
  256 + 'Rabbits': 3216,
  257 + 'Ermine': 740,
  258 + 'Cats': 8241,
  259 + };
  260 + var color = 0;
  261 + final total = data.values.fold<int>(0, (v, e) => v + e);
  262 +
  263 + pdf.addPage(
  264 + Page(
  265 + pageFormat: PdfPageFormat.standard.landscape,
  266 + build: (Context context) => Stack(
  267 + alignment: Alignment.center,
  268 + children: [
  269 + Text(
  270 + 'Pets',
  271 + textAlign: TextAlign.center,
  272 + style: const TextStyle(fontSize: 30),
  273 + ),
  274 + Chart(
  275 + grid: PieGrid(startAngle: 1),
  276 + datasets: [
  277 + for (final item in data.entries)
  278 + PieDataSet(
  279 + legend: '${item.key} ${item.value * 100 ~/ total}%',
  280 + value: item.value,
  281 + color: PdfColors
  282 + .primaries[(color++) * 2 % PdfColors.primaries.length],
  283 + innerRadius: internalRadius,
  284 + ),
  285 + ],
  286 + ),
  287 + ],
  288 + ),
  289 + ),
  290 + );
  291 + });
  292 +
216 tearDownAll(() async { 293 tearDownAll(() async {
217 final file = File('widgets-chart.pdf'); 294 final file = File('widgets-chart.pdf');
218 await file.writeAsBytes(await pdf.save()); 295 await file.writeAsBytes(await pdf.save());