Showing
2 changed files
with
175 additions
and
37 deletions
| @@ -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 | } |
-
Please register or login to post a comment