Piechart customization

How can I make a chart like in the attached image. with the values mentioned in the design as they are.


Attachment: requiredDesign_23a5acbf.zip

13 Replies 1 reply marked as answer

PB Praveen Balu Syncfusion Team July 29, 2025 11:03 AM UTC

Hi Arnold Rafi,


We have analyzed your query and based on the UI you shared, we believe that using the radial slider would be a suitable approach for your requirements. The radial slider offers extensive customization options that align well with the concentric circular progress indicators shown in your image. We have shared SB demo and sample for your reference; you can modify the sample based on your needs.

SB Link: Demos & Examples of Syncfusion Flutter Widgets

SB Sample Link: flutter-examples/lib/samples/radial_slider/customization/thumb.dart at master · syncfusion/flutter-examples

However, we noticed that in your shared image, the values displayed (4% and 1.5%6% and 2%) appear to be visually grouped but do not show a clear relationship. Could you please clarify the following:

  • Do these values have any relationship?
  • If yes, could you explain the nature of that relationship?
  • How would you like these values to be visually or logically connected in the chart?

Your clarification will help us provide a more accurate and tailored solution.

 

Regards,

Praveen Balu.




AR Arnold Rafi July 30, 2025 11:03 AM UTC

the ui has been forking fine but I am unable to place the text inside the MarkerPointer. I have given the value but for some reasons it is not visible in it. Need the solution for this. I have given the code.



 SizedBox(
height: size,
width: size,
child: SfRadialGauge(
axes: <RadialAxis>[
RadialAxis(
showLabels: false,
showTicks: false,
startAngle: 270,
endAngle: 270,
radiusFactor: 0.8,
axisLineStyle: const AxisLineStyle(
thickness: 0.1,
thicknessUnit: GaugeSizeUnit.factor,
),
pointers: <GaugePointer>[
RangePointer(
value: value,
width: 0.1,
sizeUnit: GaugeSizeUnit.factor,
color:
// model.isWebFullView
// ?
color,
// : null,
gradient:  SweepGradient(
colors: <Color>[
color,
color
],
stops: <double>[0.5, 1],
),
),
MarkerPointer(
value: value,
elevation: 5,
overlayRadius: 0,
markerType: MarkerType.circle,
markerHeight: 20,
markerWidth: 20,
enableDragging: false,
onValueChanged: (val){},
onValueChanging: (val){},
color: color,
text: "${value}%",
textStyle:  GaugeTextStyle(color: Colors.white,fontSize: 6),
),
],
annotations: <GaugeAnnotation>[
// GaugeAnnotation(
// widget: Text("",style: TextStyle(color: Colors.white),),
// ),
],
),
],
),
);







PB Praveen Balu Syncfusion Team July 31, 2025 11:37 AM UTC

Hi Arnold Rafi


The MarkerPointer in SfRadialGauge supports rendering either a shape (like a circle, triangle, or image) or text, but not both simultaneously. So, we suggest using a WidgetPointer instead. This property allows you to add any widget as custom widgets and in this case, we have used a CircleAvatar with a Text widget to show the value directly in the marker.

 

Additionally, We enabled user interaction by setting the enableDragging property of the WidgetPointer to true, which allows users to drag the pointer on the track. As the pointer moves, the onValueChanged callback gets triggered, so we can update the value in real time.

 

WidgetPointer(

 value: _value,

 enableDragging: true,

 onValueChanged: (double value) {

   setState(() {

     _value = value;

   });

 },

 child: CircleAvatar(

   radius: 25,

   backgroundColor: color,

   child: Text(

     "${_value.toStringAsFixed(1)}%",

     style: TextStyle(color: Colors.white, fontSize: 15),

   ),

 ),

),


We have also attached the sample and demo for your reference, and you can modify the sample based on your requirements. Please let us know if you need any further assistance.


Regards,

Praveen Balu.


Attachment: forum_197244_16d5986.zip


AR Arnold Rafi August 19, 2025 03:44 PM UTC

Everything has been fine. How can i add the label on the start of the circle . which is currently at the bottom and how can i change the background etc of the lable. which has the value. The main goal is to add it on the top of it. which is the start of the circle. I am unable of add the image.




PB Praveen Balu Syncfusion Team August 20, 2025 01:58 PM UTC

