flutterdartchartsflutter-layoutflutter-dependencies

How to draw a rounded border pie chart


I am trying to build a pie chart that will look like this:

Image

I've tried both Flutter_Charts and FL_Chart, but it seems none of them support a rounded corner and spaced items in the pie chart.

Does anyone know what is the best way to achieve this design as a pie chart?

Thank you!


Solution

  • A very similar version to your chart can easily be achieved with the CustomPaint widget.

    Here is the resulting chart
    Resulting chart

    To achieve this you will just need a very rudimentary CustomPainter that draws arcs across its canvas. The rounding effect is achieved through the strokeCap attribute of the Paint that is used to draw the stroke. Sadly StrokeCap only supports round and square stroke endings.
    A rounded rectangle effect like the one in your screenshot cannot be achieved through this.
    Colors are achieved by using a separate Paint for each stroke.

    // this is used to pass data about chart values to the widget
    class PieChartData {
      const PieChartData(this.color, this.percent);
    
      final Color color;
      final double percent;
    }
    
    // our pie chart widget
    class PieChart extends StatelessWidget {
      PieChart({
        required this.data,
        required this.radius,
        this.strokeWidth = 8,
        this.child,
        Key? key,
      })  : // make sure sum of data is never ovr 100 percent
            assert(data.fold<double>(0, (sum, data) => sum + data.percent) <= 100),
            super(key: key);
    
      final List<PieChartData> data;
      // radius of chart
      final double radius;
      // width of stroke
      final double strokeWidth;
      // optional child; can be used for text for example
      final Widget? child;
    
      @override
      Widget build(context) {
        return CustomPaint(
          painter: _Painter(strokeWidth, data),
          size: Size.square(radius),
          child: SizedBox.square(
            // calc diameter
            dimension: radius * 2,
            child: Center(
              child: child,
            ),
          ),
        );
      }
    }
    
    // responsible for painting our chart
    class _PainterData {
      const _PainterData(this.paint, this.radians);
    
      final Paint paint;
      final double radians;
    }
    
    class _Painter extends CustomPainter {
      _Painter(double strokeWidth, List<PieChartData> data) {
        // convert chart data to painter data
        dataList = data
            .map((e) => _PainterData(
                  Paint()
                    ..color = e.color
                    ..style = PaintingStyle.stroke
                    ..strokeWidth = strokeWidth
                    ..strokeCap = StrokeCap.round,
                  // remove padding from stroke
                  (e.percent - _padding) * _percentInRadians,
                ))
            .toList();
      }
    
      static const _percentInRadians = 0.062831853071796;
      // this is the gap between strokes in percent
      static const _padding = 4;
      static const _paddingInRadians = _percentInRadians * _padding;
      // 0 radians is to the right, but since we want to start from the top
      // we'll use -90 degrees in radians
      static const _startAngle = -1.570796 + _paddingInRadians / 2;
    
      late final List<_PainterData> dataList;
    
      @override
      void paint(Canvas canvas, Size size) {
        final rect = Offset.zero & size;
        // keep track of start angle for next stroke
        double startAngle = _startAngle;
    
        for (final data in dataList) {
          final path = Path()..addArc(rect, startAngle, data.radians);
    
          startAngle += data.radians + _paddingInRadians;
    
          canvas.drawPath(path, data.paint);
        }
      }
    
      @override
      bool shouldRepaint(covariant CustomPainter oldDelegate) {
        return oldDelegate != this;
      }
    }
    

    You can check out the dartpad to experiment with a working example.

    I am positive that the same chart you provided in that picture can be achieved with a CustomPainter but that will be a lot more complex.