David PHAM-VAN

Improve PieChart

@@ -35,7 +35,6 @@ class ChartLegend extends StatelessWidget { @@ -35,7 +35,6 @@ 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,  
39 }); 38 });
40 39
41 final TextStyle? textStyle; 40 final TextStyle? textStyle;
@@ -48,8 +47,6 @@ class ChartLegend extends StatelessWidget { @@ -48,8 +47,6 @@ class ChartLegend extends StatelessWidget {
48 47
49 final EdgeInsets padding; 48 final EdgeInsets padding;
50 49
51 - final double maxWidth;  
52 -  
53 Widget _buildLegend(Context context, Dataset dataset) { 50 Widget _buildLegend(Context context, Dataset dataset) {
54 final style = Theme.of(context).defaultTextStyle.merge(textStyle); 51 final style = Theme.of(context).defaultTextStyle.merge(textStyle);
55 52
@@ -62,13 +59,9 @@ class ChartLegend extends StatelessWidget { @@ -62,13 +59,9 @@ class ChartLegend extends StatelessWidget {
62 margin: const EdgeInsets.only(right: 5), 59 margin: const EdgeInsets.only(right: 5),
63 child: dataset.legendShape(), 60 child: dataset.legendShape(),
64 ), 61 ),
65 - ConstrainedBox(  
66 - constraints: BoxConstraints(maxWidth: maxWidth),  
67 - child: Text( 62 + Text(
68 dataset.legend!, 63 dataset.legend!,
69 style: textStyle, 64 style: textStyle,
70 - softWrap: false,  
71 - ),  
72 ), 65 ),
73 ], 66 ],
74 ); 67 );
@@ -82,6 +75,9 @@ class ChartLegend extends StatelessWidget { @@ -82,6 +75,9 @@ class ChartLegend extends StatelessWidget {
82 direction: direction, 75 direction: direction,
83 spacing: 10, 76 spacing: 10,
84 runSpacing: 10, 77 runSpacing: 10,
  78 + crossAxisAlignment: direction == Axis.horizontal
  79 + ? WrapCrossAlignment.center
  80 + : WrapCrossAlignment.start,
85 children: <Widget>[ 81 children: <Widget>[
86 for (final Dataset dataset in datasets) 82 for (final Dataset dataset in datasets)
87 if (dataset.legend != null) _buildLegend(context, dataset) 83 if (dataset.legend != null) _buildLegend(context, dataset)
@@ -109,7 +109,7 @@ class PieGrid extends ChartGrid { @@ -109,7 +109,7 @@ class PieGrid extends ChartGrid {
109 } 109 }
110 } 110 }
111 111
112 -enum PieLegendPosition { none, auto, inside } 112 +enum PieLegendPosition { none, auto, inside, outside }
113 113
114 class PieDataSet extends Dataset { 114 class PieDataSet extends Dataset {
115 PieDataSet({ 115 PieDataSet({
@@ -123,16 +123,22 @@ class PieDataSet extends Dataset { @@ -123,16 +123,22 @@ class PieDataSet extends Dataset {
123 this.surfaceOpacity = 1, 123 this.surfaceOpacity = 1,
124 this.offset = 0, 124 this.offset = 0,
125 this.legendStyle, 125 this.legendStyle,
  126 + this.legendAlign,
126 this.legendPosition = PieLegendPosition.auto, 127 this.legendPosition = PieLegendPosition.auto,
  128 + this.legendLineWidth = 1.0,
  129 + this.legendLineColor = PdfColors.black,
  130 + Widget? legendWidget,
  131 + this.legendOffset = 20,
127 }) : drawBorder = drawBorder ?? borderColor != null && color != borderColor, 132 }) : drawBorder = drawBorder ?? borderColor != null && color != borderColor,
128 assert((drawBorder ?? borderColor != null && color != borderColor) || 133 assert((drawBorder ?? borderColor != null && color != borderColor) ||
129 drawSurface), 134 drawSurface),
  135 + _legendWidget = legendWidget,
130 super( 136 super(
131 legend: legend, 137 legend: legend,
132 color: color, 138 color: color,
133 ); 139 );
134 140
135 - final double value; 141 + final num value;
136 142
137 late double angleStart; 143 late double angleStart;
138 144
@@ -150,18 +156,124 @@ class PieDataSet extends Dataset { @@ -150,18 +156,124 @@ class PieDataSet extends Dataset {
150 156
151 final TextStyle? legendStyle; 157 final TextStyle? legendStyle;
152 158
  159 + final TextAlign? legendAlign;
153 final PieLegendPosition legendPosition; 160 final PieLegendPosition legendPosition;
154 161
  162 + Widget? _legendWidget;
  163 +
  164 + final double legendOffset;
  165 +
  166 + final double legendLineWidth;
  167 +
  168 + final PdfColor legendLineColor;
  169 +
  170 + PdfPoint? _legendAnchor;
  171 + PdfPoint? _legendPivot;
  172 + PdfPoint? _legendStart;
  173 +
  174 + bool get _isFullCircle => angleEnd - angleStart >= pi * 2;
  175 +
155 @override 176 @override
156 void layout(Context context, BoxConstraints constraints, 177 void layout(Context context, BoxConstraints constraints,
157 {bool parentUsesSize = false}) { 178 {bool parentUsesSize = false}) {
158 - // final size = constraints.biggest; 179 + final _offset = _isFullCircle ? 0 : offset;
159 180
160 // ignore: avoid_as 181 // ignore: avoid_as
161 final grid = Chart.of(context).grid as PieGrid; 182 final grid = Chart.of(context).grid as PieGrid;
162 - final len = grid.pieSize + offset; 183 + final len = grid.pieSize + _offset;
  184 + var x = -len;
  185 + var y = -len;
  186 + var w = len * 2;
  187 + var h = len * 2;
  188 +
  189 + final lp = legendPosition == PieLegendPosition.auto
  190 + ? (angleEnd - angleStart > pi / 6
  191 + ? PieLegendPosition.inside
  192 + : PieLegendPosition.outside)
  193 + : legendPosition;
  194 +
  195 + // Find the legend position
  196 + final bisect = _isFullCircle ? 1 / 4 * pi : (angleStart + angleEnd) / 2;
  197 +
  198 + final _legendAlign = legendAlign ??
  199 + (lp == PieLegendPosition.inside
  200 + ? TextAlign.center
  201 + : (bisect > pi ? TextAlign.right : TextAlign.left));
  202 +
  203 + _legendWidget ??= legend == null
  204 + ? null
  205 + : Text(legend!, style: legendStyle, textAlign: _legendAlign);
  206 +
  207 + if (_legendWidget != null) {
  208 + _legendWidget!.layout(context,
  209 + BoxConstraints(maxWidth: grid.pieSize, maxHeight: grid.pieSize));
  210 + assert(_legendWidget!.box != null);
  211 +
  212 + final ls = _legendWidget!.box!.size;
  213 +
  214 + // final style = Theme.of(context).defaultTextStyle.merge(legendStyle);
  215 +
  216 + switch (lp) {
  217 + case PieLegendPosition.outside:
  218 + final o = grid.pieSize + legendOffset;
  219 + final cx = sin(bisect) * (_offset + o);
  220 + final cy = cos(bisect) * (_offset + o);
  221 +
  222 + _legendStart = PdfPoint(
  223 + sin(bisect) * (_offset + grid.pieSize + legendOffset * 0.1),
  224 + cos(bisect) * (_offset + grid.pieSize + legendOffset * 0.1),
  225 + );
  226 +
  227 + _legendPivot = PdfPoint(cx, cy);
  228 + if (bisect > pi) {
  229 + _legendAnchor = PdfPoint(
  230 + cx - legendOffset / 2 * 0.8,
  231 + cy,
  232 + );
  233 + _legendWidget!.box = PdfRect.fromPoints(
  234 + PdfPoint(
  235 + cx - legendOffset / 2 - ls.x,
  236 + cy - ls.y / 2,
  237 + ),
  238 + ls);
  239 + w = max(w, (-cx + legendOffset / 2 + ls.x) * 2);
  240 + h = max(h, cy.abs() * 2 + ls.y);
  241 + x = -w / 2;
  242 + y = -h / 2;
  243 + } else {
  244 + _legendAnchor = PdfPoint(
  245 + cx + legendOffset / 2 * 0.8,
  246 + cy,
  247 + );
  248 + _legendWidget!.box = PdfRect.fromPoints(
  249 + PdfPoint(
  250 + cx + legendOffset / 2,
  251 + cy - ls.y / 2,
  252 + ),
  253 + ls);
  254 + w = max(w, (cx + legendOffset / 2 + ls.x) * 2);
  255 + h = max(h, cy.abs() * 2 + ls.y);
  256 + x = -w / 2;
  257 + y = -h / 2;
  258 + }
  259 + break;
  260 + case PieLegendPosition.inside:
  261 + final o = _isFullCircle ? 0 : grid.pieSize * 2 / 3;
  262 + final cx = sin(bisect) * (_offset + o);
  263 + final cy = cos(bisect) * (_offset + o);
  264 + _legendWidget!.box = PdfRect.fromPoints(
  265 + PdfPoint(
  266 + cx - ls.x / 2,
  267 + cy - ls.y / 2,
  268 + ),
  269 + ls);
  270 + break;
  271 + default:
  272 + break;
  273 + }
  274 + }
163 275
164 - box = PdfRect(-len, -len, len * 2, len * 2); 276 + box = PdfRect(x, y, w, h);
165 } 277 }
166 278
167 void _shape(Context context) { 279 void _shape(Context context) {
@@ -178,12 +290,16 @@ class PieDataSet extends Dataset { @@ -178,12 +290,16 @@ class PieDataSet extends Dataset {
178 final ex = cx + sin(angleEnd) * grid.pieSize; 290 final ex = cx + sin(angleEnd) * grid.pieSize;
179 final ey = cy + cos(angleEnd) * grid.pieSize; 291 final ey = cy + cos(angleEnd) * grid.pieSize;
180 292
  293 + if (_isFullCircle) {
  294 + context.canvas.drawEllipse(0, 0, grid.pieSize, grid.pieSize);
  295 + } else {
181 context.canvas 296 context.canvas
182 ..moveTo(cx, cy) 297 ..moveTo(cx, cy)
183 ..lineTo(sx, sy) 298 ..lineTo(sx, sy)
184 ..bezierArc(sx, sy, grid.pieSize, grid.pieSize, ex, ey, 299 ..bezierArc(sx, sy, grid.pieSize, grid.pieSize, ex, ey,
185 large: angleEnd - angleStart > pi); 300 large: angleEnd - angleStart > pi);
186 } 301 }
  302 + }
187 303
188 @override 304 @override
189 void paintBackground(Context context) { 305 void paintBackground(Context context) {
@@ -224,23 +340,49 @@ class PieDataSet extends Dataset { @@ -224,23 +340,49 @@ class PieDataSet extends Dataset {
224 } 340 }
225 341
226 void paintLegend(Context context) { 342 void paintLegend(Context context) {
227 - if (legendPosition != PieLegendPosition.none && legend != null) { 343 + if (legendPosition != PieLegendPosition.none && _legendWidget != null) {
  344 + if (_legendAnchor != null &&
  345 + _legendPivot != null &&
  346 + _legendStart != null) {
  347 + context.canvas
  348 + ..saveContext()
  349 + ..moveTo(_legendStart!.x, _legendStart!.y)
  350 + ..lineTo(_legendPivot!.x, _legendPivot!.y)
  351 + ..lineTo(_legendAnchor!.x, _legendAnchor!.y)
  352 + ..setLineWidth(legendLineWidth)
  353 + ..setLineCap(PdfLineCap.round)
  354 + ..setLineJoin(PdfLineJoin.round)
  355 + ..setStrokeColor(legendLineColor)
  356 + ..strokePath()
  357 + ..restoreContext();
  358 + }
  359 +
  360 + _legendWidget!.paint(context);
  361 + }
  362 + }
  363 +
  364 + @override
  365 + void debugPaint(Context context) {
  366 + super.debugPaint(context);
  367 +
228 // ignore: avoid_as 368 // ignore: avoid_as
229 final grid = Chart.of(context).grid as PieGrid; 369 final grid = Chart.of(context).grid as PieGrid;
230 370
231 final bisect = (angleStart + angleEnd) / 2; 371 final bisect = (angleStart + angleEnd) / 2;
232 372
233 - final o = grid.pieSize * 2 / 3;  
234 - final cx = sin(bisect) * (offset + o);  
235 - final cy = cos(bisect) * (offset + o); 373 + final cx = sin(bisect) * (offset + grid.pieSize + legendOffset);
  374 + final cy = cos(bisect) * (offset + grid.pieSize + legendOffset);
236 375
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 - ); 376 + if (_legendWidget != null) {
  377 + context.canvas
  378 + ..saveContext()
  379 + ..moveTo(0, 0)
  380 + ..lineTo(cx, cy)
  381 + ..setLineWidth(0.5)
  382 + ..setLineDashPattern([3, 1])
  383 + ..setStrokeColor(PdfColors.blue)
  384 + ..strokePath()
  385 + ..restoreContext();
244 } 386 }
245 } 387 }
246 } 388 }