Hi Arnold Rafi,


In SfRadialGauge, to display the pointer at the start of the gauge range, you can set the startValue and endValue properties within GaugeRange and assign it to the ranges property of the RadialAxis. Then, assign the same start value used in GaugeRange to the value property of WidgetPointer to position the pointer at the start of the gauge range.


GaugeRange(

  endValue: _endValue,

  startValue: _startValue,

  sizeUnit: GaugeSizeUnit.factor,

  color: color,

  startWidth: 0.1,

  endWidth: 0.1,

),


To display an image on the pointer, the WidgetPointer property allows you to add any widget as a custom pointer. In this case, we’ve used a CircleAvatar containing an Image widget to display the image as needed.


WidgetPointer(

  value: _startValue,

  enableDragging: true,

  onValueChanged: (double value) {

    setState(() {

      _startValue = value;

    });

  },

  child: CircleAvatar(

    radius: 25,

    backgroundColor: color,

    child: Image.asset('assets/memphis.png'),

  ),

),


We have also attached the sample and demo for your reference, and you can modify the sample based on your requirements.


Regards,

Praveen Balu.


Attachment: forum_197244_e6996e44.zip


AR Arnold Rafi August 21, 2025 12:22 PM UTC

I am unable to understand how to put the 


GaugeRange(

  endValue: _endValue,

  startValue: _startValue,

  sizeUnit: GaugeSizeUnit.factor,

  color: color,

  startWidth: 0.1,

  endWidth: 0.1,

),

this code inside the component. If you can give the complete example that will be helpful.





PB Praveen Balu Syncfusion Team August 22, 2025 01:08 PM UTC

Hi Arnold Rafi,


We have attached the complete sample as a reference and recommend downloading it for a clearer understanding of how To position a WidgetPointer containing an image at the start value of a GaugeRange in SfRadialGauge.


In addition to the attachment, we’ve also included the full code snippet for your understanding. Within the snippet, we have highlighted the portion of your shared code to help you easily identify and understand the relevant scope.


import 'package:flutter/material.dart';

import 'package:syncfusion_flutter_gauges/gauges.dart';

 

void main() {

  runApp(const MarkerWithText());

}

 

class MarkerWithText extends StatefulWidget {

  const MarkerWithText({super.key});

 

  @override

  State<MarkerWithText> createState() => _MarkerWithTextState();

}

 

class _MarkerWithTextState extends State<MarkerWithText>

    with SingleTickerProviderStateMixin {

  double _endValue = 60;

  double _startValue = 0;

  final Color color = Colors.blue;

  final double size = 600;

 

  @override

  Widget build(BuildContext context) {

    return MaterialApp(

      debugShowCheckedModeBanner: false,

      theme: ThemeData(primarySwatch: Colors.purple, useMaterial3: true),

      home: Scaffold(

        body: Center(

          child: SizedBox(

            height: size,

            width: size,

            child: SfRadialGauge(

              axes: <RadialAxis>[

                RadialAxis(

                  showLabels: false,

                  showTicks: false,

                  startAngle: 270,

                  endAngle: 270,

                  radiusFactor: 0.8,

                  axisLineStyle: const AxisLineStyle(

                    thickness: 0.1,

                    thicknessUnit: GaugeSizeUnit.factor,

                  ),

                  ranges: <GaugeRange>[

                    GaugeRange(

                      endValue: _endValue,

                      startValue: _startValue,

                      sizeUnit: GaugeSizeUnit.factor,

                      color: color,

                      startWidth: 0.1,

                      endWidth: 0.1,

                    ),

                  ],

                  pointers: <GaugePointer>[

                    WidgetPointer(

                      value: _startValue,

                      enableDragging: true,

                      onValueChanged: (double value) {

                        setState(() {

                          _startValue = value;

                        });

                      },

                      child: CircleAvatar(

                        radius: 25,

                        backgroundColor: color,

                        child: Image.asset('assets/memphis.png'),

                      ),

                    ),

                  ],

                ),

              ],

            ),

          ),

        ),

      ),

    );

  }

}

 

 


Regards,

Praveen Balu.


Attachment: forum_197244_64420f79.zip

Marked as answer

AR Arnold Rafi August 27, 2025 08:10 AM UTC

