From 0252a59a849731d449876fe43ff197de727cd74e Mon Sep 17 00:00:00 2001 From: Harsh Yadav Date: Mon, 11 May 2026 11:29:14 +0530 Subject: [PATCH] FEATURE: Add aboveChartBoxArea to TooltipDirection for BarTouchTooltipData --- .../samples/bar/bar_chart_sample9.dart | 151 +++++ .../presentation/samples/chart_samples.dart | 2 + lib/src/chart/bar_chart/bar_chart_data.dart | 549 +++++++++--------- .../chart/bar_chart/bar_chart_painter.dart | 97 ++-- 4 files changed, 476 insertions(+), 323 deletions(-) create mode 100644 example/lib/presentation/samples/bar/bar_chart_sample9.dart diff --git a/example/lib/presentation/samples/bar/bar_chart_sample9.dart b/example/lib/presentation/samples/bar/bar_chart_sample9.dart new file mode 100644 index 000000000..8fd60ebb7 --- /dev/null +++ b/example/lib/presentation/samples/bar/bar_chart_sample9.dart @@ -0,0 +1,151 @@ +import 'package:fl_chart/fl_chart.dart'; +import 'package:fl_chart_app/presentation/resources/app_resources.dart'; +import 'package:flutter/material.dart'; + +/// This sample demonstrates the [TooltipDirection.aboveChartBoxArea] feature, +/// which always draws the tooltip above the entire chart box area, +/// regardless of the bar rod's position or value. +class BarChartSample9 extends StatelessWidget { + const BarChartSample9({super.key}); + + @override + Widget build(BuildContext context) { + return AspectRatio( + aspectRatio: 1, + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const Text( + 'Tooltip above chart box area', + style: TextStyle( + color: AppColors.contentColorWhite, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + Expanded( + child: BarChart( + BarChartData( + maxY: 20, + minY: 0, + barTouchData: BarTouchData( + enabled: true, + touchTooltipData: BarTouchTooltipData( + direction: TooltipDirection.aboveChartBoxArea, + getTooltipColor: (group) => AppColors.contentColorCyan, + getTooltipItem: (group, groupIndex, rod, rodIndex) { + return BarTooltipItem( + rod.toY.toStringAsFixed(1), + const TextStyle( + color: AppColors.contentColorBlack, + fontWeight: FontWeight.bold, + ), + ); + }, + ), + ), + titlesData: FlTitlesData( + show: true, + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + getTitlesWidget: (value, meta) { + const days = ['M', 'T', 'W', 'T', 'F', 'S', 'S']; + return Text( + days[value.toInt() % days.length], + style: const TextStyle( + color: AppColors.contentColorWhite, + fontWeight: FontWeight.bold, + ), + ); + }, + ), + ), + leftTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + topTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 40, + getTitlesWidget: (value, meta) => + const SizedBox.shrink(), + ), + ), + rightTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + ), + borderData: FlBorderData(show: false), + gridData: const FlGridData(show: false), + barGroups: [ + BarChartGroupData( + x: 0, + showingTooltipIndicators: [0], + barRods: [ + BarChartRodData( + toY: 18, color: AppColors.contentColorCyan), + ], + ), + BarChartGroupData( + x: 1, + showingTooltipIndicators: [0], + barRods: [ + BarChartRodData( + toY: 5, color: AppColors.contentColorCyan), + ], + ), + BarChartGroupData( + x: 2, + showingTooltipIndicators: [0], + barRods: [ + BarChartRodData( + toY: 14, color: AppColors.contentColorCyan), + ], + ), + BarChartGroupData( + x: 3, + showingTooltipIndicators: [0], + barRods: [ + BarChartRodData( + toY: 9, color: AppColors.contentColorCyan), + ], + ), + BarChartGroupData( + x: 4, + showingTooltipIndicators: [0], + barRods: [ + BarChartRodData( + toY: 19, color: AppColors.contentColorCyan), + ], + ), + BarChartGroupData( + x: 5, + showingTooltipIndicators: [0], + barRods: [ + BarChartRodData( + toY: 3, color: AppColors.contentColorCyan), + ], + ), + BarChartGroupData( + x: 6, + showingTooltipIndicators: [0], + barRods: [ + BarChartRodData( + toY: 12, color: AppColors.contentColorCyan), + ], + ), + ], + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/example/lib/presentation/samples/chart_samples.dart b/example/lib/presentation/samples/chart_samples.dart index 1f2d34c84..5ebbcee77 100644 --- a/example/lib/presentation/samples/chart_samples.dart +++ b/example/lib/presentation/samples/chart_samples.dart @@ -12,6 +12,7 @@ import 'bar/bar_chart_sample5.dart'; import 'bar/bar_chart_sample6.dart'; import 'bar/bar_chart_sample7.dart'; import 'bar/bar_chart_sample8.dart'; +import 'bar/bar_chart_sample9.dart'; import 'chart_sample.dart'; import 'gauge/gauge_chart_sample2.dart'; import 'line/line_chart_sample1.dart'; @@ -61,6 +62,7 @@ class ChartSamples { BarChartSample(6, (context) => const BarChartSample6()), BarChartSample(7, (context) => BarChartSample7()), BarChartSample(8, (context) => BarChartSample8()), + BarChartSample(9, (context) => const BarChartSample9()), ], ChartType.pie: [ PieChartSample(1, (context) => const PieChartSample1()), diff --git a/lib/src/chart/bar_chart/bar_chart_data.dart b/lib/src/chart/bar_chart/bar_chart_data.dart index b99cdaf2a..1cc51d57e 100644 --- a/lib/src/chart/bar_chart/bar_chart_data.dart +++ b/lib/src/chart/bar_chart/bar_chart_data.dart @@ -49,23 +49,20 @@ class BarChartData extends AxisChartData with EquatableMixin { ExtraLinesData? extraLinesData, super.rotationQuarterTurns, this.errorIndicatorData = const FlErrorIndicatorData(), - }) : barGroups = barGroups ?? const [], - groupsSpace = groupsSpace ?? 16, - alignment = alignment ?? BarChartAlignment.spaceEvenly, - barTouchData = barTouchData ?? const BarTouchData(), - super( - titlesData: titlesData ?? - const FlTitlesData( - topTitles: AxisTitles(), - ), - gridData: gridData ?? const FlGridData(), - rangeAnnotations: rangeAnnotations ?? const RangeAnnotations(), - extraLinesData: extraLinesData ?? const ExtraLinesData(), - minX: 0, - maxX: 1, - maxY: maxY ?? double.nan, - minY: minY ?? double.nan, - ); + }) : barGroups = barGroups ?? const [], + groupsSpace = groupsSpace ?? 16, + alignment = alignment ?? BarChartAlignment.spaceEvenly, + barTouchData = barTouchData ?? const BarTouchData(), + super( + titlesData: titlesData ?? const FlTitlesData(topTitles: AxisTitles()), + gridData: gridData ?? const FlGridData(), + rangeAnnotations: rangeAnnotations ?? const RangeAnnotations(), + extraLinesData: extraLinesData ?? const ExtraLinesData(), + minX: 0, + maxX: 1, + maxY: maxY ?? double.nan, + minY: minY ?? double.nan, + ); /// [BarChart] draws [barGroups] that each of them contains a list of [BarChartRodData]. final List barGroups; @@ -82,7 +79,7 @@ class BarChartData extends AxisChartData with EquatableMixin { /// Holds data for showing error (threshold) indicators on the spots in /// the different [BarChartGroupData.barRods] final FlErrorIndicatorData - errorIndicatorData; + errorIndicatorData; /// Copies current [BarChartData] to a new [BarChartData], /// and replaces provided values. @@ -102,25 +99,24 @@ class BarChartData extends AxisChartData with EquatableMixin { ExtraLinesData? extraLinesData, int? rotationQuarterTurns, FlErrorIndicatorData? - errorIndicatorData, - }) => - BarChartData( - barGroups: barGroups ?? this.barGroups, - groupsSpace: groupsSpace ?? this.groupsSpace, - alignment: alignment ?? this.alignment, - titlesData: titlesData ?? this.titlesData, - rangeAnnotations: rangeAnnotations ?? this.rangeAnnotations, - barTouchData: barTouchData ?? this.barTouchData, - gridData: gridData ?? this.gridData, - borderData: borderData ?? this.borderData, - maxY: maxY ?? this.maxY, - minY: minY ?? this.minY, - baselineY: baselineY ?? this.baselineY, - backgroundColor: backgroundColor ?? this.backgroundColor, - extraLinesData: extraLinesData ?? this.extraLinesData, - rotationQuarterTurns: rotationQuarterTurns ?? this.rotationQuarterTurns, - errorIndicatorData: errorIndicatorData ?? this.errorIndicatorData, - ); + errorIndicatorData, + }) => BarChartData( + barGroups: barGroups ?? this.barGroups, + groupsSpace: groupsSpace ?? this.groupsSpace, + alignment: alignment ?? this.alignment, + titlesData: titlesData ?? this.titlesData, + rangeAnnotations: rangeAnnotations ?? this.rangeAnnotations, + barTouchData: barTouchData ?? this.barTouchData, + gridData: gridData ?? this.gridData, + borderData: borderData ?? this.borderData, + maxY: maxY ?? this.maxY, + minY: minY ?? this.minY, + baselineY: baselineY ?? this.baselineY, + backgroundColor: backgroundColor ?? this.backgroundColor, + extraLinesData: extraLinesData ?? this.extraLinesData, + rotationQuarterTurns: rotationQuarterTurns ?? this.rotationQuarterTurns, + errorIndicatorData: errorIndicatorData ?? this.errorIndicatorData, + ); /// Lerps a [BaseChartData] based on [t] value, check [Tween.lerp]. @override @@ -131,8 +127,11 @@ class BarChartData extends AxisChartData with EquatableMixin { groupsSpace: lerpDouble(a.groupsSpace, b.groupsSpace, t), alignment: b.alignment, titlesData: FlTitlesData.lerp(a.titlesData, b.titlesData, t), - rangeAnnotations: - RangeAnnotations.lerp(a.rangeAnnotations, b.rangeAnnotations, t), + rangeAnnotations: RangeAnnotations.lerp( + a.rangeAnnotations, + b.rangeAnnotations, + t, + ), barTouchData: b.barTouchData, gridData: FlGridData.lerp(a.gridData, b.gridData, t), borderData: FlBorderData.lerp(a.borderData, b.borderData, t), @@ -140,8 +139,11 @@ class BarChartData extends AxisChartData with EquatableMixin { minY: lerpDouble(a.minY, b.minY, t), baselineY: lerpDouble(a.baselineY, b.baselineY, t), backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), - extraLinesData: - ExtraLinesData.lerp(a.extraLinesData, b.extraLinesData, t), + extraLinesData: ExtraLinesData.lerp( + a.extraLinesData, + b.extraLinesData, + t, + ), rotationQuarterTurns: b.rotationQuarterTurns, errorIndicatorData: FlErrorIndicatorData.lerp( a.errorIndicatorData, @@ -157,22 +159,22 @@ class BarChartData extends AxisChartData with EquatableMixin { /// Used for equality check, see [EquatableMixin]. @override List get props => [ - barGroups, - groupsSpace, - alignment, - titlesData, - barTouchData, - maxY, - minY, - baselineY, - gridData, - borderData, - rangeAnnotations, - backgroundColor, - extraLinesData, - rotationQuarterTurns, - errorIndicatorData, - ]; + barGroups, + groupsSpace, + alignment, + titlesData, + barTouchData, + maxY, + minY, + baselineY, + gridData, + borderData, + rangeAnnotations, + backgroundColor, + extraLinesData, + rotationQuarterTurns, + errorIndicatorData, + ]; } /// defines arrangement of [barGroups], check [MainAxisAlignment] for more details. @@ -205,10 +207,10 @@ class BarChartGroupData with EquatableMixin { List? barRods, double? barsSpace, List? showingTooltipIndicators, - }) : groupVertically = groupVertically ?? false, - barRods = barRods ?? const [], - barsSpace = barsSpace ?? 2, - showingTooltipIndicators = showingTooltipIndicators ?? const []; + }) : groupVertically = groupVertically ?? false, + barRods = barRods ?? const [], + barsSpace = barsSpace ?? 2, + showingTooltipIndicators = showingTooltipIndicators ?? const []; /// Order along the x axis in which titles, and titles only, will be shown. /// @@ -260,43 +262,41 @@ class BarChartGroupData with EquatableMixin { List? barRods, double? barsSpace, List? showingTooltipIndicators, - }) => - BarChartGroupData( - x: x ?? this.x, - groupVertically: groupVertically ?? this.groupVertically, - barRods: barRods ?? this.barRods, - barsSpace: barsSpace ?? this.barsSpace, - showingTooltipIndicators: - showingTooltipIndicators ?? this.showingTooltipIndicators, - ); + }) => BarChartGroupData( + x: x ?? this.x, + groupVertically: groupVertically ?? this.groupVertically, + barRods: barRods ?? this.barRods, + barsSpace: barsSpace ?? this.barsSpace, + showingTooltipIndicators: + showingTooltipIndicators ?? this.showingTooltipIndicators, + ); /// Lerps a [BarChartGroupData] based on [t] value, check [Tween.lerp]. static BarChartGroupData lerp( BarChartGroupData a, BarChartGroupData b, double t, - ) => - BarChartGroupData( - x: (a.x + (b.x - a.x) * t).round(), - groupVertically: b.groupVertically, - barRods: lerpBarChartRodDataList(a.barRods, b.barRods, t), - barsSpace: lerpDouble(a.barsSpace, b.barsSpace, t), - showingTooltipIndicators: lerpIntList( - a.showingTooltipIndicators, - b.showingTooltipIndicators, - t, - ), - ); + ) => BarChartGroupData( + x: (a.x + (b.x - a.x) * t).round(), + groupVertically: b.groupVertically, + barRods: lerpBarChartRodDataList(a.barRods, b.barRods, t), + barsSpace: lerpDouble(a.barsSpace, b.barsSpace, t), + showingTooltipIndicators: lerpIntList( + a.showingTooltipIndicators, + b.showingTooltipIndicators, + t, + ), + ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ - x, - groupVertically, - barRods, - barsSpace, - showingTooltipIndicators, - ]; + x, + groupVertically, + barRods, + barsSpace, + showingTooltipIndicators, + ]; } /// Holds data about rendering each rod (or bar) in the [BarChart]. @@ -341,14 +341,14 @@ class BarChartRodData with EquatableMixin { BackgroundBarChartRodData? backDrawRodData, List? rodStackItems, this.label = const BarChartRodLabel(show: false), - }) : fromY = fromY ?? 0, - color = - color ?? ((color == null && gradient == null) ? Colors.cyan : null), - width = width ?? 8, - borderRadius = Utils().normalizeBorderRadius(borderRadius, width ?? 8), - borderSide = Utils().normalizeBorderSide(borderSide, width ?? 8), - backDrawRodData = backDrawRodData ?? BackgroundBarChartRodData(), - rodStackItems = rodStackItems ?? const []; + }) : fromY = fromY ?? 0, + color = + color ?? ((color == null && gradient == null) ? Colors.cyan : null), + width = width ?? 8, + borderRadius = Utils().normalizeBorderRadius(borderRadius, width ?? 8), + borderSide = Utils().normalizeBorderSide(borderSide, width ?? 8), + backDrawRodData = backDrawRodData ?? BackgroundBarChartRodData(), + rodStackItems = rodStackItems ?? const []; /// [BarChart] renders rods vertically from [fromY]. final double fromY; @@ -417,21 +417,20 @@ class BarChartRodData with EquatableMixin { BackgroundBarChartRodData? backDrawRodData, List? rodStackItems, BarChartRodLabel? label, - }) => - BarChartRodData( - fromY: fromY ?? this.fromY, - toY: toY ?? this.toY, - toYErrorRange: toYErrorRange ?? this.toYErrorRange, - color: color ?? this.color, - gradient: gradient ?? this.gradient, - width: width ?? this.width, - borderRadius: borderRadius ?? this.borderRadius, - borderDashArray: borderDashArray, - borderSide: borderSide ?? this.borderSide, - backDrawRodData: backDrawRodData ?? this.backDrawRodData, - rodStackItems: rodStackItems ?? this.rodStackItems, - label: label ?? this.label, - ); + }) => BarChartRodData( + fromY: fromY ?? this.fromY, + toY: toY ?? this.toY, + toYErrorRange: toYErrorRange ?? this.toYErrorRange, + color: color ?? this.color, + gradient: gradient ?? this.gradient, + width: width ?? this.width, + borderRadius: borderRadius ?? this.borderRadius, + borderDashArray: borderDashArray, + borderSide: borderSide ?? this.borderSide, + backDrawRodData: backDrawRodData ?? this.backDrawRodData, + rodStackItems: rodStackItems ?? this.rodStackItems, + label: label ?? this.label, + ); /// Lerps a [BarChartRodData] based on [t] value, check [Tween.lerp]. static BarChartRodData lerp(BarChartRodData a, BarChartRodData b, double t) => @@ -450,27 +449,30 @@ class BarChartRodData with EquatableMixin { b.backDrawRodData, t, ), - rodStackItems: - lerpBarChartRodStackList(a.rodStackItems, b.rodStackItems, t), + rodStackItems: lerpBarChartRodStackList( + a.rodStackItems, + b.rodStackItems, + t, + ), label: BarChartRodLabel.lerpBarChartRodLabel(a.label, b.label, t), ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ - fromY, - toY, - toYErrorRange, - width, - borderRadius, - borderDashArray, - borderSide, - backDrawRodData, - rodStackItems, - color, - gradient, - label, - ]; + fromY, + toY, + toYErrorRange, + width, + borderRadius, + borderDashArray, + borderSide, + backDrawRodData, + rodStackItems, + color, + gradient, + label, + ]; } /// A colored section of Stacked Chart rod item @@ -501,9 +503,9 @@ class BarChartRodStackItem with EquatableMixin { this.labelStyle, this.borderSide = Utils.defaultBorderSide, }) : assert( - color != null || gradient != null, - 'You must provide either a color or gradient', - ); + color != null || gradient != null, + 'You must provide either a color or gradient', + ); final String? label; final TextStyle? labelStyle; @@ -532,37 +534,42 @@ class BarChartRodStackItem with EquatableMixin { String? label, TextStyle? labelStyle, BorderSide? borderSide, - }) => - BarChartRodStackItem( - fromY ?? this.fromY, - toY ?? this.toY, - color ?? this.color, - gradient: gradient ?? this.gradient, - label: label ?? this.label, - labelStyle: labelStyle ?? this.labelStyle, - borderSide: borderSide ?? this.borderSide, - ); + }) => BarChartRodStackItem( + fromY ?? this.fromY, + toY ?? this.toY, + color ?? this.color, + gradient: gradient ?? this.gradient, + label: label ?? this.label, + labelStyle: labelStyle ?? this.labelStyle, + borderSide: borderSide ?? this.borderSide, + ); /// Lerps a [BarChartRodStackItem] based on [t] value, check [Tween.lerp]. static BarChartRodStackItem lerp( BarChartRodStackItem a, BarChartRodStackItem b, double t, - ) => - BarChartRodStackItem( - lerpDouble(a.fromY, b.fromY, t)!, - lerpDouble(a.toY, b.toY, t)!, - Color.lerp(a.color, b.color, t), - gradient: Gradient.lerp(a.gradient, b.gradient, t), - label: b.label, - labelStyle: b.labelStyle, - borderSide: BorderSide.lerp(a.borderSide, b.borderSide, t), - ); + ) => BarChartRodStackItem( + lerpDouble(a.fromY, b.fromY, t)!, + lerpDouble(a.toY, b.toY, t)!, + Color.lerp(a.color, b.color, t), + gradient: Gradient.lerp(a.gradient, b.gradient, t), + label: b.label, + labelStyle: b.labelStyle, + borderSide: BorderSide.lerp(a.borderSide, b.borderSide, t), + ); /// Used for equality check, see [EquatableMixin]. @override - List get props => - [fromY, toY, color, gradient, label, labelStyle, borderSide]; + List get props => [ + fromY, + toY, + color, + gradient, + label, + labelStyle, + borderSide, + ]; } /// Holds values to draw a rod in rear of the main rod. @@ -581,11 +588,12 @@ class BackgroundBarChartRodData with EquatableMixin { bool? show, Color? color, this.gradient, - }) : fromY = fromY ?? 0, - toY = toY ?? 0, - show = show ?? false, - color = color ?? - ((color == null && gradient == null) ? Colors.blueGrey : null); + }) : fromY = fromY ?? 0, + toY = toY ?? 0, + show = show ?? false, + color = + color ?? + ((color == null && gradient == null) ? Colors.blueGrey : null); /// Determines to show or hide this final bool show; @@ -611,24 +619,17 @@ class BackgroundBarChartRodData with EquatableMixin { BackgroundBarChartRodData a, BackgroundBarChartRodData b, double t, - ) => - BackgroundBarChartRodData( - fromY: lerpDouble(a.fromY, b.fromY, t), - toY: lerpDouble(a.toY, b.toY, t), - color: Color.lerp(a.color, b.color, t), - gradient: Gradient.lerp(a.gradient, b.gradient, t), - show: b.show, - ); + ) => BackgroundBarChartRodData( + fromY: lerpDouble(a.fromY, b.fromY, t), + toY: lerpDouble(a.toY, b.toY, t), + color: Color.lerp(a.color, b.color, t), + gradient: Gradient.lerp(a.gradient, b.gradient, t), + show: b.show, + ); /// Used for equality check, see [EquatableMixin]. @override - List get props => [ - show, - fromY, - toY, - color, - gradient, - ]; + List get props => [show, fromY, toY, color, gradient]; } /// Holds data to handle touch events, and touch responses in the [BarChart]. @@ -662,16 +663,16 @@ class BarTouchData extends FlTouchData with EquatableMixin { EdgeInsets? touchExtraThreshold, bool? allowTouchBarBackDraw, bool? handleBuiltInTouches, - }) : touchTooltipData = touchTooltipData ?? const BarTouchTooltipData(), - touchExtraThreshold = touchExtraThreshold ?? const EdgeInsets.all(4), - allowTouchBarBackDraw = allowTouchBarBackDraw ?? false, - handleBuiltInTouches = handleBuiltInTouches ?? true, - super( - enabled ?? true, - touchCallback, - mouseCursorResolver, - longPressDuration, - ); + }) : touchTooltipData = touchTooltipData ?? const BarTouchTooltipData(), + touchExtraThreshold = touchExtraThreshold ?? const EdgeInsets.all(4), + allowTouchBarBackDraw = allowTouchBarBackDraw ?? false, + handleBuiltInTouches = handleBuiltInTouches ?? true, + super( + enabled ?? true, + touchCallback, + mouseCursorResolver, + longPressDuration, + ); /// Configs of how touch tooltip popup. final BarTouchTooltipData touchTooltipData; @@ -697,31 +698,29 @@ class BarTouchData extends FlTouchData with EquatableMixin { EdgeInsets? touchExtraThreshold, bool? allowTouchBarBackDraw, bool? handleBuiltInTouches, - }) => - BarTouchData( - enabled: enabled ?? this.enabled, - touchCallback: touchCallback ?? this.touchCallback, - mouseCursorResolver: mouseCursorResolver ?? this.mouseCursorResolver, - longPressDuration: longPressDuration ?? this.longPressDuration, - touchTooltipData: touchTooltipData ?? this.touchTooltipData, - touchExtraThreshold: touchExtraThreshold ?? this.touchExtraThreshold, - allowTouchBarBackDraw: - allowTouchBarBackDraw ?? this.allowTouchBarBackDraw, - handleBuiltInTouches: handleBuiltInTouches ?? this.handleBuiltInTouches, - ); + }) => BarTouchData( + enabled: enabled ?? this.enabled, + touchCallback: touchCallback ?? this.touchCallback, + mouseCursorResolver: mouseCursorResolver ?? this.mouseCursorResolver, + longPressDuration: longPressDuration ?? this.longPressDuration, + touchTooltipData: touchTooltipData ?? this.touchTooltipData, + touchExtraThreshold: touchExtraThreshold ?? this.touchExtraThreshold, + allowTouchBarBackDraw: allowTouchBarBackDraw ?? this.allowTouchBarBackDraw, + handleBuiltInTouches: handleBuiltInTouches ?? this.handleBuiltInTouches, + ); /// Used for equality check, see [EquatableMixin]. @override List get props => [ - enabled, - touchCallback, - mouseCursorResolver, - longPressDuration, - touchTooltipData, - touchExtraThreshold, - allowTouchBarBackDraw, - handleBuiltInTouches, - ]; + enabled, + touchCallback, + mouseCursorResolver, + longPressDuration, + touchTooltipData, + touchExtraThreshold, + allowTouchBarBackDraw, + handleBuiltInTouches, + ]; } /// Controls showing tooltip on top or bottom. @@ -734,6 +733,9 @@ enum TooltipDirection { /// Tooltip always shows on bottom. bottom, + + /// Tooltip always shows above the chart box area (above the entire chart, not just the rod). + aboveChartBoxArea, } /// Holds representation data for showing tooltip popup on top of rods. @@ -768,22 +770,23 @@ class BarTouchTooltipData with EquatableMixin { TooltipDirection? direction, double? rotateAngle, BorderSide? tooltipBorder, - }) : _tooltipBorderRadius = tooltipBorderRadius, - tooltipPadding = tooltipPadding ?? - const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - tooltipMargin = tooltipMargin ?? 16, - tooltipHorizontalAlignment = - tooltipHorizontalAlignment ?? FLHorizontalAlignment.center, - tooltipHorizontalOffset = tooltipHorizontalOffset ?? 0, - maxContentWidth = maxContentWidth ?? 120, - getTooltipItem = getTooltipItem ?? defaultBarTooltipItem, - getTooltipColor = getTooltipColor ?? defaultBarTooltipColor, - fitInsideHorizontally = fitInsideHorizontally ?? false, - fitInsideVertically = fitInsideVertically ?? false, - direction = direction ?? TooltipDirection.auto, - rotateAngle = rotateAngle ?? 0.0, - tooltipBorder = tooltipBorder ?? BorderSide.none, - super(); + }) : _tooltipBorderRadius = tooltipBorderRadius, + tooltipPadding = + tooltipPadding ?? + const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + tooltipMargin = tooltipMargin ?? 16, + tooltipHorizontalAlignment = + tooltipHorizontalAlignment ?? FLHorizontalAlignment.center, + tooltipHorizontalOffset = tooltipHorizontalOffset ?? 0, + maxContentWidth = maxContentWidth ?? 120, + getTooltipItem = getTooltipItem ?? defaultBarTooltipItem, + getTooltipColor = getTooltipColor ?? defaultBarTooltipColor, + fitInsideHorizontally = fitInsideHorizontally ?? false, + fitInsideVertically = fitInsideVertically ?? false, + direction = direction ?? TooltipDirection.auto, + rotateAngle = rotateAngle ?? 0.0, + tooltipBorder = tooltipBorder ?? BorderSide.none, + super(); /// Sets a rounded radius for the tooltip. final BorderRadius? _tooltipBorderRadius; @@ -831,19 +834,19 @@ class BarTouchTooltipData with EquatableMixin { /// Used for equality check, see [EquatableMixin]. @override List get props => [ - _tooltipBorderRadius, - tooltipPadding, - tooltipMargin, - tooltipHorizontalAlignment, - tooltipHorizontalOffset, - maxContentWidth, - getTooltipItem, - fitInsideHorizontally, - fitInsideVertically, - rotateAngle, - tooltipBorder, - getTooltipColor, - ]; + _tooltipBorderRadius, + tooltipPadding, + tooltipMargin, + tooltipHorizontalAlignment, + tooltipHorizontalOffset, + maxContentWidth, + getTooltipItem, + fitInsideHorizontally, + fitInsideVertically, + rotateAngle, + tooltipBorder, + getTooltipColor, + ]; } /// Provides a [BarTooltipItem] for showing content inside the [BarTouchTooltipData]. @@ -851,12 +854,13 @@ class BarTouchTooltipData with EquatableMixin { /// You can override [BarTouchTooltipData.getTooltipItem], it gives you /// [group], [groupIndex], [rod], and [rodIndex] that touch happened on, /// then you should and pass your custom [BarTooltipItem] to show inside the tooltip popup. -typedef GetBarTooltipItem = BarTooltipItem? Function( - BarChartGroupData group, - int groupIndex, - BarChartRodData rod, - int rodIndex, -); +typedef GetBarTooltipItem = + BarTooltipItem? Function( + BarChartGroupData group, + int groupIndex, + BarChartRodData rod, + int rodIndex, + ); /// Default implementation for [BarTouchTooltipData.getTooltipItem]. BarTooltipItem? defaultBarTooltipItem( @@ -904,12 +908,12 @@ class BarTooltipItem with EquatableMixin { /// Used for equality check, see [EquatableMixin]. @override List get props => [ - text, - textStyle, - textAlign, - textDirection, - children, - ]; + text, + textStyle, + textAlign, + textDirection, + children, + ]; } //// Provides a [Color] to show different background color for each rod @@ -917,9 +921,7 @@ class BarTooltipItem with EquatableMixin { /// You can override [BarTouchTooltipData.getTooltipColor], it gives you /// [group] that touch happened on, then you should and pass your custom [Color] to set background color /// of tooltip popup. -typedef GetBarTooltipColor = Color Function( - BarChartGroupData group, -); +typedef GetBarTooltipColor = Color Function(BarChartGroupData group); /// Default implementation for [BarTouchTooltipData.getTooltipColor]. Color defaultBarTooltipColor(BarChartGroupData group) => @@ -947,12 +949,11 @@ class BarTouchResponse extends AxisBaseTouchResponse { Offset? touchLocation, Offset? touchChartCoordinate, BarTouchedSpot? spot, - }) => - BarTouchResponse( - touchLocation: touchLocation ?? this.touchLocation, - touchChartCoordinate: touchChartCoordinate ?? this.touchChartCoordinate, - spot: spot ?? this.spot, - ); + }) => BarTouchResponse( + touchLocation: touchLocation ?? this.touchLocation, + touchChartCoordinate: touchChartCoordinate ?? this.touchChartCoordinate, + spot: spot ?? this.spot, + ); } /// It gives you information about the touched spot. @@ -990,15 +991,15 @@ class BarTouchedSpot extends TouchedSpot with EquatableMixin { /// Used for equality check, see [EquatableMixin]. @override List get props => [ - touchedBarGroup, - touchedBarGroupIndex, - touchedRodData, - touchedRodDataIndex, - touchedStackItem, - touchedStackItemIndex, - spot, - offset, - ]; + touchedBarGroup, + touchedBarGroupIndex, + touchedRodData, + touchedRodDataIndex, + touchedStackItem, + touchedStackItemIndex, + spot, + offset, + ]; } /// It is the input of the [GetSpotRangeErrorPainter] callback in @@ -1033,12 +1034,7 @@ class BarChartSpotErrorRangeCallbackInput final int barRodIndex; @override - List get props => [ - group, - groupIndex, - rod, - barRodIndex, - ]; + List get props => [group, groupIndex, rod, barRodIndex]; } /// Label configuration for a bar chart rod. @@ -1080,15 +1076,14 @@ class BarChartRodLabel extends FlLabel { double? angle, TextDirection? textDirection, Offset? offset, - }) => - BarChartRodLabel( - show: show ?? this.show, - text: text ?? this.text, - style: style ?? this.style, - angle: angle ?? this.angle, - textDirection: textDirection ?? this.textDirection, - offset: offset ?? this.offset, - ); + }) => BarChartRodLabel( + show: show ?? this.show, + text: text ?? this.text, + style: style ?? this.style, + angle: angle ?? this.angle, + textDirection: textDirection ?? this.textDirection, + offset: offset ?? this.offset, + ); @override List get props => [show, text, style, angle, textDirection, offset]; @@ -1097,7 +1092,7 @@ class BarChartRodLabel extends FlLabel { /// It lerps a [BarChartData] to another [BarChartData] (handles animation for updating values) class BarChartDataTween extends Tween { BarChartDataTween({required BarChartData begin, required BarChartData end}) - : super(begin: begin, end: end); + : super(begin: begin, end: end); /// Lerps a [BarChartData] based on [t] value, check [Tween.lerp]. @override diff --git a/lib/src/chart/bar_chart/bar_chart_painter.dart b/lib/src/chart/bar_chart/bar_chart_painter.dart index 3dac6feca..7dcfb2355 100644 --- a/lib/src/chart/bar_chart/bar_chart_painter.dart +++ b/lib/src/chart/bar_chart/bar_chart_painter.dart @@ -56,10 +56,7 @@ class BarChartPainter extends AxisChartPainter { if (holder.chartVirtualRect != null) { final canvasRect = Offset.zero & canvasWrapper.size; canvasWrapper - ..saveLayer( - canvasRect, - _clipPaint, - ) + ..saveLayer(canvasRect, _clipPaint) ..clipRect(canvasRect); } @@ -84,12 +81,7 @@ class BarChartPainter extends AxisChartPainter { ); if (!data.extraLinesData.extraLinesOnTop) { - super.drawHorizontalLines( - context, - canvasWrapper, - holder, - usableSize, - ); + super.drawHorizontalLines(context, canvasWrapper, holder, usableSize); } drawBars(canvasWrapper, _groupBarsPosition!, holder); @@ -98,12 +90,7 @@ class BarChartPainter extends AxisChartPainter { drawErrorIndicatorData(canvasWrapper, _groupBarsPosition!, holder); if (data.extraLinesData.extraLinesOnTop) { - super.drawHorizontalLines( - context, - canvasWrapper, - holder, - usableSize, - ); + super.drawHorizontalLines(context, canvasWrapper, holder, usableSize); } if (holder.chartVirtualRect != null) { @@ -260,8 +247,11 @@ class BarChartPainter extends AxisChartPainter { if (barRod.toY != barRod.fromY) { if (barRod.toY > barRod.fromY) { // positive - final bottom = - getPixelY(max(data.minY, barRod.fromY), viewSize, holder); + final bottom = getPixelY( + max(data.minY, barRod.fromY), + viewSize, + holder, + ); final top = min( getPixelY(barRod.toY, viewSize, holder), bottom - cornerHeight, @@ -279,8 +269,11 @@ class BarChartPainter extends AxisChartPainter { ); } else { // negative - final top = - getPixelY(min(data.maxY, barRod.fromY), viewSize, holder); + final top = getPixelY( + min(data.maxY, barRod.fromY), + viewSize, + holder, + ); final bottom = max( getPixelY(barRod.toY, viewSize, holder), top + cornerHeight, @@ -405,9 +398,7 @@ class BarChartPainter extends AxisChartPainter { final borderPath = Path()..addRRect(barRRect); canvasWrapper.drawPath( - borderPath.toDashedPath( - barRod.borderDashArray, - ), + borderPath.toDashedPath(barRod.borderDashArray), _barStrokePaint, ); } @@ -437,8 +428,10 @@ class BarChartPainter extends AxisChartPainter { final labelStyle = rodLabel.style; if (labelText.isEmpty) continue; - final effectiveStyle = - Utils().getThemeAwareTextStyle(context, labelStyle); + final effectiveStyle = Utils().getThemeAwareTextStyle( + context, + labelStyle, + ); final textSpan = TextSpan(text: labelText, style: effectiveStyle); final textPainter = TextPainter( @@ -510,12 +503,7 @@ class BarChartPainter extends AxisChartPainter { ) - y; - final relativeErrorPixelsRect = Rect.fromLTRB( - 0, - top, - 0, - bottom, - ); + final relativeErrorPixelsRect = Rect.fromLTRB(0, top, 0, bottom); final painter = errorIndicatorData.painter( BarChartSpotErrorRangeCallbackInput( @@ -614,14 +602,17 @@ class BarChartPainter extends AxisChartPainter { var barTopY = min(barToYPixel.dy, barFromYPixel.dy); var barBottomY = max(barToYPixel.dy, barFromYPixel.dy); final drawTooltipOnTop = tooltipData.direction == TooltipDirection.top || + tooltipData.direction == TooltipDirection.aboveChartBoxArea || (tooltipData.direction == TooltipDirection.auto && showOnRodData.isUpward()); // Shift tooltip anchor to avoid overlapping with rod label final rodLabel = showOnRodData.label; if (rodLabel.show && rodLabel.text.isNotEmpty) { - final labelStyle = - Utils().getThemeAwareTextStyle(context, rodLabel.style); + final labelStyle = Utils().getThemeAwareTextStyle( + context, + rodLabel.style, + ); final labelSpan = TextSpan(text: rodLabel.text, style: labelStyle); final labelPainter = TextPainter( text: labelSpan, @@ -647,9 +638,12 @@ class BarChartPainter extends AxisChartPainter { return; } - final tooltipTop = drawTooltipOnTop - ? barTopY - tooltipHeight - tooltipData.tooltipMargin - : barBottomY + tooltipData.tooltipMargin; + final tooltipTop = + tooltipData.direction == TooltipDirection.aboveChartBoxArea + ? -tooltipHeight - tooltipData.tooltipMargin + : drawTooltipOnTop + ? barTopY - tooltipHeight - tooltipData.tooltipMargin + : barBottomY + tooltipData.tooltipMargin; final tooltipLeft = getTooltipLeft( barToYPixel.dx, @@ -723,12 +717,16 @@ class BarChartPainter extends AxisChartPainter { _bgTouchTooltipPaint.color = tooltipData.getTooltipColor(showOnBarGroup); final rotateAngle = tooltipData.rotateAngle; - final rectRotationOffset = - Offset(0, Utils().calculateRotationOffset(rect.size, rotateAngle).dy); + final rectRotationOffset = Offset( + 0, + Utils().calculateRotationOffset(rect.size, rotateAngle).dy, + ); final rectDrawOffset = Offset(roundedRect.left, roundedRect.top); - final textRotationOffset = - Utils().calculateRotationOffset(tp.size, rotateAngle); + final textRotationOffset = Utils().calculateRotationOffset( + tp.size, + rotateAngle, + ); /// draw the texts one by one in below of each other final top = tooltipData.tooltipPadding.top; @@ -847,8 +845,11 @@ class BarChartPainter extends AxisChartPainter { if (_groupBarsPosition == null) { final groupsX = data.calculateGroupsX(viewSize.width); - _groupBarsPosition = - calculateGroupAndBarsPosition(viewSize, groupsX, data.barGroups); + _groupBarsPosition = calculateGroupAndBarsPosition( + viewSize, + groupsX, + data.barGroups, + ); } /// Find the nearest barRod @@ -930,10 +931,14 @@ class BarChartPainter extends AxisChartPainter { if (isXInTouchBounds && isYInTouchBounds) { final nearestGroup = targetData.barGroups[i]; final nearestBarRod = nearestGroup.barRods[j]; - final nearestSpot = - FlSpot(nearestGroup.x.toDouble(), nearestBarRod.toY); - final nearestSpotPos = - Offset(barX, getPixelY(nearestSpot.y, viewSize, holder)); + final nearestSpot = FlSpot( + nearestGroup.x.toDouble(), + nearestBarRod.toY, + ); + final nearestSpotPos = Offset( + barX, + getPixelY(nearestSpot.y, viewSize, holder), + ); var touchedStackIndex = -1; BarChartRodStackItem? touchedStack;