feat: display category/total amount in pie chart center
This commit is contained in:
@@ -24,6 +24,7 @@ class SpendingPieChart extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
final theme = Theme.of(context);
|
||||
final currencyFormatter = NumberFormat.currency(locale: 'en_US', symbol: '\$'); // Or your preferred locale/symbol
|
||||
|
||||
// Handle the case where there are no categories to display
|
||||
if (categories.isEmpty) {
|
||||
@@ -61,38 +62,48 @@ class SpendingPieChart extends StatelessWidget {
|
||||
// --- Pie Chart ---
|
||||
Expanded(
|
||||
flex: 5, // Give more space to the chart itself
|
||||
child: PieChart(
|
||||
PieChartData(
|
||||
// Handle touch events on the pie chart
|
||||
pieTouchData: PieTouchData(
|
||||
touchCallback: (FlTouchEvent event, pieTouchResponse) {
|
||||
// Ignore events not related to interaction
|
||||
if (!event.isInterestedForInteractions ||
|
||||
pieTouchResponse == null ||
|
||||
pieTouchResponse.touchedSection == null) {
|
||||
// If touch ends outside a section, deselect
|
||||
if (event is FlPanEndEvent || event is FlTapUpEvent) {
|
||||
if (selectedPieIndex != -1) {
|
||||
onSelectPieCategory(-1); // Deselect
|
||||
child: Stack( // Use Stack to overlay text on the chart center
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
PieChart(
|
||||
PieChartData(
|
||||
// Handle touch events on the pie chart
|
||||
pieTouchData: PieTouchData(
|
||||
touchCallback: (FlTouchEvent event, pieTouchResponse) {
|
||||
// Ignore events not related to interaction
|
||||
if (!event.isInterestedForInteractions ||
|
||||
pieTouchResponse == null ||
|
||||
pieTouchResponse.touchedSection == null) {
|
||||
// If touch ends outside a section, deselect
|
||||
if (event is FlPanEndEvent || event is FlTapUpEvent) {
|
||||
if (selectedPieIndex != -1) {
|
||||
onSelectPieCategory(-1); // Deselect
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Get the index of the touched section
|
||||
final touchedIndex = pieTouchResponse.touchedSection!.touchedSectionIndex;
|
||||
// Call the callback, toggling selection if the same slice is touched again
|
||||
onSelectPieCategory(touchedIndex == selectedPieIndex ? -1 : touchedIndex);
|
||||
},
|
||||
// Get the index of the touched section
|
||||
final touchedIndex = pieTouchResponse.touchedSection!.touchedSectionIndex;
|
||||
// Call the callback, toggling selection if the same slice is touched again
|
||||
onSelectPieCategory(touchedIndex == selectedPieIndex ? -1 : touchedIndex);
|
||||
},
|
||||
),
|
||||
borderData: FlBorderData(show: false), // No border around the chart
|
||||
sectionsSpace: 2, // Space between slices
|
||||
centerSpaceRadius: 50, // Radius of the center hole
|
||||
sections: _generatePieSections(context), // Generate slices data
|
||||
startDegreeOffset: -90, // Start chart from the top (12 o'clock)
|
||||
),
|
||||
// Optional animation when data changes
|
||||
swapAnimationDuration: const Duration(milliseconds: 250),
|
||||
swapAnimationCurve: Curves.easeInOut,
|
||||
),
|
||||
borderData: FlBorderData(show: false), // No border around the chart
|
||||
sectionsSpace: 2, // Space between slices
|
||||
centerSpaceRadius: 50, // Radius of the center hole
|
||||
sections: _generatePieSections(context), // Generate slices data
|
||||
startDegreeOffset: -90, // Start chart from the top (12 o'clock)
|
||||
),
|
||||
// Optional animation when data changes
|
||||
swapAnimationDuration: const Duration(milliseconds: 250),
|
||||
swapAnimationCurve: Curves.easeInOut,
|
||||
// --- Center Text (Displayed when a slice is selected) ---
|
||||
if (selectedPieIndex != -1)
|
||||
_buildCenterText(context, currencyFormatter)
|
||||
else // Optional: Display total or default text when nothing is selected
|
||||
_buildDefaultCenterText(context, currencyFormatter),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8), // Spacing between chart and legend
|
||||
@@ -116,6 +127,63 @@ class SpendingPieChart extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
// Builds the text displayed in the center when a slice is selected
|
||||
Widget _buildCenterText(BuildContext context, NumberFormat formatter) {
|
||||
final theme = Theme.of(context);
|
||||
final selectedCategory = categories[selectedPieIndex];
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
selectedCategory.name,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.textTheme.bodyLarge?.color?.withOpacity(0.8),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
formatter.format(selectedCategory.amount), // Format the amount as currency
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: selectedCategory.colorCode, // Use category color for amount
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Builds the default text displayed in the center when no slice is selected
|
||||
Widget _buildDefaultCenterText(BuildContext context, NumberFormat formatter) {
|
||||
final theme = Theme.of(context);
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Total', // Label for the total amount
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.textTheme.bodyLarge?.color?.withOpacity(0.7),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
formatter.format(totalExpenses), // Display total expenses
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.textTheme.bodyLarge?.color // Use default text color
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Builds a single item for the legend
|
||||
Widget _buildPieLegendItem(BuildContext context, int index) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
Reference in New Issue
Block a user