This is not what i asked. In simple words i want to change the position of the 

WidgetPointer(

value: value,

enableDragging: false,

onValueChanged: (double val) {

setState(() {

value = val;

});

},

child: CircleAvatar(

radius: 7,

backgroundColor: color,

child: Text(

"${value.toStringAsFixed(0)}%",

style: TextStyle(color: Colors.white, fontSize: 4),

),

),

),

which is currently at the end of guage. i want to place it at the start of the circle.



MR Mugunthan Ramalingam Syncfusion Team August 28, 2025 11:59 AM UTC

Hi Arnold Rafi, 

To position the gauge, use the startAngle and endAngle properties of  SfRadialGauge. These control where the gauge arc begins and ends. To position the WidgetPointer, use the value property, which places the pointer along the arc based on the specified value. In this sample, we’ve set both startAngle and endAngle to 0, which positions the gauge arc on the right side of the circle. The WidgetPointer is set with a value of 0, so it appears exactly at the start of the arc on the right.

You can control the gauge’s position using the startAngle and endAngle properties of SfRadialGauge. For example:

  • Setting both to 90 positions the gauge to start from the bottom.
  • Setting both to 180 starts it from the left center.
  • Setting both to 270 starts it from the top.
  • Setting both to 0 or 360 starts it from the right center.

 

Additionally, we noticed that dragging is disabled for the pointer, but the onValueChanged callback is still being used. We suspect that you're trying to update the gauge value using another input method. To support this, we’ve added an SfSlider below the gauge in the sample. This slider updates the gauge’s range dynamically, while the pointer remains fixed at the start position.

 

Code snippet:

SfRadialGauge(

  axes: <RadialAxis>[

    RadialAxis(

      startAngle: 0,

      endAngle: 0,

      pointers: <GaugePointer>[

        WidgetPointer(

          value: _startValue,

          enableDragging: false,

          child: CircleAvatar(

            radius: 20,

            child: Stack(

              alignment: Alignment.center,

              children: [

                Image.asset(

                  'assets/ball_progressbar.png',

                  width: 40,

                  height: 40,

                  fit: BoxFit.fill,

                ),

                Text(

                  _endValue.toStringAsFixed(0),

                  style: const TextStyle(

                    color: Colors.red,

                    fontSize: 18,

                    fontWeight: FontWeight.bold,

                  ),

                ),

              ],

            ),

          ),

        ),

      ],

    ),

  ],

),

 



We have shared the sample for your reference, and you can customize it according to your needs. If you are still not satisfied with the sample, we kindly request you to share us with more information on your requirement in detail along with screenshots/screen recordings so that it will help us assist you in a better way. 


Best regards,

Mugunthan.


Attachment: forum_197244_cbccf338.zip


AR Arnold Rafi November 28, 2025 12:30 PM UTC

Screenshot 2025-11-28 at 5.25.05 PM.jpg


Here is the image. If you can see the text is now outside the guage and inside the guage respectively. How can i do this according to the position of the guage.

here is the code as well which i am using for the design currently for both the start value and the guage values.


SfRadialGauge(
axes: <RadialAxis>[
RadialAxis(
showLabels: false,
showTicks: false,
startAngle: 270,
endAngle: 270,
radiusFactor: 0.8,
axisLineStyle: const AxisLineStyle(
thickness: 0.1,
thicknessUnit: GaugeSizeUnit.factor,
),
pointers: <GaugePointer>[
RangePointer(
value: value,
width: 0.1,
sizeUnit: GaugeSizeUnit.factor,
color: color,
gradient: SweepGradient(
colors: <Color>[color, color],
stops: <double>[0.5, 1],
),
),

// Label showing the "share" percentage (placed at center)
WidgetPointer(
value: 0,
enableDragging: false,
child: Text(
value != 0 ? "${(share).toStringAsFixed(1)}%" : '',
style: TextStyle(color: Colors.white, fontSize: 8),
),
),

// Moving circle showing current value %
WidgetPointer(
value: value,
enableDragging: false,
child: CircleAvatar(
backgroundColor: color,
radius: 6,
child: Text(
"${value.toStringAsFixed(1)}%",
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white, fontSize: 4),
),
),
),
],
),
],
)


HM Harsha Midadhala Khajareddy Midadhala Syncfusion Team December 1, 2025 01:56 PM UTC

