imaNNeo / imaNNeo/fl_chart

Pie/Ring Chart Glitch: Flicker in Bottom-Left When Using Rounded Corners + Section Spacing with >50% Segment

Open
#2,090 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.