Pie/Ring Chart Glitch: Flicker in Bottom-Left When Using Rounded Corners + Section Spacing with >50% Segment
- Dominant language
- Dart
- Stars
- 7.6k
- Forks
- 2k
- Avg merge
- 9d 1h
- Merged PRs (30d)
- 2
Description
Relevant file: lib/src/chart/pie_chart/pie_chart_painter.dart
Feature introduced in: PR #2024, merged 2026-02-26
**Expected behavior**
The chart renders cleanly for all data distributions, including when one section dominates (> 50%).
**Description**
When a pie or ring chart uses rounded corners together with spacing between sections, a visual glitch can occur under certain conditions.
Instead of a smooth animation, you may notice a brief flash or flicker, typically in the bottom-left area of the chart.
This issue seem to only appear when all of the following are true:
• rounded corners enabled,
• spacing between sections enabled.
Additionally, the glitch depends on the ordering of the sections. If the large section is positioned earlier in the sequence, the issue can appear—but if that same section is placed at the end, the chart renders normally without any visual artifacts.
**To Reproduce**
Use this main:
```
void main() => runApp(const _ReproApp());
class _ReproApp extends StatelessWidget {
const _ReproApp();
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
home: const Scaffold(
body: SafeArea(
child: Center(child: _ReproPage()),
),
),
);
}
}
class _ReproPage extends StatefulWidget {
const _ReproPage();
@override
State<_ReproPage> createState() => _ReproPageState();
}
class _ReproPageState extends State<_ReproPage> {
int _key = 0;
void _replay() => setState(() => _key++);
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
children: [
const Text(
'fl_chart bug',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w700),
),
const SizedBox(height: 24),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_AnimatedDonut(
key: ValueKey('bug-$_key'),
sectionsSpace: 3,
label: 'sectionsSpace=3',
sublabel: '✗ glitch at bottom-left',
cornerRadius: 8,
),
_AnimatedDonut(
key: ValueKey('fix1-$_key'),
sectionsSpace: 0,
label: 'sectionsSpace=0',
sublabel: '✓ no glitch',
cornerRadius: 8,
),
_AnimatedDonut(
key: ValueKey('fix2-$_key'),
sectionsSpace: 3,
label: 'cornerRadius=0',
sublabel: '✓ no glitch',
cornerRadius: 0,
),
],
),
const SizedBox(height: 32),
ElevatedButton(
onPressed: _replay,
child: const Text('Replay'),
),
],
),
);
}
}
class _AnimatedDonut extends StatefulWidget {
const _AnimatedDonut({
required this.sectionsSpace,
required this.label,
required this.sublabel,
required this.cornerRadius,
super.key,
});
final double sectionsSpace;
final String label;
final String sublabel;
final double cornerRadius;
@override
State<_AnimatedDonut> createState() => _AnimatedDonutState();
}
class _AnimatedDonutState extends State<_AnimatedDonut> {
static const _stroke = 40.0;
static const _size = 100.0;
static const double _center = _size / 2 - _stroke;
bool _hasData = false;
@override
void initState() {
super.initState();
Future.delayed(const Duration(milliseconds: 500), () {
if (mounted) setState(() => _hasData = true);
});
}
List _mock(Color grey) => [4.0, 4.0, 3.0, 2.0, 2.0]
.map(
(v) => PieChartSectionData(
value: v,
color: grey,
radius: _stroke,
showTitle: false,
cornerRadius: widget.cornerRadius,
),
)
.toList();
List _data() => [
PieChartSectionData(value: 57, color: const Color(0xFF4F8EF7), radius: _stroke, showTitle: false, cornerRadius: widget.cornerRadius),
PieChartSectionData(value: 20, color: const Color(0xFF34C88A), radius: _stroke, showTitle: false, cornerRadius: widget.cornerRadius),
PieChartSectionData(value: 12, color: const Color(0xFFFF9F40), radius: _stroke, showTitle: false, cornerRadius: widget.cornerRadius),
PieChartSectionData(value: 11, color: const Color(0xFFC340FF), radius: _stroke, showTitle: false, cornerRadius: widget.cornerRadius),
];
@override
Widget build(BuildContext context) {
final grey = Theme.of(context).brightness == Brightness.dark ? const Color(0xFF3A3A3C) : const Color(0xFFE5E5EA);
return Column(
children: [
SizedBox.square(
dimension: _size,
child: PieChart(
PieChartData(
sections: _hasData ? _data() : _mock(grey),
centerSpaceRadius: _center,
sectionsSpace: widget.sectionsSpace,
startDegreeOffset: -90,
borderData: FlBorderData(show: false),
),
duration: _hasData ? const Duration(milliseconds: 900) : Duration.zero,
curve: Curves.easeOutCubic,
),
),
const SizedBox(height: 6),
Text(widget.label, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w700)),
Text(widget.sublabel, style: const TextStyle(fontSize: 11, color: Colors.grey)),
],
);
}
}
```
**Screen recording**
https://github.com/user-attachments/assets/7de4c2df-e073-4607-9689-0ff6cc75430d
**Versions**
- Flutter version: 3.41.6
- Package v1.2.0
Contributor guide
Research direction
Start in lib/src/chart/pie_chart/pie_chart_painter.dart and reproduce the report with the provided Flutter app: a 57% first section, cornerRadius 8, sectionsSpace 3, and the 900 ms animation. Compare it with sectionsSpace=0 and cornerRadius=0 while tracing the rounded-section and spacing paths. Done means the dominant-section animation renders without the bottom-left flicker and the existing comparison cases remain clean.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- dart, flutter
- Domain
- data-visualization, frontend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 72/100