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(  
68 - dataset.legend!,  
69 - style: textStyle,  
70 - softWrap: false,  
71 - ), 62 + Text(
  63 + dataset.legend!,
  64 + style: textStyle,
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,11 +290,15 @@ class PieDataSet extends Dataset { @@ -178,11 +290,15 @@ 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
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); 293 + if (_isFullCircle) {
  294 + context.canvas.drawEllipse(0, 0, grid.pieSize, grid.pieSize);
  295 + } else {
  296 + context.canvas
  297 + ..moveTo(cx, cy)
  298 + ..lineTo(sx, sy)
  299 + ..bezierArc(sx, sy, grid.pieSize, grid.pieSize, ex, ey,
  300 + large: angleEnd - angleStart > pi);
  301 + }
186 } 302 }
187 303
188 @override 304 @override
@@ -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) {  
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 - ); 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 +
  368 + // ignore: avoid_as
  369 + final grid = Chart.of(context).grid as PieGrid;
  370 +
  371 + final bisect = (angleStart + angleEnd) / 2;
  372 +
  373 + final cx = sin(bisect) * (offset + grid.pieSize + legendOffset);
  374 + final cy = cos(bisect) * (offset + grid.pieSize + legendOffset);
  375 +
  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 }