Hi Arnold Rafi,


To position text inside or outside the gauge, use GaugeAnnotationwithin SfRadialGauge.

Key Properties:

  • angle: Specifies the angular position of the annotation around the gauge. Here, the angle property is converted from the current selected gauge value.

  • positionFactor: Determines the radial distance from the center.

    • A value greater than 1 places the annotation outside the gauge ring.

    • A value less than 1 places the annotation inside the gauge ring.


double _valueToAngle(double value, double axisMin, double axisMax) {

   final sweep = (_endAngleDeg - _startAngleDeg) % 360 == 0

       ? 360

       : (_endAngleDeg - _startAngleDeg);

 

   return _startAngleDeg + (value - axisMin) * sweep / (axisMax - axisMin);

 }

 

final double startLabelAngle = _valueToAngle(_startValue, _outerAxisMin, _outerAxisMax);

final double endLabelAngle = _valueToAngle(_endValue, _outerAxisMin, _outerAxisMax);


annotations: <GaugeAnnotation>[

  // OUTER Start label OUTSIDE

  GaugeAnnotation(

    widget: Text(

      '${_startValue.toStringAsFixed(0)}%',

      style: const TextStyle(

        fontSize: 14,

        fontWeight: FontWeight.w600,

      ),

    ),

    angle: startLabelAngle,

    positionFactor: 1.2, // outside ring

  ),

  // OUTER End label OUTSIDE

  GaugeAnnotation(

    widget: Text(

      '${_endValue.toStringAsFixed(0)}%',

      style: const TextStyle(

        fontSize: 14,

        fontWeight: FontWeight.w600,

      ),

    ),

    angle: endLabelAngle,

    positionFactor: 1.2, // outside ring

  ),

],

 



For more details, refer to the official documentation: Annotation in Flutter Radial Gauge widget | Syncfusion

Please feel free to reach out if you need any further assistance.

Regards,

Harsha.


Attachment: forum_197244_51555741.zip


AR Arnold Rafi December 2, 2025 09:25 AM UTC

Screenshot 2025-12-02 at 2.22.32 PM.jpg


I am unable to find the values for these angles. Can you be more specific about how to get these . i will send you the complete widget code. And also i am calling it like this.


