From 85726027125ec234579c0a78f32063677c528f0d Mon Sep 17 00:00:00 2001 From: nitin-janyani Date: Thu, 19 Dec 2024 09:27:05 +0530 Subject: [PATCH] Add touch handler for Pie Chart --- .../samples/pie/pie_char_sample4.dart | 292 ++++++++++++++++++ .../base/base_chart/base_chart_data.dart | 17 + lib/src/chart/pie_chart/pie_chart_data.dart | 4 + .../chart/pie_chart/pie_chart_renderer.dart | 7 + 4 files changed, 320 insertions(+) create mode 100644 example/lib/presentation/samples/pie/pie_char_sample4.dart diff --git a/example/lib/presentation/samples/pie/pie_char_sample4.dart b/example/lib/presentation/samples/pie/pie_char_sample4.dart new file mode 100644 index 000000000..adb31969e --- /dev/null +++ b/example/lib/presentation/samples/pie/pie_char_sample4.dart @@ -0,0 +1,292 @@ +import 'package:fl_chart_app/presentation/resources/app_resources.dart'; +import 'package:fl_chart_app/presentation/widgets/indicator.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; + +class PieChartSample4 extends StatefulWidget { + const PieChartSample4({super.key}); + + @override + State createState() => PieChartSample4State(); +} + +class PieChartSample4State extends State { + Offset? hoveredOffset; + int innerTouchedIndex = -1; + int outerTouchedIndex = -1; + + @override + Widget build(BuildContext context) { + return AspectRatio( + aspectRatio: 1.3, + child: Column( + children: [ + const SizedBox( + height: 28, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Indicator( + color: AppColors.contentColorBlue, + text: 'One', + isSquare: false, + size: + innerTouchedIndex == 0 || outerTouchedIndex == 0 ? 18 : 16, + textColor: innerTouchedIndex == 0 || outerTouchedIndex == 0 + ? AppColors.mainTextColor1 + : AppColors.mainTextColor3, + ), + Indicator( + color: AppColors.contentColorYellow, + text: 'Two', + isSquare: false, + size: + innerTouchedIndex == 1 || outerTouchedIndex == 1 ? 18 : 16, + textColor: innerTouchedIndex == 1 || outerTouchedIndex == 1 + ? AppColors.mainTextColor1 + : AppColors.mainTextColor3, + ), + Indicator( + color: AppColors.contentColorPink, + text: 'Three', + isSquare: false, + size: + innerTouchedIndex == 2 || outerTouchedIndex == 2 ? 18 : 16, + textColor: innerTouchedIndex == 2 || outerTouchedIndex == 2 + ? AppColors.mainTextColor1 + : AppColors.mainTextColor3, + ), + Indicator( + color: AppColors.contentColorGreen, + text: 'Four', + isSquare: false, + size: + innerTouchedIndex == 3 || outerTouchedIndex == 3 ? 18 : 16, + textColor: innerTouchedIndex == 3 || outerTouchedIndex == 3 + ? AppColors.mainTextColor1 + : AppColors.mainTextColor3, + ), + ], + ), + const SizedBox( + height: 18, + ), + Expanded( + child: AspectRatio( + aspectRatio: 1, + child: MouseRegion( + onHover: (event) { + setState(() { + hoveredOffset = event.localPosition; + }); + }, + child: Stack( + children: [ + Positioned.fill( + child: PieChart( + PieChartData( + pieTouchData: PieTouchData( + externalTouchPosition: hoveredOffset, + externalTouchCallback: (pieTouchResponse) { + int? newTouchedIndex = pieTouchResponse + ?.touchedSection?.touchedSectionIndex; + WidgetsBinding.instance + .addPostFrameCallback((timeStamp) { + if (mounted && + newTouchedIndex != innerTouchedIndex) { + setState(() { + innerTouchedIndex = newTouchedIndex ?? -1; + }); + } + }); + }, + ), + startDegreeOffset: 180, + borderData: FlBorderData( + show: false, + ), + sectionsSpace: 1, + centerSpaceRadius: 0, + sections: showingInnerSections(), + ), + ), + ), + Positioned.fill( + child: PieChart( + PieChartData( + pieTouchData: PieTouchData( + externalTouchPosition: hoveredOffset, + externalTouchCallback: (pieTouchResponse) { + int? newTouchedIndex = pieTouchResponse + ?.touchedSection?.touchedSectionIndex; + WidgetsBinding.instance + .addPostFrameCallback((timeStamp) { + if (mounted && + newTouchedIndex != outerTouchedIndex) { + setState(() { + outerTouchedIndex = newTouchedIndex ?? -1; + }); + } + }); + }, + ), + startDegreeOffset: 180, + borderData: FlBorderData( + show: false, + ), + sectionsSpace: 1, + centerSpaceRadius: 50, + sections: showingOuterSections(), + ), + ), + ), + ], + ), + )), + ), + ], + ), + ); + } + + List showingInnerSections() { + return List.generate( + 4, + (i) { + final isTouched = i == innerTouchedIndex; + const color0 = AppColors.contentColorBlue; + const color1 = AppColors.contentColorYellow; + const color2 = AppColors.contentColorPink; + const color3 = AppColors.contentColorGreen; + + switch (i) { + case 0: + return PieChartSectionData( + color: color0, + value: 25, + title: '', + radius: 45, + titlePositionPercentageOffset: 0.55, + borderSide: isTouched + ? const BorderSide( + color: AppColors.contentColorWhite, width: 6) + : BorderSide( + color: AppColors.contentColorWhite.withOpacity(0)), + ); + case 1: + return PieChartSectionData( + color: color1, + value: 30, + title: '', + radius: 45, + titlePositionPercentageOffset: 0.55, + borderSide: isTouched + ? const BorderSide( + color: AppColors.contentColorWhite, width: 6) + : BorderSide( + color: AppColors.contentColorWhite.withOpacity(0)), + ); + case 2: + return PieChartSectionData( + color: color2, + value: 25, + title: '', + radius: 45, + titlePositionPercentageOffset: 0.6, + borderSide: isTouched + ? const BorderSide( + color: AppColors.contentColorWhite, width: 6) + : BorderSide( + color: AppColors.contentColorWhite.withOpacity(0)), + ); + case 3: + return PieChartSectionData( + color: color3, + value: 40, + title: '', + radius: 45, + titlePositionPercentageOffset: 0.55, + borderSide: isTouched + ? const BorderSide( + color: AppColors.contentColorWhite, width: 6) + : BorderSide( + color: AppColors.contentColorWhite.withOpacity(0)), + ); + default: + throw Error(); + } + }, + ); + } + + List showingOuterSections() { + return List.generate( + 4, + (i) { + final isTouched = i == outerTouchedIndex; + const color0 = AppColors.contentColorBlue; + const color1 = AppColors.contentColorYellow; + const color2 = AppColors.contentColorPink; + const color3 = AppColors.contentColorGreen; + + switch (i) { + case 0: + return PieChartSectionData( + color: color0, + value: 25, + title: '', + radius: 30, + titlePositionPercentageOffset: 0.55, + borderSide: isTouched + ? const BorderSide( + color: AppColors.contentColorWhite, width: 6) + : BorderSide( + color: AppColors.contentColorWhite.withOpacity(0)), + ); + case 1: + return PieChartSectionData( + color: color1, + value: 25, + title: '', + radius: 15, + titlePositionPercentageOffset: 0.55, + borderSide: isTouched + ? const BorderSide( + color: AppColors.contentColorWhite, width: 6) + : BorderSide( + color: AppColors.contentColorWhite.withOpacity(0)), + ); + case 2: + return PieChartSectionData( + color: color2, + value: 25, + title: '', + radius: 10, + titlePositionPercentageOffset: 0.6, + borderSide: isTouched + ? const BorderSide( + color: AppColors.contentColorWhite, width: 6) + : BorderSide( + color: AppColors.contentColorWhite.withOpacity(0)), + ); + case 3: + return PieChartSectionData( + color: color3, + value: 25, + title: '', + radius: 20, + titlePositionPercentageOffset: 0.55, + borderSide: isTouched + ? const BorderSide( + color: AppColors.contentColorWhite, width: 6) + : BorderSide( + color: AppColors.contentColorWhite.withOpacity(0)), + ); + default: + throw Error(); + } + }, + ); + } +} diff --git a/lib/src/chart/base/base_chart/base_chart_data.dart b/lib/src/chart/base/base_chart/base_chart_data.dart index f6460678e..73a55117c 100644 --- a/lib/src/chart/base/base_chart/base_chart_data.dart +++ b/lib/src/chart/base/base_chart/base_chart_data.dart @@ -88,6 +88,8 @@ abstract class FlTouchData with EquatableMixin { this.touchCallback, this.mouseCursorResolver, this.longPressDuration, + this.externalTouchCallback, + this.externalTouchPosition, ); /// You can disable or enable the touch system using [enabled] flag, @@ -107,6 +109,14 @@ abstract class FlTouchData with EquatableMixin { /// default to 500 milliseconds refer to [kLongPressTimeout]. final Duration? longPressDuration; + /// If you will use an external touch handlers, you will need to pass the position. + final Offset? externalTouchPosition; + + /// [externalTouchCallback] notifies you about the happened touch/pointer events. + /// It gives you a [BaseTouchResponse] which is the chart specific type and contains information + /// about the elements that has touched. + final BaseExternalTouchCallback? externalTouchCallback; + /// Used for equality check, see [EquatableMixin]. @override List get props => [ @@ -114,6 +124,8 @@ abstract class FlTouchData with EquatableMixin { touchCallback, mouseCursorResolver, longPressDuration, + externalTouchCallback, + externalTouchPosition, ]; } @@ -185,6 +197,11 @@ typedef MouseCursorResolver = MouseCursor Function( R?, ); +/// Chart's touch callback. +typedef BaseExternalTouchCallback = void Function( + R?, +); + /// This class holds the touch response details of charts. abstract class BaseTouchResponse { const BaseTouchResponse(); diff --git a/lib/src/chart/pie_chart/pie_chart_data.dart b/lib/src/chart/pie_chart/pie_chart_data.dart index a6fa4cab0..03dee0b87 100644 --- a/lib/src/chart/pie_chart/pie_chart_data.dart +++ b/lib/src/chart/pie_chart/pie_chart_data.dart @@ -320,11 +320,15 @@ class PieTouchData extends FlTouchData with EquatableMixin { BaseTouchCallback? touchCallback, MouseCursorResolver? mouseCursorResolver, Duration? longPressDuration, + Offset? externalTouchPosition, + BaseExternalTouchCallback? externalTouchCallback, }) : super( enabled ?? true, touchCallback, mouseCursorResolver, longPressDuration, + externalTouchCallback, + externalTouchPosition, ); /// Used for equality check, see [EquatableMixin]. diff --git a/lib/src/chart/pie_chart/pie_chart_renderer.dart b/lib/src/chart/pie_chart/pie_chart_renderer.dart index 4683ca321..671023afe 100644 --- a/lib/src/chart/pie_chart/pie_chart_renderer.dart +++ b/lib/src/chart/pie_chart/pie_chart_renderer.dart @@ -36,6 +36,13 @@ class PieChartLeaf extends MultiChildRenderObjectWidget { ..targetData = targetData ..textScaler = MediaQuery.of(context).textScaler ..buildContext = context; + data.pieTouchData.externalTouchCallback?.call( + data.pieTouchData.externalTouchPosition != null + ? renderObject.getResponseAtLocation( + data.pieTouchData.externalTouchPosition!, + ) + : null, + ); } } // coverage:ignore-end