Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions example/lib/presentation/samples/chart_samples.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import 'pie/pie_chart_sample1.dart';
import 'pie/pie_chart_sample2.dart';
import 'pie/pie_chart_sample3.dart';
import 'pie/pie_chart_sample4.dart';
import 'pie/pie_chart_sample5.dart';
import 'radar/radar_chart_sample1.dart';
import 'scatter/scatter_chart_sample1.dart';
import 'scatter/scatter_chart_sample2.dart';
Expand Down Expand Up @@ -67,6 +68,7 @@ class ChartSamples {
PieChartSample(2, (context) => const PieChartSample2()),
PieChartSample(3, (context) => const PieChartSample3()),
PieChartSample(4, (context) => const PieChartSample4()),
PieChartSample(5, (context) => const PieChartSample5()),
],
ChartType.scatter: [
ScatterChartSample(1, (context) => ScatterChartSample1()),
Expand Down
126 changes: 126 additions & 0 deletions example/lib/presentation/samples/pie/pie_chart_sample5.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import 'package:fl_chart/fl_chart.dart';
import 'package:fl_chart_app/presentation/resources/app_colors.dart';
import 'package:flutter/material.dart';

class PieChartSample5 extends StatefulWidget {
const PieChartSample5({super.key});

@override
State<PieChartSample5> createState() => _PieChartSample5State();
}

class _PieChartSample5State extends State<PieChartSample5> {
int touchedIndex = -1;

@override
Widget build(BuildContext context) {
return AspectRatio(
aspectRatio: 1.3,
child: AspectRatio(
aspectRatio: 1,
child: PieChart(
PieChartData(
pieTouchData: PieTouchData(
touchCallback: (FlTouchEvent event, pieTouchResponse) {
setState(() {
if (!event.isInterestedForInteractions ||
pieTouchResponse == null ||
pieTouchResponse.touchedSection == null) {
touchedIndex = -1;
return;
}
touchedIndex =
pieTouchResponse.touchedSection!.touchedSectionIndex;
});
},
),
borderData: FlBorderData(show: false),
sectionsSpace: 3,
centerSpaceRadius: 24,
sections: showingSections(),
),
),
),
);
}

List<PieChartSectionData> showingSections() {
return List.generate(4, (i) {
final isTouched = i == touchedIndex;
final radius = 56.0;
final radialOffset = isTouched ? 12.0 : 0.0;
final fontSize = isTouched ? 18.0 : 14.0;
const shadows = [Shadow(color: Colors.black, blurRadius: 2)];

return switch (i) {
0 => PieChartSectionData(
color: AppColors.contentColorBlue,
value: 40,
title: '40%',
radius: radius,
radialOffset: radialOffset,
titleStyle: TextStyle(
fontSize: fontSize,
fontWeight: FontWeight.bold,
color: AppColors.contentColorWhite,
shadows: shadows,
),
segments: [
PieChartStackSegmentData(
fromRadius: 0,
toRadius: radius,
color: AppColors.contentColorBlue,
),
],
),
1 => PieChartSectionData(
color: AppColors.contentColorOrange,
value: 30,
title: '30%',
radius: radius,
radialOffset: radialOffset,
titleStyle: TextStyle(
fontSize: fontSize,
fontWeight: FontWeight.bold,
color: AppColors.contentColorWhite,
shadows: shadows,
),
),
2 => PieChartSectionData(
color: AppColors.contentColorPurple,
value: 20,
title: '20%',
radius: radius,
radialOffset: radialOffset,
titleStyle: TextStyle(
fontSize: fontSize,
fontWeight: FontWeight.bold,
color: AppColors.contentColorWhite,
shadows: shadows,
),
segments: [
PieChartStackSegmentData(
fromRadius: 0,
toRadius: radius,
color: AppColors.contentColorPurple,
),
],
),
3 => PieChartSectionData(
color: AppColors.contentColorGreen,
value: 10,
title: '10%',
radius: radius,
radialOffset: radialOffset,
titleStyle: TextStyle(
fontSize: fontSize,
fontWeight: FontWeight.bold,
color: AppColors.contentColorWhite,
shadows: shadows,
),
),
_ => throw StateError('Invalid'),
};
});
}
}
13 changes: 13 additions & 0 deletions lib/src/chart/pie_chart/pie_chart_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ class PieChartSectionData with EquatableMixin {
Color? color,
this.gradient,
double? radius,
double? radialOffset,
bool? showTitle,
this.titleStyle,
String? title,
Expand All @@ -169,6 +170,7 @@ class PieChartSectionData with EquatableMixin {
}) : value = value ?? 10,
color = color ?? Colors.cyan,
radius = (radius ?? 40).clamp(0, double.infinity).toDouble(),
radialOffset = radialOffset ?? 0,
showTitle = showTitle ?? true,
title = title ?? (value == null ? '' : value.toString()),
borderSide = borderSide ?? const BorderSide(width: 0),
Expand All @@ -195,6 +197,13 @@ class PieChartSectionData with EquatableMixin {
/// Defines the radius of section.
final double radius;

/// Additional radial translation applied to the whole section (in logical pixels).
/// Positive values move the section outward along its center angle.
///
/// Note: This parameter is ignored when there is only a single section
/// occupying 360 degrees, as there is no meaningful direction to offset.
final double radialOffset;

/// Defines show or hide the title of section.
final bool showTitle;

Expand Down Expand Up @@ -244,6 +253,7 @@ class PieChartSectionData with EquatableMixin {
Color? color,
Gradient? gradient,
double? radius,
double? radialOffset,
bool? showTitle,
TextStyle? titleStyle,
String? title,
Expand All @@ -259,6 +269,7 @@ class PieChartSectionData with EquatableMixin {
color: color ?? this.color,
gradient: gradient ?? this.gradient,
radius: radius ?? this.radius,
radialOffset: radialOffset ?? this.radialOffset,
showTitle: showTitle ?? this.showTitle,
titleStyle: titleStyle ?? this.titleStyle,
title: title ?? this.title,
Expand All @@ -283,6 +294,7 @@ class PieChartSectionData with EquatableMixin {
color: Color.lerp(a.color, b.color, t),
gradient: Gradient.lerp(a.gradient, b.gradient, t),
radius: lerpDouble(a.radius, b.radius, t),
radialOffset: lerpDouble(a.radialOffset, b.radialOffset, t),
showTitle: b.showTitle,
titleStyle: TextStyle.lerp(a.titleStyle, b.titleStyle, t),
title: b.title,
Expand Down Expand Up @@ -313,6 +325,7 @@ class PieChartSectionData with EquatableMixin {
color,
gradient,
radius,
radialOffset,
showTitle,
titleStyle,
title,
Expand Down
55 changes: 49 additions & 6 deletions lib/src/chart/pie_chart/pie_chart_painter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,14 @@ class PieChartPainter extends BaseChartPainter<PieChartData> {
}
final sectionDegree = sectionsAngle[i];

final sectionCenterAngle = tempAngle + (sectionDegree / 2);
final radialOffset = _computeRadialOffset(
section.radialOffset,
sectionCenterAngle,
sectionDegree,
);
final sectionCenter = center + radialOffset;

if (sectionDegree == 360) {
final fullCirclePath = generateSegmentPath(
center,
Expand All @@ -124,7 +132,7 @@ class PieChartPainter extends BaseChartPainter<PieChartData> {
sectionDegree,
centerRadius,
0,
center,
sectionCenter,
);

_sectionPaint.blendMode = BlendMode.srcOver;
Expand All @@ -136,14 +144,14 @@ class PieChartPainter extends BaseChartPainter<PieChartData> {
// Outer
canvasWrapper
..drawCircle(
center,
sectionCenter,
centerRadius + section.radius - (section.borderSide.width / 2),
_sectionStrokePaint,
)

// Inner
..drawCircle(
center,
sectionCenter,
centerRadius + (section.borderSide.width / 2),
_sectionStrokePaint,
);
Expand All @@ -158,7 +166,7 @@ class PieChartPainter extends BaseChartPainter<PieChartData> {
data.sectionsSpace,
tempAngle,
sectionDegree,
center,
sectionCenter,
centerRadius,
);

Expand All @@ -173,7 +181,7 @@ class PieChartPainter extends BaseChartPainter<PieChartData> {
sectionDegree,
centerRadius,
tempAngle,
center,
sectionCenter,
);
canvasWrapper.restore();

Expand Down Expand Up @@ -723,8 +731,15 @@ class PieChartPainter extends BaseChartPainter<PieChartData> {
}
}

final radialOffset = _computeRadialOffset(
section.radialOffset,
sectionCenterAngle,
sweepAngle,
);

Offset sectionCenter(double percentageOffset) =>
center +
radialOffset +
Offset(
math.cos(Utils().radians(sectionCenterAngle)) *
(centerRadius + (section.radius * percentageOffset)),
Expand Down Expand Up @@ -821,12 +836,19 @@ class PieChartPainter extends BaseChartPainter<PieChartData> {
break;
}

final sectionCenterAngle = tempAngle + (sectionAngle / 2);
final radialOffset = _computeRadialOffset(
section.radialOffset,
sectionCenterAngle,
sectionAngle,
);

final sectionPath = generateSectionPath(
section,
data.sectionsSpace,
tempAngle,
sectionAngle,
center,
center + radialOffset,
centerRadius,
);

Expand Down Expand Up @@ -870,8 +892,15 @@ class PieChartPainter extends BaseChartPainter<PieChartData> {
final sectionCenterAngle = startAngle + (sweepAngle / 2);
final centerRadius = calculateCenterRadius(viewSize, holder);

final radialOffset = _computeRadialOffset(
section.radialOffset,
sectionCenterAngle,
sweepAngle,
);

Offset sectionCenter(double percentageOffset) =>
center +
radialOffset +
Offset(
math.cos(Utils().radians(sectionCenterAngle)) *
(centerRadius + (section.radius * percentageOffset)),
Expand All @@ -889,4 +918,18 @@ class PieChartPainter extends BaseChartPainter<PieChartData> {

return badgeWidgetsOffsets;
}

/// Computes the radial offset translation for a section along its center angle.
/// Returns [Offset.zero] when [sectionDegree] is 360 (single full-circle section).
Offset _computeRadialOffset(
double radialOffset,
double sectionCenterAngle,
double sectionDegree,
) {
if (sectionDegree == 360) return Offset.zero;
return Offset(
math.cos(Utils().radians(sectionCenterAngle)) * radialOffset,
math.sin(Utils().radians(sectionCenterAngle)) * radialOffset,
);
}
}
1 change: 1 addition & 0 deletions repo_files/documentations/pie_chart.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ When you change the chart's state, it animates to the new state internally (usin
|color| colors the section| Colors.red|
|gradient| You can use any [Gradient](https://api.flutter.dev/flutter/dart-ui/Gradient-class.html) here. such as [LinearGradient](https://api.flutter.dev/flutter/painting/LinearGradient-class.html) or [RadialGradient](https://api.flutter.dev/flutter/painting/RadialGradient-class.html) (you have to provide either `color` or `gradient`)|null|
|radius| the width radius of each section|40|
|radialOffset| radial translation (in logical pixels) applied to the whole section, moving it outward along its center angle. Useful for "exploded" pie charts. Ignored when there is only a single section (360 degrees)|0|
|showTitle| determines whether to show or hide the titles on each section|true|
|titleStyle| TextStyle of the titles| TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold)|
|title| title of the section| value|
Expand Down
42 changes: 42 additions & 0 deletions test/chart/pie_chart/pie_chart_data_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -274,4 +274,46 @@ void main() {
expect(mid.toRadius, 50);
});
});

group('PieChartSectionData', () {
test('equality', () {
final a =
PieChartSectionData(value: 10, color: Colors.red, radialOffset: 8);
final b =
PieChartSectionData(value: 10, color: Colors.red, radialOffset: 8);
expect(a == b, true);

expect(
a == PieChartSectionData(value: 10, color: Colors.red, radialOffset: 0),
false,
);
});

test('copyWith', () {
final original = PieChartSectionData(value: 10, color: Colors.red);
expect(original.radialOffset, 0);

final withOffset = original.copyWith(radialOffset: 12);
expect(withOffset.radialOffset, 12);
expect(withOffset.value, 10);

expect(original.copyWith() == original, true);
});

test('lerp', () {
final a =
PieChartSectionData(value: 10, color: Colors.red, radialOffset: 0);
final b =
PieChartSectionData(value: 10, color: Colors.red, radialOffset: 20);

final atZero = PieChartSectionData.lerp(a, b, 0);
expect(atZero.radialOffset, 0);

final atOne = PieChartSectionData.lerp(a, b, 1);
expect(atOne.radialOffset, 20);

final mid = PieChartSectionData.lerp(a, b, 0.5);
expect(mid.radialOffset, 10);
});
});
}
Loading
Loading