From 9b124419ddfb3d0ddf446a830e8ecc6bb568e50c Mon Sep 17 00:00:00 2001 From: Nilashish Roy Date: Wed, 11 Dec 2024 00:32:21 +0600 Subject: [PATCH] enhancement to support horizontal bar chart. #113 --- .../horizontal/bar_chart_hori_sample5.dart | 375 ++++++++++++++++++ .../horizontal/bar_chart_hori_sample6.dart | 186 +++++++++ 2 files changed, 561 insertions(+) create mode 100644 example/lib/presentation/samples/bar/horizontal/bar_chart_hori_sample5.dart create mode 100644 example/lib/presentation/samples/bar/horizontal/bar_chart_hori_sample6.dart diff --git a/example/lib/presentation/samples/bar/horizontal/bar_chart_hori_sample5.dart b/example/lib/presentation/samples/bar/horizontal/bar_chart_hori_sample5.dart new file mode 100644 index 000000000..23945a726 --- /dev/null +++ b/example/lib/presentation/samples/bar/horizontal/bar_chart_hori_sample5.dart @@ -0,0 +1,375 @@ +import 'package:fl_chart_app/presentation/resources/app_resources.dart'; +import 'package:fl_chart_app/util/app_utils.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; + +class BarChartHoriSample5 extends StatefulWidget { + const BarChartHoriSample5({super.key}); + + @override + State createState() => BarChartHoriSample5State(); +} + +class BarChartHoriSample5State extends State { + static const double barWidth = 22; + static const shadowOpacity = 0.2; + static const mainItems = >{ + 0: [2, 3, 2.5, 8], + 1: [-1.8, -2.7, -3, -6.5], + 2: [1.5, 2, 3.5, 6], + 3: [1.5, 1.5, 4, 6.5], + 4: [-2, -2, -5, -9], + 5: [-1.2, -1.5, -4.3, -10], + 6: [1.2, 4.8, 5, 5], + }; + int touchedIndex = -1; + + @override + void initState() { + super.initState(); + } + + Widget bottomTitles(double value, TitleMeta meta) { + const style = TextStyle(color: Colors.white, fontSize: 10); + String text; + switch (value.toInt()) { + case 0: + text = 'Mon'; + break; + case 1: + text = 'Tue'; + break; + case 2: + text = 'Wed'; + break; + case 3: + text = 'Thu'; + break; + case 4: + text = 'Fri'; + break; + case 5: + text = 'Sat'; + break; + case 6: + text = 'Sun'; + break; + default: + text = ''; + break; + } + return SideTitleWidget( + axisSide: meta.axisSide, + child: Transform.rotate( + // THIS TO ROTATE ALL THE TEXT + angle: -1.5708, + child: Text(text, style: style)), + ); + } + + Widget topTitles(double value, TitleMeta meta) { + const style = TextStyle(color: Colors.white, fontSize: 10); + String text; + switch (value.toInt()) { + case 0: + text = 'Mon'; + break; + case 1: + text = 'Tue'; + break; + case 2: + text = 'Wed'; + break; + case 3: + text = 'Thu'; + break; + case 4: + text = 'Fri'; + break; + case 5: + text = 'Sat'; + break; + case 6: + text = 'Sun'; + break; + default: + return Container(); + } + return SideTitleWidget( + axisSide: meta.axisSide, + child: Transform.rotate( + // THIS TO ROTATE ALL THE TEXT + angle: -1.5708, + child: Text(text, style: style)), + ); + } + + Widget leftTitles(double value, TitleMeta meta) { + const style = TextStyle(color: Colors.white, fontSize: 10); + String text; + if (value == 0) { + text = '0'; + } else { + text = '${value.toInt()}0k'; + } + return SideTitleWidget( + angle: AppUtils().degreeToRadian(value < 0 ? -45 : 45), + axisSide: meta.axisSide, + space: 4, + child: Transform.rotate( + // THIS TO ROTATE ALL THE TEXT + angle: -1.5708, + child: Text( + text, + style: style, + textAlign: TextAlign.center, + ), + ), + ); + } + + Widget rightTitles(double value, TitleMeta meta) { + const style = TextStyle(color: Colors.white, fontSize: 10); + String text; + if (value == 0) { + text = '0'; + } else { + text = '${value.toInt()}0k'; + } + return SideTitleWidget( + angle: AppUtils().degreeToRadian(90), + axisSide: meta.axisSide, + space: 0, + child: Transform.rotate( + // THIS TO ROTATE ALL THE TEXT + angle: 3.15, + child: Text( + text, + style: style, + textAlign: TextAlign.center, + ), + ), + ); + } + + BarChartGroupData generateGroup( + int x, + double value1, + double value2, + double value3, + double value4, + ) { + final isTop = value1 > 0; + final sum = value1 + value2 + value3 + value4; + final isTouched = touchedIndex == x; + return BarChartGroupData( + x: x, + groupVertically: true, + showingTooltipIndicators: isTouched ? [0] : [], + barRods: [ + BarChartRodData( + toY: sum, + width: barWidth, + borderRadius: isTop + ? const BorderRadius.only( + topLeft: Radius.circular(6), + topRight: Radius.circular(6), + ) + : const BorderRadius.only( + bottomLeft: Radius.circular(6), + bottomRight: Radius.circular(6), + ), + rodStackItems: [ + BarChartRodStackItem( + 0, + value1, + AppColors.contentColorGreen, + BorderSide( + color: Colors.white, + width: isTouched ? 2 : 0, + ), + ), + BarChartRodStackItem( + value1, + value1 + value2, + AppColors.contentColorYellow, + BorderSide( + color: Colors.white, + width: isTouched ? 2 : 0, + ), + ), + BarChartRodStackItem( + value1 + value2, + value1 + value2 + value3, + AppColors.contentColorPink, + BorderSide( + color: Colors.white, + width: isTouched ? 2 : 0, + ), + ), + BarChartRodStackItem( + value1 + value2 + value3, + value1 + value2 + value3 + value4, + AppColors.contentColorBlue, + BorderSide( + color: Colors.white, + width: isTouched ? 2 : 0, + ), + ), + ], + ), + BarChartRodData( + toY: -sum, + width: barWidth, + color: Colors.transparent, + borderRadius: isTop + ? const BorderRadius.only( + bottomLeft: Radius.circular(6), + bottomRight: Radius.circular(6), + ) + : const BorderRadius.only( + topLeft: Radius.circular(6), + topRight: Radius.circular(6), + ), + rodStackItems: [ + BarChartRodStackItem( + 0, + -value1, + AppColors.contentColorGreen.withOpacity(isTouched ? shadowOpacity * 2 : shadowOpacity), + const BorderSide(color: Colors.transparent), + ), + BarChartRodStackItem( + -value1, + -(value1 + value2), + AppColors.contentColorYellow.withOpacity(isTouched ? shadowOpacity * 2 : shadowOpacity), + const BorderSide(color: Colors.transparent), + ), + BarChartRodStackItem( + -(value1 + value2), + -(value1 + value2 + value3), + AppColors.contentColorPink.withOpacity(isTouched ? shadowOpacity * 2 : shadowOpacity), + const BorderSide(color: Colors.transparent), + ), + BarChartRodStackItem( + -(value1 + value2 + value3), + -(value1 + value2 + value3 + value4), + AppColors.contentColorBlue.withOpacity(isTouched ? shadowOpacity * 2 : shadowOpacity), + const BorderSide(color: Colors.transparent), + ), + ], + ), + ], + ); + } + + bool isShadowBar(int rodIndex) => rodIndex == 1; + + @override + Widget build(BuildContext context) { + return AspectRatio( + aspectRatio: 0.8, + child: Padding( + padding: const EdgeInsets.only(top: 16), + child: BarChart( + isHorizontal: true, + BarChartData( + alignment: BarChartAlignment.center, + maxY: 20, + minY: -20, + groupsSpace: 12, + barTouchData: BarTouchData( + handleBuiltInTouches: false, + touchTooltipData: BarTouchTooltipData( + rotateAngle: 270, + fitInsideVertically: true, + tooltipMargin: 8, + tooltipPadding: const EdgeInsets.all(10), + ), + touchCallback: (FlTouchEvent event, barTouchResponse) { + if (!event.isInterestedForInteractions || barTouchResponse == null || barTouchResponse.spot == null) { + setState(() { + touchedIndex = -1; + }); + return; + } + final rodIndex = barTouchResponse.spot!.touchedRodDataIndex; + if (isShadowBar(rodIndex)) { + setState(() { + touchedIndex = -1; + }); + return; + } + setState(() { + touchedIndex = barTouchResponse.spot!.touchedBarGroupIndex; + }); + }, + ), + titlesData: FlTitlesData( + show: true, + topTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 32, + getTitlesWidget: topTitles, + ), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 32, + getTitlesWidget: bottomTitles, + ), + ), + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + getTitlesWidget: leftTitles, + interval: 5, + reservedSize: 42, + ), + ), + rightTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + getTitlesWidget: rightTitles, + interval: 5, + reservedSize: 42, + ), + ), + ), + gridData: FlGridData( + show: true, + checkToShowHorizontalLine: (value) => value % 5 == 0, + getDrawingHorizontalLine: (value) { + if (value == 0) { + return FlLine( + color: AppColors.borderColor.withOpacity(0.1), + strokeWidth: 3, + ); + } + return FlLine( + color: AppColors.borderColor.withOpacity(0.05), + strokeWidth: 0.8, + ); + }, + ), + borderData: FlBorderData( + show: false, + ), + barGroups: mainItems.entries + .map( + (e) => generateGroup( + e.key, + e.value[0], + e.value[1], + e.value[2], + e.value[3], + ), + ) + .toList(), + ), + ), + ), + ); + } +} diff --git a/example/lib/presentation/samples/bar/horizontal/bar_chart_hori_sample6.dart b/example/lib/presentation/samples/bar/horizontal/bar_chart_hori_sample6.dart new file mode 100644 index 000000000..d340bf886 --- /dev/null +++ b/example/lib/presentation/samples/bar/horizontal/bar_chart_hori_sample6.dart @@ -0,0 +1,186 @@ +import 'package:fl_chart_app/presentation/resources/app_resources.dart'; +import 'package:fl_chart_app/presentation/widgets/legend_widget.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; + +class BarChartHoriSample6 extends StatelessWidget { + const BarChartHoriSample6({super.key}); + + final pilateColor = AppColors.contentColorPurple; + final cyclingColor = AppColors.contentColorCyan; + final quickWorkoutColor = AppColors.contentColorBlue; + final betweenSpace = 0.2; + + BarChartGroupData generateGroupData( + int x, + double pilates, + double quickWorkout, + double cycling, + ) { + return BarChartGroupData( + x: x, + groupVertically: true, + barRods: [ + BarChartRodData( + fromY: 0, + toY: pilates, + color: pilateColor, + width: 5, + ), + BarChartRodData( + fromY: pilates + betweenSpace, + toY: pilates + betweenSpace + quickWorkout, + color: quickWorkoutColor, + width: 5, + ), + BarChartRodData( + fromY: pilates + betweenSpace + quickWorkout + betweenSpace, + toY: pilates + betweenSpace + quickWorkout + betweenSpace + cycling, + color: cyclingColor, + width: 5, + ), + ], + ); + } + + Widget bottomTitles(double value, TitleMeta meta) { + const style = TextStyle(fontSize: 10); + String text; + switch (value.toInt()) { + case 0: + text = 'JAN'; + break; + case 1: + text = 'FEB'; + break; + case 2: + text = 'MAR'; + break; + case 3: + text = 'APR'; + break; + case 4: + text = 'MAY'; + break; + case 5: + text = 'JUN'; + break; + case 6: + text = 'JUL'; + break; + case 7: + text = 'AUG'; + break; + case 8: + text = 'SEP'; + break; + case 9: + text = 'OCT'; + break; + case 10: + text = 'NOV'; + break; + case 11: + text = 'DEC'; + break; + default: + text = ''; + } + return SideTitleWidget( + axisSide: meta.axisSide, + child: Transform.rotate( // THIS TO ROTATE ALL THE TEXT + angle: -1.5708, + child: Text(text, style: style)), + ); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Activity', + style: TextStyle( + color: AppColors.contentColorBlue, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 8), + LegendsListWidget( + legends: [ + Legend('Pilates', pilateColor), + Legend('Quick workouts', quickWorkoutColor), + Legend('Cycling', cyclingColor), + ], + ), + const SizedBox(height: 14), + AspectRatio( + aspectRatio: 2, + child: BarChart(isHorizontal: true, + BarChartData( + alignment: BarChartAlignment.spaceBetween, + titlesData: FlTitlesData( + leftTitles: const AxisTitles(), + rightTitles: const AxisTitles(), + topTitles: const AxisTitles(), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + getTitlesWidget: bottomTitles, + reservedSize: 20, + ), + ), + ), + barTouchData: BarTouchData(enabled: false), + borderData: FlBorderData(show: false), + gridData: const FlGridData(show: false), + barGroups: [ + generateGroupData(0, 2, 3, 2), + generateGroupData(1, 2, 5, 1.7), + generateGroupData(2, 1.3, 3.1, 2.8), + generateGroupData(3, 3.1, 4, 3.1), + generateGroupData(4, 0.8, 3.3, 3.4), + generateGroupData(5, 2, 5.6, 1.8), + generateGroupData(6, 1.3, 3.2, 2), + generateGroupData(7, 2.3, 3.2, 3), + generateGroupData(8, 2, 4.8, 2.5), + generateGroupData(9, 1.2, 3.2, 2.5), + generateGroupData(10, 1, 4.8, 3), + generateGroupData(11, 2, 4.4, 2.8), + ], + maxY: 11 + (betweenSpace * 3), + extraLinesData: ExtraLinesData( + horizontalLines: [ + HorizontalLine( + y: 3.3, + color: pilateColor, + strokeWidth: 1, + dashArray: [20, 4], + ), + HorizontalLine( + y: 8, + color: quickWorkoutColor, + strokeWidth: 1, + dashArray: [20, 4], + ), + HorizontalLine( + y: 11, + color: cyclingColor, + strokeWidth: 1, + dashArray: [20, 4], + ), + ], + ), + ), + ), + ), + ], + ), + ); + } +}