Search code examples
javaandroidflutterdartuser-interface

How to Create a Multi-Valued Circular Percentage Indicator in Flutter for Displaying Calories Required?


I’m working on a Flutter project where I need to create a stats widget that displays calories required using a multi-valued percentage indicator. I’ve tried several repositories but couldn’t recreate the design. Here’s the Figma UI I’m trying to replicate:

I’ve attempted to use CustomPaint and CustomPainter, but I’m struggling to draw multiple arcs with different colors and values. Here’s my current code:

enter image description here

Could someone guide me on how to achieve this?


Solution

  • Using:

    1. capped_progress_indicator
    2. pie_chart

    Here's an example.

    import 'package:capped_progress_indicator/capped_progress_indicator.dart';
    
    import 'package:flutter/material.dart';
    import 'package:pie_chart/pie_chart.dart';
    
    void main() async {
      runApp(MyApp());
    }
    
    
    class MyApp extends StatelessWidget {
      MyApp({super.key});
    
      final FocusNode focusNode = FocusNode();
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          debugShowCheckedModeBanner: false,
          theme: ThemeData(useMaterial3: false),
          home: PieChartWidget(),
        );
      }
    }
    
    class PieChartWidget extends StatelessWidget {
      const PieChartWidget({super.key});
    
      @override
      Widget build(BuildContext context) {
        // Data for the pie chart
        Map<String, double> dataMap = {
          "Red": 47,
          "Green": 41,
          "Blue": 16,
        };
    
        // List of colors
        final colorList = <Color>[
          Colors.indigo.shade300,
          Colors.red.shade300,
          Colors.green.shade300
        ];
    
        return Scaffold(
          body: Center(
            child: SizedBox(
               height: 80,
              child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [
                  PieChart(
                    dataMap: dataMap,
                    colorList: colorList,
                    chartType: ChartType.ring,
                    chartRadius: 150,
                    // centerText: "241",
                    centerWidget: const Text.rich(TextSpan(children: [
                      TextSpan(
                          text: "241\n",
                          style:
                              TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
                      TextSpan(text: "kcal")
                    ])),
                    ringStrokeWidth: 10,
                            
                    chartValuesOptions: ChartValuesOptions(
                        showChartValues: false, showChartValueBackground: false),
                    chartLegendSpacing: 0.0,
                            
                    legendOptions: LegendOptions(
                      showLegends: false,
                    ),
                    // chartValuesOptions: ChartValuesOptions(
                    //   showChartValuesInPercentage: true,
                    //   showChartValuesOutside: false,
                    //   showChartValues: true,
                    //   decimalPlaces: 0,
                    // ),
                    // legendOptions: LegendOptions(
                    //   showLegendsInRow: false,
                    //   legendPosition: LegendPosition.right,
                    //   showLegends: true,
                    //   legendShape: BoxShape.circle,
                    //   legendTextStyle: TextStyle(
                    //     fontWeight: FontWeight.bold,
                    //   ),
                    // ),
                  ),
                  SizedBox(width: 30),
                  PercentageWidget(
                    title: "28.2g",
                    value: 47,
                    typeTitle: "carbs",
                    color: colorList[0],
                  ),
                  SizedBox(width: 30),
                  PercentageWidget(
                    title: "10.9g",
                    value: 41,
                    typeTitle: "fat",
                    color: colorList[1],
                  ),
                  SizedBox(width: 30),
                  PercentageWidget(
                    title: "9.8g",
                    value: 16,
                    typeTitle: "protein",
                    color: colorList[2],
                  ),
                ],
              ),
            ),
          ),
        );
      }
    }
    
    class PercentageWidget extends StatelessWidget {
      const PercentageWidget(
          {super.key,
          required this.title,
          required this.value,
          required this.typeTitle,
          required this.color});
    
      final String title;
      final double value;
      final String typeTitle;
    
      final Color color;
      @override
      Widget build(BuildContext context) {
        return SizedBox(
          width: 45,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            mainAxisSize: MainAxisSize.min,
            children: [
              Text(
                title,
                style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
              ),
              Text(
                typeTitle,
                style: TextStyle(fontSize: 12, fontWeight: FontWeight.w300, color: Colors.grey.shade600),
              ),
              SizedBox(height: 3),
              LinearCappedProgressIndicator(
                  color: color,
                  backgroundColor: color.withOpacity(0.5),
                  cornerRadius: 10,
                  value: value / 100,
                  minHeight: 6),
              SizedBox(height: 2),
              Text(
                '$value' "%",
                style: TextStyle(
                  color: color,
                ),
              ),
            ],
          ),
        );
      }
    }
    
    

    Note:

    • I run this code in flutter web. So, handle your required width and height accordingly.
    • Explore more chart or progress type widget on pub.dev to create the exact pie chart which is shown in the UI.
    • This is just an example. You can remove duplicate widget code based on your data.

    Output: enter image description here