_buildSliderWithCircle(
widget.plan.currentFreedomShareROIPCT?.toDouble() ?? 0,
(((widget.plan.roiValue ?? 1) /
(widget.plan.currentCustomerShareROIPCT ?? 1)) *
(widget.plan.freedomShare ?? 0))
.toDouble(),
75,
AppC.primary,
)
Widget _buildSliderWithCircle(
double value, double share, double size, Color color)
{
return SizedBox(
height: size,
width: size,
child: SfRadialGauge(
axes: <RadialAxis>[
RadialAxis(
showLabels: false,
showTicks: false,
startAngle: 270,
endAngle: 270,
radiusFactor: 0.8,
axisLineStyle: const AxisLineStyle(
thickness: 0.1,
thicknessUnit: GaugeSizeUnit.factor,
),
annotations:<GaugeAnnotation> [ // OUTER Start label OUTSIDE
GaugeAnnotation(
widget: Text(
'${value.toStringAsFixed(0)}%',
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
angle: startLabelAngle,
positionFactor: 1.2, // outside ring
),
// OUTER End label OUTSIDE
GaugeAnnotation(
widget: Text(
'${share.toStringAsFixed(0)}%',
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
angle: endLabelAngle,
positionFactor: 1.2, // outside ring
),],
pointers: <GaugePointer>[
RangePointer(
value: value,
width: 0.1,
sizeUnit: GaugeSizeUnit.factor,
color: color,
gradient: SweepGradient(
colors: <Color>[color, color],
stops: <double>[0.5, 1],
),
),

// Label showing the "share" percentage (placed at center)
// WidgetPointer(
// value: 0,
// enableDragging: false,
// child: Text(
// value != 0 ? "${(share).toStringAsFixed(1)}%" : '',
// style: TextStyle(color: Colors.white, fontSize: 8),
// ),
// ),
//
// // Moving circle showing current value %
// WidgetPointer(
// value: value,
// enableDragging: false,
// child: CircleAvatar(
// backgroundColor: color,
// radius: 6,
// child: Text(
// "${value.toStringAsFixed(1)}%",
// textAlign: TextAlign.center,
// style: TextStyle(color: Colors.white, fontSize: 4),
// ),
// ),
// ),
],
),
],
),
);
}



HM Harsha Midadhala Khajareddy Midadhala Syncfusion Team December 4, 2025 10:06 AM UTC

Hi Arnold Rafi,

We’ve declared and initialized all angle-related properties with their default values in the _MarkerWithTextState class. You can refer to the attached sample for full details.

For your convenience, here’s a quick summary of the key properties and their roles:

  • _outerEndValue (50): Represents the current value displayed on the outer gauge. Its range is 0–100.
  • _innerEndValue (100): Represents the current value displayed on the inner gauge. Its range is 0–200.
  • outerSize (300) and innerSize (220): Define the size of the outer and inner gauges respectively.
  • _startAngleDeg (0°) and _endAngleDeg (360°): Indicate that the gauge spans a full circle.
  • share: Represents the total range of the gauge (e.g., 100 for outer, 200 for inner) and determines its maximum value.
  • _valueToAngle: Converts a given value into a corresponding polar angle based on axis min/max and start/end angles.

Also modified the sample by including the _buildSliderWithCircle method based on your provided code snippet for easier understanding. Please review the attached sample and feel free to modify it further based on your requirements.


 

 _buildSliderWithCircle(_outerEndValue, 100, outerSize, outerColor),

 _buildSliderWithCircle(_innerEndValue, 200, innerSize, innerColor),

 

Widget _buildSliderWithCircle(double value, double share, double size, Color color) {

    final double clampedValue = value.clamp(0, share).toDouble();

    final double startLabelAngle = _valueToAngle(0, 0, share);

    final double endLabelAngle = _valueToAngle(clampedValue, 0, share);

    final double sep = _angleSeparationDeg(startLabelAngle, endLabelAngle);

    final bool anglesEqual = sep < _angleEpsilonDeg;         // essentially same angle

    final bool nearOverlap = !anglesEqual && sep < _minSeparationDeg;

    final bool isOuter = share == 100.0;

    final double basePositionFactor = isOuter ? 1.12 : 0.7;

    final double startPositionFactor = nearOverlap

        ? (isOuter ? basePositionFactor + _radialOffset : basePositionFactor - _radialOffset)

        : basePositionFactor;

    final double endPositionFactor = basePositionFactor;

    final double ringWidth = isOuter ? 0.1 : 0.12;

 

    return SizedBox(

      height: size,

      width: size,

      child: SfRadialGauge(

        axes: <RadialAxis>[

          RadialAxis(

            minimum: 0,

            maximum: share,

            showLabels: false,

            showTicks: false,

            startAngle: _startAngleDeg,

            endAngle: _endAngleDeg,

            radiusFactor: 0.9,

            axisLineStyle: AxisLineStyle(

              thickness: ringWidth,

              thicknessUnit: GaugeSizeUnit.factor,

            ),

            ranges: <GaugeRange>[

              GaugeRange(

                startValue: 0,

                endValue: clampedValue,

                sizeUnit: GaugeSizeUnit.factor,

                color: color,

                startWidth: ringWidth,

                endWidth: ringWidth,

              ),

            ],

            annotations: <GaugeAnnotation>[

              GaugeAnnotation(

                angle: startLabelAngle,

                positionFactor: startPositionFactor,

                widget: Text(

                  anglesEqual ? '' : '0%',

                  style: TextStyle(

                    fontSize: 14,

                    fontWeight: isOuter ? FontWeight.w600 : FontWeight.w700,

                  ),

                ),

              ),

              GaugeAnnotation(

                angle: endLabelAngle,

                positionFactor: endPositionFactor,

                widget: Text(

                  '${clampedValue.toStringAsFixed(0)}%',

                  style: TextStyle(

                    fontSize: 14,

                    fontWeight: isOuter ? FontWeight.w600 : FontWeight.w700,

                  ),

                ),

              ),

            ],

          ),

        ],

      ),

    );

  }


Please let us know if you need any further assistance or customization.


Regards,

Harsha.


Attachment: forum_197244_ce3994b4.zip

Loader.
Up arrow icon